mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
mirror db
This commit is contained in:
@@ -200,9 +200,11 @@ public abstract class AbstractCanalAdapterWorker {
|
|||||||
len = 0;
|
len = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!dmlsBatch.isEmpty()) {
|
||||||
adapter.sync(dmlsBatch);
|
adapter.sync(dmlsBatch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
if (!running) {
|
if (!running) {
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import java.sql.SQLException;
|
|||||||
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.*;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
@@ -26,6 +24,12 @@ import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService;
|
|||||||
import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil;
|
import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.*;
|
import com.alibaba.otter.canal.client.adapter.support.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
|
||||||
@@ -40,8 +44,6 @@ public class RdbAdapter implements OuterAdapter {
|
|||||||
private RdbSyncService rdbSyncService;
|
private RdbSyncService rdbSyncService;
|
||||||
private RdbMirrorDbSyncService rdbMirrorDbSyncService;
|
private RdbMirrorDbSyncService rdbMirrorDbSyncService;
|
||||||
|
|
||||||
private ExecutorService executor = Executors.newFixedThreadPool(1);
|
|
||||||
|
|
||||||
private RdbConfigMonitor rdbConfigMonitor;
|
private RdbConfigMonitor rdbConfigMonitor;
|
||||||
|
|
||||||
public Map<String, MappingConfig> getRdbMapping() {
|
public Map<String, MappingConfig> getRdbMapping() {
|
||||||
@@ -52,6 +54,11 @@ public class RdbAdapter implements OuterAdapter {
|
|||||||
return mappingConfigCache;
|
return mappingConfigCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化方法
|
||||||
|
*
|
||||||
|
* @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();
|
||||||
@@ -80,6 +87,7 @@ public class RdbAdapter implements OuterAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化连接池
|
||||||
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"));
|
||||||
@@ -88,7 +96,7 @@ 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(10);
|
||||||
dataSource.setMaxWait(60000);
|
dataSource.setMaxWait(60000);
|
||||||
dataSource.setTimeBetweenEvictionRunsMillis(60000);
|
dataSource.setTimeBetweenEvictionRunsMillis(60000);
|
||||||
dataSource.setMinEvictableIdleTimeMillis(300000);
|
dataSource.setMinEvictableIdleTimeMillis(300000);
|
||||||
@@ -102,24 +110,49 @@ 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,
|
rdbSyncService = new RdbSyncService(dataSource, threads != null ? Integer.valueOf(threads) : null);
|
||||||
dataSource,
|
|
||||||
threads != null ? Integer.valueOf(threads) : null);
|
|
||||||
|
|
||||||
rdbMirrorDbSyncService = new RdbMirrorDbSyncService(mirrorDbConfigCache,
|
rdbMirrorDbSyncService = new RdbMirrorDbSyncService(mirrorDbConfigCache,
|
||||||
dataSource,
|
dataSource,
|
||||||
threads != null ? Integer.valueOf(threads) : null);
|
threads != null ? Integer.valueOf(threads) : null,
|
||||||
|
rdbSyncService.getColumnsTypeCache());
|
||||||
|
|
||||||
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);
|
rdbMirrorDbSyncService.sync(dmls);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
future1.get();
|
||||||
|
future2.get();
|
||||||
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
@@ -168,6 +201,12 @@ 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);
|
||||||
@@ -203,6 +242,12 @@ public class RdbAdapter implements OuterAdapter {
|
|||||||
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);
|
||||||
@@ -212,6 +257,9 @@ public class RdbAdapter implements OuterAdapter {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁方法
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
if (rdbConfigMonitor != null) {
|
if (rdbConfigMonitor != null) {
|
||||||
@@ -222,8 +270,6 @@ public class RdbAdapter implements OuterAdapter {
|
|||||||
rdbSyncService.close();
|
rdbSyncService.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
executor.shutdown();
|
|
||||||
|
|
||||||
if (dataSource != null) {
|
if (dataSource != null) {
|
||||||
dataSource.close();
|
dataSource.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ public class MappingConfig {
|
|||||||
private int readBatch = 5000;
|
private int readBatch = 5000;
|
||||||
private int commitBatch = 5000; // etl等批量提交大小
|
private int commitBatch = 5000; // etl等批量提交大小
|
||||||
|
|
||||||
|
private Map<String, String> allMapColumns;
|
||||||
|
|
||||||
public boolean isMirrorDb() {
|
public boolean isMirrorDb() {
|
||||||
return mirrorDb == null ? false : mirrorDb;
|
return mirrorDb == null ? false : mirrorDb;
|
||||||
}
|
}
|
||||||
@@ -176,5 +178,13 @@ public class MappingConfig {
|
|||||||
public void setCommitBatch(int commitBatch) {
|
public void setCommitBatch(int commitBatch) {
|
||||||
this.commitBatch = commitBatch;
|
this.commitBatch = commitBatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getAllMapColumns() {
|
||||||
|
return allMapColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllMapColumns(Map<String, String> allMapColumns) {
|
||||||
|
this.allMapColumns = allMapColumns;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.alibaba.otter.canal.client.adapter.rdb.service;
|
|||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -14,25 +15,40 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
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.support.SingleDml;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RDB镜像库同步操作业务
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-12-12 下午011:23
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
public class RdbMirrorDbSyncService {
|
public class RdbMirrorDbSyncService {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory
|
private static final Logger logger = LoggerFactory.getLogger(RdbMirrorDbSyncService.class);
|
||||||
.getLogger(RdbMirrorDbSyncService.class);
|
|
||||||
|
|
||||||
private Map<String, MappingConfig> mirrorDbConfigCache; // 镜像库配置
|
private Map<String, MappingConfig> mirrorDbConfigCache; // 镜像库配置
|
||||||
private DataSource dataSource;
|
private DataSource dataSource;
|
||||||
|
private RdbSyncService rdbSyncService; // rdbSyncService代理
|
||||||
|
|
||||||
private static Map<String, MappingConfig> tableDbConfigCache = new ConcurrentHashMap<>();
|
private final Map<String, MappingConfig> tableDbConfigCache = new ConcurrentHashMap<>(); // 自动生成的库表配置缓存
|
||||||
|
|
||||||
public RdbMirrorDbSyncService(Map<String, MappingConfig> mirrorDbConfigCache, DataSource dataSource,
|
public RdbMirrorDbSyncService(Map<String, MappingConfig> mirrorDbConfigCache, DataSource dataSource,
|
||||||
Integer threads){
|
Integer threads, Map<String, Map<String, Integer>> columnsTypeCache){
|
||||||
this.mirrorDbConfigCache = mirrorDbConfigCache;
|
this.mirrorDbConfigCache = mirrorDbConfigCache;
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
|
this.rdbSyncService = new RdbSyncService(dataSource, threads, columnsTypeCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量同步方法
|
||||||
|
*
|
||||||
|
* @param dmls 批量 DML
|
||||||
|
*/
|
||||||
public void sync(List<Dml> dmls) {
|
public void sync(List<Dml> dmls) {
|
||||||
|
try {
|
||||||
|
List<Dml> dmlList = new ArrayList<>();
|
||||||
for (Dml dml : dmls) {
|
for (Dml dml : dmls) {
|
||||||
String destination = StringUtils.trimToEmpty(dml.getDestination());
|
String destination = StringUtils.trimToEmpty(dml.getDestination());
|
||||||
String database = dml.getDatabase();
|
String database = dml.getDatabase();
|
||||||
@@ -42,19 +58,65 @@ public class RdbMirrorDbSyncService {
|
|||||||
}
|
}
|
||||||
if (dml.getSql() != null) {
|
if (dml.getSql() != null) {
|
||||||
// DDL
|
// DDL
|
||||||
executeDdl(database, dml.getSql());
|
executeDdl(dml);
|
||||||
|
rdbSyncService.getColumnsTypeCache().remove(destination + "." + database + "." + dml.getTable());
|
||||||
|
tableDbConfigCache.remove(destination + "." + database + "." + dml.getTable()); // 删除对应库表配置
|
||||||
} else {
|
} else {
|
||||||
// DML
|
// DML
|
||||||
// TODO
|
initMappingConfig(destination + "." + database + "." + dml.getTable(), configMap, dml);
|
||||||
MappingConfig mappingConfig = tableDbConfigCache
|
dmlList.add(dml);
|
||||||
.get(destination + "." + database + "." + dml.getTable());
|
}
|
||||||
|
}
|
||||||
|
if (!dmlList.isEmpty()) {
|
||||||
|
rdbSyncService.sync(dmlList, dml -> {
|
||||||
|
String destination = StringUtils.trimToEmpty(dml.getDestination());
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
MappingConfig config = tableDbConfigCache.get(destination + "." + database + "." + 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化表配置
|
||||||
|
*
|
||||||
|
* @param key 配置key: destination.database.table
|
||||||
|
* @param baseConfigMap db sync config
|
||||||
|
* @param dml DML
|
||||||
|
*/
|
||||||
|
private void initMappingConfig(String key, MappingConfig baseConfigMap, Dml dml) {
|
||||||
|
MappingConfig mappingConfig = tableDbConfigCache.get(key);
|
||||||
if (mappingConfig == null) {
|
if (mappingConfig == null) {
|
||||||
// 构造一个配置
|
// 构造一个配置
|
||||||
mappingConfig = new MappingConfig();
|
mappingConfig = new MappingConfig();
|
||||||
mappingConfig.setDataSourceKey(configMap.getDataSourceKey());
|
mappingConfig.setDataSourceKey(baseConfigMap.getDataSourceKey());
|
||||||
mappingConfig.setDestination(configMap.getDestination());
|
mappingConfig.setDestination(baseConfigMap.getDestination());
|
||||||
mappingConfig.setOuterAdapterKey(configMap.getOuterAdapterKey());
|
mappingConfig.setOuterAdapterKey(baseConfigMap.getOuterAdapterKey());
|
||||||
mappingConfig.setConcurrent(configMap.getConcurrent());
|
mappingConfig.setConcurrent(baseConfigMap.getConcurrent());
|
||||||
MappingConfig.DbMapping dbMapping = new MappingConfig.DbMapping();
|
MappingConfig.DbMapping dbMapping = new MappingConfig.DbMapping();
|
||||||
mappingConfig.setDbMapping(dbMapping);
|
mappingConfig.setDbMapping(dbMapping);
|
||||||
dbMapping.setDatabase(dml.getDatabase());
|
dbMapping.setDatabase(dml.getDatabase());
|
||||||
@@ -67,17 +129,22 @@ public class RdbMirrorDbSyncService {
|
|||||||
pkNames.forEach(pkName -> pkMapping.put(pkName, pkName));
|
pkNames.forEach(pkName -> pkMapping.put(pkName, pkName));
|
||||||
dbMapping.setTargetPk(pkMapping);
|
dbMapping.setTargetPk(pkMapping);
|
||||||
|
|
||||||
tableDbConfigCache.put(destination + "." + database + "." + dml.getTable(), mappingConfig);
|
tableDbConfigCache.put(key, mappingConfig);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void executeDdl(String database, String sql) {
|
/**
|
||||||
|
* DDL 操作
|
||||||
|
*
|
||||||
|
* @param ddl DDL
|
||||||
|
*/
|
||||||
|
private void executeDdl(Dml ddl) {
|
||||||
try (Connection conn = dataSource.getConnection(); Statement statement = conn.createStatement()) {
|
try (Connection conn = dataSource.getConnection(); Statement statement = conn.createStatement()) {
|
||||||
statement.execute(sql);
|
statement.execute(ddl.getSql());
|
||||||
|
// 移除对应配置
|
||||||
|
tableDbConfigCache.remove(ddl.getDatabase() + "." + ddl.getDatabase() + "." + ddl.getTable());
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("Execute DDL sql: {} for database: {}", sql, database);
|
logger.trace("Execute DDL sql: {} for database: {}", ddl.getSql(), ddl.getDatabase());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -38,9 +39,8 @@ 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;
|
||||||
|
|
||||||
@@ -48,14 +48,26 @@ public class RdbSyncService {
|
|||||||
private BatchExecutor[] batchExecutors;
|
private BatchExecutor[] batchExecutors;
|
||||||
private ExecutorService[] executorThreads;
|
private ExecutorService[] executorThreads;
|
||||||
|
|
||||||
|
public List<SyncItem>[] getDmlsPartition() {
|
||||||
|
return dmlsPartition;
|
||||||
|
}
|
||||||
|
|
||||||
|
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){
|
||||||
Integer threads){
|
this(dataSource, threads, new ConcurrentHashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
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,44 +81,29 @@ 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());
|
if (!toExecute) {
|
||||||
String database = dml.getDatabase();
|
toExecute = function.apply(dml);
|
||||||
String table = dml.getTable();
|
|
||||||
Map<String, MappingConfig> configMap = mappingConfigCache
|
|
||||||
.get(destination + "." + database + "." + table);
|
|
||||||
|
|
||||||
if (configMap == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
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 {
|
} else {
|
||||||
int hash = 0;
|
function.apply(dml);
|
||||||
// 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));
|
dmlsPartition[j]
|
||||||
|
.forEach(syncItem -> sync(batchExecutors[j], syncItem.config, syncItem.singleDml));
|
||||||
batchExecutors[j].commit();
|
batchExecutors[j].commit();
|
||||||
return true;
|
return true;
|
||||||
}));
|
}));
|
||||||
@@ -123,12 +120,72 @@ public class RdbSyncService {
|
|||||||
for (int i = 0; i < threads; i++) {
|
for (int i = 0; i < threads; i++) {
|
||||||
dmlsPartition[i].clear();
|
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) {
|
/**
|
||||||
|
* 批量同步
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单条 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()) {
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ 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.isMapAll()) {
|
||||||
|
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;
|
||||||
@@ -39,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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user