Compare commits

...

59 Commits

Author SHA1 Message Date
ziyang.lb
c6f28eb386 fix bug : full ddl sqls should not assign to extra field but data field 2024-05-24 18:19:58 +08:00
ziyang.lb
a28806311a fix bug : full ddl sqls should not assign to extra field but data field 2024-05-24 17:53:55 +08:00
ziyang.lb
05562f1dfc migrate parser from druid to polardbx-parser(with version 5.4.19) 2024-05-24 13:43:31 +08:00
agapple
a98549ee0d Merge branch 'master' into feature/ziyang_migrate_parser 2024-05-22 21:05:31 +08:00
jianghang.loujh
aa05d8e2a0 reformat 2024-05-22 09:46:13 +08:00
leoloel
5ff6449620 fix issue #5127 escape key of json data (#5130) 2024-05-22 09:41:50 +08:00
ziyou
bbea802914 feat(es8): es8 支持证书认证 (#5106) 2024-04-28 09:54:53 +08:00
JZ
e2219c88dd add support for listener name which is described in https://pulsar.apache.org/docs/next/concepts-multiple-advertised-listeners/ (#5118) 2024-04-28 09:53:39 +08:00
jianghang.loujh
a92e61f5a9 fixed issue #5073 , optimize docker build 2024-04-15 18:52:42 +08:00
jianghang.loujh
eaacc01b77 fixed issue #5094 , support function unique index 2024-04-15 15:34:50 +08:00
ziyang.lb
a6d73ad16b migrate parser from druid to polardbx-parser 2024-04-06 21:54:34 +08:00
jianghang.loujh
caa46f3672 merge pull #5089 2024-04-03 10:45:29 +08:00
jianghang.loujh
c0ff058fe1 remove unnecessary bean copy 2024-03-18 15:58:53 +08:00
jianghang.loujh
059f21208d fixed issue #5066 , es7.x npe 2024-03-18 14:19:45 +08:00
jianghang.loujh
8ca900377a merge & reformat code 2024-03-08 11:58:46 +08:00
jackzlz
6dffcc8573 yml解析支持占位符 (#5045) 2024-03-08 11:56:30 +08:00
wbxlll
711971599f fix issue client-adapter.rdb模块在转换时间时丢失精度 #5052 (#5053)
* fix the issue that client-adapter.rdb module loses precision when converting microsencond time

* fix the issue that client-adapter.rdb module loses precision when converting to java.sql.Time or java.sql.Timestamp

* fix the issue that client-adapter.rdb module loses precision when converting to java.sql.Time or java.sql.Timestamp
2024-03-08 11:50:53 +08:00
Devin Zhang
87c7a95523 startup.sh支持JDK17启动 (#5060) 2024-03-08 11:48:16 +08:00
ChengDaqi2023
e255baff11 update com.rabbitmq:amqp-client 5.5.0 to 5.18.0 (#5050) 2024-02-26 19:25:48 +08:00
jianghang.loujh
d457cd835e fixed issue #4819 , optmize code 2024-02-05 17:01:33 +08:00
jianghang.loujh
cf9c64629e optimize split IPAndPort 2024-02-05 16:49:05 +08:00
jianghang.loujh
307547746c optimize test & connected mistake 2024-02-04 13:59:29 +08:00
jianghang.loujh
c7d5ed4ba1 fixed issue #5036 , connected mistake 2024-02-04 13:58:52 +08:00
jianghang.loujh
b9ed5caf05 format 2024-02-04 13:35:10 +08:00
jianghang.loujh
348d218120 fixed issue #5029 , add deserializer message 2024-02-04 13:28:27 +08:00
zoemak
e2a64daad8 fixed issue #5037 ,使用client-adapter.rdb模块进行ETL同步,源库为MySQL,数据库名有短横线,读取源数据时报SQL异常 (#5044) 2024-02-04 13:07:51 +08:00
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
167 changed files with 4510 additions and 922 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,197 @@
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.*;
/**
* 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();
DruidDataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
String sql = "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType());
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

@@ -28,9 +28,9 @@ import com.google.common.cache.LoadingCache;
public class Util { public class Util {
private static final Logger logger = LoggerFactory.getLogger(Util.class); private static final Logger logger = LoggerFactory.getLogger(Util.class);
public static final String AUTO_GENERATED_PREFIX = "AUTO_GENERATED_"; public static final String AUTO_GENERATED_PREFIX = "AUTO_GENERATED_";
/** /**
* 通过DS执行sql * 通过DS执行sql
@@ -165,39 +165,38 @@ public class Util {
public static ThreadPoolExecutor newFixedDaemonThreadPool(int nThreads, long keepAliveTime) { public static ThreadPoolExecutor newFixedDaemonThreadPool(int nThreads, long keepAliveTime) {
return new ThreadPoolExecutor(nThreads, return new ThreadPoolExecutor(nThreads,
nThreads, nThreads,
keepAliveTime, keepAliveTime,
TimeUnit.MILLISECONDS, TimeUnit.MILLISECONDS,
new SynchronousQueue<>(), new SynchronousQueue<>(),
DaemonThreadFactory.daemonThreadFactory, DaemonThreadFactory.daemonThreadFactory,
(r, exe) -> { (r, exe) -> {
if (!exe.isShutdown()) { if (!exe.isShutdown()) {
try { try {
exe.getQueue().put(r); exe.getQueue().put(r);
} catch (InterruptedException e) { } catch (InterruptedException e) {
// ignore // ignore
}
} }
} }
); });
} }
public static ThreadPoolExecutor newSingleDaemonThreadExecutor(long keepAliveTime) { public static ThreadPoolExecutor newSingleDaemonThreadExecutor(long keepAliveTime) {
return new ThreadPoolExecutor(1, return new ThreadPoolExecutor(1,
1, 1,
keepAliveTime, keepAliveTime,
TimeUnit.MILLISECONDS, TimeUnit.MILLISECONDS,
new SynchronousQueue<>(), new SynchronousQueue<>(),
DaemonThreadFactory.daemonThreadFactory, DaemonThreadFactory.daemonThreadFactory,
(r, exe) -> { (r, exe) -> {
if (!exe.isShutdown()) { if (!exe.isShutdown()) {
try { try {
exe.getQueue().put(r); exe.getQueue().put(r);
} catch (InterruptedException e) { } catch (InterruptedException e) {
// ignore // ignore
}
} }
}); }
});
} }
public final static String timeZone; // 当前时区 public final static String timeZone; // 当前时区
@@ -327,4 +326,56 @@ public class Util {
return null; return null;
} }
/**
* Check if the time string has millisecond or microsecond units
*
* @param timeStr time string
* @return boolean
*/
public static boolean isAccuracyOverSecond(String timeStr) {
if (StringUtils.isEmpty(timeStr)) {
return false;
}
String[] times = StringUtils.split(timeStr, ".");
return times.length > 1 && !times[times.length - 1].isEmpty();
}
/**
* Check if the datetime string has microsecond or nanosecond units
*
* @param datetimeStr datetime string
* @return boolean
*/
public static boolean isAccuracyOverMillisecond(String datetimeStr) {
if (StringUtils.isEmpty(datetimeStr)) {
return false;
}
String[] times = StringUtils.split(datetimeStr, ".");
return times.length > 1 && times[times.length - 1].length() > 3;
}
/**
* MySQL has fractional seconds support for TIME, DATETIME, and TIMESTAMP
* values, with up to microseconds (6 digits) precision. ISO-8601 standard
* format is with up to nanoseconds (9 digits) precision, which is sufficient
* for storing MySQL time-related data.
*
* @param datetimeStr datetime string
* @return LocalDateTime
*/
public static LocalDateTime parseISOLocalDateTime(String datetimeStr) {
if (StringUtils.isEmpty(datetimeStr)) {
return null;
}
datetimeStr = datetimeStr.trim();
try {
// Replace SPACE in middle with `T` to comply with ISO-8601 standard format
return LocalDateTime.parse(datetimeStr.replace(" ", "T"));
} catch (Exception e) {
logger.error("Convert datetime string to ISOLocalDateTime fail:{}", datetimeStr, e);
}
return null;
}
} }

View File

@@ -4,9 +4,11 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.Binder;
@@ -18,6 +20,7 @@ import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.util.PropertyPlaceholderHelper;
public class YamlUtils { public class YamlUtils {
@@ -87,7 +90,13 @@ public class YamlUtils {
} }
ConfigurationPropertySource sources = new MapConfigurationPropertySource(properties); ConfigurationPropertySource sources = new MapConfigurationPropertySource(properties);
Binder binder = new Binder(sources); PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}");
Binder binder = new Binder(Arrays.asList(sources), value -> {
if (value instanceof String) {
return propertyPlaceholderHelper.replacePlaceholders((String) value, baseProperties);
}
return value;
});
return binder.bind(prefix, Bindable.of(clazz)).get(); return binder.bind(prefix, Bindable.of(clazz)).get();
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);

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

@@ -2,11 +2,7 @@ package com.alibaba.otter.canal.client.adapter.es7x.support;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
@@ -66,17 +62,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 +81,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 +144,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 +200,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 +209,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 +224,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 +241,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 +285,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 +295,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,15 +310,19 @@ 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();
if (columnItem.getOwner() == null || columnItem.getColumnName() == null) {
continue;
}
if (!columnItem.getOwner().equals(owner)) { if (!columnItem.getOwner().equals(owner)) {
continue; continue;
} }
String columnName = columnItem.getColumnName();
String columnName = columnItem.getColumnName();
if (fieldItem.getFieldName().equals(idFieldName)) { if (fieldItem.getFieldName().equals(idFieldName)) {
resultIdVal = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName); resultIdVal = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName);
} }
@@ -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,18 @@ 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())
pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true); .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 +82,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 +145,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 +201,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 +210,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 +225,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 +242,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 +287,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 +297,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 +312,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 +346,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 +364,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 +385,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

