Compare commits

...

33 Commits

Author SHA1 Message Date
jianghang.loujh
1413cc9196 fixed issue #4899 , druid SchemaRepository support quota 2024-01-15 15:14:11 +08:00
jianghang.loujh
04c11ec8eb fixed issue #5017 , json partial update fixed & format 2024-01-02 16:42:01 +08:00
Wang Minan
54145ced5e fix parser failed when binlog_row_value_options=partial_json (#5018)
* 修改json部分更新的问题

* 保证after-image的情况下位点回退完整
2024-01-02 10:44:40 +08:00
agapple
fb35dd4254 Update README.md 2023-12-29 12:12:22 +08:00
jianghang.loujh
e233bec66a fixed issue #5012 , support mariadb 11 for Q_CHARACTER_SET_COLLATIONS 2023-12-29 12:08:28 +08:00
jianghang.loujh
67e0928d80 fixed security bugfix #4941 , process auth failed 2023-12-22 10:29:38 +08:00
jianghang.loujh
93dac2d335 format clickhouse adapter code 2023-12-04 09:46:23 +08:00
xander-os
9baeff55e4 enhance clickhouse support (#4957) 2023-12-04 09:34:43 +08:00
Devin Zhang
ca79258e4f support rabbitmq ssl (#4965) 2023-12-04 09:28:26 +08:00
jianghang.loujh
0e4c5c40d7 format code 2023-11-09 15:35:37 +08:00
e345
c65477d97a loadVersionComment() add logger.warn info (#4943) 2023-11-09 15:31:33 +08:00
mask
fc3cfc5c0c Fixed bug where cp -f actually failed, (#4942)
because the symlink already existed.
2023-11-09 15:31:04 +08:00
jianghang.loujh
b4074206ca fixed issue #4940 , support percona 8.0.33 2023-11-08 16:37:24 +08:00
jianghang.loujh
98677c5bc8 fixed issue #4941 , remove default passwd 2023-11-08 10:58:43 +08:00
PeanutVvl
2c7e918f30 将 Logback 升级到 1.2.9 及以上版本 (#4934)
Co-authored-by: wangsc <Asdf1234>
2023-11-07 14:42:56 +08:00
jianghang.loujh
163f719971 fixed issue #4923 , support fastJSON Feature.LargeObject 2023-11-03 12:39:53 +08:00
jianghang.loujh
55a5e7592b fixed issue #4930 , check embeddedServerstate 2023-11-03 11:04:11 +08:00
jianghang.loujh
259008a7cd update config 2023-11-03 10:39:17 +08:00
marun
26ba84e27e Update KafkaCanalConnector.java (#4927)
update fastjson to fastjson2
2023-11-03 10:36:44 +08:00
jianghang.loujh
c8ef9552ec fixed #4907 , reset tsdb build context 2023-10-13 14:05:15 +08:00
jianghang.loujh
bff394042a fixed testcase 2023-10-09 14:31:46 +08:00
jianghang.loujh
b322201889 [maven-release-plugin] prepare for next development iteration 2023-10-09 14:27:37 +08:00
jianghang.loujh
ea8949298f [maven-release-plugin] prepare release canal-1.1.7 2023-10-09 14:27:33 +08:00
dack-su
ba924b9f1d update org.postgresql:postgresql 42.3.3 to 42.5.1 (#4898) 2023-10-09 13:46:20 +08:00
jianghang.loujh
7e0061fe93 build.sh add platform 2023-10-08 19:31:10 +08:00
jianghang.loujh
6440b744f2 fixed arm64 stack size too small, upgrade all to 1m 2023-10-08 18:55:06 +08:00
jianghang.loujh
066b022bb3 fixed arm64 , support mysql 8.0 2023-10-08 17:51:56 +08:00
jianghang.loujh
738f41049f fixed arm64 stack size too small, upgrade to 1m 2023-10-08 14:39:59 +08:00
jianghang.loujh
0f6b9d156a fixed concurrent set properties NPE 2023-10-07 21:54:34 +08:00
jianghang.loujh
ed754b5203 fixed issue #4884, osbase/osadmin image support amd64/arm64 2023-10-07 21:54:00 +08:00
小鹏
11f89163c1 fixed issue #4813, support metric.port = -1 (#4891) 2023-10-07 13:15:07 +08:00
cooper lee
cda330e49e Fix Configuration property name '-id' is not valid on canal 1.1.7 alpha 3 #4879 (#4887)
* Fix Configuration property name '-id' is not valid

* Fix Configuration property name '-id' is not valid
读取原配置文件
2023-09-21 10:53:47 +08:00
jianghang.loujh
f9f810226d fixed issue #4869 , support admin tsdb config 2023-09-11 10:57:43 +08:00
136 changed files with 3738 additions and 590 deletions

View File

@@ -93,6 +93,10 @@ canal 作为 MySQL binlog 增量获取和解析工具,可将变更记录投递
- 参考文档: [Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart) - 参考文档: [Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart)
## 基于canal开发的工具
- canal2sql(基于binlog生成SQL) : [https://github.com/zhuchao941/canal2sql]
## 相关开源&产品 ## 相关开源&产品
- [canal 消费端开源项目: Otter](http://github.com/alibaba/otter) - [canal 消费端开源项目: Otter](http://github.com/alibaba/otter)

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.admin</artifactId> <artifactId>canal.admin</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.admin</artifactId> <artifactId>canal.admin</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>canal.admin-web</artifactId> <artifactId>canal.admin-web</artifactId>
@@ -30,6 +30,8 @@
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-java</artifactId>
<!-- 支持arm64需要mysql 8.0,升级为8.0的新版驱动 -->
<version>8.0.33</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.ben-manes.caffeine</groupId> <groupId>com.github.ben-manes.caffeine</groupId>

View File

@@ -59,7 +59,7 @@ esac
JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'` JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'`
str=`file -L $JAVA | grep 64-bit` str=`file -L $JAVA | grep 64-bit`
JAVA_OPTS="$JAVA_OPTS -Xss256k -XX:+AggressiveOpts -XX:-UseBiasedLocking -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$base/logs" JAVA_OPTS="$JAVA_OPTS -Xss1m -XX:+AggressiveOpts -XX:-UseBiasedLocking -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$base/logs"
if [ $JavaVersion -ge 11 ] ; then if [ $JavaVersion -ge 11 ] ; then
#JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$base_log/gc.log:time " #JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$base_log/gc.log:time "
JAVA_OPTS="$JAVA_OPTS" JAVA_OPTS="$JAVA_OPTS"

View File

@@ -10,6 +10,7 @@ import java.util.List;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;

View File

@@ -5,6 +5,8 @@ import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -26,8 +28,24 @@ import com.fasterxml.jackson.databind.ObjectMapper;
@Configuration @Configuration
public class WebConfig implements WebMvcConfigurer { public class WebConfig implements WebMvcConfigurer {
@Value(value = "${canal.adminUser}")
private String user;
@Value(value = "${canal.adminPasswd}")
private String passwd;
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
if (StringUtils.isEmpty(user)) {
throw new IllegalArgumentException(
"canal.adminUser is empty , pls check https://github.com/alibaba/canal/issues/4941");
}
if (StringUtils.isEmpty(passwd)) {
throw new IllegalArgumentException(
"canal.adminPasswd is empty , pls check https://github.com/alibaba/canal/issues/4941");
}
registry.addInterceptor(new HandlerInterceptor() { registry.addInterceptor(new HandlerInterceptor() {
@Override @Override

View File

@@ -2,15 +2,11 @@ package com.alibaba.otter.canal.admin.controller;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.otter.canal.admin.model.BaseModel; import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.CanalConfig; import com.alibaba.otter.canal.admin.model.CanalConfig;
@@ -28,7 +24,7 @@ import com.alibaba.otter.canal.protocol.SecurityUtil;
@RequestMapping("/api/{env}/config") @RequestMapping("/api/{env}/config")
public class PollingConfigController { public class PollingConfigController {
private static final byte[] seeds = "canal is best!".getBytes(); private static final byte[] seeds = RandomStringUtils.random(16).getBytes();
@Value(value = "${canal.adminUser}") @Value(value = "${canal.adminUser}")
String user; String user;

View File

@@ -18,4 +18,4 @@ spring.datasource:
canal: canal:
adminUser: admin adminUser: admin
adminPasswd: admin adminPasswd:

View File

@@ -3,13 +3,13 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.1.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal.admin</artifactId> <artifactId>canal.admin</artifactId>
<version>1.1.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>canal admin module for otter ${project.version}</name> <name>canal admin module for otter ${project.version}</name>
<modules> <modules>

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.clickhouse</artifactId>
<packaging>jar</packaging>
<name>canal client adapter clickhouse module for otter ${project.version}</name>
<dependencies>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.common</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ru.yandex.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<copy todir="${project.basedir}/../launcher/target/classes/clickhouse" overwrite="true">
<fileset dir="${project.basedir}/target/classes/clickhouse" erroronmissingdir="true">
<include name="*.yml" />
</fileset>
</copy>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,340 @@
package com.alibaba.otter.canal.client.adapter.clickhouse;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.util.JdbcUtils;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.clickhouse.config.ConfigLoader;
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MirrorDbConfig;
import com.alibaba.otter.canal.client.adapter.clickhouse.monitor.ClickHouseConfigMonitor;
import com.alibaba.otter.canal.client.adapter.clickhouse.service.ClickHouseBatchSyncService;
import com.alibaba.otter.canal.client.adapter.clickhouse.service.ClickHouseEtlService;
import com.alibaba.otter.canal.client.adapter.clickhouse.service.ClickHouseMirrorDbBatchSyncService;
import com.alibaba.otter.canal.client.adapter.clickhouse.support.SyncUtil;
import com.alibaba.otter.canal.client.adapter.support.*;
/**
* ClickHouse Adapter implementation
*
* @author: Xander
* @date: Created in 2023/11/10 1:13
* @email: zhrunxin33@gmail.com
*/
@SPI
public class ClickHouseAdapter implements OuterAdapter {
private static final Logger logger = LoggerFactory.getLogger(ClickHouseAdapter.class);
// Store the mapping of filename and configuration, load yml files below
// resource path
private Map<String, MappingConfig> clickHouseMapping = new ConcurrentHashMap<>();
// Schema -> Table -> MappingConfig
private Map<String, Map<String, MappingConfig>> mappingConfigCache = new ConcurrentHashMap<>();
// Mirror DB Configuration, don't need to load column mapping
private Map<String, MirrorDbConfig> mirrorDbConfigCache = new ConcurrentHashMap<>();
private DruidDataSource dataSource;
private ClickHouseBatchSyncService clickHouseBatchSyncService;
private ClickHouseMirrorDbBatchSyncService clickHouseMirrorDbBatchSyncService;
private Properties envProperties;
// Launch configuration
private OuterAdapterConfig configuration;
private ClickHouseConfigMonitor clickHouseConfigMonitor;
public Map<String, MappingConfig> getClickHouseMapping() {
return clickHouseMapping;
}
@Override
public void init(OuterAdapterConfig configuration, Properties envProperties) {
this.envProperties = envProperties;
this.configuration = configuration;
// Load DB type from adapter.launch/bootstrap.yml
Map<String, String> properties = configuration.getProperties();
String dbType = JdbcUtils.getDbType(properties.get("jdbc.url"), null);
// 当.yml文件编码格式存在问题,此处clickhouse yml文件构建 可能会抛出异常
Map<String, MappingConfig> clickHouseMappingTmp = ConfigLoader.load(envProperties);
// 过滤不匹配的key的配置
clickHouseMappingTmp.forEach((key, config) -> {
addConfig(key, config);
});
if (clickHouseMapping.isEmpty()) {
throw new RuntimeException("No clickhouse adapter found for config key: " + configuration.getKey());
}
// 初始化连接池
dataSource = new DruidDataSource();
dataSource.setDriverClassName(properties.get("jdbc.driverClassName"));
dataSource.setUrl(properties.get("jdbc.url"));
dataSource.setUsername(properties.get("jdbc.username"));
dataSource.setPassword(properties.get("jdbc.password"));
dataSource.setInitialSize(1);
dataSource.setMinIdle(1);
dataSource.setMaxActive(30);
dataSource.setMaxWait(60000);
dataSource.setTimeBetweenEvictionRunsMillis(60000);
dataSource.setMinEvictableIdleTimeMillis(300000);
dataSource.setUseUnfairLock(true);
dataSource.setDefaultAutoCommit(false); // disable auto commit or disable Transactional
dataSource.setDbType(dbType);
if ("true".equals(properties.getOrDefault("druid.stat.enable", "true"))) {
StatFilter statFilter = new StatFilter();
statFilter.setSlowSqlMillis(Long.parseLong(properties.getOrDefault("druid.stat.slowSqlMillis", "1000")));
statFilter.setMergeSql(true);
statFilter.setLogSlowSql(true);
dataSource.setProxyFilters(Collections.singletonList(statFilter));
}
try {
dataSource.init();
} catch (SQLException e) {
logger.error("ERROR ## failed to initial datasource: " + properties.get("jdbc.url"), e);
}
String threads = properties.get("threads");
String scheduleTime = properties.get("scheduleTime");
String batchSize = properties.get("batchSize");
boolean skipDupException = BooleanUtils.toBoolean(configuration.getProperties()
.getOrDefault("skipDupException", "true"));
clickHouseBatchSyncService = new ClickHouseBatchSyncService(dataSource,
threads != null ? Integer.valueOf(threads) : null,
batchSize != null ? Integer.valueOf(batchSize) : null,
scheduleTime != null ? Long.valueOf(scheduleTime) : null,
skipDupException);
clickHouseMirrorDbBatchSyncService = new ClickHouseMirrorDbBatchSyncService(mirrorDbConfigCache,
dataSource,
threads != null ? Integer.valueOf(threads) : null,
batchSize != null ? Integer.valueOf(batchSize) : null,
scheduleTime != null ? Long.valueOf(scheduleTime) : null,
clickHouseBatchSyncService.getColumnsTypeCache(),
skipDupException);
clickHouseConfigMonitor = new ClickHouseConfigMonitor();
clickHouseConfigMonitor.init(configuration.getKey(), this, envProperties);
}
/**
* Sync main entrance
*
* @param dmls 数据包
*/
@Override
public void sync(List<Dml> dmls) {
if (dmls == null || dmls.isEmpty()) {
return;
}
try {
// If mappingConfigCache(column mapping) is empty, that must be mirroring synchronize
if (!mappingConfigCache.isEmpty()) {
clickHouseBatchSyncService.sync(mappingConfigCache, dmls, envProperties);
}
clickHouseMirrorDbBatchSyncService.sync(dmls);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Destroy
*/
@Override
public void destroy() {
if (clickHouseConfigMonitor != null) {
clickHouseConfigMonitor.destroy();
}
if (clickHouseBatchSyncService != null) {
clickHouseBatchSyncService.close();
}
if (dataSource != null) {
dataSource.close();
}
}
/**
* ETL方法
*
* @param task 任务名, 对应配置名
* @param params etl筛选条件
* @return ETL结果
*/
@Override
public EtlResult etl(String task, List<String> params) {
EtlResult etlResult = new EtlResult();
MappingConfig config = clickHouseMapping.get(task);
ClickHouseEtlService clickhouseEtlService = new ClickHouseEtlService(dataSource, config);
if (config != null) {
return clickhouseEtlService.importData(params);
} else {
StringBuilder resultMsg = new StringBuilder();
boolean resSucc = true;
for (MappingConfig configTmp : clickHouseMapping.values()) {
// 取所有的destination为task的配置
if (configTmp.getDestination().equals(task)) {
EtlResult etlRes = clickhouseEtlService.importData(params);
if (!etlRes.getSucceeded()) {
resSucc = false;
resultMsg.append(etlRes.getErrorMessage()).append("\n");
} else {
resultMsg.append(etlRes.getResultMessage()).append("\n");
}
}
}
if (resultMsg.length() > 0) {
etlResult.setSucceeded(resSucc);
if (resSucc) {
etlResult.setResultMessage(resultMsg.toString());
} else {
etlResult.setErrorMessage(resultMsg.toString());
}
return etlResult;
}
}
etlResult.setSucceeded(false);
etlResult.setErrorMessage("Task not found");
return etlResult;
}
/**
* 获取总数方法
*
* @param task 任务名, 对应配置名
* @return 总数
*/
@Override
public Map<String, Object> count(String task) {
MappingConfig config = clickHouseMapping.get(task);
MappingConfig.DbMapping dbMapping = config.getDbMapping();
String sql = "SELECT COUNT(1) AS cnt FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType());
Connection conn = null;
Map<String, Object> res = new LinkedHashMap<>();
try {
conn = dataSource.getConnection();
Util.sqlRS(conn, sql, rs -> {
try {
if (rs.next()) {
Long rowCount = rs.getLong("cnt");
res.put("count", rowCount);
}
} catch (SQLException e) {
logger.error(e.getMessage(), e);
}
});
} catch (SQLException e) {
logger.error(e.getMessage(), e);
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
logger.error(e.getMessage(), e);
}
}
}
res.put("targetTable", SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()));
return res;
}
/**
* 获取对应canal instance name 或 mq topic
*
* @param task 任务名, 对应配置名
* @return destination
*/
@Override
public String getDestination(String task) {
MappingConfig config = clickHouseMapping.get(task);
if (config != null) {
return config.getDestination();
}
return null;
}
private void addSyncConfigToCache(String configName, MappingConfig mappingConfig) {
if (!mappingConfig.getDbMapping().getMirrorDb()) {
String key;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
} else {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(key,
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));
}
}
public boolean addConfig(String fileName, MappingConfig config) {
if (match(config)) {
clickHouseMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
FileName2KeyMapping.register(getClass().getAnnotation(SPI.class).value(), fileName,
configuration.getKey());
return true;
}
return false;
}
public void updateConfig(String fileName, MappingConfig config) {
if (config.getOuterAdapterKey() != null && !config.getOuterAdapterKey()
.equals(configuration.getKey())) {
// 理论上不允许改这个 因为本身就是通过这个关联起Adapter和Config的
throw new RuntimeException("not allow to change outAdapterKey");
}
clickHouseMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
clickHouseMapping.remove(fileName);
for (Map<String, MappingConfig> configMap : mappingConfigCache.values()) {
if (configMap != null) {
configMap.remove(fileName);
}
}
FileName2KeyMapping.unregister(getClass().getAnnotation(SPI.class).value(), fileName);
}
private boolean match(MappingConfig config) {
boolean sameMatch = config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey());
boolean prefixMatch = config.getOuterAdapterKey() == null && configuration.getKey()
.startsWith(StringUtils
.join(new String[]{Util.AUTO_GENERATED_PREFIX, config.getDestination(),
config.getGroupId()}, '-'));
return sameMatch || prefixMatch;
}
}

View File

@@ -0,0 +1,52 @@
package com.alibaba.otter.canal.client.adapter.clickhouse.config;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.YamlUtils;
/**
* CLICKHOUSE表映射配置加载器
*
* @author: Xander
* @date: Created in 2023/11/10 22:23
* @email: zhrunxin33@gmail.com
* @version 1.1.8
*/
public class ConfigLoader {
private static Logger logger = LoggerFactory.getLogger(ConfigLoader.class);
/**
* 加载CLICKHOUSE表映射配置
*
* @return 配置名/配置文件名--对象
*/
public static Map<String, MappingConfig> load(Properties envProperties) {
logger.info("## Start loading clickhouse mapping config ... ");
Map<String, MappingConfig> result = new LinkedHashMap<>();
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("clickhouse");
configContentMap.forEach((fileName, content) -> {
MappingConfig config = YamlUtils.ymlToObj(null, content, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}
try {
config.validate();
} catch (Exception e) {
throw new RuntimeException("ERROR Config: " + fileName + " " + e.getMessage(), e);
}
result.put(fileName, config);
});
logger.info("## ClickHouse mapping config loaded");
return result;
}
}

View File

@@ -0,0 +1,227 @@
package com.alibaba.otter.canal.client.adapter.clickhouse.config;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import com.alibaba.otter.canal.client.adapter.support.AdapterConfig;
/**
* CLICKHOUSE表映射配置
*
* @author: Xander
* @date: Created in 2023/11/10 22:23
* @email: zhrunxin33@gmail.com
* @version 1.1.8
*/
public class MappingConfig implements AdapterConfig {
private String dataSourceKey; // 数据源key
private String destination; // canal实例或MQ的topic
private String groupId; // groupId
private String outerAdapterKey; // 对应适配器的key
private boolean concurrent = false; // 是否并行同步
private DbMapping dbMapping; // db映射配置
public String getDataSourceKey() {
return dataSourceKey;
}
public void setDataSourceKey(String dataSourceKey) {
this.dataSourceKey = dataSourceKey;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getOuterAdapterKey() {
return outerAdapterKey;
}
public void setOuterAdapterKey(String outerAdapterKey) {
this.outerAdapterKey = outerAdapterKey;
}
public boolean getConcurrent() {
return concurrent;
}
public void setConcurrent(boolean concurrent) {
this.concurrent = concurrent;
}
public DbMapping getDbMapping() {
return dbMapping;
}
public void setDbMapping(DbMapping dbMapping) {
this.dbMapping = dbMapping;
}
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
public AdapterMapping getMapping() {
return dbMapping;
}
public void validate() {
if (dbMapping.database == null || dbMapping.database.isEmpty()) {
throw new NullPointerException("dbMapping.database");
}
if (!dbMapping.getMirrorDb() && (dbMapping.table == null || dbMapping.table.isEmpty())) {
throw new NullPointerException("dbMapping.table");
}
if (!dbMapping.getMirrorDb() && (dbMapping.targetTable == null || dbMapping.targetTable.isEmpty())) {
throw new NullPointerException("dbMapping.targetTable");
}
}
public static class DbMapping implements AdapterMapping {
private boolean mirrorDb = false; // 是否镜像库
private String database; // 数据库名或schema名
private String table; // 表名
private Map<String, String> targetPk = new LinkedHashMap<>(); // 目标表主键字段
private boolean mapAll = false; // 映射所有字段
private String targetDb; // 目标库名
private String targetTable; // 目标表名
private Map<String, String> targetColumns; // 目标表字段映射
private boolean caseInsensitive = false; // 目标表不区分大小写,默认是否
private String etlCondition; // etl条件sql
private int readBatch = 5000;
private int commitBatch = 5000; // etl等批量提交大小
private Map<String, String> allMapColumns;
public boolean getMirrorDb() {
return mirrorDb;
}
public void setMirrorDb(boolean mirrorDb) {
this.mirrorDb = mirrorDb;
}
public String getDatabase() {
return database;
}
public void setDatabase(String database) {
this.database = database;
}
public String getTable() {
return table;
}
public void setTable(String table) {
this.table = table;
}
public Map<String, String> getTargetPk() {
return targetPk;
}
public void setTargetPk(Map<String, String> targetPk) {
this.targetPk = targetPk;
}
public Boolean getMapAll() {
return mapAll;
}
public void setMapAll(Boolean mapAll) {
this.mapAll = mapAll;
}
public String getTargetDb() {
return targetDb;
}
public void setTargetDb(String targetDb) {
this.targetDb = targetDb;
}
public String getTargetTable() {
return targetTable;
}
public void setTargetTable(String targetTable) {
this.targetTable = targetTable;
}
public Map<String, String> getTargetColumns() {
if (targetColumns != null) {
targetColumns.forEach((key, value) -> {
if (StringUtils.isEmpty(value)) {
targetColumns.put(key, key);
}
});
}
return targetColumns;
}
public void setTargetColumns(Map<String, String> targetColumns) {
this.targetColumns = targetColumns;
}
public boolean isCaseInsensitive() {
return caseInsensitive;
}
public void setCaseInsensitive(boolean caseInsensitive) {
this.caseInsensitive = caseInsensitive;
}
public String getEtlCondition() {
return etlCondition;
}
public void setEtlCondition(String etlCondition) {
this.etlCondition = etlCondition;
}
public int getReadBatch() {
return readBatch;
}
public void setReadBatch(int readBatch) {
this.readBatch = readBatch;
}
public int getCommitBatch() {
return commitBatch;
}
public void setCommitBatch(int commitBatch) {
this.commitBatch = commitBatch;
}
public Map<String, String> getAllMapColumns() {
return allMapColumns;
}
public void setAllMapColumns(Map<String, String> allMapColumns) {
this.allMapColumns = allMapColumns;
}
}
}

View File

@@ -0,0 +1,51 @@
package com.alibaba.otter.canal.client.adapter.clickhouse.config;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author: Xander
* @date: Created in 2023/11/10 22:23
* @email: zhrunxin33@gmail.com
* @version 1.1.8
*/
public class MirrorDbConfig {
private String fileName;
private MappingConfig mappingConfig;
private 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;
}
}

View File

@@ -0,0 +1,131 @@
package com.alibaba.otter.canal.client.adapter.clickhouse.monitor;
import java.io.File;
import java.util.Properties;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.clickhouse.ClickHouseAdapter;
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig;
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.YamlUtils;
/**
* @author: Xander
* @date: Created in 2023/11/10 22:23
* @email: zhrunxin33@gmail.com
* @version 1.1.8
*/
public class ClickHouseConfigMonitor {
private static final Logger logger = LoggerFactory.getLogger(ClickHouseConfigMonitor.class);
private static final String adapterName = "clickhouse";
private String key;
private ClickHouseAdapter clickHouseAdapter;
private Properties envProperties;
private FileAlterationMonitor fileMonitor;
public void init(String key, ClickHouseAdapter clickHouseAdapter, Properties envProperties) {
this.key = key;
this.clickHouseAdapter = clickHouseAdapter;
this.envProperties = envProperties;
File confDir = Util.getConfDirPath(adapterName);
try {
FileAlterationObserver observer = new FileAlterationObserver(confDir,
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileListener listener = new FileListener();
observer.addListener(listener);
fileMonitor = new FileAlterationMonitor(3000, observer);
fileMonitor.start();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
public void destroy() {
try {
fileMonitor.stop();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
private class FileListener extends FileAlterationListenerAdaptor {
@Override
public void onFileCreate(File file) {
super.onFileCreate(file);
try {
// 加载新增的配置文件
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
MappingConfig config = YamlUtils
.ymlToObj(null, configContent, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}
config.validate();
boolean result = clickHouseAdapter.addConfig(file.getName(), config);
if (result) {
logger.info("Add a new clickhouse mapping config: {} to canal adapter", file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
@Override
public void onFileChange(File file) {
super.onFileChange(file);
try {
if (clickHouseAdapter.getClickHouseMapping().containsKey(file.getName())) {
// 加载配置文件
String configContent = MappingConfigsLoader
.loadConfig(adapterName + File.separator + file.getName());
if (configContent == null) {
onFileDelete(file);
return;
}
MappingConfig config = YamlUtils
.ymlToObj(null, configContent, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}
config.validate();
clickHouseAdapter.updateConfig(file.getName(), config);
logger.info("Change a clickhouse mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
@Override
public void onFileDelete(File file) {
super.onFileDelete(file);
try {
if (clickHouseAdapter.getClickHouseMapping().containsKey(file.getName())) {
clickHouseAdapter.deleteConfig(file.getName());
logger.info("Delete a clickhouse mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
}

View File

@@ -0,0 +1,555 @@
package com.alibaba.otter.canal.client.adapter.clickhouse.service;
import java.sql.Connection;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig.DbMapping;
import com.alibaba.otter.canal.client.adapter.clickhouse.support.BatchExecutor;
import com.alibaba.otter.canal.client.adapter.clickhouse.support.SingleDml;
import com.alibaba.otter.canal.client.adapter.clickhouse.support.SyncUtil;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import com.alibaba.otter.canal.client.adapter.support.Util;
/**
* ClickHouse batch synchronize
*
* @author: Xander
* @date: Created in 2023/11/10 22:23
* @email: zhrunxin33@gmail.com
* @version 1.1.8
*/
public class ClickHouseBatchSyncService {
private static final Logger logger = LoggerFactory.getLogger(ClickHouseBatchSyncService.class);
private DruidDataSource dataSource;
private Map<String, Map<String, Integer>> columnsTypeCache; // Cache of instance.schema.table -> <columnName, jdbcType>
private Map<MappingConfig, List<SingleDml>>[] bufferPools; // Data buffer pool store sync data, List<Dml> dispersed as arrays according to hash value
private BatchExecutor[] batchExecutors; // Batch Executor
private BatchExecutor alterExecutors; // Alter Single Executor(update/delete/truncate)
private ExecutorService[] executorThreads; // Be initialized once
private ScheduledExecutorService[] scheduledExecutors;
private int threads = 3; // Default parallel thread count
private int batchSize = 1000;
private long scheduleTime = 10;
private boolean skipDupException;
public Map<String, Map<String, Integer>> getColumnsTypeCache() {
return columnsTypeCache;
}
public ClickHouseBatchSyncService(DruidDataSource dataSource, Integer threads, Integer batchSize, Long scheduleTime, boolean skipDupException){
this(dataSource, threads, batchSize, scheduleTime, new ConcurrentHashMap<>(), skipDupException);
}
@SuppressWarnings("unchecked")
public ClickHouseBatchSyncService(DruidDataSource dataSource, Integer threads, Integer batchSize, Long scheduleTime, Map<String, Map<String, Integer>> columnsTypeCache,
boolean skipDupException){
this.dataSource = dataSource;
this.columnsTypeCache = columnsTypeCache;
this.skipDupException = skipDupException;
try {
if (threads != null) {
this.threads = threads;
}
if (batchSize != null) {
this.batchSize = batchSize;
}
if (scheduleTime != null) {
this.scheduleTime = scheduleTime;
}
this.alterExecutors = new BatchExecutor(dataSource);
this.bufferPools = new ConcurrentHashMap[this.threads];
this.batchExecutors = new BatchExecutor[this.threads];
this.executorThreads = new ExecutorService[this.threads];
this.scheduledExecutors = new ScheduledExecutorService[this.threads];
for (int i = 0; i < this.threads; i++) {
bufferPools[i] = new ConcurrentHashMap<>();
batchExecutors[i] = new BatchExecutor(dataSource);
executorThreads[i] = Executors.newSingleThreadExecutor();
scheduledExecutors[i] = Executors.newSingleThreadScheduledExecutor();
}
scheduleBatchSync();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Timing-driven event
* start schedule batch sync threadPool
*/
private void scheduleBatchSync() {
for (int i = 0; i < scheduledExecutors.length; i++) {
int index = i;
scheduledExecutors[i].scheduleAtFixedRate(()->{
List<Future<Boolean>> futures = new ArrayList<>();
for (MappingConfig mappingConfig : bufferPools[index].keySet()) {
List<SingleDml> dmls = bufferPools[index].get(mappingConfig);
if (dmls == null || dmls.isEmpty()) {
return;
}
List<SingleDml> tempDmls;
synchronized (dmls) {
tempDmls = new ArrayList<>(dmls);
dmls.clear();
}
futures.add(executorThreads[index].submit(()->{
try {
insert(batchExecutors[index], mappingConfig, tempDmls);
batchExecutors[index].commit();
return true;
} catch (Exception e){
batchExecutors[index].rollback();
throw new RuntimeException(e);
}
}));
}
}, 0, scheduleTime, TimeUnit.SECONDS);
}
logger.info("Schedule batch executors has started successfully!");
}
/**
* 批量同步回调
*
* @param dmls 批量 DML
* @param function 回调方法
*/
public void sync(List<Dml> dmls, Function<Dml, Boolean> function) {
boolean toExecute = false;
for (Dml dml : dmls) {
if (!toExecute) {
toExecute = function.apply(dml);
} else {
function.apply(dml);
}
}
}
/**
* Distribute dmls into different partition
*
* @param mappingConfig {@link com.alibaba.otter.canal.client.adapter.clickhouse.ClickHouseAdapter#mappingConfigCache }
* @param dmls received DML
*/
public void sync(Map<String, Map<String, MappingConfig>> mappingConfig, List<Dml> dmls, Properties envProperties) {
sync(dmls, dml -> {
if (dml.getIsDdl() != null && dml.getIsDdl() && StringUtils.isNotEmpty(dml.getSql())) {
// DDL(Cache need to update when DDL was executed)
columnsTypeCache.remove(dml.getDestination() + "." + dml.getDatabase() + "." + dml.getTable());
return false;
} else {
// DML
String destination = StringUtils.trimToEmpty(dml.getDestination());
String groupId = StringUtils.trimToEmpty(dml.getGroupId());
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, MappingConfig> configMap;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
configMap = mappingConfig.get(destination + "-" + groupId + "_" + database + "-" + table);
} else {
configMap = mappingConfig.get(destination + "_" + database + "-" + table);
}
if (configMap == null) {
return false;
}
if (configMap.values().isEmpty()) {
return false;
}
for (MappingConfig config : configMap.values()) {
distributeDml(config, dml);
}
return true;
}
} );
}
/**
* Dml distributor
*/
private void distributeDml(MappingConfig config, Dml dml) {
if (config != null) {
try {
String type = dml.getType();
if (type == null) return;
if (type.equalsIgnoreCase("INSERT")) {
appendDmlBufferPartition(config, dml);
} else {
boolean caseInsensitive = config.getDbMapping().isCaseInsensitive();
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
if (type.equalsIgnoreCase("UPDATE")) {
for (SingleDml singleDml : singleDmls) {
update(alterExecutors, config, singleDml);
}
} else if (type.equalsIgnoreCase("DELETE")) {
for (SingleDml singleDml : singleDmls) {
delete(alterExecutors, config, singleDml);
}
} else if (type.equalsIgnoreCase("TRUNCATE")) {
truncate(alterExecutors, config);
}
}
if (logger.isDebugEnabled()) {
logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
public void appendDmlBufferPartition(MappingConfig config, Dml dml) {
boolean caseInsensitive = config.getDbMapping().isCaseInsensitive();
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
int hash = mappingHash(config.getDbMapping());
if (!config.getConcurrent()) {
hash = 0;
}
List<SingleDml> dmls = bufferPools[hash].computeIfAbsent(config, k -> new ArrayList<>());
synchronized (dmls) {
dmls.add(singleDml);
logger.info("Append one data into pool, id {}", singleDml.getData().get("id"));
}
// Check the size of the List, achieve when it reaches the maximum value
if (dmls.size() >= batchSize) syncToClickHouse(config, hash);
});
}
/**
* sync when size of list{@link #bufferPools } reaches the maximum value
*
* @param config key
* @param index parallel thread index
*/
private void syncToClickHouse(MappingConfig config, int index) {
List<SingleDml> dmls = bufferPools[index].get(config);
logger.info("schema:{} table:{} reaches the maximum value, ready to synchronize, size {}", config.getDbMapping().getDatabase(), config.getDbMapping().getTable(), dmls.size());
if (dmls ==null || dmls.isEmpty()) {
return;
}
List<SingleDml> tempDmls;
synchronized (dmls) {
tempDmls = new ArrayList<>(dmls);
dmls.clear();
}
executorThreads[index].submit(() -> {
try {
insert(batchExecutors[index], config, tempDmls);
batchExecutors[index].commit();
return true;
} catch (Exception e) {
batchExecutors[index].rollback();
throw new RuntimeException(e);
}
});
}
/**
* Insert
*
* @param batchExecutor batch translational executor
* @param config corresponding configuration object
* @param dmls DMLs
*/
private void insert(BatchExecutor batchExecutor, MappingConfig config, List<SingleDml> dmls) throws SQLException {
if (dmls == null || dmls.isEmpty()) {
return;
}
List<SingleDml> clearDmls = dmls.stream().filter(e -> e.getData() != null && !e.getData().isEmpty()).collect(Collectors.toList());
if (clearDmls == null || clearDmls.isEmpty()) {
return;
}
DbMapping dbMapping = config.getDbMapping();
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, clearDmls.get(0).getData());
StringBuilder insertSql = new StringBuilder();
insertSql.append("INSERT INTO ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())).append(" (");
columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(backtick)
.append(targetColumnName)
.append(backtick)
.append(","));
int len = insertSql.length();
insertSql.delete(len - 1, len).append(") VALUES (");
int mapLen = columnsMap.size();
for (int i = 0; i < mapLen; i++) {
insertSql.append("?,");
}
len = insertSql.length();
insertSql.delete(len - 1, len).append(")");
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
List<List<Map<String, ?>>> values = new ArrayList<>();
boolean flag = false;
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");
}
for (int i = 0; i < clearDmls.size(); i++) {
Map<String, Object> dmlData = clearDmls.get(i).getData();
List<Map<String, ?>> item;
if (flag == false) {
item = new ArrayList<>();
values.add(item);
} else {
item = values.get(i);
}
Object value = dmlData.get(srcColumnName);
BatchExecutor.setValue(item, type, value);
}
flag = true;
}
try {
batchExecutor.batchExecute(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);
}
}
/**
* 更新操作
*
* @param config 配置项
* @param dml DML数据
*/
private void update(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) throws SQLException {
Map<String, Object> data = dml.getData();
if (data == null || data.isEmpty()) {
return;
}
Map<String, Object> old = dml.getOld();
if (old == null || old.isEmpty()) {
return;
}
DbMapping dbMapping = config.getDbMapping();
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data);
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
StringBuilder updateSql = new StringBuilder();
updateSql.append("ALTER TABLE ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())).append(" UPDATE ");
List<Map<String, ?>> values = new ArrayList<>();
boolean hasMatched = false;
for (String srcColumnName : old.keySet()) {
List<String> targetColumnNames = new ArrayList<>();
columnsMap.forEach((targetColumn, srcColumn) -> {
if (srcColumnName.equalsIgnoreCase(srcColumn)) {
targetColumnNames.add(targetColumn);
}
});
if (!targetColumnNames.isEmpty()) {
hasMatched = true;
for (String targetColumnName : targetColumnNames) {
updateSql.append(backtick).append(targetColumnName).append(backtick).append("=?, ");
Integer type = ctype.get(Util.cleanColumn(targetColumnName).toLowerCase());
if (type == null) {
throw new RuntimeException("Target column: " + targetColumnName + " not matched");
}
BatchExecutor.setValue(values, type, data.get(srcColumnName));
}
}
}
if (!hasMatched) {
logger.warn("Did not matched any columns to update ");
return;
}
int len = updateSql.length();
updateSql.delete(len - 2, len).append(" WHERE ");
// 拼接主键
appendCondition(dbMapping, updateSql, ctype, values, data, old);
batchExecutor.execute(updateSql.toString(), values);
if (logger.isTraceEnabled()) {
logger.trace("Alter update target table, sql: {}", updateSql);
}
}
/**
* 删除操作
*
* @param config
* @param dml
*/
private void delete(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) throws SQLException {
Map<String, Object> data = dml.getData();
if (data == null || data.isEmpty()) {
return;
}
DbMapping dbMapping = config.getDbMapping();
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
StringBuilder sql = new StringBuilder();
sql.append("ALTER TABLE ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())).append(" DELETE WHERE ");
List<Map<String, ?>> values = new ArrayList<>();
// 拼接主键
appendCondition(dbMapping, sql, ctype, values, data);
batchExecutor.execute(sql.toString(), values);
if (logger.isTraceEnabled()) {
logger.trace("Alter delete from target table, sql: {}", sql);
}
}
/**
* truncate操作
*
* @param config
*/
private void truncate(BatchExecutor batchExecutor, MappingConfig config) throws SQLException {
DbMapping dbMapping = config.getDbMapping();
StringBuilder sql = new StringBuilder();
sql.append("TRUNCATE TABLE ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()));
batchExecutor.execute(sql.toString(), new ArrayList<>());
if (logger.isTraceEnabled()) {
logger.trace("Truncate target table, sql: {}", sql);
}
}
/**
* 获取目标字段类型
*
* @param conn sql connection
* @param config 映射配置
* @return 字段sqlType
*/
private Map<String, Integer> getTargetColumnType(Connection conn, MappingConfig config) {
DbMapping dbMapping = config.getDbMapping();
String cacheKey = config.getDestination() + "." + dbMapping.getDatabase() + "." + dbMapping.getTable();
Map<String, Integer> columnType = columnsTypeCache.get(cacheKey);
if (columnType == null) {
synchronized (ClickHouseBatchSyncService.class) {
columnType = columnsTypeCache.get(cacheKey);
if (columnType == null) {
columnType = new LinkedHashMap<>();
final Map<String, Integer> columnTypeTmp = columnType;
String sql = "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " WHERE 1=2";
Util.sqlRS(conn, sql, rs -> {
try {
ResultSetMetaData rsd = rs.getMetaData();
int columnCount = rsd.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
int colType = rsd.getColumnType(i);
// 修复year类型作为date处理时的data truncated问题
if ("YEAR".equals(rsd.getColumnTypeName(i))) {
colType = Types.VARCHAR;
}
columnTypeTmp.put(rsd.getColumnName(i).toLowerCase(), colType);
}
columnsTypeCache.put(cacheKey, columnTypeTmp);
} catch (SQLException e) {
logger.error(e.getMessage(), e);
}
});
}
}
}
return columnType;
}
/**
* 拼接主键 where条件
*/
private void appendCondition(MappingConfig.DbMapping dbMapping, StringBuilder sql, Map<String, Integer> ctype,
List<Map<String, ?>> values, Map<String, Object> d) {
appendCondition(dbMapping, sql, ctype, values, d, null);
}
private void appendCondition(MappingConfig.DbMapping dbMapping, StringBuilder sql, Map<String, Integer> ctype,
List<Map<String, ?>> values, Map<String, Object> d, Map<String, Object> o) {
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
// 拼接主键
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) {
String targetColumnName = entry.getKey();
String srcColumnName = entry.getValue();
if (srcColumnName == null) {
srcColumnName = Util.cleanColumn(targetColumnName);
}
sql.append(backtick).append(targetColumnName).append(backtick).append("=? AND ");
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)) {
BatchExecutor.setValue(values, type, o.get(srcColumnName));
} else {
BatchExecutor.setValue(values, type, d.get(srcColumnName));
}
}
int len = sql.length();
sql.delete(len - 4, len);
}
/**
* make sure the same table in one index
*
* @param dbMapping
* @return
*/
private int mappingHash(MappingConfig.DbMapping dbMapping) {
int hash = dbMapping.getDatabase().toLowerCase().hashCode() + dbMapping.getTable().toLowerCase().hashCode();
hash = Math.abs(hash) % threads;
return Math.abs(hash);
}
public void close() {
for (int i = 0; i < threads; i++) {
executorThreads[i].shutdown();
}
}
}

View File

@@ -0,0 +1,199 @@
package com.alibaba.otter.canal.client.adapter.clickhouse.service;
import java.sql.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig.DbMapping;
import com.alibaba.otter.canal.client.adapter.clickhouse.support.SyncUtil;
import com.alibaba.otter.canal.client.adapter.support.AbstractEtlService;
import com.alibaba.otter.canal.client.adapter.support.AdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.Util;
/**
* ClickHouse ETL 操作业务类
*
* @author: Xander
* @date: Created in 2023/11/10 22:23
* @email: zhrunxin33@gmail.com
* @version 1.1.8
*/
public class ClickHouseEtlService extends AbstractEtlService {
private DataSource targetDS;
private MappingConfig config;
public ClickHouseEtlService(DataSource targetDS, MappingConfig config){
super("CLICKHOUSE", config);
this.targetDS = targetDS;
this.config = config;
}
/**
* 导入数据
*/
public EtlResult importData(List<String> params) {
DbMapping dbMapping = config.getDbMapping();
String sql = "SELECT * FROM " + dbMapping.getDatabase() + "." + dbMapping.getTable();
return importData(sql, params);
}
/**
* 执行导入
*/
protected boolean executeSqlImport(DataSource srcDS, String sql, List<Object> values,
AdapterConfig.AdapterMapping mapping, AtomicLong impCount, List<String> errMsg) {
try {
DbMapping dbMapping = (DbMapping) mapping;
Map<String, String> columnsMap = new LinkedHashMap<>();
Map<String, Integer> columnType = new LinkedHashMap<>();
DruidDataSource dataSource = (DruidDataSource) srcDS;
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
Util.sqlRS(targetDS,
"SELECT * FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " LIMIT 1 ",
rs -> {
try {
ResultSetMetaData rsd = rs.getMetaData();
int columnCount = rsd.getColumnCount();
List<String> columns = new ArrayList<>();
for (int i = 1; i <= columnCount; i++) {
columnType.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i));
columns.add(rsd.getColumnName(i));
}
columnsMap.putAll(SyncUtil.getColumnsMap(dbMapping, columns));
return true;
} catch (Exception e) {
logger.error(e.getMessage(), e);
return false;
}
});
Util.sqlRS(srcDS, sql, values, rs -> {
int idx = 1;
try {
boolean completed = false;
StringBuilder insertSql = new StringBuilder();
insertSql.append("INSERT INTO ")
.append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()))
.append(" (");
columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(backtick)
.append(targetColumnName)
.append(backtick)
.append(","));
int len = insertSql.length();
insertSql.delete(len - 1, len).append(") VALUES (");
int mapLen = columnsMap.size();
for (int i = 0; i < mapLen; i++) {
insertSql.append("?,");
}
len = insertSql.length();
insertSql.delete(len - 1, len).append(")");
logger.info("executeSqlImport sql:{}", insertSql.toString());
try (Connection connTarget = targetDS.getConnection();
PreparedStatement pstmt = connTarget.prepareStatement(insertSql.toString())) {
connTarget.setAutoCommit(false);
while (rs.next()) {
completed = false;
pstmt.clearParameters();
// 删除数据
Map<String, Object> pkVal = new LinkedHashMap<>();
StringBuilder deleteSql = new StringBuilder(
"ALTER TABLE " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())
+ " DELETE WHERE ");
appendCondition(dbMapping, deleteSql, pkVal, rs, backtick);
try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) {
int k = 1;
for (Object val : pkVal.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++;
impCount.incrementAndGet();
if (logger.isDebugEnabled()) {
logger.debug("successful import count:" + impCount.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;
} catch (Exception e) {
logger.error(e.getMessage(), e);
return false;
}
}
/**
* 拼接目标表主键where条件
*/
private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map<String, Object> values,
ResultSet rs, String backtick) throws SQLException {
// 拼接主键
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) {
String targetColumnName = entry.getKey();
String srcColumnName = entry.getValue();
if (srcColumnName == null) {
srcColumnName = targetColumnName;
}
sql.append(backtick).append(targetColumnName).append(backtick).append("=? AND ");
values.put(targetColumnName, rs.getObject(srcColumnName));
}
int len = sql.length();
sql.delete(len - 4, len);
}
}

View File

@@ -0,0 +1,180 @@
package com.alibaba.otter.canal.client.adapter.clickhouse.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 org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MirrorDbConfig;
import com.alibaba.otter.canal.client.adapter.clickhouse.support.SyncUtil;
import com.alibaba.otter.canal.client.adapter.support.Dml;
/**
* CLICKHOUSE镜像库同步操作业务
*
* @author: Xander
* @date: Created in 2023/11/10 22:23
* @email: zhrunxin33@gmail.com
* @version 1.1.8
*/
public class ClickHouseMirrorDbBatchSyncService {
private static final Logger logger = LoggerFactory.getLogger(ClickHouseMirrorDbBatchSyncService.class);
// 镜像库配置
private Map<String, MirrorDbConfig> mirrorDbConfigCache;
private DruidDataSource dataSource;
// clickhouseSyncService代理
private ClickHouseBatchSyncService clickHouseBatchSyncService;
public ClickHouseMirrorDbBatchSyncService(Map<String, MirrorDbConfig> mirrorDbConfigCache,
DruidDataSource dataSource, Integer threads, Integer batchSize,
Long scheduleTime, Map<String, Map<String, Integer>> columnsTypeCache,
boolean skipDupException){
this.mirrorDbConfigCache = mirrorDbConfigCache;
this.dataSource = dataSource;
this.clickHouseBatchSyncService = new ClickHouseBatchSyncService(dataSource,
threads,
batchSize,
scheduleTime,
columnsTypeCache,
skipDupException);
}
/**
* 批量同步方法
*
* @param dmls 批量 DML,包含DDL
*/
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 (mirrorDbConfig.getMappingConfig() == null) {
continue;
}
if (dml.getGroupId() != null && StringUtils.isNotEmpty(mirrorDbConfig.getMappingConfig().getGroupId())) {
if (!mirrorDbConfig.getMappingConfig().getGroupId().equals(dml.getGroupId())) {
continue; // 如果groupId不匹配则过滤
}
}
if (dml.getIsDdl() != null && dml.getIsDdl() && StringUtils.isNotEmpty(dml.getSql())) {
// 确保执行DDL前DML已执行完
syncDml(dmlList);
dmlList.clear();
// DDL
if (logger.isDebugEnabled()) {
logger.debug("DDL: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
executeDdl(mirrorDbConfig, dml);
clickHouseBatchSyncService.getColumnsTypeCache()
.remove(destination + "." + database + "." + dml.getTable());
mirrorDbConfig.getTableConfig().remove(dml.getTable()); // 删除对应库表配置
} else {
// DML
initMappingConfig(dml.getTable(), mirrorDbConfig.getMappingConfig(), mirrorDbConfig, dml);
dmlList.add(dml);
}
}
syncDml(dmlList);
}
/**
* 批量同步Dml
*
* @param dmlList Dml列表,不包含DDL
*/
private void syncDml(List<Dml> dmlList) {
if (dmlList == null || dmlList.isEmpty()) {
return;
}
clickHouseBatchSyncService.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;
}
clickHouseBatchSyncService.appendDmlBufferPartition(config, dml);
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.setGroupId(baseConfigMap.getGroupId());
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()) {
// 替换反引号
String sql = ddl.getSql();
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
if (!"`".equals(backtick)) {
sql = sql.replaceAll("`", backtick);
}
statement.execute(sql);
// 移除对应配置
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);
}
}
}

View File

@@ -0,0 +1,113 @@
package com.alibaba.otter.canal.client.adapter.clickhouse.support;
import java.io.Closeable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* sql批量执行器
*
* @author rewerma 2018-11-7 下午06:45:49
* @version 1.0.0
*/
public class BatchExecutor implements Closeable {
private static final Logger logger = LoggerFactory.getLogger(BatchExecutor.class);
private DataSource dataSource;
private Connection conn;
private AtomicInteger idx = new AtomicInteger(0);
public BatchExecutor(DataSource dataSource){
this.dataSource = dataSource;
}
public Connection getConn() {
if (conn == null) {
try {
conn = dataSource.getConnection();
this.conn.setAutoCommit(false);
} catch (SQLException e) {
logger.error(e.getMessage(), e);
}
}
return conn;
}
public static void setValue(List<Map<String, ?>> values, int type, Object value) {
Map<String, Object> valueItem = new HashMap<>();
valueItem.put("type", type);
valueItem.put("value", value);
values.add(valueItem);
}
public void execute(String sql, List<Map<String, ?>> values) throws SQLException {
PreparedStatement pstmt = getConn().prepareStatement(sql);
int len = values.size();
for (int i = 0; i < len; i++) {
int type = (Integer) values.get(i).get("type");
Object value = values.get(i).get("value");
SyncUtil.setPStmt(type, pstmt, value, i + 1);
}
pstmt.execute();
idx.incrementAndGet();
pstmt.close();
}
public void batchExecute(String sql, List<List<Map<String, ?>>> batchValues) throws SQLException {
PreparedStatement pstmt = getConn().prepareStatement(sql);
for (int i = 0; i < batchValues.size(); i++) {
List<Map<String, ?>> values = batchValues.get(i);
for (int j = 0; j < values.size(); j++) {
int type = (Integer) values.get(j).get("type");
Object value = values.get(j).get("value");
SyncUtil.setPStmt(type, pstmt, value, j + 1);
}
pstmt.addBatch();
}
pstmt.executeBatch();
idx.addAndGet(batchValues.size());
pstmt.close();
}
public void commit() throws SQLException {
getConn().commit();
if (logger.isTraceEnabled()) {
logger.trace("Batch executor commit " + idx.get() + " rows");
}
idx.set(0);
}
public void rollback() throws SQLException {
getConn().rollback();
if (logger.isTraceEnabled()) {
logger.trace("Batch executor rollback " + idx.get() + " rows");
}
idx.set(0);
}
@Override
public void close() {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
logger.error(e.getMessage(), e);
} finally {
conn = null;
}
}
}
}

View File

@@ -0,0 +1,112 @@
package com.alibaba.otter.canal.client.adapter.clickhouse.support;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.util.LinkedCaseInsensitiveMap;
import com.alibaba.otter.canal.client.adapter.support.Dml;
public class SingleDml {
private String destination;
private String database;
private String table;
private String type;
private Map<String, Object> data;
private Map<String, Object> old;
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
public String getDatabase() {
return database;
}
public void setDatabase(String database) {
this.database = database;
}
public String getTable() {
return table;
}
public void setTable(String table) {
this.table = table;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Map<String, Object> getData() {
return data;
}
public void setData(Map<String, Object> data) {
this.data = data;
}
public Map<String, Object> getOld() {
return old;
}
public void setOld(Map<String, Object> old) {
this.old = old;
}
public static List<SingleDml> dml2SingleDmls(Dml dml, boolean caseInsensitive) {
List<SingleDml> singleDmls = new ArrayList<>();
if (dml.getData() != null) {
int size = dml.getData().size();
for (int i = 0; i < size; i++) {
SingleDml singleDml = new SingleDml();
singleDml.setDestination(dml.getDestination());
singleDml.setDatabase(dml.getDatabase());
singleDml.setTable(dml.getTable());
singleDml.setType(dml.getType());
Map<String, Object> data = dml.getData().get(i);
if (caseInsensitive) {
data = toCaseInsensitiveMap(data);
}
singleDml.setData(data);
if (dml.getOld() != null) {
Map<String, Object> oldData = dml.getOld().get(i);
if (caseInsensitive) {
oldData = toCaseInsensitiveMap(oldData);
}
singleDml.setOld(oldData);
}
singleDmls.add(singleDml);
}
} else if ("TRUNCATE".equalsIgnoreCase(dml.getType())) {
SingleDml singleDml = new SingleDml();
singleDml.setDestination(dml.getDestination());
singleDml.setDatabase(dml.getDatabase());
singleDml.setTable(dml.getTable());
singleDml.setType(dml.getType());
singleDmls.add(singleDml);
}
return singleDmls;
}
public static List<SingleDml> dml2SingleDmls(Dml dml) {
return dml2SingleDmls(dml, false);
}
private static <V> LinkedCaseInsensitiveMap<V> toCaseInsensitiveMap(Map<String, V> data) {
LinkedCaseInsensitiveMap map = new LinkedCaseInsensitiveMap();
map.putAll(data);
return map;
}
}

View File

@@ -0,0 +1,293 @@
package com.alibaba.otter.canal.client.adapter.clickhouse.support;
import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.druid.DbType;
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.support.Util;
public class SyncUtil {
private static final Logger logger = LoggerFactory.getLogger(SyncUtil.class);
public static Map<String, String> getColumnsMap(MappingConfig.DbMapping dbMapping, Map<String, Object> data) {
return getColumnsMap(dbMapping, data.keySet());
}
public static Map<String, String> getColumnsMap(MappingConfig.DbMapping dbMapping, Collection<String> columns) {
Map<String, String> columnsMap;
if (dbMapping.getMapAll()) {
if (dbMapping.getAllMapColumns() != null) {
return dbMapping.getAllMapColumns();
}
columnsMap = new LinkedHashMap<>();
for (String srcColumn : columns) {
boolean flag = true;
if (dbMapping.getTargetColumns() != null) {
for (Map.Entry<String, String> entry : dbMapping.getTargetColumns().entrySet()) {
if (srcColumn.equals(entry.getValue())) {
columnsMap.put(entry.getKey(), srcColumn);
flag = false;
break;
}
}
}
if (flag) {
columnsMap.put(srcColumn, srcColumn);
}
}
dbMapping.setAllMapColumns(columnsMap);
} else {
columnsMap = dbMapping.getTargetColumns();
}
return columnsMap;
}
/**
* 设置 preparedStatement
*
* @param type sqlType
* @param pstmt 需要设置的preparedStatement
* @param value 值
* @param i 索引号
*/
public static void setPStmt(int type, PreparedStatement pstmt, Object value, int i) throws SQLException {
switch (type) {
case Types.BIT:
case Types.BOOLEAN:
if (value instanceof Boolean) {
pstmt.setBoolean(i, (Boolean) value);
} else if (value instanceof String) {
boolean v = !value.equals("0");
pstmt.setBoolean(i, v);
} else if (value instanceof Number) {
boolean v = ((Number) value).intValue() != 0;
pstmt.setBoolean(i, v);
} else {
pstmt.setNull(i, type);
}
break;
case Types.CHAR:
case Types.NCHAR:
case Types.VARCHAR:
case Types.LONGVARCHAR:
if (value instanceof String) {
pstmt.setString(i, (String) value);
} else if (value == null) {
pstmt.setNull(i, type);
} else {
pstmt.setString(i, value.toString());
}
break;
case Types.TINYINT:
// 向上提升一级,处理unsigned情况
if (value instanceof Number) {
pstmt.setShort(i, ((Number) value).shortValue());
} else if (value instanceof String) {
pstmt.setShort(i, Short.parseShort((String) value));
} else {
pstmt.setNull(i, type);
}
break;
case Types.SMALLINT:
if (value instanceof Number) {
pstmt.setInt(i, ((Number) value).intValue());
} else if (value instanceof String) {
pstmt.setInt(i, Integer.parseInt((String) value));
} else {
pstmt.setNull(i, type);
}
break;
case Types.INTEGER:
if (value instanceof Number) {
pstmt.setLong(i, ((Number) value).longValue());
} else if (value instanceof String) {
pstmt.setLong(i, Long.parseLong((String) value));
} else {
pstmt.setNull(i, type);
}
break;
case Types.BIGINT:
if (value instanceof Number) {
pstmt.setBigDecimal(i, new BigDecimal(value.toString()));
} else if (value instanceof String) {
pstmt.setBigDecimal(i, new BigDecimal(value.toString()));
} else {
pstmt.setNull(i, type);
}
break;
case Types.DECIMAL:
case Types.NUMERIC:
if (value instanceof BigDecimal) {
pstmt.setBigDecimal(i, (BigDecimal) value);
} else if (value instanceof Byte) {
pstmt.setInt(i, ((Byte) value).intValue());
} else if (value instanceof Short) {
pstmt.setInt(i, ((Short) value).intValue());
} else if (value instanceof Integer) {
pstmt.setInt(i, (Integer) value);
} else if (value instanceof Long) {
pstmt.setLong(i, (Long) value);
} else if (value instanceof Float) {
pstmt.setBigDecimal(i, new BigDecimal((float) value));
} else if (value instanceof Double) {
pstmt.setBigDecimal(i, new BigDecimal((double) value));
} else if (value != null) {
pstmt.setBigDecimal(i, new BigDecimal(value.toString()));
} else {
pstmt.setNull(i, type);
}
break;
case Types.REAL:
if (value instanceof Number) {
pstmt.setFloat(i, ((Number) value).floatValue());
} else if (value instanceof String) {
pstmt.setFloat(i, Float.parseFloat((String) value));
} else {
pstmt.setNull(i, type);
}
break;
case Types.FLOAT:
case Types.DOUBLE:
if (value instanceof Number) {
pstmt.setDouble(i, ((Number) value).doubleValue());
} else if (value instanceof String) {
pstmt.setDouble(i, Double.parseDouble((String) value));
} else {
pstmt.setNull(i, type);
}
break;
case Types.BINARY:
case Types.VARBINARY:
case Types.LONGVARBINARY:
case Types.BLOB:
if (value instanceof Blob) {
pstmt.setBlob(i, (Blob) value);
} else if (value instanceof byte[]) {
pstmt.setBytes(i, (byte[]) value);
} else if (value instanceof String) {
pstmt.setBytes(i, ((String) value).getBytes(StandardCharsets.ISO_8859_1));
} else {
pstmt.setNull(i, type);
}
break;
case Types.CLOB:
if (value instanceof Clob) {
pstmt.setClob(i, (Clob) value);
} else if (value instanceof byte[]) {
pstmt.setBytes(i, (byte[]) value);
} else if (value instanceof String) {
Reader clobReader = new StringReader((String) value);
pstmt.setCharacterStream(i, clobReader);
} else {
pstmt.setNull(i, type);
}
break;
case Types.DATE:
if (value instanceof Date) {
pstmt.setDate(i, (Date) value);
} else if (value instanceof java.util.Date) {
pstmt.setDate(i, new Date(((java.util.Date) value).getTime()));
} else if (value instanceof String) {
String v = (String) value;
if (!v.startsWith("0000-00-00")) {
java.util.Date date = Util.parseDate(v);
if (date != null) {
pstmt.setDate(i, new Date(date.getTime()));
} else {
pstmt.setNull(i, type);
}
} else {
pstmt.setObject(i, value);
}
} else {
pstmt.setNull(i, type);
}
break;
case Types.TIME:
if (value instanceof Time) {
pstmt.setTime(i, (Time) value);
} else if (value instanceof java.util.Date) {
pstmt.setTime(i, new Time(((java.util.Date) value).getTime()));
} else if (value instanceof String) {
String v = (String) value;
java.util.Date date = Util.parseDate(v);
if (date != null) {
pstmt.setTime(i, new Time(date.getTime()));
} else {
pstmt.setNull(i, type);
}
} else {
pstmt.setNull(i, type);
}
break;
case Types.TIMESTAMP:
if (value instanceof Timestamp) {
pstmt.setTimestamp(i, (Timestamp) value);
} else if (value instanceof java.util.Date) {
pstmt.setTimestamp(i, new Timestamp(((java.util.Date) value).getTime()));
} else if (value instanceof String) {
String v = (String) value;
if (!v.startsWith("0000-00-00")) {
java.util.Date date = Util.parseDate(v);
if (date != null) {
pstmt.setTimestamp(i, new Timestamp(date.getTime()));
} else {
pstmt.setNull(i, type);
}
} else {
pstmt.setObject(i, value);
}
} else {
pstmt.setNull(i, type);
}
break;
default:
pstmt.setObject(i, value, type);
}
}
public static String getDbTableName(MappingConfig.DbMapping dbMapping, String dbType) {
String result = "";
String backtick = getBacktickByDbType(dbType);
if (StringUtils.isNotEmpty(dbMapping.getTargetDb())) {
result += (backtick + dbMapping.getTargetDb() + backtick + ".");
}
result += (backtick + dbMapping.getTargetTable() + backtick);
return result;
}
/**
* 根据DbType返回反引号或空字符串
*
* @param dbTypeName DbType名称
* @return 反引号或空字符串
*/
public static String getBacktickByDbType(String dbTypeName) {
DbType dbType = DbType.of(dbTypeName);
if (dbType == null) {
dbType = DbType.other;
}
// 只有当dbType为MySQL/MariaDB或OceanBase时返回反引号
switch (dbType) {
case mysql:
case mariadb:
case oceanbase:
return "`";
default:
return "";
}
}
}

View File

@@ -0,0 +1 @@
clickhouse=com.alibaba.otter.canal.client.adapter.clickhouse.ClickHouseAdapter

View File

@@ -0,0 +1,31 @@
dataSourceKey: defaultDS
destination: example
groupId: g1
outerAdapterKey: clickhouse1
concurrent: true
dbMapping:
database: mytest
table: user
targetTable: mytest.user
targetPk:
id: id
# mapAll: true
targetColumns:
id:
name:
role_id:
c_time:
test1:
etlCondition: "where c_time>={}"
commitBatch: 3000 # 批量提交的大小
## Mirror schema synchronize config
#dataSourceKey: defaultDS
#destination: example
#groupId: g1
#outerAdapterKey: mysql1
#concurrent: true
#dbMapping:
# mirrorDb: true
# database: mytest

View File

@@ -0,0 +1,109 @@
package com.alibaba.otter.canal.client.adapter.clickhouse;
import java.util.*;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.alibaba.otter.canal.client.adapter.clickhouse.sync.Common;
import com.alibaba.otter.canal.client.adapter.support.Dml;
@Ignore
public class ClickHouseBatchSyncServiceTest {
private ClickHouseAdapter clickHouseAdapter;
@Before
public void init() {
clickHouseAdapter = Common.init();
}
@Test
public void insert() throws InterruptedException {
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("INSERT");
dml.setDatabase("mytest");
dml.setTable("user");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("name", "Eric");
data.put("role_id", 1L);
data.put("c_time", new Date());
data.put("test1", "sdfasdfawe中国asfwef");
dml.setData(dataList);
clickHouseAdapter.sync(Collections.singletonList(dml));
Thread.sleep(10000L);
}
@Test
public void update() {
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("UPDATE");
dml.setDatabase("mytest");
dml.setTable("user");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("name", "Eric2");
dml.setData(dataList);
List<Map<String, Object>> oldList = new ArrayList<>();
Map<String, Object> old = new LinkedHashMap<>();
oldList.add(old);
old.put("name", "Eric");
dml.setOld(oldList);
clickHouseAdapter.sync(Collections.singletonList(dml));
}
@Test
public void delete() {
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("DELETE");
dml.setDatabase("mytest");
dml.setTable("user");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("name", "Eric2");
dml.setData(dataList);
clickHouseAdapter.sync(Collections.singletonList(dml));
}
@Test
public void truncate() throws InterruptedException {
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("TRUNCATE");
dml.setDatabase("mytest");
dml.setTable("user");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("name", "Eric2");
dml.setData(dataList);
List<Map<String, Object>> oldList = new ArrayList<>();
Map<String, Object> old = new LinkedHashMap<>();
oldList.add(old);
old.put("name", "Eric");
dml.setOld(oldList);
clickHouseAdapter.sync(Collections.singletonList(dml));
Thread.sleep(1000L);
}
}

View File

@@ -0,0 +1,81 @@
package com.alibaba.otter.canal.client.adapter.clickhouse;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.alibaba.otter.canal.client.adapter.clickhouse.sync.Common;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import ch.qos.logback.classic.Level;
/**
* @author: Xander
* @date: Created in 2023/11/13 22:27
* @email: zhrunxin33@gmail.com
* @description: Testing thread safe
*/
@Ignore
public class ClickHouseBatchSyncThreadSafeTest {
private ClickHouseAdapter clickHouseAdapter;
private ExecutorService executorService;
private String[] operations = new String[] { "INSERT", "UPDATE" };
private String[] tables = new String[] { "user", "customer" };
@Before
public void init() {
clickHouseAdapter = Common.init();
Common.setLogLevel(Level.INFO);
executorService = Executors.newFixedThreadPool(5);
}
@Test
public void test01() throws InterruptedException, ExecutionException {
ArrayList<Future> list = new ArrayList();
AtomicInteger count = new AtomicInteger();
for (int i = 0; i < 10; i++) {
list.add(executorService.submit(() -> {
for (int j = 0; j < 300; j++) {
Random random = new Random();
int cou = count.incrementAndGet();
// test insert
String dmlType = operations[random.nextInt(1)];
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType(dmlType);
dml.setDatabase("mytest");
dml.setTable(tables[(int) Math.round(Math.random())]);
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", cou);
data.put("name", "Eric" + cou);
data.put("role_id", cou);
data.put("c_time", new Date());
data.put("test1", "sdfasdfawe中国asfwef");
dml.setData(dataList);
clickHouseAdapter.sync(Collections.singletonList(dml));
}
}));
}
for (Future future : list) {
future.get();
}
Thread.sleep(10000L); // waiting multiple threads execute successfully.
}
}

View File

@@ -0,0 +1,37 @@
package com.alibaba.otter.canal.client.adapter.clickhouse;
import java.sql.SQLException;
import com.alibaba.druid.pool.DruidDataSource;
public class TestConstant {
public final static String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true";
public final static String jdbcUser = "root";
public final static String jdbcPassword = "121212";
public final static DruidDataSource dataSource;
static {
dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl(jdbcUrl);
dataSource.setUsername(jdbcUser);
dataSource.setPassword(jdbcPassword);
dataSource.setInitialSize(1);
dataSource.setMinIdle(1);
dataSource.setMaxActive(1);
dataSource.setMaxWait(60000);
dataSource.setTimeBetweenEvictionRunsMillis(60000);
dataSource.setMinEvictableIdleTimeMillis(300000);
dataSource.setPoolPreparedStatements(false);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
dataSource.setValidationQuery("select 1");
try {
dataSource.init();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,51 @@
package com.alibaba.otter.canal.client.adapter.clickhouse.sync;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.clickhouse.ClickHouseAdapter;
import com.alibaba.otter.canal.client.adapter.clickhouse.TestConstant;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
/**
* @author: Xander
* @date: Created in 2023/11/13 0:16
* @email: zhrunxin33@gmail.com @description:
*/
public class Common {
public static ClickHouseAdapter init() {
DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource);
OuterAdapterConfig outerAdapterConfig = new OuterAdapterConfig();
outerAdapterConfig.setName("clickhouse");
outerAdapterConfig.setKey("clickhouse1");
Map<String, String> properties = new HashMap<>();
properties.put("jdbc.driveClassName", "ru.yandex.clickhouse.ClickHouseDriver");
properties.put("jdbc.url", "jdbc:clickhouse://127.0.0.1:8123/default");
properties.put("jdbc.username", "default");
properties.put("jdbc.password", "123456");
outerAdapterConfig.setProperties(properties);
ClickHouseAdapter adapter = new ClickHouseAdapter();
adapter.init(outerAdapterConfig, null);
return adapter;
}
/**
* set global log level
*
* @param logLevel
*/
public static void setLogLevel(Level logLevel) {
ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory
.getLogger(Logger.ROOT_LOGGER_NAME);
logger.setLevel(logLevel);
}
}

View File

@@ -0,0 +1,31 @@
dataSourceKey: defaultDS
destination: example
groupId: g1
outerAdapterKey: clickhouse1
concurrent: true
dbMapping:
database: mytest
table: customer
targetTable: mytest.customer
targetPk:
id: id
# mapAll: true
targetColumns:
id:
name:
role_id:
c_time:
test1:
etlCondition: "where c_time>={}"
commitBatch: 3000 # 批量提交的大小
## Mirror schema synchronize config
#dataSourceKey: defaultDS
#destination: example
#groupId: g1
#outerAdapterKey: mysql1
#concurrent: true
#dbMapping:
# mirrorDb: true
# database: mytest

View File

@@ -0,0 +1,31 @@
dataSourceKey: defaultDS
destination: example
groupId: g1
outerAdapterKey: clickhouse1
concurrent: true
dbMapping:
database: mytest
table: user
targetTable: mytest.user
targetPk:
id: id
# mapAll: true
targetColumns:
id:
name:
role_id:
c_time:
test1:
etlCondition: "where c_time>={}"
commitBatch: 3000 # 批量提交的大小
## Mirror schema synchronize config
#dataSourceKey: defaultDS
#destination: example
#groupId: g1
#outerAdapterKey: mysql1
#concurrent: true
#dbMapping:
# mirrorDb: true
# database: mytest

View File

@@ -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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -54,6 +54,11 @@
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>com.diffblue</groupId> <groupId>com.diffblue</groupId>
<artifactId>deeptestutils</artifactId> <artifactId>deeptestutils</artifactId>

View File

@@ -1,14 +1,17 @@
package com.alibaba.otter.canal.client.adapter.support; package com.alibaba.otter.canal.client.adapter.support;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Value;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import org.junit.Assert;
import org.junit.Test;
public class YamlUtilsTest { public class YamlUtilsTest {
@Test @Test
public void testLoadConfigToYml() { public void testLoadConfigToYml() {
String configStr="dataSourceKey: defaultDS\n" String configStr="dataSourceKey: defaultDS\n"
@@ -17,6 +20,7 @@ public class YamlUtilsTest {
+ "outerAdapterKey: mysql1\n" + "outerAdapterKey: mysql1\n"
+ "concurrent: true\n" + "concurrent: true\n"
+ "dbMapping:\n" + "dbMapping:\n"
+ " _id: _id\n"
+ " database: mytest\n" + " database: mytest\n"
+ " table: user\n" + " table: user\n"
+ " targetTable: mytest2.user\n" + " targetTable: mytest2.user\n"
@@ -35,6 +39,7 @@ public class YamlUtilsTest {
MappingConfig config = YamlUtils.ymlToObj(null, configStr, MappingConfig.class, null, new Properties()); MappingConfig config = YamlUtils.ymlToObj(null, configStr, MappingConfig.class, null, new Properties());
Assert.assertNotNull(config); Assert.assertNotNull(config);
Assert.assertEquals(config.getDbMapping().getId(), "_id");
Assert.assertEquals(config.getDestination(), "example"); Assert.assertEquals(config.getDestination(), "example");
Assert.assertEquals(config.getOuterAdapterKey(), "mysql1"); Assert.assertEquals(config.getOuterAdapterKey(), "mysql1");
Assert.assertEquals(config.getDbMapping().getDatabase(), "mytest"); Assert.assertEquals(config.getDbMapping().getDatabase(), "mytest");
@@ -104,6 +109,9 @@ public class YamlUtilsTest {
} }
private static class DbMapping { private static class DbMapping {
@Value("${_id}")
private String id ;
private boolean mirrorDb = false; // 是否镜像库 private boolean mirrorDb = false; // 是否镜像库
private String database; // 数据库名或schema名 private String database; // 数据库名或schema名
private String table; // 表名 private String table; // 表名
@@ -225,5 +233,13 @@ public class YamlUtilsTest {
public void setAllMapColumns(Map<String, String> allMapColumns) { public void setAllMapColumns(Map<String, String> allMapColumns) {
this.allMapColumns = allMapColumns; this.allMapColumns = allMapColumns;
} }
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
} }
} }

View File

@@ -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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -59,12 +59,12 @@ public class ES6xAdapter extends ESAdapter {
public Map<String, Object> count(String task) { public Map<String, Object> count(String task) {
ESSyncConfig config = esSyncConfig.get(task); ESSyncConfig config = esSyncConfig.get(task);
ESSyncConfig.ESMapping mapping = config.getEsMapping(); ESSyncConfig.ESMapping mapping = config.getEsMapping();
SearchResponse response = this.esConnection.new ESSearchRequest(mapping.get_index(), mapping.get_type()).size(0) SearchResponse response = this.esConnection.new ESSearchRequest(mapping.getIndex(), mapping.getType()).size(0)
.getResponse(); .getResponse();
long rowCount = response.getHits().getTotalHits(); long rowCount = response.getHits().getTotalHits();
Map<String, Object> res = new LinkedHashMap<>(); Map<String, Object> res = new LinkedHashMap<>();
res.put("esIndex", mapping.get_index()); res.put("esIndex", mapping.getIndex());
res.put("count", rowCount); res.put("count", rowCount);
return res; return res;
} }

View File

@@ -51,7 +51,7 @@ public class ESEtlService extends AbstractEtlService {
public EtlResult importData(List<String> params) { public EtlResult importData(List<String> params) {
ESMapping mapping = config.getEsMapping(); ESMapping mapping = config.getEsMapping();
logger.info("start etl to import data to index: {}", mapping.get_index()); logger.info("start etl to import data to index: {}", mapping.getIndex());
String sql = mapping.getSql(); String sql = mapping.getSql();
return importData(sql, params); return importData(sql, params);
} }
@@ -78,7 +78,7 @@ public class ESEtlService extends AbstractEtlService {
} }
// 如果是主键字段则不插入 // 如果是主键字段则不插入
if (fieldItem.getFieldName().equals(mapping.get_id())) { if (fieldItem.getFieldName().equals(mapping.getId())) {
idVal = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName); idVal = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName);
} else { } else {
Object val = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName); Object val = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName);
@@ -117,8 +117,8 @@ public class ESEtlService extends AbstractEtlService {
if (idVal != null) { if (idVal != null) {
String parentVal = (String) esFieldData.remove("$parent_routing"); String parentVal = (String) esFieldData.remove("$parent_routing");
if (mapping.isUpsert()) { if (mapping.isUpsert()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES6xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES6xUpdateRequest(mapping.getIndex(),
mapping.get_type(), mapping.getType(),
idVal.toString()).setDoc(esFieldData).setDocAsUpsert(true); idVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
@@ -127,8 +127,8 @@ public class ESEtlService extends AbstractEtlService {
esBulkRequest.add(esUpdateRequest); esBulkRequest.add(esUpdateRequest);
} else { } else {
ESIndexRequest esIndexRequest = this.esConnection.new ES6xIndexRequest(mapping.get_index(), ESIndexRequest esIndexRequest = this.esConnection.new ES6xIndexRequest(mapping.getIndex(),
mapping.get_type(), mapping.getType(),
idVal.toString()).setSource(esFieldData); idVal.toString()).setSource(esFieldData);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
esIndexRequest.setRouting(parentVal); esIndexRequest.setRouting(parentVal);
@@ -137,12 +137,12 @@ public class ESEtlService extends AbstractEtlService {
} }
} else { } else {
idVal = esFieldData.get(mapping.getPk()); idVal = esFieldData.get(mapping.getPk());
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.get_index(), ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex(),
mapping.get_type()).setQuery(QueryBuilders.termQuery(mapping.getPk(), idVal)).size(10000); mapping.getType()).setQuery(QueryBuilders.termQuery(mapping.getPk(), idVal)).size(10000);
SearchResponse response = esSearchRequest.getResponse(); SearchResponse response = esSearchRequest.getResponse();
for (SearchHit hit : response.getHits()) { for (SearchHit hit : response.getHits()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES6xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES6xUpdateRequest(mapping.getIndex(),
mapping.get_type(), mapping.getType(),
hit.getId()).setDoc(esFieldData); hit.getId()).setDoc(esFieldData);
esBulkRequest.add(esUpdateRequest); esBulkRequest.add(esUpdateRequest);
} }
@@ -160,7 +160,7 @@ public class ESEtlService extends AbstractEtlService {
(System.currentTimeMillis() - batchBegin), (System.currentTimeMillis() - batchBegin),
(System.currentTimeMillis() - esBatchBegin), (System.currentTimeMillis() - esBatchBegin),
esBulkRequest.numberOfActions(), esBulkRequest.numberOfActions(),
mapping.get_index()); mapping.getIndex());
} }
batchBegin = System.currentTimeMillis(); batchBegin = System.currentTimeMillis();
esBulkRequest.resetBulk(); esBulkRequest.resetBulk();
@@ -180,12 +180,12 @@ public class ESEtlService extends AbstractEtlService {
(System.currentTimeMillis() - batchBegin), (System.currentTimeMillis() - batchBegin),
(System.currentTimeMillis() - esBatchBegin), (System.currentTimeMillis() - esBatchBegin),
esBulkRequest.numberOfActions(), esBulkRequest.numberOfActions(),
mapping.get_index()); mapping.getIndex());
} }
} }
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
errMsg.add(mapping.get_index() + " etl failed! ==>" + e.getMessage()); errMsg.add(mapping.getIndex() + " etl failed! ==>" + e.getMessage());
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return count; return count;

View File

@@ -66,19 +66,19 @@ public class ES6xTemplate implements ESTemplate {
@Override @Override
public void insert(ESSyncConfig.ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) { public void insert(ESSyncConfig.ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
if (mapping.get_id() != null) { if (mapping.getId() != null) {
String parentVal = (String) esFieldData.remove("$parent_routing"); String parentVal = (String) esFieldData.remove("$parent_routing");
if (mapping.isUpsert()) { if (mapping.isUpsert()) {
ESUpdateRequest updateRequest = esConnection.new ES6xUpdateRequest(mapping.get_index(), ESUpdateRequest updateRequest = esConnection.new ES6xUpdateRequest(mapping.getIndex(),
mapping.get_type(), mapping.getType(),
pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true); pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
updateRequest.setRouting(parentVal); updateRequest.setRouting(parentVal);
} }
getBulk().add(updateRequest); getBulk().add(updateRequest);
} else { } else {
ESIndexRequest indexRequest = esConnection.new ES6xIndexRequest(mapping.get_index(), ESIndexRequest indexRequest = esConnection.new ES6xIndexRequest(mapping.getIndex(),
mapping.get_type(), mapping.getType(),
pkVal.toString()).setSource(esFieldData); pkVal.toString()).setSource(esFieldData);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
indexRequest.setRouting(parentVal); indexRequest.setRouting(parentVal);
@@ -87,13 +87,13 @@ public class ES6xTemplate implements ESTemplate {
} }
commitBulk(); commitBulk();
} else { } else {
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.get_index(), ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex(),
mapping.get_type()).setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal)).size(10000); mapping.getType()).setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal)).size(10000);
SearchResponse response = esSearchRequest.getResponse(); SearchResponse response = esSearchRequest.getResponse();
for (SearchHit hit : response.getHits()) { for (SearchHit hit : response.getHits()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES6xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES6xUpdateRequest(mapping.getIndex(),
mapping.get_type(), mapping.getType(),
hit.getId()).setDoc(esFieldData); hit.getId()).setDoc(esFieldData);
getBulk().add(esUpdateRequest); getBulk().add(esUpdateRequest);
commitBulk(); commitBulk();
@@ -150,19 +150,19 @@ public class ES6xTemplate implements ESTemplate {
@Override @Override
public void delete(ESSyncConfig.ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) { public void delete(ESSyncConfig.ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
if (mapping.get_id() != null) { if (mapping.getId() != null) {
ESDeleteRequest esDeleteRequest = this.esConnection.new ES6xDeleteRequest(mapping.get_index(), ESDeleteRequest esDeleteRequest = this.esConnection.new ES6xDeleteRequest(mapping.getIndex(),
mapping.get_type(), mapping.getType(),
pkVal.toString()); pkVal.toString());
getBulk().add(esDeleteRequest); getBulk().add(esDeleteRequest);
commitBulk(); commitBulk();
} else { } else {
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.get_index(), ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex(),
mapping.get_type()).setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal)).size(10000); mapping.getType()).setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal)).size(10000);
SearchResponse response = esSearchRequest.getResponse(); SearchResponse response = esSearchRequest.getResponse();
for (SearchHit hit : response.getHits()) { for (SearchHit hit : response.getHits()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES6xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES6xUpdateRequest(mapping.getIndex(),
mapping.get_type(), mapping.getType(),
hit.getId()).setDoc(esFieldData); hit.getId()).setDoc(esFieldData);
getBulk().add(esUpdateRequest); getBulk().add(esUpdateRequest);
commitBulk(); commitBulk();
@@ -207,7 +207,7 @@ public class ES6xTemplate implements ESTemplate {
public Object getESDataFromRS(ESSyncConfig.ESMapping mapping, ResultSet resultSet, public Object getESDataFromRS(ESSyncConfig.ESMapping mapping, ResultSet resultSet,
Map<String, Object> esFieldData) throws SQLException { Map<String, Object> esFieldData) throws SQLException {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName()); Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
@@ -216,7 +216,7 @@ public class ES6xTemplate implements ESTemplate {
resultIdVal = value; resultIdVal = value;
} }
if (!fieldItem.getFieldName().equals(mapping.get_id()) if (!fieldItem.getFieldName().equals(mapping.getId())
&& !mapping.getSkips().contains(fieldItem.getFieldName())) { && !mapping.getSkips().contains(fieldItem.getFieldName())) {
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value); esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
} }
@@ -231,7 +231,7 @@ public class ES6xTemplate implements ESTemplate {
@Override @Override
public Object getIdValFromRS(ESSyncConfig.ESMapping mapping, ResultSet resultSet) throws SQLException { public Object getIdValFromRS(ESSyncConfig.ESMapping mapping, ResultSet resultSet) throws SQLException {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName()); Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
@@ -248,7 +248,7 @@ public class ES6xTemplate implements ESTemplate {
public Object getESDataFromRS(ESSyncConfig.ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld, public Object getESDataFromRS(ESSyncConfig.ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld,
Map<String, Object> esFieldData) throws SQLException { Map<String, Object> esFieldData) throws SQLException {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
if (fieldItem.getFieldName().equals(idFieldName)) { if (fieldItem.getFieldName().equals(idFieldName)) {
@@ -294,7 +294,7 @@ public class ES6xTemplate implements ESTemplate {
public Object getESDataFromDmlData(ESSyncConfig.ESMapping mapping, Map<String, Object> dmlData, public Object getESDataFromDmlData(ESSyncConfig.ESMapping mapping, Map<String, Object> dmlData,
Map<String, Object> esFieldData) { Map<String, Object> esFieldData) {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
String columnName = fieldItem.getColumnItems().iterator().next().getColumnName(); String columnName = fieldItem.getColumnItems().iterator().next().getColumnName();
@@ -304,7 +304,7 @@ public class ES6xTemplate implements ESTemplate {
resultIdVal = value; resultIdVal = value;
} }
if (!fieldItem.getFieldName().equals(mapping.get_id()) if (!fieldItem.getFieldName().equals(mapping.getId())
&& !mapping.getSkips().contains(fieldItem.getFieldName())) { && !mapping.getSkips().contains(fieldItem.getFieldName())) {
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value); esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
} }
@@ -319,7 +319,7 @@ public class ES6xTemplate implements ESTemplate {
public Object getESDataFromDmlData(ESSyncConfig.ESMapping mapping,String owner, Map<String, Object> dmlData, public Object getESDataFromDmlData(ESSyncConfig.ESMapping mapping,String owner, Map<String, Object> dmlData,
Map<String, Object> dmlOld, Map<String, Object> esFieldData) { Map<String, Object> dmlOld, Map<String, Object> esFieldData) {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
ColumnItem columnItem = fieldItem.getColumnItems().iterator().next(); ColumnItem columnItem = fieldItem.getColumnItems().iterator().next();
@@ -353,19 +353,19 @@ public class ES6xTemplate implements ESTemplate {
} }
private void append4Update(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) { private void append4Update(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
if (mapping.get_id() != null) { if (mapping.getId() != null) {
String parentVal = (String) esFieldData.remove("$parent_routing"); String parentVal = (String) esFieldData.remove("$parent_routing");
if (mapping.isUpsert()) { if (mapping.isUpsert()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES6xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES6xUpdateRequest(mapping.getIndex(),
mapping.get_type(), mapping.getType(),
pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true); pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
esUpdateRequest.setRouting(parentVal); esUpdateRequest.setRouting(parentVal);
} }
getBulk().add(esUpdateRequest); getBulk().add(esUpdateRequest);
} else { } else {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES6xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES6xUpdateRequest(mapping.getIndex(),
mapping.get_type(), mapping.getType(),
pkVal.toString()).setDoc(esFieldData); pkVal.toString()).setDoc(esFieldData);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
esUpdateRequest.setRouting(parentVal); esUpdateRequest.setRouting(parentVal);
@@ -373,12 +373,12 @@ public class ES6xTemplate implements ESTemplate {
getBulk().add(esUpdateRequest); getBulk().add(esUpdateRequest);
} }
} else { } else {
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.get_index(), ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex(),
mapping.get_type()).setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal)).size(10000); mapping.getType()).setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal)).size(10000);
SearchResponse response = esSearchRequest.getResponse(); SearchResponse response = esSearchRequest.getResponse();
for (SearchHit hit : response.getHits()) { for (SearchHit hit : response.getHits()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES6xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES6xUpdateRequest(mapping.getIndex(),
mapping.get_type(), mapping.getType(),
hit.getId()).setDoc(esFieldData); hit.getId()).setDoc(esFieldData);
getBulk().add(esUpdateRequest); getBulk().add(esUpdateRequest);
} }
@@ -394,15 +394,15 @@ public class ES6xTemplate implements ESTemplate {
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private String getEsType(ESMapping mapping, String fieldName) { private String getEsType(ESMapping mapping, String fieldName) {
String key = mapping.get_index() + "-" + mapping.get_type(); String key = mapping.getIndex() + "-" + mapping.getType();
Map<String, String> fieldType = esFieldTypes.get(key); Map<String, String> fieldType = esFieldTypes.get(key);
if (fieldType != null) { if (fieldType != null) {
return fieldType.get(fieldName); return fieldType.get(fieldName);
} else { } else {
MappingMetaData mappingMetaData = esConnection.getMapping(mapping.get_index(), mapping.get_type()); MappingMetaData mappingMetaData = esConnection.getMapping(mapping.getIndex(), mapping.getType());
if (mappingMetaData == null) { if (mappingMetaData == null) {
throw new IllegalArgumentException("Not found the mapping info of index: " + mapping.get_index()); throw new IllegalArgumentException("Not found the mapping info of index: " + mapping.getIndex());
} }
fieldType = new LinkedHashMap<>(); fieldType = new LinkedHashMap<>();

View File

@@ -29,9 +29,9 @@ public class ConfigLoadTest {
Assert.assertNotNull(config); Assert.assertNotNull(config);
Assert.assertEquals("defaultDS", config.getDataSourceKey()); Assert.assertEquals("defaultDS", config.getDataSourceKey());
ESSyncConfig.ESMapping esMapping = config.getEsMapping(); ESSyncConfig.ESMapping esMapping = config.getEsMapping();
Assert.assertEquals("mytest_user", esMapping.get_index()); Assert.assertEquals("mytest_user", esMapping.getIndex());
Assert.assertEquals("_doc", esMapping.get_type()); Assert.assertEquals("_doc", esMapping.getType());
Assert.assertEquals("id", esMapping.get_id()); Assert.assertEquals("id", esMapping.getId());
Assert.assertNotNull(esMapping.getSql()); Assert.assertNotNull(esMapping.getSql());
// Map<String, List<ESSyncConfig>> dbTableEsSyncConfig = // Map<String, List<ESSyncConfig>> dbTableEsSyncConfig =

View File

@@ -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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -59,11 +59,11 @@ public class ES7xAdapter extends ESAdapter {
public Map<String, Object> count(String task) { public Map<String, Object> count(String task) {
ESSyncConfig config = esSyncConfig.get(task); ESSyncConfig config = esSyncConfig.get(task);
ESSyncConfig.ESMapping mapping = config.getEsMapping(); ESSyncConfig.ESMapping mapping = config.getEsMapping();
SearchResponse response = this.esConnection.new ESSearchRequest(mapping.get_index()).size(0).getResponse(); SearchResponse response = this.esConnection.new ESSearchRequest(mapping.getIndex()).size(0).getResponse();
long rowCount = response.getHits().getTotalHits().value; long rowCount = response.getHits().getTotalHits().value;
Map<String, Object> res = new LinkedHashMap<>(); Map<String, Object> res = new LinkedHashMap<>();
res.put("esIndex", mapping.get_index()); res.put("esIndex", mapping.getIndex());
res.put("count", rowCount); res.put("count", rowCount);
return res; return res;
} }

View File

@@ -51,7 +51,7 @@ public class ESEtlService extends AbstractEtlService {
public EtlResult importData(List<String> params) { public EtlResult importData(List<String> params) {
ESMapping mapping = config.getEsMapping(); ESMapping mapping = config.getEsMapping();
logger.info("start etl to import data to index: {}", mapping.get_index()); logger.info("start etl to import data to index: {}", mapping.getIndex());
String sql = mapping.getSql(); String sql = mapping.getSql();
return importData(sql, params); return importData(sql, params);
} }
@@ -78,7 +78,7 @@ public class ESEtlService extends AbstractEtlService {
} }
// 如果是主键字段则不插入 // 如果是主键字段则不插入
if (fieldItem.getFieldName().equals(mapping.get_id())) { if (fieldItem.getFieldName().equals(mapping.getId())) {
idVal = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName); idVal = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName);
} else { } else {
Object val = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName); Object val = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName);
@@ -118,7 +118,7 @@ public class ESEtlService extends AbstractEtlService {
String parentVal = (String) esFieldData.remove("$parent_routing"); String parentVal = (String) esFieldData.remove("$parent_routing");
if (mapping.isUpsert()) { if (mapping.isUpsert()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest( ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(
mapping.get_index(), mapping.getIndex(),
idVal.toString()).setDoc(esFieldData).setDocAsUpsert(true); idVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
@@ -128,7 +128,7 @@ public class ESEtlService extends AbstractEtlService {
esBulkRequest.add(esUpdateRequest); esBulkRequest.add(esUpdateRequest);
} else { } else {
ESIndexRequest esIndexRequest = this.esConnection.new ES7xIndexRequest( ESIndexRequest esIndexRequest = this.esConnection.new ES7xIndexRequest(
mapping.get_index(), mapping.getIndex(),
idVal.toString()).setSource(esFieldData); idVal.toString()).setSource(esFieldData);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
esIndexRequest.setRouting(parentVal); esIndexRequest.setRouting(parentVal);
@@ -137,13 +137,13 @@ public class ESEtlService extends AbstractEtlService {
} }
} else { } else {
idVal = esFieldData.get(mapping.getPk()); idVal = esFieldData.get(mapping.getPk());
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.get_index()) ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex())
.setQuery(QueryBuilders.termQuery(mapping.getPk(), idVal)) .setQuery(QueryBuilders.termQuery(mapping.getPk(), idVal))
.size(10000); .size(10000);
SearchResponse response = esSearchRequest.getResponse(); SearchResponse response = esSearchRequest.getResponse();
for (SearchHit hit : response.getHits()) { for (SearchHit hit : response.getHits()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest( ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(
mapping.get_index(), mapping.getIndex(),
hit.getId()).setDoc(esFieldData); hit.getId()).setDoc(esFieldData);
esBulkRequest.add(esUpdateRequest); esBulkRequest.add(esUpdateRequest);
} }
@@ -162,7 +162,7 @@ public class ESEtlService extends AbstractEtlService {
(System.currentTimeMillis() - batchBegin), (System.currentTimeMillis() - batchBegin),
(System.currentTimeMillis() - esBatchBegin), (System.currentTimeMillis() - esBatchBegin),
esBulkRequest.numberOfActions(), esBulkRequest.numberOfActions(),
mapping.get_index()); mapping.getIndex());
} }
batchBegin = System.currentTimeMillis(); batchBegin = System.currentTimeMillis();
esBulkRequest.resetBulk(); esBulkRequest.resetBulk();
@@ -182,12 +182,12 @@ public class ESEtlService extends AbstractEtlService {
(System.currentTimeMillis() - batchBegin), (System.currentTimeMillis() - batchBegin),
(System.currentTimeMillis() - esBatchBegin), (System.currentTimeMillis() - esBatchBegin),
esBulkRequest.numberOfActions(), esBulkRequest.numberOfActions(),
mapping.get_index()); mapping.getIndex());
} }
} }
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
errMsg.add(mapping.get_index() + " etl failed! ==>" + e.getMessage()); errMsg.add(mapping.getIndex() + " etl failed! ==>" + e.getMessage());
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return count; return count;

View File

@@ -66,17 +66,17 @@ public class ES7xTemplate implements ESTemplate {
@Override @Override
public void insert(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) { public void insert(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
if (mapping.get_id() != null) { if (mapping.getId() != null) {
String parentVal = (String) esFieldData.remove("$parent_routing"); String parentVal = (String) esFieldData.remove("$parent_routing");
if (mapping.isUpsert()) { if (mapping.isUpsert()) {
ESUpdateRequest updateRequest = esConnection.new ES7xUpdateRequest(mapping.get_index(), ESUpdateRequest updateRequest = esConnection.new ES7xUpdateRequest(mapping.getIndex(),
pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true); pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
updateRequest.setRouting(parentVal); updateRequest.setRouting(parentVal);
} }
getBulk().add(updateRequest); getBulk().add(updateRequest);
} else { } else {
ESIndexRequest indexRequest = esConnection.new ES7xIndexRequest(mapping.get_index(), pkVal.toString()) ESIndexRequest indexRequest = esConnection.new ES7xIndexRequest(mapping.getIndex(), pkVal.toString())
.setSource(esFieldData); .setSource(esFieldData);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
indexRequest.setRouting(parentVal); indexRequest.setRouting(parentVal);
@@ -85,13 +85,13 @@ public class ES7xTemplate implements ESTemplate {
} }
commitBulk(); commitBulk();
} else { } else {
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.get_index()) ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex())
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal)) .setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
.size(10000); .size(10000);
SearchResponse response = esSearchRequest.getResponse(); SearchResponse response = esSearchRequest.getResponse();
for (SearchHit hit : response.getHits()) { for (SearchHit hit : response.getHits()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.getIndex(),
hit.getId()).setDoc(esFieldData); hit.getId()).setDoc(esFieldData);
getBulk().add(esUpdateRequest); getBulk().add(esUpdateRequest);
commitBulk(); commitBulk();
@@ -148,18 +148,18 @@ public class ES7xTemplate implements ESTemplate {
@Override @Override
public void delete(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) { public void delete(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
if (mapping.get_id() != null) { if (mapping.getId() != null) {
ESDeleteRequest esDeleteRequest = this.esConnection.new ES7xDeleteRequest(mapping.get_index(), ESDeleteRequest esDeleteRequest = this.esConnection.new ES7xDeleteRequest(mapping.getIndex(),
pkVal.toString()); pkVal.toString());
getBulk().add(esDeleteRequest); getBulk().add(esDeleteRequest);
commitBulk(); commitBulk();
} else { } else {
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.get_index()) ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex())
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal)) .setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
.size(10000); .size(10000);
SearchResponse response = esSearchRequest.getResponse(); SearchResponse response = esSearchRequest.getResponse();
for (SearchHit hit : response.getHits()) { for (SearchHit hit : response.getHits()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.getIndex(),
hit.getId()).setDoc(esFieldData); hit.getId()).setDoc(esFieldData);
getBulk().add(esUpdateRequest); getBulk().add(esUpdateRequest);
commitBulk(); commitBulk();
@@ -204,7 +204,7 @@ public class ES7xTemplate implements ESTemplate {
public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet,
Map<String, Object> esFieldData) throws SQLException { Map<String, Object> esFieldData) throws SQLException {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName()); Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
@@ -213,7 +213,7 @@ public class ES7xTemplate implements ESTemplate {
resultIdVal = value; resultIdVal = value;
} }
if (!fieldItem.getFieldName().equals(mapping.get_id()) if (!fieldItem.getFieldName().equals(mapping.getId())
&& !mapping.getSkips().contains(fieldItem.getFieldName())) { && !mapping.getSkips().contains(fieldItem.getFieldName())) {
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value); esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
} }
@@ -228,7 +228,7 @@ public class ES7xTemplate implements ESTemplate {
@Override @Override
public Object getIdValFromRS(ESMapping mapping, ResultSet resultSet) throws SQLException { public Object getIdValFromRS(ESMapping mapping, ResultSet resultSet) throws SQLException {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName()); Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
@@ -245,7 +245,7 @@ public class ES7xTemplate implements ESTemplate {
public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld, public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld,
Map<String, Object> esFieldData) throws SQLException { Map<String, Object> esFieldData) throws SQLException {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
if (fieldItem.getFieldName().equals(idFieldName)) { if (fieldItem.getFieldName().equals(idFieldName)) {
@@ -289,7 +289,7 @@ public class ES7xTemplate implements ESTemplate {
@Override @Override
public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> esFieldData) { public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> esFieldData) {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
String columnName = fieldItem.getColumnItems().iterator().next().getColumnName(); String columnName = fieldItem.getColumnItems().iterator().next().getColumnName();
@@ -299,7 +299,7 @@ public class ES7xTemplate implements ESTemplate {
resultIdVal = value; resultIdVal = value;
} }
if (!fieldItem.getFieldName().equals(mapping.get_id()) if (!fieldItem.getFieldName().equals(mapping.getId())
&& !mapping.getSkips().contains(fieldItem.getFieldName())) { && !mapping.getSkips().contains(fieldItem.getFieldName())) {
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value); esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
} }
@@ -314,7 +314,7 @@ public class ES7xTemplate implements ESTemplate {
public Object getESDataFromDmlData(ESMapping mapping,String owner, Map<String, Object> dmlData, Map<String, Object> dmlOld, public Object getESDataFromDmlData(ESMapping mapping,String owner, Map<String, Object> dmlData, Map<String, Object> dmlOld,
Map<String, Object> esFieldData) { Map<String, Object> esFieldData) {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
ColumnItem columnItem = fieldItem.getColumnItems().iterator().next(); ColumnItem columnItem = fieldItem.getColumnItems().iterator().next();
@@ -348,17 +348,17 @@ public class ES7xTemplate implements ESTemplate {
} }
private void append4Update(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) { private void append4Update(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
if (mapping.get_id() != null) { if (mapping.getId() != null) {
String parentVal = (String) esFieldData.remove("$parent_routing"); String parentVal = (String) esFieldData.remove("$parent_routing");
if (mapping.isUpsert()) { if (mapping.isUpsert()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.getIndex(),
pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true); pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
esUpdateRequest.setRouting(parentVal); esUpdateRequest.setRouting(parentVal);
} }
getBulk().add(esUpdateRequest); getBulk().add(esUpdateRequest);
} else { } else {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.getIndex(),
pkVal.toString()).setDoc(esFieldData); pkVal.toString()).setDoc(esFieldData);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
esUpdateRequest.setRouting(parentVal); esUpdateRequest.setRouting(parentVal);
@@ -366,12 +366,12 @@ public class ES7xTemplate implements ESTemplate {
getBulk().add(esUpdateRequest); getBulk().add(esUpdateRequest);
} }
} else { } else {
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.get_index()) ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex())
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal)) .setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
.size(10000); .size(10000);
SearchResponse response = esSearchRequest.getResponse(); SearchResponse response = esSearchRequest.getResponse();
for (SearchHit hit : response.getHits()) { for (SearchHit hit : response.getHits()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.getIndex(),
hit.getId()).setDoc(esFieldData); hit.getId()).setDoc(esFieldData);
getBulk().add(esUpdateRequest); getBulk().add(esUpdateRequest);
} }
@@ -387,15 +387,15 @@ public class ES7xTemplate implements ESTemplate {
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private String getEsType(ESMapping mapping, String fieldName) { private String getEsType(ESMapping mapping, String fieldName) {
String key = mapping.get_index() + "-" + mapping.get_type(); String key = mapping.getIndex() + "-" + mapping.getType();
Map<String, String> fieldType = esFieldTypes.get(key); Map<String, String> fieldType = esFieldTypes.get(key);
if (fieldType != null) { if (fieldType != null) {
return fieldType.get(fieldName); return fieldType.get(fieldName);
} else { } else {
MappingMetaData mappingMetaData = esConnection.getMapping(mapping.get_index()); MappingMetaData mappingMetaData = esConnection.getMapping(mapping.getIndex());
if (mappingMetaData == null) { if (mappingMetaData == null) {
throw new IllegalArgumentException("Not found the mapping info of index: " + mapping.get_index()); throw new IllegalArgumentException("Not found the mapping info of index: " + mapping.getIndex());
} }
fieldType = new LinkedHashMap<>(); fieldType = new LinkedHashMap<>();

View File

@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -81,7 +80,7 @@
<tasks> <tasks>
<copy todir="${project.basedir}/../launcher/target/classes/es8" overwrite="true"> <copy todir="${project.basedir}/../launcher/target/classes/es8" overwrite="true">
<fileset dir="${project.basedir}/target/classes/es8" erroronmissingdir="true"> <fileset dir="${project.basedir}/target/classes/es8" erroronmissingdir="true">
<include name="*.yml"/> <include name="*.yml" />
</fileset> </fileset>
</copy> </copy>
</tasks> </tasks>

View File

@@ -53,11 +53,11 @@ public class ES8xAdapter extends ESAdapter {
public Map<String, Object> count(String task) { public Map<String, Object> count(String task) {
ESSyncConfig config = esSyncConfig.get(task); ESSyncConfig config = esSyncConfig.get(task);
ESSyncConfig.ESMapping mapping = config.getEsMapping(); ESSyncConfig.ESMapping mapping = config.getEsMapping();
SearchResponse response = this.esConnection.new ESSearchRequest(mapping.get_index()).size(0).getResponse(); SearchResponse response = this.esConnection.new ESSearchRequest(mapping.getIndex()).size(0).getResponse();
long rowCount = response.getHits().getTotalHits().value; long rowCount = response.getHits().getTotalHits().value;
Map<String, Object> res = new LinkedHashMap<>(); Map<String, Object> res = new LinkedHashMap<>();
res.put("esIndex", mapping.get_index()); res.put("esIndex", mapping.getIndex());
res.put("count", rowCount); res.put("count", rowCount);
return res; return res;
} }

View File

@@ -51,7 +51,7 @@ public class ESEtlService extends AbstractEtlService {
public EtlResult importData(List<String> params) { public EtlResult importData(List<String> params) {
ESMapping mapping = config.getEsMapping(); ESMapping mapping = config.getEsMapping();
logger.info("start etl to import data to index: {}", mapping.get_index()); logger.info("start etl to import data to index: {}", mapping.getIndex());
String sql = mapping.getSql(); String sql = mapping.getSql();
return importData(sql, params); return importData(sql, params);
} }
@@ -78,7 +78,7 @@ public class ESEtlService extends AbstractEtlService {
} }
// 如果是主键字段则不插入 // 如果是主键字段则不插入
if (fieldItem.getFieldName().equals(mapping.get_id())) { if (fieldItem.getFieldName().equals(mapping.getId())) {
idVal = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName); idVal = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName);
} else { } else {
Object val = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName); Object val = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName);
@@ -118,7 +118,7 @@ public class ESEtlService extends AbstractEtlService {
String parentVal = (String) esFieldData.remove("$parent_routing"); String parentVal = (String) esFieldData.remove("$parent_routing");
if (mapping.isUpsert()) { if (mapping.isUpsert()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest( ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(
mapping.get_index(), mapping.getIndex(),
idVal.toString()).setDoc(esFieldData).setDocAsUpsert(true); idVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
@@ -128,7 +128,7 @@ public class ESEtlService extends AbstractEtlService {
esBulkRequest.add(esUpdateRequest); esBulkRequest.add(esUpdateRequest);
} else { } else {
ESIndexRequest esIndexRequest = this.esConnection.new ES8xIndexRequest( ESIndexRequest esIndexRequest = this.esConnection.new ES8xIndexRequest(
mapping.get_index(), mapping.getIndex(),
idVal.toString()).setSource(esFieldData); idVal.toString()).setSource(esFieldData);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
esIndexRequest.setRouting(parentVal); esIndexRequest.setRouting(parentVal);
@@ -137,13 +137,13 @@ public class ESEtlService extends AbstractEtlService {
} }
} else { } else {
idVal = esFieldData.get(mapping.getPk()); idVal = esFieldData.get(mapping.getPk());
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.get_index()) ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex())
.setQuery(QueryBuilders.termQuery(mapping.getPk(), idVal)) .setQuery(QueryBuilders.termQuery(mapping.getPk(), idVal))
.size(10000); .size(10000);
SearchResponse response = esSearchRequest.getResponse(); SearchResponse response = esSearchRequest.getResponse();
for (SearchHit hit : response.getHits()) { for (SearchHit hit : response.getHits()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest( ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(
mapping.get_index(), mapping.getIndex(),
hit.getId()).setDoc(esFieldData); hit.getId()).setDoc(esFieldData);
esBulkRequest.add(esUpdateRequest); esBulkRequest.add(esUpdateRequest);
} }
@@ -162,7 +162,7 @@ public class ESEtlService extends AbstractEtlService {
(System.currentTimeMillis() - batchBegin), (System.currentTimeMillis() - batchBegin),
(System.currentTimeMillis() - esBatchBegin), (System.currentTimeMillis() - esBatchBegin),
esBulkRequest.numberOfActions(), esBulkRequest.numberOfActions(),
mapping.get_index()); mapping.getIndex());
} }
batchBegin = System.currentTimeMillis(); batchBegin = System.currentTimeMillis();
esBulkRequest.resetBulk(); esBulkRequest.resetBulk();
@@ -182,12 +182,12 @@ public class ESEtlService extends AbstractEtlService {
(System.currentTimeMillis() - batchBegin), (System.currentTimeMillis() - batchBegin),
(System.currentTimeMillis() - esBatchBegin), (System.currentTimeMillis() - esBatchBegin),
esBulkRequest.numberOfActions(), esBulkRequest.numberOfActions(),
mapping.get_index()); mapping.getIndex());
} }
} }
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
errMsg.add(mapping.get_index() + " etl failed! ==>" + e.getMessage()); errMsg.add(mapping.getIndex() + " etl failed! ==>" + e.getMessage());
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return count; return count;

View File

@@ -62,17 +62,17 @@ public class ES8xTemplate implements ESTemplate {
@Override @Override
public void insert(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) { public void insert(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
if (mapping.get_id() != null) { if (mapping.getId() != null) {
String parentVal = (String) esFieldData.remove("$parent_routing"); String parentVal = (String) esFieldData.remove("$parent_routing");
if (mapping.isUpsert()) { if (mapping.isUpsert()) {
ESUpdateRequest updateRequest = esConnection.new ES8xUpdateRequest(mapping.get_index(), ESUpdateRequest updateRequest = esConnection.new ES8xUpdateRequest(mapping.getIndex(),
pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true); pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
updateRequest.setRouting(parentVal); updateRequest.setRouting(parentVal);
} }
getBulk().add(updateRequest); getBulk().add(updateRequest);
} else { } else {
ESIndexRequest indexRequest = esConnection.new ES8xIndexRequest(mapping.get_index(), pkVal.toString()) ESIndexRequest indexRequest = esConnection.new ES8xIndexRequest(mapping.getIndex(), pkVal.toString())
.setSource(esFieldData); .setSource(esFieldData);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
indexRequest.setRouting(parentVal); indexRequest.setRouting(parentVal);
@@ -81,13 +81,13 @@ public class ES8xTemplate implements ESTemplate {
} }
commitBulk(); commitBulk();
} else { } else {
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.get_index()) ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex())
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal)) .setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
.size(10000); .size(10000);
SearchResponse response = esSearchRequest.getResponse(); SearchResponse response = esSearchRequest.getResponse();
for (SearchHit hit : response.getHits()) { for (SearchHit hit : response.getHits()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(mapping.getIndex(),
hit.getId()).setDoc(esFieldData); hit.getId()).setDoc(esFieldData);
getBulk().add(esUpdateRequest); getBulk().add(esUpdateRequest);
commitBulk(); commitBulk();
@@ -144,18 +144,18 @@ public class ES8xTemplate implements ESTemplate {
@Override @Override
public void delete(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) { public void delete(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
if (mapping.get_id() != null) { if (mapping.getId() != null) {
ESDeleteRequest esDeleteRequest = this.esConnection.new ES8xDeleteRequest(mapping.get_index(), ESDeleteRequest esDeleteRequest = this.esConnection.new ES8xDeleteRequest(mapping.getIndex(),
pkVal.toString()); pkVal.toString());
getBulk().add(esDeleteRequest); getBulk().add(esDeleteRequest);
commitBulk(); commitBulk();
} else { } else {
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.get_index()) ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex())
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal)) .setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
.size(10000); .size(10000);
SearchResponse response = esSearchRequest.getResponse(); SearchResponse response = esSearchRequest.getResponse();
for (SearchHit hit : response.getHits()) { for (SearchHit hit : response.getHits()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(mapping.getIndex(),
hit.getId()).setDoc(esFieldData); hit.getId()).setDoc(esFieldData);
getBulk().add(esUpdateRequest); getBulk().add(esUpdateRequest);
commitBulk(); commitBulk();
@@ -200,7 +200,7 @@ public class ES8xTemplate implements ESTemplate {
public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet,
Map<String, Object> esFieldData) throws SQLException { Map<String, Object> esFieldData) throws SQLException {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName()); Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
@@ -209,7 +209,7 @@ public class ES8xTemplate implements ESTemplate {
resultIdVal = value; resultIdVal = value;
} }
if (!fieldItem.getFieldName().equals(mapping.get_id()) if (!fieldItem.getFieldName().equals(mapping.getId())
&& !mapping.getSkips().contains(fieldItem.getFieldName())) { && !mapping.getSkips().contains(fieldItem.getFieldName())) {
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value); esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
} }
@@ -224,7 +224,7 @@ public class ES8xTemplate implements ESTemplate {
@Override @Override
public Object getIdValFromRS(ESMapping mapping, ResultSet resultSet) throws SQLException { public Object getIdValFromRS(ESMapping mapping, ResultSet resultSet) throws SQLException {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName()); Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
@@ -241,7 +241,7 @@ public class ES8xTemplate implements ESTemplate {
public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld, public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld,
Map<String, Object> esFieldData) throws SQLException { Map<String, Object> esFieldData) throws SQLException {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
if (fieldItem.getFieldName().equals(idFieldName)) { if (fieldItem.getFieldName().equals(idFieldName)) {
@@ -286,7 +286,7 @@ public class ES8xTemplate implements ESTemplate {
public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData,
Map<String, Object> esFieldData) { Map<String, Object> esFieldData) {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
String columnName = fieldItem.getColumnItems().iterator().next().getColumnName(); String columnName = fieldItem.getColumnItems().iterator().next().getColumnName();
@@ -296,7 +296,7 @@ public class ES8xTemplate implements ESTemplate {
resultIdVal = value; resultIdVal = value;
} }
if (!fieldItem.getFieldName().equals(mapping.get_id()) if (!fieldItem.getFieldName().equals(mapping.getId())
&& !mapping.getSkips().contains(fieldItem.getFieldName())) { && !mapping.getSkips().contains(fieldItem.getFieldName())) {
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value); esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
} }
@@ -311,7 +311,7 @@ public class ES8xTemplate implements ESTemplate {
public Object getESDataFromDmlData(ESMapping mapping, String owner, Map<String, Object> dmlData, public Object getESDataFromDmlData(ESMapping mapping, String owner, Map<String, Object> dmlData,
Map<String, Object> dmlOld, Map<String, Object> esFieldData) { Map<String, Object> dmlOld, Map<String, Object> esFieldData) {
SchemaItem schemaItem = mapping.getSchemaItem(); SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
Object resultIdVal = null; Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) { for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
ColumnItem columnItem = fieldItem.getColumnItems().iterator().next(); ColumnItem columnItem = fieldItem.getColumnItems().iterator().next();
@@ -345,17 +345,17 @@ public class ES8xTemplate implements ESTemplate {
} }
private void append4Update(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) { private void append4Update(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
if (mapping.get_id() != null) { if (mapping.getId() != null) {
String parentVal = (String) esFieldData.remove("$parent_routing"); String parentVal = (String) esFieldData.remove("$parent_routing");
if (mapping.isUpsert()) { if (mapping.isUpsert()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(mapping.getIndex(),
pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true); pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
esUpdateRequest.setRouting(parentVal); esUpdateRequest.setRouting(parentVal);
} }
getBulk().add(esUpdateRequest); getBulk().add(esUpdateRequest);
} else { } else {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(mapping.getIndex(),
pkVal.toString()).setDoc(esFieldData); pkVal.toString()).setDoc(esFieldData);
if (StringUtils.isNotEmpty(parentVal)) { if (StringUtils.isNotEmpty(parentVal)) {
esUpdateRequest.setRouting(parentVal); esUpdateRequest.setRouting(parentVal);
@@ -363,12 +363,12 @@ public class ES8xTemplate implements ESTemplate {
getBulk().add(esUpdateRequest); getBulk().add(esUpdateRequest);
} }
} else { } else {
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.get_index()) ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex())
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal)) .setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
.size(10000); .size(10000);
SearchResponse response = esSearchRequest.getResponse(); SearchResponse response = esSearchRequest.getResponse();
for (SearchHit hit : response.getHits()) { for (SearchHit hit : response.getHits()) {
ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(mapping.get_index(), ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(mapping.getIndex(),
hit.getId()).setDoc(esFieldData); hit.getId()).setDoc(esFieldData);
getBulk().add(esUpdateRequest); getBulk().add(esUpdateRequest);
} }
@@ -384,15 +384,15 @@ public class ES8xTemplate implements ESTemplate {
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private String getEsType(ESMapping mapping, String fieldName) { private String getEsType(ESMapping mapping, String fieldName) {
String key = mapping.get_index() + "-" + mapping.get_type(); String key = mapping.getIndex() + "-" + mapping.getType();
Map<String, String> fieldType = esFieldTypes.get(key); Map<String, String> fieldType = esFieldTypes.get(key);
if (fieldType != null) { if (fieldType != null) {
return fieldType.get(fieldName); return fieldType.get(fieldName);
} else { } else {
MappingMetadata mappingMetaData = esConnection.getMapping(mapping.get_index()); MappingMetadata mappingMetaData = esConnection.getMapping(mapping.getIndex());
if (mappingMetaData == null) { if (mappingMetaData == null) {
throw new IllegalArgumentException("Not found the mapping info of index: " + mapping.get_index()); throw new IllegalArgumentException("Not found the mapping info of index: " + mapping.getIndex());
} }
fieldType = new LinkedHashMap<>(); fieldType = new LinkedHashMap<>();

View File

@@ -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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -6,6 +6,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import com.alibaba.otter.canal.client.adapter.support.AdapterConfig; import com.alibaba.otter.canal.client.adapter.support.AdapterConfig;
import org.springframework.beans.factory.annotation.Value;
/** /**
* ES 映射配置 * ES 映射配置
@@ -28,13 +29,13 @@ public class ESSyncConfig implements AdapterConfig {
private String esVersion = "es6"; private String esVersion = "es6";
public void validate() { public void validate() {
if (esMapping._index == null) { if (esMapping.index == null) {
throw new NullPointerException("esMapping._index"); throw new NullPointerException("esMapping._index");
} }
if ("es6".equals(esVersion) && esMapping._type == null) { if ("es6".equals(esVersion) && esMapping.type == null) {
throw new NullPointerException("esMapping._type"); throw new NullPointerException("esMapping._type");
} }
if (esMapping._id == null && esMapping.getPk() == null) { if (esMapping.id == null && esMapping.getPk() == null) {
throw new NullPointerException("esMapping._id or esMapping.pk"); throw new NullPointerException("esMapping._id or esMapping.pk");
} }
if (esMapping.sql == null) { if (esMapping.sql == null) {
@@ -96,9 +97,14 @@ public class ESSyncConfig implements AdapterConfig {
public static class ESMapping implements AdapterMapping { public static class ESMapping implements AdapterMapping {
private String _index; @Value("${_index}")
private String _type; private String index;
private String _id;
@Value("${_type}")
private String type;
@Value("${_id}")
private String id;
private boolean upsert = false; private boolean upsert = false;
private String pk; private String pk;
private Map<String, RelationMapping> relations = new LinkedHashMap<>(); private Map<String, RelationMapping> relations = new LinkedHashMap<>();
@@ -114,28 +120,28 @@ public class ESSyncConfig implements AdapterConfig {
private SchemaItem schemaItem; // sql解析结果模型 private SchemaItem schemaItem; // sql解析结果模型
public String get_index() { public String getIndex() {
return _index; return index;
} }
public void set_index(String _index) { public void setIndex(String index) {
this._index = _index; this.index = index;
} }
public String get_type() { public String getType() {
return _type; return type;
} }
public void set_type(String _type) { public void setType(String type) {
this._type = _type; this.type = type;
} }
public String get_id() { public String getId() {
return _id; return id;
} }
public void set_id(String _id) { public void setId(String id) {
this._id = _id; this.id = id;
} }
public boolean isUpsert() { public boolean isUpsert() {

View File

@@ -136,8 +136,8 @@ public class SchemaItem {
} }
public FieldItem getIdFieldItem(ESSyncConfig.ESMapping mapping) { public FieldItem getIdFieldItem(ESSyncConfig.ESMapping mapping) {
if (mapping.get_id() != null) { if (mapping.getId() != null) {
return getSelectFields().get(mapping.get_id()); return getSelectFields().get(mapping.getId());
} else { } else {
return getSelectFields().get(mapping.getPk()); return getSelectFields().get(mapping.getPk());
} }

View File

@@ -54,13 +54,13 @@ public class ESSyncService {
for (ESSyncConfig config : esSyncConfigs) { for (ESSyncConfig config : esSyncConfigs) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Prepared to sync index: {}, destination: {}", logger.trace("Prepared to sync index: {}, destination: {}",
config.getEsMapping().get_index(), config.getEsMapping().getIndex(),
dml.getDestination()); dml.getDestination());
} }
this.sync(config, dml); this.sync(config, dml);
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Sync completed: {}, destination: {}", logger.trace("Sync completed: {}, destination: {}",
config.getEsMapping().get_index(), config.getEsMapping().getIndex(),
dml.getDestination()); dml.getDestination());
} }
} }
@@ -73,7 +73,7 @@ public class ESSyncService {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
StringBuilder configIndexes = new StringBuilder(); StringBuilder configIndexes = new StringBuilder();
esSyncConfigs esSyncConfigs
.forEach(esSyncConfig -> configIndexes.append(esSyncConfig.getEsMapping().get_index()).append(" ")); .forEach(esSyncConfig -> configIndexes.append(esSyncConfig.getEsMapping().getIndex()).append(" "));
logger.debug("DML: {} \nAffected indexes: {}", logger.debug("DML: {} \nAffected indexes: {}",
JSON.toJSONString(dml, JSONWriter.Feature.WriteNulls), JSON.toJSONString(dml, JSONWriter.Feature.WriteNulls),
configIndexes.toString()); configIndexes.toString());
@@ -105,10 +105,10 @@ public class ESSyncService {
logger.trace("Sync elapsed time: {} ms,destination: {}, es index: {}", logger.trace("Sync elapsed time: {} ms,destination: {}, es index: {}",
(System.currentTimeMillis() - begin), (System.currentTimeMillis() - begin),
dml.getDestination(), dml.getDestination(),
config.getEsMapping().get_index()); config.getEsMapping().getIndex());
} }
} catch (Throwable 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().getIndex(), dml);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@@ -210,7 +210,7 @@ public class ESSyncService {
// ------主表 查询sql来更新------ // ------主表 查询sql来更新------
if (schemaItem.getMainTable().getTableName().equalsIgnoreCase(dml.getTable())) { if (schemaItem.getMainTable().getTableName().equalsIgnoreCase(dml.getTable())) {
ESMapping mapping = config.getEsMapping(); ESMapping mapping = config.getEsMapping();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id(); String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
FieldItem idFieldItem = schemaItem.getSelectFields().get(idFieldName); FieldItem idFieldItem = schemaItem.getSelectFields().get(idFieldName);
boolean idFieldSimple = true; boolean idFieldSimple = true;
@@ -336,7 +336,7 @@ public class ESSyncService {
// ------是主表------ // ------是主表------
if (schemaItem.getMainTable().getTableName().equalsIgnoreCase(dml.getTable())) { if (schemaItem.getMainTable().getTableName().equalsIgnoreCase(dml.getTable())) {
if (mapping.get_id() != null) { if (mapping.getId() != null) {
FieldItem idFieldItem = schemaItem.getIdFieldItem(mapping); FieldItem idFieldItem = schemaItem.getIdFieldItem(mapping);
// 主键为简单字段 // 主键为简单字段
if (!idFieldItem.isMethod() && !idFieldItem.isBinaryOp()) { if (!idFieldItem.isMethod() && !idFieldItem.isBinaryOp()) {
@@ -349,7 +349,7 @@ public class ESSyncService {
logger.trace("Main table delete es index, destination:{}, table: {}, index: {}, id: {}", logger.trace("Main table delete es index, destination:{}, table: {}, index: {}, id: {}",
config.getDestination(), config.getDestination(),
dml.getTable(), dml.getTable(),
mapping.get_index(), mapping.getIndex(),
idVal); idVal);
} }
esTemplate.delete(mapping, idVal, null); esTemplate.delete(mapping, idVal, null);
@@ -368,7 +368,7 @@ public class ESSyncService {
logger.trace("Main table delete es index, destination:{}, table: {}, index: {}, pk: {}", logger.trace("Main table delete es index, destination:{}, table: {}, index: {}, pk: {}",
config.getDestination(), config.getDestination(),
dml.getTable(), dml.getTable(),
mapping.get_index(), mapping.getIndex(),
pkVal); pkVal);
} }
esFieldData.remove(pkFieldItem.getFieldName()); esFieldData.remove(pkFieldItem.getFieldName());
@@ -438,7 +438,7 @@ public class ESSyncService {
logger.trace("Single table insert to es index, destination:{}, table: {}, index: {}, id: {}", logger.trace("Single table insert to es index, destination:{}, table: {}, index: {}, id: {}",
config.getDestination(), config.getDestination(),
dml.getTable(), dml.getTable(),
mapping.get_index(), mapping.getIndex(),
idVal); idVal);
} }
esTemplate.insert(mapping, idVal, esFieldData); esTemplate.insert(mapping, idVal, esFieldData);
@@ -461,7 +461,7 @@ public class ESSyncService {
logger.trace("Main table insert to es index by query sql, destination:{}, table: {}, index: {}, sql: {}", logger.trace("Main table insert to es index by query sql, destination:{}, table: {}, index: {}, sql: {}",
config.getDestination(), config.getDestination(),
dml.getTable(), dml.getTable(),
mapping.get_index(), mapping.getIndex(),
sql.replace("\n", " ")); sql.replace("\n", " "));
} }
Util.sqlRS(ds, sql, rs -> { Util.sqlRS(ds, sql, rs -> {
@@ -475,7 +475,7 @@ public class ESSyncService {
"Main table insert to es index by query sql, destination:{}, table: {}, index: {}, id: {}", "Main table insert to es index by query sql, destination:{}, table: {}, index: {}, id: {}",
config.getDestination(), config.getDestination(),
dml.getTable(), dml.getTable(),
mapping.get_index(), mapping.getIndex(),
idVal); idVal);
} }
esTemplate.insert(mapping, idVal, esFieldData); esTemplate.insert(mapping, idVal, esFieldData);
@@ -497,7 +497,7 @@ public class ESSyncService {
logger.trace("Main table delete es index by query sql, destination:{}, table: {}, index: {}, sql: {}", logger.trace("Main table delete es index by query sql, destination:{}, table: {}, index: {}, sql: {}",
config.getDestination(), config.getDestination(),
dml.getTable(), dml.getTable(),
mapping.get_index(), mapping.getIndex(),
sql.replace("\n", " ")); sql.replace("\n", " "));
} }
Util.sqlRS(ds, sql, rs -> { Util.sqlRS(ds, sql, rs -> {
@@ -519,7 +519,7 @@ public class ESSyncService {
"Main table delete to es index by query sql, destination:{}, table: {}, index: {}, id: {}", "Main table delete to es index by query sql, destination:{}, table: {}, index: {}, id: {}",
config.getDestination(), config.getDestination(),
dml.getTable(), dml.getTable(),
mapping.get_index(), mapping.getIndex(),
idVal); idVal);
} }
esTemplate.delete(mapping, idVal, esFieldData); esTemplate.delete(mapping, idVal, esFieldData);
@@ -554,7 +554,7 @@ public class ESSyncService {
String fieldName = fieldItem.getFieldName(); String fieldName = fieldItem.getFieldName();
// 判断是否是主键 // 判断是否是主键
if (fieldName.equals(mapping.get_id())) { if (fieldName.equals(mapping.getId())) {
fieldName = "_id"; fieldName = "_id";
} }
paramsTmp.put(fieldName, value); paramsTmp.put(fieldName, value);
@@ -566,7 +566,7 @@ public class ESSyncService {
logger.trace("Join table update es index by foreign key, destination:{}, table: {}, index: {}", logger.trace("Join table update es index by foreign key, destination:{}, table: {}, index: {}",
config.getDestination(), config.getDestination(),
dml.getTable(), dml.getTable(),
mapping.get_index()); mapping.getIndex());
} }
esTemplate.updateByQuery(config, paramsTmp, esFieldData); esTemplate.updateByQuery(config, paramsTmp, esFieldData);
} }
@@ -617,7 +617,7 @@ public class ESSyncService {
logger.trace("Join table update es index by query sql, destination:{}, table: {}, index: {}, sql: {}", logger.trace("Join table update es index by query sql, destination:{}, table: {}, index: {}, sql: {}",
config.getDestination(), config.getDestination(),
dml.getTable(), dml.getTable(),
mapping.get_index(), mapping.getIndex(),
sql.toString().replace("\n", " ")); sql.toString().replace("\n", " "));
} }
Util.sqlRS(ds, sql.toString(), values, rs -> { Util.sqlRS(ds, sql.toString(), values, rs -> {
@@ -661,7 +661,7 @@ public class ESSyncService {
entry.getKey().getColumn().getColumnName()); entry.getKey().getColumn().getColumnName());
String fieldName = fieldItem.getFieldName(); String fieldName = fieldItem.getFieldName();
// 判断是否是主键 // 判断是否是主键
if (fieldName.equals(mapping.get_id())) { if (fieldName.equals(mapping.getId())) {
fieldName = "_id"; fieldName = "_id";
} }
paramsTmp.put(fieldName, value); paramsTmp.put(fieldName, value);
@@ -673,7 +673,7 @@ public class ESSyncService {
logger.trace("Join table update es index by query sql, destination:{}, table: {}, index: {}", logger.trace("Join table update es index by query sql, destination:{}, table: {}, index: {}",
config.getDestination(), config.getDestination(),
dml.getTable(), dml.getTable(),
mapping.get_index()); mapping.getIndex());
} }
esTemplate.updateByQuery(config, paramsTmp, esFieldData); esTemplate.updateByQuery(config, paramsTmp, esFieldData);
} }
@@ -721,7 +721,7 @@ public class ESSyncService {
logger.trace("Join table update es index by query whole sql, destination:{}, table: {}, index: {}, sql: {}", logger.trace("Join table update es index by query whole sql, destination:{}, table: {}, index: {}, sql: {}",
config.getDestination(), config.getDestination(),
dml.getTable(), dml.getTable(),
mapping.get_index(), mapping.getIndex(),
sql.toString().replace("\n", " ")); sql.toString().replace("\n", " "));
} }
Util.sqlRS(ds, sql.toString(), rs -> { Util.sqlRS(ds, sql.toString(), rs -> {
@@ -775,7 +775,7 @@ public class ESSyncService {
.getValFromRS(mapping, rs, fieldItem.getFieldName(), fieldItem.getFieldName()); .getValFromRS(mapping, rs, fieldItem.getFieldName(), fieldItem.getFieldName());
String fieldName = fieldItem.getFieldName(); String fieldName = fieldItem.getFieldName();
// 判断是否是主键 // 判断是否是主键
if (fieldName.equals(mapping.get_id())) { if (fieldName.equals(mapping.getId())) {
fieldName = "_id"; fieldName = "_id";
} }
paramsTmp.put(fieldName, value); paramsTmp.put(fieldName, value);
@@ -787,7 +787,7 @@ public class ESSyncService {
"Join table update es index by query whole sql, destination:{}, table: {}, index: {}", "Join table update es index by query whole sql, destination:{}, table: {}, index: {}",
config.getDestination(), config.getDestination(),
dml.getTable(), dml.getTable(),
mapping.get_index()); mapping.getIndex());
} }
esTemplate.updateByQuery(config, paramsTmp, esFieldData); esTemplate.updateByQuery(config, paramsTmp, esFieldData);
} }
@@ -817,7 +817,7 @@ public class ESSyncService {
logger.trace("Main table update to es index, destination:{}, table: {}, index: {}, id: {}", logger.trace("Main table update to es index, destination:{}, table: {}, index: {}, id: {}",
config.getDestination(), config.getDestination(),
dml.getTable(), dml.getTable(),
mapping.get_index(), mapping.getIndex(),
idVal); idVal);
} }
esTemplate.update(mapping, idVal, esFieldData); esTemplate.update(mapping, idVal, esFieldData);
@@ -840,7 +840,7 @@ public class ESSyncService {
logger.trace("Main table update to es index by query sql, destination:{}, table: {}, index: {}, sql: {}", logger.trace("Main table update to es index by query sql, destination:{}, table: {}, index: {}, sql: {}",
config.getDestination(), config.getDestination(),
dml.getTable(), dml.getTable(),
mapping.get_index(), mapping.getIndex(),
sql.replace("\n", " ")); sql.replace("\n", " "));
} }
Util.sqlRS(ds, sql, rs -> { Util.sqlRS(ds, sql, rs -> {
@@ -854,7 +854,7 @@ public class ESSyncService {
"Main table update to es index by query sql, destination:{}, table: {}, index: {}, id: {}", "Main table update to es index by query sql, destination:{}, table: {}, index: {}, id: {}",
config.getDestination(), config.getDestination(),
dml.getTable(), dml.getTable(),
mapping.get_index(), mapping.getIndex(),
idVal); idVal);
} }
esTemplate.update(mapping, idVal, esFieldData); esTemplate.update(mapping, idVal, esFieldData);

View File

@@ -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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -74,6 +74,10 @@
<artifactId>hbase-shaded-client</artifactId> <artifactId>hbase-shaded-client</artifactId>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>ru.yandex.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
</dependency>
<!-- outer adapter jar with dependencies--> <!-- outer adapter jar with dependencies-->
<dependency> <dependency>
@@ -167,6 +171,19 @@
<classifier>jar-with-dependencies</classifier> <classifier>jar-with-dependencies</classifier>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.clickhouse</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<artifactId>*</artifactId>
<groupId>*</groupId>
</exclusion>
</exclusions>
<classifier>jar-with-dependencies</classifier>
<scope>provided</scope>
</dependency>
<!-- connector plugin --> <!-- connector plugin -->
<dependency> <dependency>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -59,7 +59,7 @@ esac
JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'` JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'`
str=`file -L $JAVA | grep 64-bit` str=`file -L $JAVA | grep 64-bit`
JAVA_OPTS="$JAVA_OPTS -Xss256k -XX:+AggressiveOpts -XX:-UseBiasedLocking -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$base/logs" JAVA_OPTS="$JAVA_OPTS -Xss1m -XX:+AggressiveOpts -XX:-UseBiasedLocking -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$base/logs"
if [ $JavaVersion -ge 11 ] ; then if [ $JavaVersion -ge 11 ] ; then
#JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$base_log/gc.log:time " #JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$base_log/gc.log:time "
JAVA_OPTS="$JAVA_OPTS" JAVA_OPTS="$JAVA_OPTS"

View File

@@ -104,3 +104,13 @@ canal.conf:
# jdbc.url: jdbc:phoenix:127.0.0.1:2181:/hbase/db # jdbc.url: jdbc:phoenix:127.0.0.1:2181:/hbase/db
# jdbc.username: # jdbc.username:
# jdbc.password: # jdbc.password:
# - name: clickhouse
# key: clickhouse1
# properties:
# jdbc.driverClassName: ru.yandex.clickhouse.ClickHouseDriver
# jdbc.url: jdbc:clickhouse://127.0.0.1:8123/default
# jdbc.username: default
# jdbc.password: 123456
# batchSize: 3000
# scheduleTime: 600 # second unit
# threads: 3 # parallel threads

View File

@@ -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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -4,12 +4,12 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.1.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal.client-adapter</artifactId> <artifactId>canal.client-adapter</artifactId>
<version>1.1.7-SNAPSHOT</version> <version>1.1.8-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>
@@ -36,6 +36,7 @@
<module>kudu</module> <module>kudu</module>
<module>phoenix</module> <module>phoenix</module>
<module>tablestore</module> <module>tablestore</module>
<module>clickhouse</module>
</modules> </modules>
<licenses> <licenses>
@@ -155,7 +156,7 @@
<dependency> <dependency>
<groupId>org.postgresql</groupId> <groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
<version>42.3.3</version> <version>42.5.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.oracle.database.jdbc</groupId> <groupId>com.oracle.database.jdbc</groupId>
@@ -192,6 +193,11 @@
<artifactId>hbase-client</artifactId> <artifactId>hbase-client</artifactId>
<version>1.4.8</version> <version>1.4.8</version>
</dependency> </dependency>
<dependency>
<groupId>ru.yandex.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>0.3.1</version>
</dependency>
<dependency> <dependency>
<groupId>com.aliyun.openservices</groupId> <groupId>com.aliyun.openservices</groupId>
<artifactId>tablestore</artifactId> <artifactId>tablestore</artifactId>

View File

@@ -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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -1,6 +1,5 @@
package com.alibaba.otter.canal.client.kafka; package com.alibaba.otter.canal.client.kafka;
import com.alibaba.fastjson.JSON;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -11,6 +10,7 @@ import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.serialization.StringDeserializer;
import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.client.CanalMQConnector; import com.alibaba.otter.canal.client.CanalMQConnector;
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector; import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;
import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.protocol.FlatMessage;
@@ -43,7 +43,7 @@ public class KafkaCanalConnector implements CanalMQConnector {
private Map<Integer, Long> currentOffsets = new ConcurrentHashMap<>(); private Map<Integer, Long> currentOffsets = new ConcurrentHashMap<>();
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){
this.topic = topic; this.topic = topic;
this.partition = partition; this.partition = partition;
this.flatMessage = flatMessage; this.flatMessage = flatMessage;
@@ -324,4 +324,3 @@ public class KafkaCanalConnector implements CanalMQConnector {
} }
} }

View File

@@ -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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<artifactId>canal.common</artifactId> <artifactId>canal.common</artifactId>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.connector</artifactId> <artifactId>canal.connector</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.connector</artifactId> <artifactId>canal.connector</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@@ -9,7 +9,6 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import com.alibaba.otter.canal.common.utils.PropertiesUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.Producer;
@@ -21,6 +20,7 @@ import org.slf4j.LoggerFactory;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter; import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.otter.canal.common.utils.ExecutorTemplate; import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
import com.alibaba.otter.canal.common.utils.PropertiesUtils;
import com.alibaba.otter.canal.connector.core.producer.AbstractMQProducer; import com.alibaba.otter.canal.connector.core.producer.AbstractMQProducer;
import com.alibaba.otter.canal.connector.core.producer.MQDestination; import com.alibaba.otter.canal.connector.core.producer.MQDestination;
import com.alibaba.otter.canal.connector.core.producer.MQMessageUtils; import com.alibaba.otter.canal.connector.core.producer.MQMessageUtils;
@@ -249,13 +249,15 @@ public class CanalKafkaProducer extends AbstractMQProducer implements CanalMQPro
FlatMessage flatMessagePart = partitionFlatMessage[i]; FlatMessage flatMessagePart = partitionFlatMessage[i];
if (flatMessagePart != null) { if (flatMessagePart != null) {
records.add(new ProducerRecord<>(topicName, i, null, JSON.toJSONBytes(flatMessagePart, records.add(new ProducerRecord<>(topicName, i, null, JSON.toJSONBytes(flatMessagePart,
JSONWriter.Feature.WriteNulls))); JSONWriter.Feature.WriteNulls,
JSONWriter.Feature.LargeObject)));
} }
} }
} else { } else {
final int partition = mqDestination.getPartition() != null ? mqDestination.getPartition() : 0; final int partition = mqDestination.getPartition() != null ? mqDestination.getPartition() : 0;
records.add(new ProducerRecord<>(topicName, partition, null, JSON.toJSONBytes(flatMessage, records.add(new ProducerRecord<>(topicName, partition, null, JSON.toJSONBytes(flatMessage,
JSONWriter.Feature.WriteNulls))); JSONWriter.Feature.WriteNulls,
JSONWriter.Feature.LargeObject)));
} }
} }
} }

View File

@@ -4,13 +4,13 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.1.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal.connector</artifactId> <artifactId>canal.connector</artifactId>
<version>1.1.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>canal connector module for otter ${project.version}</name> <name>canal connector module for otter ${project.version}</name>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.connector</artifactId> <artifactId>canal.connector</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -13,6 +13,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.JSONWriter.Feature; import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.common.utils.ExecutorTemplate; import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
import com.alibaba.otter.canal.common.utils.NamedThreadFactory; import com.alibaba.otter.canal.common.utils.NamedThreadFactory;
@@ -330,7 +331,7 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
try { try {
MessageId msgResultId = producer.newMessage() MessageId msgResultId = producer.newMessage()
.property(MSG_PROPERTY_PARTITION_NAME, String.valueOf(partition)) .property(MSG_PROPERTY_PARTITION_NAME, String.valueOf(partition))
.value(JSON.toJSONBytes(f, Feature.WriteNulls)) .value(JSON.toJSONBytes(f, Feature.WriteNulls, JSONWriter.Feature.LargeObject))
.send() .send()
// //
; ;

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.connector</artifactId> <artifactId>canal.connector</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@@ -1,6 +1,9 @@
package com.alibaba.otter.canal.connector.rabbitmq.producer; package com.alibaba.otter.canal.connector.rabbitmq.producer;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
@@ -51,7 +54,13 @@ public class CanalRabbitMQProducer extends AbstractMQProducer implements CanalMQ
ConnectionFactory factory = new ConnectionFactory(); ConnectionFactory factory = new ConnectionFactory();
String servers = rabbitMQProperties.getHost(); String servers = rabbitMQProperties.getHost();
if (servers.contains(":")) { if (servers.startsWith("amqp")) {
try {
factory.setUri(servers);
} catch (URISyntaxException | NoSuchAlgorithmException | KeyManagementException ex) {
throw new CanalException("failed to parse host", ex);
}
} else if (servers.contains(":")) {
String[] serverHostAndPort = servers.split(":"); String[] serverHostAndPort = servers.split(":");
factory.setHost(serverHostAndPort[0]); factory.setHost(serverHostAndPort[0]);
factory.setPort(Integer.parseInt(serverHostAndPort[1])); factory.setPort(Integer.parseInt(serverHostAndPort[1]));
@@ -165,7 +174,8 @@ public class CanalRabbitMQProducer extends AbstractMQProducer implements CanalMQ
// 串行分区 // 串行分区
List<FlatMessage> flatMessages = MQMessageUtils.messageConverter(datas, messageSub.getId()); List<FlatMessage> flatMessages = MQMessageUtils.messageConverter(datas, messageSub.getId());
for (FlatMessage flatMessage : flatMessages) { for (FlatMessage flatMessage : flatMessages) {
byte[] message = JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls); byte[] message = JSON
.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls, JSONWriter.Feature.LargeObject);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("send message:{} to destination:{}", message, canalDestination.getCanalDestination()); logger.debug("send message:{} to destination:{}", message, canalDestination.getCanalDestination());
} }

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.connector</artifactId> <artifactId>canal.connector</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@@ -263,7 +263,7 @@ import java.util.stream.Collectors;
List<Message> messages = flatMessagePart.stream() List<Message> messages = flatMessagePart.stream()
.map(flatMessage -> new Message(topicName, .map(flatMessage -> new Message(topicName,
((RocketMQProducerConfig) this.mqProperties).getTag(), ((RocketMQProducerConfig) this.mqProperties).getTag(),
JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls))) JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls, JSONWriter.Feature.LargeObject)))
.collect(Collectors.toList()); .collect(Collectors.toList());
// 批量发送 // 批量发送
sendMessage(messages, index); sendMessage(messages, index);
@@ -278,7 +278,7 @@ import java.util.stream.Collectors;
List<Message> messages = flatMessages.stream() List<Message> messages = flatMessages.stream()
.map(flatMessage -> new Message(topicName, .map(flatMessage -> new Message(topicName,
((RocketMQProducerConfig) this.mqProperties).getTag(), ((RocketMQProducerConfig) this.mqProperties).getTag(),
JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls))) JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls,JSONWriter.Feature.LargeObject)))
.collect(Collectors.toList()); .collect(Collectors.toList());
// 批量发送 // 批量发送
sendMessage(messages, partition); sendMessage(messages, partition);

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>canal.connector</artifactId> <artifactId>canal.connector</artifactId>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<version>1.1.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@@ -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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -3,13 +3,14 @@ package com.taobao.tddl.dbsync.binlog;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import com.taobao.tddl.dbsync.binlog.JsonConversion.Json_Value; import com.taobao.tddl.dbsync.binlog.JsonConversion.Json_Value;
import com.taobao.tddl.dbsync.binlog.JsonConversion.Json_enum_type; import com.taobao.tddl.dbsync.binlog.JsonConversion.Json_enum_type;
/** /**
* 处理mysql8.0 parital json diff解析 * 处理mysql8.0 parital json diff解析
* *
* @author agapple 2018年11月4日 下午3:53:46 * @author agapple 2018年11月4日 下午3:53:46
* @since 1.1.2 * @since 1.1.2
*/ */
@@ -71,12 +72,21 @@ public class JsonDiffConversion {
buffer.forward((int) value_length); buffer.forward((int) value_length);
} }
// see https://github.com/alibaba/canal/pull/5018
if (buffer.position() - position >= len) {
break;
}
}
if (buffer.position() - position != len) {
throw new IllegalArgumentException("reading json diff");
} }
// Print function names in reverse order. // Print function names in reverse order.
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
for (int i = operation_names.size() - 1; i >= 0; i--) { for (int i = operation_names.size() - 1; i >= 0; i--) {
if (i == 0 || operation_names.get(i - 1) != operation_names.get(i)) { if (i == 0 || !Objects.equals(operation_names.get(i - 1), operation_names.get(i))) {
builder.append(operation_names.get(i)).append("("); builder.append(operation_names.get(i)).append("(");
} }
} }
@@ -90,7 +100,7 @@ public class JsonDiffConversion {
// In case this vector is empty (a no-op), make an early return // In case this vector is empty (a no-op), make an early return
// after printing only the column name // after printing only the column name
if (operation_names.size() == 0) { if (operation_names.isEmpty()) {
return builder; return builder;
} }
@@ -128,8 +138,14 @@ public class JsonDiffConversion {
builder.append(jsonBuilder); builder.append(jsonBuilder);
} }
// see https://github.com/alibaba/canal/pull/5018
if (buffer.position() - position >= len) {
builder.append(")");
break;
}
// Print closing parenthesis // Print closing parenthesis
if (!buffer.hasRemaining() || operation_names.get(diff_i + 1) != operation_names.get(diff_i)) { if (!buffer.hasRemaining() || !Objects.equals(operation_names.get(diff_i + 1), operation_names.get(diff_i))) {
builder.append(")"); builder.append(")");
} }
@@ -139,6 +155,10 @@ public class JsonDiffConversion {
diff_i++; diff_i++;
} }
if (buffer.position() - position != len) {
throw new IllegalArgumentException("reading json diff");
}
return builder; return builder;
} }

View File

@@ -5,7 +5,6 @@ import java.util.Map;
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet; import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent; import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent;
import com.taobao.tddl.dbsync.binlog.event.GtidLogEvent;
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent; import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
/** /**
@@ -28,6 +27,8 @@ public final class LogContext {
private boolean iterateDecode = false; private boolean iterateDecode = false;
private boolean compatiablePercona = false;
public LogContext(){ public LogContext(){
this.formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x; this.formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
} }
@@ -92,4 +93,12 @@ public final class LogContext {
public void setIterateDecode(boolean iterateDecode) { public void setIterateDecode(boolean iterateDecode) {
this.iterateDecode = iterateDecode; this.iterateDecode = iterateDecode;
} }
public boolean isCompatiablePercona() {
return compatiablePercona;
}
public void setCompatiablePercona(boolean compatiablePercona) {
this.compatiablePercona = compatiablePercona;
}
} }

View File

@@ -158,8 +158,6 @@ public final class LogDecoder {
} finally { } finally {
context.setIterateDecode(false); context.setIterateDecode(false);
} }
} else {
// TODO support mariadb compress binlog
} }
return events; return events;
} }
@@ -193,7 +191,10 @@ public final class LogDecoder {
LogEvent gtidLogEvent = context.getGtidLogEvent(); LogEvent gtidLogEvent = context.getGtidLogEvent();
switch (header.getType()) { switch (header.getType()) {
case LogEvent.QUERY_EVENT: { case LogEvent.QUERY_EVENT: {
QueryLogEvent event = new QueryLogEvent(header, buffer, descriptionEvent); QueryLogEvent event = new QueryLogEvent(header,
buffer,
descriptionEvent,
context.isCompatiablePercona());
/* updating position in context */ /* updating position in context */
logPosition.position = header.getLogPos(); logPosition.position = header.getLogPos();
header.putGtid(context.getGtidSet(), gtidLogEvent); header.putGtid(context.getGtidSet(), gtidLogEvent);
@@ -354,7 +355,10 @@ public final class LogDecoder {
return event; return event;
} }
case LogEvent.EXECUTE_LOAD_QUERY_EVENT: { case LogEvent.EXECUTE_LOAD_QUERY_EVENT: {
ExecuteLoadQueryLogEvent event = new ExecuteLoadQueryLogEvent(header, buffer, descriptionEvent); ExecuteLoadQueryLogEvent event = new ExecuteLoadQueryLogEvent(header,
buffer,
descriptionEvent,
context.isCompatiablePercona());
/* updating position in context */ /* updating position in context */
logPosition.position = header.getLogPos(); logPosition.position = header.getLogPos();
return event; return event;

View File

@@ -52,9 +52,9 @@ public final class ExecuteLoadQueryLogEvent extends QueryLogEvent {
public static final int ELQ_FN_POS_END_OFFSET = ELQ_FILE_ID_OFFSET + 8; public static final int ELQ_FN_POS_END_OFFSET = ELQ_FILE_ID_OFFSET + 8;
public static final int ELQ_DUP_HANDLING_OFFSET = ELQ_FILE_ID_OFFSET + 12; public static final int ELQ_DUP_HANDLING_OFFSET = ELQ_FILE_ID_OFFSET + 12;
public ExecuteLoadQueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent) public ExecuteLoadQueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent,
throws IOException{ boolean compatiablePercona) throws IOException{
super(header, buffer, descriptionEvent); super(header, buffer, descriptionEvent, compatiablePercona);
buffer.position(descriptionEvent.commonHeaderLen + ELQ_FILE_ID_OFFSET); buffer.position(descriptionEvent.commonHeaderLen + ELQ_FILE_ID_OFFSET);

View File

@@ -434,8 +434,8 @@ public class QueryLogEvent extends LogEvent {
private int tvSec = -1; private int tvSec = -1;
private BigInteger ddlXid = BigInteger.valueOf(-1L); private BigInteger ddlXid = BigInteger.valueOf(-1L);
private Charset charset; private Charset charset;
private String timezone; private String timezone;
private boolean compatiablePercona = false;
public QueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent) public QueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent)
throws IOException{ throws IOException{
@@ -443,8 +443,15 @@ public class QueryLogEvent extends LogEvent {
} }
public QueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent, public QueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent,
boolean compatiablePercona) throws IOException{
this(header, buffer, descriptionEvent, compatiablePercona, false);
}
public QueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent,
boolean compatiablePercona,
boolean compress) throws IOException{ boolean compress) throws IOException{
super(header); super(header);
this.compatiablePercona = compatiablePercona;
final int commonHeaderLen = descriptionEvent.commonHeaderLen; final int commonHeaderLen = descriptionEvent.commonHeaderLen;
final int postHeaderLen = descriptionEvent.postHeaderLen[header.type - 1]; final int postHeaderLen = descriptionEvent.postHeaderLen[header.type - 1];
/* /*
@@ -607,6 +614,21 @@ public class QueryLogEvent extends LogEvent {
*/ */
public static final int Q_SQL_REQUIRE_PRIMARY_KEY = 19; public static final int Q_SQL_REQUIRE_PRIMARY_KEY = 19;
/**
* Replicate default_table_encryption.
*/
public static final int Q_DEFAULT_TABLE_ENCRYPTION = 20;
/**
* @since percona 8.0.31 Replicate ddl_skip_rewrite.
*/
public static final int Q_DDL_SKIP_REWRITE = 21;
/**
* @since percona 8.0.31 Replicate Q_WSREP_SKIP_READONLY_CHECKS.
*/
public static final int Q_WSREP_SKIP_READONLY_CHECKS = 128;
/** /**
* FROM MariaDB 5.5.34 * FROM MariaDB 5.5.34
*/ */
@@ -619,6 +641,8 @@ public class QueryLogEvent extends LogEvent {
public static final int Q_GTID_FLAGS3 = 130; public static final int Q_GTID_FLAGS3 = 130;
public static final int Q_CHARACTER_SET_COLLATIONS = 131;
private final void unpackVariables(LogBuffer buffer, final int end) throws IOException { private final void unpackVariables(LogBuffer buffer, final int end) throws IOException {
int code = -1; int code = -1;
try { try {
@@ -713,9 +737,24 @@ public class QueryLogEvent extends LogEvent {
// *start++ = thd->variables.sql_require_primary_key; // *start++ = thd->variables.sql_require_primary_key;
buffer.forward(1); buffer.forward(1);
break; break;
case Q_DEFAULT_TABLE_ENCRYPTION:
// *start++ = thd->variables.default_table_encryption;
buffer.forward(1);
case Q_DDL_SKIP_REWRITE:
// *start++ = thd->variables.binlog_ddl_skip_rewrite;
buffer.forward(1);
case Q_HRNOW: case Q_HRNOW:
// int when_sec_part = buffer.getUint24(); // https://github.com/alibaba/canal/issues/4940
buffer.forward(3); // percona 和 mariadb各自扩展mysql binlog的格式后有冲突
// 需要精确识别一下数据库类型做兼容处理
if (compatiablePercona) {
// percona 8.0.31
// Q_WSREP_SKIP_READONLY_CHECKS *start++ = 1;
buffer.forward(1);
} else {
// int when_sec_part = buffer.getUint24();
buffer.forward(3);
}
break; break;
case Q_XID: case Q_XID:
// xid= uint8korr(pos); // xid= uint8korr(pos);
@@ -728,7 +767,6 @@ public class QueryLogEvent extends LogEvent {
// sa_seq_no = uint8korr(pos); // sa_seq_no = uint8korr(pos);
// pos+= 8; // pos+= 8;
// } // }
int gtid_flags_extra = buffer.getUint8(); int gtid_flags_extra = buffer.getUint8();
final int FL_COMMIT_ALTER_E1= 4; final int FL_COMMIT_ALTER_E1= 4;
final int FL_ROLLBACK_ALTER_E1= 8; final int FL_ROLLBACK_ALTER_E1= 8;
@@ -736,6 +774,11 @@ public class QueryLogEvent extends LogEvent {
buffer.forward(8); buffer.forward(8);
} }
break; break;
case Q_CHARACTER_SET_COLLATIONS :
// mariadb
int count = buffer.getUint8();
// character_set_collations= Lex_cstring((const char *) pos0 , (const char *) pos);
buffer.forward(count * 4);
default: default:
/* /*
* That's why you must write status vars in growing * That's why you must write status vars in growing
@@ -789,8 +832,18 @@ public class QueryLogEvent extends LogEvent {
return "Q_DEFAULT_COLLATION_FOR_UTF8MB4"; return "Q_DEFAULT_COLLATION_FOR_UTF8MB4";
case Q_SQL_REQUIRE_PRIMARY_KEY: case Q_SQL_REQUIRE_PRIMARY_KEY:
return "Q_SQL_REQUIRE_PRIMARY_KEY"; return "Q_SQL_REQUIRE_PRIMARY_KEY";
case Q_DEFAULT_TABLE_ENCRYPTION:
return "Q_DEFAULT_TABLE_ENCRYPTION";
case Q_DDL_SKIP_REWRITE:
return "Q_DDL_SKIP_REWRITE";
case Q_HRNOW: case Q_HRNOW:
return "Q_HRNOW"; return "Q_HRNOW";
case Q_XID:
return "Q_XID";
case Q_GTID_FLAGS3:
return "Q_GTID_FLAGS3";
case Q_CHARACTER_SET_COLLATIONS :
return "Q_CHARACTER_SET_COLLATIONS";
} }
return "CODE#" + code; return "CODE#" + code;
} }

View File

@@ -4,8 +4,6 @@ import java.io.Serializable;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.sql.Types; import java.sql.Types;
import java.time.ZoneId;
import java.time.zone.ZoneRules;
import java.util.BitSet; import java.util.BitSet;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@@ -19,7 +17,7 @@ import com.taobao.tddl.dbsync.binlog.LogEvent;
/** /**
* Extracting JDBC type & value information from packed rows-buffer. * Extracting JDBC type & value information from packed rows-buffer.
* *
* @see mysql-5.1.60/sql/log_event.cc - Rows_log_event::print_verbose_one_row * @see mysql-5.1.60/sql/log_event.cc - Rows_log_event::print_verbose_one_row
* @author <a href="mailto:changyuan.lh@taobao.com">Changyuan.lh</a> * @author <a href="mailto:changyuan.lh@taobao.com">Changyuan.lh</a>
* @version 1.0 * @version 1.0
@@ -32,27 +30,27 @@ public final class RowsLogBuffer {
public static final Integer[] integerCache = new Integer[1024 * 128]; public static final Integer[] integerCache = new Integer[1024 * 128];
public static final int integerCacheLimit = longCache.length + 127; public static final int integerCacheLimit = longCache.length + 127;
public static final long DATETIMEF_INT_OFS = 0x8000000000L; public static final long DATETIMEF_INT_OFS = 0x8000000000L;
public static final long TIMEF_INT_OFS = 0x800000L; public static final long TIMEF_INT_OFS = 0x800000L;
public static final long TIMEF_OFS = 0x800000000000L; public static final long TIMEF_OFS = 0x800000000000L;
private static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; private static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
private final LogBuffer buffer; private final LogBuffer buffer;
private final int columnLen; private final int columnLen;
private final int jsonColumnCount; private final int jsonColumnCount;
private final Charset charset; private final Charset charset;
private final BitSet nullBits; private final BitSet nullBits;
private int nullBitIndex; private int nullBitIndex;
// Read value_options if this is AI for PARTIAL_UPDATE_ROWS_EVENT // Read value_options if this is AI for PARTIAL_UPDATE_ROWS_EVENT
private final boolean partial; private final boolean partial;
private final BitSet partialBits; private final BitSet partialBits;
private boolean fNull; private boolean fNull;
private int javaType; private int javaType;
private int length; private int length;
private Serializable value; private Serializable value;
public RowsLogBuffer(LogBuffer buffer, final int columnLen, Charset charset, int jsonColumnCount, boolean partial){ public RowsLogBuffer(LogBuffer buffer, final int columnLen, Charset charset, int jsonColumnCount, boolean partial){
this.buffer = buffer; this.buffer = buffer;
@@ -70,9 +68,8 @@ public final class RowsLogBuffer {
/** /**
* Extracting next row from packed buffer. * Extracting next row from packed buffer.
* *
* @see mysql-5.1.60/sql/log_event.cc - * @see mysql-5.1.60/sql/log_event.cc - Rows_log_event::print_verbose_one_row
* Rows_log_event::print_verbose_one_row
*/ */
public final boolean nextOneRow(BitSet columns, boolean after) { public final boolean nextOneRow(BitSet columns, boolean after) {
final boolean hasOneRow = buffer.hasRemaining(); final boolean hasOneRow = buffer.hasRemaining();
@@ -104,9 +101,8 @@ public final class RowsLogBuffer {
/** /**
* Extracting next field value from packed buffer. * Extracting next field value from packed buffer.
* *
* @see mysql-5.1.60/sql/log_event.cc - * @see mysql-5.1.60/sql/log_event.cc - Rows_log_event::print_verbose_one_row
* Rows_log_event::print_verbose_one_row
*/ */
public final Serializable nextValue(final String columName, final int columnIndex, final int type, final int meta) { public final Serializable nextValue(final String columName, final int columnIndex, final int type, final int meta) {
return nextValue(columName, columnIndex, type, meta, false); return nextValue(columName, columnIndex, type, meta, false);
@@ -114,9 +110,8 @@ public final class RowsLogBuffer {
/** /**
* Extracting next field value from packed buffer. * Extracting next field value from packed buffer.
* *
* @see mysql-5.1.60/sql/log_event.cc - * @see mysql-5.1.60/sql/log_event.cc - Rows_log_event::print_verbose_one_row
* Rows_log_event::print_verbose_one_row
*/ */
public final Serializable nextValue(final String columName, final int columnIndex, final int type, final int meta, public final Serializable nextValue(final String columName, final int columnIndex, final int type, final int meta,
boolean isBinary) { boolean isBinary) {
@@ -277,7 +272,7 @@ public final class RowsLogBuffer {
/** /**
* Extracting next field value from packed buffer. * Extracting next field value from packed buffer.
* *
* @see mysql-5.1.60/sql/log_event.cc - log_event_print_value * @see mysql-5.1.60/sql/log_event.cc - log_event_print_value
*/ */
final Serializable fetchValue(String columnName, int columnIndex, int type, final int meta, boolean isBinary) { final Serializable fetchValue(String columnName, int columnIndex, int type, final int meta, boolean isBinary) {
@@ -300,10 +295,8 @@ public final class RowsLogBuffer {
len = byte1; len = byte1;
break; break;
default: default:
throw new IllegalArgumentException(String.format("!! Don't know how to handle column type=%d meta=%d (%04X)", throw new IllegalArgumentException(String
type, .format("!! Don't know how to handle column type=%d meta=%d (%04X)", type, meta, meta));
meta,
meta));
} }
} }
} else { } else {
@@ -359,8 +352,8 @@ public final class RowsLogBuffer {
} }
case LogEvent.MYSQL_TYPE_DECIMAL: { case LogEvent.MYSQL_TYPE_DECIMAL: {
/* /*
* log_event.h : This enumeration value is only used internally * log_event.h : This enumeration value is only used internally and cannot exist
* and cannot exist in a binlog. * in a binlog.
*/ */
logger.warn("MYSQL_TYPE_DECIMAL : This enumeration value is " logger.warn("MYSQL_TYPE_DECIMAL : This enumeration value is "
+ "only used internally and cannot exist in a binlog!"); + "only used internally and cannot exist in a binlog!");
@@ -541,12 +534,10 @@ public final class RowsLogBuffer {
} }
case LogEvent.MYSQL_TYPE_DATETIME2: { case LogEvent.MYSQL_TYPE_DATETIME2: {
/* /*
* DATETIME and DATE low-level memory and disk representation * DATETIME and DATE low-level memory and disk representation routines 1 bit
* routines 1 bit sign (used when on disk) 17 bits year*13+month * sign (used when on disk) 17 bits year*13+month (year 0-9999, month 0-12) 5
* (year 0-9999, month 0-12) 5 bits day (0-31) 5 bits hour * bits day (0-31) 5 bits hour (0-23) 6 bits minute (0-59) 6 bits second (0-59)
* (0-23) 6 bits minute (0-59) 6 bits second (0-59) 24 bits * 24 bits microseconds (0-999999) Total: 64 bits = 8 bytes SYYYYYYY.YYYYYYYY
* microseconds (0-999999) Total: 64 bits = 8 bytes
* SYYYYYYY.YYYYYYYY
* .YYdddddh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff * .YYdddddh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
*/ */
long intpart = buffer.getBeUlong40() - DATETIMEF_INT_OFS; // big-endian long intpart = buffer.getBeUlong40() - DATETIMEF_INT_OFS; // big-endian
@@ -665,12 +656,10 @@ public final class RowsLogBuffer {
} }
case LogEvent.MYSQL_TYPE_TIME2: { case LogEvent.MYSQL_TYPE_TIME2: {
/* /*
* TIME low-level memory and disk representation routines * TIME low-level memory and disk representation routines In-memory format: 1
* In-memory format: 1 bit sign (Used for sign, when on disk) 1 * bit sign (Used for sign, when on disk) 1 bit unused (Reserved for wider hour
* bit unused (Reserved for wider hour range, e.g. for * range, e.g. for intervals) 10 bit hour (0-836) 6 bit minute (0-59) 6 bit
* intervals) 10 bit hour (0-836) 6 bit minute (0-59) 6 bit * second (0-59) 24 bits microseconds (0-999999) Total: 48 bits = 6 bytes
* second (0-59) 24 bits microseconds (0-999999) Total: 48 bits
* = 6 bytes
* Suhhhhhh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff * Suhhhhhh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
*/ */
long intpart = 0; long intpart = 0;
@@ -687,20 +676,15 @@ public final class RowsLogBuffer {
frac = buffer.getUint8(); frac = buffer.getUint8();
if (intpart < 0 && frac > 0) { if (intpart < 0 && frac > 0) {
/* /*
* Negative values are stored with reverse * Negative values are stored with reverse fractional part order, for binary
* fractional part order, for binary sort * sort compatibility. Disk value intpart frac Time value Memory value 800000.00
* compatibility. Disk value intpart frac Time value * 0 0 00:00:00.00 0000000000.000000 7FFFFF.FF -1 255 -00:00:00.01
* Memory value 800000.00 0 0 00:00:00.00 * FFFFFFFFFF.FFD8F0 7FFFFF.9D -1 99 -00:00:00.99 FFFFFFFFFF.F0E4D0 7FFFFF.00 -1
* 0000000000.000000 7FFFFF.FF -1 255 -00:00:00.01 * 0 -00:00:01.00 FFFFFFFFFF.000000 7FFFFE.FF -1 255 -00:00:01.01
* FFFFFFFFFF.FFD8F0 7FFFFF.9D -1 99 -00:00:00.99 * FFFFFFFFFE.FFD8F0 7FFFFE.F6 -2 246 -00:00:01.10 FFFFFFFFFE.FE7960 Formula to
* FFFFFFFFFF.F0E4D0 7FFFFF.00 -1 0 -00:00:01.00 * convert fractional part from disk format (now stored in "frac" variable) to
* FFFFFFFFFF.000000 7FFFFE.FF -1 255 -00:00:01.01 * absolute value: "0x100 - frac". To reconstruct in-memory value, we shift to
* FFFFFFFFFE.FFD8F0 7FFFFE.F6 -2 246 -00:00:01.10 * the next integer value and then substruct fractional part.
* FFFFFFFFFE.FE7960 Formula to convert fractional
* part from disk format (now stored in "frac"
* variable) to absolute value: "0x100 - frac". To
* reconstruct in-memory value, we shift to the next
* integer value and then substruct fractional part.
*/ */
intpart++; /* Shift to the next integer value */ intpart++; /* Shift to the next integer value */
frac -= 0x100; /* -(0x100 - frac) */ frac -= 0x100; /* -(0x100 - frac) */
@@ -715,9 +699,8 @@ public final class RowsLogBuffer {
frac = buffer.getBeUint16(); frac = buffer.getBeUint16();
if (intpart < 0 && frac > 0) { if (intpart < 0 && frac > 0) {
/* /*
* Fix reverse fractional part order: * Fix reverse fractional part order: "0x10000 - frac". See comments for FSP=1
* "0x10000 - frac". See comments for FSP=1 and * and FSP=2 above.
* FSP=2 above.
*/ */
intpart++; /* Shift to the next integer value */ intpart++; /* Shift to the next integer value */
frac -= 0x10000; /* -(0x10000-frac) */ frac -= 0x10000; /* -(0x10000-frac) */
@@ -789,8 +772,8 @@ public final class RowsLogBuffer {
} }
case LogEvent.MYSQL_TYPE_NEWDATE: { case LogEvent.MYSQL_TYPE_NEWDATE: {
/* /*
* log_event.h : This enumeration value is only used internally * log_event.h : This enumeration value is only used internally and cannot exist
* and cannot exist in a binlog. * in a binlog.
*/ */
logger.warn("MYSQL_TYPE_NEWDATE : This enumeration value is " logger.warn("MYSQL_TYPE_NEWDATE : This enumeration value is "
+ "only used internally and cannot exist in a binlog!"); + "only used internally and cannot exist in a binlog!");
@@ -862,8 +845,8 @@ public final class RowsLogBuffer {
case LogEvent.MYSQL_TYPE_ENUM: { case LogEvent.MYSQL_TYPE_ENUM: {
final int int32; final int int32;
/* /*
* log_event.h : This enumeration value is only used internally * log_event.h : This enumeration value is only used internally and cannot exist
* and cannot exist in a binlog. * in a binlog.
*/ */
switch (len) { switch (len) {
case 1: case 1:
@@ -929,24 +912,24 @@ public final class RowsLogBuffer {
} }
case LogEvent.MYSQL_TYPE_TINY_BLOB: { case LogEvent.MYSQL_TYPE_TINY_BLOB: {
/* /*
* log_event.h : This enumeration value is only used internally * log_event.h : This enumeration value is only used internally and cannot exist
* and cannot exist in a binlog. * in a binlog.
*/ */
logger.warn("MYSQL_TYPE_TINY_BLOB : This enumeration value is " logger.warn("MYSQL_TYPE_TINY_BLOB : This enumeration value is "
+ "only used internally and cannot exist in a binlog!"); + "only used internally and cannot exist in a binlog!");
} }
case LogEvent.MYSQL_TYPE_MEDIUM_BLOB: { case LogEvent.MYSQL_TYPE_MEDIUM_BLOB: {
/* /*
* log_event.h : This enumeration value is only used internally * log_event.h : This enumeration value is only used internally and cannot exist
* and cannot exist in a binlog. * in a binlog.
*/ */
logger.warn("MYSQL_TYPE_MEDIUM_BLOB : This enumeration value is " logger.warn("MYSQL_TYPE_MEDIUM_BLOB : This enumeration value is "
+ "only used internally and cannot exist in a binlog!"); + "only used internally and cannot exist in a binlog!");
} }
case LogEvent.MYSQL_TYPE_LONG_BLOB: { case LogEvent.MYSQL_TYPE_LONG_BLOB: {
/* /*
* log_event.h : This enumeration value is only used internally * log_event.h : This enumeration value is only used internally and cannot exist
* and cannot exist in a binlog. * in a binlog.
*/ */
logger.warn("MYSQL_TYPE_LONG_BLOB : This enumeration value is " logger.warn("MYSQL_TYPE_LONG_BLOB : This enumeration value is "
+ "only used internally and cannot exist in a binlog!"); + "only used internally and cannot exist in a binlog!");
@@ -1005,8 +988,7 @@ public final class RowsLogBuffer {
case LogEvent.MYSQL_TYPE_VAR_STRING: { case LogEvent.MYSQL_TYPE_VAR_STRING: {
/* /*
* Except for the data length calculation, MYSQL_TYPE_VARCHAR, * Except for the data length calculation, MYSQL_TYPE_VARCHAR,
* MYSQL_TYPE_VAR_STRING and MYSQL_TYPE_STRING are handled the * MYSQL_TYPE_VAR_STRING and MYSQL_TYPE_STRING are handled the same way.
* same way.
*/ */
len = meta; len = meta;
if (len < 256) { if (len < 256) {
@@ -1077,30 +1059,19 @@ public final class RowsLogBuffer {
if (partialBits.get(1)) { if (partialBits.get(1)) {
// print_json_diff // print_json_diff
int position = buffer.position(); int position = buffer.position();
StringBuilder builder = JsonDiffConversion.print_json_diff(buffer, try {
len, // https://github.com/alibaba/canal/pull/5018
columnName, StringBuilder builder = JsonDiffConversion
columnIndex, .print_json_diff(buffer, len, columnName, columnIndex, charset);
charset);
value = builder.toString();
buffer.position(position + len);
} else {
if (0 == len) {
// fixed issue #1 by lava, json column of zero length
// has no
// value, value parsing should be skipped
value = "";
} else {
int position = buffer.position();
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(),
buffer,
len - 1,
charset);
StringBuilder builder = new StringBuilder();
jsonValue.toJsonString(builder, charset);
value = builder.toString(); value = builder.toString();
buffer.position(position + len); buffer.position(position + len);
} catch (IllegalArgumentException e) {
buffer.position(position);
// print_json_diff failed, fallback to parse_value
parseJsonFromFullValue(len);
} }
} else {
parseJsonFromFullValue(len);
} }
javaType = Types.VARCHAR; javaType = Types.VARCHAR;
length = len; length = len;
@@ -1129,24 +1100,19 @@ public final class RowsLogBuffer {
/* fill binary */ /* fill binary */
byte[] binary = new byte[len]; byte[] binary = new byte[len];
buffer.fillBytes(binary, 0, len); buffer.fillBytes(binary, 0, len);
// Warning unsupport cloumn type
/* Warning unsupport cloumn type */ // logger.warn(String.format("!! Unsupport column type MYSQL_TYPE_GEOMETRY:
// logger.warn(String.format("!! Unsupport column type MYSQL_TYPE_GEOMETRY: meta=%d (%04X), len = %d", // meta=%d (%04X), len = %d", meta,meta, len));
// meta,
// meta,
// len));
javaType = Types.BINARY; javaType = Types.BINARY;
value = binary; value = binary;
length = len; length = len;
break; break;
} }
case LogEvent.MYSQL_TYPE_BOOL : case LogEvent.MYSQL_TYPE_BOOL:
case LogEvent.MYSQL_TYPE_INVALID : case LogEvent.MYSQL_TYPE_INVALID:
default: default:
logger.error(String.format("!! Don't know how to handle column type=%d meta=%d (%04X)", logger.error(
type, String.format("!! Don't know how to handle column type=%d meta=%d (%04X)", type, meta, meta));
meta,
meta));
javaType = Types.OTHER; javaType = Types.OTHER;
value = null; value = null;
length = 0; length = 0;
@@ -1155,6 +1121,21 @@ public final class RowsLogBuffer {
return value; return value;
} }
private void parseJsonFromFullValue(int len) {
if (0 == len) {
// fixed issue #1 by lava, json column of zero length has no value, value
// parsing should be skipped
value = "";
} else {
int position = buffer.position();
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(), buffer, len - 1, charset);
StringBuilder builder = new StringBuilder();
jsonValue.toJsonString(builder, charset);
value = builder.toString();
buffer.position(position + len);
}
}
public final boolean isNull() { public final boolean isNull() {
return fNull; return fNull;
} }

View File

@@ -1 +1 @@
package com.taobao.tddl.dbsync.binlog.event.mariadb; package com.taobao.tddl.dbsync.binlog.event.mariadb;

View File

@@ -8,11 +8,13 @@ import java.util.List;
import com.taobao.tddl.dbsync.binlog.event.mariadb.BinlogCheckPointLogEvent; import com.taobao.tddl.dbsync.binlog.event.mariadb.BinlogCheckPointLogEvent;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import com.taobao.tddl.dbsync.binlog.event.*; import com.taobao.tddl.dbsync.binlog.event.*;
import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent; import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent;
@Ignore
public class DirectLogFetcherTest extends BaseLogFetcherTest { public class DirectLogFetcherTest extends BaseLogFetcherTest {
@Test @Test

View File

@@ -37,7 +37,7 @@ public class JsonDiffConversionTest {
final long len = 71L; final long len = 71L;
final String columnName = "foo"; final String columnName = "foo";
final int columnIndex = 0; final int columnIndex = 0;
final String charsetName = "foo"; final String charsetName = "gbk";
try { try {
// Act // Act
@@ -78,7 +78,7 @@ public class JsonDiffConversionTest {
final long len = 0L; final long len = 0L;
final String columnName = "foo"; final String columnName = "foo";
final int columnIndex = 0; final int columnIndex = 0;
final String charsetName = "foo"; final String charsetName = "gbk";
try { try {
// Act // Act
@@ -119,7 +119,7 @@ public class JsonDiffConversionTest {
final long len = 0L; final long len = 0L;
final String columnName = "foo"; final String columnName = "foo";
final int columnIndex = 0; final int columnIndex = 0;
final String charsetName = "foo"; final String charsetName = "gbk";
try { try {
// Act // Act
@@ -160,7 +160,7 @@ public class JsonDiffConversionTest {
final long len = 0L; final long len = 0L;
final String columnName = "foo"; final String columnName = "foo";
final int columnIndex = 0; final int columnIndex = 0;
final String charsetName = "foo"; final String charsetName = "gbk";
try { try {
// Act // Act
@@ -191,7 +191,7 @@ public class JsonDiffConversionTest {
final long len = 0L; final long len = 0L;
final String columnName = ","; final String columnName = ",";
final int columnIndex = 0; final int columnIndex = 0;
final String charsetName = "1a 2b 3c"; final String charsetName = "gbk";
// Act // Act
final StringBuilder actual = JsonDiffConversion.print_json_diff(buffer, final StringBuilder actual = JsonDiffConversion.print_json_diff(buffer,

View File

@@ -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.7-SNAPSHOT</version> <version>1.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -81,7 +81,7 @@ esac
JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'` JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'`
str=`file -L $JAVA | grep 64-bit` str=`file -L $JAVA | grep 64-bit`
JAVA_OPTS="$JAVA_OPTS -Xss256k -XX:+AggressiveOpts -XX:-UseBiasedLocking -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$base/logs" JAVA_OPTS="$JAVA_OPTS -Xss1m -XX:+AggressiveOpts -XX:-UseBiasedLocking -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$base/logs"
if [ $JavaVersion -ge 11 ] ; then if [ $JavaVersion -ge 11 ] ; then
#JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$base_log/gc.log:time " #JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$base_log/gc.log:time "

View File

@@ -1,5 +1,26 @@
package com.alibaba.otter.canal.deployer; package com.alibaba.otter.canal.deployer;
import static com.alibaba.otter.canal.deployer.CanalConstants.CANAL_DESTINATIONS;
import static com.alibaba.otter.canal.deployer.CanalConstants.CANAL_DESTINATIONS_EXPR;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.I0Itec.zkclient.IZkStateListener;
import org.I0Itec.zkclient.exception.ZkNoNodeException;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import com.alibaba.otter.canal.common.utils.AddressUtils; import com.alibaba.otter.canal.common.utils.AddressUtils;
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;
@@ -23,26 +44,6 @@ import com.alibaba.otter.canal.server.netty.CanalServerWithNetty;
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;
import org.I0Itec.zkclient.IZkStateListener;
import org.I0Itec.zkclient.exception.ZkNoNodeException;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.alibaba.otter.canal.deployer.CanalConstants.CANAL_DESTINATIONS;
import static com.alibaba.otter.canal.deployer.CanalConstants.CANAL_DESTINATIONS_EXPR;
/** /**
* canal调度控制器 * canal调度控制器
@@ -116,8 +117,14 @@ public class CanalController {
this.adminUser = getProperty(properties, CanalConstants.CANAL_ADMIN_USER); this.adminUser = getProperty(properties, CanalConstants.CANAL_ADMIN_USER);
this.adminPasswd = getProperty(properties, CanalConstants.CANAL_ADMIN_PASSWD); this.adminPasswd = getProperty(properties, CanalConstants.CANAL_ADMIN_PASSWD);
embeddedCanalServer.setUser(getProperty(properties, CanalConstants.CANAL_USER)); String user = getProperty(properties, CanalConstants.CANAL_USER);
embeddedCanalServer.setPasswd(getProperty(properties, CanalConstants.CANAL_PASSWD)); String passwd = getProperty(properties, CanalConstants.CANAL_PASSWD);
if (StringUtils.isNotEmpty(user) && StringUtils.isEmpty(passwd)) {
throw new IllegalArgumentException(
"canal.user = " + user + " , but canal.passwd is empty , pls check https://github.com/alibaba/canal/issues/4941");
}
embeddedCanalServer.setUser(user);
embeddedCanalServer.setPasswd(passwd);
String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY); String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY);
if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) { if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) {
@@ -601,7 +608,7 @@ public class CanalController {
ZkClientx.clearClients(); ZkClientx.clearClients();
// 需要释放 CanalServerWithEmbedded 否则主线程退出后,进程无法自动完整退出... // 需要释放 CanalServerWithEmbedded 否则主线程退出后,进程无法自动完整退出...
if (embeddedCanalServer != null) { if (embeddedCanalServer != null && embeddedCanalServer.isStart()) {
embeddedCanalServer.stop(); embeddedCanalServer.stop();
} }
} }

View File

@@ -54,6 +54,10 @@ public class CanalLauncher {
if (StringUtils.isNotEmpty(managerAddress)) { if (StringUtils.isNotEmpty(managerAddress)) {
String user = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_USER); String user = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_USER);
String passwd = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_PASSWD); String passwd = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_PASSWD);
if (StringUtils.isEmpty(passwd)) {
throw new IllegalArgumentException(
"canal.admin.passwd is empty , pls check https://github.com/alibaba/canal/issues/4941");
}
String adminPort = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_PORT, "11110"); String adminPort = CanalController.getProperty(properties, CanalConstants.CANAL_ADMIN_PORT, "11110");
boolean autoRegister = BooleanUtils.toBoolean(CanalController.getProperty(properties, boolean autoRegister = BooleanUtils.toBoolean(CanalController.getProperty(properties,
CanalConstants.CANAL_ADMIN_AUTO_REGISTER)); CanalConstants.CANAL_ADMIN_AUTO_REGISTER));

View File

@@ -115,7 +115,6 @@ public class CanalStarter {
CanalAdminController canalAdmin = new CanalAdminController(this); CanalAdminController canalAdmin = new CanalAdminController(this);
canalAdmin.setUser(user); canalAdmin.setUser(user);
canalAdmin.setPasswd(passwd); canalAdmin.setPasswd(passwd);
String ip = CanalController.getProperty(properties, CanalConstants.CANAL_IP); String ip = CanalController.getProperty(properties, CanalConstants.CANAL_IP);
logger.debug("canal admin port:{}, canal admin user:{}, canal admin password: {}, canal ip:{}", logger.debug("canal admin port:{}, canal admin user:{}, canal admin password: {}, canal ip:{}",

View File

@@ -37,7 +37,7 @@ public class CanalAdminController implements CanalAdmin {
private static final Logger logger = LoggerFactory.getLogger(CanalAdminController.class); private static final Logger logger = LoggerFactory.getLogger(CanalAdminController.class);
private String user; private String user;
private String passwd; private String passwd;
private CanalStarter canalStater; private CanalStarter canalStater;
public CanalAdminController(CanalStarter canalStater){ public CanalAdminController(CanalStarter canalStater){
this.canalStater = canalStater; this.canalStater = canalStater;

View File

@@ -9,13 +9,13 @@ canal.port = 11111
canal.metrics.pull.port = 11112 canal.metrics.pull.port = 11112
# canal instance user/passwd # canal instance user/passwd
# canal.user = canal # canal.user = canal
# canal.passwd = E3619321C1A937C46A0D8BD1DAC39F93B27D4458 # canal.passwd =
# canal admin config # canal admin config
#canal.admin.manager = 127.0.0.1:8089 #canal.admin.manager = 127.0.0.1:8089
canal.admin.port = 11110 canal.admin.port = 11110
canal.admin.user = admin canal.admin.user = admin
canal.admin.passwd = 4ACFE3202A5FF5CF467898FC58AAB1D615029441 canal.admin.passwd =
# admin auto register # admin auto register
#canal.admin.register.auto = true #canal.admin.register.auto = true
#canal.admin.register.cluster = #canal.admin.register.cluster =
@@ -176,6 +176,8 @@ rabbitmq.virtual.host =
rabbitmq.exchange = rabbitmq.exchange =
rabbitmq.username = rabbitmq.username =
rabbitmq.password = rabbitmq.password =
rabbitmq.queue =
rabbitmq.routingKey =
rabbitmq.deliveryMode = rabbitmq.deliveryMode =

View File

@@ -5,8 +5,8 @@ canal.register.ip =
canal.admin.manager = 127.0.0.1:8089 canal.admin.manager = 127.0.0.1:8089
canal.admin.port = 11110 canal.admin.port = 11110
canal.admin.user = admin canal.admin.user = admin
canal.admin.passwd = 4ACFE3202A5FF5CF467898FC58AAB1D615029441 canal.admin.passwd =
# admin auto register # admin auto register
canal.admin.register.auto = true canal.admin.register.auto = true
canal.admin.register.cluster = canal.admin.register.cluster =
canal.admin.register.name = canal.admin.register.name =

View File

@@ -185,6 +185,9 @@
<!--表结构相关--> <!--表结构相关-->
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/> <property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/> <property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
<property name="tsdbJdbcUrl" value="${canal.instance.tsdb.url:}"/>
<property name="tsdbJdbcUserName" value="${canal.instance.tsdb.dbUsername:}"/>
<property name="tsdbJdbcPassword" value="${canal.instance.tsdb.dbPassword:}"/>
<property name="tsdbSnapshotInterval" value="${canal.instance.tsdb.snapshot.interval:24}" /> <property name="tsdbSnapshotInterval" value="${canal.instance.tsdb.snapshot.interval:24}" />
<property name="tsdbSnapshotExpire" value="${canal.instance.tsdb.snapshot.expire:360}" /> <property name="tsdbSnapshotExpire" value="${canal.instance.tsdb.snapshot.expire:360}" />

View File

@@ -171,6 +171,9 @@
<!--表结构相关--> <!--表结构相关-->
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/> <property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/> <property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
<property name="tsdbJdbcUrl" value="${canal.instance.tsdb.url:}"/>
<property name="tsdbJdbcUserName" value="${canal.instance.tsdb.dbUsername:}"/>
<property name="tsdbJdbcPassword" value="${canal.instance.tsdb.dbPassword:}"/>
<property name="tsdbSnapshotInterval" value="${canal.instance.tsdb.snapshot.interval:24}" /> <property name="tsdbSnapshotInterval" value="${canal.instance.tsdb.snapshot.interval:24}" />
<property name="tsdbSnapshotExpire" value="${canal.instance.tsdb.snapshot.expire:360}" /> <property name="tsdbSnapshotExpire" value="${canal.instance.tsdb.snapshot.expire:360}" />

View File

@@ -159,6 +159,9 @@
<!--表结构相关--> <!--表结构相关-->
<property name="enableTsdb" value="${canal.instance.tsdb.enable:false}"/> <property name="enableTsdb" value="${canal.instance.tsdb.enable:false}"/>
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/> <property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
<property name="tsdbJdbcUrl" value="${canal.instance.tsdb.url:}"/>
<property name="tsdbJdbcUserName" value="${canal.instance.tsdb.dbUsername:}"/>
<property name="tsdbJdbcPassword" value="${canal.instance.tsdb.dbPassword:}"/>
<property name="tsdbSnapshotInterval" value="${canal.instance.tsdb.snapshot.interval:24}" /> <property name="tsdbSnapshotInterval" value="${canal.instance.tsdb.snapshot.interval:24}" />
<property name="tsdbSnapshotExpire" value="${canal.instance.tsdb.snapshot.expire:360}" /> <property name="tsdbSnapshotExpire" value="${canal.instance.tsdb.snapshot.expire:360}" />

View File

@@ -1,4 +1,5 @@
FROM canal/osbase:v2 FROM canal/osbase:v3-amd64
#FROM canal/osbase:v3-arm64
LABEL agapple (jianghang115@gmail.com) LABEL agapple (jianghang115@gmail.com)
@@ -19,7 +20,7 @@ RUN \
/bin/rm -f /home/admin/canal.deployer-*.tar.gz && \ /bin/rm -f /home/admin/canal.deployer-*.tar.gz && \
tar zxvf /tmp/node_exporter.tar.gz -C /home/admin && \ tar zxvf /tmp/node_exporter.tar.gz -C /home/admin && \
ln -s /home/admin/node_exporter-0.18.1.linux-arm64 /home/admin/node_exporter && \ ln -s /home/admin/node_exporter-1.6.1* /home/admin/node_exporter && \
mkdir -p home/admin/canal-server/logs && \ mkdir -p home/admin/canal-server/logs && \
chmod +x /home/admin/*.sh && \ chmod +x /home/admin/*.sh && \

View File

@@ -1,4 +1,5 @@
FROM canal/osadmin:v1 FROM canal/osadmin:v3-amd64
#FROM canal/osadmin:v3-arm64
MAINTAINER agapple (jianghang115@gmail.com) MAINTAINER agapple (jianghang115@gmail.com)

View File

@@ -1,11 +0,0 @@
FROM canal/osbase:v1
MAINTAINER agapple (jianghang115@gmail.com)
RUN \
groupadd -r mysql && useradd -r -g mysql mysql && \
yum -y install wget mysql-server --nogpgcheck && \
yum clean all && \
true
CMD ["/bin/bash"]

View File

@@ -1,11 +0,0 @@
FROM canal/osbase:v1
MAINTAINER agapple (jianghang115@gmail.com)
env NODE_EPORTER_LINK="https://github.com/prometheus/node_exporter/releases/download/v0.18.1/node_exporter-0.18.1.linux-arm64.tar.gz"
RUN \
wget "$NODE_EPORTER_LINK" -O /tmp/node_exporter.tar.gz && \
true
CMD ["/bin/bash"]

6
docker/base/README.md Normal file
View File

@@ -0,0 +1,6 @@
# osbase
cd amd64 && docker build --no-cache -t canal/osbase ./ -f Dockerfile
# osadmin
cd amd64 && docker build --no-cache -t canal/osadmin ./ -f ./Dockerfile_admin

View File

@@ -0,0 +1,60 @@
# amd64
FROM centos:centos7.9.2009@sha256:dead07b4d8ed7e29e98de0f4504d87e8880d4347859d839686a31da35a3b532f
ARG jdk_rpm
ARG platform_env
MAINTAINER agapple (jianghang115@gmail.com)
# env DOWNLOAD_AMD_LINK="https://download.oracle.com/otn/java/jdk/8u361-b09/0ae14417abb444ebb02b9815e2103550/jdk-8u361-linux-x64.rpm"
# env DOWNLOAD_ARM_LINK="https://download.oracle.com/otn/java/jdk/8u361-b09/0ae14417abb444ebb02b9815e2103550/jdk-8u361-linux-aarch64.rpm"
env NODE_EPORTER_LINK="https://github.com/prometheus/node_exporter/releases/download/v1.6.1/node_exporter-1.6.1.linux-amd64.tar.gz"
# install system
# update yum config, fix "centos6.x yum install failure && Determining fastest mirrors slow" problems
COPY yum/ /tmp/
RUN \
/bin/cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak && \
/bin/cp -f /tmp/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo && \
/bin/cp /etc/yum/pluginconf.d/fastestmirror.conf /etc/yum/pluginconf.d/fastestmirror.conf.bak && \
awk '{ if($0=="enabled=1"){print "enabled=0";} else{print $0;} }' /etc/yum/pluginconf.d/fastestmirror.conf.bak > /etc/yum/pluginconf.d/fastestmirror.conf && \
/bin/cp /etc/yum.conf /etc/yum.conf.bak && \
echo 'minrate=1' >> /etc/yum.conf && echo 'timeout=600' >> /etc/yum.conf && \
yum clean all && yum makecache
RUN \
/usr/bin/ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo 'root:Hello1234' | chpasswd && \
groupadd -r admin && useradd -g admin admin && \
yum install -y man && \
yum install -y dstat && \
yum install -y unzip && \
yum install -y nc && \
yum install -y openssh-server && \
yum install -y tar && \
yum install -y which && \
yum install -y wget && \
yum install -y perl && \
yum install -y file && \
ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key && \
ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key && \
sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd && \
sed -i -e 's/^#Port 22$/Port 2222/' /etc/ssh/sshd_config && \
mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh && \
yum install -y cronie && \
sed -i '/session required pam_loginuid.so/d' /etc/pam.d/crond && \
true
COPY ${jdk_rpm} /tmp/
RUN \
touch /var/lib/rpm/* && \
#if [ "$BUILDPLATFORM" == "linux/amd64" ] ; then wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=xxx; oraclelicense=accept-securebackup-cookie" "$DOWNLOAD_AMD_LINK" -O /tmp/jdk-8-linux.rpm ; fi && \
#if [ "$BUILDPLATFORM" == "linux/arm64" ] ; then wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=xxx; oraclelicense=accept-securebackup-cookie" "$DOWNLOAD_ARM_LINK" -O /tmp/jdk-8-linux.rpm ; fi && \
yum -y install /tmp/jdk-*.rpm && \
/bin/rm -f /tmp/jdk-*.rpm && \
echo "export JAVA_HOME=/usr/java/latest" >> /etc/profile && \
echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile && \
wget "$NODE_EPORTER_LINK" -O /tmp/node_exporter.tar.gz && \
yum clean all && \
true
CMD ["/bin/bash"]

View File

@@ -0,0 +1,15 @@
# amd64
FROM canal/osbase:v3-amd64
MAINTAINER agapple (jianghang115@gmail.com)
RUN \
groupadd -r mysql && useradd -r -g mysql mysql && \
wget https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm && \
rpm -ivh mysql80-community-release-el7-3.noarch.rpm && \
yum install sudo mysql-community-server --nogpgcheck -y && \
rm -f mysql80-community-release-el7-3.noarch.rpm && \
yum clean all && \
true
CMD ["/bin/bash"]

Some files were not shown because too many files have changed in this diff Show More