|
@@ -11,6 +11,7 @@ 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 java.util.concurrent.Future;
|
|
|
|
+import java.util.function.Function;
|
|
|
|
|
|
import javax.sql.DataSource;
|
|
import javax.sql.DataSource;
|
|
|
|
|
|
@@ -36,26 +37,37 @@ 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 int threads = 3;
|
|
|
|
|
|
+ private List<SyncItem>[] dmlsPartition;
|
|
|
|
+ private BatchExecutor[] batchExecutors;
|
|
|
|
+ private ExecutorService[] executorThreads;
|
|
|
|
|
|
- private List<SyncItem>[] dmlsPartition;
|
|
|
|
- private BatchExecutor[] batchExecutors;
|
|
|
|
- private ExecutorService[] executorThreads;
|
|
|
|
|
|
+ public List<SyncItem>[] getDmlsPartition() {
|
|
|
|
+ return dmlsPartition;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public Map<String, Map<String, Integer>> getColumnsTypeCache() {
|
|
|
|
+ return columnsTypeCache;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
+ public RdbSyncService(DataSource dataSource, Integer threads){
|
|
|
|
+ this(dataSource, threads, new ConcurrentHashMap<>());
|
|
|
|
+ }
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@SuppressWarnings("unchecked")
|
|
- public RdbSyncService(Map<String, Map<String, MappingConfig>> mappingConfigCache, DataSource dataSource,
|
|
|
|
- Integer threads){
|
|
|
|
|
|
+ public RdbSyncService(DataSource dataSource, Integer threads, Map<String, Map<String, Integer>> columnsTypeCache){
|
|
|
|
+ this.columnsTypeCache = columnsTypeCache;
|
|
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];
|
|
@@ -69,66 +81,111 @@ public class RdbSyncService {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- public void sync(List<Dml> dmls) {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * 批量同步回调
|
|
|
|
+ *
|
|
|
|
+ * @param dmls 批量 DML
|
|
|
|
+ * @param function 回调方法
|
|
|
|
+ */
|
|
|
|
+ public void sync(List<Dml> dmls, Function<Dml, Boolean> function) {
|
|
try {
|
|
try {
|
|
|
|
+ boolean toExecute = false;
|
|
for (Dml dml : dmls) {
|
|
for (Dml dml : dmls) {
|
|
- String destination = StringUtils.trimToEmpty(dml.getDestination());
|
|
|
|
- String database = dml.getDatabase();
|
|
|
|
- String table = dml.getTable();
|
|
|
|
- Map<String, MappingConfig> configMap = mappingConfigCache
|
|
|
|
- .get(destination + "." + database + "." + table);
|
|
|
|
-
|
|
|
|
- if (configMap == null) {
|
|
|
|
- continue;
|
|
|
|
|
|
+ if (!toExecute) {
|
|
|
|
+ toExecute = function.apply(dml);
|
|
|
|
+ } else {
|
|
|
|
+ function.apply(dml);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (toExecute) {
|
|
|
|
+ List<Future> futures = new ArrayList<>();
|
|
|
|
+ for (int i = 0; i < threads; i++) {
|
|
|
|
+ int j = i;
|
|
|
|
+ futures.add(executorThreads[i].submit(() -> {
|
|
|
|
+ dmlsPartition[j]
|
|
|
|
+ .forEach(syncItem -> sync(batchExecutors[j], syncItem.config, syncItem.singleDml));
|
|
|
|
+ batchExecutors[j].commit();
|
|
|
|
+ return true;
|
|
|
|
+ }));
|
|
}
|
|
}
|
|
- 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;
|
|
|
|
- // 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);
|
|
|
|
- });
|
|
|
|
|
|
+
|
|
|
|
+ futures.forEach(future -> {
|
|
|
|
+ try {
|
|
|
|
+ future.get();
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ logger.error(e.getMessage(), e);
|
|
}
|
|
}
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ for (int i = 0; i < threads; i++) {
|
|
|
|
+ dmlsPartition[i].clear();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- List<Future> futures = new ArrayList<>();
|
|
|
|
- for (int i = 0; i < threads; i++) {
|
|
|
|
- int j = i;
|
|
|
|
- futures.add(executorThreads[i].submit(() -> {
|
|
|
|
- dmlsPartition[j].forEach(syncItem -> sync(batchExecutors[j], syncItem.config, syncItem.singleDml));
|
|
|
|
- batchExecutors[j].commit();
|
|
|
|
- return true;
|
|
|
|
- }));
|
|
|
|
- }
|
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ logger.error(e.getMessage(), e);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 批量同步
|
|
|
|
+ *
|
|
|
|
+ * @param mappingConfig 配置集合
|
|
|
|
+ * @param dmls 批量 DML
|
|
|
|
+ */
|
|
|
|
+ public void sync(Map<String, Map<String, MappingConfig>> mappingConfig, List<Dml> dmls) {
|
|
|
|
+ try {
|
|
|
|
+ sync(dmls, dml -> {
|
|
|
|
+ if (dml.getSql() != null) {
|
|
|
|
+ // 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;
|
|
|
|
+ }
|
|
|
|
|
|
- futures.forEach(future -> {
|
|
|
|
- try {
|
|
|
|
- future.get();
|
|
|
|
- } catch (Exception e) {
|
|
|
|
- logger.error(e.getMessage(), e);
|
|
|
|
|
|
+ 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;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
-
|
|
|
|
- for (int i = 0; i < threads; i++) {
|
|
|
|
- dmlsPartition[i].clear();
|
|
|
|
- }
|
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
logger.error(e.getMessage(), e);
|
|
logger.error(e.getMessage(), e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- private void sync(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * 单条 dml 同步
|
|
|
|
+ *
|
|
|
|
+ * @param batchExecutor 批量事务执行器
|
|
|
|
+ * @param config 对应配置对象
|
|
|
|
+ * @param dml DML
|
|
|
|
+ */
|
|
|
|
+ public void sync(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) {
|
|
try {
|
|
try {
|
|
if (config != null) {
|
|
if (config != null) {
|
|
String type = dml.getType();
|
|
String type = dml.getType();
|
|
@@ -308,10 +365,10 @@ 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;
|
|
@@ -323,7 +380,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);
|
|
}
|
|
}
|
|
@@ -364,12 +421,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;
|
|
}
|
|
}
|
|
@@ -378,11 +435,11 @@ 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()) {
|