@@ -1,18 +1,29 @@
package com.alibaba.otter.canal.client.adapter.es8x.support; package com.alibaba.otter.canal.client.adapter.es8x.support;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import javax.net.ssl.SSLContext;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope; import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider; import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.elasticsearch.action.bulk.BulkItemResponse; import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkRequestBuilder; import org.elasticsearch.action.bulk.BulkRequestBuilder;
@@ -51,6 +62,47 @@ public class ESConnection {
private RestHighLevelClient restHighLevelClient; private RestHighLevelClient restHighLevelClient;
public ESConnection(String[] hosts, Map<String, String> properties) throws UnknownHostException{ public ESConnection(String[] hosts, Map<String, String> properties) throws UnknownHostException{
String caPath = properties.get("security.ca.path");
if (StringUtils.isNotEmpty(caPath)) {
connectEsWithCa(hosts, properties, caPath);
} else {
connectEsWithoutCa(hosts, properties);
}
}
private void connectEsWithCa(String[] hosts, Map<String, String> properties, String caPath) {
Path caCertificatePath = Paths.get(caPath);
try (InputStream is = Files.newInputStream(caCertificatePath)) {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
Certificate trustedCa = factory.generateCertificate(is);
KeyStore trustStore = KeyStore.getInstance("pkcs12");
trustStore.load(null, null);
trustStore.setCertificateEntry("ca", trustedCa);
SSLContextBuilder sslContextBuilder = SSLContexts.custom().loadTrustMaterial(trustStore, null);
final SSLContext sslContext = sslContextBuilder.build();
HttpHost[] httpHosts = Arrays.stream(hosts).map(this::createHttpHost).toArray(HttpHost[]::new);
RestClientBuilder restClientBuilder = RestClient.builder(httpHosts);
String nameAndPwd = properties.get("security.auth");
if (StringUtils.isNotEmpty(nameAndPwd) && nameAndPwd.contains(":")) {
String[] nameAndPwdArr = nameAndPwd.split(":");
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(nameAndPwdArr[0], nameAndPwdArr[1]));
restClientBuilder.setHttpClientConfigCallback(httpClientBuilder -> {
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
return httpClientBuilder.setSSLContext(sslContext);
});
}
restHighLevelClient = new RestHighLevelClientBuilder(restClientBuilder.build())
.setApiCompatibilityMode(true)
.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void connectEsWithoutCa(String[] hosts, Map<String, String> properties) {
HttpHost[] httpHosts = Arrays.stream(hosts).map(this::createHttpHost).toArray(HttpHost[]::new); HttpHost[] httpHosts = Arrays.stream(hosts).map(this::createHttpHost).toArray(HttpHost[]::new);
RestClientBuilder restClientBuilder = RestClient.builder(httpHosts); RestClientBuilder restClientBuilder = RestClient.builder(httpHosts);
String nameAndPwd = properties.get("security.auth"); String nameAndPwd = properties.get("security.auth");
@@ -280,9 +332,7 @@ public class ESConnection {
private BulkRequest bulkRequest; private BulkRequest bulkRequest;
public ES8xBulkRequest(){ public ES8xBulkRequest(){
bulkRequest = new BulkRequest(); bulkRequest = new BulkRequest();
} }
public void resetBulk() { public void resetBulk() {

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

@@ -2,11 +2,7 @@ package com.alibaba.otter.canal.client.adapter.kudu.service;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import javax.sql.DataSource; import javax.sql.DataSource;
@@ -45,13 +41,13 @@ public class KuduEtlService extends AbstractEtlService {
boolean flag = kuduTemplate.tableExists(kuduMapping.getTargetTable()); boolean flag = kuduTemplate.tableExists(kuduMapping.getTargetTable());
// 表不存在,停止导入 // 表不存在,停止导入
if (!flag) { if (!flag) {
logger.info("{} is don't hava,please check your kudu table !", kuduMapping.getTargetTable()); logger.info("{} is don't exist , please check your kudu table !", kuduMapping.getTargetTable());
errMsg.add(kuduMapping.getTargetTable() + " is don't hava,please check your kudu table !"); errMsg.add(kuduMapping.getTargetTable() + " is don't hava,please check your kudu table !");
etlResult.setErrorMessage(Joiner.on("\n").join(errMsg)); etlResult.setErrorMessage(Joiner.on("\n").join(errMsg));
return etlResult; return etlResult;
} }
logger.info("{} etl is starting!", kuduMapping.getTargetTable()); logger.info("{} etl is starting!", kuduMapping.getTargetTable());
String sql = "SELECT * FROM " + kuduMapping.getDatabase() + "." + kuduMapping.getTable(); String sql = "SELECT * FROM " + SyncUtil.getDbTableName(kuduMapping);
return importData(sql, params); return importData(sql, params);
} }

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

@@ -224,7 +224,7 @@ public class AdapterProcessor {
logger.error("finish turn off switch of destination:" + canalDestination); logger.error("finish turn off switch of destination:" + canalDestination);
} else { } else {
canalMsgConsumer.ack(); canalMsgConsumer.ack();
logger.error(e.getMessage() + " Error sync but ACK!"); logger.error(e.getMessage() + " Error sync but ACK!", e);
} }
} }
Thread.sleep(500); Thread.sleep(500);

View File

@@ -91,6 +91,7 @@ canal.conf:
# hosts: 127.0.0.1:9300 # 127.0.0.1:9200 for rest mode # hosts: 127.0.0.1:9300 # 127.0.0.1:9200 for rest mode
# properties: # properties:
# mode: transport # or rest # mode: transport # or rest
# # security.ca.path: /etc/es8/ca.crt
# # security.auth: test:123456 # only used for rest mode # # security.auth: test:123456 # only used for rest mode
# cluster.name: elasticsearch # cluster.name: elasticsearch
# - name: kudu # - name: kudu
@@ -104,3 +105,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

@@ -1,10 +1,6 @@
package com.alibaba.otter.canal.client.adapter.rdb.service; package com.alibaba.otter.canal.client.adapter.rdb.service;
import java.sql.Connection; import java.sql.*;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@@ -17,10 +13,7 @@ import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping;
import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil; import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil;
import com.alibaba.otter.canal.client.adapter.support.AbstractEtlService; import com.alibaba.otter.canal.client.adapter.support.*;
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;
/** /**
* RDB ETL 操作业务类 * RDB ETL 操作业务类
@@ -44,7 +37,9 @@ public class RdbEtlService extends AbstractEtlService {
*/ */
public EtlResult importData(List<String> params) { public EtlResult importData(List<String> params) {
DbMapping dbMapping = config.getDbMapping(); DbMapping dbMapping = config.getDbMapping();
String sql = "SELECT * FROM " + dbMapping.getDatabase() + "." + dbMapping.getTable(); // 获取源数据源,根据数据库类型拼装数据库名和表名
DruidDataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
String sql = "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType());
return importData(sql, params); return importData(sql, params);
} }

View File

@@ -1,21 +1,23 @@
package com.alibaba.otter.canal.client.adapter.rdb.support; package com.alibaba.otter.canal.client.adapter.rdb.support;
import com.alibaba.druid.DbType;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.support.Util;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Reader; import java.io.Reader;
import java.io.StringReader; import java.io.StringReader;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.sql.*; import java.sql.*;
import java.time.LocalDateTime;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.druid.DbType;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.support.Util;
public class SyncUtil { public class SyncUtil {
private static final Logger logger = LoggerFactory.getLogger(SyncUtil.class); private static final Logger logger = LoggerFactory.getLogger(SyncUtil.class);
@@ -220,11 +222,16 @@ public class SyncUtil {
pstmt.setTime(i, new java.sql.Time(((java.util.Date) value).getTime())); pstmt.setTime(i, new java.sql.Time(((java.util.Date) value).getTime()));
} else if (value instanceof String) { } else if (value instanceof String) {
String v = (String) value; String v = (String) value;
java.util.Date date = Util.parseDate(v); if(Util.isAccuracyOverSecond(v)) {
if (date != null) { //the java.sql.time doesn't support for even millisecond, only setObject works here.
pstmt.setTime(i, new Time(date.getTime())); pstmt.setObject(i, v);
} else { }else {
pstmt.setNull(i, type); java.util.Date date = Util.parseDate(v);
if (date != null) {
pstmt.setTime(i, new Time(date.getTime()));
} else {
pstmt.setNull(i, type);
}
} }
} else { } else {
pstmt.setNull(i, type); pstmt.setNull(i, type);
@@ -238,11 +245,22 @@ public class SyncUtil {
} else if (value instanceof String) { } else if (value instanceof String) {
String v = (String) value; String v = (String) value;
if (!v.startsWith("0000-00-00")) { if (!v.startsWith("0000-00-00")) {
java.util.Date date = Util.parseDate(v); if(Util.isAccuracyOverMillisecond(v)){
if (date != null) { //convert to ISO-8601 standard format, with up to nanoseconds (9 digits) precision
pstmt.setTimestamp(i, new Timestamp(date.getTime())); LocalDateTime isoDatetime = Util.parseISOLocalDateTime(v);
} else { if (isoDatetime != null) {
pstmt.setNull(i, type); pstmt.setTimestamp(i, Timestamp.valueOf(isoDatetime));
} else {
//if can't convert, set to null
pstmt.setNull(i, type);
}
}else {
java.util.Date date = Util.parseDate(v);
if (date != null) {
pstmt.setTimestamp(i, new Timestamp(date.getTime()));
} else {
pstmt.setNull(i, type);
}
} }
} else { } else {
pstmt.setObject(i, value); pstmt.setObject(i, value);

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

@@ -51,7 +51,7 @@ public class CanalMessageDeserializer {
} }
} }
} catch (Exception e) { } catch (Exception e) {
throw new CanalClientException("deserializer failed", e); throw new CanalClientException("deserializer failed by " + e.getMessage(), e);
} }
} }
} }

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;
@@ -78,14 +78,15 @@ public class KafkaCanalConnector implements CanalMQConnector {
return; return;
} }
connected = true;
if (kafkaConsumer == null && !flatMessage) { if (kafkaConsumer == null && !flatMessage) {
kafkaConsumer = new KafkaConsumer<>(properties); kafkaConsumer = new KafkaConsumer<>(properties);
} }
if (kafkaConsumer2 == null && flatMessage) { if (kafkaConsumer2 == null && flatMessage) {
kafkaConsumer2 = new KafkaConsumer<>(properties); kafkaConsumer2 = new KafkaConsumer<>(properties);
} }
connected = true;
} }
/** /**
@@ -324,4 +325,3 @@ public class KafkaCanalConnector implements CanalMQConnector {
} }
} }

View File

@@ -1,5 +1,13 @@
package com.alibaba.otter.canal.client.pulsarmq; package com.alibaba.otter.canal.client.pulsarmq;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.apache.pulsar.client.api.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson2.JSON; 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.CanalMessageDeserializer; import com.alibaba.otter.canal.client.CanalMessageDeserializer;
@@ -9,13 +17,6 @@ import com.alibaba.otter.canal.protocol.FlatMessage;
import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.exception.CanalClientException; import com.alibaba.otter.canal.protocol.exception.CanalClientException;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.apache.pulsar.client.api.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.TimeUnit;
/** /**
* PulsarMQ的连接 * PulsarMQ的连接
@@ -24,91 +25,98 @@ import java.util.concurrent.TimeUnit;
* 注意点: * 注意点:
* 1. 相比于canal {@linkplain SimpleCanalConnector}, 这里get和ack操作不能有并发, 必须是一个线程执行get后,内存里执行完毕ack后再取下一个get * 1. 相比于canal {@linkplain SimpleCanalConnector}, 这里get和ack操作不能有并发, 必须是一个线程执行get后,内存里执行完毕ack后再取下一个get
* </pre> * </pre>
*
* todo 重复消费的概率相当高。一次批处理中,只要有一个消息处理失败,则该批次全部重试 * todo 重复消费的概率相当高。一次批处理中,只要有一个消息处理失败,则该批次全部重试
* *
* @since 1.1.1 * @since 1.1.1
*/ */
public class PulsarMQCanalConnector implements CanalMQConnector { public class PulsarMQCanalConnector implements CanalMQConnector {
private static final Logger logger = LoggerFactory.getLogger(PulsarMQCanalConnector.class); private static final Logger logger = LoggerFactory
.getLogger(PulsarMQCanalConnector.class);
private volatile Messages<byte[]> lastGetBatchMessage; private volatile Messages<byte[]> lastGetBatchMessage;
/** /**
* 连接pulsar客户端 * 连接pulsar客户端
*/ */
private PulsarClient pulsarClient; private PulsarClient pulsarClient;
/** /**
* 消费者 * 消费者
*/ */
private Consumer<byte[]> consumer; private Consumer<byte[]> consumer;
/** /**
* 是否扁平化Canal消息内容 * 是否扁平化Canal消息内容
*/ */
private boolean isFlatMessage = false; private boolean isFlatMessage = false;
/** /**
* 主题名称 * 主题名称
*/ */
private String topic; private String topic;
/** /**
* 环境连接URL * 环境连接URL
*/ */
private String serviceUrl; private String serviceUrl;
/** /**
* 角色认证token * 角色认证token
*/ */
private String roleToken; private String roleToken;
/**
* listener name
*/
private String listenerName;
/** /**
* 订阅客户端名称 * 订阅客户端名称
*/ */
private String subscriptName; private String subscriptName;
/** /**
* 每次批量获取数据的最大条目数,默认30 * 每次批量获取数据的最大条目数,默认30
*/ */
private int batchSize = 30; private int batchSize = 30;
/** /**
* 与{@code batchSize}一起决定批量获取的数据大小 * 与{@code batchSize}一起决定批量获取的数据大小 当:
* 当:
* <p> * <p>
* 1. {@code batchSize} 条消息未消费时<br/> * 1. {@code batchSize} 条消息未消费时<br/>
* 2. 距上一次批量消费时间达到{@code batchTimeoutSeconds}秒时 * 2. 距上一次批量消费时间达到{@code batchTimeoutSeconds}秒时
* </p> * </p>
* 任一条件满足,即执行批量消费 * 任一条件满足,即执行批量消费
*/ */
private int getBatchTimeoutSeconds = 30; private int getBatchTimeoutSeconds = 30;
/** /**
* 批量处理消息时,一次批量处理的超时时间秒数 * 批量处理消息时,一次批量处理的超时时间秒数
* <p> * <p>
* 该时间应该根据{@code batchSize}和{@code batchTimeoutSeconds}合理设置 * 该时间应该根据{@code batchSize}和{@code batchTimeoutSeconds}合理设置
* </p> * </p>
*/ */
private int batchProcessTimeoutSeconds = 60; private int batchProcessTimeoutSeconds = 60;
/** /**
* 消费失败后的重试秒数,默认60秒 * 消费失败后的重试秒数,默认60秒
*/ */
private int redeliveryDelaySeconds = 60; private int redeliveryDelaySeconds = 60;
/** /**
* 当客户端接收到消息,30秒还没有返回ack给服务端时,ack超时,会重新消费该消息 * 当客户端接收到消息,30秒还没有返回ack给服务端时,ack超时,会重新消费该消息
*/ */
private int ackTimeoutSeconds = 30; private int ackTimeoutSeconds = 30;
/** /**
* 是否开启消息失败重试功能,默认开启 * 是否开启消息失败重试功能,默认开启
*/ */
private boolean isRetry = true; private boolean isRetry = true;
/** /**
* <p> * <p>
* true重试(-RETRY)和死信队列(-DLQ)后缀为大写,有些地方创建的为小写,需确保正确 * true重试(-RETRY)和死信队列(-DLQ)后缀为大写,有些地方创建的为小写,需确保正确
* </p> * </p>
*/ */
private boolean isRetryDLQUpperCase = false; private boolean isRetryDLQUpperCase = false;
/** /**
* 最大重试次数 * 最大重试次数
*/ */
private int maxRedeliveryCount = 128; private int maxRedeliveryCount = 128;
/** /**
* 连接标识位,在连接或关闭连接后改变值 * 连接标识位,在连接或关闭连接后改变值
*/ */
private boolean connected = false; private boolean connected = false;
/** /**
* 除必要参数外,其他参数使用默认值 * 除必要参数外,其他参数使用默认值
@@ -118,16 +126,21 @@ public class PulsarMQCanalConnector implements CanalMQConnector {
* </p> * </p>
* *
* @param isFlatMessage true使用扁平消息 * @param isFlatMessage true使用扁平消息
* @param serviceUrl pulsar服务连接地址,通常为:pulsar:host:ip或http://host:ip * @param serviceUrl pulsar服务连接地址,通常为:pulsar:host:ip或http://host:ip
* @param roleToken 有对应topic的消费者权限的角色token * @param roleToken 有对应topic的消费者权限的角色token
* @param topic 订阅主题 * @param topic 订阅主题
* @param subscriptName 订阅和客户端名称,同一个订阅名视为同一个消费实例 * @param subscriptName 订阅和客户端名称,同一个订阅名视为同一个消费实例
* @date 2021/9/18 08:54 * @date 2021/9/18 08:54
* @author chad * @author chad
* @since 1 by chad at 2021/9/18 完善 * @since 1 by chad at 2021/9/18 完善
*/ */
public PulsarMQCanalConnector(boolean isFlatMessage, String serviceUrl, String roleToken, String topic public PulsarMQCanalConnector(boolean isFlatMessage, String serviceUrl, String roleToken, String topic,
, String subscriptName) { String subscriptName){
this(isFlatMessage, serviceUrl, roleToken, topic, subscriptName, null);
}
public PulsarMQCanalConnector(boolean isFlatMessage, String serviceUrl, String roleToken, String topic,
String subscriptName, String listenerName){
this.isFlatMessage = isFlatMessage; this.isFlatMessage = isFlatMessage;
this.serviceUrl = serviceUrl; this.serviceUrl = serviceUrl;
this.roleToken = roleToken; this.roleToken = roleToken;
@@ -136,6 +149,7 @@ public class PulsarMQCanalConnector implements CanalMQConnector {
if (StringUtils.isEmpty(this.subscriptName)) { if (StringUtils.isEmpty(this.subscriptName)) {
throw new RuntimeException("Pulsar Consumer subscriptName required"); throw new RuntimeException("Pulsar Consumer subscriptName required");
} }
this.listenerName = listenerName;
} }
/** /**
@@ -145,10 +159,31 @@ public class PulsarMQCanalConnector implements CanalMQConnector {
* @author chad * @author chad
* @since 1 by chad at 2021/9/18 完善 * @since 1 by chad at 2021/9/18 完善
*/ */
public PulsarMQCanalConnector(boolean isFlatMessage, String serviceUrl, String roleToken, String topic public PulsarMQCanalConnector(boolean isFlatMessage, String serviceUrl, String roleToken, String topic,
, String subscriptName, int batchSize, int getBatchTimeoutSeconds, int batchProcessTimeoutSeconds String subscriptName, int batchSize, int getBatchTimeoutSeconds,
, int redeliveryDelaySeconds, int ackTimeoutSeconds, boolean isRetry, boolean isRetryDLQUpperCase int batchProcessTimeoutSeconds, int redeliveryDelaySeconds, int ackTimeoutSeconds,
, int maxRedeliveryCount) { boolean isRetry, boolean isRetryDLQUpperCase, int maxRedeliveryCount){
this(isFlatMessage,
serviceUrl,
roleToken,
topic,
subscriptName,
batchSize,
getBatchTimeoutSeconds,
batchProcessTimeoutSeconds,
redeliveryDelaySeconds,
ackTimeoutSeconds,
isRetry,
isRetryDLQUpperCase,
maxRedeliveryCount,
null);
}
public PulsarMQCanalConnector(boolean isFlatMessage, String serviceUrl, String roleToken, String topic,
String subscriptName, int batchSize, int getBatchTimeoutSeconds,
int batchProcessTimeoutSeconds, int redeliveryDelaySeconds, int ackTimeoutSeconds,
boolean isRetry, boolean isRetryDLQUpperCase, int maxRedeliveryCount,
String listenerName){
this.isFlatMessage = isFlatMessage; this.isFlatMessage = isFlatMessage;
this.serviceUrl = serviceUrl; this.serviceUrl = serviceUrl;
this.roleToken = roleToken; this.roleToken = roleToken;
@@ -165,16 +200,20 @@ public class PulsarMQCanalConnector implements CanalMQConnector {
this.isRetry = isRetry; this.isRetry = isRetry;
this.isRetryDLQUpperCase = isRetryDLQUpperCase; this.isRetryDLQUpperCase = isRetryDLQUpperCase;
this.maxRedeliveryCount = maxRedeliveryCount; this.maxRedeliveryCount = maxRedeliveryCount;
this.listenerName = listenerName;
} }
@Override @Override
public void connect() throws CanalClientException { public void connect() throws CanalClientException {
// 连接创建客户端 // 连接创建客户端
try { try {
pulsarClient = PulsarClient.builder() ClientBuilder builder = PulsarClient.builder()
.serviceUrl(serviceUrl) .serviceUrl(serviceUrl)
.authentication(AuthenticationFactory.token(roleToken)) .authentication(AuthenticationFactory.token(roleToken));
.build(); if (StringUtils.isNotEmpty(listenerName)) {
builder.listenerName(listenerName);
}
pulsarClient = builder.build();
} catch (PulsarClientException e) { } catch (PulsarClientException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -223,15 +262,14 @@ public class PulsarMQCanalConnector implements CanalMQConnector {
builder.subscriptionType(SubscriptionType.Failover); builder.subscriptionType(SubscriptionType.Failover);
builder builder
// 调用consumer.negativeAcknowledge(message) (即nack)来表示消费失败的消息 // 调用consumer.negativeAcknowledge(message) (即nack)来表示消费失败的消息
// 在指定的时间进行重新消费,默认是1分钟。 // 在指定的时间进行重新消费,默认是1分钟。
.negativeAckRedeliveryDelay(this.redeliveryDelaySeconds, TimeUnit.SECONDS) .negativeAckRedeliveryDelay(this.redeliveryDelaySeconds, TimeUnit.SECONDS)
.subscriptionName(this.subscriptName) .subscriptionName(this.subscriptName);
;
if (this.isRetry) { if (this.isRetry) {
DeadLetterPolicy.DeadLetterPolicyBuilder dlqBuilder = DeadLetterPolicy.builder() DeadLetterPolicy.DeadLetterPolicyBuilder dlqBuilder = DeadLetterPolicy.builder()
// 最大重试次数 // 最大重试次数
.maxRedeliverCount(this.maxRedeliveryCount); .maxRedeliverCount(this.maxRedeliveryCount);
// 指定重试队列,不是多个或通配符topic才能判断重试队列 // 指定重试队列,不是多个或通配符topic才能判断重试队列
if (!MQUtil.isPatternTag(this.topic)) { if (!MQUtil.isPatternTag(this.topic)) {
String retryTopic = this.topic + (this.isRetryDLQUpperCase ? "-RETRY" : "-retry"); String retryTopic = this.topic + (this.isRetryDLQUpperCase ? "-RETRY" : "-retry");
@@ -240,29 +278,26 @@ public class PulsarMQCanalConnector implements CanalMQConnector {
dlqBuilder.deadLetterTopic(dlqTopic); dlqBuilder.deadLetterTopic(dlqTopic);
} }
//默认关闭,如果需要重试则开启 // 默认关闭,如果需要重试则开启
builder.enableRetry(true) builder.enableRetry(true).deadLetterPolicy(dlqBuilder.build());
.deadLetterPolicy(dlqBuilder.build());
} }
// ack超时 // ack超时
builder.ackTimeout(this.ackTimeoutSeconds, TimeUnit.SECONDS); builder.ackTimeout(this.ackTimeoutSeconds, TimeUnit.SECONDS);
// pulsar批量获取消息设置 // pulsar批量获取消息设置
builder.batchReceivePolicy(new BatchReceivePolicy.Builder() builder.batchReceivePolicy(new BatchReceivePolicy.Builder().maxNumMessages(this.batchSize)
.maxNumMessages(this.batchSize) .timeout(this.getBatchTimeoutSeconds, TimeUnit.SECONDS)
.timeout(this.getBatchTimeoutSeconds, TimeUnit.SECONDS) .build());
.build());
try { try {
this.consumer = builder.subscribe(); this.consumer = builder.subscribe();
connected = true;
} catch (PulsarClientException e) { } catch (PulsarClientException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
connected = true;
} }
@Override @Override
public void subscribe() throws CanalClientException { public void subscribe() throws CanalClientException {
this.subscribe(null); this.subscribe(null);
@@ -283,7 +318,7 @@ public class PulsarMQCanalConnector implements CanalMQConnector {
* 不关注业务执行结果,只要收到消息即认识消费成功,自动ack * 不关注业务执行结果,只要收到消息即认识消费成功,自动ack
* *
* @param timeout 阻塞获取消息的超时时间 * @param timeout 阻塞获取消息的超时时间
* @param unit 时间单位 * @param unit 时间单位
* @return java.util.List<com.alibaba.otter.canal.protocol.Message> * @return java.util.List<com.alibaba.otter.canal.protocol.Message>
* @date 2021/9/13 22:24 * @date 2021/9/13 22:24
* @author chad * @author chad
@@ -299,13 +334,14 @@ public class PulsarMQCanalConnector implements CanalMQConnector {
} }
/** /**
* 关心业务执行结果,业务侧根据执行结果调用 {@link PulsarMQCanalConnector#ack()}或{@link PulsarMQCanalConnector#rollback()} * 关心业务执行结果,业务侧根据执行结果调用
* {@link PulsarMQCanalConnector#ack()}或{@link PulsarMQCanalConnector#rollback()}
* <p> * <p>
* 本方法示支持多线程,在MQ保障顺序的前提下,也无法提供单Topic多线程 * 本方法示支持多线程,在MQ保障顺序的前提下,也无法提供单Topic多线程
* </p> * </p>
* *
* @param timeout 阻塞获取消息的超时时间 * @param timeout 阻塞获取消息的超时时间
* @param unit 时间单位 * @param unit 时间单位
* @return java.util.List<com.alibaba.otter.canal.protocol.Message> * @return java.util.List<com.alibaba.otter.canal.protocol.Message>
* @date 2021/9/13 22:26 * @date 2021/9/13 22:26
* @author chad * @author chad
@@ -339,7 +375,8 @@ public class PulsarMQCanalConnector implements CanalMQConnector {
* @return java.util.List<T> * @return java.util.List<T>
* @date 2021/9/14 15:20 * @date 2021/9/14 15:20
* @author chad * @author chad
* @since 1 by chad at 2021/9/14 供{@link PulsarMQCanalConnector#getListWithoutAck(Long, TimeUnit)} * @since 1 by chad at 2021/9/14
* 供{@link PulsarMQCanalConnector#getListWithoutAck(Long, TimeUnit)}
* 和{@link PulsarMQCanalConnector#getFlatListWithoutAck(Long, TimeUnit)}调用 * 和{@link PulsarMQCanalConnector#getFlatListWithoutAck(Long, TimeUnit)}调用
*/ */
private <T> List<T> getListWithoutAck() { private <T> List<T> getListWithoutAck() {

View File

@@ -6,7 +6,6 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import com.alibaba.otter.canal.client.ConsumerBatchMessage;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.rocketmq.acl.common.AclClientRPCHook; import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.acl.common.SessionCredentials;
@@ -25,6 +24,7 @@ import org.slf4j.LoggerFactory;
import com.alibaba.fastjson2.JSON; 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.CanalMessageDeserializer; import com.alibaba.otter.canal.client.CanalMessageDeserializer;
import com.alibaba.otter.canal.client.ConsumerBatchMessage;
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;
import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.Message;
@@ -155,11 +155,10 @@ public class RocketMQCanalConnector implements CanalMQConnector {
} }
}); });
rocketMQConsumer.start(); rocketMQConsumer.start();
connected = true;
} catch (MQClientException ex) { } catch (MQClientException ex) {
connected = false; throw new RuntimeException("Start RocketMQ consumer error", ex);
logger.error("Start RocketMQ consumer error", ex);
} }
connected = true;
} }
private boolean process(List<MessageExt> messageExts) { private boolean process(List<MessageExt> messageExts) {

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>
@@ -87,6 +87,11 @@
<groupId>com.google.code.findbugs</groupId> <groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId> <artifactId>jsr305</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId> <artifactId>spring-test</artifactId>

View File

@@ -7,6 +7,7 @@ import java.net.ServerSocket;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -75,4 +76,27 @@ public class AddressUtils {
logger.error("Could not get local host ip address, will use 127.0.0.1 instead."); logger.error("Could not get local host ip address, will use 127.0.0.1 instead.");
return localAddress; return localAddress;
} }
/**
* 拆分IP地址和端口号
*
* @param text ip地址和端口号,ip和端口号以英文冒号(:)分隔;
*
* <pre>
* ipv4 127.0.0.1:3306
* ipv6 [::1]:3306
* </pre>
*
* @return
*/
public static String[] splitIPAndPort(String text) {
text = text.replace("[", "").replace("]", "");
int idx = text.lastIndexOf(':');
if (idx > 0) {
String ip = text.substring(0, idx);
String port = text.substring(idx + 1);
return new String[] { ip, port };
}
return ArrayUtils.EMPTY_STRING_ARRAY;
}
} }

View File

@@ -1,5 +1,7 @@
package com.alibaba.otter.canal.common.utils; package com.alibaba.otter.canal.common.utils;
import com.alibaba.druid.filter.config.ConfigTools;
import java.io.File; import java.io.File;
/** /**
@@ -50,4 +52,8 @@ public class CommonUtils {
return dirFile.delete(); return dirFile.delete();
} }
public static String decryptDruidPassword(String publicKeyText, String cipherText) throws Exception {
return ConfigTools.decrypt(publicKeyText, cipherText);
}
} }

View File

@@ -44,7 +44,7 @@ public class ExecutorTemplate {
check(future); check(future);
} }
public void submit(Callable<Exception> task) { public void submit(Callable task) {
Future future = executor.submit(task); Future future = executor.submit(task);
futures.add(future); futures.add(future);
check(future); check(future);

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

@@ -79,7 +79,7 @@ public class CanalMessageSerializerUtil {
} }
} }
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Error when serializing message to byte[] "); throw new RuntimeException("Error when serializing message to byte[] by " + e.getMessage() , e);
} }
return null; return null;
} }
@@ -125,7 +125,7 @@ public class CanalMessageSerializerUtil {
} }
} }
} catch (Exception e) { } catch (Exception e) {
throw new CanalClientException("deserializer failed", e); throw new CanalClientException("deserializer failed by " + e.getMessage(), e);
} }
} }
} }

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

@@ -61,4 +61,9 @@ public class PulsarMQConstants {
*/ */
public static final String PULSARMQ_ADMIN_SERVER_URL = ROOT + "." + "adminServerUrl"; public static final String PULSARMQ_ADMIN_SERVER_URL = ROOT + "." + "adminServerUrl";
/**
* Pulsar admin服务器地址
*/
public static final String PULSARMQ_LISTENER_NAME = ROOT + "." + "listenerName";
} }

View File

@@ -37,6 +37,11 @@ public class PulsarMQProducerConfig extends MQProperties {
*/ */
private String adminServerUrl; private String adminServerUrl;
/**
* listener name
*/
private String listenerName;
public String getServerUrl() { public String getServerUrl() {
return serverUrl; return serverUrl;
} }
@@ -68,4 +73,12 @@ public class PulsarMQProducerConfig extends MQProperties {
public void setAdminServerUrl(String adminServerUrl) { public void setAdminServerUrl(String adminServerUrl) {
this.adminServerUrl = adminServerUrl; this.adminServerUrl = adminServerUrl;
} }
public String getListenerName() {
return listenerName;
}
public void setListenerName(String listenerName) {
this.listenerName = listenerName;
}
} }

View File

@@ -54,6 +54,12 @@ public class CanalPulsarMQConsumer implements CanalMsgConsumer {
* 角色认证token * 角色认证token
*/ */
private String roleToken; private String roleToken;
/**
* listener name
*/
private String listenerName;
/** /**
* 订阅客户端名称 * 订阅客户端名称
*/ */
@@ -110,6 +116,7 @@ public class CanalPulsarMQConsumer implements CanalMsgConsumer {
} }
this.serviceUrl = properties.getProperty(PulsarMQConstants.PULSARMQ_SERVER_URL); this.serviceUrl = properties.getProperty(PulsarMQConstants.PULSARMQ_SERVER_URL);
this.roleToken = properties.getProperty(PulsarMQConstants.PULSARMQ_ROLE_TOKEN); this.roleToken = properties.getProperty(PulsarMQConstants.PULSARMQ_ROLE_TOKEN);
this.listenerName = properties.getProperty(PulsarMQConstants.PULSARMQ_LISTENER_NAME);
this.subscriptName = properties.getProperty(PulsarMQConstants.PULSARMQ_SUBSCRIPT_NAME); this.subscriptName = properties.getProperty(PulsarMQConstants.PULSARMQ_SUBSCRIPT_NAME);
// 采用groupId作为subscriptName,避免所有的都是同一个订阅者名称 // 采用groupId作为subscriptName,避免所有的都是同一个订阅者名称
if (StringUtils.isEmpty(this.subscriptName)) { if (StringUtils.isEmpty(this.subscriptName)) {
@@ -165,6 +172,9 @@ public class CanalPulsarMQConsumer implements CanalMsgConsumer {
if (StringUtils.isNotEmpty(roleToken)) { if (StringUtils.isNotEmpty(roleToken)) {
builder.authentication(AuthenticationFactory.token(roleToken)); builder.authentication(AuthenticationFactory.token(roleToken));
} }
if (StringUtils.isNotEmpty(listenerName)) {
builder.authentication(AuthenticationFactory.token(listenerName));
}
pulsarClient = builder.build(); pulsarClient = builder.build();
} catch (PulsarClientException e) { } catch (PulsarClientException e) {
throw new RuntimeException(e); throw new RuntimeException(e);

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;
@@ -72,6 +73,11 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
// 角色权限认证的token // 角色权限认证的token
builder.authentication(AuthenticationFactory.token(pulsarMQProducerConfig.getRoleToken())); builder.authentication(AuthenticationFactory.token(pulsarMQProducerConfig.getRoleToken()));
} }
if (StringUtils.isNotEmpty(pulsarMQProducerConfig.getListenerName())) {
// listener name
builder.listenerName(pulsarMQProducerConfig.getListenerName());
}
client = builder.build(); client = builder.build();
} catch (PulsarClientException e) { } catch (PulsarClientException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@@ -126,6 +132,11 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
if (!StringUtils.isEmpty(adminServerUrl)) { if (!StringUtils.isEmpty(adminServerUrl)) {
tmpProperties.setAdminServerUrl(adminServerUrl); tmpProperties.setAdminServerUrl(adminServerUrl);
} }
String listenerName = PropertiesUtils.getProperty(properties, PulsarMQConstants.PULSARMQ_LISTENER_NAME);
if (!StringUtils.isEmpty(listenerName)) {
tmpProperties.setListenerName(listenerName);
}
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Load pulsar properties ==> {}", JSON.toJSON(this.mqProperties)); logger.debug("Load pulsar properties ==> {}", JSON.toJSON(this.mqProperties));
} }
@@ -330,7 +341,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

@@ -9,6 +9,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import com.alibaba.otter.canal.common.utils.AddressUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -88,7 +89,7 @@ public class CanalRabbitMQConsumer implements CanalMsgConsumer {
} }
// 解析出端口 modified by 16075140 // 解析出端口 modified by 16075140
if (nameServer != null && nameServer.contains(":")) { if (nameServer != null && nameServer.contains(":")) {
String[] serverHostAndPort = nameServer.split(":"); String[] serverHostAndPort = AddressUtils.splitIPAndPort(nameServer);
factory.setHost(serverHostAndPort[0]); factory.setHost(serverHostAndPort[0]);
factory.setPort(Integer.parseInt(serverHostAndPort[1])); factory.setPort(Integer.parseInt(serverHostAndPort[1]));
} else { } else {

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;
@@ -13,6 +16,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.CanalException; import com.alibaba.otter.canal.common.CanalException;
import com.alibaba.otter.canal.common.utils.AddressUtils;
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.common.utils.PropertiesUtils;
import com.alibaba.otter.canal.connector.core.producer.AbstractMQProducer; import com.alibaba.otter.canal.connector.core.producer.AbstractMQProducer;
@@ -51,8 +55,14 @@ 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")) {
String[] serverHostAndPort = servers.split(":"); try {
factory.setUri(servers);
} catch (URISyntaxException | NoSuchAlgorithmException | KeyManagementException ex) {
throw new CanalException("failed to parse host", ex);
}
} else if (servers.contains(":")) {
String[] serverHostAndPort = AddressUtils.splitIPAndPort(servers);
factory.setHost(serverHostAndPort[0]); factory.setHost(serverHostAndPort[0]);
factory.setPort(Integer.parseInt(serverHostAndPort[1])); factory.setPort(Integer.parseInt(serverHostAndPort[1]));
} else { } else {
@@ -72,13 +82,18 @@ public class CanalRabbitMQProducer extends AbstractMQProducer implements CanalMQ
try { try {
connect = factory.newConnection(); connect = factory.newConnection();
channel = connect.createChannel(); channel = connect.createChannel();
channel.queueDeclare(rabbitMQProperties.getQueue(), true, false, false, null); String queue = rabbitMQProperties.getQueue();
channel.exchangeDeclare(rabbitMQProperties String exchange = rabbitMQProperties.getExchange();
.getExchange(), rabbitMQProperties.getDeliveryMode(), true, false, false, null); String deliveryMode = rabbitMQProperties.getDeliveryMode();
channel.queueBind(rabbitMQProperties.getQueue(), String routingKey = rabbitMQProperties.getRoutingKey();
rabbitMQProperties.getExchange(), if (!StringUtils.isEmpty(queue)) {
rabbitMQProperties.getRoutingKey()); channel.queueDeclare(queue, true, false, false, null);
}
if (!StringUtils.isEmpty(queue) && !StringUtils.isEmpty(exchange) && !StringUtils.isEmpty(deliveryMode)
&& !StringUtils.isEmpty(routingKey)) {
channel.exchangeDeclare(exchange, deliveryMode, true, false, false, null);
channel.queueBind(queue, exchange, routingKey);
}
} catch (IOException | TimeoutException ex) { } catch (IOException | TimeoutException ex) {
throw new CanalException("Start RabbitMQ producer error", ex); throw new CanalException("Start RabbitMQ producer error", ex);
} }
@@ -165,7 +180,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

@@ -6,6 +6,7 @@ import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import com.alibaba.otter.canal.common.utils.AddressUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import com.alibaba.otter.canal.client.CanalConnector; import com.alibaba.otter.canal.client.CanalConnector;
@@ -47,7 +48,7 @@ public class CanalTCPConsumer implements CanalMsgConsumer {
batchSize = Integer.parseInt(batchSizePro); batchSize = Integer.parseInt(batchSizePro);
} }
if (StringUtils.isNotBlank(host)) { if (StringUtils.isNotBlank(host)) {
String[] ipPort = host.split(":"); String[] ipPort = AddressUtils.splitIPAndPort(host);
SocketAddress sa = new InetSocketAddress(ipPort[0], Integer.parseInt(ipPort[1])); SocketAddress sa = new InetSocketAddress(ipPort[0], Integer.parseInt(ipPort[1]));
this.canalConnector = new SimpleCanalConnector(sa, username, password, destination); this.canalConnector = new SimpleCanalConnector(sa, username, password, destination);
} else { } else {

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,10 +1,8 @@
package com.taobao.tddl.dbsync.binlog; package com.taobao.tddl.dbsync.binlog;
import java.nio.charset.Charset; import static com.taobao.tddl.dbsync.binlog.event.RowsLogBuffer.*;
import static com.taobao.tddl.dbsync.binlog.event.RowsLogBuffer.appendNumber2; import java.nio.charset.Charset;
import static com.taobao.tddl.dbsync.binlog.event.RowsLogBuffer.appendNumber4;
import static com.taobao.tddl.dbsync.binlog.event.RowsLogBuffer.usecondsToStr;
/** /**
* 处理下MySQL json二进制转化为可读的字符串 * 处理下MySQL json二进制转化为可读的字符串
@@ -34,26 +32,25 @@ public class JsonConversion {
public static final char JSONB_FALSE_LITERAL = '\2'; public static final char JSONB_FALSE_LITERAL = '\2';
/* /*
* The size of offset or size fields in the small and the large storage * The size of offset or size fields in the small and the large storage format
* format for JSON objects and JSON arrays. * for JSON objects and JSON arrays.
*/ */
public static final int SMALL_OFFSET_SIZE = 2; public static final int SMALL_OFFSET_SIZE = 2;
public static final int LARGE_OFFSET_SIZE = 4; public static final int LARGE_OFFSET_SIZE = 4;
/* /*
* The size of key entries for objects when using the small storage format * The size of key entries for objects when using the small storage format or
* or the large storage format. In the small format it is 4 bytes (2 bytes * the large storage format. In the small format it is 4 bytes (2 bytes for key
* for key length and 2 bytes for key offset). In the large format it is 6 * length and 2 bytes for key offset). In the large format it is 6 (2 bytes for
* (2 bytes for length, 4 bytes for offset). * length, 4 bytes for offset).
*/ */
public static final int KEY_ENTRY_SIZE_SMALL = (2 + SMALL_OFFSET_SIZE); public static final int KEY_ENTRY_SIZE_SMALL = (2 + SMALL_OFFSET_SIZE);
public static final int KEY_ENTRY_SIZE_LARGE = (2 + LARGE_OFFSET_SIZE); public static final int KEY_ENTRY_SIZE_LARGE = (2 + LARGE_OFFSET_SIZE);
/* /*
* The size of value entries for objects or arrays. When using the small * The size of value entries for objects or arrays. When using the small storage
* storage format, the entry size is 3 (1 byte for type, 2 bytes for * format, the entry size is 3 (1 byte for type, 2 bytes for offset). When using
* offset). When using the large storage format, it is 5 (1 byte for type, 4 * the large storage format, it is 5 (1 byte for type, 4 bytes for offset).
* bytes for offset).
*/ */
public static final int VALUE_ENTRY_SIZE_SMALL = (1 + SMALL_OFFSET_SIZE); public static final int VALUE_ENTRY_SIZE_SMALL = (1 + SMALL_OFFSET_SIZE);
public static final int VALUE_ENTRY_SIZE_LARGE = (1 + LARGE_OFFSET_SIZE); public static final int VALUE_ENTRY_SIZE_LARGE = (1 + LARGE_OFFSET_SIZE);
@@ -160,13 +157,12 @@ public class JsonConversion {
if (len < n + str_len) { if (len < n + str_len) {
throw new IllegalArgumentException("illegal json data"); throw new IllegalArgumentException("illegal json data");
} }
return new Json_Value(Json_enum_type.STRING, buffer.rewind() return new Json_Value(Json_enum_type.STRING,
.forward((int) n) buffer.rewind().forward((int) n).getFixString((int) str_len, charset));
.getFixString((int) str_len, charset));
case JSONB_TYPE_OPAQUE: case JSONB_TYPE_OPAQUE:
/* /*
* There should always be at least one byte, which tells the * There should always be at least one byte, which tells the field type of the
* field type of the opaque value. * opaque value.
*/ */
// The type is encoded as a uint8 that maps to an // The type is encoded as a uint8 that maps to an
// enum_field_types. // enum_field_types.
@@ -291,7 +287,7 @@ public class JsonConversion {
if (i > 0) { if (i > 0) {
buf.append(", "); buf.append(", ");
} }
buf.append('"').append(key(i, charset)).append('"'); buf.append('"').append(escapse(key(i, charset))).append('"');
buf.append(": "); buf.append(": ");
element(i, charset).toJsonString(buf, charset); element(i, charset).toJsonString(buf, charset);
} }
@@ -368,45 +364,45 @@ public class JsonConversion {
buf.append('"').append(text).append('"'); buf.append('"').append(text).append('"');
} else if (m_field_type == LogEvent.MYSQL_TYPE_DATE || m_field_type == LogEvent.MYSQL_TYPE_DATETIME } else if (m_field_type == LogEvent.MYSQL_TYPE_DATE || m_field_type == LogEvent.MYSQL_TYPE_DATETIME
|| m_field_type == LogEvent.MYSQL_TYPE_TIMESTAMP) { || m_field_type == LogEvent.MYSQL_TYPE_TIMESTAMP) {
long packed_value = m_data.getLong64(); long packed_value = m_data.getLong64();
if (packed_value == 0) { if (packed_value == 0) {
text = "0000-00-00 00:00:00"; text = "0000-00-00 00:00:00";
} else { } else {
// 构造TimeStamp只处理到秒 // 构造TimeStamp只处理到秒
long ultime = Math.abs(packed_value); long ultime = Math.abs(packed_value);
long intpart = ultime >> 24; long intpart = ultime >> 24;
int frac = (int) (ultime % (1L << 24)); int frac = (int) (ultime % (1L << 24));
long ymd = intpart >> 17; long ymd = intpart >> 17;
long ym = ymd >> 5; long ym = ymd >> 5;
long hms = intpart % (1 << 17); long hms = intpart % (1 << 17);
// text = // text =
// String.format("%04d-%02d-%02d %02d:%02d:%02d", // String.format("%04d-%02d-%02d %02d:%02d:%02d",
// (int) (ym / 13), // (int) (ym / 13),
// (int) (ym % 13), // (int) (ym % 13),
// (int) (ymd % (1 << 5)), // (int) (ymd % (1 << 5)),
// (int) (hms >> 12), // (int) (hms >> 12),
// (int) ((hms >> 6) % (1 << 6)), // (int) ((hms >> 6) % (1 << 6)),
// (int) (hms % (1 << 6))); // (int) (hms % (1 << 6)));
StringBuilder builder = new StringBuilder(26); StringBuilder builder = new StringBuilder(26);
appendNumber4(builder, (int) (ym / 13)); appendNumber4(builder, (int) (ym / 13));
builder.append('-'); builder.append('-');
appendNumber2(builder, (int) (ym % 13)); appendNumber2(builder, (int) (ym % 13));
builder.append('-'); builder.append('-');
appendNumber2(builder, (int) (ymd % (1 << 5))); appendNumber2(builder, (int) (ymd % (1 << 5)));
builder.append(' '); builder.append(' ');
appendNumber2(builder, (int) (hms >> 12)); appendNumber2(builder, (int) (hms >> 12));
builder.append(':'); builder.append(':');
appendNumber2(builder, (int) ((hms >> 6) % (1 << 6))); appendNumber2(builder, (int) ((hms >> 6) % (1 << 6)));
builder.append(':'); builder.append(':');
appendNumber2(builder, (int) (hms % (1 << 6))); appendNumber2(builder, (int) (hms % (1 << 6)));
builder.append('.').append(usecondsToStr(frac, 6)); builder.append('.').append(usecondsToStr(frac, 6));
text = builder.toString(); text = builder.toString();
} }
buf.append('"').append(text).append('"'); buf.append('"').append(text).append('"');
} else { } else {
text = m_data.getFixString((int) m_length, charset); text = m_data.getFixString((int) m_length, charset);
buf.append('"').append(escapse(text)).append('"'); buf.append('"').append(escapse(text)).append('"');
} }
break; break;
case STRING: case STRING:
@@ -452,7 +448,8 @@ public class JsonConversion {
} }
public static enum Json_enum_type { public static enum Json_enum_type {
OBJECT, ARRAY, STRING, INT, UINT, DOUBLE, LITERAL_NULL, LITERAL_TRUE, LITERAL_FALSE, OPAQUE, ERROR OBJECT, ARRAY, STRING, INT, UINT, DOUBLE, LITERAL_NULL, LITERAL_TRUE,
LITERAL_FALSE, OPAQUE, ERROR
} }
} }

View File

@@ -3,6 +3,7 @@ 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;
@@ -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;
@@ -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;
@@ -71,8 +69,7 @@ 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();
@@ -105,8 +102,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 - * @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);
@@ -115,8 +111,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 - * @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) {
@@ -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;

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