mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
Compare commits
113 Commits
canal-1.1.
...
canal-1.1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de501c85a1 | ||
|
|
670b7830a5 | ||
|
|
fdf150bd24 | ||
|
|
c66b6a0f0f | ||
|
|
a91aebafc2 | ||
|
|
35fcd56822 | ||
|
|
a63e982115 | ||
|
|
4bdf3d766f | ||
|
|
779ebad890 | ||
|
|
b348260288 | ||
|
|
3011c3292b | ||
|
|
94b8fdbbf4 | ||
|
|
1dd9934f9c | ||
|
|
3dec7c50ce | ||
|
|
ecd9ababa1 | ||
|
|
b0eaa156cc | ||
|
|
a70178afef | ||
|
|
114c3bd020 | ||
|
|
f9e19d46c4 | ||
|
|
661752096c | ||
|
|
100b43a5db | ||
|
|
5e116de264 | ||
|
|
a1091368fb | ||
|
|
71b7c0b868 | ||
|
|
7c79714528 | ||
|
|
8d84521767 | ||
|
|
14ba358525 | ||
|
|
7ea1468a8d | ||
|
|
f72ac9a3bb | ||
|
|
f100aeda39 | ||
|
|
953a0d0583 | ||
|
|
2afe7debda | ||
|
|
d8737a5228 | ||
|
|
150a13824b | ||
|
|
830e149d0e | ||
|
|
d9e259dde4 | ||
|
|
9e6adff942 | ||
|
|
82a78546ab | ||
|
|
19c35c0b08 | ||
|
|
ade2a54369 | ||
|
|
dffbc437a6 | ||
|
|
b5697a366a | ||
|
|
0469d28c05 | ||
|
|
d5f44dd76f | ||
|
|
354d64069f | ||
|
|
3465866278 | ||
|
|
840148fbf6 | ||
|
|
34b5f9c927 | ||
|
|
75ee65f9e8 | ||
|
|
035ef26542 | ||
|
|
4ef45b9a1d | ||
|
|
990fa9498a | ||
|
|
276a2b988a | ||
|
|
ef72cddfe8 | ||
|
|
d8d3246e30 | ||
|
|
bba800eb96 | ||
|
|
ff71bf515a | ||
|
|
6ef48591a1 | ||
|
|
a7526dbf4a | ||
|
|
51d94efc9c | ||
|
|
833cc55e26 | ||
|
|
689366248d | ||
|
|
3acdffb297 | ||
|
|
fbf249580f | ||
|
|
d2e3e19831 | ||
|
|
0523b68c26 | ||
|
|
995a33a645 | ||
|
|
5c4b1f6e63 | ||
|
|
ba5865683f | ||
|
|
89a4943349 | ||
|
|
35e0e882c1 | ||
|
|
0197482618 | ||
|
|
72bad4d54c | ||
|
|
48de904ee1 | ||
|
|
f6a931d8c6 | ||
|
|
277a273ebc | ||
|
|
bd5110b951 | ||
|
|
aeecf87490 | ||
|
|
eac3f5ce5f | ||
|
|
706ee88914 | ||
|
|
7c63e81e01 | ||
|
|
99a1123e01 | ||
|
|
24d898d87a | ||
|
|
5650b56bed | ||
|
|
547cbf82c8 | ||
|
|
03ee9c7ecf | ||
|
|
ac738dae05 | ||
|
|
dda8081984 | ||
|
|
f0fc37c43b | ||
|
|
5a99de1fa9 | ||
|
|
ce956c980d | ||
|
|
c045dc244e | ||
|
|
1b11aa1d21 | ||
|
|
dac642f744 | ||
|
|
19e164b599 | ||
|
|
339ecdd817 | ||
|
|
5fe7ae1613 | ||
|
|
65f2695963 | ||
|
|
cf5e42442b | ||
|
|
e7b678d5e0 | ||
|
|
f04447837c | ||
|
|
b7d2ad2e18 | ||
|
|
f1710f1c7f | ||
|
|
37094447b1 | ||
|
|
b97d377d83 | ||
|
|
46b6528273 | ||
|
|
6c3b3acde0 | ||
|
|
91b036a4fc | ||
|
|
67cd0e5fca | ||
|
|
9dfa5da7bc | ||
|
|
9bda60bf89 | ||
|
|
9aaf20fb5e | ||
|
|
3b25b3b342 |
13
.github/issue_template.md
vendored
Normal file
13
.github/issue_template.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
### environment
|
||||||
|
|
||||||
|
* canal version
|
||||||
|
* mysql version
|
||||||
|
|
||||||
|
### Issue Description
|
||||||
|
|
||||||
|
|
||||||
|
### Steps to reproduce
|
||||||
|
|
||||||
|
### Expected behaviour
|
||||||
|
|
||||||
|
### Actual behaviour
|
||||||
@@ -75,7 +75,7 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
|
|||||||
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/TableMetaTSDB">TableMetaTSDB</a></li>
|
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/TableMetaTSDB">TableMetaTSDB</a></li>
|
||||||
<li><a href="http://alibaba.github.com/canal/release.html">ReleaseNotes</a></li>
|
<li><a href="http://alibaba.github.com/canal/release.html">ReleaseNotes</a></li>
|
||||||
<li><a href="https://github.com/alibaba/canal/releases">Download</a></li>
|
<li><a href="https://github.com/alibaba/canal/releases">Download</a></li>
|
||||||
<li><a class="internal present" href="/alibaba/canal/wiki/FAQ">FAQ</a></li>
|
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/FAQ">FAQ</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h1>多语言业务</h1>
|
<h1>多语言业务</h1>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal.protocol</artifactId>
|
<artifactId>canal.protocol</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>joda-time</groupId>
|
<groupId>joda-time</groupId>
|
||||||
@@ -26,11 +26,6 @@
|
|||||||
<artifactId>druid</artifactId>
|
<artifactId>druid</artifactId>
|
||||||
<version>1.1.9</version>
|
<version>1.1.9</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>mysql</groupId>
|
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
|
||||||
<version>5.1.40</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ public class Dml implements Serializable {
|
|||||||
private String destination; // 对应canal的实例或者MQ的topic
|
private String destination; // 对应canal的实例或者MQ的topic
|
||||||
private String database; // 数据库或schema
|
private String database; // 数据库或schema
|
||||||
private String table; // 表名
|
private String table; // 表名
|
||||||
|
private List<String> pkNames;
|
||||||
|
private Boolean isDdl;
|
||||||
private String type; // 类型: INSERT UPDATE DELETE
|
private String type; // 类型: INSERT UPDATE DELETE
|
||||||
// binlog executeTime
|
// binlog executeTime
|
||||||
private Long es; // 执行耗时
|
private Long es; // 执行耗时
|
||||||
@@ -50,6 +52,22 @@ public class Dml implements Serializable {
|
|||||||
this.table = table;
|
this.table = table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getPkNames() {
|
||||||
|
return pkNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPkNames(List<String> pkNames) {
|
||||||
|
this.pkNames = pkNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getIsDdl() {
|
||||||
|
return isDdl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsDdl(Boolean isDdl) {
|
||||||
|
this.isDdl = isDdl;
|
||||||
|
}
|
||||||
|
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
package com.alibaba.otter.canal.client.adapter.support;
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry;
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
import com.alibaba.otter.canal.protocol.FlatMessage;
|
import com.alibaba.otter.canal.protocol.FlatMessage;
|
||||||
@@ -55,6 +50,8 @@ public class MessageUtil {
|
|||||||
|
|
||||||
if (!rowChange.getIsDdl()) {
|
if (!rowChange.getIsDdl()) {
|
||||||
Set<String> updateSet = new HashSet<>();
|
Set<String> updateSet = new HashSet<>();
|
||||||
|
dml.setPkNames(new ArrayList<>());
|
||||||
|
int i = 0;
|
||||||
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
|
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
|
||||||
if (eventType != CanalEntry.EventType.INSERT && eventType != CanalEntry.EventType.UPDATE
|
if (eventType != CanalEntry.EventType.INSERT && eventType != CanalEntry.EventType.UPDATE
|
||||||
&& eventType != CanalEntry.EventType.DELETE) {
|
&& eventType != CanalEntry.EventType.DELETE) {
|
||||||
@@ -71,6 +68,11 @@ public class MessageUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (CanalEntry.Column column : columns) {
|
for (CanalEntry.Column column : columns) {
|
||||||
|
if (i == 0) {
|
||||||
|
if (column.getIsKey()) {
|
||||||
|
dml.getPkNames().add(column.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
row.put(column.getName(),
|
row.put(column.getName(),
|
||||||
JdbcTypeUtil.typeConvert(column.getName(),
|
JdbcTypeUtil.typeConvert(column.getName(),
|
||||||
column.getValue(),
|
column.getValue(),
|
||||||
@@ -101,6 +103,8 @@ public class MessageUtil {
|
|||||||
old.add(rowOld);
|
old.add(rowOld);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
if (!data.isEmpty()) {
|
if (!data.isEmpty()) {
|
||||||
dml.setData(data);
|
dml.setData(data);
|
||||||
@@ -134,13 +138,15 @@ public class MessageUtil {
|
|||||||
dml.setDestination(destination);
|
dml.setDestination(destination);
|
||||||
dml.setDatabase(flatMessage.getDatabase());
|
dml.setDatabase(flatMessage.getDatabase());
|
||||||
dml.setTable(flatMessage.getTable());
|
dml.setTable(flatMessage.getTable());
|
||||||
|
dml.setPkNames(flatMessage.getPkNames());
|
||||||
|
dml.setIsDdl(flatMessage.getIsDdl());
|
||||||
dml.setType(flatMessage.getType());
|
dml.setType(flatMessage.getType());
|
||||||
dml.setTs(flatMessage.getTs());
|
dml.setTs(flatMessage.getTs());
|
||||||
dml.setEs(flatMessage.getEs());
|
dml.setEs(flatMessage.getEs());
|
||||||
dml.setSql(flatMessage.getSql());
|
dml.setSql(flatMessage.getSql());
|
||||||
if (flatMessage.getSqlType() == null || flatMessage.getMysqlType() == null) {
|
// if (flatMessage.getSqlType() == null || flatMessage.getMysqlType() == null) {
|
||||||
throw new RuntimeException("SqlType or mysqlType is null");
|
// throw new RuntimeException("SqlType or mysqlType is null");
|
||||||
}
|
// }
|
||||||
List<Map<String, String>> data = flatMessage.getData();
|
List<Map<String, String>> data = flatMessage.getData();
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
dml.setData(changeRows(data, flatMessage.getSqlType(), flatMessage.getMysqlType()));
|
dml.setData(changeRows(data, flatMessage.getSqlType(), flatMessage.getMysqlType()));
|
||||||
|
|||||||
@@ -117,4 +117,23 @@ public class Util {
|
|||||||
|
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String cleanColumn(String column) {
|
||||||
|
if (column == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (column.contains("`")) {
|
||||||
|
column = column.replaceAll("`", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column.contains("'")) {
|
||||||
|
column = column.replaceAll("'", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column.contains("\"")) {
|
||||||
|
column = column.replaceAll("\"", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return column;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -75,8 +75,8 @@ public class ESAdapter implements OuterAdapter {
|
|||||||
// 过滤不匹配的key的配置
|
// 过滤不匹配的key的配置
|
||||||
esSyncConfigTmp.forEach((key, config) -> {
|
esSyncConfigTmp.forEach((key, config) -> {
|
||||||
if ((config.getOuterAdapterKey() == null && configuration.getKey() == null)
|
if ((config.getOuterAdapterKey() == null && configuration.getKey() == null)
|
||||||
|| (config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
|
|| (config.getOuterAdapterKey() != null
|
||||||
.equalsIgnoreCase(configuration.getKey()))) {
|
&& config.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) {
|
||||||
esSyncConfig.put(key, config);
|
esSyncConfig.put(key, config);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -98,15 +98,11 @@ public class ESAdapter implements OuterAdapter {
|
|||||||
}
|
}
|
||||||
String schema = matcher.group(2);
|
String schema = matcher.group(2);
|
||||||
|
|
||||||
schemaItem.getAliasTableItems()
|
schemaItem.getAliasTableItems().values().forEach(tableItem -> {
|
||||||
.values()
|
Map<String, ESSyncConfig> esSyncConfigMap = dbTableEsSyncConfig
|
||||||
.forEach(tableItem -> {
|
.computeIfAbsent(schema + "-" + tableItem.getTableName(), k -> new HashMap<>());
|
||||||
Map<String, ESSyncConfig> esSyncConfigMap = dbTableEsSyncConfig.computeIfAbsent(schema
|
esSyncConfigMap.put(configName, config);
|
||||||
+ "-"
|
});
|
||||||
+ tableItem.getTableName(),
|
|
||||||
k -> new HashMap<>());
|
|
||||||
esSyncConfigMap.put(configName, config);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> properties = configuration.getProperties();
|
Map<String, String> properties = configuration.getProperties();
|
||||||
@@ -140,7 +136,9 @@ public class ESAdapter implements OuterAdapter {
|
|||||||
String database = dml.getDatabase();
|
String database = dml.getDatabase();
|
||||||
String table = dml.getTable();
|
String table = dml.getTable();
|
||||||
Map<String, ESSyncConfig> configMap = dbTableEsSyncConfig.get(database + "-" + table);
|
Map<String, ESSyncConfig> configMap = dbTableEsSyncConfig.get(database + "-" + table);
|
||||||
esSyncService.sync(configMap.values(), dml);
|
if (configMap != null) {
|
||||||
|
esSyncService.sync(configMap.values(), dml);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.alibaba.otter.canal.client.adapter.es.config;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.yaml.snakeyaml.Yaml;
|
import org.yaml.snakeyaml.Yaml;
|
||||||
@@ -19,6 +20,7 @@ public class ESSyncConfigLoader {
|
|||||||
|
|
||||||
private static Logger logger = LoggerFactory.getLogger(ESSyncConfigLoader.class);
|
private static Logger logger = LoggerFactory.getLogger(ESSyncConfigLoader.class);
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public static synchronized Map<String, ESSyncConfig> load() {
|
public static synchronized Map<String, ESSyncConfig> load() {
|
||||||
logger.info("## Start loading es mapping config ... ");
|
logger.info("## Start loading es mapping config ... ");
|
||||||
|
|
||||||
@@ -26,7 +28,9 @@ public class ESSyncConfigLoader {
|
|||||||
|
|
||||||
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("es");
|
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("es");
|
||||||
configContentMap.forEach((fileName, content) -> {
|
configContentMap.forEach((fileName, content) -> {
|
||||||
ESSyncConfig config = new Yaml().loadAs(content, ESSyncConfig.class);
|
Map configMap = new Yaml().loadAs(content, Map.class); // yml自带的对象反射不是很稳定
|
||||||
|
JSONObject configJson = new JSONObject(configMap);
|
||||||
|
ESSyncConfig config = configJson.toJavaObject(ESSyncConfig.class);
|
||||||
try {
|
try {
|
||||||
config.validate();
|
config.validate();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ public class ESConfigMonitor {
|
|||||||
// 加载配置文件
|
// 加载配置文件
|
||||||
String configContent = MappingConfigsLoader
|
String configContent = MappingConfigsLoader
|
||||||
.loadConfig(adapterName + File.separator + file.getName());
|
.loadConfig(adapterName + File.separator + file.getName());
|
||||||
|
if (configContent == null) {
|
||||||
|
onFileDelete(file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
ESSyncConfig config = new Yaml().loadAs(configContent, ESSyncConfig.class);
|
ESSyncConfig config = new Yaml().loadAs(configContent, ESSyncConfig.class);
|
||||||
config.validate();
|
config.validate();
|
||||||
if (esAdapter.getEsSyncConfig().containsKey(file.getName())) {
|
if (esAdapter.getEsSyncConfig().containsKey(file.getName())) {
|
||||||
|
|||||||
@@ -100,9 +100,9 @@ public class ESSyncService {
|
|||||||
dml.getDestination(),
|
dml.getDestination(),
|
||||||
config.getEsMapping().get_index());
|
config.getEsMapping().get_index());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Throwable e) {
|
||||||
logger.error("sync error, es index: {}, DML : {}", config.getEsMapping().get_index(), dml);
|
logger.error("sync error, es index: {}, DML : {}", config.getEsMapping().get_index(), dml);
|
||||||
logger.error(e.getMessage(), e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import org.apache.commons.lang.StringUtils;
|
|||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.hbase.HBaseConfiguration;
|
import org.apache.hadoop.hbase.HBaseConfiguration;
|
||||||
import org.apache.hadoop.hbase.TableName;
|
import org.apache.hadoop.hbase.TableName;
|
||||||
import org.apache.hadoop.hbase.client.Connection;
|
|
||||||
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
|
||||||
import org.apache.hadoop.hbase.client.HTable;
|
import org.apache.hadoop.hbase.client.HTable;
|
||||||
import org.apache.hadoop.hbase.client.Result;
|
import org.apache.hadoop.hbase.client.Result;
|
||||||
import org.apache.hadoop.hbase.client.ResultScanner;
|
import org.apache.hadoop.hbase.client.ResultScanner;
|
||||||
@@ -30,11 +28,7 @@ import com.alibaba.otter.canal.client.adapter.hbase.monitor.HbaseConfigMonitor;
|
|||||||
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseEtlService;
|
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseEtlService;
|
||||||
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService;
|
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService;
|
||||||
import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate;
|
import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
import com.alibaba.otter.canal.client.adapter.support.*;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
|
||||||
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
|
|
||||||
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
|
|
||||||
import com.alibaba.otter.canal.client.adapter.support.SPI;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HBase外部适配器
|
* HBase外部适配器
|
||||||
@@ -50,7 +44,6 @@ public class HbaseAdapter implements OuterAdapter {
|
|||||||
private Map<String, MappingConfig> hbaseMapping = new ConcurrentHashMap<>(); // 文件名对应配置
|
private Map<String, MappingConfig> hbaseMapping = new ConcurrentHashMap<>(); // 文件名对应配置
|
||||||
private Map<String, Map<String, MappingConfig>> mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置
|
private Map<String, Map<String, MappingConfig>> mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置
|
||||||
|
|
||||||
private Connection conn;
|
|
||||||
private HbaseSyncService hbaseSyncService;
|
private HbaseSyncService hbaseSyncService;
|
||||||
private HbaseTemplate hbaseTemplate;
|
private HbaseTemplate hbaseTemplate;
|
||||||
|
|
||||||
@@ -90,8 +83,7 @@ public class HbaseAdapter implements OuterAdapter {
|
|||||||
|
|
||||||
Configuration hbaseConfig = HBaseConfiguration.create();
|
Configuration hbaseConfig = HBaseConfiguration.create();
|
||||||
properties.forEach(hbaseConfig::set);
|
properties.forEach(hbaseConfig::set);
|
||||||
conn = ConnectionFactory.createConnection(hbaseConfig);
|
hbaseTemplate = new HbaseTemplate(hbaseConfig);
|
||||||
hbaseTemplate = new HbaseTemplate(conn);
|
|
||||||
hbaseSyncService = new HbaseSyncService(hbaseTemplate);
|
hbaseSyncService = new HbaseSyncService(hbaseTemplate);
|
||||||
|
|
||||||
configMonitor = new HbaseConfigMonitor();
|
configMonitor = new HbaseConfigMonitor();
|
||||||
@@ -115,7 +107,9 @@ public class HbaseAdapter implements OuterAdapter {
|
|||||||
String database = dml.getDatabase();
|
String database = dml.getDatabase();
|
||||||
String table = dml.getTable();
|
String table = dml.getTable();
|
||||||
Map<String, MappingConfig> configMap = mappingConfigCache.get(destination + "." + database + "." + table);
|
Map<String, MappingConfig> configMap = mappingConfigCache.get(destination + "." + database + "." + table);
|
||||||
configMap.values().forEach(config -> hbaseSyncService.sync(config, dml));
|
if (configMap != null) {
|
||||||
|
configMap.values().forEach(config -> hbaseSyncService.sync(config, dml));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -172,7 +166,7 @@ public class HbaseAdapter implements OuterAdapter {
|
|||||||
String hbaseTable = config.getHbaseMapping().getHbaseTable();
|
String hbaseTable = config.getHbaseMapping().getHbaseTable();
|
||||||
long rowCount = 0L;
|
long rowCount = 0L;
|
||||||
try {
|
try {
|
||||||
HTable table = (HTable) conn.getTable(TableName.valueOf(hbaseTable));
|
HTable table = (HTable) hbaseTemplate.getConnection().getTable(TableName.valueOf(hbaseTable));
|
||||||
Scan scan = new Scan();
|
Scan scan = new Scan();
|
||||||
scan.setFilter(new FirstKeyOnlyFilter());
|
scan.setFilter(new FirstKeyOnlyFilter());
|
||||||
ResultScanner resultScanner = table.getScanner(scan);
|
ResultScanner resultScanner = table.getScanner(scan);
|
||||||
@@ -193,12 +187,10 @@ public class HbaseAdapter implements OuterAdapter {
|
|||||||
if (configMonitor != null) {
|
if (configMonitor != null) {
|
||||||
configMonitor.destroy();
|
configMonitor.destroy();
|
||||||
}
|
}
|
||||||
if (conn != null) {
|
try {
|
||||||
try {
|
hbaseTemplate.close();
|
||||||
conn.close();
|
} catch (IOException e) {
|
||||||
} catch (IOException e) {
|
throw new RuntimeException(e);
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.alibaba.otter.canal.client.adapter.hbase.config;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.yaml.snakeyaml.Yaml;
|
import org.yaml.snakeyaml.Yaml;
|
||||||
@@ -24,6 +25,7 @@ public class MappingConfigLoader {
|
|||||||
*
|
*
|
||||||
* @return 配置名/配置文件名--对象
|
* @return 配置名/配置文件名--对象
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public static Map<String, MappingConfig> load() {
|
public static Map<String, MappingConfig> load() {
|
||||||
logger.info("## Start loading hbase mapping config ... ");
|
logger.info("## Start loading hbase mapping config ... ");
|
||||||
|
|
||||||
@@ -31,7 +33,9 @@ public class MappingConfigLoader {
|
|||||||
|
|
||||||
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("hbase");
|
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("hbase");
|
||||||
configContentMap.forEach((fileName, content) -> {
|
configContentMap.forEach((fileName, content) -> {
|
||||||
MappingConfig config = new Yaml().loadAs(content, MappingConfig.class);
|
Map configMap = new Yaml().loadAs(content, Map.class); // yml自带的对象反射不是很稳定
|
||||||
|
JSONObject configJson = new JSONObject(configMap);
|
||||||
|
MappingConfig config = configJson.toJavaObject(MappingConfig.class);
|
||||||
try {
|
try {
|
||||||
config.validate();
|
config.validate();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -79,6 +79,10 @@ public class HbaseConfigMonitor {
|
|||||||
// 加载配置文件
|
// 加载配置文件
|
||||||
String configContent = MappingConfigsLoader
|
String configContent = MappingConfigsLoader
|
||||||
.loadConfig(adapterName + File.separator + file.getName());
|
.loadConfig(adapterName + File.separator + file.getName());
|
||||||
|
if (configContent == null) {
|
||||||
|
onFileDelete(file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
MappingConfig config = new Yaml().loadAs(configContent, MappingConfig.class);
|
MappingConfig config = new Yaml().loadAs(configContent, MappingConfig.class);
|
||||||
config.validate();
|
config.validate();
|
||||||
if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) {
|
if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) {
|
||||||
|
|||||||
@@ -29,22 +29,18 @@ public class HbaseSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void sync(MappingConfig config, Dml dml) {
|
public void sync(MappingConfig config, Dml dml) {
|
||||||
try {
|
if (config != null) {
|
||||||
if (config != null) {
|
String type = dml.getType();
|
||||||
String type = dml.getType();
|
if (type != null && type.equalsIgnoreCase("INSERT")) {
|
||||||
if (type != null && type.equalsIgnoreCase("INSERT")) {
|
insert(config, dml);
|
||||||
insert(config, dml);
|
} else if (type != null && type.equalsIgnoreCase("UPDATE")) {
|
||||||
} else if (type != null && type.equalsIgnoreCase("UPDATE")) {
|
update(config, dml);
|
||||||
update(config, dml);
|
} else if (type != null && type.equalsIgnoreCase("DELETE")) {
|
||||||
} else if (type != null && type.equalsIgnoreCase("DELETE")) {
|
delete(config, dml);
|
||||||
delete(config, dml);
|
}
|
||||||
}
|
if (logger.isDebugEnabled()) {
|
||||||
if (logger.isDebugEnabled()) {
|
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
||||||
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.hbase.HColumnDescriptor;
|
import org.apache.hadoop.hbase.HColumnDescriptor;
|
||||||
import org.apache.hadoop.hbase.HTableDescriptor;
|
import org.apache.hadoop.hbase.HTableDescriptor;
|
||||||
import org.apache.hadoop.hbase.TableName;
|
import org.apache.hadoop.hbase.TableName;
|
||||||
import org.apache.hadoop.hbase.client.Connection;
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
import org.apache.hadoop.hbase.client.Delete;
|
import org.apache.hadoop.hbase.client.Delete;
|
||||||
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
||||||
import org.apache.hadoop.hbase.client.HTable;
|
import org.apache.hadoop.hbase.client.HTable;
|
||||||
@@ -25,16 +27,33 @@ import org.slf4j.LoggerFactory;
|
|||||||
*/
|
*/
|
||||||
public class HbaseTemplate {
|
public class HbaseTemplate {
|
||||||
|
|
||||||
private Logger logger = LoggerFactory.getLogger(this.getClass());
|
private Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
private Connection conn;
|
private Configuration hbaseConfig; // hbase配置对象
|
||||||
|
private Connection conn; // hbase连接
|
||||||
|
|
||||||
public HbaseTemplate(Connection conn){
|
public HbaseTemplate(Configuration hbaseConfig){
|
||||||
this.conn = conn;
|
this.hbaseConfig = hbaseConfig;
|
||||||
|
initConn();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initConn() {
|
||||||
|
try {
|
||||||
|
this.conn = ConnectionFactory.createConnection(hbaseConfig);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Connection getConnection() {
|
||||||
|
if (conn == null || conn.isAborted() || conn.isClosed()) {
|
||||||
|
initConn();
|
||||||
|
}
|
||||||
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean tableExists(String tableName) {
|
public boolean tableExists(String tableName) {
|
||||||
try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) {
|
try (HBaseAdmin admin = (HBaseAdmin) getConnection().getAdmin()) {
|
||||||
|
|
||||||
return admin.tableExists(TableName.valueOf(tableName));
|
return admin.tableExists(TableName.valueOf(tableName));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -43,7 +62,7 @@ public class HbaseTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void createTable(String tableName, String... familyNames) {
|
public void createTable(String tableName, String... familyNames) {
|
||||||
try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) {
|
try (HBaseAdmin admin = (HBaseAdmin) getConnection().getAdmin()) {
|
||||||
|
|
||||||
HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(tableName));
|
HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(tableName));
|
||||||
// 添加列簇
|
// 添加列簇
|
||||||
@@ -60,7 +79,7 @@ public class HbaseTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void disableTable(String tableName) {
|
public void disableTable(String tableName) {
|
||||||
try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) {
|
try (HBaseAdmin admin = (HBaseAdmin) getConnection().getAdmin()) {
|
||||||
admin.disableTable(tableName);
|
admin.disableTable(tableName);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
@@ -69,7 +88,7 @@ public class HbaseTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void deleteTable(String tableName) {
|
public void deleteTable(String tableName) {
|
||||||
try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) {
|
try (HBaseAdmin admin = (HBaseAdmin) getConnection().getAdmin()) {
|
||||||
if (admin.isTableEnabled(tableName)) {
|
if (admin.isTableEnabled(tableName)) {
|
||||||
disableTable(tableName);
|
disableTable(tableName);
|
||||||
}
|
}
|
||||||
@@ -90,7 +109,7 @@ public class HbaseTemplate {
|
|||||||
public Boolean put(String tableName, HRow hRow) {
|
public Boolean put(String tableName, HRow hRow) {
|
||||||
boolean flag = false;
|
boolean flag = false;
|
||||||
try {
|
try {
|
||||||
HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));
|
HTable table = (HTable) getConnection().getTable(TableName.valueOf(tableName));
|
||||||
Put put = new Put(hRow.getRowKey());
|
Put put = new Put(hRow.getRowKey());
|
||||||
for (HRow.HCell hCell : hRow.getCells()) {
|
for (HRow.HCell hCell : hRow.getCells()) {
|
||||||
put.addColumn(Bytes.toBytes(hCell.getFamily()), Bytes.toBytes(hCell.getQualifier()), hCell.getValue());
|
put.addColumn(Bytes.toBytes(hCell.getFamily()), Bytes.toBytes(hCell.getQualifier()), hCell.getValue());
|
||||||
@@ -99,6 +118,7 @@ public class HbaseTemplate {
|
|||||||
flag = true;
|
flag = true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
return flag;
|
return flag;
|
||||||
|
|
||||||
@@ -114,7 +134,7 @@ public class HbaseTemplate {
|
|||||||
public Boolean puts(String tableName, List<HRow> rows) {
|
public Boolean puts(String tableName, List<HRow> rows) {
|
||||||
boolean flag = false;
|
boolean flag = false;
|
||||||
try {
|
try {
|
||||||
HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));
|
HTable table = (HTable) getConnection().getTable(TableName.valueOf(tableName));
|
||||||
List<Put> puts = new ArrayList<>();
|
List<Put> puts = new ArrayList<>();
|
||||||
for (HRow hRow : rows) {
|
for (HRow hRow : rows) {
|
||||||
Put put = new Put(hRow.getRowKey());
|
Put put = new Put(hRow.getRowKey());
|
||||||
@@ -131,6 +151,7 @@ public class HbaseTemplate {
|
|||||||
flag = true;
|
flag = true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
return flag;
|
return flag;
|
||||||
}
|
}
|
||||||
@@ -145,7 +166,7 @@ public class HbaseTemplate {
|
|||||||
public Boolean deletes(String tableName, Set<byte[]> rowKeys) {
|
public Boolean deletes(String tableName, Set<byte[]> rowKeys) {
|
||||||
boolean flag = false;
|
boolean flag = false;
|
||||||
try {
|
try {
|
||||||
HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));
|
HTable table = (HTable) getConnection().getTable(TableName.valueOf(tableName));
|
||||||
List<Delete> deletes = new ArrayList<>();
|
List<Delete> deletes = new ArrayList<>();
|
||||||
for (byte[] rowKey : rowKeys) {
|
for (byte[] rowKey : rowKeys) {
|
||||||
Delete delete = new Delete(rowKey);
|
Delete delete = new Delete(rowKey);
|
||||||
@@ -157,7 +178,14 @@ public class HbaseTemplate {
|
|||||||
flag = true;
|
flag = true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
return flag;
|
return flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (conn != null) {
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal.client</artifactId>
|
<artifactId>canal.client</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.yaml</groupId>
|
<groupId>org.yaml</groupId>
|
||||||
@@ -72,6 +72,11 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- jdbc -->
|
<!-- jdbc -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
<version>5.1.40</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
<artifactId>postgresql</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.alibaba.otter.canal.adapter.launcher.config;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.adapter.launcher.monitor.AdapterRemoteConfigMonitor;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap级别配置加载
|
||||||
|
*
|
||||||
|
* @author rewerma @ 2019-01-05
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class BootstrapConfiguration {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(BootstrapConfiguration.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private Environment env;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void loadRemoteConfig() {
|
||||||
|
try {
|
||||||
|
// 加载远程配置
|
||||||
|
String jdbcUrl = env.getProperty("canal.manager.jdbc.url");
|
||||||
|
if (StringUtils.isNotEmpty(jdbcUrl)) {
|
||||||
|
String jdbcUsername = env.getProperty("canal.manager.jdbc.username");
|
||||||
|
String jdbcPassword = env.getProperty("canal.manager.jdbc.password");
|
||||||
|
AdapterRemoteConfigMonitor configMonitor = new AdapterRemoteConfigMonitor(jdbcUrl,
|
||||||
|
jdbcUsername,
|
||||||
|
jdbcPassword);
|
||||||
|
configMonitor.loadRemoteConfig();
|
||||||
|
configMonitor.loadRemoteAdapterConfigs();
|
||||||
|
configMonitor.start(); // 启动监听
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package com.alibaba.otter.canal.adapter.launcher.loader;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
@@ -70,25 +69,30 @@ public abstract class AbstractCanalAdapterWorker {
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 等待所有适配器写入完成
|
// 等待所有适配器写入完成
|
||||||
// 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成
|
// 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成
|
||||||
futures.forEach(future -> {
|
RuntimeException exception = null;
|
||||||
|
for (Future<Boolean> future : futures) {
|
||||||
try {
|
try {
|
||||||
if (!future.get()) {
|
if (!future.get()) {
|
||||||
logger.error("Outer adapter write failed");
|
exception = new RuntimeException("Outer adapter sync failed! ");
|
||||||
}
|
}
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
} catch (Exception e) {
|
||||||
// ignore
|
exception = new RuntimeException(e);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
if (exception != null) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeOut(final List<FlatMessage> flatMessages) {
|
protected void writeOut(final List<FlatMessage> flatMessages) {
|
||||||
List<Future<Boolean>> futures = new ArrayList<>();
|
List<Future<Boolean>> futures = new ArrayList<>();
|
||||||
// 组间适配器并行运行
|
// 组间适配器并行运行
|
||||||
canalOuterAdapters.forEach(outerAdapters -> {
|
canalOuterAdapters.forEach(outerAdapters -> {
|
||||||
@@ -108,72 +112,77 @@ public abstract class AbstractCanalAdapterWorker {
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 等待所有适配器写入完成
|
// 等待所有适配器写入完成
|
||||||
// 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成
|
// 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成
|
||||||
futures.forEach(future -> {
|
RuntimeException exception = null;
|
||||||
|
for (Future<Boolean> future : futures) {
|
||||||
try {
|
try {
|
||||||
if (!future.get()) {
|
if (!future.get()) {
|
||||||
logger.error("Outer adapter write failed");
|
exception = new RuntimeException("Outer adapter sync failed! ");
|
||||||
}
|
}
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
} catch (Exception e) {
|
||||||
// ignore
|
exception = new RuntimeException(e);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
if (exception != null) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
protected void mqWriteOutData(int retry, long timeout, final boolean flatMessage, CanalMQConnector connector,
|
protected boolean mqWriteOutData(int retry, long timeout, int i, final boolean flatMessage,
|
||||||
ExecutorService workerExecutor) {
|
CanalMQConnector connector, ExecutorService workerExecutor) {
|
||||||
for (int i = 0; i < retry; i++) {
|
try {
|
||||||
try {
|
List<?> messages;
|
||||||
List<?> messages;
|
if (!flatMessage) {
|
||||||
if (!flatMessage) {
|
messages = connector.getListWithoutAck(100L, TimeUnit.MILLISECONDS);
|
||||||
messages = connector.getListWithoutAck(100L, TimeUnit.MILLISECONDS);
|
} else {
|
||||||
} else {
|
messages = connector.getFlatListWithoutAck(100L, TimeUnit.MILLISECONDS);
|
||||||
messages = connector.getFlatListWithoutAck(100L, TimeUnit.MILLISECONDS);
|
}
|
||||||
}
|
if (messages != null && !messages.isEmpty()) {
|
||||||
if (messages != null) {
|
Future<Boolean> future = workerExecutor.submit(() -> {
|
||||||
Future<Boolean> future = workerExecutor.submit(() -> {
|
if (flatMessage) {
|
||||||
if (flatMessage) {
|
// batch write
|
||||||
// batch write
|
writeOut((List<FlatMessage>) messages);
|
||||||
writeOut((List<FlatMessage>) messages);
|
} else {
|
||||||
} else {
|
for (final Object message : messages) {
|
||||||
for (final Object message : messages) {
|
writeOut((Message) message);
|
||||||
writeOut((Message) message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
future.get(timeout, TimeUnit.MILLISECONDS);
|
|
||||||
} catch (Exception e) {
|
|
||||||
future.cancel(true);
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
future.get(timeout, TimeUnit.MILLISECONDS);
|
||||||
|
} catch (Exception e) {
|
||||||
|
future.cancel(true);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
connector.ack();
|
connector.ack();
|
||||||
break;
|
}
|
||||||
} catch (Throwable e) {
|
return true;
|
||||||
if (i == retry - 1) {
|
} catch (Throwable e) {
|
||||||
connector.ack();
|
if (i == retry - 1) {
|
||||||
} else {
|
connector.ack();
|
||||||
connector.rollback();
|
logger.error(e.getMessage() + " Error sync but ACK!");
|
||||||
}
|
return true;
|
||||||
|
} else {
|
||||||
logger.error(e.getMessage(), e);
|
connector.rollback();
|
||||||
try {
|
logger.error(e.getMessage() + " Error sync and rollback, execute times: " + (i + 1));
|
||||||
TimeUnit.SECONDS.sleep(1L);
|
}
|
||||||
} catch (InterruptedException e1) {
|
try {
|
||||||
// ignore
|
Thread.sleep(500);
|
||||||
}
|
} catch (InterruptedException e1) {
|
||||||
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -198,7 +207,9 @@ public abstract class AbstractCanalAdapterWorker {
|
|||||||
len = 0;
|
len = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adapter.sync(dmlsBatch);
|
if (!dmlsBatch.isEmpty()) {
|
||||||
|
adapter.sync(dmlsBatch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ package com.alibaba.otter.canal.adapter.launcher.loader;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.protocol.FlatMessage;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
import org.apache.kafka.common.errors.WakeupException;
|
import org.apache.kafka.common.errors.WakeupException;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
||||||
@@ -61,7 +64,17 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker {
|
|||||||
connector.disconnect();
|
connector.disconnect();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
mqWriteOutData(retry, timeout, flatMessage, connector, workerExecutor);
|
if (retry == -1) {
|
||||||
|
retry = Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < retry; i++) {
|
||||||
|
if (!running) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (mqWriteOutData(retry, timeout, i, flatMessage, connector, workerExecutor)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ package com.alibaba.otter.canal.adapter.launcher.loader;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.protocol.FlatMessage;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
import org.apache.kafka.common.errors.WakeupException;
|
import org.apache.kafka.common.errors.WakeupException;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
||||||
@@ -39,7 +43,8 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker {
|
|||||||
;
|
;
|
||||||
|
|
||||||
ExecutorService workerExecutor = Executors.newSingleThreadExecutor();
|
ExecutorService workerExecutor = Executors.newSingleThreadExecutor();
|
||||||
int retry = canalClientConfig.getRetries() == null || canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries();
|
int retry = canalClientConfig.getRetries() == null
|
||||||
|
|| canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries();
|
||||||
long timeout = canalClientConfig.getTimeout() == null ? 30000 : canalClientConfig.getTimeout(); // 默认超时30秒
|
long timeout = canalClientConfig.getTimeout() == null ? 30000 : canalClientConfig.getTimeout(); // 默认超时30秒
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
@@ -56,7 +61,17 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker {
|
|||||||
connector.disconnect();
|
connector.disconnect();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
mqWriteOutData(retry, timeout, flatMessage, connector, workerExecutor);
|
if (retry == -1) {
|
||||||
|
retry = Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < retry; i++) {
|
||||||
|
if (!running) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (mqWriteOutData(retry, timeout, i, flatMessage, connector, workerExecutor)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
|
|||||||
@@ -4,9 +4,13 @@ import javax.annotation.PostConstruct;
|
|||||||
import javax.annotation.PreDestroy;
|
import javax.annotation.PreDestroy;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.adapter.launcher.monitor.AdapterRemoteConfigMonitor;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||||
|
import org.springframework.cloud.context.refresh.ContextRefresher;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import com.alibaba.druid.pool.DruidDataSource;
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
@@ -25,20 +29,27 @@ import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
|||||||
@RefreshScope
|
@RefreshScope
|
||||||
public class CanalAdapterService {
|
public class CanalAdapterService {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CanalAdapterService.class);
|
private static final Logger logger = LoggerFactory.getLogger(CanalAdapterService.class);
|
||||||
|
|
||||||
private CanalAdapterLoader adapterLoader;
|
private CanalAdapterLoader adapterLoader;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private AdapterCanalConfig adapterCanalConfig;
|
private ContextRefresher contextRefresher;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AdapterCanalConfig adapterCanalConfig;
|
||||||
|
@Resource
|
||||||
|
private Environment env;
|
||||||
|
|
||||||
// 注入bean保证优先注册
|
// 注入bean保证优先注册
|
||||||
@Resource
|
@Resource
|
||||||
private SpringContext springContext;
|
private SpringContext springContext;
|
||||||
@Resource
|
@Resource
|
||||||
private SyncSwitch syncSwitch;
|
private SyncSwitch syncSwitch;
|
||||||
|
|
||||||
private volatile boolean running = false;
|
private volatile boolean running = false;
|
||||||
|
|
||||||
|
private AdapterRemoteConfigMonitor configMonitor = null;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public synchronized void init() {
|
public synchronized void init() {
|
||||||
@@ -64,6 +75,10 @@ public class CanalAdapterService {
|
|||||||
try {
|
try {
|
||||||
running = false;
|
running = false;
|
||||||
logger.info("## stop the canal client adapters");
|
logger.info("## stop the canal client adapters");
|
||||||
|
if (configMonitor != null) {
|
||||||
|
configMonitor.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
if (adapterLoader != null) {
|
if (adapterLoader != null) {
|
||||||
adapterLoader.destroy();
|
adapterLoader.destroy();
|
||||||
adapterLoader = null;
|
adapterLoader = null;
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ import com.alibaba.otter.canal.protocol.Message;
|
|||||||
*/
|
*/
|
||||||
public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
||||||
|
|
||||||
private static final int BATCH_SIZE = 50;
|
private static final int BATCH_SIZE = 50;
|
||||||
private static final int SO_TIMEOUT = 0;
|
private static final int SO_TIMEOUT = 0;
|
||||||
|
|
||||||
private CanalConnector connector;
|
private CanalConnector connector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单台client适配器worker的构造方法
|
* 单台client适配器worker的构造方法
|
||||||
@@ -61,10 +61,14 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
|||||||
while (!running)
|
while (!running)
|
||||||
; // waiting until running == true
|
; // waiting until running == true
|
||||||
|
|
||||||
ExecutorService workerExecutor = Executors.newSingleThreadExecutor();
|
|
||||||
int retry = canalClientConfig.getRetries() == null
|
int retry = canalClientConfig.getRetries() == null
|
||||||
|| canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries();
|
|| canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries();
|
||||||
long timeout = canalClientConfig.getTimeout() == null ? 300000 : canalClientConfig.getTimeout(); // 默认超时5分钟
|
if (retry == -1) {
|
||||||
|
// 重试次数-1代表异常时一直阻塞重试
|
||||||
|
retry = Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
// long timeout = canalClientConfig.getTimeout() == null ? 300000 :
|
||||||
|
// canalClientConfig.getTimeout(); // 默认超时5分钟
|
||||||
Integer batchSize = canalClientConfig.getBatchSize();
|
Integer batchSize = canalClientConfig.getBatchSize();
|
||||||
if (batchSize == null) {
|
if (batchSize == null) {
|
||||||
batchSize = BATCH_SIZE;
|
batchSize = BATCH_SIZE;
|
||||||
@@ -90,6 +94,9 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < retry; i++) {
|
for (int i = 0; i < retry; i++) {
|
||||||
|
if (!running) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
|
Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
|
||||||
long batchId = message.getId();
|
long batchId = message.getId();
|
||||||
try {
|
try {
|
||||||
@@ -97,29 +104,19 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
|||||||
if (batchId == -1 || size == 0) {
|
if (batchId == -1 || size == 0) {
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
} else {
|
} else {
|
||||||
Future<Boolean> future = workerExecutor.submit(() -> {
|
if (logger.isDebugEnabled()) {
|
||||||
if (logger.isDebugEnabled()) {
|
logger.debug("destination: {} batchId: {} batchSize: {} ",
|
||||||
logger.debug("destination: {} batchId: {} batchSize: {} ",
|
canalDestination,
|
||||||
canalDestination,
|
batchId,
|
||||||
batchId,
|
size);
|
||||||
size);
|
}
|
||||||
}
|
long begin = System.currentTimeMillis();
|
||||||
long begin = System.currentTimeMillis();
|
writeOut(message);
|
||||||
writeOut(message);
|
if (logger.isDebugEnabled()) {
|
||||||
if (logger.isDebugEnabled()) {
|
logger.debug("destination: {} batchId: {} elapsed time: {} ms",
|
||||||
logger.debug("destination: {} batchId: {} elapsed time: {} ms",
|
canalDestination,
|
||||||
canalDestination,
|
batchId,
|
||||||
batchId,
|
System.currentTimeMillis() - begin);
|
||||||
System.currentTimeMillis() - begin);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
future.get(timeout, TimeUnit.MILLISECONDS);
|
|
||||||
} catch (Exception e) {
|
|
||||||
future.cancel(true);
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
connector.ack(batchId); // 提交确认
|
connector.ack(batchId); // 提交确认
|
||||||
@@ -127,10 +124,11 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (i != retry - 1) {
|
if (i != retry - 1) {
|
||||||
connector.rollback(batchId); // 处理失败, 回滚数据
|
connector.rollback(batchId); // 处理失败, 回滚数据
|
||||||
|
logger.error(e.getMessage() + " Error sync and rollback, execute times: " + (i + 1));
|
||||||
} else {
|
} else {
|
||||||
connector.ack(batchId);
|
connector.ack(batchId);
|
||||||
|
logger.error(e.getMessage() + " Error sync but ACK!");
|
||||||
}
|
}
|
||||||
logger.error("sync error!", e);
|
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,8 +149,6 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
workerExecutor.shutdown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,342 @@
|
|||||||
|
package com.alibaba.otter.canal.adapter.launcher.monitor;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Constant;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
|
||||||
|
import com.alibaba.otter.canal.common.utils.NamedThreadFactory;
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.collect.MapMaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 远程配置装载、监控类
|
||||||
|
*
|
||||||
|
* @author rewerma @ 2019-01-05
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class AdapterRemoteConfigMonitor {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(AdapterRemoteConfigMonitor.class);
|
||||||
|
|
||||||
|
private Connection conn;
|
||||||
|
private String jdbcUrl;
|
||||||
|
private String jdbcUsername;
|
||||||
|
private String jdbcPassword;
|
||||||
|
|
||||||
|
private long currentConfigTimestamp = 0;
|
||||||
|
private Map<String, ConfigItem> remoteAdapterConfigs = new MapMaker().makeMap();
|
||||||
|
|
||||||
|
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(2,
|
||||||
|
new NamedThreadFactory("remote-adapter-config-scan"));
|
||||||
|
|
||||||
|
public AdapterRemoteConfigMonitor(String jdbcUrl, String jdbcUsername, String jdbcPassword){
|
||||||
|
this.jdbcUrl = jdbcUrl;
|
||||||
|
this.jdbcUsername = jdbcUsername;
|
||||||
|
this.jdbcPassword = jdbcPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Connection getConn() throws Exception {
|
||||||
|
if (conn == null || conn.isClosed()) {
|
||||||
|
Class.forName("com.mysql.jdbc.Driver");
|
||||||
|
conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
|
||||||
|
}
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载远程application.yml配置到本地
|
||||||
|
*/
|
||||||
|
public void loadRemoteConfig() {
|
||||||
|
try {
|
||||||
|
// 加载远程adapter配置
|
||||||
|
ConfigItem configItem = getRemoteAdapterConfig();
|
||||||
|
if (configItem != null) {
|
||||||
|
if (configItem.getModifiedTime() != currentConfigTimestamp) {
|
||||||
|
currentConfigTimestamp = configItem.getModifiedTime();
|
||||||
|
overrideLocalCanalConfig(configItem.getContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取远程application.yml配置
|
||||||
|
*
|
||||||
|
* @return 配置对象
|
||||||
|
*/
|
||||||
|
private ConfigItem getRemoteAdapterConfig() {
|
||||||
|
String sql = "select name, content, modified_time from canal_config where id=2";
|
||||||
|
try (Statement stmt = getConn().createStatement(); ResultSet rs = stmt.executeQuery(sql)) {
|
||||||
|
if (rs.next()) {
|
||||||
|
ConfigItem configItem = new ConfigItem();
|
||||||
|
configItem.setId(2L);
|
||||||
|
configItem.setName(rs.getString("name"));
|
||||||
|
configItem.setContent(rs.getString("content"));
|
||||||
|
configItem.setModifiedTime(rs.getTimestamp("modified_time").getTime());
|
||||||
|
return configItem;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 覆盖本地application.yml文件
|
||||||
|
*
|
||||||
|
* @param content 文件内容
|
||||||
|
*/
|
||||||
|
private void overrideLocalCanalConfig(String content) {
|
||||||
|
try (FileWriter writer = new FileWriter(getConfPath() + "application.yml")) {
|
||||||
|
writer.write(content);
|
||||||
|
writer.flush();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动监听数据库变化
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
// 监听application.yml变化
|
||||||
|
executor.scheduleWithFixedDelay(() -> {
|
||||||
|
try {
|
||||||
|
loadRemoteConfig();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("scan remote application.yml failed", e);
|
||||||
|
}
|
||||||
|
}, 10, 3, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
// 监听adapter变化
|
||||||
|
executor.scheduleWithFixedDelay(() -> {
|
||||||
|
try {
|
||||||
|
loadRemoteAdapterConfigs();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("scan remote adapter configs failed", e);
|
||||||
|
}
|
||||||
|
}, 10, 3, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载adapter配置到本地
|
||||||
|
*/
|
||||||
|
public void loadRemoteAdapterConfigs() {
|
||||||
|
try {
|
||||||
|
// 加载远程instance配置
|
||||||
|
Map<String, ConfigItem>[] modifiedConfigs = getModifiedAdapterConfigs();
|
||||||
|
if (modifiedConfigs != null) {
|
||||||
|
overrideLocalAdapterConfigs(modifiedConfigs);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取有变动的adapter配置
|
||||||
|
*
|
||||||
|
* @return Map[0]: 新增修改的配置, Map[1]: 删除的配置
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Map<String, ConfigItem>[] getModifiedAdapterConfigs() {
|
||||||
|
Map<String, ConfigItem>[] res = new Map[2];
|
||||||
|
Map<String, ConfigItem> remoteConfigStatus = new HashMap<>();
|
||||||
|
String sql = "select id, category, name, modified_time from canal_adapter_config";
|
||||||
|
try (Statement stmt = getConn().createStatement(); ResultSet rs = stmt.executeQuery(sql)) {
|
||||||
|
while (rs.next()) {
|
||||||
|
ConfigItem configItem = new ConfigItem();
|
||||||
|
configItem.setId(rs.getLong("id"));
|
||||||
|
configItem.setCategory(rs.getString("category"));
|
||||||
|
configItem.setName(rs.getString("name"));
|
||||||
|
configItem.setModifiedTime(rs.getTimestamp("modified_time").getTime());
|
||||||
|
remoteConfigStatus.put(configItem.getCategory() + "/" + configItem.getName(), configItem);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!remoteConfigStatus.isEmpty()) {
|
||||||
|
List<Long> changedIds = new ArrayList<>();
|
||||||
|
|
||||||
|
for (ConfigItem remoteConfigStat : remoteConfigStatus.values()) {
|
||||||
|
ConfigItem currentConfig = remoteAdapterConfigs
|
||||||
|
.get(remoteConfigStat.getCategory() + "/" + remoteConfigStat.getName());
|
||||||
|
if (currentConfig == null) {
|
||||||
|
// 新增
|
||||||
|
changedIds.add(remoteConfigStat.getId());
|
||||||
|
} else {
|
||||||
|
// 修改
|
||||||
|
if (currentConfig.getModifiedTime() != remoteConfigStat.getModifiedTime()) {
|
||||||
|
changedIds.add(remoteConfigStat.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!changedIds.isEmpty()) {
|
||||||
|
Map<String, ConfigItem> changedInstanceConfig = new HashMap<>();
|
||||||
|
String contentsSql = "select id, category, name, content, modified_time from canal_adapter_config where id in ("
|
||||||
|
+ Joiner.on(",").join(changedIds) + ")";
|
||||||
|
try (Statement stmt = getConn().createStatement(); ResultSet rs = stmt.executeQuery(contentsSql)) {
|
||||||
|
while (rs.next()) {
|
||||||
|
ConfigItem configItemNew = new ConfigItem();
|
||||||
|
configItemNew.setId(rs.getLong("id"));
|
||||||
|
configItemNew.setCategory(rs.getString("category"));
|
||||||
|
configItemNew.setName(rs.getString("name"));
|
||||||
|
configItemNew.setContent(rs.getString("content"));
|
||||||
|
configItemNew.setModifiedTime(rs.getTimestamp("modified_time").getTime());
|
||||||
|
|
||||||
|
remoteAdapterConfigs.put(configItemNew.getCategory() + "/" + configItemNew.getName(),
|
||||||
|
configItemNew);
|
||||||
|
changedInstanceConfig.put(configItemNew.getCategory() + "/" + configItemNew.getName(),
|
||||||
|
configItemNew);
|
||||||
|
}
|
||||||
|
|
||||||
|
res[0] = changedInstanceConfig.isEmpty() ? null : changedInstanceConfig;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, ConfigItem> removedInstanceConfig = new HashMap<>();
|
||||||
|
for (ConfigItem configItem : remoteAdapterConfigs.values()) {
|
||||||
|
if (!remoteConfigStatus.containsKey(configItem.getCategory() + "/" + configItem.getName())) {
|
||||||
|
// 删除
|
||||||
|
remoteAdapterConfigs.remove(configItem.getCategory() + "/" + configItem.getName());
|
||||||
|
removedInstanceConfig.put(configItem.getCategory() + "/" + configItem.getName(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res[1] = removedInstanceConfig.isEmpty() ? null : removedInstanceConfig;
|
||||||
|
|
||||||
|
if (res[0] == null && res[1] == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 覆盖adapter配置到本地
|
||||||
|
*
|
||||||
|
* @param modifiedInstanceConfigs 变动的配置集合
|
||||||
|
*/
|
||||||
|
private void overrideLocalAdapterConfigs(Map<String, ConfigItem>[] modifiedInstanceConfigs) {
|
||||||
|
Map<String, ConfigItem> changedInstanceConfigs = modifiedInstanceConfigs[0];
|
||||||
|
if (changedInstanceConfigs != null) {
|
||||||
|
for (ConfigItem configItem : changedInstanceConfigs.values()) {
|
||||||
|
try (FileWriter writer = new FileWriter(
|
||||||
|
getConfPath() + configItem.getCategory() + "/" + configItem.getName())) {
|
||||||
|
writer.write(configItem.getContent());
|
||||||
|
writer.flush();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<String, ConfigItem> removedInstanceConfigs = modifiedInstanceConfigs[1];
|
||||||
|
if (removedInstanceConfigs != null) {
|
||||||
|
for (String name : removedInstanceConfigs.keySet()) {
|
||||||
|
File file = new File(getConfPath() + name);
|
||||||
|
if (file.exists()) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取conf文件夹所在路径
|
||||||
|
*
|
||||||
|
* @return 路径地址
|
||||||
|
*/
|
||||||
|
private String getConfPath() {
|
||||||
|
String classpath = this.getClass().getResource("/").getPath();
|
||||||
|
String confPath = classpath + "../conf/";
|
||||||
|
if (new File(confPath).exists()) {
|
||||||
|
return confPath;
|
||||||
|
} else {
|
||||||
|
return classpath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void destroy() {
|
||||||
|
executor.shutdownNow();
|
||||||
|
if (conn != null) {
|
||||||
|
try {
|
||||||
|
conn.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置对应对象
|
||||||
|
*/
|
||||||
|
public static class ConfigItem {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String category;
|
||||||
|
private String name;
|
||||||
|
private String content;
|
||||||
|
private long modifiedTime;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCategory() {
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategory(String category) {
|
||||||
|
this.category = category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getModifiedTime() {
|
||||||
|
return modifiedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiedTime(long modifiedTime) {
|
||||||
|
this.modifiedTime = modifiedTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Bootstrap Configuration
|
||||||
|
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
|
||||||
|
com.alibaba.otter.canal.adapter.launcher.config.BootstrapConfiguration
|
||||||
@@ -15,7 +15,7 @@ spring:
|
|||||||
canal.conf:
|
canal.conf:
|
||||||
canalServerHost: 127.0.0.1:11111
|
canalServerHost: 127.0.0.1:11111
|
||||||
# zookeeperHosts: slave1:2181
|
# zookeeperHosts: slave1:2181
|
||||||
# mqServers: slave1:6667 #or rocketmq
|
# mqServers: 127.0.0.1:9092 #or rocketmq
|
||||||
# flatMessage: true
|
# flatMessage: true
|
||||||
batchSize: 500
|
batchSize: 500
|
||||||
syncBatchSize: 1000
|
syncBatchSize: 1000
|
||||||
@@ -36,6 +36,13 @@ canal.conf:
|
|||||||
outerAdapters:
|
outerAdapters:
|
||||||
- name: logger
|
- name: logger
|
||||||
# - name: rdb
|
# - name: rdb
|
||||||
|
# key: mysql1
|
||||||
|
# properties:
|
||||||
|
# jdbc.driverClassName: com.mysql.jdbc.Driver
|
||||||
|
# jdbc.url: jdbc:mysql://127.0.0.1:3306/mytest2?useUnicode=true
|
||||||
|
# jdbc.username: root
|
||||||
|
# jdbc.password: 121212
|
||||||
|
# - name: rdb
|
||||||
# key: oracle1
|
# key: oracle1
|
||||||
# properties:
|
# properties:
|
||||||
# jdbc.driverClassName: oracle.jdbc.OracleDriver
|
# jdbc.driverClassName: oracle.jdbc.OracleDriver
|
||||||
|
|||||||
6
client-adapter/launcher/src/main/resources/bootstrap.yml
Normal file
6
client-adapter/launcher/src/main/resources/bootstrap.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#canal:
|
||||||
|
# manager:
|
||||||
|
# jdbc:
|
||||||
|
# url: jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8
|
||||||
|
# username: root
|
||||||
|
# password: 121212
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>canal client adapter module for otter ${project.version}</name>
|
<name>canal client adapter module for otter ${project.version}</name>
|
||||||
|
|
||||||
@@ -25,9 +25,42 @@
|
|||||||
<module>rdb</module>
|
<module>rdb</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>Apache License, Version 2.0</name>
|
||||||
|
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
|
||||||
|
<scm>
|
||||||
|
<url>git@github.com:alibaba/canal.git</url>
|
||||||
|
<connection>scm:git:git@github.com:alibaba/canal.git</connection>
|
||||||
|
<developerConnection>scm:git:git@github.com:alibaba/canal.git</developerConnection>
|
||||||
|
</scm>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
<repository>
|
||||||
<id>central</id>
|
<id>central</id>
|
||||||
|
<url>http://repo1.maven.org/maven2</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>java.net</id>
|
||||||
|
<url>http://download.java.net/maven/2/</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>aliyun</id>
|
||||||
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
|
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
|
||||||
<releases>
|
<releases>
|
||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
@@ -36,6 +69,28 @@
|
|||||||
<enabled>false</enabled>
|
<enabled>false</enabled>
|
||||||
</snapshots>
|
</snapshots>
|
||||||
</repository>
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>sonatype</id>
|
||||||
|
<name>sonatype</name>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>sonatype-release</id>
|
||||||
|
<name>sonatype-release</name>
|
||||||
|
<url>https://oss.sonatype.org/service/local/repositories/releases/content</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -52,4 +107,17 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
<distributionManagement>
|
||||||
|
<snapshotRepository>
|
||||||
|
<id>sonatype-nexus-snapshots</id>
|
||||||
|
<name>Sonatype Nexus Snapshots</name>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
|
||||||
|
</snapshotRepository>
|
||||||
|
<repository>
|
||||||
|
<id>sonatype-nexus-staging</id>
|
||||||
|
<name>Nexus Release Repository</name>
|
||||||
|
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
|
||||||
|
</repository>
|
||||||
|
</distributionManagement>
|
||||||
</project>
|
</project>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -25,6 +25,12 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
<version>5.1.40</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
<artifactId>postgresql</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
|
|||||||
@@ -2,15 +2,17 @@ package com.alibaba.otter.canal.client.adapter.rdb;
|
|||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.BooleanUtils;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -19,24 +21,38 @@ import com.alibaba.druid.pool.DruidDataSource;
|
|||||||
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
||||||
import com.alibaba.otter.canal.client.adapter.rdb.config.ConfigLoader;
|
import com.alibaba.otter.canal.client.adapter.rdb.config.ConfigLoader;
|
||||||
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MirrorDbConfig;
|
||||||
import com.alibaba.otter.canal.client.adapter.rdb.monitor.RdbConfigMonitor;
|
import com.alibaba.otter.canal.client.adapter.rdb.monitor.RdbConfigMonitor;
|
||||||
import com.alibaba.otter.canal.client.adapter.rdb.service.RdbEtlService;
|
import com.alibaba.otter.canal.client.adapter.rdb.service.RdbEtlService;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.service.RdbMirrorDbSyncService;
|
||||||
import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService;
|
import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.*;
|
import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.SPI;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RDB适配器实现类
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-11-7 下午06:45:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
@SPI("rdb")
|
@SPI("rdb")
|
||||||
public class RdbAdapter implements OuterAdapter {
|
public class RdbAdapter implements OuterAdapter {
|
||||||
|
|
||||||
private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class);
|
private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class);
|
||||||
|
|
||||||
private Map<String, MappingConfig> rdbMapping = new HashMap<>(); // 文件名对应配置
|
private Map<String, MappingConfig> rdbMapping = new ConcurrentHashMap<>(); // 文件名对应配置
|
||||||
private Map<String, Map<String, MappingConfig>> mappingConfigCache = new HashMap<>(); // 库名-表名对应配置
|
private Map<String, Map<String, MappingConfig>> mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置
|
||||||
|
private Map<String, MirrorDbConfig> mirrorDbConfigCache = new ConcurrentHashMap<>(); // 镜像库配置
|
||||||
|
|
||||||
private DruidDataSource dataSource;
|
private DruidDataSource dataSource;
|
||||||
|
|
||||||
private RdbSyncService rdbSyncService;
|
private RdbSyncService rdbSyncService;
|
||||||
|
private RdbMirrorDbSyncService rdbMirrorDbSyncService;
|
||||||
private ExecutorService executor = Executors.newFixedThreadPool(1);
|
|
||||||
|
|
||||||
private RdbConfigMonitor rdbConfigMonitor;
|
private RdbConfigMonitor rdbConfigMonitor;
|
||||||
|
|
||||||
@@ -48,28 +64,50 @@ public class RdbAdapter implements OuterAdapter {
|
|||||||
return mappingConfigCache;
|
return mappingConfigCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, MirrorDbConfig> getMirrorDbConfigCache() {
|
||||||
|
return mirrorDbConfigCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化方法
|
||||||
|
*
|
||||||
|
* @param configuration 外部适配器配置信息
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void init(OuterAdapterConfig configuration) {
|
public void init(OuterAdapterConfig configuration) {
|
||||||
Map<String, MappingConfig> rdbMappingTmp = ConfigLoader.load();
|
Map<String, MappingConfig> rdbMappingTmp = ConfigLoader.load();
|
||||||
// 过滤不匹配的key的配置
|
// 过滤不匹配的key的配置
|
||||||
rdbMappingTmp.forEach((key, mappingConfig) -> {
|
rdbMappingTmp.forEach((key, mappingConfig) -> {
|
||||||
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|
||||||
|| (mappingConfig.getOuterAdapterKey() != null
|
|| (mappingConfig.getOuterAdapterKey() != null && mappingConfig.getOuterAdapterKey()
|
||||||
&& mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) {
|
.equalsIgnoreCase(configuration.getKey()))) {
|
||||||
rdbMapping.put(key, mappingConfig);
|
rdbMapping.put(key, mappingConfig);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (rdbMapping.isEmpty()) {
|
||||||
|
throw new RuntimeException("No rdb adapter found for config key: " + configuration.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
for (Map.Entry<String, MappingConfig> entry : rdbMapping.entrySet()) {
|
for (Map.Entry<String, MappingConfig> entry : rdbMapping.entrySet()) {
|
||||||
String configName = entry.getKey();
|
String configName = entry.getKey();
|
||||||
MappingConfig mappingConfig = entry.getValue();
|
MappingConfig mappingConfig = entry.getValue();
|
||||||
Map<String, MappingConfig> configMap = mappingConfigCache
|
if (!mappingConfig.getDbMapping().getMirrorDb()) {
|
||||||
.computeIfAbsent(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
|
String key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
|
||||||
+ mappingConfig.getDbMapping().getDatabase() + "."
|
+ mappingConfig.getDbMapping().getDatabase() + "."
|
||||||
+ mappingConfig.getDbMapping().getTable(),
|
+ mappingConfig.getDbMapping().getTable();
|
||||||
k1 -> new HashMap<>());
|
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(key,
|
||||||
configMap.put(configName, mappingConfig);
|
k1 -> new ConcurrentHashMap<>());
|
||||||
|
configMap.put(configName, mappingConfig);
|
||||||
|
} else {
|
||||||
|
// mirrorDB
|
||||||
|
String key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
|
||||||
|
+ mappingConfig.getDbMapping().getDatabase();
|
||||||
|
mirrorDbConfigCache.put(key, MirrorDbConfig.create(configName, mappingConfig));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化连接池
|
||||||
Map<String, String> properties = configuration.getProperties();
|
Map<String, String> properties = configuration.getProperties();
|
||||||
dataSource = new DruidDataSource();
|
dataSource = new DruidDataSource();
|
||||||
dataSource.setDriverClassName(properties.get("jdbc.driverClassName"));
|
dataSource.setDriverClassName(properties.get("jdbc.driverClassName"));
|
||||||
@@ -78,10 +116,11 @@ public class RdbAdapter implements OuterAdapter {
|
|||||||
dataSource.setPassword(properties.get("jdbc.password"));
|
dataSource.setPassword(properties.get("jdbc.password"));
|
||||||
dataSource.setInitialSize(1);
|
dataSource.setInitialSize(1);
|
||||||
dataSource.setMinIdle(1);
|
dataSource.setMinIdle(1);
|
||||||
dataSource.setMaxActive(20);
|
dataSource.setMaxActive(30);
|
||||||
dataSource.setMaxWait(60000);
|
dataSource.setMaxWait(60000);
|
||||||
dataSource.setTimeBetweenEvictionRunsMillis(60000);
|
dataSource.setTimeBetweenEvictionRunsMillis(60000);
|
||||||
dataSource.setMinEvictableIdleTimeMillis(300000);
|
dataSource.setMinEvictableIdleTimeMillis(300000);
|
||||||
|
dataSource.setUseUnfairLock(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
dataSource.init();
|
dataSource.init();
|
||||||
@@ -92,19 +131,54 @@ public class RdbAdapter implements OuterAdapter {
|
|||||||
String threads = properties.get("threads");
|
String threads = properties.get("threads");
|
||||||
// String commitSize = properties.get("commitSize");
|
// String commitSize = properties.get("commitSize");
|
||||||
|
|
||||||
rdbSyncService = new RdbSyncService(mappingConfigCache,
|
boolean skipDupException = BooleanUtils.toBoolean(configuration.getProperties()
|
||||||
|
.getOrDefault("skipDupException", "true"));
|
||||||
|
rdbSyncService = new RdbSyncService(dataSource,
|
||||||
|
threads != null ? Integer.valueOf(threads) : null,
|
||||||
|
skipDupException);
|
||||||
|
|
||||||
|
rdbMirrorDbSyncService = new RdbMirrorDbSyncService(mirrorDbConfigCache,
|
||||||
dataSource,
|
dataSource,
|
||||||
threads != null ? Integer.valueOf(threads) : null);
|
threads != null ? Integer.valueOf(threads) : null,
|
||||||
|
rdbSyncService.getColumnsTypeCache(),
|
||||||
|
skipDupException);
|
||||||
|
|
||||||
rdbConfigMonitor = new RdbConfigMonitor();
|
rdbConfigMonitor = new RdbConfigMonitor();
|
||||||
rdbConfigMonitor.init(configuration.getKey(), this);
|
rdbConfigMonitor.init(configuration.getKey(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步方法
|
||||||
|
*
|
||||||
|
* @param dmls 数据包
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void sync(List<Dml> dmls) {
|
public void sync(List<Dml> dmls) {
|
||||||
rdbSyncService.sync(dmls);
|
ExecutorService executorService = Executors.newFixedThreadPool(2);
|
||||||
|
|
||||||
|
Future<Boolean> future1 = executorService.submit(() -> {
|
||||||
|
rdbSyncService.sync(mappingConfigCache, dmls);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
Future<Boolean> future2 = executorService.submit(() -> {
|
||||||
|
rdbMirrorDbSyncService.sync(dmls);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
future1.get();
|
||||||
|
future2.get();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ETL方法
|
||||||
|
*
|
||||||
|
* @param task 任务名, 对应配置名
|
||||||
|
* @param params etl筛选条件
|
||||||
|
* @return ETL结果
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public EtlResult etl(String task, List<String> params) {
|
public EtlResult etl(String task, List<String> params) {
|
||||||
EtlResult etlResult = new EtlResult();
|
EtlResult etlResult = new EtlResult();
|
||||||
@@ -153,11 +227,17 @@ public class RdbAdapter implements OuterAdapter {
|
|||||||
return etlResult;
|
return etlResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取总数方法
|
||||||
|
*
|
||||||
|
* @param task 任务名, 对应配置名
|
||||||
|
* @return 总数
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> count(String task) {
|
public Map<String, Object> count(String task) {
|
||||||
MappingConfig config = rdbMapping.get(task);
|
MappingConfig config = rdbMapping.get(task);
|
||||||
MappingConfig.DbMapping dbMapping = config.getDbMapping();
|
MappingConfig.DbMapping dbMapping = config.getDbMapping();
|
||||||
String sql = "SELECT COUNT(1) AS cnt FROM " + dbMapping.getTargetTable();
|
String sql = "SELECT COUNT(1) AS cnt FROM " + SyncUtil.getDbTableName(dbMapping);
|
||||||
Connection conn = null;
|
Connection conn = null;
|
||||||
Map<String, Object> res = new LinkedHashMap<>();
|
Map<String, Object> res = new LinkedHashMap<>();
|
||||||
try {
|
try {
|
||||||
@@ -183,11 +263,17 @@ public class RdbAdapter implements OuterAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.put("targetTable", dbMapping.getTargetTable());
|
res.put("targetTable", SyncUtil.getDbTableName(dbMapping));
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取对应canal instance name 或 mq topic
|
||||||
|
*
|
||||||
|
* @param task 任务名, 对应配置名
|
||||||
|
* @return destination
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getDestination(String task) {
|
public String getDestination(String task) {
|
||||||
MappingConfig config = rdbMapping.get(task);
|
MappingConfig config = rdbMapping.get(task);
|
||||||
@@ -197,6 +283,9 @@ public class RdbAdapter implements OuterAdapter {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁方法
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
if (rdbConfigMonitor != null) {
|
if (rdbConfigMonitor != null) {
|
||||||
@@ -207,8 +296,6 @@ public class RdbAdapter implements OuterAdapter {
|
|||||||
rdbSyncService.close();
|
rdbSyncService.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
executor.shutdown();
|
|
||||||
|
|
||||||
if (dataSource != null) {
|
if (dataSource != null) {
|
||||||
dataSource.close();
|
dataSource.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.alibaba.otter.canal.client.adapter.rdb.config;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.yaml.snakeyaml.Yaml;
|
import org.yaml.snakeyaml.Yaml;
|
||||||
@@ -24,6 +25,7 @@ public class ConfigLoader {
|
|||||||
*
|
*
|
||||||
* @return 配置名/配置文件名--对象
|
* @return 配置名/配置文件名--对象
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public static Map<String, MappingConfig> load() {
|
public static Map<String, MappingConfig> load() {
|
||||||
logger.info("## Start loading rdb mapping config ... ");
|
logger.info("## Start loading rdb mapping config ... ");
|
||||||
|
|
||||||
@@ -31,7 +33,9 @@ public class ConfigLoader {
|
|||||||
|
|
||||||
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("rdb");
|
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("rdb");
|
||||||
configContentMap.forEach((fileName, content) -> {
|
configContentMap.forEach((fileName, content) -> {
|
||||||
MappingConfig config = new Yaml().loadAs(content, MappingConfig.class);
|
Map configMap = new Yaml().loadAs(content, Map.class); // yml自带的对象反射不是很稳定
|
||||||
|
JSONObject configJson = new JSONObject(configMap);
|
||||||
|
MappingConfig config = configJson.toJavaObject(MappingConfig.class);
|
||||||
try {
|
try {
|
||||||
config.validate();
|
config.validate();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package com.alibaba.otter.canal.client.adapter.rdb.config;
|
package com.alibaba.otter.canal.client.adapter.rdb.config;
|
||||||
|
|
||||||
import java.util.LinkedHashSet;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RDB表映射配置
|
* RDB表映射配置
|
||||||
@@ -66,30 +67,39 @@ public class MappingConfig {
|
|||||||
if (dbMapping.database == null || dbMapping.database.isEmpty()) {
|
if (dbMapping.database == null || dbMapping.database.isEmpty()) {
|
||||||
throw new NullPointerException("dbMapping.database");
|
throw new NullPointerException("dbMapping.database");
|
||||||
}
|
}
|
||||||
if (dbMapping.table == null || dbMapping.table.isEmpty()) {
|
if (!dbMapping.getMirrorDb() && (dbMapping.table == null || dbMapping.table.isEmpty())) {
|
||||||
throw new NullPointerException("dbMapping.table");
|
throw new NullPointerException("dbMapping.table");
|
||||||
}
|
}
|
||||||
if (dbMapping.targetTable == null || dbMapping.targetTable.isEmpty()) {
|
if (!dbMapping.getMirrorDb() && (dbMapping.targetTable == null || dbMapping.targetTable.isEmpty())) {
|
||||||
throw new NullPointerException("dbMapping.targetTable");
|
throw new NullPointerException("dbMapping.targetTable");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DbMapping {
|
public static class DbMapping {
|
||||||
|
|
||||||
|
private Boolean mirrorDb = false; // 是否镜像库
|
||||||
private String database; // 数据库名或schema名
|
private String database; // 数据库名或schema名
|
||||||
private String table; // 表面名
|
private String table; // 表名
|
||||||
private Map<String, String> targetPk; // 目标表主键字段
|
private Map<String, String> targetPk = new LinkedHashMap<>(); // 目标表主键字段
|
||||||
private boolean mapAll = false; // 映射所有字段
|
private Boolean mapAll = false; // 映射所有字段
|
||||||
|
private String targetDb; // 目标库名
|
||||||
private String targetTable; // 目标表名
|
private String targetTable; // 目标表名
|
||||||
private Map<String, String> targetColumns; // 目标表字段映射
|
private Map<String, String> targetColumns; // 目标表字段映射
|
||||||
|
|
||||||
private String etlCondition; // etl条件sql
|
private String etlCondition; // etl条件sql
|
||||||
|
|
||||||
private Set<String> families = new LinkedHashSet<>(); // column family列表
|
|
||||||
private int readBatch = 5000;
|
private int readBatch = 5000;
|
||||||
private int commitBatch = 5000; // etl等批量提交大小
|
private int commitBatch = 5000; // etl等批量提交大小
|
||||||
|
|
||||||
// private volatile Map<String, String> allColumns; // mapAll为true,自动设置改字段
|
private Map<String, String> allMapColumns;
|
||||||
|
|
||||||
|
public Boolean getMirrorDb() {
|
||||||
|
return mirrorDb == null ? false : mirrorDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMirrorDb(Boolean mirrorDb) {
|
||||||
|
this.mirrorDb = mirrorDb;
|
||||||
|
}
|
||||||
|
|
||||||
public String getDatabase() {
|
public String getDatabase() {
|
||||||
return database;
|
return database;
|
||||||
@@ -115,14 +125,22 @@ public class MappingConfig {
|
|||||||
this.targetPk = targetPk;
|
this.targetPk = targetPk;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMapAll() {
|
public Boolean getMapAll() {
|
||||||
return mapAll;
|
return mapAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMapAll(boolean mapAll) {
|
public void setMapAll(Boolean mapAll) {
|
||||||
this.mapAll = mapAll;
|
this.mapAll = mapAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTargetDb() {
|
||||||
|
return targetDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTargetDb(String targetDb) {
|
||||||
|
this.targetDb = targetDb;
|
||||||
|
}
|
||||||
|
|
||||||
public String getTargetTable() {
|
public String getTargetTable() {
|
||||||
return targetTable;
|
return targetTable;
|
||||||
}
|
}
|
||||||
@@ -132,6 +150,13 @@ public class MappingConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> getTargetColumns() {
|
public Map<String, String> getTargetColumns() {
|
||||||
|
if (targetColumns != null) {
|
||||||
|
targetColumns.forEach((key, value) -> {
|
||||||
|
if (StringUtils.isEmpty(value)) {
|
||||||
|
targetColumns.put(key, key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
return targetColumns;
|
return targetColumns;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,14 +172,6 @@ public class MappingConfig {
|
|||||||
this.etlCondition = etlCondition;
|
this.etlCondition = etlCondition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> getFamilies() {
|
|
||||||
return families;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFamilies(Set<String> families) {
|
|
||||||
this.families = families;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getReadBatch() {
|
public int getReadBatch() {
|
||||||
return readBatch;
|
return readBatch;
|
||||||
}
|
}
|
||||||
@@ -171,12 +188,12 @@ public class MappingConfig {
|
|||||||
this.commitBatch = commitBatch;
|
this.commitBatch = commitBatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
// public Map<String, String> getAllColumns() {
|
public Map<String, String> getAllMapColumns() {
|
||||||
// return allColumns;
|
return allMapColumns;
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// public void setAllColumns(Map<String, String> allColumns) {
|
public void setAllMapColumns(Map<String, String> allMapColumns) {
|
||||||
// this.allColumns = allColumns;
|
this.allMapColumns = allMapColumns;
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb.config;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class MirrorDbConfig {
|
||||||
|
|
||||||
|
private String fileName;
|
||||||
|
private MappingConfig mappingConfig;
|
||||||
|
Map<String, MappingConfig> tableConfig = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public static MirrorDbConfig create(String fileName, MappingConfig mappingConfig) {
|
||||||
|
return new MirrorDbConfig(fileName, mappingConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MirrorDbConfig(String fileName, MappingConfig mappingConfig){
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.mappingConfig = mappingConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFileName() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFileName(String fileName) {
|
||||||
|
this.fileName = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingConfig getMappingConfig() {
|
||||||
|
return mappingConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMappingConfig(MappingConfig mappingConfig) {
|
||||||
|
this.mappingConfig = mappingConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, MappingConfig> getTableConfig() {
|
||||||
|
return tableConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTableConfig(Map<String, MappingConfig> tableConfig) {
|
||||||
|
this.tableConfig = tableConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import java.io.File;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MirrorDbConfig;
|
||||||
import org.apache.commons.io.filefilter.FileFilterUtils;
|
import org.apache.commons.io.filefilter.FileFilterUtils;
|
||||||
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
|
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
|
||||||
import org.apache.commons.io.monitor.FileAlterationMonitor;
|
import org.apache.commons.io.monitor.FileAlterationMonitor;
|
||||||
@@ -15,12 +16,13 @@ import org.yaml.snakeyaml.Yaml;
|
|||||||
|
|
||||||
import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter;
|
import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter;
|
||||||
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MirrorDbConfig;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
|
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.Util;
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
|
|
||||||
public class RdbConfigMonitor {
|
public class RdbConfigMonitor {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RdbConfigMonitor.class);
|
private static final Logger logger = LoggerFactory.getLogger(RdbConfigMonitor.class);
|
||||||
|
|
||||||
private static final String adapterName = "rdb";
|
private static final String adapterName = "rdb";
|
||||||
|
|
||||||
@@ -83,7 +85,12 @@ public class RdbConfigMonitor {
|
|||||||
try {
|
try {
|
||||||
if (rdbAdapter.getRdbMapping().containsKey(file.getName())) {
|
if (rdbAdapter.getRdbMapping().containsKey(file.getName())) {
|
||||||
// 加载配置文件
|
// 加载配置文件
|
||||||
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
|
String configContent = MappingConfigsLoader
|
||||||
|
.loadConfig(adapterName + File.separator + file.getName());
|
||||||
|
if (configContent == null) {
|
||||||
|
onFileDelete(file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
MappingConfig config = new Yaml().loadAs(configContent, MappingConfig.class);
|
MappingConfig config = new Yaml().loadAs(configContent, MappingConfig.class);
|
||||||
config.validate();
|
config.validate();
|
||||||
if ((key == null && config.getOuterAdapterKey() == null)
|
if ((key == null && config.getOuterAdapterKey() == null)
|
||||||
@@ -118,22 +125,44 @@ public class RdbConfigMonitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addConfigToCache(File file, MappingConfig config) {
|
private void addConfigToCache(File file, MappingConfig mappingConfig) {
|
||||||
rdbAdapter.getRdbMapping().put(file.getName(), config);
|
if (mappingConfig == null || mappingConfig.getDbMapping() == null) {
|
||||||
Map<String, MappingConfig> configMap = rdbAdapter.getMappingConfigCache()
|
return;
|
||||||
.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "."
|
}
|
||||||
+ config.getDbMapping().getDatabase() + "." + config.getDbMapping().getTable(),
|
rdbAdapter.getRdbMapping().put(file.getName(), mappingConfig);
|
||||||
k1 -> new HashMap<>());
|
if (!mappingConfig.getDbMapping().getMirrorDb()) {
|
||||||
configMap.put(file.getName(), config);
|
Map<String, MappingConfig> configMap = rdbAdapter.getMappingConfigCache()
|
||||||
|
.computeIfAbsent(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
|
||||||
|
+ mappingConfig.getDbMapping().getDatabase() + "."
|
||||||
|
+ mappingConfig.getDbMapping().getTable(),
|
||||||
|
k1 -> new HashMap<>());
|
||||||
|
configMap.put(file.getName(), mappingConfig);
|
||||||
|
} else {
|
||||||
|
Map<String, MirrorDbConfig> mirrorDbConfigCache = rdbAdapter.getMirrorDbConfigCache();
|
||||||
|
mirrorDbConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
|
||||||
|
+ mappingConfig.getDbMapping().getDatabase(),
|
||||||
|
MirrorDbConfig.create(file.getName(), mappingConfig));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteConfigFromCache(File file) {
|
private void deleteConfigFromCache(File file) {
|
||||||
|
MappingConfig mappingConfig = rdbAdapter.getRdbMapping().remove(file.getName());
|
||||||
|
|
||||||
rdbAdapter.getRdbMapping().remove(file.getName());
|
if (mappingConfig == null || mappingConfig.getDbMapping() == null) {
|
||||||
for (Map<String, MappingConfig> configMap : rdbAdapter.getMappingConfigCache().values()) {
|
return;
|
||||||
if (configMap != null) {
|
}
|
||||||
configMap.remove(file.getName());
|
if (!mappingConfig.getDbMapping().getMirrorDb()) {
|
||||||
|
for (Map<String, MappingConfig> configMap : rdbAdapter.getMappingConfigCache().values()) {
|
||||||
|
if (configMap != null) {
|
||||||
|
configMap.remove(file.getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
rdbAdapter.getMirrorDbConfigCache().forEach((key, mirrorDbConfig) -> {
|
||||||
|
if (mirrorDbConfig.getFileName().equals(file.getName())) {
|
||||||
|
rdbAdapter.getMirrorDbConfigCache().remove(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ public class RdbEtlService {
|
|||||||
/**
|
/**
|
||||||
* 导入数据
|
* 导入数据
|
||||||
*/
|
*/
|
||||||
public static EtlResult importData(DataSource srcDS, DataSource targetDS, MappingConfig config,
|
public static EtlResult importData(DataSource srcDS, DataSource targetDS, MappingConfig config, List<String> params) {
|
||||||
List<String> params) {
|
|
||||||
EtlResult etlResult = new EtlResult();
|
EtlResult etlResult = new EtlResult();
|
||||||
AtomicLong successCount = new AtomicLong();
|
AtomicLong successCount = new AtomicLong();
|
||||||
List<String> errMsg = new ArrayList<>();
|
List<String> errMsg = new ArrayList<>();
|
||||||
@@ -54,8 +53,8 @@ public class RdbEtlService {
|
|||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
// 拼接sql
|
// 拼接sql
|
||||||
StringBuilder sql = new StringBuilder(
|
StringBuilder sql = new StringBuilder("SELECT * FROM " + dbMapping.getDatabase() + "."
|
||||||
"SELECT * FROM " + dbMapping.getDatabase() + "." + dbMapping.getTable());
|
+ dbMapping.getTable());
|
||||||
|
|
||||||
// 拼接条件
|
// 拼接条件
|
||||||
appendCondition(params, dbMapping, srcDS, sql);
|
appendCondition(params, dbMapping, srcDS, sql);
|
||||||
@@ -92,8 +91,12 @@ public class RdbEtlService {
|
|||||||
} else {
|
} else {
|
||||||
sqlFinal = sql + " LIMIT " + offset + "," + cnt;
|
sqlFinal = sql + " LIMIT " + offset + "," + cnt;
|
||||||
}
|
}
|
||||||
Future<Boolean> future = executor
|
Future<Boolean> future = executor.submit(() -> executeSqlImport(srcDS,
|
||||||
.submit(() -> executeSqlImport(srcDS, targetDS, sqlFinal, dbMapping, successCount, errMsg));
|
targetDS,
|
||||||
|
sqlFinal,
|
||||||
|
dbMapping,
|
||||||
|
successCount,
|
||||||
|
errMsg));
|
||||||
futures.add(future);
|
futures.add(future);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,10 +109,11 @@ public class RdbEtlService {
|
|||||||
executeSqlImport(srcDS, targetDS, sql.toString(), dbMapping, successCount, errMsg);
|
executeSqlImport(srcDS, targetDS, sql.toString(), dbMapping, successCount, errMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(dbMapping.getTable() + " etl completed in: " + (System.currentTimeMillis() - start) / 1000
|
||||||
dbMapping.getTable() + " etl completed in: " + (System.currentTimeMillis() - start) / 1000 + "s!");
|
+ "s!");
|
||||||
|
|
||||||
etlResult.setResultMessage("导入目标表 " + dbMapping.getTargetTable() + " 数据:" + successCount.get() + " 条");
|
etlResult.setResultMessage("导入目标表 " + SyncUtil.getDbTableName(dbMapping) + " 数据:" + successCount.get()
|
||||||
|
+ " 条");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
errMsg.add(hbaseTable + " etl failed! ==>" + e.getMessage());
|
errMsg.add(hbaseTable + " etl failed! ==>" + e.getMessage());
|
||||||
@@ -123,8 +127,8 @@ public class RdbEtlService {
|
|||||||
return etlResult;
|
return etlResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void appendCondition(List<String> params, DbMapping dbMapping, DataSource ds,
|
private static void appendCondition(List<String> params, DbMapping dbMapping, DataSource ds, StringBuilder sql)
|
||||||
StringBuilder sql) throws SQLException {
|
throws SQLException {
|
||||||
if (params != null && params.size() == 1 && dbMapping.getEtlCondition() == null) {
|
if (params != null && params.size() == 1 && dbMapping.getEtlCondition() == null) {
|
||||||
AtomicBoolean stExists = new AtomicBoolean(false);
|
AtomicBoolean stExists = new AtomicBoolean(false);
|
||||||
// 验证是否有SYS_TIME字段
|
// 验证是否有SYS_TIME字段
|
||||||
@@ -141,9 +145,9 @@ public class RdbEtlService {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
} );
|
||||||
if (stExists.get()) {
|
if (stExists.get()) {
|
||||||
sql.append(" WHERE SYS_TIME >= '").append(params.get(0)).append("' ");
|
sql.append(" WHERE SYS_TIME >= '").append(params.get(0)).append("' ");
|
||||||
}
|
}
|
||||||
@@ -186,85 +190,84 @@ public class RdbEtlService {
|
|||||||
// columnsMap = dbMapping.getTargetColumns();
|
// columnsMap = dbMapping.getTargetColumns();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
StringBuilder insertSql = new StringBuilder();
|
StringBuilder insertSql = new StringBuilder();
|
||||||
insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" (");
|
insertSql.append("INSERT INTO ").append(SyncUtil.getDbTableName(dbMapping)).append(" (");
|
||||||
columnsMap
|
columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(","));
|
||||||
.forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(","));
|
|
||||||
|
|
||||||
int len = insertSql.length();
|
int len = insertSql.length();
|
||||||
insertSql.delete(len - 1, len).append(") VALUES (");
|
insertSql.delete(len - 1, len).append(") VALUES (");
|
||||||
int mapLen = columnsMap.size();
|
int mapLen = columnsMap.size();
|
||||||
for (int i = 0; i < mapLen; i++) {
|
for (int i = 0; i < mapLen; i++) {
|
||||||
insertSql.append("?,");
|
insertSql.append("?,");
|
||||||
}
|
|
||||||
len = insertSql.length();
|
|
||||||
insertSql.delete(len - 1, len).append(")");
|
|
||||||
try (Connection connTarget = targetDS.getConnection();
|
|
||||||
PreparedStatement pstmt = connTarget.prepareStatement(insertSql.toString())) {
|
|
||||||
connTarget.setAutoCommit(false);
|
|
||||||
|
|
||||||
while (rs.next()) {
|
|
||||||
pstmt.clearParameters();
|
|
||||||
|
|
||||||
// 删除数据
|
|
||||||
Map<String, Object> values = new LinkedHashMap<>();
|
|
||||||
StringBuilder deleteSql = new StringBuilder(
|
|
||||||
"DELETE FROM " + dbMapping.getTargetTable() + " WHERE ");
|
|
||||||
appendCondition(dbMapping, deleteSql, values, rs);
|
|
||||||
try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) {
|
|
||||||
int k = 1;
|
|
||||||
for (Object val : values.values()) {
|
|
||||||
pstmt2.setObject(k++, val);
|
|
||||||
}
|
|
||||||
pstmt2.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
int i = 1;
|
|
||||||
for (Map.Entry<String, String> entry : columnsMap.entrySet()) {
|
|
||||||
String targetClolumnName = entry.getKey();
|
|
||||||
String srcColumnName = entry.getValue();
|
|
||||||
if (srcColumnName == null) {
|
|
||||||
srcColumnName = targetClolumnName;
|
|
||||||
}
|
|
||||||
|
|
||||||
Integer type = columnType.get(targetClolumnName.toLowerCase());
|
|
||||||
|
|
||||||
Object value = rs.getObject(srcColumnName);
|
|
||||||
if (value != null) {
|
|
||||||
SyncUtil.setPStmt(type, pstmt, value, i);
|
|
||||||
} else {
|
|
||||||
pstmt.setNull(i, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
pstmt.execute();
|
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
logger.trace("Insert into target table, sql: {}", insertSql);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idx % dbMapping.getCommitBatch() == 0) {
|
|
||||||
connTarget.commit();
|
|
||||||
completed = true;
|
|
||||||
}
|
|
||||||
idx++;
|
|
||||||
successCount.incrementAndGet();
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("successful import count:" + successCount.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!completed) {
|
|
||||||
connTarget.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error(dbMapping.getTable() + " etl failed! ==>" + e.getMessage(), e);
|
|
||||||
errMsg.add(dbMapping.getTable() + " etl failed! ==>" + e.getMessage());
|
|
||||||
}
|
}
|
||||||
return idx;
|
len = insertSql.length();
|
||||||
});
|
insertSql.delete(len - 1, len).append(")");
|
||||||
|
try (Connection connTarget = targetDS.getConnection();
|
||||||
|
PreparedStatement pstmt = connTarget.prepareStatement(insertSql.toString())) {
|
||||||
|
connTarget.setAutoCommit(false);
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
pstmt.clearParameters();
|
||||||
|
|
||||||
|
// 删除数据
|
||||||
|
Map<String, Object> values = new LinkedHashMap<>();
|
||||||
|
StringBuilder deleteSql = new StringBuilder("DELETE FROM " + SyncUtil.getDbTableName(dbMapping)
|
||||||
|
+ " WHERE ");
|
||||||
|
appendCondition(dbMapping, deleteSql, values, rs);
|
||||||
|
try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) {
|
||||||
|
int k = 1;
|
||||||
|
for (Object val : values.values()) {
|
||||||
|
pstmt2.setObject(k++, val);
|
||||||
|
}
|
||||||
|
pstmt2.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
for (Map.Entry<String, String> entry : columnsMap.entrySet()) {
|
||||||
|
String targetClolumnName = entry.getKey();
|
||||||
|
String srcColumnName = entry.getValue();
|
||||||
|
if (srcColumnName == null) {
|
||||||
|
srcColumnName = targetClolumnName;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer type = columnType.get(targetClolumnName.toLowerCase());
|
||||||
|
|
||||||
|
Object value = rs.getObject(srcColumnName);
|
||||||
|
if (value != null) {
|
||||||
|
SyncUtil.setPStmt(type, pstmt, value, i);
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
pstmt.execute();
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Insert into target table, sql: {}", insertSql);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx % dbMapping.getCommitBatch() == 0) {
|
||||||
|
connTarget.commit();
|
||||||
|
completed = true;
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
successCount.incrementAndGet();
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("successful import count:" + successCount.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!completed) {
|
||||||
|
connTarget.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(dbMapping.getTable() + " etl failed! ==>" + e.getMessage(), e);
|
||||||
|
errMsg.add(dbMapping.getTable() + " etl failed! ==>" + e.getMessage());
|
||||||
|
}
|
||||||
|
return idx;
|
||||||
|
} );
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
@@ -275,8 +278,8 @@ public class RdbEtlService {
|
|||||||
/**
|
/**
|
||||||
* 拼接目标表主键where条件
|
* 拼接目标表主键where条件
|
||||||
*/
|
*/
|
||||||
private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map<String, Object> values,
|
private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map<String, Object> values, ResultSet rs)
|
||||||
ResultSet rs) throws SQLException {
|
throws SQLException {
|
||||||
// 拼接主键
|
// 拼接主键
|
||||||
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) {
|
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) {
|
||||||
String targetColumnName = entry.getKey();
|
String targetColumnName = entry.getKey();
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb.service;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MirrorDbConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.support.SingleDml;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RDB镜像库同步操作业务
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-12-12 下午011:23
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class RdbMirrorDbSyncService {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RdbMirrorDbSyncService.class);
|
||||||
|
|
||||||
|
private Map<String, MirrorDbConfig> mirrorDbConfigCache; // 镜像库配置
|
||||||
|
private DataSource dataSource;
|
||||||
|
private RdbSyncService rdbSyncService; // rdbSyncService代理
|
||||||
|
|
||||||
|
public RdbMirrorDbSyncService(Map<String, MirrorDbConfig> mirrorDbConfigCache, DataSource dataSource,
|
||||||
|
Integer threads, Map<String, Map<String, Integer>> columnsTypeCache,
|
||||||
|
boolean skipDupException){
|
||||||
|
this.mirrorDbConfigCache = mirrorDbConfigCache;
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
this.rdbSyncService = new RdbSyncService(dataSource, threads, columnsTypeCache, skipDupException);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量同步方法
|
||||||
|
*
|
||||||
|
* @param dmls 批量 DML
|
||||||
|
*/
|
||||||
|
public void sync(List<Dml> dmls) {
|
||||||
|
List<Dml> dmlList = new ArrayList<>();
|
||||||
|
for (Dml dml : dmls) {
|
||||||
|
String destination = StringUtils.trimToEmpty(dml.getDestination());
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
MirrorDbConfig mirrorDbConfig = mirrorDbConfigCache.get(destination + "." + database);
|
||||||
|
if (mirrorDbConfig == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (dml.getIsDdl() != null && dml.getIsDdl() && StringUtils.isNotEmpty(dml.getSql())) {
|
||||||
|
// DDL
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("DDL: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
||||||
|
}
|
||||||
|
executeDdl(mirrorDbConfig, dml);
|
||||||
|
rdbSyncService.getColumnsTypeCache().remove(destination + "." + database + "." + dml.getTable());
|
||||||
|
mirrorDbConfig.getTableConfig().remove(dml.getTable()); // 删除对应库表配置
|
||||||
|
} else {
|
||||||
|
// DML
|
||||||
|
initMappingConfig(dml.getTable(), mirrorDbConfig.getMappingConfig(), mirrorDbConfig, dml);
|
||||||
|
dmlList.add(dml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!dmlList.isEmpty()) {
|
||||||
|
rdbSyncService.sync(dmlList,
|
||||||
|
dml -> {
|
||||||
|
MirrorDbConfig mirrorDbConfig = mirrorDbConfigCache.get(dml.getDestination() + "."
|
||||||
|
+ dml.getDatabase());
|
||||||
|
if (mirrorDbConfig == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String table = dml.getTable();
|
||||||
|
MappingConfig config = mirrorDbConfig.getTableConfig().get(table);
|
||||||
|
|
||||||
|
if (config == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.getConcurrent()) {
|
||||||
|
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml);
|
||||||
|
singleDmls.forEach(singleDml -> {
|
||||||
|
int hash = rdbSyncService.pkHash(config.getDbMapping(), singleDml.getData());
|
||||||
|
RdbSyncService.SyncItem syncItem = new RdbSyncService.SyncItem(config, singleDml);
|
||||||
|
rdbSyncService.getDmlsPartition()[hash].add(syncItem);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
int hash = 0;
|
||||||
|
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml);
|
||||||
|
singleDmls.forEach(singleDml -> {
|
||||||
|
RdbSyncService.SyncItem syncItem = new RdbSyncService.SyncItem(config, singleDml);
|
||||||
|
rdbSyncService.getDmlsPartition()[hash].add(syncItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化表配置
|
||||||
|
*
|
||||||
|
* @param key 配置key: destination.database.table
|
||||||
|
* @param baseConfigMap db sync config
|
||||||
|
* @param dml DML
|
||||||
|
*/
|
||||||
|
private void initMappingConfig(String key, MappingConfig baseConfigMap, MirrorDbConfig mirrorDbConfig, Dml dml) {
|
||||||
|
MappingConfig mappingConfig = mirrorDbConfig.getTableConfig().get(key);
|
||||||
|
if (mappingConfig == null) {
|
||||||
|
// 构造表配置
|
||||||
|
mappingConfig = new MappingConfig();
|
||||||
|
mappingConfig.setDataSourceKey(baseConfigMap.getDataSourceKey());
|
||||||
|
mappingConfig.setDestination(baseConfigMap.getDestination());
|
||||||
|
mappingConfig.setOuterAdapterKey(baseConfigMap.getOuterAdapterKey());
|
||||||
|
mappingConfig.setConcurrent(baseConfigMap.getConcurrent());
|
||||||
|
MappingConfig.DbMapping dbMapping = new MappingConfig.DbMapping();
|
||||||
|
mappingConfig.setDbMapping(dbMapping);
|
||||||
|
dbMapping.setDatabase(dml.getDatabase());
|
||||||
|
dbMapping.setTable(dml.getTable());
|
||||||
|
dbMapping.setTargetDb(dml.getDatabase());
|
||||||
|
dbMapping.setTargetTable(dml.getTable());
|
||||||
|
dbMapping.setMapAll(true);
|
||||||
|
List<String> pkNames = dml.getPkNames();
|
||||||
|
Map<String, String> pkMapping = new LinkedHashMap<>();
|
||||||
|
pkNames.forEach(pkName -> pkMapping.put(pkName, pkName));
|
||||||
|
dbMapping.setTargetPk(pkMapping);
|
||||||
|
|
||||||
|
mirrorDbConfig.getTableConfig().put(key, mappingConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DDL 操作
|
||||||
|
*
|
||||||
|
* @param ddl DDL
|
||||||
|
*/
|
||||||
|
private void executeDdl(MirrorDbConfig mirrorDbConfig, Dml ddl) {
|
||||||
|
try (Connection conn = dataSource.getConnection(); Statement statement = conn.createStatement()) {
|
||||||
|
statement.execute(ddl.getSql());
|
||||||
|
// 移除对应配置
|
||||||
|
mirrorDbConfig.getTableConfig().remove(ddl.getTable());
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Execute DDL sql: {} for database: {}", ddl.getSql(), ddl.getDatabase());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,9 +8,11 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
@@ -36,26 +38,40 @@ import com.alibaba.otter.canal.client.adapter.support.Util;
|
|||||||
*/
|
*/
|
||||||
public class RdbSyncService {
|
public class RdbSyncService {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RdbSyncService.class);
|
private static final Logger logger = LoggerFactory.getLogger(RdbSyncService.class);
|
||||||
|
|
||||||
private final Map<String, Map<String, Integer>> COLUMNS_TYPE_CACHE = new ConcurrentHashMap<>();
|
// 源库表字段类型缓存: instance.schema.table -> <columnName, jdbcType>
|
||||||
|
private Map<String, Map<String, Integer>> columnsTypeCache;
|
||||||
|
|
||||||
private Map<String, Map<String, MappingConfig>> mappingConfigCache; // 库名-表名对应配置
|
private int threads = 3;
|
||||||
|
private boolean skipDupException;
|
||||||
|
|
||||||
private int threads = 3;
|
private List<SyncItem>[] dmlsPartition;
|
||||||
|
private BatchExecutor[] batchExecutors;
|
||||||
|
private ExecutorService[] executorThreads;
|
||||||
|
|
||||||
private List<SyncItem>[] dmlsPartition;
|
public List<SyncItem>[] getDmlsPartition() {
|
||||||
private BatchExecutor[] batchExecutors;
|
return dmlsPartition;
|
||||||
private ExecutorService[] executorThreads;
|
}
|
||||||
|
|
||||||
|
public Map<String, Map<String, Integer>> getColumnsTypeCache() {
|
||||||
|
return columnsTypeCache;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public RdbSyncService(Map<String, Map<String, MappingConfig>> mappingConfigCache, DataSource dataSource,
|
public RdbSyncService(DataSource dataSource, Integer threads, boolean skipDupException){
|
||||||
Integer threads){
|
this(dataSource, threads, new ConcurrentHashMap<>(), skipDupException);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public RdbSyncService(DataSource dataSource, Integer threads, Map<String, Map<String, Integer>> columnsTypeCache,
|
||||||
|
boolean skipDupException){
|
||||||
|
this.columnsTypeCache = columnsTypeCache;
|
||||||
|
this.skipDupException = skipDupException;
|
||||||
try {
|
try {
|
||||||
if (threads != null) {
|
if (threads != null) {
|
||||||
this.threads = threads;
|
this.threads = threads;
|
||||||
}
|
}
|
||||||
this.mappingConfigCache = mappingConfigCache;
|
|
||||||
this.dmlsPartition = new List[this.threads];
|
this.dmlsPartition = new List[this.threads];
|
||||||
this.batchExecutors = new BatchExecutor[this.threads];
|
this.batchExecutors = new BatchExecutor[this.threads];
|
||||||
this.executorThreads = new ExecutorService[this.threads];
|
this.executorThreads = new ExecutorService[this.threads];
|
||||||
@@ -65,67 +81,111 @@ public class RdbSyncService {
|
|||||||
executorThreads[i] = Executors.newSingleThreadExecutor();
|
executorThreads[i] = Executors.newSingleThreadExecutor();
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
logger.error(e.getMessage(), e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sync(List<Dml> dmls) {
|
/**
|
||||||
try {
|
* 批量同步回调
|
||||||
for (Dml dml : dmls) {
|
*
|
||||||
String destination = StringUtils.trimToEmpty(dml.getDestination());
|
* @param dmls 批量 DML
|
||||||
String database = dml.getDatabase();
|
* @param function 回调方法
|
||||||
String table = dml.getTable();
|
*/
|
||||||
Map<String, MappingConfig> configMap = mappingConfigCache
|
public void sync(List<Dml> dmls, Function<Dml, Boolean> function) {
|
||||||
.get(destination + "." + database + "." + table);
|
boolean toExecute = false;
|
||||||
|
for (Dml dml : dmls) {
|
||||||
for (MappingConfig config : configMap.values()) {
|
if (!toExecute) {
|
||||||
|
toExecute = function.apply(dml);
|
||||||
if (config.getConcurrent()) {
|
} else {
|
||||||
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml);
|
function.apply(dml);
|
||||||
singleDmls.forEach(singleDml -> {
|
|
||||||
int hash = pkHash(config.getDbMapping(), singleDml.getData());
|
|
||||||
SyncItem syncItem = new SyncItem(config, singleDml);
|
|
||||||
dmlsPartition[hash].add(syncItem);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
int hash = Math.abs(Math.abs(config.getDbMapping().getTargetTable().hashCode()) % threads);
|
|
||||||
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml);
|
|
||||||
singleDmls.forEach(singleDml -> {
|
|
||||||
SyncItem syncItem = new SyncItem(config, singleDml);
|
|
||||||
dmlsPartition[hash].add(syncItem);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (toExecute) {
|
||||||
List<Future> futures = new ArrayList<>();
|
List<Future> futures = new ArrayList<>();
|
||||||
for (int i = 0; i < threads; i++) {
|
for (int i = 0; i < threads; i++) {
|
||||||
int j = i;
|
int j = i;
|
||||||
futures.add(executorThreads[i].submit(() -> {
|
futures.add(executorThreads[i].submit(() -> {
|
||||||
dmlsPartition[j].forEach(syncItem -> sync(batchExecutors[j], syncItem.config, syncItem.singleDml));
|
try {
|
||||||
batchExecutors[j].commit();
|
dmlsPartition[j].forEach(syncItem -> sync(batchExecutors[j],
|
||||||
return true;
|
syncItem.config,
|
||||||
|
syncItem.singleDml));
|
||||||
|
dmlsPartition[j].clear();
|
||||||
|
batchExecutors[j].commit();
|
||||||
|
return true;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
batchExecutors[j].rollback();
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
futures.forEach(future -> {
|
futures.forEach(future -> {
|
||||||
try {
|
try {
|
||||||
future.get();
|
future.get();
|
||||||
} catch (Exception e) {
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
logger.error(e.getMessage(), e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (int i = 0; i < threads; i++) {
|
|
||||||
dmlsPartition[i].clear();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sync(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) {
|
/**
|
||||||
try {
|
* 批量同步
|
||||||
if (config != null) {
|
*
|
||||||
|
* @param mappingConfig 配置集合
|
||||||
|
* @param dmls 批量 DML
|
||||||
|
*/
|
||||||
|
public void sync(Map<String, Map<String, MappingConfig>> mappingConfig, List<Dml> dmls) {
|
||||||
|
sync(dmls, dml -> {
|
||||||
|
if (dml.getIsDdl() != null && dml.getIsDdl() && StringUtils.isNotEmpty(dml.getSql())) {
|
||||||
|
// DDL
|
||||||
|
columnsTypeCache.remove(dml.getDestination() + "." + dml.getDatabase() + "." + dml.getTable());
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// DML
|
||||||
|
String destination = StringUtils.trimToEmpty(dml.getDestination());
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, MappingConfig> configMap = mappingConfig.get(destination + "." + database + "." + table);
|
||||||
|
|
||||||
|
if (configMap == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean executed = false;
|
||||||
|
for (MappingConfig config : configMap.values()) {
|
||||||
|
if (config.getConcurrent()) {
|
||||||
|
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml);
|
||||||
|
singleDmls.forEach(singleDml -> {
|
||||||
|
int hash = pkHash(config.getDbMapping(), singleDml.getData());
|
||||||
|
SyncItem syncItem = new SyncItem(config, singleDml);
|
||||||
|
dmlsPartition[hash].add(syncItem);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
int hash = 0;
|
||||||
|
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml);
|
||||||
|
singleDmls.forEach(singleDml -> {
|
||||||
|
SyncItem syncItem = new SyncItem(config, singleDml);
|
||||||
|
dmlsPartition[hash].add(syncItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
executed = true;
|
||||||
|
}
|
||||||
|
return executed;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单条 dml 同步
|
||||||
|
*
|
||||||
|
* @param batchExecutor 批量事务执行器
|
||||||
|
* @param config 对应配置对象
|
||||||
|
* @param dml DML
|
||||||
|
*/
|
||||||
|
public void sync(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) {
|
||||||
|
if (config != null) {
|
||||||
|
try {
|
||||||
String type = dml.getType();
|
String type = dml.getType();
|
||||||
if (type != null && type.equalsIgnoreCase("INSERT")) {
|
if (type != null && type.equalsIgnoreCase("INSERT")) {
|
||||||
insert(batchExecutor, config, dml);
|
insert(batchExecutor, config, dml);
|
||||||
@@ -137,9 +197,9 @@ public class RdbSyncService {
|
|||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
||||||
}
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +209,7 @@ public class RdbSyncService {
|
|||||||
* @param config 配置项
|
* @param config 配置项
|
||||||
* @param dml DML数据
|
* @param dml DML数据
|
||||||
*/
|
*/
|
||||||
private void insert(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) {
|
private void insert(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) throws SQLException {
|
||||||
Map<String, Object> data = dml.getData();
|
Map<String, Object> data = dml.getData();
|
||||||
if (data == null || data.isEmpty()) {
|
if (data == null || data.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
@@ -157,47 +217,54 @@ public class RdbSyncService {
|
|||||||
|
|
||||||
DbMapping dbMapping = config.getDbMapping();
|
DbMapping dbMapping = config.getDbMapping();
|
||||||
|
|
||||||
try {
|
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data);
|
||||||
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data);
|
|
||||||
|
|
||||||
StringBuilder insertSql = new StringBuilder();
|
StringBuilder insertSql = new StringBuilder();
|
||||||
insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" (");
|
insertSql.append("INSERT INTO ").append(SyncUtil.getDbTableName(dbMapping)).append(" (");
|
||||||
|
|
||||||
columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(","));
|
columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(","));
|
||||||
int len = insertSql.length();
|
int len = insertSql.length();
|
||||||
insertSql.delete(len - 1, len).append(") VALUES (");
|
insertSql.delete(len - 1, len).append(") VALUES (");
|
||||||
int mapLen = columnsMap.size();
|
int mapLen = columnsMap.size();
|
||||||
for (int i = 0; i < mapLen; i++) {
|
for (int i = 0; i < mapLen; i++) {
|
||||||
insertSql.append("?,");
|
insertSql.append("?,");
|
||||||
}
|
|
||||||
len = insertSql.length();
|
|
||||||
insertSql.delete(len - 1, len).append(")");
|
|
||||||
|
|
||||||
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
|
|
||||||
|
|
||||||
List<Map<String, ?>> values = new ArrayList<>();
|
|
||||||
for (Map.Entry<String, String> entry : columnsMap.entrySet()) {
|
|
||||||
String targetClolumnName = entry.getKey();
|
|
||||||
String srcColumnName = entry.getValue();
|
|
||||||
if (srcColumnName == null) {
|
|
||||||
srcColumnName = targetClolumnName;
|
|
||||||
}
|
|
||||||
|
|
||||||
Integer type = ctype.get(targetClolumnName.toLowerCase());
|
|
||||||
|
|
||||||
Object value = data.get(srcColumnName);
|
|
||||||
|
|
||||||
BatchExecutor.setValue(values, type, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
batchExecutor.execute(insertSql.toString(), values);
|
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
logger.trace("Insert into target table, sql: {}", insertSql);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
|
len = insertSql.length();
|
||||||
|
insertSql.delete(len - 1, len).append(")");
|
||||||
|
|
||||||
|
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
|
||||||
|
|
||||||
|
List<Map<String, ?>> values = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, String> entry : columnsMap.entrySet()) {
|
||||||
|
String targetColumnName = entry.getKey();
|
||||||
|
String srcColumnName = entry.getValue();
|
||||||
|
if (srcColumnName == null) {
|
||||||
|
srcColumnName = Util.cleanColumn(targetColumnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer type = ctype.get(Util.cleanColumn(targetColumnName).toLowerCase());
|
||||||
|
if (type == null) {
|
||||||
|
throw new RuntimeException("Target column: " + targetColumnName + " not matched");
|
||||||
|
}
|
||||||
|
Object value = data.get(srcColumnName);
|
||||||
|
BatchExecutor.setValue(values, type, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
batchExecutor.execute(insertSql.toString(), values);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (skipDupException
|
||||||
|
&& (e.getMessage().contains("Duplicate entry") || e.getMessage().startsWith("ORA-00001: 违反唯一约束条件"))) {
|
||||||
|
// ignore
|
||||||
|
// TODO 增加更多关系数据库的主键冲突的错误码
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Insert into target table, sql: {}", insertSql);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -206,7 +273,7 @@ public class RdbSyncService {
|
|||||||
* @param config 配置项
|
* @param config 配置项
|
||||||
* @param dml DML数据
|
* @param dml DML数据
|
||||||
*/
|
*/
|
||||||
private void update(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) {
|
private void update(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) throws SQLException {
|
||||||
Map<String, Object> data = dml.getData();
|
Map<String, Object> data = dml.getData();
|
||||||
if (data == null || data.isEmpty()) {
|
if (data == null || data.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
@@ -219,43 +286,40 @@ public class RdbSyncService {
|
|||||||
|
|
||||||
DbMapping dbMapping = config.getDbMapping();
|
DbMapping dbMapping = config.getDbMapping();
|
||||||
|
|
||||||
try {
|
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data);
|
||||||
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data);
|
|
||||||
|
|
||||||
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
|
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
|
||||||
|
|
||||||
StringBuilder updateSql = new StringBuilder();
|
StringBuilder updateSql = new StringBuilder();
|
||||||
updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET ");
|
updateSql.append("UPDATE ").append(SyncUtil.getDbTableName(dbMapping)).append(" SET ");
|
||||||
List<Map<String, ?>> values = new ArrayList<>();
|
List<Map<String, ?>> values = new ArrayList<>();
|
||||||
for (String srcColumnName : old.keySet()) {
|
for (String srcColumnName : old.keySet()) {
|
||||||
List<String> targetColumnNames = new ArrayList<>();
|
List<String> targetColumnNames = new ArrayList<>();
|
||||||
columnsMap.forEach((targetColumn, srcColumn) -> {
|
columnsMap.forEach((targetColumn, srcColumn) -> {
|
||||||
if (srcColumnName.toLowerCase().equals(srcColumn)) {
|
if (srcColumnName.toLowerCase().equals(srcColumn)) {
|
||||||
targetColumnNames.add(targetColumn);
|
targetColumnNames.add(targetColumn);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!targetColumnNames.isEmpty()) {
|
if (!targetColumnNames.isEmpty()) {
|
||||||
|
|
||||||
for (String targetColumnName : targetColumnNames) {
|
for (String targetColumnName : targetColumnNames) {
|
||||||
updateSql.append(targetColumnName).append("=?, ");
|
updateSql.append(targetColumnName).append("=?, ");
|
||||||
Integer type = ctype.get(targetColumnName.toLowerCase());
|
Integer type = ctype.get(Util.cleanColumn(targetColumnName).toLowerCase());
|
||||||
BatchExecutor.setValue(values, type, data.get(srcColumnName));
|
if (type == null) {
|
||||||
|
throw new RuntimeException("Target column: " + targetColumnName + " not matched");
|
||||||
}
|
}
|
||||||
|
BatchExecutor.setValue(values, type, data.get(srcColumnName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int len = updateSql.length();
|
}
|
||||||
updateSql.delete(len - 2, len).append(" WHERE ");
|
int len = updateSql.length();
|
||||||
|
updateSql.delete(len - 2, len).append(" WHERE ");
|
||||||
|
|
||||||
// 拼接主键
|
// 拼接主键
|
||||||
appendCondition(dbMapping, updateSql, ctype, values, data, old);
|
appendCondition(dbMapping, updateSql, ctype, values, data, old);
|
||||||
|
batchExecutor.execute(updateSql.toString(), values);
|
||||||
batchExecutor.execute(updateSql.toString(), values);
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Update target table, sql: {}", updateSql);
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
logger.trace("Update target table, sql: {}", updateSql);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +329,7 @@ public class RdbSyncService {
|
|||||||
* @param config
|
* @param config
|
||||||
* @param dml
|
* @param dml
|
||||||
*/
|
*/
|
||||||
private void delete(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) {
|
private void delete(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) throws SQLException {
|
||||||
Map<String, Object> data = dml.getData();
|
Map<String, Object> data = dml.getData();
|
||||||
if (data == null || data.isEmpty()) {
|
if (data == null || data.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
@@ -273,23 +337,17 @@ public class RdbSyncService {
|
|||||||
|
|
||||||
DbMapping dbMapping = config.getDbMapping();
|
DbMapping dbMapping = config.getDbMapping();
|
||||||
|
|
||||||
try {
|
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
|
||||||
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
|
|
||||||
|
|
||||||
StringBuilder sql = new StringBuilder();
|
StringBuilder sql = new StringBuilder();
|
||||||
sql.append("DELETE FROM ").append(dbMapping.getTargetTable()).append(" WHERE ");
|
sql.append("DELETE FROM ").append(SyncUtil.getDbTableName(dbMapping)).append(" WHERE ");
|
||||||
|
|
||||||
List<Map<String, ?>> values = new ArrayList<>();
|
List<Map<String, ?>> values = new ArrayList<>();
|
||||||
// 拼接主键
|
// 拼接主键
|
||||||
appendCondition(dbMapping, sql, ctype, values, data);
|
appendCondition(dbMapping, sql, ctype, values, data);
|
||||||
|
batchExecutor.execute(sql.toString(), values);
|
||||||
batchExecutor.execute(sql.toString(), values);
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Delete from target table, sql: {}", sql);
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
logger.trace("Delete from target table, sql: {}", sql);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,14 +361,14 @@ public class RdbSyncService {
|
|||||||
private Map<String, Integer> getTargetColumnType(Connection conn, MappingConfig config) {
|
private Map<String, Integer> getTargetColumnType(Connection conn, MappingConfig config) {
|
||||||
DbMapping dbMapping = config.getDbMapping();
|
DbMapping dbMapping = config.getDbMapping();
|
||||||
String cacheKey = config.getDestination() + "." + dbMapping.getDatabase() + "." + dbMapping.getTable();
|
String cacheKey = config.getDestination() + "." + dbMapping.getDatabase() + "." + dbMapping.getTable();
|
||||||
Map<String, Integer> columnType = COLUMNS_TYPE_CACHE.get(cacheKey);
|
Map<String, Integer> columnType = columnsTypeCache.get(cacheKey);
|
||||||
if (columnType == null) {
|
if (columnType == null) {
|
||||||
synchronized (RdbSyncService.class) {
|
synchronized (RdbSyncService.class) {
|
||||||
columnType = COLUMNS_TYPE_CACHE.get(cacheKey);
|
columnType = columnsTypeCache.get(cacheKey);
|
||||||
if (columnType == null) {
|
if (columnType == null) {
|
||||||
columnType = new LinkedHashMap<>();
|
columnType = new LinkedHashMap<>();
|
||||||
final Map<String, Integer> columnTypeTmp = columnType;
|
final Map<String, Integer> columnTypeTmp = columnType;
|
||||||
String sql = "SELECT * FROM " + dbMapping.getTargetTable() + " WHERE 1=2";
|
String sql = "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping) + " WHERE 1=2";
|
||||||
Util.sqlRS(conn, sql, rs -> {
|
Util.sqlRS(conn, sql, rs -> {
|
||||||
try {
|
try {
|
||||||
ResultSetMetaData rsd = rs.getMetaData();
|
ResultSetMetaData rsd = rs.getMetaData();
|
||||||
@@ -318,7 +376,7 @@ public class RdbSyncService {
|
|||||||
for (int i = 1; i <= columnCount; i++) {
|
for (int i = 1; i <= columnCount; i++) {
|
||||||
columnTypeTmp.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i));
|
columnTypeTmp.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i));
|
||||||
}
|
}
|
||||||
COLUMNS_TYPE_CACHE.put(cacheKey, columnTypeTmp);
|
columnsTypeCache.put(cacheKey, columnTypeTmp);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
@@ -344,10 +402,13 @@ public class RdbSyncService {
|
|||||||
String targetColumnName = entry.getKey();
|
String targetColumnName = entry.getKey();
|
||||||
String srcColumnName = entry.getValue();
|
String srcColumnName = entry.getValue();
|
||||||
if (srcColumnName == null) {
|
if (srcColumnName == null) {
|
||||||
srcColumnName = targetColumnName;
|
srcColumnName = Util.cleanColumn(targetColumnName);
|
||||||
}
|
}
|
||||||
sql.append(targetColumnName).append("=? AND ");
|
sql.append(targetColumnName).append("=? AND ");
|
||||||
Integer type = ctype.get(targetColumnName.toLowerCase());
|
Integer type = ctype.get(Util.cleanColumn(targetColumnName).toLowerCase());
|
||||||
|
if (type == null) {
|
||||||
|
throw new RuntimeException("Target column: " + targetColumnName + " not matched");
|
||||||
|
}
|
||||||
// 如果有修改主键的情况
|
// 如果有修改主键的情况
|
||||||
if (o != null && o.containsKey(srcColumnName)) {
|
if (o != null && o.containsKey(srcColumnName)) {
|
||||||
BatchExecutor.setValue(values, type, o.get(srcColumnName));
|
BatchExecutor.setValue(values, type, o.get(srcColumnName));
|
||||||
@@ -359,12 +420,12 @@ public class RdbSyncService {
|
|||||||
sql.delete(len - 4, len);
|
sql.delete(len - 4, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SyncItem {
|
public static class SyncItem {
|
||||||
|
|
||||||
private MappingConfig config;
|
private MappingConfig config;
|
||||||
private SingleDml singleDml;
|
private SingleDml singleDml;
|
||||||
|
|
||||||
private SyncItem(MappingConfig config, SingleDml singleDml){
|
public SyncItem(MappingConfig config, SingleDml singleDml){
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.singleDml = singleDml;
|
this.singleDml = singleDml;
|
||||||
}
|
}
|
||||||
@@ -373,18 +434,18 @@ public class RdbSyncService {
|
|||||||
/**
|
/**
|
||||||
* 取主键hash
|
* 取主键hash
|
||||||
*/
|
*/
|
||||||
private int pkHash(DbMapping dbMapping, Map<String, Object> d) {
|
public int pkHash(DbMapping dbMapping, Map<String, Object> d) {
|
||||||
return pkHash(dbMapping, d, null);
|
return pkHash(dbMapping, d, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int pkHash(DbMapping dbMapping, Map<String, Object> d, Map<String, Object> o) {
|
public int pkHash(DbMapping dbMapping, Map<String, Object> d, Map<String, Object> o) {
|
||||||
int hash = 0;
|
int hash = 0;
|
||||||
// 取主键
|
// 取主键
|
||||||
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) {
|
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) {
|
||||||
String targetColumnName = entry.getKey();
|
String targetColumnName = entry.getKey();
|
||||||
String srcColumnName = entry.getValue();
|
String srcColumnName = entry.getValue();
|
||||||
if (srcColumnName == null) {
|
if (srcColumnName == null) {
|
||||||
srcColumnName = targetColumnName;
|
srcColumnName = Util.cleanColumn(targetColumnName);
|
||||||
}
|
}
|
||||||
Object value;
|
Object value;
|
||||||
if (o != null && o.containsKey(srcColumnName)) {
|
if (o != null && o.containsKey(srcColumnName)) {
|
||||||
|
|||||||
@@ -16,27 +16,12 @@ public class BatchExecutor implements Closeable {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(BatchExecutor.class);
|
private static final Logger logger = LoggerFactory.getLogger(BatchExecutor.class);
|
||||||
|
|
||||||
private Integer key;
|
|
||||||
private Connection conn;
|
private Connection conn;
|
||||||
private AtomicInteger idx = new AtomicInteger(0);
|
private AtomicInteger idx = new AtomicInteger(0);
|
||||||
|
|
||||||
public BatchExecutor(Connection conn){
|
public BatchExecutor(Connection conn) throws SQLException{
|
||||||
this(1, conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BatchExecutor(Integer key, Connection conn){
|
|
||||||
this.key = key;
|
|
||||||
this.conn = conn;
|
this.conn = conn;
|
||||||
|
this.conn.setAutoCommit(false);
|
||||||
try {
|
|
||||||
this.conn.setAutoCommit(false);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getKey() {
|
|
||||||
return key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Connection getConn() {
|
public Connection getConn() {
|
||||||
@@ -50,41 +35,43 @@ public class BatchExecutor implements Closeable {
|
|||||||
values.add(valueItem);
|
values.add(valueItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void execute(String sql, List<Map<String, ?>> values) {
|
public void execute(String sql, List<Map<String, ?>> values) throws SQLException {
|
||||||
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
|
PreparedStatement pstmt = conn.prepareStatement(sql);
|
||||||
int len = values.size();
|
int len = values.size();
|
||||||
for (int i = 0; i < len; i++) {
|
for (int i = 0; i < len; i++) {
|
||||||
int type = (Integer) values.get(i).get("type");
|
int type = (Integer) values.get(i).get("type");
|
||||||
Object value = values.get(i).get("value");
|
Object value = values.get(i).get("value");
|
||||||
SyncUtil.setPStmt(type, pstmt, value, i + 1);
|
SyncUtil.setPStmt(type, pstmt, value, i + 1);
|
||||||
}
|
|
||||||
|
|
||||||
pstmt.execute();
|
|
||||||
idx.incrementAndGet();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pstmt.execute();
|
||||||
|
idx.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void commit() {
|
public void commit() throws SQLException {
|
||||||
try {
|
conn.commit();
|
||||||
conn.commit();
|
if (logger.isTraceEnabled()) {
|
||||||
if (logger.isTraceEnabled()) {
|
logger.trace("Batch executor commit " + idx.get() + " rows");
|
||||||
logger.trace("Batch executor: " + key + " commit " + idx.get() + " rows");
|
|
||||||
}
|
|
||||||
idx.set(0);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
|
idx.set(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rollback() throws SQLException {
|
||||||
|
conn.rollback();
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Batch executor rollback " + idx.get() + " rows");
|
||||||
|
}
|
||||||
|
idx.set(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (conn != null) {
|
if (conn != null) {
|
||||||
try {
|
try {
|
||||||
conn.close();
|
if (conn != null) {
|
||||||
} catch (SQLException e) {
|
conn.close();
|
||||||
logger.error(e.getMessage(), e);
|
}
|
||||||
|
} catch (SQLException ioe) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import java.util.Collection;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
|
||||||
@@ -21,7 +22,10 @@ public class SyncUtil {
|
|||||||
|
|
||||||
public static Map<String, String> getColumnsMap(MappingConfig.DbMapping dbMapping, Collection<String> columns) {
|
public static Map<String, String> getColumnsMap(MappingConfig.DbMapping dbMapping, Collection<String> columns) {
|
||||||
Map<String, String> columnsMap;
|
Map<String, String> columnsMap;
|
||||||
if (dbMapping.isMapAll()) {
|
if (dbMapping.getMapAll()) {
|
||||||
|
if (dbMapping.getAllMapColumns() != null) {
|
||||||
|
return dbMapping.getAllMapColumns();
|
||||||
|
}
|
||||||
columnsMap = new LinkedHashMap<>();
|
columnsMap = new LinkedHashMap<>();
|
||||||
for (String srcColumn : columns) {
|
for (String srcColumn : columns) {
|
||||||
boolean flag = true;
|
boolean flag = true;
|
||||||
@@ -38,6 +42,7 @@ public class SyncUtil {
|
|||||||
columnsMap.put(srcColumn, srcColumn);
|
columnsMap.put(srcColumn, srcColumn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
dbMapping.setAllMapColumns(columnsMap);
|
||||||
} else {
|
} else {
|
||||||
columnsMap = dbMapping.getTargetColumns();
|
columnsMap = dbMapping.getTargetColumns();
|
||||||
}
|
}
|
||||||
@@ -74,14 +79,14 @@ public class SyncUtil {
|
|||||||
case Types.LONGVARCHAR:
|
case Types.LONGVARCHAR:
|
||||||
if (value instanceof String) {
|
if (value instanceof String) {
|
||||||
pstmt.setString(i, (String) value);
|
pstmt.setString(i, (String) value);
|
||||||
|
} else if (value == null) {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
} else {
|
} else {
|
||||||
pstmt.setString(i, value.toString());
|
pstmt.setString(i, value.toString());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Types.TINYINT:
|
case Types.TINYINT:
|
||||||
if (value instanceof Byte || value instanceof Short || value instanceof Integer) {
|
if (value instanceof Number) {
|
||||||
pstmt.setByte(i, (byte) value);
|
|
||||||
} else if (value instanceof Number) {
|
|
||||||
pstmt.setByte(i, ((Number) value).byteValue());
|
pstmt.setByte(i, ((Number) value).byteValue());
|
||||||
} else if (value instanceof String) {
|
} else if (value instanceof String) {
|
||||||
pstmt.setByte(i, Byte.parseByte((String) value));
|
pstmt.setByte(i, Byte.parseByte((String) value));
|
||||||
@@ -90,9 +95,7 @@ public class SyncUtil {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Types.SMALLINT:
|
case Types.SMALLINT:
|
||||||
if (value instanceof Byte || value instanceof Short || value instanceof Integer) {
|
if (value instanceof Number) {
|
||||||
pstmt.setShort(i, (short) value);
|
|
||||||
} else if (value instanceof Number) {
|
|
||||||
pstmt.setShort(i, ((Number) value).shortValue());
|
pstmt.setShort(i, ((Number) value).shortValue());
|
||||||
} else if (value instanceof String) {
|
} else if (value instanceof String) {
|
||||||
pstmt.setShort(i, Short.parseShort((String) value));
|
pstmt.setShort(i, Short.parseShort((String) value));
|
||||||
@@ -101,10 +104,7 @@ public class SyncUtil {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Types.INTEGER:
|
case Types.INTEGER:
|
||||||
if (value instanceof Byte || value instanceof Short || value instanceof Integer
|
if (value instanceof Number) {
|
||||||
|| value instanceof Long) {
|
|
||||||
pstmt.setInt(i, (int) value);
|
|
||||||
} else if (value instanceof Number) {
|
|
||||||
pstmt.setInt(i, ((Number) value).intValue());
|
pstmt.setInt(i, ((Number) value).intValue());
|
||||||
} else if (value instanceof String) {
|
} else if (value instanceof String) {
|
||||||
pstmt.setInt(i, Integer.parseInt((String) value));
|
pstmt.setInt(i, Integer.parseInt((String) value));
|
||||||
@@ -113,10 +113,7 @@ public class SyncUtil {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Types.BIGINT:
|
case Types.BIGINT:
|
||||||
if (value instanceof Byte || value instanceof Short || value instanceof Integer
|
if (value instanceof Number) {
|
||||||
|| value instanceof Long) {
|
|
||||||
pstmt.setLong(i, (long) value);
|
|
||||||
} else if (value instanceof Number) {
|
|
||||||
pstmt.setLong(i, ((Number) value).longValue());
|
pstmt.setLong(i, ((Number) value).longValue());
|
||||||
} else if (value instanceof String) {
|
} else if (value instanceof String) {
|
||||||
pstmt.setLong(i, Long.parseLong((String) value));
|
pstmt.setLong(i, Long.parseLong((String) value));
|
||||||
@@ -129,26 +126,25 @@ public class SyncUtil {
|
|||||||
if (value instanceof BigDecimal) {
|
if (value instanceof BigDecimal) {
|
||||||
pstmt.setBigDecimal(i, (BigDecimal) value);
|
pstmt.setBigDecimal(i, (BigDecimal) value);
|
||||||
} else if (value instanceof Byte) {
|
} else if (value instanceof Byte) {
|
||||||
pstmt.setInt(i, (int) value);
|
pstmt.setInt(i, ((Byte) value).intValue());
|
||||||
} else if (value instanceof Short) {
|
} else if (value instanceof Short) {
|
||||||
pstmt.setInt(i, (int) value);
|
pstmt.setInt(i, ((Short) value).intValue());
|
||||||
} else if (value instanceof Integer) {
|
} else if (value instanceof Integer) {
|
||||||
pstmt.setInt(i, (int) value);
|
pstmt.setInt(i, (Integer) value);
|
||||||
} else if (value instanceof Long) {
|
} else if (value instanceof Long) {
|
||||||
pstmt.setLong(i, (long) value);
|
pstmt.setLong(i, (Long) value);
|
||||||
} else if (value instanceof Float) {
|
} else if (value instanceof Float) {
|
||||||
pstmt.setBigDecimal(i, new BigDecimal((float) value));
|
pstmt.setBigDecimal(i, new BigDecimal((float) value));
|
||||||
} else if (value instanceof Double) {
|
} else if (value instanceof Double) {
|
||||||
pstmt.setBigDecimal(i, new BigDecimal((double) value));
|
pstmt.setBigDecimal(i, new BigDecimal((double) value));
|
||||||
} else {
|
} else if (value != null) {
|
||||||
pstmt.setBigDecimal(i, new BigDecimal(value.toString()));
|
pstmt.setBigDecimal(i, new BigDecimal(value.toString()));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Types.REAL:
|
case Types.REAL:
|
||||||
if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long
|
if (value instanceof Number) {
|
||||||
|| value instanceof Float || value instanceof Double) {
|
|
||||||
pstmt.setFloat(i, (float) value);
|
|
||||||
} else if (value instanceof Number) {
|
|
||||||
pstmt.setFloat(i, ((Number) value).floatValue());
|
pstmt.setFloat(i, ((Number) value).floatValue());
|
||||||
} else if (value instanceof String) {
|
} else if (value instanceof String) {
|
||||||
pstmt.setFloat(i, Float.parseFloat((String) value));
|
pstmt.setFloat(i, Float.parseFloat((String) value));
|
||||||
@@ -158,10 +154,7 @@ public class SyncUtil {
|
|||||||
break;
|
break;
|
||||||
case Types.FLOAT:
|
case Types.FLOAT:
|
||||||
case Types.DOUBLE:
|
case Types.DOUBLE:
|
||||||
if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long
|
if (value instanceof Number) {
|
||||||
|| value instanceof Float || value instanceof Double) {
|
|
||||||
pstmt.setDouble(i, (double) value);
|
|
||||||
} else if (value instanceof Number) {
|
|
||||||
pstmt.setDouble(i, ((Number) value).doubleValue());
|
pstmt.setDouble(i, ((Number) value).doubleValue());
|
||||||
} else if (value instanceof String) {
|
} else if (value instanceof String) {
|
||||||
pstmt.setDouble(i, Double.parseDouble((String) value));
|
pstmt.setDouble(i, Double.parseDouble((String) value));
|
||||||
@@ -249,4 +242,13 @@ public class SyncUtil {
|
|||||||
pstmt.setObject(i, value, type);
|
pstmt.setObject(i, value, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getDbTableName(MappingConfig.DbMapping dbMapping) {
|
||||||
|
String result = "";
|
||||||
|
if (StringUtils.isNotEmpty(dbMapping.getTargetDb())) {
|
||||||
|
result += dbMapping.getTargetDb() + ".";
|
||||||
|
}
|
||||||
|
result += dbMapping.getTargetTable();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,20 @@ dbMapping:
|
|||||||
targetTable: mytest.tb_user
|
targetTable: mytest.tb_user
|
||||||
targetPk:
|
targetPk:
|
||||||
id: id
|
id: id
|
||||||
mapAll: true
|
# mapAll: true
|
||||||
# targetColumns:
|
targetColumns:
|
||||||
# id:
|
id:
|
||||||
# name:
|
name:
|
||||||
# role_id:
|
role_id:
|
||||||
# c_time:
|
c_time:
|
||||||
# test1:
|
test1:
|
||||||
|
|
||||||
|
|
||||||
|
# Mirror schema synchronize config
|
||||||
|
#dataSourceKey: defaultDS
|
||||||
|
#destination: example
|
||||||
|
#outerAdapterKey: mysql1
|
||||||
|
#concurrent: true
|
||||||
|
#dbMapping:
|
||||||
|
# mirrorDb: true
|
||||||
|
# database: mytest
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class DBTest {
|
|||||||
.prepareStatement("insert into user (id,name,role_id,c_time,test1,test2) values (?,?,?,?,?,?)");
|
.prepareStatement("insert into user (id,name,role_id,c_time,test1,test2) values (?,?,?,?,?,?)");
|
||||||
|
|
||||||
java.util.Date now = new java.util.Date();
|
java.util.Date now = new java.util.Date();
|
||||||
for (int i = 1; i <= 100000; i++) {
|
for (int i = 1; i <= 10000; i++) {
|
||||||
pstmt.clearParameters();
|
pstmt.clearParameters();
|
||||||
pstmt.setLong(1, (long) i);
|
pstmt.setLong(1, (long) i);
|
||||||
pstmt.setString(2, "test_" + i);
|
pstmt.setString(2, "test_" + i);
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
//wpackage com.alibaba.otter.canal.client.adapter.rdb.test;
|
||||||
|
//
|
||||||
|
//import com.alibaba.fastsql.sql.ast.SQLName;
|
||||||
|
//import com.alibaba.fastsql.sql.ast.SQLStatement;
|
||||||
|
//import com.alibaba.fastsql.sql.ast.statement.SQLCreateTableStatement;
|
||||||
|
//import com.alibaba.fastsql.sql.ast.statement.SQLExprTableSource;
|
||||||
|
//import com.alibaba.fastsql.sql.dialect.mysql.parser.MySqlCreateTableParser;
|
||||||
|
//import com.alibaba.fastsql.sql.dialect.mysql.parser.MySqlStatementParser;
|
||||||
|
//import com.alibaba.fastsql.sql.dialect.mysql.visitor.MySqlOutputVisitor;
|
||||||
|
//import com.alibaba.fastsql.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
|
||||||
|
//import com.alibaba.fastsql.sql.parser.SQLStatementParser;
|
||||||
|
//
|
||||||
|
//import java.io.StringWriter;
|
||||||
|
//
|
||||||
|
//public class SqlParserTest {
|
||||||
|
//
|
||||||
|
// public static class TableNameVisitor extends MySqlOutputVisitor {
|
||||||
|
//
|
||||||
|
// public TableNameVisitor(Appendable appender){
|
||||||
|
// super(appender);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public boolean visit(SQLExprTableSource x) {
|
||||||
|
// SQLName table = (SQLName) x.getExpr();
|
||||||
|
// String tableName = table.getSimpleName();
|
||||||
|
//
|
||||||
|
// // 改写tableName
|
||||||
|
// print0("new_" + tableName.toUpperCase());
|
||||||
|
//
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public static void main(String[] args) {
|
||||||
|
// // String sql = "select * from `mytest`.`t` where id=1 and name=ming group by
|
||||||
|
// // uid limit 1,200 order by ctime";
|
||||||
|
//
|
||||||
|
// String sql = "CREATE TABLE `mytest`.`user` (\n" + " `id` bigint(20) NOT NULL AUTO_INCREMENT,\n"
|
||||||
|
// + " `name` varchar(30) NOT NULL,\n" + " `c_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n"
|
||||||
|
// + " `role_id` bigint(20) DEFAULT NULL,\n" + " `test1` text,\n" + " `test2` blob,\n"
|
||||||
|
// + " `key` varchar(30) DEFAULT NULL,\n" + " PRIMARY KEY (`id`)\n"
|
||||||
|
// + ") ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;";
|
||||||
|
//
|
||||||
|
// // // 新建 MySQL Parser
|
||||||
|
// // SQLStatementParser parser = new MySqlStatementParser(sql);
|
||||||
|
// //
|
||||||
|
// // // 使用Parser解析生成AST,这里SQLStatement就是AST
|
||||||
|
// // SQLStatement sqlStatement = parser.parseStatement();
|
||||||
|
// //
|
||||||
|
// // MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
|
||||||
|
// // sqlStatement.accept(visitor);
|
||||||
|
// //
|
||||||
|
// // System.out.println("getTables:" + visitor.getTables());
|
||||||
|
// // System.out.println("getParameters:" + visitor.getParameters());
|
||||||
|
// // System.out.println("getOrderByColumns:" + visitor.getOrderByColumns());
|
||||||
|
// // System.out.println("getGroupByColumns:" + visitor.getGroupByColumns());
|
||||||
|
// // System.out.println("---------------------------------------------------------------------------");
|
||||||
|
// //
|
||||||
|
// // // 使用select访问者进行select的关键信息打印
|
||||||
|
// // // SelectPrintVisitor selectPrintVisitor = new SelectPrintVisitor();
|
||||||
|
// // // sqlStatement.accept(selectPrintVisitor);
|
||||||
|
// //
|
||||||
|
// // System.out.println("---------------------------------------------------------------------------");
|
||||||
|
// // // 最终sql输出
|
||||||
|
// // StringWriter out = new StringWriter();
|
||||||
|
// // TableNameVisitor outputVisitor = new TableNameVisitor(out);
|
||||||
|
// // sqlStatement.accept(outputVisitor);
|
||||||
|
// // System.out.println(out.toString());
|
||||||
|
//
|
||||||
|
// MySqlCreateTableParser parser1 = new MySqlCreateTableParser(sql);
|
||||||
|
// SQLCreateTableStatement createTableStatement = parser1.parseCreateTable();
|
||||||
|
//// MySqlSchemaStatVisitor visitor1 = new MySqlSchemaStatVisitor();
|
||||||
|
//// createTableStatement.accept(visitor1);
|
||||||
|
// // visitor1.getTables().forEach((k, v) -> {
|
||||||
|
// // System.out.println(k.);
|
||||||
|
// // System.out.println(v);
|
||||||
|
// // });
|
||||||
|
// // 最终sql输出
|
||||||
|
// StringWriter out = new StringWriter();
|
||||||
|
// TableNameVisitor outputVisitor = new TableNameVisitor(out);
|
||||||
|
// createTableStatement.accept(outputVisitor);
|
||||||
|
// System.out.println(out.toString());
|
||||||
|
// }
|
||||||
|
//}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -105,6 +105,10 @@
|
|||||||
<groupId>org.apache.rocketmq</groupId>
|
<groupId>org.apache.rocketmq</groupId>
|
||||||
<artifactId>rocketmq-client</artifactId>
|
<artifactId>rocketmq-client</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun.openservices</groupId>
|
||||||
|
<artifactId>aliware-apache-rocketmq-cloud</artifactId>
|
||||||
|
</dependency>
|
||||||
<!-- 客户端要使用请单独引入kafka-clients依赖 -->
|
<!-- 客户端要使用请单独引入kafka-clients依赖 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.kafka</groupId>
|
<groupId>org.apache.kafka</groupId>
|
||||||
@@ -119,10 +123,6 @@
|
|||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>com.aliyun.openservices</groupId>
|
|
||||||
<artifactId>aliware-apache-rocketmq-cloud</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -134,26 +134,13 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||||
<artifactSet>
|
<artifactSet>
|
||||||
<excludes>
|
<includes>
|
||||||
<exclude>org.apache.zookeeper:zookeeper</exclude>
|
<include>com.alibaba.otter:*</include>
|
||||||
<exclude>com.101tec:zkclient</exclude>
|
<include>com.google.guava:*</include>
|
||||||
<exclude>commons-io:commons-io</exclude>
|
<include>org.joda:*</include>
|
||||||
<exclude>commons-lang:commons-lang</exclude>
|
<include>com.google.common:*</include>
|
||||||
<exclude>commons-codec:commons-codec</exclude>
|
<include>com.google.thirdparty:*</include>
|
||||||
<exclude>com.alibaba:fastjson</exclude>
|
</includes>
|
||||||
<exclude>ch.qos.logback:logback-core</exclude>
|
|
||||||
<exclude>ch.qos.logback:logback-classic</exclude>
|
|
||||||
<exclude>org.slf4j:jcl-over-slf4j</exclude>
|
|
||||||
<exclude>org.slf4j:slf4j-api</exclude>
|
|
||||||
<exclude>org.springframework:*</exclude>
|
|
||||||
<exclude>org.jboss.netty:netty</exclude>
|
|
||||||
<exclude>commons-logging:commons-logging</exclude>
|
|
||||||
<exclude>aopalliance:aopalliance</exclude>
|
|
||||||
<exclude>com.google.protobuf:protobuf-java</exclude>
|
|
||||||
<exclude>com.google.code.findbugs:jsr305</exclude>
|
|
||||||
<exclude>io.netty:*</exclude>
|
|
||||||
<exclude>junit:junit</exclude>
|
|
||||||
</excludes>
|
|
||||||
</artifactSet>
|
</artifactSet>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import com.alibaba.otter.canal.common.utils.JsonUtils;
|
|||||||
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
||||||
import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils;
|
import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils;
|
||||||
import com.alibaba.otter.canal.common.zookeeper.running.ServerRunningData;
|
import com.alibaba.otter.canal.common.zookeeper.running.ServerRunningData;
|
||||||
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 集群模式的调度策略
|
* 集群模式的调度策略
|
||||||
@@ -25,6 +24,7 @@ import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
|||||||
*/
|
*/
|
||||||
public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
|
public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
|
||||||
|
|
||||||
|
private String destination;
|
||||||
private IZkChildListener childListener; // 监听所有的服务器列表
|
private IZkChildListener childListener; // 监听所有的服务器列表
|
||||||
private IZkDataListener dataListener; // 监听当前的工作节点
|
private IZkDataListener dataListener; // 监听当前的工作节点
|
||||||
private ZkClientx zkClient;
|
private ZkClientx zkClient;
|
||||||
@@ -32,6 +32,7 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
|
|||||||
private volatile InetSocketAddress runningAddress = null;
|
private volatile InetSocketAddress runningAddress = null;
|
||||||
|
|
||||||
public ClusterNodeAccessStrategy(String destination, ZkClientx zkClient){
|
public ClusterNodeAccessStrategy(String destination, ZkClientx zkClient){
|
||||||
|
this.destination = destination;
|
||||||
this.zkClient = zkClient;
|
this.zkClient = zkClient;
|
||||||
childListener = new IZkChildListener() {
|
childListener = new IZkChildListener() {
|
||||||
|
|
||||||
@@ -73,7 +74,7 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
|
|||||||
} else if (!currentAddress.isEmpty()) { // 如果不存在已经启动的服务,可能服务是一种lazy启动,随机选择一台触发服务器进行启动
|
} else if (!currentAddress.isEmpty()) { // 如果不存在已经启动的服务,可能服务是一种lazy启动,随机选择一台触发服务器进行启动
|
||||||
return currentAddress.get(0);// 默认返回第一个节点,之前已经做过shuffle
|
return currentAddress.get(0);// 默认返回第一个节点,之前已经做过shuffle
|
||||||
} else {
|
} else {
|
||||||
throw new CanalClientException("no alive canal server");
|
throw new ServerNotFoundException("no alive canal server for " + destination);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.alibaba.otter.canal.client.impl;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||||
|
|
||||||
|
public class ServerNotFoundException extends CanalClientException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -3471518241911601774L;
|
||||||
|
|
||||||
|
public ServerNotFoundException(String errorCode, String errorDesc, Throwable cause){
|
||||||
|
super(errorCode, errorDesc, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerNotFoundException(String errorCode, String errorDesc){
|
||||||
|
super(errorCode, errorDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerNotFoundException(String errorCode, Throwable cause){
|
||||||
|
super(errorCode, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerNotFoundException(String errorCode){
|
||||||
|
super(errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerNotFoundException(Throwable cause){
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.slf4j.MDC;
|
import org.slf4j.MDC;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.impl.ServerNotFoundException;
|
||||||
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
||||||
import com.alibaba.otter.canal.common.utils.BooleanMutex;
|
import com.alibaba.otter.canal.common.utils.BooleanMutex;
|
||||||
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
||||||
@@ -141,6 +142,15 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
logger.error(MessageFormat.format("There is an error when execute initRunning method, with destination [{0}].",
|
logger.error(MessageFormat.format("There is an error when execute initRunning method, with destination [{0}].",
|
||||||
destination),
|
destination),
|
||||||
t);
|
t);
|
||||||
|
|
||||||
|
// fixed issue 1220, 针对server节点不工作避免死循环
|
||||||
|
if (t instanceof ServerNotFoundException) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 出现任何异常尝试release
|
// 出现任何异常尝试release
|
||||||
releaseRunning();
|
releaseRunning();
|
||||||
throw new CanalClientException("something goes wrong in initRunning method. ", t);
|
throw new CanalClientException("something goes wrong in initRunning method. ", t);
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package com.alibaba.otter.canal.client.kafka;
|
package com.alibaba.otter.canal.client.kafka;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||||
@@ -33,14 +30,16 @@ import com.google.common.collect.Lists;
|
|||||||
*/
|
*/
|
||||||
public class KafkaCanalConnector implements CanalMQConnector {
|
public class KafkaCanalConnector implements CanalMQConnector {
|
||||||
|
|
||||||
private KafkaConsumer<String, Message> kafkaConsumer;
|
protected KafkaConsumer<String, Message> kafkaConsumer;
|
||||||
private KafkaConsumer<String, String> kafkaConsumer2; // 用于扁平message的数据消费
|
protected KafkaConsumer<String, String> kafkaConsumer2; // 用于扁平message的数据消费
|
||||||
private String topic;
|
protected String topic;
|
||||||
private Integer partition;
|
protected Integer partition;
|
||||||
private Properties properties;
|
protected Properties properties;
|
||||||
private volatile boolean connected = false;
|
protected volatile boolean connected = false;
|
||||||
private volatile boolean running = false;
|
protected volatile boolean running = false;
|
||||||
private boolean flatMessage;
|
protected boolean flatMessage;
|
||||||
|
|
||||||
|
private Map<Integer, Long> currentOffsets = new HashMap<>();
|
||||||
|
|
||||||
public KafkaCanalConnector(String servers, String topic, Integer partition, String groupId, Integer batchSize,
|
public KafkaCanalConnector(String servers, String topic, Integer partition, String groupId, Integer batchSize,
|
||||||
boolean flatMessage){
|
boolean flatMessage){
|
||||||
@@ -71,6 +70,7 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
/**
|
/**
|
||||||
* 打开连接
|
* 打开连接
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void connect() {
|
public void connect() {
|
||||||
if (connected) {
|
if (connected) {
|
||||||
return;
|
return;
|
||||||
@@ -79,6 +79,7 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
connected = true;
|
connected = true;
|
||||||
if (kafkaConsumer == null && !flatMessage) {
|
if (kafkaConsumer == null && !flatMessage) {
|
||||||
kafkaConsumer = new KafkaConsumer<String, Message>(properties);
|
kafkaConsumer = new KafkaConsumer<String, Message>(properties);
|
||||||
|
|
||||||
}
|
}
|
||||||
if (kafkaConsumer2 == null && flatMessage) {
|
if (kafkaConsumer2 == null && flatMessage) {
|
||||||
kafkaConsumer2 = new KafkaConsumer<String, String>(properties);
|
kafkaConsumer2 = new KafkaConsumer<String, String>(properties);
|
||||||
@@ -88,6 +89,7 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
/**
|
/**
|
||||||
* 关闭链接
|
* 关闭链接
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void disconnect() {
|
public void disconnect() {
|
||||||
if (kafkaConsumer != null) {
|
if (kafkaConsumer != null) {
|
||||||
kafkaConsumer.close();
|
kafkaConsumer.close();
|
||||||
@@ -101,10 +103,11 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
connected = false;
|
connected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void waitClientRunning() {
|
protected void waitClientRunning() {
|
||||||
running = true;
|
running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean checkValid() {
|
public boolean checkValid() {
|
||||||
return true;// 默认都放过
|
return true;// 默认都放过
|
||||||
}
|
}
|
||||||
@@ -112,6 +115,7 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
/**
|
/**
|
||||||
* 订阅topic
|
* 订阅topic
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void subscribe() {
|
public void subscribe() {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
if (!running) {
|
if (!running) {
|
||||||
@@ -139,6 +143,7 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
/**
|
/**
|
||||||
* 取消订阅
|
* 取消订阅
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void unsubscribe() {
|
public void unsubscribe() {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
if (!running) {
|
if (!running) {
|
||||||
@@ -176,6 +181,11 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
|
|
||||||
ConsumerRecords<String, Message> records = kafkaConsumer.poll(unit.toMillis(timeout));
|
ConsumerRecords<String, Message> records = kafkaConsumer.poll(unit.toMillis(timeout));
|
||||||
|
|
||||||
|
currentOffsets.clear();
|
||||||
|
for (TopicPartition topicPartition : records.partitions()) {
|
||||||
|
currentOffsets.put(topicPartition.partition(), kafkaConsumer.position(topicPartition));
|
||||||
|
}
|
||||||
|
|
||||||
if (!records.isEmpty()) {
|
if (!records.isEmpty()) {
|
||||||
List<Message> messages = new ArrayList<>();
|
List<Message> messages = new ArrayList<>();
|
||||||
for (ConsumerRecord<String, Message> record : records) {
|
for (ConsumerRecord<String, Message> record : records) {
|
||||||
@@ -208,6 +218,12 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ConsumerRecords<String, String> records = kafkaConsumer2.poll(unit.toMillis(timeout));
|
ConsumerRecords<String, String> records = kafkaConsumer2.poll(unit.toMillis(timeout));
|
||||||
|
|
||||||
|
currentOffsets.clear();
|
||||||
|
for (TopicPartition topicPartition : records.partitions()) {
|
||||||
|
currentOffsets.put(topicPartition.partition(), kafkaConsumer2.position(topicPartition));
|
||||||
|
}
|
||||||
|
|
||||||
if (!records.isEmpty()) {
|
if (!records.isEmpty()) {
|
||||||
List<FlatMessage> flatMessages = new ArrayList<>();
|
List<FlatMessage> flatMessages = new ArrayList<>();
|
||||||
for (ConsumerRecord<String, String> record : records) {
|
for (ConsumerRecord<String, String> record : records) {
|
||||||
@@ -222,12 +238,28 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void rollback() throws CanalClientException {
|
public void rollback() {
|
||||||
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 回滚所有分区
|
||||||
|
if (kafkaConsumer != null) {
|
||||||
|
for (Map.Entry<Integer, Long> entry : currentOffsets.entrySet()) {
|
||||||
|
kafkaConsumer.seek(new TopicPartition(topic, entry.getKey()), entry.getValue() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (kafkaConsumer2 != null) {
|
||||||
|
for (Map.Entry<Integer, Long> entry : currentOffsets.entrySet()) {
|
||||||
|
kafkaConsumer2.seek(new TopicPartition(topic, entry.getKey()), entry.getValue() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提交offset,如果超过 session.timeout.ms 设置的时间没有ack则会抛出异常,ack失败
|
* 提交offset,如果超过 session.timeout.ms 设置的时间没有ack则会抛出异常,ack失败
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void ack() {
|
public void ack() {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
if (!running) {
|
if (!running) {
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package com.alibaba.otter.canal.client.kafka;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.otter.canal.client.kafka.protocol.KafkaFlatMessage;
|
||||||
|
import com.alibaba.otter.canal.client.kafka.protocol.KafkaMessage;
|
||||||
|
import com.alibaba.otter.canal.protocol.FlatMessage;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||||
|
import org.apache.kafka.clients.consumer.ConsumerRecords;
|
||||||
|
import org.apache.kafka.common.TopicPartition;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* kafka带消息offset的连接器
|
||||||
|
*
|
||||||
|
* @Author panjianping
|
||||||
|
* @Email ipanjianping@qq.com
|
||||||
|
* @Date 2018/12/17
|
||||||
|
*/
|
||||||
|
public class KafkaOffsetCanalConnector extends KafkaCanalConnector {
|
||||||
|
|
||||||
|
public KafkaOffsetCanalConnector(String servers, String topic, Integer partition, String groupId, boolean flatMessage) {
|
||||||
|
super(servers, topic, partition, groupId, 100, flatMessage);
|
||||||
|
// 启动时从未消费的消息位置开始
|
||||||
|
properties.put("auto.offset.reset", "earliest");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Kafka消息,不确认
|
||||||
|
*
|
||||||
|
* @param timeout
|
||||||
|
* @param unit
|
||||||
|
* @param offset 消息偏移地址(-1为不偏移)
|
||||||
|
* @return
|
||||||
|
* @throws CanalClientException
|
||||||
|
*/
|
||||||
|
public List<KafkaMessage> getListWithoutAck(Long timeout, TimeUnit unit, long offset) throws CanalClientException {
|
||||||
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return Lists.newArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset > -1) {
|
||||||
|
TopicPartition tp = new TopicPartition(topic, partition == null ? 0 : partition);
|
||||||
|
kafkaConsumer.seek(tp, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsumerRecords<String, Message> records = kafkaConsumer.poll(unit.toMillis(timeout));
|
||||||
|
|
||||||
|
if (!records.isEmpty()) {
|
||||||
|
List<KafkaMessage> messages = new ArrayList<>();
|
||||||
|
for (ConsumerRecord<String, Message> record : records) {
|
||||||
|
KafkaMessage message = new KafkaMessage(record.value(), record.offset());
|
||||||
|
messages.add(message);
|
||||||
|
}
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
return Lists.newArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Kafka消息,不确认
|
||||||
|
*
|
||||||
|
* @param timeout
|
||||||
|
* @param unit
|
||||||
|
* @param offset 消息偏移地址(-1为不偏移)
|
||||||
|
* @return
|
||||||
|
* @throws CanalClientException
|
||||||
|
*/
|
||||||
|
public List<KafkaFlatMessage> getFlatListWithoutAck(Long timeout, TimeUnit unit, long offset) throws CanalClientException {
|
||||||
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return Lists.newArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset > -1) {
|
||||||
|
TopicPartition tp = new TopicPartition(topic, partition == null ? 0 : partition);
|
||||||
|
kafkaConsumer2.seek(tp, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsumerRecords<String, String> records = kafkaConsumer2.poll(unit.toMillis(timeout));
|
||||||
|
if (!records.isEmpty()) {
|
||||||
|
List<KafkaFlatMessage> flatMessages = new ArrayList<>();
|
||||||
|
for (ConsumerRecord<String, String> record : records) {
|
||||||
|
String flatMessageJson = record.value();
|
||||||
|
FlatMessage flatMessage = JSON.parseObject(flatMessageJson, FlatMessage.class);
|
||||||
|
KafkaFlatMessage message = new KafkaFlatMessage(flatMessage, record.offset());
|
||||||
|
flatMessages.add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return flatMessages;
|
||||||
|
}
|
||||||
|
return Lists.newArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新设置AutoOffsetReset(默认 earliest )
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
public void setAutoOffsetReset(String value) {
|
||||||
|
if (StringUtils.isNotBlank(value)) {
|
||||||
|
properties.put("auto.offset.reset", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.alibaba.otter.canal.client.kafka.protocol;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.protocol.FlatMessage;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息对象(Kafka)
|
||||||
|
*
|
||||||
|
* @Author panjianping
|
||||||
|
* @Email ipanjianping@qq.com
|
||||||
|
* @Date 2018/12/17
|
||||||
|
*/
|
||||||
|
public class KafkaFlatMessage extends FlatMessage {
|
||||||
|
/**
|
||||||
|
* Kafka 消息 offset
|
||||||
|
*/
|
||||||
|
private long offset;
|
||||||
|
|
||||||
|
public KafkaFlatMessage(FlatMessage message, long offset) {
|
||||||
|
super(message.getId());
|
||||||
|
BeanUtils.copyProperties(message, this);
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getOffset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOffset(long offset) {
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.alibaba.otter.canal.client.kafka.protocol;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息对象(Kafka)
|
||||||
|
*
|
||||||
|
* @Author panjianping
|
||||||
|
* @Email ipanjianping@qq.com
|
||||||
|
* @Date 2018/12/17
|
||||||
|
*/
|
||||||
|
public class KafkaMessage extends Message {
|
||||||
|
/**
|
||||||
|
* Kafka 消息 offset
|
||||||
|
*/
|
||||||
|
private long offset;
|
||||||
|
|
||||||
|
public KafkaMessage(Message message, long offset) {
|
||||||
|
super(message.getId());
|
||||||
|
BeanUtils.copyProperties(message, this);
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public long getOffset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOffset(long offset) {
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package com.alibaba.otter.canal.client.running.kafka;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.kafka.KafkaOffsetCanalConnector;
|
||||||
|
import com.alibaba.otter.canal.client.kafka.protocol.KafkaMessage;
|
||||||
|
import org.apache.kafka.common.errors.WakeupException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KafkaOffsetCanalConnector 使用示例
|
||||||
|
* <p>KafkaOffsetCanalConnector 与 KafkaCanalConnector 的另一区别是 auto.offset.reset 默认值不同;</p>
|
||||||
|
* <p>KafkaOffsetCanalConnector 默认为 earliest;canal-kafka-client重启后从未被消费的记录开始拉取消息,同时提供了修改 auto.offset.reset 的方法 setAutoOffsetReset</p>
|
||||||
|
*
|
||||||
|
* @author panjianping @ 2018-12-18
|
||||||
|
* @version 1.1.3
|
||||||
|
*/
|
||||||
|
public class CanalKafkaOffsetClientExample {
|
||||||
|
|
||||||
|
protected final static Logger logger = LoggerFactory.getLogger(CanalKafkaOffsetClientExample.class);
|
||||||
|
|
||||||
|
private KafkaOffsetCanalConnector connector;
|
||||||
|
|
||||||
|
private static volatile boolean running = false;
|
||||||
|
|
||||||
|
private Thread thread = null;
|
||||||
|
|
||||||
|
private Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() {
|
||||||
|
|
||||||
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
|
logger.error("parse events has an error", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public CanalKafkaOffsetClientExample(String servers, String topic, Integer partition, String groupId) {
|
||||||
|
connector = new KafkaOffsetCanalConnector(servers, topic, partition, groupId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
final CanalKafkaOffsetClientExample kafkaCanalClientExample = new CanalKafkaOffsetClientExample(
|
||||||
|
AbstractKafkaTest.servers,
|
||||||
|
AbstractKafkaTest.topic,
|
||||||
|
AbstractKafkaTest.partition,
|
||||||
|
AbstractKafkaTest.groupId);
|
||||||
|
logger.info("## start the kafka consumer: {}-{}", AbstractKafkaTest.topic, AbstractKafkaTest.groupId);
|
||||||
|
kafkaCanalClientExample.start();
|
||||||
|
logger.info("## the canal kafka consumer is running now ......");
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
logger.info("## stop the kafka consumer");
|
||||||
|
kafkaCanalClientExample.stop();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("##something goes wrong when stopping kafka consumer:", e);
|
||||||
|
} finally {
|
||||||
|
logger.info("## kafka consumer is down.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
while (running)
|
||||||
|
;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("## Something goes wrong when starting up the kafka consumer:", e);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
Assert.notNull(connector, "connector is null");
|
||||||
|
thread = new Thread(new Runnable() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.setUncaughtExceptionHandler(handler);
|
||||||
|
thread.start();
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
running = false;
|
||||||
|
if (thread != null) {
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process() {
|
||||||
|
while (!running)
|
||||||
|
;
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
// 修改 AutoOffsetReset 的值,默认(earliest)
|
||||||
|
//connector.setAutoOffsetReset(null);
|
||||||
|
connector.connect();
|
||||||
|
connector.subscribe();
|
||||||
|
// 消息起始偏移地址
|
||||||
|
long offset = -1;
|
||||||
|
// 错误次数
|
||||||
|
int errorCount = 0;
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
// 错误重试次数超过3次后,每30秒递增重试
|
||||||
|
if (errorCount > 2) {
|
||||||
|
Thread.sleep((errorCount - 2) * 1000 * 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<KafkaMessage> messages = connector.getListWithoutAck(100L, TimeUnit.MILLISECONDS, offset); // 获取message
|
||||||
|
if (messages == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (KafkaMessage message : messages) {
|
||||||
|
long batchId = message.getId();
|
||||||
|
int size = message.getEntries().size();
|
||||||
|
|
||||||
|
if (batchId == -1 || size == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录第一条消息的offset,用于处理数据异常时重新从此位置获取消息
|
||||||
|
if (offset < 0) {
|
||||||
|
offset = message.getOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// printSummary(message, batchId, size);
|
||||||
|
// printEntry(message.getEntries());
|
||||||
|
logger.info(message.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
connector.ack(); // 提交确认
|
||||||
|
// 还原offset
|
||||||
|
offset = -1;
|
||||||
|
errorCount = 0;
|
||||||
|
} catch (Exception e) {
|
||||||
|
errorCount++;
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
if (errorCount == 3) {
|
||||||
|
// 重试3次后发送邮件提醒异常
|
||||||
|
// mailService.sendMail("同步数据异常,请及时处理", "错误消息");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
connector.unsubscribe();
|
||||||
|
} catch (WakeupException e) {
|
||||||
|
// No-op. Continue process
|
||||||
|
}
|
||||||
|
connector.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.common</artifactId>
|
<artifactId>canal.common</artifactId>
|
||||||
|
|||||||
@@ -54,15 +54,6 @@ public class AddressUtils {
|
|||||||
|
|
||||||
public static InetAddress getHostAddress() {
|
public static InetAddress getHostAddress() {
|
||||||
InetAddress localAddress = null;
|
InetAddress localAddress = null;
|
||||||
try {
|
|
||||||
localAddress = InetAddress.getLocalHost();
|
|
||||||
if (isValidHostAddress(localAddress)) {
|
|
||||||
return localAddress;
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
logger.warn("Failed to retriving local host ip address, try scan network card ip address. cause: "
|
|
||||||
+ e.getMessage());
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||||
if (interfaces != null) {
|
if (interfaces != null) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public final class CharsetConversion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Character set data used in lookups. The array will be sparse.
|
// Character set data used in lookups. The array will be sparse.
|
||||||
static final Entry[] entries = new Entry[0xff];
|
static final Entry[] entries = new Entry[2048];
|
||||||
|
|
||||||
static Entry getEntry(final int id) {
|
static Entry getEntry(final int id) {
|
||||||
if (id >= 0 && id < entries.length) {
|
if (id >= 0 && id < entries.length) {
|
||||||
@@ -95,7 +95,7 @@ public final class CharsetConversion {
|
|||||||
putEntry(34, "cp1250", "cp1250_czech_cs", "Cp1250");
|
putEntry(34, "cp1250", "cp1250_czech_cs", "Cp1250");
|
||||||
putEntry(35, "ucs2", "ucs2_general_ci", "UnicodeBig");
|
putEntry(35, "ucs2", "ucs2_general_ci", "UnicodeBig");
|
||||||
putEntry(36, "cp866", "cp866_general_ci", "Cp866");
|
putEntry(36, "cp866", "cp866_general_ci", "Cp866");
|
||||||
putEntry(37, "keybcs2", "keybcs2_general_ci", "Cp895");
|
putEntry(37, "keybcs2", "keybcs2_general_ci", "Cp852");
|
||||||
putEntry(38, "macce", "macce_general_ci", "MacCentralEurope");
|
putEntry(38, "macce", "macce_general_ci", "MacCentralEurope");
|
||||||
putEntry(39, "macroman", "macroman_general_ci", "MacRoman");
|
putEntry(39, "macroman", "macroman_general_ci", "MacRoman");
|
||||||
putEntry(40, "cp852", "cp852_general_ci", "Cp852");
|
putEntry(40, "cp852", "cp852_general_ci", "Cp852");
|
||||||
@@ -129,7 +129,7 @@ public final class CharsetConversion {
|
|||||||
putEntry(70, "greek", "greek_bin", "ISO8859_7");
|
putEntry(70, "greek", "greek_bin", "ISO8859_7");
|
||||||
putEntry(71, "hebrew", "hebrew_bin", "ISO8859_8");
|
putEntry(71, "hebrew", "hebrew_bin", "ISO8859_8");
|
||||||
putEntry(72, "hp8", "hp8_bin", "US-ASCII");
|
putEntry(72, "hp8", "hp8_bin", "US-ASCII");
|
||||||
putEntry(73, "keybcs2", "keybcs2_bin", "Cp895");
|
putEntry(73, "keybcs2", "keybcs2_bin", "Cp852");
|
||||||
putEntry(74, "koi8r", "koi8r_bin", "KOI8_R");
|
putEntry(74, "koi8r", "koi8r_bin", "KOI8_R");
|
||||||
putEntry(75, "koi8u", "koi8u_bin", "KOI8_U");
|
putEntry(75, "koi8u", "koi8u_bin", "KOI8_U");
|
||||||
putEntry(77, "latin2", "latin2_bin", "ISO8859_2");
|
putEntry(77, "latin2", "latin2_bin", "ISO8859_2");
|
||||||
@@ -260,8 +260,86 @@ public final class CharsetConversion {
|
|||||||
putEntry(241, "utf8mb4", "utf8mb4_esperanto_ci", "UTF-8");
|
putEntry(241, "utf8mb4", "utf8mb4_esperanto_ci", "UTF-8");
|
||||||
putEntry(242, "utf8mb4", "utf8mb4_hungarian_ci", "UTF-8");
|
putEntry(242, "utf8mb4", "utf8mb4_hungarian_ci", "UTF-8");
|
||||||
putEntry(243, "utf8mb4", "utf8mb4_sinhala_ci", "UTF-8");
|
putEntry(243, "utf8mb4", "utf8mb4_sinhala_ci", "UTF-8");
|
||||||
|
putEntry(244, "utf8mb4", "utf8mb4_german2_ci", "UTF-8");
|
||||||
|
putEntry(245, "utf8mb4", "utf8mb4_croatian_ci", "UTF-8");
|
||||||
|
putEntry(246, "utf8mb4", "utf8mb4_unicode_520_ci", "UTF-8");
|
||||||
|
putEntry(247, "utf8mb4", "utf8mb4_vietnamese_ci", "UTF-8");
|
||||||
|
putEntry(248, "gb18030", "gb18030_chinese_ci", "GB18030");
|
||||||
|
putEntry(249, "gb18030", "gb18030_bin", "GB18030");
|
||||||
|
putEntry(250, "gb18030", "gb18030_unicode_520_ci", "GB18030");
|
||||||
|
|
||||||
putEntry(254, "utf8", "utf8_general_cs", "UTF-8");
|
putEntry(254, "utf8", "utf8_general_cs", "UTF-8");
|
||||||
|
putEntry(255, "utf8mb4", "utf8mb4_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(256, "utf8mb4", "utf8mb4_de_pb_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(257, "utf8mb4", "utf8mb4_is_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(258, "utf8mb4", "utf8mb4_lv_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(259, "utf8mb4", "utf8mb4_ro_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(260, "utf8mb4", "utf8mb4_sl_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(261, "utf8mb4", "utf8mb4_pl_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(262, "utf8mb4", "utf8mb4_et_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(263, "utf8mb4", "utf8mb4_es_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(264, "utf8mb4", "utf8mb4_sv_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(265, "utf8mb4", "utf8mb4_tr_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(266, "utf8mb4", "utf8mb4_cs_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(267, "utf8mb4", "utf8mb4_da_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(268, "utf8mb4", "utf8mb4_lt_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(269, "utf8mb4", "utf8mb4_sk_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(270, "utf8mb4", "utf8mb4_es_trad_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(271, "utf8mb4", "utf8mb4_la_0900_ai_ci", "UTF-8");
|
||||||
|
|
||||||
|
putEntry(273, "utf8mb4", "utf8mb4_eo_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(274, "utf8mb4", "utf8mb4_hu_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(275, "utf8mb4", "utf8mb4_hr_0900_ai_ci", "UTF-8");
|
||||||
|
|
||||||
|
putEntry(277, "utf8mb4", "utf8mb4_vi_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(278, "utf8mb4", "utf8mb4_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(279, "utf8mb4", "utf8mb4_de_pb_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(280, "utf8mb4", "utf8mb4_is_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(281, "utf8mb4", "utf8mb4_lv_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(282, "utf8mb4", "utf8mb4_ro_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(283, "utf8mb4", "utf8mb4_sl_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(284, "utf8mb4", "utf8mb4_pl_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(285, "utf8mb4", "utf8mb4_et_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(286, "utf8mb4", "utf8mb4_es_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(287, "utf8mb4", "utf8mb4_sv_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(288, "utf8mb4", "utf8mb4_tr_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(289, "utf8mb4", "utf8mb4_cs_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(290, "utf8mb4", "utf8mb4_da_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(291, "utf8mb4", "utf8mb4_lt_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(292, "utf8mb4", "utf8mb4_sk_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(293, "utf8mb4", "utf8mb4_es_trad_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(294, "utf8mb4", "utf8mb4_la_0900_as_cs", "UTF-8");
|
||||||
|
|
||||||
|
putEntry(296, "utf8mb4", "utf8mb4_eo_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(297, "utf8mb4", "utf8mb4_hu_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(298, "utf8mb4", "utf8mb4_hr_0900_as_cs", "UTF-8");
|
||||||
|
|
||||||
|
putEntry(300, "utf8mb4", "utf8mb4_vi_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(303, "utf8mb4", "utf8mb4_ja_0900_as_cs", "UTF-8");
|
||||||
|
putEntry(304, "utf8mb4", "utf8mb4_ja_0900_as_cs_ks", "UTF-8");
|
||||||
|
putEntry(305, "utf8mb4", "utf8mb4_0900_as_ci", "UTF-8");
|
||||||
|
putEntry(306, "utf8mb4", "utf8mb4_ru_0900_ai_ci", "UTF-8");
|
||||||
|
putEntry(307, "utf8mb4", "utf8mb4_ru_0900_as_cs", "UTF-8");
|
||||||
|
|
||||||
|
putEntry(326, "utf8mb4", "utf8mb4_test_ci", "UTF-8");
|
||||||
|
putEntry(327, "utf16", "utf16_test_ci", "UTF-16");
|
||||||
|
putEntry(328, "utf8mb4", "utf8mb4_test_400_ci", "UTF-8");
|
||||||
|
|
||||||
|
putEntry(336, "utf8", "utf8_bengali_standard_ci", "UTF-8");
|
||||||
|
putEntry(337, "utf8", "utf8_bengali_standard_ci", "UTF-8");
|
||||||
|
putEntry(352, "utf8", "utf8_phone_ci", "UTF-8");
|
||||||
|
putEntry(353, "utf8", "utf8_test_ci", "UTF-8");
|
||||||
|
putEntry(354, "utf8", "utf8_5624_1", "UTF-8");
|
||||||
|
putEntry(355, "utf8", "utf8_5624_2", "UTF-8");
|
||||||
|
putEntry(356, "utf8", "utf8_5624_3", "UTF-8");
|
||||||
|
putEntry(357, "utf8", "utf8_5624_4", "UTF-8");
|
||||||
|
putEntry(358, "ucs2", "ucs2_test_ci", "UnicodeBig");
|
||||||
|
putEntry(359, "ucs2", "ucs2_vn_ci", "UnicodeBig");
|
||||||
|
putEntry(360, "ucs2", "ucs2_5624_1", "UnicodeBig");
|
||||||
|
|
||||||
|
putEntry(368, "utf8", "utf8_5624_5", "UTF-8");
|
||||||
|
putEntry(391, "utf32", "utf32_test_ci", "UTF-32");
|
||||||
|
putEntry(2047, "utf8", "utf8_maxuserid_ci", "UTF-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
37
deployer/manager_ddl.sql
Normal file
37
deployer/manager_ddl.sql
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
CREATE SCHEMA `canal_manager` DEFAULT CHARACTER SET utf8mb4 ;
|
||||||
|
|
||||||
|
CREATE TABLE `canal_config` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(45) NOT NULL,
|
||||||
|
`content` text NOT NULL,
|
||||||
|
`modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `name_UNIQUE` (`name`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 ;
|
||||||
|
|
||||||
|
CREATE TABLE `canal_instance_config` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(45) NOT NULL,
|
||||||
|
`content` text NOT NULL,
|
||||||
|
`modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `name_UNIQUE` (`name`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 ;
|
||||||
|
|
||||||
|
CREATE TABLE `canal_adapter_config` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||||
|
`category` varchar(45) NOT NULL,
|
||||||
|
`name` varchar(45) NOT NULL,
|
||||||
|
`content` text NOT NULL,
|
||||||
|
`modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 ;
|
||||||
|
|
||||||
|
INSERT INTO `canal_config` VALUES ('1', 'canal.properties', '#################################################\r\n######### common argument ############# \r\n#################################################\r\n#canal.manager.jdbc.url=jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8\r\n#canal.manager.jdbc.username=root\r\n#canal.manager.jdbc.password=121212\r\ncanal.id = 1\r\ncanal.ip =\r\ncanal.port = 11111\r\ncanal.metrics.pull.port = 11112\r\ncanal.zkServers =\r\n# flush data to zk\r\ncanal.zookeeper.flush.period = 1000\r\ncanal.withoutNetty = false\r\n# tcp, kafka, RocketMQ\r\ncanal.serverMode = tcp\r\n# flush meta cursor/parse position to file\r\ncanal.file.data.dir = ${canal.conf.dir}\r\ncanal.file.flush.period = 1000\r\n## memory store RingBuffer size, should be Math.pow(2,n)\r\ncanal.instance.memory.buffer.size = 16384\r\n## memory store RingBuffer used memory unit size , default 1kb\r\ncanal.instance.memory.buffer.memunit = 1024 \r\n## meory store gets mode used MEMSIZE or ITEMSIZE\r\ncanal.instance.memory.batch.mode = MEMSIZE\r\ncanal.instance.memory.rawEntry = true\r\n\r\n## detecing config\r\ncanal.instance.detecting.enable = false\r\n#canal.instance.detecting.sql = insert into retl.xdual values(1,now()) on duplicate key update x=now()\r\ncanal.instance.detecting.sql = select 1\r\ncanal.instance.detecting.interval.time = 3\r\ncanal.instance.detecting.retry.threshold = 3\r\ncanal.instance.detecting.heartbeatHaEnable = false\r\n\r\n# support maximum transaction size, more than the size of the transaction will be cut into multiple transactions delivery\r\ncanal.instance.transaction.size = 1024\r\n# mysql fallback connected to new master should fallback times\r\ncanal.instance.fallbackIntervalInSeconds = 60\r\n\r\n# network config\r\ncanal.instance.network.receiveBufferSize = 16384\r\ncanal.instance.network.sendBufferSize = 16384\r\ncanal.instance.network.soTimeout = 30\r\n\r\n# binlog filter config\r\ncanal.instance.filter.druid.ddl = true\r\ncanal.instance.filter.query.dcl = false\r\ncanal.instance.filter.query.dml = false\r\ncanal.instance.filter.query.ddl = false\r\ncanal.instance.filter.table.error = false\r\ncanal.instance.filter.rows = false\r\ncanal.instance.filter.transaction.entry = false\r\n\r\n# binlog format/image check\r\ncanal.instance.binlog.format = ROW,STATEMENT,MIXED \r\ncanal.instance.binlog.image = FULL,MINIMAL,NOBLOB\r\n\r\n# binlog ddl isolation\r\ncanal.instance.get.ddl.isolation = false\r\n\r\n# parallel parser config\r\ncanal.instance.parser.parallel = true\r\n## concurrent thread number, default 60% available processors, suggest not to exceed Runtime.getRuntime().availableProcessors()\r\n#canal.instance.parser.parallelThreadSize = 16\r\n## disruptor ringbuffer size, must be power of 2\r\ncanal.instance.parser.parallelBufferSize = 256\r\n\r\n# table meta tsdb info\r\ncanal.instance.tsdb.enable = true\r\ncanal.instance.tsdb.dir = ${canal.file.data.dir:../conf}/${canal.instance.destination:}\r\ncanal.instance.tsdb.url = jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;\r\ncanal.instance.tsdb.dbUsername = canal\r\ncanal.instance.tsdb.dbPassword = canal\r\n# dump snapshot interval, default 24 hour\r\ncanal.instance.tsdb.snapshot.interval = 24\r\n# purge snapshot expire , default 360 hour(15 days)\r\ncanal.instance.tsdb.snapshot.expire = 360\r\n\r\n# aliyun ak/sk , support rds/mq\r\ncanal.aliyun.accesskey =\r\ncanal.aliyun.secretkey =\r\n\r\n#################################################\r\n######### destinations ############# \r\n#################################################\r\ncanal.destinations = example\r\n# conf root dir\r\ncanal.conf.dir = ../conf\r\n# auto scan instance dir add/remove and start/stop instance\r\ncanal.auto.scan = true\r\ncanal.auto.scan.interval = 5\r\n\r\ncanal.instance.tsdb.spring.xml = classpath:spring/tsdb/h2-tsdb.xml\r\n#canal.instance.tsdb.spring.xml = classpath:spring/tsdb/mysql-tsdb.xml\r\n\r\ncanal.instance.global.mode = spring\r\ncanal.instance.global.lazy = false\r\n#canal.instance.global.manager.address = 127.0.0.1:1099\r\n#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml\r\ncanal.instance.global.spring.xml = classpath:spring/file-instance.xml\r\n#canal.instance.global.spring.xml = classpath:spring/default-instance.xml\r\n\r\n##################################################\r\n######### MQ #############\r\n##################################################\r\ncanal.mq.servers = 127.0.0.1:6667\r\ncanal.mq.retries = 0\r\ncanal.mq.batchSize = 16384\r\ncanal.mq.maxRequestSize = 1048576\r\ncanal.mq.lingerMs = 1\r\ncanal.mq.bufferMemory = 33554432\r\ncanal.mq.canalBatchSize = 50\r\ncanal.mq.canalGetTimeout = 100\r\ncanal.mq.flatMessage = true\r\ncanal.mq.compressionType = none\r\ncanal.mq.acks = all\r\n', '2018-12-30 16:46:00');
|
||||||
|
INSERT INTO `canal_config` VALUES ('2', 'application.yml', 'server:\n port: 8081\nlogging:\n level:\n org.springframework: WARN\n com.alibaba.otter.canal.client.adapter.hbase: DEBUG\n com.alibaba.otter.canal.client.adapter.es: DEBUG\n com.alibaba.otter.canal.client.adapter.rdb: DEBUG\nspring:\n jackson:\n date-format: yyyy-MM-dd HH:mm:ss\n time-zone: GMT+8\n default-property-inclusion: non_null\n\ncanal.conf:\n canalServerHost: 127.0.0.1:11111\n# zookeeperHosts: slave1:2181\n# mqServers: 127.0.0.1:9092 #or rocketmq\n# flatMessage: true\n batchSize: 500\n syncBatchSize: 1000\n retries: 0\n timeout:\n accessKey:\n secretKey:\n mode: tcp # kafka rocketMQ\n# srcDataSources:\n# defaultDS:\n# url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true\n# username: root\n# password: 121212\n canalAdapters:\n - instance: example # canal instance Name or mq topic name\n groups:\n - groupId: g1\n outerAdapters:\n - name: logger\n# - name: rdb\n# key: mysql1\n# properties:\n# jdbc.driverClassName: com.mysql.jdbc.Driver\n# jdbc.url: jdbc:mysql://127.0.0.1:3306/mytest2?useUnicode=true\n# jdbc.username: root\n# jdbc.password: 121212\n# - name: rdb\n# key: oracle1\n# properties:\n# jdbc.driverClassName: oracle.jdbc.OracleDriver\n# jdbc.url: jdbc:oracle:thin:@localhost:49161:XE\n# jdbc.username: mytest\n# jdbc.password: m121212\n# - name: rdb\n# key: postgres1\n# properties:\n# jdbc.driverClassName: org.postgresql.Driver\n# jdbc.url: jdbc:postgresql://localhost:5432/postgres\n# jdbc.username: postgres\n# jdbc.password: 121212\n# threads: 1\n# commitSize: 3000\n# - name: hbase\n# properties:\n# hbase.zookeeper.quorum: 127.0.0.1\n# hbase.zookeeper.property.clientPort: 2181\n# zookeeper.znode.parent: /hbase\n# - name: es\n# hosts: 127.0.0.1:9300\n# properties:\n# cluster.name: elasticsearch\n', '2019-01-05 01:37:43');
|
||||||
|
|
||||||
|
INSERT INTO `canal_instance_config` VALUES ('1', 'example', '#################################################\r\n## mysql serverId , v1.0.26+ will autoGen\r\n# canal.instance.mysql.slaveId=0\r\n\r\n# enable gtid use true/false\r\ncanal.instance.gtidon=false\r\n\r\n# position info\r\ncanal.instance.master.address=127.0.0.1:3306\r\ncanal.instance.master.journal.name=\r\ncanal.instance.master.position=\r\ncanal.instance.master.timestamp=\r\ncanal.instance.master.gtid=\r\n\r\n# rds oss binlog\r\ncanal.instance.rds.accesskey=\r\ncanal.instance.rds.secretkey=\r\ncanal.instance.rds.instanceId=\r\n\r\n# table meta tsdb info\r\ncanal.instance.tsdb.enable=true\r\n#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb\r\n#canal.instance.tsdb.dbUsername=canal\r\n#canal.instance.tsdb.dbPassword=canal\r\n\r\n#canal.instance.standby.address =\r\n#canal.instance.standby.journal.name =\r\n#canal.instance.standby.position =\r\n#canal.instance.standby.timestamp =\r\n#canal.instance.standby.gtid=\r\n\r\n# username/password\r\ncanal.instance.dbUsername=canal\r\ncanal.instance.dbPassword=canal\r\ncanal.instance.connectionCharset = UTF-8\r\ncanal.instance.defaultDatabaseName =test\r\n# enable druid Decrypt database password\r\ncanal.instance.enableDruid=false\r\n#canal.instance.pwdPublicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALK4BUxdDltRRE5/zXpVEVPUgunvscYFtEip3pmLlhrWpacX7y7GCMo2/JM6LeHmiiNdH1FWgGCpUfircSwlWKUCAwEAAQ==\r\n\r\n# table regex\r\ncanal.instance.filter.regex=.*\\\\..*\r\n# table black regex\r\ncanal.instance.filter.black.regex=\r\n\r\n# mq config\r\ncanal.mq.topic=example\r\n# 动态topic, 需mq支持动态创建topic\r\n#canal.mq.dynamicTopic=.*,mytest\\\\..*,mytest2.user\r\ncanal.mq.partition=0\r\n# hash partition config\r\n#canal.mq.partitionsNum=3\r\n#canal.mq.partitionHash=test.table:id^name,.*\\\\..*\r\n#################################################\r\n', '2018-12-30 16:46:16');
|
||||||
|
|
||||||
|
INSERT INTO `canal_adapter_config` VALUES ('1', 'es', 'mytest_user.yml', 'dataSourceKey: defaultDS\ndestination: example\nesMapping:\n _index: mytest_user\n _type: _doc\n _id: _id\n# pk: id\n sql: \"select a.id as _id, a.name as _name, a.role_id as _role_id, b.role_name as _role_name,\n a.c_time as _c_time, c.labels as _labels from user a\n left join role b on b.id=a.role_id\n left join (select user_id, group_concat(label order by id desc separator \';\') as labels from label\n group by user_id) c on c.user_id=a.id\"\n# objFields:\n# _labels: array:;\n etlCondition: \"where a.c_time>=\'{0}\'\"\n commitBatch: 3000\n', '2019-01-01 17:05:43');
|
||||||
|
INSERT INTO `canal_adapter_config` VALUES ('2', 'hhase', 'mytest_person2.yml', 'dataSourceKey: defaultDS\ndestination: example\nhbaseMapping:\n mode: STRING #NATIVE #PHOENIX\n database: mytest # 数据库名\n table: person2 # 数据库表名\n hbaseTable: MYTEST.PERSON2 # HBase表名\n family: CF # 默认统一Family名称\n uppercaseQualifier: true # 字段名转大写, 默认为true\n commitBatch: 3000 # 批量提交的大小\n #rowKey: id,type # 复合字段rowKey不能和columns中的rowKey重复\n columns:\n # 数据库字段:HBase对应字段\n id: ROWKEY LEN:15\n name: NAME\n email: EMAIL\n type:\n c_time: C_TIME\n birthday: BIRTHDAY\n# excludeColumns:\n# - lat # 忽略字段\n\n# -- NATIVE类型\n# $DEFAULT\n# $STRING\n# $INTEGER\n# $LONG\n# $SHORT\n# $BOOLEAN\n# $FLOAT\n# $DOUBLE\n# $BIGDECIMAL\n# $DATE\n# $BYTE\n# $BYTES\n\n# -- PHOENIX类型\n# $DEFAULT 对应PHOENIX里的VARCHAR\n# $UNSIGNED_INT 对应PHOENIX里的UNSIGNED_INT 4字节\n# $UNSIGNED_LONG 对应PHOENIX里的UNSIGNED_LONG 8字节\n# $UNSIGNED_TINYINT 对应PHOENIX里的UNSIGNED_TINYINT 1字节\n# $UNSIGNED_SMALLINT 对应PHOENIX里的UNSIGNED_SMALLINT 2字节\n# $UNSIGNED_FLOAT 对应PHOENIX里的UNSIGNED_FLOAT 4字节\n# $UNSIGNED_DOUBLE 对应PHOENIX里的UNSIGNED_DOUBLE 8字节\n# $INTEGER 对应PHOENIX里的INTEGER 4字节\n# $BIGINT 对应PHOENIX里的BIGINT 8字节\n# $TINYINT 对应PHOENIX里的TINYINT 1字节\n# $SMALLINT 对应PHOENIX里的SMALLINT 2字节\n# $FLOAT 对应PHOENIX里的FLOAT 4字节\n# $DOUBLE 对应PHOENIX里的DOUBLE 8字节\n# $BOOLEAN 对应PHOENIX里的BOOLEAN 1字节\n# $TIME 对应PHOENIX里的TIME 8字节\n# $DATE 对应PHOENIX里的DATE 8字节\n# $TIMESTAMP 对应PHOENIX里的TIMESTAMP 12字节\n# $UNSIGNED_TIME 对应PHOENIX里的UNSIGNED_TIME 8字节\n# $UNSIGNED_DATE 对应PHOENIX里的UNSIGNED_DATE 8字节\n# $UNSIGNED_TIMESTAMP 对应PHOENIX里的UNSIGNED_TIMESTAMP 12字节\n# $VARCHAR 对应PHOENIX里的VARCHAR 动态长度\n# $VARBINARY 对应PHOENIX里的VARBINARY 动态长度\n# $DECIMAL 对应PHOENIX里的DECIMAL 动态长度', '2019-01-01 17:08:08');
|
||||||
|
INSERT INTO `canal_adapter_config` VALUES ('3', 'rdb', 'mytest_user.yml', 'dataSourceKey: defaultDS\ndestination: example\nouterAdapterKey: oracle1\nconcurrent: true\ndbMapping:\n database: mytest\n table: user\n targetTable: mytest.tb_user\n targetPk:\n id: id\n# mapAll: true\n targetColumns:\n id:\n name:\n role_id:\n c_time:\n test1:\n\n\n# Mirror schema synchronize config\n#dataSourceKey: defaultDS\n#destination: example\n#outerAdapterKey: mysql1\n#concurrent: true\n#dbMapping:\n# mirrorDb: true\n# database: mytest\n', '2019-01-01 17:08:50');
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -7,11 +7,7 @@ import org.apache.commons.lang.StringUtils;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.common.MQProperties;
|
import com.alibaba.otter.canal.deployer.monitor.ManagerRemoteConfigMonitor;
|
||||||
import com.alibaba.otter.canal.kafka.CanalKafkaProducer;
|
|
||||||
import com.alibaba.otter.canal.rocketmq.CanalRocketMQProducer;
|
|
||||||
import com.alibaba.otter.canal.server.CanalMQStarter;
|
|
||||||
import com.alibaba.otter.canal.spi.CanalMQProducer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* canal独立版本启动的入口类
|
* canal独立版本启动的入口类
|
||||||
@@ -21,17 +17,20 @@ import com.alibaba.otter.canal.spi.CanalMQProducer;
|
|||||||
*/
|
*/
|
||||||
public class CanalLauncher {
|
public class CanalLauncher {
|
||||||
|
|
||||||
private static final String CLASSPATH_URL_PREFIX = "classpath:";
|
private static final String CLASSPATH_URL_PREFIX = "classpath:";
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CanalLauncher.class);
|
private static final Logger logger = LoggerFactory.getLogger(CanalLauncher.class);
|
||||||
|
public static volatile boolean running = false;
|
||||||
|
|
||||||
public static void main(String[] args) throws Throwable {
|
public static void main(String[] args) {
|
||||||
try {
|
try {
|
||||||
|
running = true;
|
||||||
logger.info("## set default uncaught exception handler");
|
logger.info("## set default uncaught exception handler");
|
||||||
setGlobalUncaughtExceptionHandler();
|
setGlobalUncaughtExceptionHandler();
|
||||||
|
|
||||||
logger.info("## load canal configurations");
|
logger.info("## load canal configurations");
|
||||||
String conf = System.getProperty("canal.conf", "classpath:canal.properties");
|
String conf = System.getProperty("canal.conf", "classpath:canal.properties");
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
|
ManagerRemoteConfigMonitor managerDbConfigMonitor = null;
|
||||||
if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
|
if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
|
||||||
conf = StringUtils.substringAfter(conf, CLASSPATH_URL_PREFIX);
|
conf = StringUtils.substringAfter(conf, CLASSPATH_URL_PREFIX);
|
||||||
properties.load(CanalLauncher.class.getClassLoader().getResourceAsStream(conf));
|
properties.load(CanalLauncher.class.getClassLoader().getResourceAsStream(conf));
|
||||||
@@ -39,108 +38,56 @@ public class CanalLauncher {
|
|||||||
properties.load(new FileInputStream(conf));
|
properties.load(new FileInputStream(conf));
|
||||||
}
|
}
|
||||||
|
|
||||||
CanalMQProducer canalMQProducer = null;
|
String jdbcUrl = properties.getProperty("canal.manager.jdbc.url");
|
||||||
String serverMode = CanalController.getProperty(properties, CanalConstants.CANAL_SERVER_MODE);
|
if (!StringUtils.isEmpty(jdbcUrl)) {
|
||||||
if (serverMode.equalsIgnoreCase("kafka")) {
|
logger.info("## load remote canal configurations");
|
||||||
canalMQProducer = new CanalKafkaProducer();
|
// load remote config
|
||||||
} else if (serverMode.equalsIgnoreCase("rocketmq")) {
|
String jdbcUsername = properties.getProperty("canal.manager.jdbc.username");
|
||||||
canalMQProducer = new CanalRocketMQProducer();
|
String jdbcPassword = properties.getProperty("canal.manager.jdbc.password");
|
||||||
}
|
managerDbConfigMonitor = new ManagerRemoteConfigMonitor(jdbcUrl, jdbcUsername, jdbcPassword);
|
||||||
|
// 加载远程canal.properties
|
||||||
if (canalMQProducer != null) {
|
Properties remoteConfig = managerDbConfigMonitor.loadRemoteConfig();
|
||||||
// disable netty
|
// 加载remote instance配置
|
||||||
System.setProperty(CanalConstants.CANAL_WITHOUT_NETTY, "true");
|
managerDbConfigMonitor.loadRemoteInstanceConfigs();
|
||||||
}
|
if (remoteConfig != null) {
|
||||||
|
properties = remoteConfig;
|
||||||
logger.info("## start the canal server.");
|
} else {
|
||||||
final CanalController controller = new CanalController(properties);
|
managerDbConfigMonitor = null;
|
||||||
controller.start();
|
|
||||||
logger.info("## the canal server is running now ......");
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
logger.info("## stop the canal server");
|
|
||||||
controller.stop();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
logger.warn("##something goes wrong when stopping canal Server:", e);
|
|
||||||
} finally {
|
|
||||||
logger.info("## canal server is down.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("## load canal configurations");
|
||||||
|
}
|
||||||
|
|
||||||
});
|
final CanalStater canalStater = new CanalStater();
|
||||||
|
canalStater.start(properties);
|
||||||
|
|
||||||
if (canalMQProducer != null) {
|
if (managerDbConfigMonitor != null) {
|
||||||
CanalMQStarter canalMQStarter = new CanalMQStarter(canalMQProducer);
|
managerDbConfigMonitor.start(new ManagerRemoteConfigMonitor.Listener<Properties>() {
|
||||||
MQProperties mqProperties = buildMQPosition(properties);
|
|
||||||
canalMQStarter.start(mqProperties);
|
@Override
|
||||||
controller.setCanalMQStarter(canalMQStarter);
|
public void onChange(Properties properties) {
|
||||||
|
try {
|
||||||
|
// 远程配置canal.properties修改重新加载整个应用
|
||||||
|
canalStater.destroy();
|
||||||
|
canalStater.start(properties);
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
logger.error(throwable.getMessage(), throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
while (running)
|
||||||
|
;
|
||||||
|
|
||||||
|
if (managerDbConfigMonitor != null) {
|
||||||
|
managerDbConfigMonitor.destroy();
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.error("## Something goes wrong when starting up the canal Server:", e);
|
logger.error("## Something goes wrong when starting up the canal Server:", e);
|
||||||
System.exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MQProperties buildMQPosition(Properties properties) {
|
|
||||||
MQProperties mqProperties = new MQProperties();
|
|
||||||
String servers = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_SERVERS);
|
|
||||||
if (!StringUtils.isEmpty(servers)) {
|
|
||||||
mqProperties.setServers(servers);
|
|
||||||
}
|
|
||||||
String retires = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_RETRIES);
|
|
||||||
if (!StringUtils.isEmpty(retires)) {
|
|
||||||
mqProperties.setRetries(Integer.valueOf(retires));
|
|
||||||
}
|
|
||||||
String batchSize = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_BATCHSIZE);
|
|
||||||
if (!StringUtils.isEmpty(batchSize)) {
|
|
||||||
mqProperties.setBatchSize(Integer.valueOf(batchSize));
|
|
||||||
}
|
|
||||||
String lingerMs = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_LINGERMS);
|
|
||||||
if (!StringUtils.isEmpty(lingerMs)) {
|
|
||||||
mqProperties.setLingerMs(Integer.valueOf(lingerMs));
|
|
||||||
}
|
|
||||||
String maxRequestSize = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_MAXREQUESTSIZE);
|
|
||||||
if (!StringUtils.isEmpty(maxRequestSize)) {
|
|
||||||
mqProperties.setMaxRequestSize(Integer.valueOf(maxRequestSize));
|
|
||||||
}
|
|
||||||
String bufferMemory = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_BUFFERMEMORY);
|
|
||||||
if (!StringUtils.isEmpty(bufferMemory)) {
|
|
||||||
mqProperties.setBufferMemory(Long.valueOf(bufferMemory));
|
|
||||||
}
|
|
||||||
String canalBatchSize = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_CANALBATCHSIZE);
|
|
||||||
if (!StringUtils.isEmpty(canalBatchSize)) {
|
|
||||||
mqProperties.setCanalBatchSize(Integer.valueOf(canalBatchSize));
|
|
||||||
}
|
|
||||||
String canalGetTimeout = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_CANALGETTIMEOUT);
|
|
||||||
if (!StringUtils.isEmpty(canalGetTimeout)) {
|
|
||||||
mqProperties.setCanalGetTimeout(Long.valueOf(canalGetTimeout));
|
|
||||||
}
|
|
||||||
String flatMessage = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_FLATMESSAGE);
|
|
||||||
if (!StringUtils.isEmpty(flatMessage)) {
|
|
||||||
mqProperties.setFlatMessage(Boolean.valueOf(flatMessage));
|
|
||||||
}
|
|
||||||
String compressionType = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_COMPRESSION_TYPE);
|
|
||||||
if (!StringUtils.isEmpty(compressionType)) {
|
|
||||||
mqProperties.setCompressionType(compressionType);
|
|
||||||
}
|
|
||||||
String acks = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_ACKS);
|
|
||||||
if (!StringUtils.isEmpty(acks)) {
|
|
||||||
mqProperties.setAcks(acks);
|
|
||||||
}
|
|
||||||
|
|
||||||
String aliyunAccessKey = CanalController.getProperty(properties, CanalConstants.CANAL_ALIYUN_ACCESSKEY);
|
|
||||||
if (!StringUtils.isEmpty(aliyunAccessKey)) {
|
|
||||||
mqProperties.setAliyunAccessKey(aliyunAccessKey);
|
|
||||||
}
|
|
||||||
String aliyunSecretKey = CanalController.getProperty(properties, CanalConstants.CANAL_ALIYUN_SECRETKEY);
|
|
||||||
if (!StringUtils.isEmpty(aliyunSecretKey)) {
|
|
||||||
mqProperties.setAliyunSecretKey(aliyunSecretKey);
|
|
||||||
}
|
|
||||||
return mqProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void setGlobalUncaughtExceptionHandler() {
|
private static void setGlobalUncaughtExceptionHandler() {
|
||||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||||
|
|
||||||
@@ -150,4 +97,5 @@ public class CanalLauncher {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
package com.alibaba.otter.canal.deployer;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.common.MQProperties;
|
||||||
|
import com.alibaba.otter.canal.kafka.CanalKafkaProducer;
|
||||||
|
import com.alibaba.otter.canal.rocketmq.CanalRocketMQProducer;
|
||||||
|
import com.alibaba.otter.canal.server.CanalMQStarter;
|
||||||
|
import com.alibaba.otter.canal.spi.CanalMQProducer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Canal server 启动类
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-12-30 下午05:12:16
|
||||||
|
* @version 1.0.1
|
||||||
|
*/
|
||||||
|
public class CanalStater {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(CanalStater.class);
|
||||||
|
|
||||||
|
private CanalController controller = null;
|
||||||
|
private CanalMQProducer canalMQProducer = null;
|
||||||
|
private Thread shutdownThread = null;
|
||||||
|
private CanalMQStarter canalMQStarter = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动方法
|
||||||
|
*
|
||||||
|
* @param properties canal.properties 配置
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
synchronized void start(Properties properties) throws Throwable {
|
||||||
|
String serverMode = CanalController.getProperty(properties, CanalConstants.CANAL_SERVER_MODE);
|
||||||
|
if (serverMode.equalsIgnoreCase("kafka")) {
|
||||||
|
canalMQProducer = new CanalKafkaProducer();
|
||||||
|
} else if (serverMode.equalsIgnoreCase("rocketmq")) {
|
||||||
|
canalMQProducer = new CanalRocketMQProducer();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canalMQProducer != null) {
|
||||||
|
// disable netty
|
||||||
|
System.setProperty(CanalConstants.CANAL_WITHOUT_NETTY, "true");
|
||||||
|
System.setProperty(CanalConstants.CANAL_DESTINATIONS,
|
||||||
|
properties.getProperty(CanalConstants.CANAL_DESTINATIONS));
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("## start the canal server.");
|
||||||
|
controller = new CanalController(properties);
|
||||||
|
controller.start();
|
||||||
|
logger.info("## the canal server is running now ......");
|
||||||
|
shutdownThread = new Thread() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
logger.info("## stop the canal server");
|
||||||
|
controller.stop();
|
||||||
|
CanalLauncher.running = false;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("##something goes wrong when stopping canal Server:", e);
|
||||||
|
} finally {
|
||||||
|
logger.info("## canal server is down.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
Runtime.getRuntime().addShutdownHook(shutdownThread);
|
||||||
|
|
||||||
|
if (canalMQProducer != null) {
|
||||||
|
canalMQStarter = new CanalMQStarter(canalMQProducer);
|
||||||
|
MQProperties mqProperties = buildMQProperties(properties);
|
||||||
|
canalMQStarter.start(mqProperties);
|
||||||
|
controller.setCanalMQStarter(canalMQStarter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁方法,远程配置变更时调用
|
||||||
|
*
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
synchronized void destroy() throws Throwable {
|
||||||
|
if (controller != null) {
|
||||||
|
controller.stop();
|
||||||
|
controller = null;
|
||||||
|
}
|
||||||
|
if (shutdownThread != null) {
|
||||||
|
Runtime.getRuntime().removeShutdownHook(shutdownThread);
|
||||||
|
shutdownThread = null;
|
||||||
|
}
|
||||||
|
if (canalMQProducer != null && canalMQStarter != null) {
|
||||||
|
canalMQStarter.destroy();
|
||||||
|
canalMQStarter = null;
|
||||||
|
canalMQProducer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造MQ对应的配置
|
||||||
|
*
|
||||||
|
* @param properties canal.properties 配置
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static MQProperties buildMQProperties(Properties properties) {
|
||||||
|
MQProperties mqProperties = new MQProperties();
|
||||||
|
String servers = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_SERVERS);
|
||||||
|
if (!StringUtils.isEmpty(servers)) {
|
||||||
|
mqProperties.setServers(servers);
|
||||||
|
}
|
||||||
|
String retires = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_RETRIES);
|
||||||
|
if (!StringUtils.isEmpty(retires)) {
|
||||||
|
mqProperties.setRetries(Integer.valueOf(retires));
|
||||||
|
}
|
||||||
|
String batchSize = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_BATCHSIZE);
|
||||||
|
if (!StringUtils.isEmpty(batchSize)) {
|
||||||
|
mqProperties.setBatchSize(Integer.valueOf(batchSize));
|
||||||
|
}
|
||||||
|
String lingerMs = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_LINGERMS);
|
||||||
|
if (!StringUtils.isEmpty(lingerMs)) {
|
||||||
|
mqProperties.setLingerMs(Integer.valueOf(lingerMs));
|
||||||
|
}
|
||||||
|
String maxRequestSize = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_MAXREQUESTSIZE);
|
||||||
|
if (!StringUtils.isEmpty(maxRequestSize)) {
|
||||||
|
mqProperties.setMaxRequestSize(Integer.valueOf(maxRequestSize));
|
||||||
|
}
|
||||||
|
String bufferMemory = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_BUFFERMEMORY);
|
||||||
|
if (!StringUtils.isEmpty(bufferMemory)) {
|
||||||
|
mqProperties.setBufferMemory(Long.valueOf(bufferMemory));
|
||||||
|
}
|
||||||
|
String canalBatchSize = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_CANALBATCHSIZE);
|
||||||
|
if (!StringUtils.isEmpty(canalBatchSize)) {
|
||||||
|
mqProperties.setCanalBatchSize(Integer.valueOf(canalBatchSize));
|
||||||
|
}
|
||||||
|
String canalGetTimeout = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_CANALGETTIMEOUT);
|
||||||
|
if (!StringUtils.isEmpty(canalGetTimeout)) {
|
||||||
|
mqProperties.setCanalGetTimeout(Long.valueOf(canalGetTimeout));
|
||||||
|
}
|
||||||
|
String flatMessage = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_FLATMESSAGE);
|
||||||
|
if (!StringUtils.isEmpty(flatMessage)) {
|
||||||
|
mqProperties.setFlatMessage(Boolean.valueOf(flatMessage));
|
||||||
|
}
|
||||||
|
String compressionType = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_COMPRESSION_TYPE);
|
||||||
|
if (!StringUtils.isEmpty(compressionType)) {
|
||||||
|
mqProperties.setCompressionType(compressionType);
|
||||||
|
}
|
||||||
|
String acks = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_ACKS);
|
||||||
|
if (!StringUtils.isEmpty(acks)) {
|
||||||
|
mqProperties.setAcks(acks);
|
||||||
|
}
|
||||||
|
|
||||||
|
String aliyunAccessKey = CanalController.getProperty(properties, CanalConstants.CANAL_ALIYUN_ACCESSKEY);
|
||||||
|
if (!StringUtils.isEmpty(aliyunAccessKey)) {
|
||||||
|
mqProperties.setAliyunAccessKey(aliyunAccessKey);
|
||||||
|
}
|
||||||
|
String aliyunSecretKey = CanalController.getProperty(properties, CanalConstants.CANAL_ALIYUN_SECRETKEY);
|
||||||
|
if (!StringUtils.isEmpty(aliyunSecretKey)) {
|
||||||
|
mqProperties.setAliyunSecretKey(aliyunSecretKey);
|
||||||
|
}
|
||||||
|
return mqProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,358 @@
|
|||||||
|
package com.alibaba.otter.canal.deployer.monitor;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.common.utils.NamedThreadFactory;
|
||||||
|
import com.alibaba.otter.canal.deployer.CanalConstants;
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.collect.MapMaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 远程配置装载监控
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-12-30 下午05:12:16
|
||||||
|
* @version 1.0.1
|
||||||
|
*/
|
||||||
|
public class ManagerRemoteConfigMonitor {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ManagerRemoteConfigMonitor.class);
|
||||||
|
|
||||||
|
private Map<String, ConfigItem> remoteInstanceConfigs = new MapMaker().makeMap();
|
||||||
|
|
||||||
|
private Connection conn;
|
||||||
|
private String jdbcUrl;
|
||||||
|
private String jdbcUsername;
|
||||||
|
private String jdbcPassword;
|
||||||
|
|
||||||
|
private long currentConfigTimestamp = 0;
|
||||||
|
|
||||||
|
private long scanIntervalInSecond = 5;
|
||||||
|
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(2,
|
||||||
|
new NamedThreadFactory("remote-canal-config-scan"));
|
||||||
|
|
||||||
|
public ManagerRemoteConfigMonitor(String jdbcUrl, String jdbcUsername, String jdbcPassword){
|
||||||
|
this.jdbcUrl = jdbcUrl;
|
||||||
|
this.jdbcUsername = jdbcUsername;
|
||||||
|
this.jdbcPassword = jdbcPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScanIntervalInSecond(long scanIntervalInSecond) {
|
||||||
|
this.scanIntervalInSecond = scanIntervalInSecond;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Connection getConn() throws Exception {
|
||||||
|
if (conn == null || conn.isClosed()) {
|
||||||
|
Class.forName("com.mysql.jdbc.Driver");
|
||||||
|
conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
|
||||||
|
}
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载远程 canal.properties文件 覆盖本地
|
||||||
|
*
|
||||||
|
* @return 远程配置的properties
|
||||||
|
*/
|
||||||
|
public Properties loadRemoteConfig() {
|
||||||
|
Properties properties = null;
|
||||||
|
try {
|
||||||
|
// 加载远程canal配置
|
||||||
|
ConfigItem configItem = getRemoteCanalConfig();
|
||||||
|
if (configItem != null) {
|
||||||
|
if (configItem.getModifiedTime() != currentConfigTimestamp) {
|
||||||
|
currentConfigTimestamp = configItem.getModifiedTime();
|
||||||
|
overrideLocalCanalConfig(configItem.getContent());
|
||||||
|
properties = new Properties();
|
||||||
|
properties.load(new ByteArrayInputStream(configItem.getContent().getBytes(StandardCharsets.UTF_8)));
|
||||||
|
scanIntervalInSecond = Integer
|
||||||
|
.valueOf(properties.getProperty(CanalConstants.CANAL_AUTO_SCAN_INTERVAL, "5"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载远程的instance配置 覆盖本地
|
||||||
|
*/
|
||||||
|
public void loadRemoteInstanceConfigs() {
|
||||||
|
try {
|
||||||
|
// 加载远程instance配置
|
||||||
|
Map<String, ConfigItem>[] modifiedConfigs = getModifiedInstanceConfigs();
|
||||||
|
if (modifiedConfigs != null) {
|
||||||
|
overrideLocalInstanceConfigs(modifiedConfigs);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取远程canal.properties配置内容
|
||||||
|
*
|
||||||
|
* @return 内容对象
|
||||||
|
*/
|
||||||
|
private ConfigItem getRemoteCanalConfig() {
|
||||||
|
String sql = "select name, content, modified_time from canal_config where id=1";
|
||||||
|
try (Statement stmt = getConn().createStatement(); ResultSet rs = stmt.executeQuery(sql)) {
|
||||||
|
if (rs.next()) {
|
||||||
|
ConfigItem configItem = new ConfigItem();
|
||||||
|
configItem.setId(1L);
|
||||||
|
configItem.setName(rs.getString("name"));
|
||||||
|
configItem.setContent(rs.getString("content"));
|
||||||
|
configItem.setModifiedTime(rs.getTimestamp("modified_time").getTime());
|
||||||
|
return configItem;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 覆盖本地 canal.properties
|
||||||
|
*
|
||||||
|
* @param content 远程配置内容文本
|
||||||
|
*/
|
||||||
|
private void overrideLocalCanalConfig(String content) {
|
||||||
|
try (FileWriter writer = new FileWriter(getConfPath() + "canal.properties")) {
|
||||||
|
writer.write(content);
|
||||||
|
writer.flush();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取远程instance新增、修改、删除配置
|
||||||
|
*
|
||||||
|
* @return Map[0]:新增或修改的instance配置; Map[1]:删除的instance配置
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Map<String, ConfigItem>[] getModifiedInstanceConfigs() {
|
||||||
|
Map<String, ConfigItem>[] res = new Map[2];
|
||||||
|
Map<String, ConfigItem> remoteConfigStatus = new HashMap<>();
|
||||||
|
String sql = "select id, name, modified_time from canal_instance_config";
|
||||||
|
try (Statement stmt = getConn().createStatement(); ResultSet rs = stmt.executeQuery(sql)) {
|
||||||
|
while (rs.next()) {
|
||||||
|
ConfigItem configItem = new ConfigItem();
|
||||||
|
configItem.setId(rs.getLong("id"));
|
||||||
|
configItem.setName(rs.getString("name"));
|
||||||
|
configItem.setModifiedTime(rs.getTimestamp("modified_time").getTime());
|
||||||
|
remoteConfigStatus.put(configItem.getName(), configItem);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!remoteConfigStatus.isEmpty()) {
|
||||||
|
List<Long> changedIds = new ArrayList<>();
|
||||||
|
|
||||||
|
for (ConfigItem remoteConfigStat : remoteConfigStatus.values()) {
|
||||||
|
ConfigItem currentConfig = remoteInstanceConfigs.get(remoteConfigStat.getName());
|
||||||
|
if (currentConfig == null) {
|
||||||
|
// 新增
|
||||||
|
changedIds.add(remoteConfigStat.getId());
|
||||||
|
} else {
|
||||||
|
// 修改
|
||||||
|
if (currentConfig.getModifiedTime() != remoteConfigStat.getModifiedTime()) {
|
||||||
|
changedIds.add(remoteConfigStat.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!changedIds.isEmpty()) {
|
||||||
|
Map<String, ConfigItem> changedInstanceConfig = new HashMap<>();
|
||||||
|
String contentsSql = "select id, name, content, modified_time from canal_instance_config where id in ("
|
||||||
|
+ Joiner.on(",").join(changedIds) + ")";
|
||||||
|
try (Statement stmt = getConn().createStatement(); ResultSet rs = stmt.executeQuery(contentsSql)) {
|
||||||
|
while (rs.next()) {
|
||||||
|
ConfigItem configItemNew = new ConfigItem();
|
||||||
|
configItemNew.setId(rs.getLong("id"));
|
||||||
|
configItemNew.setName(rs.getString("name"));
|
||||||
|
configItemNew.setContent(rs.getString("content"));
|
||||||
|
configItemNew.setModifiedTime(rs.getTimestamp("modified_time").getTime());
|
||||||
|
|
||||||
|
remoteInstanceConfigs.put(configItemNew.getName(), configItemNew);
|
||||||
|
changedInstanceConfig.put(configItemNew.getName(), configItemNew);
|
||||||
|
}
|
||||||
|
|
||||||
|
res[0] = changedInstanceConfig.isEmpty() ? null : changedInstanceConfig;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, ConfigItem> removedInstanceConfig = new HashMap<>();
|
||||||
|
for (String name : remoteInstanceConfigs.keySet()) {
|
||||||
|
if (!remoteConfigStatus.containsKey(name)) {
|
||||||
|
// 删除
|
||||||
|
remoteInstanceConfigs.remove(name);
|
||||||
|
removedInstanceConfig.put(name, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res[1] = removedInstanceConfig.isEmpty() ? null : removedInstanceConfig;
|
||||||
|
|
||||||
|
if (res[0] == null && res[1] == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 覆盖本地instance配置
|
||||||
|
*
|
||||||
|
* @param modifiedInstanceConfigs 有变更的配置项
|
||||||
|
*/
|
||||||
|
private void overrideLocalInstanceConfigs(Map<String, ConfigItem>[] modifiedInstanceConfigs) {
|
||||||
|
Map<String, ConfigItem> changedInstanceConfigs = modifiedInstanceConfigs[0];
|
||||||
|
if (changedInstanceConfigs != null) {
|
||||||
|
for (ConfigItem configItem : changedInstanceConfigs.values()) {
|
||||||
|
try (FileWriter writer = new FileWriter(
|
||||||
|
getConfPath() + configItem.getName() + "/instance.properties")) {
|
||||||
|
writer.write(configItem.getContent());
|
||||||
|
writer.flush();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<String, ConfigItem> removedInstanceConfigs = modifiedInstanceConfigs[1];
|
||||||
|
if (removedInstanceConfigs != null) {
|
||||||
|
for (String name : removedInstanceConfigs.keySet()) {
|
||||||
|
File file = new File(getConfPath() + name + "/");
|
||||||
|
if (file.exists()) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听 canal 主配置和 instance 配置变化
|
||||||
|
*
|
||||||
|
* @param listener 监听回调方法
|
||||||
|
*/
|
||||||
|
public void start(final Listener<Properties> listener) {
|
||||||
|
// 监听canal.properties变化
|
||||||
|
executor.scheduleWithFixedDelay(new Runnable() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
Properties properties = loadRemoteConfig();
|
||||||
|
if (properties != null) {
|
||||||
|
listener.onChange(properties);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("scan failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 10, scanIntervalInSecond, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
// 监听instance变化
|
||||||
|
executor.scheduleWithFixedDelay(new Runnable() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
loadRemoteInstanceConfigs();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("scan failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 10, 3, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁
|
||||||
|
*/
|
||||||
|
public void destroy() {
|
||||||
|
executor.shutdownNow();
|
||||||
|
if (conn != null) {
|
||||||
|
try {
|
||||||
|
conn.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取conf文件夹所在路径
|
||||||
|
*
|
||||||
|
* @return 路径地址
|
||||||
|
*/
|
||||||
|
private String getConfPath() {
|
||||||
|
String classpath = this.getClass().getResource("/").getPath();
|
||||||
|
String confPath = classpath + ".." + File.separator + "conf" + File.separator;
|
||||||
|
if (new File(confPath).exists()) {
|
||||||
|
return confPath;
|
||||||
|
} else {
|
||||||
|
return classpath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置对应对象
|
||||||
|
*/
|
||||||
|
public static class ConfigItem {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String name;
|
||||||
|
private String content;
|
||||||
|
private long modifiedTime;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getModifiedTime() {
|
||||||
|
return modifiedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiedTime(long modifiedTime) {
|
||||||
|
this.modifiedTime = modifiedTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Listener<Properties> {
|
||||||
|
|
||||||
|
void onChange(Properties properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
#################################################
|
#################################################
|
||||||
######### common argument #############
|
######### common argument #############
|
||||||
#################################################
|
#################################################
|
||||||
|
#canal.manager.jdbc.url=jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8
|
||||||
|
#canal.manager.jdbc.username=root
|
||||||
|
#canal.manager.jdbc.password=121212
|
||||||
canal.id = 1
|
canal.id = 1
|
||||||
canal.ip =
|
canal.ip =
|
||||||
canal.port = 11111
|
canal.port = 11111
|
||||||
|
|||||||
@@ -45,8 +45,10 @@ canal.instance.filter.black.regex=
|
|||||||
|
|
||||||
# mq config
|
# mq config
|
||||||
canal.mq.topic=example
|
canal.mq.topic=example
|
||||||
|
# dynamic topic route by table regex
|
||||||
|
#canal.mq.dynamicTopic=.*,mytest\\..*,mytest2.user
|
||||||
canal.mq.partition=0
|
canal.mq.partition=0
|
||||||
# hash partition config
|
# hash partition config
|
||||||
#canal.mq.partitionsNum=3
|
#canal.mq.partitionsNum=3
|
||||||
#canal.mq.partitionHash=mytest.person:id,mytest.role:id
|
#canal.mq.partitionHash=test.table:id^name,.*\\..*
|
||||||
#################################################
|
#################################################
|
||||||
@@ -826,7 +826,7 @@
|
|||||||
"bars": false,
|
"bars": false,
|
||||||
"dashLength": 10,
|
"dashLength": 10,
|
||||||
"dashes": false,
|
"dashes": false,
|
||||||
"datasource": "${DS_PROMETHEUS}",
|
"datasource": "$datasource",
|
||||||
"description": "client 请求的GET与ACK包的QPS。",
|
"description": "client 请求的GET与ACK包的QPS。",
|
||||||
"fill": 1,
|
"fill": 1,
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
|
|||||||
@@ -193,6 +193,7 @@
|
|||||||
|
|
||||||
<bean id="mqConfig" class="com.alibaba.otter.canal.instance.core.CanalMQConfig">
|
<bean id="mqConfig" class="com.alibaba.otter.canal.instance.core.CanalMQConfig">
|
||||||
<property name="topic" value="${canal.mq.topic}" />
|
<property name="topic" value="${canal.mq.topic}" />
|
||||||
|
<property name="dynamicTopic" value="${canal.mq.dynamicTopic}" />
|
||||||
<property name="partition" value="${canal.mq.partition}" />
|
<property name="partition" value="${canal.mq.partition}" />
|
||||||
<property name="partitionsNum" value="${canal.mq.partitionsNum}" />
|
<property name="partitionsNum" value="${canal.mq.partitionsNum}" />
|
||||||
<property name="partitionHash" value="${canal.mq.partitionHash}" />
|
<property name="partitionHash" value="${canal.mq.partitionHash}" />
|
||||||
|
|||||||
@@ -178,6 +178,7 @@
|
|||||||
|
|
||||||
<bean id="mqConfig" class="com.alibaba.otter.canal.instance.core.CanalMQConfig">
|
<bean id="mqConfig" class="com.alibaba.otter.canal.instance.core.CanalMQConfig">
|
||||||
<property name="topic" value="${canal.mq.topic}" />
|
<property name="topic" value="${canal.mq.topic}" />
|
||||||
|
<property name="dynamicTopic" value="${canal.mq.dynamicTopic}" />
|
||||||
<property name="partition" value="${canal.mq.partition}" />
|
<property name="partition" value="${canal.mq.partition}" />
|
||||||
<property name="partitionsNum" value="${canal.mq.partitionsNum}" />
|
<property name="partitionsNum" value="${canal.mq.partitionsNum}" />
|
||||||
<property name="partitionHash" value="${canal.mq.partitionHash}" />
|
<property name="partitionHash" value="${canal.mq.partitionHash}" />
|
||||||
|
|||||||
@@ -268,6 +268,7 @@
|
|||||||
|
|
||||||
<bean id="mqConfig" class="com.alibaba.otter.canal.instance.core.CanalMQConfig">
|
<bean id="mqConfig" class="com.alibaba.otter.canal.instance.core.CanalMQConfig">
|
||||||
<property name="topic" value="${canal.mq.topic}" />
|
<property name="topic" value="${canal.mq.topic}" />
|
||||||
|
<property name="dynamicTopic" value="${canal.mq.dynamicTopic}" />
|
||||||
<property name="partition" value="${canal.mq.partition}" />
|
<property name="partition" value="${canal.mq.partition}" />
|
||||||
<property name="partitionsNum" value="${canal.mq.partitionsNum}" />
|
<property name="partitionsNum" value="${canal.mq.partitionsNum}" />
|
||||||
<property name="partitionHash" value="${canal.mq.partitionHash}" />
|
<property name="partitionHash" value="${canal.mq.partitionHash}" />
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
|
|
||||||
<bean id="mqConfig" class="com.alibaba.otter.canal.instance.core.CanalMQConfig">
|
<bean id="mqConfig" class="com.alibaba.otter.canal.instance.core.CanalMQConfig">
|
||||||
<property name="topic" value="${canal.mq.topic}" />
|
<property name="topic" value="${canal.mq.topic}" />
|
||||||
|
<property name="dynamicTopic" value="${canal.mq.dynamicTopic}" />
|
||||||
<property name="partition" value="${canal.mq.partition}" />
|
<property name="partition" value="${canal.mq.partition}" />
|
||||||
<property name="partitionsNum" value="${canal.mq.partitionsNum}" />
|
<property name="partitionsNum" value="${canal.mq.partitionsNum}" />
|
||||||
<property name="partitionHash" value="${canal.mq.partitionHash}" />
|
<property name="partitionHash" value="${canal.mq.partitionHash}" />
|
||||||
|
|||||||
@@ -19,11 +19,6 @@ if [ "$1" == "base" ] ; then
|
|||||||
else
|
else
|
||||||
rm -rf $BASE/canal.*.tar.gz ;
|
rm -rf $BASE/canal.*.tar.gz ;
|
||||||
cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ;
|
cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ;
|
||||||
if [ "$1" == "kafka" ] ; then
|
cp $BASE/../target/canal.deployer-*.tar.gz $BASE/
|
||||||
cp $BASE/../target/canal-kafka-*.tar.gz $BASE/
|
docker build --no-cache -t canal/canal-server $BASE/
|
||||||
docker build --no-cache -t canal/canal-server $BASE/
|
|
||||||
else
|
|
||||||
cp $BASE/../target/canal.deployer-*.tar.gz $BASE/
|
|
||||||
docker build --no-cache -t canal/canal-server $BASE/
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -87,6 +87,6 @@ done
|
|||||||
|
|
||||||
MEMORY="-m 4096m"
|
MEMORY="-m 4096m"
|
||||||
LOCALHOST=`getMyIp`
|
LOCALHOST=`getMyIp`
|
||||||
cmd="docker run -it -h $LOCALHOST $CONFIG --name=canal-server $VOLUMNS $NET_MODE $PORTS $MEMORY canal/canal-server"
|
cmd="docker run -d -it -h $LOCALHOST $CONFIG --name=canal-server $VOLUMNS $NET_MODE $PORTS $MEMORY canal/canal-server"
|
||||||
echo $cmd
|
echo $cmd
|
||||||
eval $cmd
|
eval $cmd
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0%
|
|||||||
set conf_dir=%ENV_PATH%\..\conf
|
set conf_dir=%ENV_PATH%\..\conf
|
||||||
set logback_configurationFile=%conf_dir%\logback.xml
|
set logback_configurationFile=%conf_dir%\logback.xml
|
||||||
set client_mode=Simple
|
set client_mode=Simple
|
||||||
if "%1%" != "" set client_mode=%1%
|
if not "%1" == "" set client_mode=%1
|
||||||
|
|
||||||
set CLASSPATH=%conf_dir%
|
set CLASSPATH=%conf_dir%
|
||||||
set CLASSPATH=%conf_dir%\..\lib\*;%CLASSPATH%
|
set CLASSPATH=%conf_dir%\..\lib\*;%CLASSPATH%
|
||||||
@@ -20,7 +20,8 @@ set CANAL_OPTS= -DappName=otter-canal-example -Dlogback.configurationFile="%logb
|
|||||||
|
|
||||||
set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %JAVA_DEBUG_OPT% %CANAL_OPTS%
|
set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %JAVA_DEBUG_OPT% %CANAL_OPTS%
|
||||||
|
|
||||||
if "%client_mode%" == "Cluster"
|
if "%client_mode%" == "Cluster" (
|
||||||
java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.example.ClusterCanalClientTest
|
java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.example.ClusterCanalClientTest
|
||||||
else
|
) else (
|
||||||
java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.example.SimpleCanalClientTest
|
java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.example.SimpleCanalClientTest
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -7,17 +7,10 @@ import org.apache.oro.text.regex.Pattern;
|
|||||||
import org.apache.oro.text.regex.PatternCompiler;
|
import org.apache.oro.text.regex.PatternCompiler;
|
||||||
import org.apache.oro.text.regex.Perl5Compiler;
|
import org.apache.oro.text.regex.Perl5Compiler;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.filter.exception.CanalFilterException;
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.collect.MapMaker;
|
import com.google.common.collect.MapMaker;
|
||||||
import com.google.common.collect.MigrateMap;
|
import com.google.common.collect.MigrateMap;
|
||||||
|
|
||||||
/**
|
|
||||||
* 提供{@linkplain Pattern}的lazy get处理
|
|
||||||
*
|
|
||||||
* @author jianghang 2013-1-22 下午09:36:44
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
|
||||||
public class PatternUtils {
|
public class PatternUtils {
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@@ -32,7 +25,7 @@ public class PatternUtils {
|
|||||||
| Perl5Compiler.READ_ONLY_MASK
|
| Perl5Compiler.READ_ONLY_MASK
|
||||||
| Perl5Compiler.SINGLELINE_MASK);
|
| Perl5Compiler.SINGLELINE_MASK);
|
||||||
} catch (MalformedPatternException e) {
|
} catch (MalformedPatternException e) {
|
||||||
throw new CanalFilterException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -125,4 +125,9 @@ public class AviaterRegexFilter implements CanalEventFilter<String> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.instance.core</artifactId>
|
<artifactId>canal.instance.core</artifactId>
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
package com.alibaba.otter.canal.instance.core;
|
package com.alibaba.otter.canal.instance.core;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class CanalMQConfig {
|
public class CanalMQConfig {
|
||||||
|
|
||||||
private String topic;
|
private String topic;
|
||||||
private Integer partition;
|
private Integer partition;
|
||||||
private Integer partitionsNum;
|
private Integer partitionsNum;
|
||||||
private String partitionHash;
|
private String partitionHash;
|
||||||
|
private String dynamicTopic;
|
||||||
private volatile Map<String, String> partitionHashProperties;
|
|
||||||
|
|
||||||
public String getTopic() {
|
public String getTopic() {
|
||||||
return topic;
|
return topic;
|
||||||
@@ -44,25 +40,11 @@ public class CanalMQConfig {
|
|||||||
this.partitionHash = partitionHash;
|
this.partitionHash = partitionHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> getPartitionHashProperties() {
|
public String getDynamicTopic() {
|
||||||
if (partitionHashProperties == null) {
|
return dynamicTopic;
|
||||||
synchronized (CanalMQConfig.class) {
|
}
|
||||||
if (partitionHashProperties == null) {
|
|
||||||
if (partitionHash != null) {
|
public void setDynamicTopic(String dynamicTopic) {
|
||||||
partitionHashProperties = new LinkedHashMap<>();
|
this.dynamicTopic = dynamicTopic;
|
||||||
String[] items = partitionHash.split(",");
|
|
||||||
for (String item : items) {
|
|
||||||
int i = item.indexOf(":");
|
|
||||||
if (i > -1) {
|
|
||||||
String dbTable = item.substring(0, i).trim();
|
|
||||||
String pk = item.substring(i + 1).trim();
|
|
||||||
partitionHashProperties.put(dbTable, pk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return partitionHashProperties;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.parse</artifactId>
|
<artifactId>canal.parse</artifactId>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public interface ErosaConnection {
|
|||||||
/**
|
/**
|
||||||
* 用于快速数据查找,和dump的区别在于,seek会只给出部分的数据
|
* 用于快速数据查找,和dump的区别在于,seek会只给出部分的数据
|
||||||
*/
|
*/
|
||||||
public void seek(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException;
|
public void seek(String binlogfilename, Long binlogPosition, String gtid, SinkFunction func) throws IOException;
|
||||||
|
|
||||||
public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException;
|
public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException;
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
|
|||||||
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
||||||
import com.alibaba.otter.canal.parse.inbound.MultiStageCoprocessor;
|
import com.alibaba.otter.canal.parse.inbound.MultiStageCoprocessor;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DefaultTableMetaTSDBFactory;
|
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DefaultTableMetaTSDBFactory;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDB;
|
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDB;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDBFactory;
|
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDBFactory;
|
||||||
@@ -64,8 +65,29 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
|||||||
super.setEventFilter(eventFilter);
|
super.setEventFilter(eventFilter);
|
||||||
|
|
||||||
// 触发一下filter变更
|
// 触发一下filter变更
|
||||||
if (eventFilter != null && eventFilter instanceof AviaterRegexFilter && binlogParser instanceof LogEventConvert) {
|
if (eventFilter != null && eventFilter instanceof AviaterRegexFilter) {
|
||||||
((LogEventConvert) binlogParser).setNameFilter((AviaterRegexFilter) eventFilter);
|
if (binlogParser instanceof LogEventConvert) {
|
||||||
|
((LogEventConvert) binlogParser).setNameFilter((AviaterRegexFilter) eventFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tableMetaTSDB != null && tableMetaTSDB instanceof DatabaseTableMeta) {
|
||||||
|
((DatabaseTableMeta) tableMetaTSDB).setFilter(eventFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEventBlackFilter(CanalEventFilter eventBlackFilter) {
|
||||||
|
super.setEventBlackFilter(eventBlackFilter);
|
||||||
|
|
||||||
|
// 触发一下filter变更
|
||||||
|
if (eventBlackFilter != null && eventBlackFilter instanceof AviaterRegexFilter) {
|
||||||
|
if (binlogParser instanceof LogEventConvert) {
|
||||||
|
((LogEventConvert) binlogParser).setNameBlackFilter((AviaterRegexFilter) eventBlackFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tableMetaTSDB != null && tableMetaTSDB instanceof DatabaseTableMeta) {
|
||||||
|
((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,16 +137,6 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
|||||||
super.stop();
|
super.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEventBlackFilter(CanalEventFilter eventBlackFilter) {
|
|
||||||
super.setEventBlackFilter(eventBlackFilter);
|
|
||||||
|
|
||||||
// 触发一下filter变更
|
|
||||||
if (eventBlackFilter != null && eventBlackFilter instanceof AviaterRegexFilter
|
|
||||||
&& binlogParser instanceof LogEventConvert) {
|
|
||||||
((LogEventConvert) binlogParser).setNameBlackFilter((AviaterRegexFilter) eventBlackFilter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MultiStageCoprocessor buildMultiStageCoprocessor() {
|
protected MultiStageCoprocessor buildMultiStageCoprocessor() {
|
||||||
MysqlMultiStageCoprocessor mysqlMultiStageCoprocessor = new MysqlMultiStageCoprocessor(parallelBufferSize,
|
MysqlMultiStageCoprocessor mysqlMultiStageCoprocessor = new MysqlMultiStageCoprocessor(parallelBufferSize,
|
||||||
parallelThreadSize,
|
parallelThreadSize,
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ public class LocalBinLogConnection implements ErosaConnection {
|
|||||||
return running;
|
return running;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void seek(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
public void seek(String binlogfilename, Long binlogPosition, String gtid, SinkFunction func) throws IOException {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import java.util.List;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang.math.NumberUtils;
|
import org.apache.commons.lang.math.NumberUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -118,7 +119,7 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
/**
|
/**
|
||||||
* 加速主备切换时的查找速度,做一些特殊优化,比如只解析事务头或者尾
|
* 加速主备切换时的查找速度,做一些特殊优化,比如只解析事务头或者尾
|
||||||
*/
|
*/
|
||||||
public void seek(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
public void seek(String binlogfilename, Long binlogPosition, String gtid, SinkFunction func) throws IOException {
|
||||||
updateSettings();
|
updateSettings();
|
||||||
loadBinlogChecksum();
|
loadBinlogChecksum();
|
||||||
sendBinlogDump(binlogfilename, binlogPosition);
|
sendBinlogDump(binlogfilename, binlogPosition);
|
||||||
@@ -130,6 +131,13 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
decoder.handle(LogEvent.QUERY_EVENT);
|
decoder.handle(LogEvent.QUERY_EVENT);
|
||||||
decoder.handle(LogEvent.XID_EVENT);
|
decoder.handle(LogEvent.XID_EVENT);
|
||||||
LogContext context = new LogContext();
|
LogContext context = new LogContext();
|
||||||
|
// 若entry position存在gtid,则使用传入的gtid作为gtidSet 拼接的标准,否则同时开启gtid和tsdb时,会导致丢失gtid
|
||||||
|
// 而当源端数据库gtid 有purged时会有如下类似报错
|
||||||
|
// 'errno = 1236, sqlstate = HY000 errmsg = The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION = 1 ...
|
||||||
|
if (StringUtils.isNotEmpty(gtid)) {
|
||||||
|
decoder.handle(LogEvent.GTID_LOG_EVENT);
|
||||||
|
context.setGtidSet(MysqlGTIDSet.parse(gtid));
|
||||||
|
}
|
||||||
context.setFormatDescription(new FormatDescriptionLogEvent(4, binlogChecksum));
|
context.setFormatDescription(new FormatDescriptionLogEvent(4, binlogChecksum));
|
||||||
while (fetcher.fetch()) {
|
while (fetcher.fetch()) {
|
||||||
accumulateReceivedBytes(fetcher.limit());
|
accumulateReceivedBytes(fetcher.limit());
|
||||||
@@ -506,8 +514,9 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
ResultSetPacket rs = null;
|
ResultSetPacket rs = null;
|
||||||
try {
|
try {
|
||||||
rs = query("select @@global.binlog_checksum");
|
rs = query("select @@global.binlog_checksum");
|
||||||
} catch (IOException e) {
|
} catch (Throwable e) {
|
||||||
throw new CanalParseException(e);
|
// ignore
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> columnValues = rs.getFieldValues();
|
List<String> columnValues = rs.getFieldValues();
|
||||||
|
|||||||
@@ -527,7 +527,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
// 针对开始的第一条为非Begin记录,需要从该binlog扫描
|
// 针对开始的第一条为非Begin记录,需要从该binlog扫描
|
||||||
final java.util.concurrent.atomic.AtomicLong preTransactionStartPosition = new java.util.concurrent.atomic.AtomicLong(0L);
|
final java.util.concurrent.atomic.AtomicLong preTransactionStartPosition = new java.util.concurrent.atomic.AtomicLong(0L);
|
||||||
mysqlConnection.reconnect();
|
mysqlConnection.reconnect();
|
||||||
mysqlConnection.seek(entryPosition.getJournalName(), 4L, new SinkFunction<LogEvent>() {
|
mysqlConnection.seek(entryPosition.getJournalName(), 4L, entryPosition.getGtid(), new SinkFunction<LogEvent>() {
|
||||||
|
|
||||||
private LogPosition lastPosition;
|
private LogPosition lastPosition;
|
||||||
|
|
||||||
@@ -740,7 +740,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
try {
|
try {
|
||||||
mysqlConnection.reconnect();
|
mysqlConnection.reconnect();
|
||||||
// 开始遍历文件
|
// 开始遍历文件
|
||||||
mysqlConnection.seek(searchBinlogFile, 4L, new SinkFunction<LogEvent>() {
|
mysqlConnection.seek(searchBinlogFile, 4L, endPosition.getGtid(), new SinkFunction<LogEvent>() {
|
||||||
|
|
||||||
private LogPosition lastPosition;
|
private LogPosition lastPosition;
|
||||||
|
|
||||||
@@ -754,6 +754,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
event.getLogPos() - event.getEventLen(),
|
event.getLogPos() - event.getEventLen(),
|
||||||
event.getWhen() * 1000,
|
event.getWhen() * 1000,
|
||||||
event.getServerId());
|
event.getServerId());
|
||||||
|
entryPosition.setGtid(event.getHeader().getGtidSetStr());
|
||||||
logPosition.setPostion(entryPosition);
|
logPosition.setPostion(entryPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -789,6 +790,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
entryPosition);
|
entryPosition);
|
||||||
}
|
}
|
||||||
logPosition.setPostion(entryPosition);
|
logPosition.setPostion(entryPosition);
|
||||||
|
entryPosition.setGtid(entry.getHeader().getGtid());
|
||||||
} else if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())) {
|
} else if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())) {
|
||||||
// 当前事务开始位点
|
// 当前事务开始位点
|
||||||
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp, serverId);
|
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp, serverId);
|
||||||
@@ -796,6 +798,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
logger.debug("set {} to be pending start position before finding another proper one...",
|
logger.debug("set {} to be pending start position before finding another proper one...",
|
||||||
entryPosition);
|
entryPosition);
|
||||||
}
|
}
|
||||||
|
entryPosition.setGtid(entry.getHeader().getGtid());
|
||||||
logPosition.setPostion(entryPosition);
|
logPosition.setPostion(entryPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -621,9 +621,10 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
+ "," + columnInfo.length + " vs " + tableMeta.getFields().size());
|
+ "," + columnInfo.length + " vs " + tableMeta.getFields().size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
// } else {
|
||||||
logger.warn("[" + event.getTable().getDbName() + "." + event.getTable().getTableName()
|
// logger.warn("[" + event.getTable().getDbName() + "." +
|
||||||
+ "] is no primary key , skip alibaba_rds_row_id column");
|
// event.getTable().getTableName()
|
||||||
|
// + "] is no primary key , skip alibaba_rds_row_id column");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,18 +635,35 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (existRDSNoPrimaryKey && i == columnCnt - 1 && info.type == LogEvent.MYSQL_TYPE_LONGLONG) {
|
||||||
|
// 不解析最后一列
|
||||||
|
String rdsRowIdColumnName = "#alibaba_rds_row_id#";
|
||||||
|
buffer.nextValue(rdsRowIdColumnName, i, info.type, info.meta, false);
|
||||||
|
Column.Builder columnBuilder = Column.newBuilder();
|
||||||
|
columnBuilder.setName(rdsRowIdColumnName);
|
||||||
|
columnBuilder.setIsKey(true);
|
||||||
|
columnBuilder.setMysqlType("bigint");
|
||||||
|
columnBuilder.setIndex(i);
|
||||||
|
columnBuilder.setIsNull(false);
|
||||||
|
Serializable value = buffer.getValue();
|
||||||
|
columnBuilder.setValue(value.toString());
|
||||||
|
columnBuilder.setSqlType(Types.BIGINT);
|
||||||
|
columnBuilder.setUpdated(false);
|
||||||
|
|
||||||
|
if (isAfter) {
|
||||||
|
rowDataBuilder.addAfterColumns(columnBuilder.build());
|
||||||
|
} else {
|
||||||
|
rowDataBuilder.addBeforeColumns(columnBuilder.build());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
FieldMeta fieldMeta = null;
|
FieldMeta fieldMeta = null;
|
||||||
if (tableMeta != null && !tableError) {
|
if (tableMeta != null && !tableError) {
|
||||||
// 处理file meta
|
// 处理file meta
|
||||||
fieldMeta = tableMeta.getFields().get(i);
|
fieldMeta = tableMeta.getFields().get(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existRDSNoPrimaryKey && i == columnCnt - 1 && info.type == LogEvent.MYSQL_TYPE_LONGLONG) {
|
|
||||||
// 不解析最后一列
|
|
||||||
buffer.nextValue(fieldMeta.getColumnName(), i, info.type, info.meta, false);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldMeta != null && existOptionalMetaData && tableMetaCache.isOnTSDB()) {
|
if (fieldMeta != null && existOptionalMetaData && tableMetaCache.isOnTSDB()) {
|
||||||
// check column info
|
// check column info
|
||||||
boolean check = StringUtils.equalsIgnoreCase(fieldMeta.getColumnName(), info.name);
|
boolean check = StringUtils.equalsIgnoreCase(fieldMeta.getColumnName(), info.name);
|
||||||
@@ -974,10 +992,12 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
|
|
||||||
public void setNameFilter(AviaterRegexFilter nameFilter) {
|
public void setNameFilter(AviaterRegexFilter nameFilter) {
|
||||||
this.nameFilter = nameFilter;
|
this.nameFilter = nameFilter;
|
||||||
|
logger.warn("--> init table filter : " + nameFilter.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNameBlackFilter(AviaterRegexFilter nameBlackFilter) {
|
public void setNameBlackFilter(AviaterRegexFilter nameBlackFilter) {
|
||||||
this.nameBlackFilter = nameBlackFilter;
|
this.nameBlackFilter = nameBlackFilter;
|
||||||
|
logger.warn("--> init table black filter : " + nameBlackFilter.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTableMetaCache(TableMetaCache tableMetaCache) {
|
public void setTableMetaCache(TableMetaCache tableMetaCache) {
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ public abstract class AbstractRequest<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final String formatUTCTZ(Date date) {
|
public final String formatUTCTZ(Date date) {
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd'T'HH:mm:ss'Z'");
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
return sdf.format(date);
|
return sdf.format(date);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -529,9 +529,10 @@ public class DatabaseTableMeta implements TableMetaTSDB {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!StringUtils.equalsIgnoreCase(sourceField.getDefaultValue(), targetField.getDefaultValue())) {
|
// if (!StringUtils.equalsIgnoreCase(sourceField.getDefaultValue(),
|
||||||
return false;
|
// targetField.getDefaultValue())) {
|
||||||
}
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
if (sourceField.isNullable() != targetField.isNullable()) {
|
if (sourceField.isNullable() != targetField.isNullable()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import com.alibaba.fastsql.sql.ast.statement.SQLSelectOrderByItem;
|
|||||||
import com.alibaba.fastsql.sql.ast.statement.SQLTableElement;
|
import com.alibaba.fastsql.sql.ast.statement.SQLTableElement;
|
||||||
import com.alibaba.fastsql.sql.dialect.mysql.ast.MySqlPrimaryKey;
|
import com.alibaba.fastsql.sql.dialect.mysql.ast.MySqlPrimaryKey;
|
||||||
import com.alibaba.fastsql.sql.dialect.mysql.ast.MySqlUnique;
|
import com.alibaba.fastsql.sql.dialect.mysql.ast.MySqlUnique;
|
||||||
|
import com.alibaba.fastsql.sql.dialect.mysql.ast.expr.MySqlOrderingExpr;
|
||||||
import com.alibaba.fastsql.sql.repository.Schema;
|
import com.alibaba.fastsql.sql.repository.Schema;
|
||||||
import com.alibaba.fastsql.sql.repository.SchemaObject;
|
import com.alibaba.fastsql.sql.repository.SchemaObject;
|
||||||
import com.alibaba.fastsql.sql.repository.SchemaRepository;
|
import com.alibaba.fastsql.sql.repository.SchemaRepository;
|
||||||
@@ -74,7 +75,11 @@ public class MemoryTableMeta implements TableMetaTSDB {
|
|||||||
try {
|
try {
|
||||||
// druid暂时flush privileges语法解析有问题
|
// druid暂时flush privileges语法解析有问题
|
||||||
if (!StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "flush")
|
if (!StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "flush")
|
||||||
&& !StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "grant")) {
|
&& !StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "grant")
|
||||||
|
&& !StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "create user")
|
||||||
|
&& !StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "drop user")
|
||||||
|
&& !StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "create database")
|
||||||
|
&& !StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "drop database")) {
|
||||||
repository.console(ddl);
|
repository.console(ddl);
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
@@ -254,6 +259,8 @@ public class MemoryTableMeta implements TableMetaTSDB {
|
|||||||
return ((SQLCharExpr) sqlName).getText();
|
return ((SQLCharExpr) sqlName).getText();
|
||||||
} else if (sqlName instanceof SQLMethodInvokeExpr) {
|
} else if (sqlName instanceof SQLMethodInvokeExpr) {
|
||||||
return DruidDdlParser.unescapeName(((SQLMethodInvokeExpr) sqlName).getMethodName());
|
return DruidDdlParser.unescapeName(((SQLMethodInvokeExpr) sqlName).getMethodName());
|
||||||
|
} else if (sqlName instanceof MySqlOrderingExpr) {
|
||||||
|
return getSqlName(((MySqlOrderingExpr) sqlName).getExpr());
|
||||||
} else {
|
} else {
|
||||||
return sqlName.toString();
|
return sqlName.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
4
pom.xml
4
pom.xml
@@ -4,7 +4,7 @@
|
|||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>canal module for otter ${project.version}</name>
|
<name>canal module for otter ${project.version}</name>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<url>https://github.com/alibaba/canal</url>
|
<url>https://github.com/alibaba/canal</url>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.sonatype.oss</groupId>
|
<groupId>org.sonatype.oss</groupId>
|
||||||
@@ -381,6 +381,7 @@
|
|||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<!-- javadoc -->
|
<!-- javadoc -->
|
||||||
|
<!--
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
@@ -399,6 +400,7 @@
|
|||||||
<additionalparam>-Xdoclint:none</additionalparam>
|
<additionalparam>-Xdoclint:none</additionalparam>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
-->
|
||||||
</plugins>
|
</plugins>
|
||||||
<sourceDirectory>src/main/java</sourceDirectory>
|
<sourceDirectory>src/main/java</sourceDirectory>
|
||||||
<testSourceDirectory>src/test/java</testSourceDirectory>
|
<testSourceDirectory>src/test/java</testSourceDirectory>
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal.prometheus</artifactId>
|
<artifactId>canal.prometheus</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<name>canal prometheus module for otter ${project.version}</name>
|
<name>canal prometheus module for otter ${project.version}</name>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
package com.alibaba.otter.canal.protocol;
|
package com.alibaba.otter.canal.protocol;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author machengyuan 2018-9-13 下午10:31:14
|
* @author machengyuan 2018-9-13 下午10:31:14
|
||||||
@@ -17,10 +13,10 @@ import com.google.protobuf.ByteString;
|
|||||||
public class FlatMessage implements Serializable {
|
public class FlatMessage implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = -3386650678735860050L;
|
private static final long serialVersionUID = -3386650678735860050L;
|
||||||
|
|
||||||
private long id;
|
private long id;
|
||||||
private String database;
|
private String database;
|
||||||
private String table;
|
private String table;
|
||||||
|
private List<String> pkNames;
|
||||||
private Boolean isDdl;
|
private Boolean isDdl;
|
||||||
private String type;
|
private String type;
|
||||||
// binlog executeTime
|
// binlog executeTime
|
||||||
@@ -33,7 +29,7 @@ public class FlatMessage implements Serializable {
|
|||||||
private List<Map<String, String>> data;
|
private List<Map<String, String>> data;
|
||||||
private List<Map<String, String>> old;
|
private List<Map<String, String>> old;
|
||||||
|
|
||||||
public FlatMessage(){
|
public FlatMessage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public FlatMessage(long id){
|
public FlatMessage(long id){
|
||||||
@@ -64,6 +60,21 @@ public class FlatMessage implements Serializable {
|
|||||||
this.table = table;
|
this.table = table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getPkNames() {
|
||||||
|
return pkNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPkName(String pkName) {
|
||||||
|
if (this.pkNames == null) {
|
||||||
|
this.pkNames = Lists.newArrayList();
|
||||||
|
}
|
||||||
|
this.pkNames.add(pkName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPkNames(List<String> pkNames) {
|
||||||
|
this.pkNames = pkNames;
|
||||||
|
}
|
||||||
|
|
||||||
public Boolean getIsDdl() {
|
public Boolean getIsDdl() {
|
||||||
return isDdl;
|
return isDdl;
|
||||||
}
|
}
|
||||||
@@ -136,206 +147,6 @@ public class FlatMessage implements Serializable {
|
|||||||
this.es = es;
|
this.es = es;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 将Message转换为FlatMessage
|
|
||||||
*
|
|
||||||
* @param message 原生message
|
|
||||||
* @return FlatMessage列表
|
|
||||||
*/
|
|
||||||
public static List<FlatMessage> messageConverter(Message message) {
|
|
||||||
try {
|
|
||||||
if (message == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<FlatMessage> flatMessages = new ArrayList<>();
|
|
||||||
List<CanalEntry.Entry> entrys = null;
|
|
||||||
if (message.isRaw()) {
|
|
||||||
List<ByteString> rawEntries = message.getRawEntries();
|
|
||||||
entrys = new ArrayList<CanalEntry.Entry>(rawEntries.size());
|
|
||||||
for (ByteString byteString : rawEntries) {
|
|
||||||
CanalEntry.Entry entry = CanalEntry.Entry.parseFrom(byteString);
|
|
||||||
entrys.add(entry);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
entrys = message.getEntries();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (CanalEntry.Entry entry : entrys) {
|
|
||||||
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
|
|
||||||
|| entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
CanalEntry.RowChange rowChange;
|
|
||||||
try {
|
|
||||||
rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(
|
|
||||||
"ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
|
|
||||||
e);
|
|
||||||
}
|
|
||||||
|
|
||||||
CanalEntry.EventType eventType = rowChange.getEventType();
|
|
||||||
|
|
||||||
FlatMessage flatMessage = new FlatMessage(message.getId());
|
|
||||||
flatMessages.add(flatMessage);
|
|
||||||
flatMessage.setDatabase(entry.getHeader().getSchemaName());
|
|
||||||
flatMessage.setTable(entry.getHeader().getTableName());
|
|
||||||
flatMessage.setIsDdl(rowChange.getIsDdl());
|
|
||||||
flatMessage.setType(eventType.toString());
|
|
||||||
flatMessage.setEs(entry.getHeader().getExecuteTime());
|
|
||||||
flatMessage.setTs(System.currentTimeMillis());
|
|
||||||
flatMessage.setSql(rowChange.getSql());
|
|
||||||
|
|
||||||
if (!rowChange.getIsDdl()) {
|
|
||||||
Map<String, Integer> sqlType = new LinkedHashMap<>();
|
|
||||||
Map<String, String> mysqlType = new LinkedHashMap<>();
|
|
||||||
List<Map<String, String>> data = new ArrayList<>();
|
|
||||||
List<Map<String, String>> old = new ArrayList<>();
|
|
||||||
|
|
||||||
Set<String> updateSet = new HashSet<>();
|
|
||||||
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
|
|
||||||
if (eventType != CanalEntry.EventType.INSERT && eventType != CanalEntry.EventType.UPDATE
|
|
||||||
&& eventType != CanalEntry.EventType.DELETE) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, String> row = new LinkedHashMap<>();
|
|
||||||
List<CanalEntry.Column> columns;
|
|
||||||
|
|
||||||
if (eventType == CanalEntry.EventType.DELETE) {
|
|
||||||
columns = rowData.getBeforeColumnsList();
|
|
||||||
} else {
|
|
||||||
columns = rowData.getAfterColumnsList();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (CanalEntry.Column column : columns) {
|
|
||||||
sqlType.put(column.getName(), column.getSqlType());
|
|
||||||
mysqlType.put(column.getName(), column.getMysqlType());
|
|
||||||
if (column.getIsNull()) {
|
|
||||||
row.put(column.getName(), null);
|
|
||||||
} else {
|
|
||||||
row.put(column.getName(), column.getValue());
|
|
||||||
}
|
|
||||||
// 获取update为true的字段
|
|
||||||
if (column.getUpdated()) {
|
|
||||||
updateSet.add(column.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!row.isEmpty()) {
|
|
||||||
data.add(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventType == CanalEntry.EventType.UPDATE) {
|
|
||||||
Map<String, String> rowOld = new LinkedHashMap<>();
|
|
||||||
for (CanalEntry.Column column : rowData.getBeforeColumnsList()) {
|
|
||||||
if (updateSet.contains(column.getName())) {
|
|
||||||
if (column.getIsNull()) {
|
|
||||||
rowOld.put(column.getName(), null);
|
|
||||||
} else {
|
|
||||||
rowOld.put(column.getName(), column.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// update操作将记录修改前的值
|
|
||||||
if (!rowOld.isEmpty()) {
|
|
||||||
old.add(rowOld);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!sqlType.isEmpty()) {
|
|
||||||
flatMessage.setSqlType(sqlType);
|
|
||||||
}
|
|
||||||
if (!mysqlType.isEmpty()) {
|
|
||||||
flatMessage.setMysqlType(mysqlType);
|
|
||||||
}
|
|
||||||
if (!data.isEmpty()) {
|
|
||||||
flatMessage.setData(data);
|
|
||||||
}
|
|
||||||
if (!old.isEmpty()) {
|
|
||||||
flatMessage.setOld(old);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return flatMessages;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将FlatMessage按指定的字段值hash拆分
|
|
||||||
*
|
|
||||||
* @param flatMessage flatMessage
|
|
||||||
* @param partitionsNum 分区数量
|
|
||||||
* @param pkHashConfig hash映射
|
|
||||||
* @return 拆分后的flatMessage数组
|
|
||||||
*/
|
|
||||||
public static FlatMessage[] messagePartition(FlatMessage flatMessage, Integer partitionsNum,
|
|
||||||
Map<String, String> pkHashConfig) {
|
|
||||||
if (partitionsNum == null) {
|
|
||||||
partitionsNum = 1;
|
|
||||||
}
|
|
||||||
FlatMessage[] partitionMessages = new FlatMessage[partitionsNum];
|
|
||||||
String pk = pkHashConfig.get(flatMessage.getDatabase() + "." + flatMessage.getTable());
|
|
||||||
if (pk == null || flatMessage.getIsDdl()) {
|
|
||||||
partitionMessages[0] = flatMessage;
|
|
||||||
} else {
|
|
||||||
if (flatMessage.getData() != null) {
|
|
||||||
int idx = 0;
|
|
||||||
for (Map<String, String> row : flatMessage.getData()) {
|
|
||||||
Map<String, String> o = flatMessage.getOld().get(idx);
|
|
||||||
String value;
|
|
||||||
// 如果old中有pk值说明主键有修改, 以旧的主键值hash为准
|
|
||||||
if (o != null && o.containsKey(pk)) {
|
|
||||||
value = o.get(pk);
|
|
||||||
} else {
|
|
||||||
value = row.get(pk);
|
|
||||||
}
|
|
||||||
if (value == null) {
|
|
||||||
value = "";
|
|
||||||
}
|
|
||||||
int hash = value.hashCode();
|
|
||||||
int pkHash = Math.abs(hash) % partitionsNum;
|
|
||||||
// math.abs可能返回负值,这里再取反,把出现负值的数据还是写到固定的分区,仍然可以保证消费顺序
|
|
||||||
pkHash = Math.abs(pkHash);
|
|
||||||
|
|
||||||
FlatMessage flatMessageTmp = partitionMessages[pkHash];
|
|
||||||
if (flatMessageTmp == null) {
|
|
||||||
flatMessageTmp = new FlatMessage(flatMessage.getId());
|
|
||||||
partitionMessages[pkHash] = flatMessageTmp;
|
|
||||||
flatMessageTmp.setDatabase(flatMessage.getDatabase());
|
|
||||||
flatMessageTmp.setTable(flatMessage.getTable());
|
|
||||||
flatMessageTmp.setIsDdl(flatMessage.getIsDdl());
|
|
||||||
flatMessageTmp.setType(flatMessage.getType());
|
|
||||||
flatMessageTmp.setSql(flatMessage.getSql());
|
|
||||||
flatMessageTmp.setSqlType(flatMessage.getSqlType());
|
|
||||||
flatMessageTmp.setMysqlType(flatMessage.getMysqlType());
|
|
||||||
flatMessageTmp.setEs(flatMessage.getEs());
|
|
||||||
flatMessageTmp.setTs(flatMessage.getTs());
|
|
||||||
}
|
|
||||||
List<Map<String, String>> data = flatMessageTmp.getData();
|
|
||||||
if (data == null) {
|
|
||||||
data = new ArrayList<>();
|
|
||||||
flatMessageTmp.setData(data);
|
|
||||||
}
|
|
||||||
data.add(row);
|
|
||||||
if (flatMessage.getOld() != null && !flatMessage.getOld().isEmpty()) {
|
|
||||||
List<Map<String, String>> old = flatMessageTmp.getOld();
|
|
||||||
if (old == null) {
|
|
||||||
old = new ArrayList<>();
|
|
||||||
flatMessageTmp.setOld(old);
|
|
||||||
}
|
|
||||||
old.add(flatMessage.getOld().get(idx));
|
|
||||||
}
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return partitionMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "FlatMessage [id=" + id + ", database=" + database + ", table=" + table + ", isDdl=" + isDdl + ", type="
|
return "FlatMessage [id=" + id + ", database=" + database + ", table=" + table + ", isDdl=" + isDdl + ", type="
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import com.google.protobuf.ByteString;
|
|||||||
public class Message implements Serializable {
|
public class Message implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1234034768477580009L;
|
private static final long serialVersionUID = 1234034768477580009L;
|
||||||
|
|
||||||
private long id;
|
private long id;
|
||||||
private List<CanalEntry.Entry> entries = new ArrayList<CanalEntry.Entry>();
|
private List<CanalEntry.Entry> entries = new ArrayList<CanalEntry.Entry>();
|
||||||
// row data for performance, see:
|
// row data for performance, see:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.2</version>
|
<version>1.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.server</artifactId>
|
<artifactId>canal.server</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,557 @@
|
|||||||
|
package com.alibaba.otter.canal.common;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
||||||
|
import com.alibaba.otter.canal.protocol.FlatMessage;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.MapMaker;
|
||||||
|
import com.google.common.collect.MigrateMap;
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* process MQ Message utils
|
||||||
|
*
|
||||||
|
* @author agapple 2018年12月11日 下午1:28:32
|
||||||
|
*/
|
||||||
|
public class MQMessageUtils {
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private static Map<String, List<PartitionData>> partitionDatas = MigrateMap.makeComputingMap(new MapMaker().softValues(),
|
||||||
|
new Function<String, List<PartitionData>>() {
|
||||||
|
|
||||||
|
public List<PartitionData> apply(String pkHashConfigs) {
|
||||||
|
List<PartitionData> datas = Lists.newArrayList();
|
||||||
|
String[] pkHashConfigArray = StringUtils.split(pkHashConfigs,
|
||||||
|
",");
|
||||||
|
// schema.table:id^name
|
||||||
|
for (String pkHashConfig : pkHashConfigArray) {
|
||||||
|
PartitionData data = new PartitionData();
|
||||||
|
int i = pkHashConfig.lastIndexOf(":");
|
||||||
|
if (i > 0) {
|
||||||
|
String pkStr = pkHashConfig.substring(i + 1);
|
||||||
|
if (pkStr.equalsIgnoreCase("$pk$")) {
|
||||||
|
data.hashMode.autoPkHash = true;
|
||||||
|
} else {
|
||||||
|
data.hashMode.pkNames = Lists.newArrayList(StringUtils.split(pkStr,
|
||||||
|
'^'));
|
||||||
|
}
|
||||||
|
|
||||||
|
pkHashConfig = pkHashConfig.substring(0,
|
||||||
|
i);
|
||||||
|
} else {
|
||||||
|
data.hashMode.tableHash = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isWildCard(pkHashConfig)) {
|
||||||
|
data.simpleName = pkHashConfig;
|
||||||
|
} else {
|
||||||
|
data.regexFilter = new AviaterRegexFilter(pkHashConfig);
|
||||||
|
}
|
||||||
|
datas.add(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return datas;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private static Map<String, List<DynamicTopicData>> dynamicTopicDatas = MigrateMap.makeComputingMap(new MapMaker().softValues(),
|
||||||
|
new Function<String, List<DynamicTopicData>>() {
|
||||||
|
|
||||||
|
public List<DynamicTopicData> apply(String pkHashConfigs) {
|
||||||
|
List<DynamicTopicData> datas = Lists.newArrayList();
|
||||||
|
String[] dynamicTopicArray = StringUtils.split(pkHashConfigs,
|
||||||
|
",");
|
||||||
|
// schema.table
|
||||||
|
for (String dynamicTopic : dynamicTopicArray) {
|
||||||
|
DynamicTopicData data = new DynamicTopicData();
|
||||||
|
|
||||||
|
if (!isWildCard(dynamicTopic)) {
|
||||||
|
data.simpleName = dynamicTopic;
|
||||||
|
} else {
|
||||||
|
if (dynamicTopic.contains("\\.")) {
|
||||||
|
data.tableRegexFilter = new AviaterRegexFilter(dynamicTopic);
|
||||||
|
} else {
|
||||||
|
data.schemaRegexFilter = new AviaterRegexFilter(dynamicTopic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
datas.add(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return datas;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按 schema 或者 schema+table 将 message 分配到对应topic
|
||||||
|
*
|
||||||
|
* @param message 原message
|
||||||
|
* @param defaultTopic 默认topic
|
||||||
|
* @param dynamicTopicConfigs 动态topic规则
|
||||||
|
* @return 分隔后的message map
|
||||||
|
*/
|
||||||
|
public static Map<String, Message> messageTopics(Message message, String defaultTopic, String dynamicTopicConfigs) {
|
||||||
|
List<CanalEntry.Entry> entries;
|
||||||
|
if (message.isRaw()) {
|
||||||
|
List<ByteString> rawEntries = message.getRawEntries();
|
||||||
|
entries = new ArrayList<>(rawEntries.size());
|
||||||
|
for (ByteString byteString : rawEntries) {
|
||||||
|
CanalEntry.Entry entry;
|
||||||
|
try {
|
||||||
|
entry = CanalEntry.Entry.parseFrom(byteString);
|
||||||
|
} catch (InvalidProtocolBufferException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
entries.add(entry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entries = message.getEntries();
|
||||||
|
}
|
||||||
|
Map<String, Message> messages = new HashMap<>();
|
||||||
|
for (CanalEntry.Entry entry : entries) {
|
||||||
|
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
|
||||||
|
|| entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String schemaName = entry.getHeader().getSchemaName();
|
||||||
|
String tableName = entry.getHeader().getTableName();
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(schemaName) || StringUtils.isEmpty(tableName)) {
|
||||||
|
put2MapMessage(messages, message.getId(), defaultTopic, entry);
|
||||||
|
} else {
|
||||||
|
if (matchDynamicTopic(schemaName + "." + tableName, dynamicTopicConfigs)) {
|
||||||
|
put2MapMessage(messages, message.getId(), schemaName + "." + tableName, entry);
|
||||||
|
} else if (matchDynamicTopic(schemaName, dynamicTopicConfigs)) {
|
||||||
|
put2MapMessage(messages, message.getId(), schemaName, entry);
|
||||||
|
} else {
|
||||||
|
put2MapMessage(messages, message.getId(), defaultTopic, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 message 分区
|
||||||
|
*
|
||||||
|
* @param partitionsNum 分区数
|
||||||
|
* @param pkHashConfigs 分区库表主键正则表达式
|
||||||
|
* @return 分区message数组
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static Message[] messagePartition(Message message, Integer partitionsNum, String pkHashConfigs) {
|
||||||
|
if (partitionsNum == null) {
|
||||||
|
partitionsNum = 1;
|
||||||
|
}
|
||||||
|
Message[] partitionMessages = new Message[partitionsNum];
|
||||||
|
List<Entry>[] partitionEntries = new List[partitionsNum];
|
||||||
|
for (int i = 0; i < partitionsNum; i++) {
|
||||||
|
partitionEntries[i] = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CanalEntry.Entry> entries;
|
||||||
|
if (message.isRaw()) {
|
||||||
|
List<ByteString> rawEntries = message.getRawEntries();
|
||||||
|
entries = new ArrayList<>(rawEntries.size());
|
||||||
|
for (ByteString byteString : rawEntries) {
|
||||||
|
Entry entry;
|
||||||
|
try {
|
||||||
|
entry = Entry.parseFrom(byteString);
|
||||||
|
} catch (InvalidProtocolBufferException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
entries.add(entry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entries = message.getEntries();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Entry entry : entries) {
|
||||||
|
CanalEntry.RowChange rowChange;
|
||||||
|
try {
|
||||||
|
rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rowChange.getIsDdl()) {
|
||||||
|
partitionEntries[0].add(entry);
|
||||||
|
} else {
|
||||||
|
if (rowChange.getRowDatasList() != null && !rowChange.getRowDatasList().isEmpty()) {
|
||||||
|
String database = entry.getHeader().getSchemaName();
|
||||||
|
String table = entry.getHeader().getTableName();
|
||||||
|
HashMode hashMode = getPartitionHashColumns(database + "." + table, pkHashConfigs);
|
||||||
|
if (hashMode == null) {
|
||||||
|
// 如果都没有匹配,发送到第一个分区
|
||||||
|
partitionEntries[0].add(entry);
|
||||||
|
} else {
|
||||||
|
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
|
||||||
|
int hashCode = table.hashCode();
|
||||||
|
if (hashMode.autoPkHash) {
|
||||||
|
// isEmpty use default pkNames
|
||||||
|
for (CanalEntry.Column column : rowData.getAfterColumnsList()) {
|
||||||
|
if (column.getIsKey()) {
|
||||||
|
hashCode = hashCode ^ column.getValue().hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!hashMode.tableHash) {
|
||||||
|
for (CanalEntry.Column column : rowData.getAfterColumnsList()) {
|
||||||
|
if (checkPkNamesHasContain(hashMode.pkNames, column.getName())) {
|
||||||
|
hashCode = hashCode ^ column.getValue().hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int pkHash = Math.abs(hashCode) % partitionsNum;
|
||||||
|
pkHash = Math.abs(pkHash);
|
||||||
|
partitionEntries[pkHash].add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 针对stmt/mixed binlog格式的query事件
|
||||||
|
partitionEntries[0].add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < partitionsNum; i++) {
|
||||||
|
List<Entry> entriesTmp = partitionEntries[i];
|
||||||
|
if (!entriesTmp.isEmpty()) {
|
||||||
|
partitionMessages[i] = new Message(message.getId(), entriesTmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return partitionMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将Message转换为FlatMessage
|
||||||
|
*
|
||||||
|
* @param message 原生message
|
||||||
|
* @return FlatMessage列表
|
||||||
|
*/
|
||||||
|
public static List<FlatMessage> messageConverter(Message message) {
|
||||||
|
try {
|
||||||
|
if (message == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FlatMessage> flatMessages = new ArrayList<>();
|
||||||
|
List<CanalEntry.Entry> entrys = null;
|
||||||
|
if (message.isRaw()) {
|
||||||
|
List<ByteString> rawEntries = message.getRawEntries();
|
||||||
|
entrys = new ArrayList<CanalEntry.Entry>(rawEntries.size());
|
||||||
|
for (ByteString byteString : rawEntries) {
|
||||||
|
CanalEntry.Entry entry = CanalEntry.Entry.parseFrom(byteString);
|
||||||
|
entrys.add(entry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entrys = message.getEntries();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (CanalEntry.Entry entry : entrys) {
|
||||||
|
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
|
||||||
|
|| entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CanalEntry.RowChange rowChange;
|
||||||
|
try {
|
||||||
|
rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:"
|
||||||
|
+ entry.toString(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
CanalEntry.EventType eventType = rowChange.getEventType();
|
||||||
|
|
||||||
|
FlatMessage flatMessage = new FlatMessage(message.getId());
|
||||||
|
flatMessages.add(flatMessage);
|
||||||
|
flatMessage.setDatabase(entry.getHeader().getSchemaName());
|
||||||
|
flatMessage.setTable(entry.getHeader().getTableName());
|
||||||
|
flatMessage.setIsDdl(rowChange.getIsDdl());
|
||||||
|
flatMessage.setType(eventType.toString());
|
||||||
|
flatMessage.setEs(entry.getHeader().getExecuteTime());
|
||||||
|
flatMessage.setTs(System.currentTimeMillis());
|
||||||
|
flatMessage.setSql(rowChange.getSql());
|
||||||
|
|
||||||
|
if (!rowChange.getIsDdl()) {
|
||||||
|
Map<String, Integer> sqlType = new LinkedHashMap<>();
|
||||||
|
Map<String, String> mysqlType = new LinkedHashMap<>();
|
||||||
|
List<Map<String, String>> data = new ArrayList<>();
|
||||||
|
List<Map<String, String>> old = new ArrayList<>();
|
||||||
|
|
||||||
|
Set<String> updateSet = new HashSet<>();
|
||||||
|
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
|
||||||
|
if (eventType != CanalEntry.EventType.INSERT && eventType != CanalEntry.EventType.UPDATE
|
||||||
|
&& eventType != CanalEntry.EventType.DELETE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> row = new LinkedHashMap<>();
|
||||||
|
List<CanalEntry.Column> columns;
|
||||||
|
|
||||||
|
if (eventType == CanalEntry.EventType.DELETE) {
|
||||||
|
columns = rowData.getBeforeColumnsList();
|
||||||
|
} else {
|
||||||
|
columns = rowData.getAfterColumnsList();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (CanalEntry.Column column : columns) {
|
||||||
|
if (column.getIsKey()) {
|
||||||
|
flatMessage.addPkName(column.getName());
|
||||||
|
}
|
||||||
|
sqlType.put(column.getName(), column.getSqlType());
|
||||||
|
mysqlType.put(column.getName(), column.getMysqlType());
|
||||||
|
if (column.getIsNull()) {
|
||||||
|
row.put(column.getName(), null);
|
||||||
|
} else {
|
||||||
|
row.put(column.getName(), column.getValue());
|
||||||
|
}
|
||||||
|
// 获取update为true的字段
|
||||||
|
if (column.getUpdated()) {
|
||||||
|
updateSet.add(column.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!row.isEmpty()) {
|
||||||
|
data.add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventType == CanalEntry.EventType.UPDATE) {
|
||||||
|
Map<String, String> rowOld = new LinkedHashMap<>();
|
||||||
|
for (CanalEntry.Column column : rowData.getBeforeColumnsList()) {
|
||||||
|
if (updateSet.contains(column.getName())) {
|
||||||
|
if (column.getIsNull()) {
|
||||||
|
rowOld.put(column.getName(), null);
|
||||||
|
} else {
|
||||||
|
rowOld.put(column.getName(), column.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update操作将记录修改前的值
|
||||||
|
if (!rowOld.isEmpty()) {
|
||||||
|
old.add(rowOld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!sqlType.isEmpty()) {
|
||||||
|
flatMessage.setSqlType(sqlType);
|
||||||
|
}
|
||||||
|
if (!mysqlType.isEmpty()) {
|
||||||
|
flatMessage.setMysqlType(mysqlType);
|
||||||
|
}
|
||||||
|
if (!data.isEmpty()) {
|
||||||
|
flatMessage.setData(data);
|
||||||
|
}
|
||||||
|
if (!old.isEmpty()) {
|
||||||
|
flatMessage.setOld(old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flatMessages;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将FlatMessage按指定的字段值hash拆分
|
||||||
|
*
|
||||||
|
* @param flatMessage flatMessage
|
||||||
|
* @param partitionsNum 分区数量
|
||||||
|
* @param pkHashConfigs hash映射
|
||||||
|
* @return 拆分后的flatMessage数组
|
||||||
|
*/
|
||||||
|
public static FlatMessage[] messagePartition(FlatMessage flatMessage, Integer partitionsNum, String pkHashConfigs) {
|
||||||
|
if (partitionsNum == null) {
|
||||||
|
partitionsNum = 1;
|
||||||
|
}
|
||||||
|
FlatMessage[] partitionMessages = new FlatMessage[partitionsNum];
|
||||||
|
|
||||||
|
if (flatMessage.getIsDdl()) {
|
||||||
|
partitionMessages[0] = flatMessage;
|
||||||
|
} else {
|
||||||
|
if (flatMessage.getData() != null && !flatMessage.getData().isEmpty()) {
|
||||||
|
String database = flatMessage.getDatabase();
|
||||||
|
String table = flatMessage.getTable();
|
||||||
|
HashMode hashMode = getPartitionHashColumns(database + "." + table, pkHashConfigs);
|
||||||
|
if (hashMode == null) {
|
||||||
|
// 如果都没有匹配,发送到第一个分区
|
||||||
|
partitionMessages[0] = flatMessage;
|
||||||
|
} else {
|
||||||
|
List<String> pkNames = hashMode.pkNames;
|
||||||
|
if (hashMode.autoPkHash) {
|
||||||
|
pkNames = flatMessage.getPkNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
int hashCode = table.hashCode();
|
||||||
|
int idx = 0;
|
||||||
|
for (Map<String, String> row : flatMessage.getData()) {
|
||||||
|
if (!hashMode.tableHash) {
|
||||||
|
for (String pkName : pkNames) {
|
||||||
|
String value = row.get(pkName);
|
||||||
|
if (value == null) {
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
hashCode = hashCode ^ value.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int pkHash = Math.abs(hashCode) % partitionsNum;
|
||||||
|
// math.abs可能返回负值,这里再取反,把出现负值的数据还是写到固定的分区,仍然可以保证消费顺序
|
||||||
|
pkHash = Math.abs(pkHash);
|
||||||
|
|
||||||
|
FlatMessage flatMessageTmp = partitionMessages[pkHash];
|
||||||
|
if (flatMessageTmp == null) {
|
||||||
|
flatMessageTmp = new FlatMessage(flatMessage.getId());
|
||||||
|
partitionMessages[pkHash] = flatMessageTmp;
|
||||||
|
flatMessageTmp.setDatabase(flatMessage.getDatabase());
|
||||||
|
flatMessageTmp.setTable(flatMessage.getTable());
|
||||||
|
flatMessageTmp.setIsDdl(flatMessage.getIsDdl());
|
||||||
|
flatMessageTmp.setType(flatMessage.getType());
|
||||||
|
flatMessageTmp.setSql(flatMessage.getSql());
|
||||||
|
flatMessageTmp.setSqlType(flatMessage.getSqlType());
|
||||||
|
flatMessageTmp.setMysqlType(flatMessage.getMysqlType());
|
||||||
|
flatMessageTmp.setEs(flatMessage.getEs());
|
||||||
|
flatMessageTmp.setTs(flatMessage.getTs());
|
||||||
|
}
|
||||||
|
List<Map<String, String>> data = flatMessageTmp.getData();
|
||||||
|
if (data == null) {
|
||||||
|
data = new ArrayList<>();
|
||||||
|
flatMessageTmp.setData(data);
|
||||||
|
}
|
||||||
|
data.add(row);
|
||||||
|
if (flatMessage.getOld() != null && !flatMessage.getOld().isEmpty()) {
|
||||||
|
List<Map<String, String>> old = flatMessageTmp.getOld();
|
||||||
|
if (old == null) {
|
||||||
|
old = new ArrayList<>();
|
||||||
|
flatMessageTmp.setOld(old);
|
||||||
|
}
|
||||||
|
old.add(flatMessage.getOld().get(idx));
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 针对stmt/mixed binlog格式的query事件
|
||||||
|
partitionMessages[0] = flatMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return partitionMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* match return List , not match return null
|
||||||
|
*/
|
||||||
|
public static HashMode getPartitionHashColumns(String name, String pkHashConfigs) {
|
||||||
|
if (StringUtils.isEmpty(pkHashConfigs)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PartitionData> datas = partitionDatas.get(pkHashConfigs);
|
||||||
|
for (PartitionData data : datas) {
|
||||||
|
if (data.simpleName != null) {
|
||||||
|
if (data.simpleName.equalsIgnoreCase(name)) {
|
||||||
|
return data.hashMode;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (data.regexFilter.filter(name)) {
|
||||||
|
return data.hashMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean matchDynamicTopic(String name, String dynamicTopicConfigs) {
|
||||||
|
if (StringUtils.isEmpty(dynamicTopicConfigs)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean res = false;
|
||||||
|
List<DynamicTopicData> datas = dynamicTopicDatas.get(dynamicTopicConfigs);
|
||||||
|
for (DynamicTopicData data : datas) {
|
||||||
|
if (data.simpleName != null) {
|
||||||
|
if (data.simpleName.equalsIgnoreCase(name)) {
|
||||||
|
res = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (name.contains(".")) {
|
||||||
|
if (data.tableRegexFilter != null && data.tableRegexFilter.filter(name)) {
|
||||||
|
res = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (data.schemaRegexFilter != null && data.schemaRegexFilter.filter(name)) {
|
||||||
|
res = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean checkPkNamesHasContain(List<String> pkNames, String name) {
|
||||||
|
for (String pkName : pkNames) {
|
||||||
|
if (pkName.equalsIgnoreCase(name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isWildCard(String value) {
|
||||||
|
// not contaiins '.' ?
|
||||||
|
return StringUtils.containsAny(value, new char[] { '*', '?', '+', '|', '(', ')', '{', '}', '[', ']', '\\', '$',
|
||||||
|
'^' });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void put2MapMessage(Map<String, Message> messageMap, Long messageId, String topicName,
|
||||||
|
CanalEntry.Entry entry) {
|
||||||
|
Message message = messageMap.get(topicName);
|
||||||
|
if (message == null) {
|
||||||
|
message = new Message(messageId, new ArrayList<CanalEntry.Entry>());
|
||||||
|
messageMap.put(topicName, message);
|
||||||
|
}
|
||||||
|
message.getEntries().add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PartitionData {
|
||||||
|
|
||||||
|
public String simpleName;
|
||||||
|
public AviaterRegexFilter regexFilter;
|
||||||
|
public HashMode hashMode = new HashMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HashMode {
|
||||||
|
|
||||||
|
public boolean autoPkHash = false;
|
||||||
|
public boolean tableHash = false;
|
||||||
|
public List<String> pkNames = Lists.newArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DynamicTopicData {
|
||||||
|
|
||||||
|
public String simpleName;
|
||||||
|
public AviaterRegexFilter schemaRegexFilter;
|
||||||
|
public AviaterRegexFilter tableRegexFilter;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user