mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
Compare commits
90 Commits
canal-1.1.
...
canal-1.1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1413cc9196 | ||
|
|
04c11ec8eb | ||
|
|
54145ced5e | ||
|
|
fb35dd4254 | ||
|
|
e233bec66a | ||
|
|
67e0928d80 | ||
|
|
93dac2d335 | ||
|
|
9baeff55e4 | ||
|
|
ca79258e4f | ||
|
|
0e4c5c40d7 | ||
|
|
c65477d97a | ||
|
|
fc3cfc5c0c | ||
|
|
b4074206ca | ||
|
|
98677c5bc8 | ||
|
|
2c7e918f30 | ||
|
|
163f719971 | ||
|
|
55a5e7592b | ||
|
|
259008a7cd | ||
|
|
26ba84e27e | ||
|
|
c8ef9552ec | ||
|
|
bff394042a | ||
|
|
b322201889 | ||
|
|
ea8949298f | ||
|
|
ba924b9f1d | ||
|
|
7e0061fe93 | ||
|
|
6440b744f2 | ||
|
|
066b022bb3 | ||
|
|
738f41049f | ||
|
|
0f6b9d156a | ||
|
|
ed754b5203 | ||
|
|
11f89163c1 | ||
|
|
cda330e49e | ||
|
|
f9f810226d | ||
|
|
d4a09704f2 | ||
|
|
34e9440383 | ||
|
|
d623bea71a | ||
|
|
5ea9259f0d | ||
|
|
a216b55010 | ||
|
|
2786e06ccf | ||
|
|
63dfbf65df | ||
|
|
d90066e01b | ||
|
|
69cfbabb10 | ||
|
|
ab30f3bd8d | ||
|
|
681d03fbe7 | ||
|
|
f13cc4ee9f | ||
|
|
252ca0279f | ||
|
|
7241af84f7 | ||
|
|
159457cccd | ||
|
|
d8a70d0c2f | ||
|
|
ec247114fd | ||
|
|
f067f761c7 | ||
|
|
d4c8780f26 | ||
|
|
894ef6e610 | ||
|
|
cc75d3dda4 | ||
|
|
d8169cbf71 | ||
|
|
fccc6bc816 | ||
|
|
97aa441bca | ||
|
|
b8c2a757a0 | ||
|
|
c8de48bbd5 | ||
|
|
08239ef23f | ||
|
|
04a2b6f941 | ||
|
|
5f667c3574 | ||
|
|
bfb8cf4190 | ||
|
|
18c38ee057 | ||
|
|
82b3aed62b | ||
|
|
a04a1912a8 | ||
|
|
5d7d19fdb1 | ||
|
|
9edbe7e827 | ||
|
|
b52c5d149b | ||
|
|
aada80ba38 | ||
|
|
a10698e4a8 | ||
|
|
9d0a37241f | ||
|
|
41a2a87a91 | ||
|
|
62c4333790 | ||
|
|
ea8fb5e5b8 | ||
|
|
cd798b71e9 | ||
|
|
b937ee613d | ||
|
|
9c6f6f5cdf | ||
|
|
869e831b04 | ||
|
|
f7a4979b15 | ||
|
|
afef45500e | ||
|
|
4c7cdbc7f3 | ||
|
|
279d503cf4 | ||
|
|
272b1d5d11 | ||
|
|
fa0c08c170 | ||
|
|
10ff148aba | ||
|
|
571fa0d6fb | ||
|
|
aa29f12d96 | ||
|
|
3bceb07c44 | ||
|
|
c16d6e7729 |
@@ -4,6 +4,8 @@
|
|||||||

|

|
||||||
[](http://isitmaintained.com/project/alibaba/canal "average time to resolve an issue")
|
[](http://isitmaintained.com/project/alibaba/canal "average time to resolve an issue")
|
||||||
[](http://isitmaintained.com/project/alibaba/canal "percentage of issues still open")
|
[](http://isitmaintained.com/project/alibaba/canal "percentage of issues still open")
|
||||||
|
[](https://opensource.alibaba.com/contribution_leaderboard/details?projectValue=canal)
|
||||||
|
|
||||||
|
|
||||||
## 简介
|
## 简介
|
||||||
|
|
||||||
@@ -91,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)
|
||||||
@@ -101,3 +107,5 @@ canal 作为 MySQL binlog 增量获取和解析工具,可将变更记录投递
|
|||||||
|
|
||||||
## 问题反馈
|
## 问题反馈
|
||||||
- 报告 issue: [github issues](https://github.com/alibaba/canal/issues)
|
- 报告 issue: [github issues](https://github.com/alibaba/canal/issues)
|
||||||
|
|
||||||
|
本项目的Issues会被同步沉淀至[阿里云开发者社区](https://developer.aliyun.com/ask)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const validatePassword = (rule, value, callback) => {
|
const validatePassword = (rule, value, callback) => {
|
||||||
if (value.length < 6) {
|
if (value.length < 5) {
|
||||||
callback(new Error('The password can not be less than 6 digits'))
|
callback(new Error('The password can not be less than 5 digits'))
|
||||||
} else {
|
} else {
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ public class SimpleAdminConnectors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String getDesensitizationPassword(String password) {
|
private static String getDesensitizationPassword(String password) {
|
||||||
String defaultPassword = "******";
|
String defaultPassword = "*****";
|
||||||
|
|
||||||
if (Strings.isNullOrEmpty(password) || password.length() < 6) {
|
if (Strings.isNullOrEmpty(password) || password.length() < 5) {
|
||||||
return defaultPassword;
|
return defaultPassword;
|
||||||
} else {
|
} else {
|
||||||
return String.format("%s******", password.substring(0, 3));
|
return String.format("%s*****", password.substring(0, 3));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
|
|
||||||
private static byte[] seeds = "canal is best!".getBytes();
|
private static byte[] seeds = "canal is best!".getBytes();
|
||||||
|
|
||||||
private static final Integer PASSWORD_LENGTH = 6;
|
private static final Integer PASSWORD_LENGTH = 5;
|
||||||
|
|
||||||
public User find4Login(String username, String password) {
|
public User find4Login(String username, String password) {
|
||||||
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
|
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ spring.datasource:
|
|||||||
|
|
||||||
canal:
|
canal:
|
||||||
adminUser: admin
|
adminUser: admin
|
||||||
adminPasswd: admin
|
adminPasswd:
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ canal.zkServers =
|
|||||||
# flush data to zk
|
# flush data to zk
|
||||||
canal.zookeeper.flush.period = 1000
|
canal.zookeeper.flush.period = 1000
|
||||||
canal.withoutNetty = false
|
canal.withoutNetty = false
|
||||||
# tcp, kafka, rocketMQ, rabbitMQ
|
# tcp, kafka, rocketMQ, rabbitMQ, pulsarMQ
|
||||||
canal.serverMode = tcp
|
canal.serverMode = tcp
|
||||||
# flush meta cursor/parse position to file
|
# flush meta cursor/parse position to file
|
||||||
canal.file.data.dir = ${canal.conf.dir}
|
canal.file.data.dir = ${canal.conf.dir}
|
||||||
@@ -96,7 +96,7 @@ canal.instance.tsdb.snapshot.expire = 360
|
|||||||
#################################################
|
#################################################
|
||||||
######### destinations #############
|
######### destinations #############
|
||||||
#################################################
|
#################################################
|
||||||
canal.destinations =
|
canal.destinations =
|
||||||
# conf root dir
|
# conf root dir
|
||||||
canal.conf.dir = ../conf
|
canal.conf.dir = ../conf
|
||||||
# auto scan instance dir add/remove and start/stop instance
|
# auto scan instance dir add/remove and start/stop instance
|
||||||
@@ -148,8 +148,13 @@ kafka.max.in.flight.requests.per.connection = 1
|
|||||||
kafka.retries = 0
|
kafka.retries = 0
|
||||||
|
|
||||||
kafka.kerberos.enable = false
|
kafka.kerberos.enable = false
|
||||||
kafka.kerberos.krb5.file = "../conf/kerberos/krb5.conf"
|
kafka.kerberos.krb5.file = ../conf/kerberos/krb5.conf
|
||||||
kafka.kerberos.jaas.file = "../conf/kerberos/jaas.conf"
|
kafka.kerberos.jaas.file = ../conf/kerberos/jaas.conf
|
||||||
|
|
||||||
|
# sasl demo
|
||||||
|
# kafka.sasl.jaas.config = org.apache.kafka.common.security.scram.ScramLoginModule required \\n username=\"alice\" \\npassword="alice-secret\";
|
||||||
|
# kafka.sasl.mechanism = SCRAM-SHA-512
|
||||||
|
# kafka.security.protocol = SASL_PLAINTEXT
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
######### RocketMQ #############
|
######### RocketMQ #############
|
||||||
@@ -161,7 +166,7 @@ rocketmq.namespace =
|
|||||||
rocketmq.namesrv.addr = 127.0.0.1:9876
|
rocketmq.namesrv.addr = 127.0.0.1:9876
|
||||||
rocketmq.retry.times.when.send.failed = 0
|
rocketmq.retry.times.when.send.failed = 0
|
||||||
rocketmq.vip.channel.enabled = false
|
rocketmq.vip.channel.enabled = false
|
||||||
rocketmq.tag =
|
rocketmq.tag =
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
######### RabbitMQ #############
|
######### RabbitMQ #############
|
||||||
@@ -171,4 +176,12 @@ rabbitmq.virtual.host =
|
|||||||
rabbitmq.exchange =
|
rabbitmq.exchange =
|
||||||
rabbitmq.username =
|
rabbitmq.username =
|
||||||
rabbitmq.password =
|
rabbitmq.password =
|
||||||
rabbitmq.deliveryMode =
|
rabbitmq.deliveryMode =
|
||||||
|
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
######### Pulsar #############
|
||||||
|
##################################################
|
||||||
|
pulsarmq.serverUrl =
|
||||||
|
pulsarmq.roleToken =
|
||||||
|
pulsarmq.topicTenantPrefix =
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
<?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" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
<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>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal</artifactId>
|
||||||
|
<version>1.1.8-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</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>
|
||||||
@@ -18,7 +24,6 @@
|
|||||||
<java_source_version>1.8</java_source_version>
|
<java_source_version>1.8</java_source_version>
|
||||||
<java_target_version>1.8</java_target_version>
|
<java_target_version>1.8</java_target_version>
|
||||||
<file_encoding>UTF-8</file_encoding>
|
<file_encoding>UTF-8</file_encoding>
|
||||||
<log4j_version>2.17.0</log4j_version>
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@@ -26,87 +31,25 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>org.springframework.cloud</groupId>
|
||||||
<artifactId>spring-cloud-context</artifactId>
|
<artifactId>spring-cloud-context</artifactId>
|
||||||
<version>2.0.0.RELEASE</version>
|
<version>3.0.6</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-core</artifactId>
|
|
||||||
<version>5.0.5.RELEASE</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-aop</artifactId>
|
|
||||||
<version>5.0.5.RELEASE</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-context</artifactId>
|
|
||||||
<version>5.0.5.RELEASE</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-jdbc</artifactId>
|
|
||||||
<version>5.0.5.RELEASE</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-orm</artifactId>
|
|
||||||
<version>5.0.5.RELEASE</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-test</artifactId>
|
|
||||||
<version>5.0.5.RELEASE</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-dependencies</artifactId>
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
<version>2.0.1.RELEASE</version>
|
<version>2.5.4</version>
|
||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>mysql</groupId>
|
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
|
||||||
<version>5.1.48</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
<artifactId>caffeine</artifactId>
|
<artifactId>caffeine</artifactId>
|
||||||
<version>2.6.2</version>
|
<version>2.6.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>commons-beanutils</groupId>
|
|
||||||
<artifactId>commons-beanutils</artifactId>
|
|
||||||
<version>1.8.2</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>commons-lang</groupId>
|
|
||||||
<artifactId>commons-lang</artifactId>
|
|
||||||
<version>2.6</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.ebean</groupId>
|
<groupId>io.ebean</groupId>
|
||||||
<artifactId>ebean</artifactId>
|
<artifactId>ebean</artifactId>
|
||||||
<version>11.41.1</version>
|
<version>11.41.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
|
||||||
<artifactId>log4j-core</artifactId>
|
|
||||||
<version>${log4j_version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
|
||||||
<artifactId>log4j-api</artifactId>
|
|
||||||
<version>${log4j_version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
|
||||||
<artifactId>log4j-to-slf4j</artifactId>
|
|
||||||
<version>${log4j_version}</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|||||||
84
client-adapter/clickhouse/pom.xml
Normal file
84
client-adapter/clickhouse/pom.xml
Normal 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>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.clickhouse.service;
|
||||||
|
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.config.MappingConfig.DbMapping;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.clickhouse.support.SyncUtil;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.AbstractEtlService;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.AdapterConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ClickHouse ETL 操作业务类
|
||||||
|
*
|
||||||
|
* @author: Xander
|
||||||
|
* @date: Created in 2023/11/10 22:23
|
||||||
|
* @email: zhrunxin33@gmail.com
|
||||||
|
* @version 1.1.8
|
||||||
|
*/
|
||||||
|
public class ClickHouseEtlService extends AbstractEtlService {
|
||||||
|
|
||||||
|
private DataSource targetDS;
|
||||||
|
private MappingConfig config;
|
||||||
|
|
||||||
|
public ClickHouseEtlService(DataSource targetDS, MappingConfig config){
|
||||||
|
super("CLICKHOUSE", config);
|
||||||
|
this.targetDS = targetDS;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入数据
|
||||||
|
*/
|
||||||
|
public EtlResult importData(List<String> params) {
|
||||||
|
DbMapping dbMapping = config.getDbMapping();
|
||||||
|
String sql = "SELECT * FROM " + dbMapping.getDatabase() + "." + dbMapping.getTable();
|
||||||
|
return importData(sql, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行导入
|
||||||
|
*/
|
||||||
|
protected boolean executeSqlImport(DataSource srcDS, String sql, List<Object> values,
|
||||||
|
AdapterConfig.AdapterMapping mapping, AtomicLong impCount, List<String> errMsg) {
|
||||||
|
try {
|
||||||
|
DbMapping dbMapping = (DbMapping) mapping;
|
||||||
|
Map<String, String> columnsMap = new LinkedHashMap<>();
|
||||||
|
Map<String, Integer> columnType = new LinkedHashMap<>();
|
||||||
|
DruidDataSource dataSource = (DruidDataSource) srcDS;
|
||||||
|
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
|
||||||
|
|
||||||
|
Util.sqlRS(targetDS,
|
||||||
|
"SELECT * FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " LIMIT 1 ",
|
||||||
|
rs -> {
|
||||||
|
try {
|
||||||
|
|
||||||
|
ResultSetMetaData rsd = rs.getMetaData();
|
||||||
|
int columnCount = rsd.getColumnCount();
|
||||||
|
List<String> columns = new ArrayList<>();
|
||||||
|
for (int i = 1; i <= columnCount; i++) {
|
||||||
|
columnType.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i));
|
||||||
|
columns.add(rsd.getColumnName(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
columnsMap.putAll(SyncUtil.getColumnsMap(dbMapping, columns));
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Util.sqlRS(srcDS, sql, values, rs -> {
|
||||||
|
int idx = 1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean completed = false;
|
||||||
|
|
||||||
|
StringBuilder insertSql = new StringBuilder();
|
||||||
|
insertSql.append("INSERT INTO ")
|
||||||
|
.append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()))
|
||||||
|
.append(" (");
|
||||||
|
columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(backtick)
|
||||||
|
.append(targetColumnName)
|
||||||
|
.append(backtick)
|
||||||
|
.append(","));
|
||||||
|
|
||||||
|
int len = insertSql.length();
|
||||||
|
insertSql.delete(len - 1, len).append(") VALUES (");
|
||||||
|
int mapLen = columnsMap.size();
|
||||||
|
for (int i = 0; i < mapLen; i++) {
|
||||||
|
insertSql.append("?,");
|
||||||
|
}
|
||||||
|
len = insertSql.length();
|
||||||
|
insertSql.delete(len - 1, len).append(")");
|
||||||
|
logger.info("executeSqlImport sql:{}", insertSql.toString());
|
||||||
|
try (Connection connTarget = targetDS.getConnection();
|
||||||
|
PreparedStatement pstmt = connTarget.prepareStatement(insertSql.toString())) {
|
||||||
|
connTarget.setAutoCommit(false);
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
completed = false;
|
||||||
|
|
||||||
|
pstmt.clearParameters();
|
||||||
|
|
||||||
|
// 删除数据
|
||||||
|
Map<String, Object> pkVal = new LinkedHashMap<>();
|
||||||
|
StringBuilder deleteSql = new StringBuilder(
|
||||||
|
"ALTER TABLE " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())
|
||||||
|
+ " DELETE WHERE ");
|
||||||
|
appendCondition(dbMapping, deleteSql, pkVal, rs, backtick);
|
||||||
|
try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) {
|
||||||
|
int k = 1;
|
||||||
|
for (Object val : pkVal.values()) {
|
||||||
|
pstmt2.setObject(k++, val);
|
||||||
|
}
|
||||||
|
pstmt2.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
for (Map.Entry<String, String> entry : columnsMap.entrySet()) {
|
||||||
|
String targetClolumnName = entry.getKey();
|
||||||
|
String srcColumnName = entry.getValue();
|
||||||
|
if (srcColumnName == null) {
|
||||||
|
srcColumnName = targetClolumnName;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer type = columnType.get(targetClolumnName.toLowerCase());
|
||||||
|
Object value = rs.getObject(srcColumnName);
|
||||||
|
if (value != null) {
|
||||||
|
SyncUtil.setPStmt(type, pstmt, value, i);
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
pstmt.execute();
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Insert into target table, sql: {}", insertSql);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx % dbMapping.getCommitBatch() == 0) {
|
||||||
|
connTarget.commit();
|
||||||
|
completed = true;
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
impCount.incrementAndGet();
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("successful import count:" + impCount.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!completed) {
|
||||||
|
connTarget.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(dbMapping.getTable() + " etl failed! ==>" + e.getMessage(), e);
|
||||||
|
errMsg.add(dbMapping.getTable() + " etl failed! ==>" + e.getMessage());
|
||||||
|
}
|
||||||
|
return idx;
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拼接目标表主键where条件
|
||||||
|
*/
|
||||||
|
private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map<String, Object> values,
|
||||||
|
ResultSet rs, String backtick) throws SQLException {
|
||||||
|
// 拼接主键
|
||||||
|
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) {
|
||||||
|
String targetColumnName = entry.getKey();
|
||||||
|
String srcColumnName = entry.getValue();
|
||||||
|
if (srcColumnName == null) {
|
||||||
|
srcColumnName = targetColumnName;
|
||||||
|
}
|
||||||
|
sql.append(backtick).append(targetColumnName).append(backtick).append("=? AND ");
|
||||||
|
values.put(targetColumnName, rs.getObject(srcColumnName));
|
||||||
|
}
|
||||||
|
int len = sql.length();
|
||||||
|
sql.delete(len - 4, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
clickhouse=com.alibaba.otter.canal.client.adapter.clickhouse.ClickHouseAdapter
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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,11 +54,15 @@
|
|||||||
<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>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public class ProxyOuterAdapter implements OuterAdapter {
|
||||||
|
|
||||||
|
private OuterAdapter outerAdapter;
|
||||||
|
|
||||||
|
public ProxyOuterAdapter(OuterAdapter outerAdapter) {
|
||||||
|
this.outerAdapter = outerAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClassLoader changeCL() {
|
||||||
|
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||||
|
Thread.currentThread().setContextClassLoader(outerAdapter.getClass().getClassLoader());
|
||||||
|
return cl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void revertCL(ClassLoader cl) {
|
||||||
|
Thread.currentThread().setContextClassLoader(cl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(OuterAdapterConfig configuration, Properties envProperties) {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
outerAdapter.init(configuration, envProperties);
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sync(List<Dml> dmls) {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
outerAdapter.sync(dmls);
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
outerAdapter.destroy();
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EtlResult etl(String task, List<String> params) {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
return OuterAdapter.super.etl(task, params);
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> count(String task) {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
return OuterAdapter.super.count(task);
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDestination(String task) {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
return OuterAdapter.super.getDestination(task);
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,6 +73,9 @@ public class YamlUtils {
|
|||||||
PropertySource<?> propertySource = propertySources.get(0);
|
PropertySource<?> propertySource = propertySources.get(0);
|
||||||
|
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
|
if (baseProperties != null) {
|
||||||
|
properties.putAll(baseProperties);
|
||||||
|
}
|
||||||
|
|
||||||
properties.putAll((Map<?, ?>) propertySource.getSource());
|
properties.putAll((Map<?, ?>) propertySource.getSource());
|
||||||
|
|
||||||
@@ -82,12 +85,7 @@ public class YamlUtils {
|
|||||||
entry.setValue(((OriginTrackedValue) value).getValue());
|
entry.setValue(((OriginTrackedValue) value).getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseProperties != null) {
|
|
||||||
baseProperties.putAll(properties);
|
|
||||||
properties = baseProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigurationPropertySource sources = new MapConfigurationPropertySource(properties);
|
ConfigurationPropertySource sources = new MapConfigurationPropertySource(properties);
|
||||||
Binder binder = new Binder(sources);
|
Binder binder = new Binder(sources);
|
||||||
return binder.bind(prefix, Bindable.of(clazz)).get();
|
return binder.bind(prefix, Bindable.of(clazz)).get();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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<>();
|
||||||
|
|||||||
@@ -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 =
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.sql.SQLException;
|
|||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ public class Common {
|
|||||||
outerAdapterConfig.setProperties(properties);
|
outerAdapterConfig.setProperties(properties);
|
||||||
|
|
||||||
ES6xAdapter esAdapter = new ES6xAdapter();
|
ES6xAdapter esAdapter = new ES6xAdapter();
|
||||||
esAdapter.init(outerAdapterConfig, null);
|
esAdapter.init(outerAdapterConfig, new Properties());
|
||||||
return esAdapter;
|
return esAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -66,17 +66,17 @@ public class ES7xTemplate implements ESTemplate {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void insert(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
|
public void insert(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
|
||||||
if (mapping.get_id() != null) {
|
if (mapping.getId() != null) {
|
||||||
String parentVal = (String) esFieldData.remove("$parent_routing");
|
String parentVal = (String) esFieldData.remove("$parent_routing");
|
||||||
if (mapping.isUpsert()) {
|
if (mapping.isUpsert()) {
|
||||||
ESUpdateRequest updateRequest = esConnection.new ES7xUpdateRequest(mapping.get_index(),
|
ESUpdateRequest updateRequest = esConnection.new ES7xUpdateRequest(mapping.getIndex(),
|
||||||
pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
|
pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
|
||||||
if (StringUtils.isNotEmpty(parentVal)) {
|
if (StringUtils.isNotEmpty(parentVal)) {
|
||||||
updateRequest.setRouting(parentVal);
|
updateRequest.setRouting(parentVal);
|
||||||
}
|
}
|
||||||
getBulk().add(updateRequest);
|
getBulk().add(updateRequest);
|
||||||
} else {
|
} else {
|
||||||
ESIndexRequest indexRequest = esConnection.new ES7xIndexRequest(mapping.get_index(), pkVal.toString())
|
ESIndexRequest indexRequest = esConnection.new ES7xIndexRequest(mapping.getIndex(), pkVal.toString())
|
||||||
.setSource(esFieldData);
|
.setSource(esFieldData);
|
||||||
if (StringUtils.isNotEmpty(parentVal)) {
|
if (StringUtils.isNotEmpty(parentVal)) {
|
||||||
indexRequest.setRouting(parentVal);
|
indexRequest.setRouting(parentVal);
|
||||||
@@ -85,13 +85,13 @@ public class ES7xTemplate implements ESTemplate {
|
|||||||
}
|
}
|
||||||
commitBulk();
|
commitBulk();
|
||||||
} else {
|
} else {
|
||||||
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.get_index())
|
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex())
|
||||||
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
|
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
|
||||||
.size(10000);
|
.size(10000);
|
||||||
SearchResponse response = esSearchRequest.getResponse();
|
SearchResponse response = esSearchRequest.getResponse();
|
||||||
|
|
||||||
for (SearchHit hit : response.getHits()) {
|
for (SearchHit hit : response.getHits()) {
|
||||||
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.get_index(),
|
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.getIndex(),
|
||||||
hit.getId()).setDoc(esFieldData);
|
hit.getId()).setDoc(esFieldData);
|
||||||
getBulk().add(esUpdateRequest);
|
getBulk().add(esUpdateRequest);
|
||||||
commitBulk();
|
commitBulk();
|
||||||
@@ -148,18 +148,18 @@ public class ES7xTemplate implements ESTemplate {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
|
public void delete(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
|
||||||
if (mapping.get_id() != null) {
|
if (mapping.getId() != null) {
|
||||||
ESDeleteRequest esDeleteRequest = this.esConnection.new ES7xDeleteRequest(mapping.get_index(),
|
ESDeleteRequest esDeleteRequest = this.esConnection.new ES7xDeleteRequest(mapping.getIndex(),
|
||||||
pkVal.toString());
|
pkVal.toString());
|
||||||
getBulk().add(esDeleteRequest);
|
getBulk().add(esDeleteRequest);
|
||||||
commitBulk();
|
commitBulk();
|
||||||
} else {
|
} else {
|
||||||
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.get_index())
|
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex())
|
||||||
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
|
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
|
||||||
.size(10000);
|
.size(10000);
|
||||||
SearchResponse response = esSearchRequest.getResponse();
|
SearchResponse response = esSearchRequest.getResponse();
|
||||||
for (SearchHit hit : response.getHits()) {
|
for (SearchHit hit : response.getHits()) {
|
||||||
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.get_index(),
|
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.getIndex(),
|
||||||
hit.getId()).setDoc(esFieldData);
|
hit.getId()).setDoc(esFieldData);
|
||||||
getBulk().add(esUpdateRequest);
|
getBulk().add(esUpdateRequest);
|
||||||
commitBulk();
|
commitBulk();
|
||||||
@@ -204,7 +204,7 @@ public class ES7xTemplate implements ESTemplate {
|
|||||||
public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet,
|
public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet,
|
||||||
Map<String, Object> esFieldData) throws SQLException {
|
Map<String, Object> esFieldData) throws SQLException {
|
||||||
SchemaItem schemaItem = mapping.getSchemaItem();
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
|
String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
|
||||||
Object resultIdVal = null;
|
Object resultIdVal = null;
|
||||||
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
|
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
|
||||||
@@ -213,7 +213,7 @@ public class ES7xTemplate implements ESTemplate {
|
|||||||
resultIdVal = value;
|
resultIdVal = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fieldItem.getFieldName().equals(mapping.get_id())
|
if (!fieldItem.getFieldName().equals(mapping.getId())
|
||||||
&& !mapping.getSkips().contains(fieldItem.getFieldName())) {
|
&& !mapping.getSkips().contains(fieldItem.getFieldName())) {
|
||||||
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
|
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
|
||||||
}
|
}
|
||||||
@@ -228,7 +228,7 @@ public class ES7xTemplate implements ESTemplate {
|
|||||||
@Override
|
@Override
|
||||||
public Object getIdValFromRS(ESMapping mapping, ResultSet resultSet) throws SQLException {
|
public Object getIdValFromRS(ESMapping mapping, ResultSet resultSet) throws SQLException {
|
||||||
SchemaItem schemaItem = mapping.getSchemaItem();
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
|
String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
|
||||||
Object resultIdVal = null;
|
Object resultIdVal = null;
|
||||||
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
|
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
|
||||||
@@ -245,7 +245,7 @@ public class ES7xTemplate implements ESTemplate {
|
|||||||
public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld,
|
public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld,
|
||||||
Map<String, Object> esFieldData) throws SQLException {
|
Map<String, Object> esFieldData) throws SQLException {
|
||||||
SchemaItem schemaItem = mapping.getSchemaItem();
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
|
String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
|
||||||
Object resultIdVal = null;
|
Object resultIdVal = null;
|
||||||
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
if (fieldItem.getFieldName().equals(idFieldName)) {
|
if (fieldItem.getFieldName().equals(idFieldName)) {
|
||||||
@@ -289,7 +289,7 @@ public class ES7xTemplate implements ESTemplate {
|
|||||||
@Override
|
@Override
|
||||||
public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> esFieldData) {
|
public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> esFieldData) {
|
||||||
SchemaItem schemaItem = mapping.getSchemaItem();
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
|
String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
|
||||||
Object resultIdVal = null;
|
Object resultIdVal = null;
|
||||||
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
String columnName = fieldItem.getColumnItems().iterator().next().getColumnName();
|
String columnName = fieldItem.getColumnItems().iterator().next().getColumnName();
|
||||||
@@ -299,7 +299,7 @@ public class ES7xTemplate implements ESTemplate {
|
|||||||
resultIdVal = value;
|
resultIdVal = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fieldItem.getFieldName().equals(mapping.get_id())
|
if (!fieldItem.getFieldName().equals(mapping.getId())
|
||||||
&& !mapping.getSkips().contains(fieldItem.getFieldName())) {
|
&& !mapping.getSkips().contains(fieldItem.getFieldName())) {
|
||||||
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
|
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
|
||||||
}
|
}
|
||||||
@@ -314,7 +314,7 @@ public class ES7xTemplate implements ESTemplate {
|
|||||||
public Object getESDataFromDmlData(ESMapping mapping,String owner, Map<String, Object> dmlData, Map<String, Object> dmlOld,
|
public Object getESDataFromDmlData(ESMapping mapping,String owner, Map<String, Object> dmlData, Map<String, Object> dmlOld,
|
||||||
Map<String, Object> esFieldData) {
|
Map<String, Object> esFieldData) {
|
||||||
SchemaItem schemaItem = mapping.getSchemaItem();
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
|
String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
|
||||||
Object resultIdVal = null;
|
Object resultIdVal = null;
|
||||||
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
ColumnItem columnItem = fieldItem.getColumnItems().iterator().next();
|
ColumnItem columnItem = fieldItem.getColumnItems().iterator().next();
|
||||||
@@ -348,17 +348,17 @@ public class ES7xTemplate implements ESTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void append4Update(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
|
private void append4Update(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
|
||||||
if (mapping.get_id() != null) {
|
if (mapping.getId() != null) {
|
||||||
String parentVal = (String) esFieldData.remove("$parent_routing");
|
String parentVal = (String) esFieldData.remove("$parent_routing");
|
||||||
if (mapping.isUpsert()) {
|
if (mapping.isUpsert()) {
|
||||||
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.get_index(),
|
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.getIndex(),
|
||||||
pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
|
pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
|
||||||
if (StringUtils.isNotEmpty(parentVal)) {
|
if (StringUtils.isNotEmpty(parentVal)) {
|
||||||
esUpdateRequest.setRouting(parentVal);
|
esUpdateRequest.setRouting(parentVal);
|
||||||
}
|
}
|
||||||
getBulk().add(esUpdateRequest);
|
getBulk().add(esUpdateRequest);
|
||||||
} else {
|
} else {
|
||||||
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.get_index(),
|
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.getIndex(),
|
||||||
pkVal.toString()).setDoc(esFieldData);
|
pkVal.toString()).setDoc(esFieldData);
|
||||||
if (StringUtils.isNotEmpty(parentVal)) {
|
if (StringUtils.isNotEmpty(parentVal)) {
|
||||||
esUpdateRequest.setRouting(parentVal);
|
esUpdateRequest.setRouting(parentVal);
|
||||||
@@ -366,12 +366,12 @@ public class ES7xTemplate implements ESTemplate {
|
|||||||
getBulk().add(esUpdateRequest);
|
getBulk().add(esUpdateRequest);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.get_index())
|
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex())
|
||||||
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
|
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
|
||||||
.size(10000);
|
.size(10000);
|
||||||
SearchResponse response = esSearchRequest.getResponse();
|
SearchResponse response = esSearchRequest.getResponse();
|
||||||
for (SearchHit hit : response.getHits()) {
|
for (SearchHit hit : response.getHits()) {
|
||||||
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.get_index(),
|
ESUpdateRequest esUpdateRequest = this.esConnection.new ES7xUpdateRequest(mapping.getIndex(),
|
||||||
hit.getId()).setDoc(esFieldData);
|
hit.getId()).setDoc(esFieldData);
|
||||||
getBulk().add(esUpdateRequest);
|
getBulk().add(esUpdateRequest);
|
||||||
}
|
}
|
||||||
@@ -387,15 +387,15 @@ public class ES7xTemplate implements ESTemplate {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private String getEsType(ESMapping mapping, String fieldName) {
|
private String getEsType(ESMapping mapping, String fieldName) {
|
||||||
String key = mapping.get_index() + "-" + mapping.get_type();
|
String key = mapping.getIndex() + "-" + mapping.getType();
|
||||||
Map<String, String> fieldType = esFieldTypes.get(key);
|
Map<String, String> fieldType = esFieldTypes.get(key);
|
||||||
if (fieldType != null) {
|
if (fieldType != null) {
|
||||||
return fieldType.get(fieldName);
|
return fieldType.get(fieldName);
|
||||||
} else {
|
} else {
|
||||||
MappingMetaData mappingMetaData = esConnection.getMapping(mapping.get_index());
|
MappingMetaData mappingMetaData = esConnection.getMapping(mapping.getIndex());
|
||||||
|
|
||||||
if (mappingMetaData == null) {
|
if (mappingMetaData == null) {
|
||||||
throw new IllegalArgumentException("Not found the mapping info of index: " + mapping.get_index());
|
throw new IllegalArgumentException("Not found the mapping info of index: " + mapping.getIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldType = new LinkedHashMap<>();
|
fieldType = new LinkedHashMap<>();
|
||||||
|
|||||||
93
client-adapter/es8x/pom.xml
Normal file
93
client-adapter/es8x/pom.xml
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?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.es8x</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>canal client adapter es v8x 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>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.escore</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.elastic.clients</groupId>
|
||||||
|
<artifactId>elasticsearch-java</artifactId>
|
||||||
|
<version>8.6.2</version>
|
||||||
|
</dependency>
|
||||||
|
<!--<dependency>
|
||||||
|
<groupId>org.elasticsearch.client</groupId>
|
||||||
|
<artifactId>elasticsearch-rest-client</artifactId>
|
||||||
|
<version>8.6.2</version>
|
||||||
|
</dependency>-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.elasticsearch.client</groupId>
|
||||||
|
<artifactId>elasticsearch-rest-high-level-client</artifactId>
|
||||||
|
<version>7.17.9</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</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/es8" overwrite="true">
|
||||||
|
<fileset dir="${project.basedir}/target/classes/es8" erroronmissingdir="true">
|
||||||
|
<include name="*.yml" />
|
||||||
|
</fileset>
|
||||||
|
</copy>
|
||||||
|
</tasks>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es8x;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.ESAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es8x.etl.ESEtlService;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es8x.support.ES8xTemplate;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es8x.support.ESConnection;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.SPI;
|
||||||
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ES 8.x 外部适配器
|
||||||
|
*
|
||||||
|
* @author ymz 2013-02-23
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@SPI("es8")
|
||||||
|
public class ES8xAdapter extends ESAdapter {
|
||||||
|
|
||||||
|
private ESConnection esConnection;
|
||||||
|
|
||||||
|
public ESConnection getEsConnection() {
|
||||||
|
return esConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(OuterAdapterConfig configuration, Properties envProperties) {
|
||||||
|
try {
|
||||||
|
Map<String, String> properties = configuration.getProperties();
|
||||||
|
|
||||||
|
String[] hostArray = configuration.getHosts().split(",");
|
||||||
|
esConnection = new ESConnection(hostArray, properties);
|
||||||
|
|
||||||
|
this.esTemplate = new ES8xTemplate(esConnection);
|
||||||
|
|
||||||
|
envProperties.put("es.version", "es8");
|
||||||
|
super.init(configuration, envProperties);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> count(String task) {
|
||||||
|
ESSyncConfig config = esSyncConfig.get(task);
|
||||||
|
ESSyncConfig.ESMapping mapping = config.getEsMapping();
|
||||||
|
SearchResponse response = this.esConnection.new ESSearchRequest(mapping.getIndex()).size(0).getResponse();
|
||||||
|
|
||||||
|
long rowCount = response.getHits().getTotalHits().value;
|
||||||
|
Map<String, Object> res = new LinkedHashMap<>();
|
||||||
|
res.put("esIndex", mapping.getIndex());
|
||||||
|
res.put("count", rowCount);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EtlResult etl(String task, List<String> params) {
|
||||||
|
EtlResult etlResult = new EtlResult();
|
||||||
|
ESSyncConfig config = esSyncConfig.get(task);
|
||||||
|
if (config != null) {
|
||||||
|
DataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
|
||||||
|
ESEtlService esEtlService = new ESEtlService(esConnection, config);
|
||||||
|
if (dataSource != null) {
|
||||||
|
return esEtlService.importData(params);
|
||||||
|
} else {
|
||||||
|
etlResult.setSucceeded(false);
|
||||||
|
etlResult.setErrorMessage("DataSource not found");
|
||||||
|
return etlResult;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
StringBuilder resultMsg = new StringBuilder();
|
||||||
|
boolean resSuccess = true;
|
||||||
|
for (ESSyncConfig configTmp : esSyncConfig.values()) {
|
||||||
|
// 取所有的destination为task的配置
|
||||||
|
if (configTmp.getDestination().equals(task)) {
|
||||||
|
ESEtlService esEtlService = new ESEtlService(esConnection, configTmp);
|
||||||
|
EtlResult etlRes = esEtlService.importData(params);
|
||||||
|
if (!etlRes.getSucceeded()) {
|
||||||
|
resSuccess = false;
|
||||||
|
resultMsg.append(etlRes.getErrorMessage()).append("\n");
|
||||||
|
} else {
|
||||||
|
resultMsg.append(etlRes.getResultMessage()).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resultMsg.length() > 0) {
|
||||||
|
etlResult.setSucceeded(resSuccess);
|
||||||
|
if (resSuccess) {
|
||||||
|
etlResult.setResultMessage(resultMsg.toString());
|
||||||
|
} else {
|
||||||
|
etlResult.setErrorMessage(resultMsg.toString());
|
||||||
|
}
|
||||||
|
return etlResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
etlResult.setSucceeded(false);
|
||||||
|
etlResult.setErrorMessage("Task not found");
|
||||||
|
return etlResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
super.destroy();
|
||||||
|
if (esConnection != null) {
|
||||||
|
esConnection.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es8x.etl;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
|
import org.elasticsearch.index.query.QueryBuilders;
|
||||||
|
import org.elasticsearch.search.SearchHit;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig.ESMapping;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem.FieldItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.support.ESBulkRequest;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.support.ESBulkRequest.ESBulkResponse;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.support.ESBulkRequest.ESIndexRequest;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.support.ESBulkRequest.ESUpdateRequest;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.support.ESTemplate;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es8x.support.ES8xTemplate;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es8x.support.ESConnection;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es8x.support.ESConnection.ESSearchRequest;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.AbstractEtlService;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.AdapterConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ES ETL Service
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-11-01
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class ESEtlService extends AbstractEtlService {
|
||||||
|
|
||||||
|
private ESConnection esConnection;
|
||||||
|
private ESTemplate esTemplate;
|
||||||
|
private ESSyncConfig config;
|
||||||
|
|
||||||
|
public ESEtlService(ESConnection esConnection, ESSyncConfig config){
|
||||||
|
super("ES", config);
|
||||||
|
this.esConnection = esConnection;
|
||||||
|
this.esTemplate = new ES8xTemplate(esConnection);
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EtlResult importData(List<String> params) {
|
||||||
|
ESMapping mapping = config.getEsMapping();
|
||||||
|
logger.info("start etl to import data to index: {}", mapping.getIndex());
|
||||||
|
String sql = mapping.getSql();
|
||||||
|
return importData(sql, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean executeSqlImport(DataSource ds, String sql, List<Object> values,
|
||||||
|
AdapterConfig.AdapterMapping adapterMapping, AtomicLong impCount,
|
||||||
|
List<String> errMsg) {
|
||||||
|
try {
|
||||||
|
ESMapping mapping = (ESMapping) adapterMapping;
|
||||||
|
Util.sqlRS(ds, sql, values, rs -> {
|
||||||
|
int count = 0;
|
||||||
|
try {
|
||||||
|
ESBulkRequest esBulkRequest = this.esConnection.new ES8xBulkRequest();
|
||||||
|
|
||||||
|
long batchBegin = System.currentTimeMillis();
|
||||||
|
while (rs.next()) {
|
||||||
|
Map<String, Object> esFieldData = new LinkedHashMap<>();
|
||||||
|
Object idVal = null;
|
||||||
|
for (FieldItem fieldItem : mapping.getSchemaItem().getSelectFields().values()) {
|
||||||
|
|
||||||
|
String fieldName = fieldItem.getFieldName();
|
||||||
|
if (mapping.getSkips().contains(fieldName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是主键字段则不插入
|
||||||
|
if (fieldItem.getFieldName().equals(mapping.getId())) {
|
||||||
|
idVal = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName);
|
||||||
|
} else {
|
||||||
|
Object val = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName);
|
||||||
|
esFieldData.put(Util.cleanColumn(fieldName), val);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mapping.getRelations().isEmpty()) {
|
||||||
|
mapping.getRelations().forEach((relationField, relationMapping) -> {
|
||||||
|
Map<String, Object> relations = new HashMap<>();
|
||||||
|
relations.put("name", relationMapping.getName());
|
||||||
|
if (StringUtils.isNotEmpty(relationMapping.getParent())) {
|
||||||
|
FieldItem parentFieldItem = mapping.getSchemaItem()
|
||||||
|
.getSelectFields()
|
||||||
|
.get(relationMapping.getParent());
|
||||||
|
Object parentVal;
|
||||||
|
try {
|
||||||
|
parentVal = esTemplate.getValFromRS(mapping,
|
||||||
|
rs,
|
||||||
|
parentFieldItem.getFieldName(),
|
||||||
|
parentFieldItem.getFieldName());
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
if (parentVal != null) {
|
||||||
|
relations.put("parent", parentVal.toString());
|
||||||
|
esFieldData.put("$parent_routing", parentVal.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
esFieldData.put(Util.cleanColumn(relationField), relations);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idVal != null) {
|
||||||
|
String parentVal = (String) esFieldData.remove("$parent_routing");
|
||||||
|
if (mapping.isUpsert()) {
|
||||||
|
ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(
|
||||||
|
mapping.getIndex(),
|
||||||
|
idVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
|
||||||
|
|
||||||
|
if (StringUtils.isNotEmpty(parentVal)) {
|
||||||
|
esUpdateRequest.setRouting(parentVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
esBulkRequest.add(esUpdateRequest);
|
||||||
|
} else {
|
||||||
|
ESIndexRequest esIndexRequest = this.esConnection.new ES8xIndexRequest(
|
||||||
|
mapping.getIndex(),
|
||||||
|
idVal.toString()).setSource(esFieldData);
|
||||||
|
if (StringUtils.isNotEmpty(parentVal)) {
|
||||||
|
esIndexRequest.setRouting(parentVal);
|
||||||
|
}
|
||||||
|
esBulkRequest.add(esIndexRequest);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
idVal = esFieldData.get(mapping.getPk());
|
||||||
|
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex())
|
||||||
|
.setQuery(QueryBuilders.termQuery(mapping.getPk(), idVal))
|
||||||
|
.size(10000);
|
||||||
|
SearchResponse response = esSearchRequest.getResponse();
|
||||||
|
for (SearchHit hit : response.getHits()) {
|
||||||
|
ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(
|
||||||
|
mapping.getIndex(),
|
||||||
|
hit.getId()).setDoc(esFieldData);
|
||||||
|
esBulkRequest.add(esUpdateRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esBulkRequest.numberOfActions() % mapping.getCommitBatch() == 0
|
||||||
|
&& esBulkRequest.numberOfActions() > 0) {
|
||||||
|
long esBatchBegin = System.currentTimeMillis();
|
||||||
|
ESBulkResponse rp = esBulkRequest.bulk();
|
||||||
|
if (rp.hasFailures()) {
|
||||||
|
rp.processFailBulkResponse("全量数据 etl 异常 ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("全量数据批量导入批次耗时: {}, es执行时间: {}, 批次大小: {}, index; {}",
|
||||||
|
(System.currentTimeMillis() - batchBegin),
|
||||||
|
(System.currentTimeMillis() - esBatchBegin),
|
||||||
|
esBulkRequest.numberOfActions(),
|
||||||
|
mapping.getIndex());
|
||||||
|
}
|
||||||
|
batchBegin = System.currentTimeMillis();
|
||||||
|
esBulkRequest.resetBulk();
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
impCount.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esBulkRequest.numberOfActions() > 0) {
|
||||||
|
long esBatchBegin = System.currentTimeMillis();
|
||||||
|
ESBulkResponse rp = esBulkRequest.bulk();
|
||||||
|
if (rp.hasFailures()) {
|
||||||
|
rp.processFailBulkResponse("全量数据 etl 异常 ");
|
||||||
|
}
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("全量数据批量导入最后批次耗时: {}, es执行时间: {}, 批次大小: {}, index; {}",
|
||||||
|
(System.currentTimeMillis() - batchBegin),
|
||||||
|
(System.currentTimeMillis() - esBatchBegin),
|
||||||
|
esBulkRequest.numberOfActions(),
|
||||||
|
mapping.getIndex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
errMsg.add(mapping.getIndex() + " etl failed! ==>" + e.getMessage());
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,466 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es8x.support;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
|
import org.elasticsearch.cluster.metadata.MappingMetadata;
|
||||||
|
import org.elasticsearch.index.query.BoolQueryBuilder;
|
||||||
|
import org.elasticsearch.index.query.QueryBuilders;
|
||||||
|
import org.elasticsearch.search.SearchHit;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig.ESMapping;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem.ColumnItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem.FieldItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.support.ESBulkRequest;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.support.ESBulkRequest.ESBulkResponse;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.support.ESBulkRequest.ESDeleteRequest;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.support.ESBulkRequest.ESIndexRequest;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.support.ESBulkRequest.ESUpdateRequest;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.support.ESSyncUtil;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.support.ESTemplate;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es8x.support.ESConnection.ESSearchRequest;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
|
|
||||||
|
public class ES8xTemplate implements ESTemplate {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory
|
||||||
|
.getLogger(ESTemplate.class);
|
||||||
|
|
||||||
|
private static final int MAX_BATCH_SIZE = 1000;
|
||||||
|
|
||||||
|
private ESConnection esConnection;
|
||||||
|
|
||||||
|
private ESBulkRequest esBulkRequest;
|
||||||
|
|
||||||
|
// es 字段类型本地缓存
|
||||||
|
private static ConcurrentMap<String, Map<String, String>> esFieldTypes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public ES8xTemplate(ESConnection esConnection){
|
||||||
|
this.esConnection = esConnection;
|
||||||
|
this.esBulkRequest = this.esConnection.new ES8xBulkRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ESBulkRequest getBulk() {
|
||||||
|
return esBulkRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetBulkRequestBuilder() {
|
||||||
|
this.esBulkRequest.resetBulk();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insert(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
|
||||||
|
if (mapping.getId() != null) {
|
||||||
|
String parentVal = (String) esFieldData.remove("$parent_routing");
|
||||||
|
if (mapping.isUpsert()) {
|
||||||
|
ESUpdateRequest updateRequest = esConnection.new ES8xUpdateRequest(mapping.getIndex(),
|
||||||
|
pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
|
||||||
|
if (StringUtils.isNotEmpty(parentVal)) {
|
||||||
|
updateRequest.setRouting(parentVal);
|
||||||
|
}
|
||||||
|
getBulk().add(updateRequest);
|
||||||
|
} else {
|
||||||
|
ESIndexRequest indexRequest = esConnection.new ES8xIndexRequest(mapping.getIndex(), pkVal.toString())
|
||||||
|
.setSource(esFieldData);
|
||||||
|
if (StringUtils.isNotEmpty(parentVal)) {
|
||||||
|
indexRequest.setRouting(parentVal);
|
||||||
|
}
|
||||||
|
getBulk().add(indexRequest);
|
||||||
|
}
|
||||||
|
commitBulk();
|
||||||
|
} else {
|
||||||
|
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex())
|
||||||
|
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
|
||||||
|
.size(10000);
|
||||||
|
SearchResponse response = esSearchRequest.getResponse();
|
||||||
|
|
||||||
|
for (SearchHit hit : response.getHits()) {
|
||||||
|
ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(mapping.getIndex(),
|
||||||
|
hit.getId()).setDoc(esFieldData);
|
||||||
|
getBulk().add(esUpdateRequest);
|
||||||
|
commitBulk();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
|
||||||
|
Map<String, Object> esFieldDataTmp = new LinkedHashMap<>(esFieldData.size());
|
||||||
|
esFieldData.forEach((k, v) -> esFieldDataTmp.put(Util.cleanColumn(k), v));
|
||||||
|
append4Update(mapping, pkVal, esFieldDataTmp);
|
||||||
|
commitBulk();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateByQuery(ESSyncConfig config, Map<String, Object> paramsTmp, Map<String, Object> esFieldData) {
|
||||||
|
if (paramsTmp.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESMapping mapping = config.getEsMapping();
|
||||||
|
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
|
||||||
|
paramsTmp.forEach((fieldName, value) -> queryBuilder.must(QueryBuilders.termsQuery(fieldName, value)));
|
||||||
|
|
||||||
|
// 查询sql批量更新
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
|
||||||
|
StringBuilder sql = new StringBuilder("SELECT * FROM (" + mapping.getSql() + ") _v WHERE ");
|
||||||
|
List<Object> values = new ArrayList<>();
|
||||||
|
paramsTmp.forEach((fieldName, value) -> {
|
||||||
|
sql.append("_v.").append(fieldName).append("=? AND ");
|
||||||
|
values.add(value);
|
||||||
|
});
|
||||||
|
// TODO 直接外部包裹sql会导致全表扫描性能低, 待优化拼接内部where条件
|
||||||
|
int len = sql.length();
|
||||||
|
sql.delete(len - 4, len);
|
||||||
|
Integer syncCount = (Integer) Util.sqlRS(ds, sql.toString(), values, rs -> {
|
||||||
|
int count = 0;
|
||||||
|
try {
|
||||||
|
while (rs.next()) {
|
||||||
|
Object idVal = getIdValFromRS(mapping, rs);
|
||||||
|
append4Update(mapping, idVal, esFieldData);
|
||||||
|
commitBulk();
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
});
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Update ES by query affected {} records", syncCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
|
||||||
|
if (mapping.getId() != null) {
|
||||||
|
ESDeleteRequest esDeleteRequest = this.esConnection.new ES8xDeleteRequest(mapping.getIndex(),
|
||||||
|
pkVal.toString());
|
||||||
|
getBulk().add(esDeleteRequest);
|
||||||
|
commitBulk();
|
||||||
|
} else {
|
||||||
|
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex())
|
||||||
|
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
|
||||||
|
.size(10000);
|
||||||
|
SearchResponse response = esSearchRequest.getResponse();
|
||||||
|
for (SearchHit hit : response.getHits()) {
|
||||||
|
ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(mapping.getIndex(),
|
||||||
|
hit.getId()).setDoc(esFieldData);
|
||||||
|
getBulk().add(esUpdateRequest);
|
||||||
|
commitBulk();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commit() {
|
||||||
|
if (getBulk().numberOfActions() > 0) {
|
||||||
|
ESBulkResponse response = getBulk().bulk();
|
||||||
|
if (response.hasFailures()) {
|
||||||
|
response.processFailBulkResponse("ES sync commit error ");
|
||||||
|
}
|
||||||
|
resetBulkRequestBuilder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValFromRS(ESMapping mapping, ResultSet resultSet, String fieldName,
|
||||||
|
String columnName) throws SQLException {
|
||||||
|
fieldName = Util.cleanColumn(fieldName);
|
||||||
|
columnName = Util.cleanColumn(columnName);
|
||||||
|
String esType = getEsType(mapping, fieldName);
|
||||||
|
|
||||||
|
Object value = resultSet.getObject(columnName);
|
||||||
|
if (value instanceof Boolean) {
|
||||||
|
if (!"boolean".equals(esType)) {
|
||||||
|
value = resultSet.getByte(columnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是对象类型
|
||||||
|
if (mapping.getObjFields().containsKey(fieldName)) {
|
||||||
|
return ESSyncUtil.convertToEsObj(value, mapping.getObjFields().get(fieldName));
|
||||||
|
} else {
|
||||||
|
return ESSyncUtil.typeConvert(value, esType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet,
|
||||||
|
Map<String, Object> esFieldData) throws SQLException {
|
||||||
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
|
String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
|
||||||
|
Object resultIdVal = null;
|
||||||
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
|
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
|
||||||
|
|
||||||
|
if (fieldItem.getFieldName().equals(idFieldName)) {
|
||||||
|
resultIdVal = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fieldItem.getFieldName().equals(mapping.getId())
|
||||||
|
&& !mapping.getSkips().contains(fieldItem.getFieldName())) {
|
||||||
|
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加父子文档关联信息
|
||||||
|
putRelationDataFromRS(mapping, schemaItem, resultSet, esFieldData);
|
||||||
|
|
||||||
|
return resultIdVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getIdValFromRS(ESMapping mapping, ResultSet resultSet) throws SQLException {
|
||||||
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
|
String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
|
||||||
|
Object resultIdVal = null;
|
||||||
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
|
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
|
||||||
|
|
||||||
|
if (fieldItem.getFieldName().equals(idFieldName)) {
|
||||||
|
resultIdVal = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultIdVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld,
|
||||||
|
Map<String, Object> esFieldData) throws SQLException {
|
||||||
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
|
String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
|
||||||
|
Object resultIdVal = null;
|
||||||
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
|
if (fieldItem.getFieldName().equals(idFieldName)) {
|
||||||
|
resultIdVal = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ColumnItem columnItem : fieldItem.getColumnItems()) {
|
||||||
|
if (dmlOld.containsKey(columnItem.getColumnName())
|
||||||
|
&& !mapping.getSkips().contains(fieldItem.getFieldName())) {
|
||||||
|
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()),
|
||||||
|
getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加父子文档关联信息
|
||||||
|
putRelationDataFromRS(mapping, schemaItem, resultSet, esFieldData);
|
||||||
|
|
||||||
|
return resultIdVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValFromData(ESMapping mapping, Map<String, Object> dmlData, String fieldName, String columnName) {
|
||||||
|
String esType = getEsType(mapping, fieldName);
|
||||||
|
Object value = dmlData.get(columnName);
|
||||||
|
if (value instanceof Byte) {
|
||||||
|
if ("boolean".equals(esType)) {
|
||||||
|
value = ((Byte) value).intValue() != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是对象类型
|
||||||
|
if (mapping.getObjFields().containsKey(fieldName)) {
|
||||||
|
return ESSyncUtil.convertToEsObj(value, mapping.getObjFields().get(fieldName));
|
||||||
|
} else {
|
||||||
|
return ESSyncUtil.typeConvert(value, esType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData,
|
||||||
|
Map<String, Object> esFieldData) {
|
||||||
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
|
String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
|
||||||
|
Object resultIdVal = null;
|
||||||
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
|
String columnName = fieldItem.getColumnItems().iterator().next().getColumnName();
|
||||||
|
Object value = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName);
|
||||||
|
|
||||||
|
if (fieldItem.getFieldName().equals(idFieldName)) {
|
||||||
|
resultIdVal = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fieldItem.getFieldName().equals(mapping.getId())
|
||||||
|
&& !mapping.getSkips().contains(fieldItem.getFieldName())) {
|
||||||
|
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加父子文档关联信息
|
||||||
|
putRelationData(mapping, schemaItem, dmlData, esFieldData);
|
||||||
|
return resultIdVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getESDataFromDmlData(ESMapping mapping, String owner, Map<String, Object> dmlData,
|
||||||
|
Map<String, Object> dmlOld, Map<String, Object> esFieldData) {
|
||||||
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
|
String idFieldName = mapping.getId() == null ? mapping.getPk() : mapping.getId();
|
||||||
|
Object resultIdVal = null;
|
||||||
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
|
ColumnItem columnItem = fieldItem.getColumnItems().iterator().next();
|
||||||
|
if (!columnItem.getOwner().equals(owner)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String columnName = columnItem.getColumnName();
|
||||||
|
|
||||||
|
if (fieldItem.getFieldName().equals(idFieldName)) {
|
||||||
|
resultIdVal = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dmlOld.containsKey(columnName) && !mapping.getSkips().contains(fieldItem.getFieldName())) {
|
||||||
|
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()),
|
||||||
|
getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加父子文档关联信息
|
||||||
|
putRelationData(mapping, schemaItem, dmlOld, esFieldData);
|
||||||
|
return resultIdVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果大于批量数则提交批次
|
||||||
|
*/
|
||||||
|
private void commitBulk() {
|
||||||
|
if (getBulk().numberOfActions() >= MAX_BATCH_SIZE) {
|
||||||
|
commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void append4Update(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
|
||||||
|
if (mapping.getId() != null) {
|
||||||
|
String parentVal = (String) esFieldData.remove("$parent_routing");
|
||||||
|
if (mapping.isUpsert()) {
|
||||||
|
ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(mapping.getIndex(),
|
||||||
|
pkVal.toString()).setDoc(esFieldData).setDocAsUpsert(true);
|
||||||
|
if (StringUtils.isNotEmpty(parentVal)) {
|
||||||
|
esUpdateRequest.setRouting(parentVal);
|
||||||
|
}
|
||||||
|
getBulk().add(esUpdateRequest);
|
||||||
|
} else {
|
||||||
|
ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(mapping.getIndex(),
|
||||||
|
pkVal.toString()).setDoc(esFieldData);
|
||||||
|
if (StringUtils.isNotEmpty(parentVal)) {
|
||||||
|
esUpdateRequest.setRouting(parentVal);
|
||||||
|
}
|
||||||
|
getBulk().add(esUpdateRequest);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ESSearchRequest esSearchRequest = this.esConnection.new ESSearchRequest(mapping.getIndex())
|
||||||
|
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
|
||||||
|
.size(10000);
|
||||||
|
SearchResponse response = esSearchRequest.getResponse();
|
||||||
|
for (SearchHit hit : response.getHits()) {
|
||||||
|
ESUpdateRequest esUpdateRequest = this.esConnection.new ES8xUpdateRequest(mapping.getIndex(),
|
||||||
|
hit.getId()).setDoc(esFieldData);
|
||||||
|
getBulk().add(esUpdateRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取es mapping中的属性类型
|
||||||
|
*
|
||||||
|
* @param mapping mapping配置
|
||||||
|
* @param fieldName 属性名
|
||||||
|
* @return 类型
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private String getEsType(ESMapping mapping, String fieldName) {
|
||||||
|
String key = mapping.getIndex() + "-" + mapping.getType();
|
||||||
|
Map<String, String> fieldType = esFieldTypes.get(key);
|
||||||
|
if (fieldType != null) {
|
||||||
|
return fieldType.get(fieldName);
|
||||||
|
} else {
|
||||||
|
MappingMetadata mappingMetaData = esConnection.getMapping(mapping.getIndex());
|
||||||
|
|
||||||
|
if (mappingMetaData == null) {
|
||||||
|
throw new IllegalArgumentException("Not found the mapping info of index: " + mapping.getIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldType = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
Map<String, Object> sourceMap = mappingMetaData.getSourceAsMap();
|
||||||
|
Map<String, Object> esMapping = (Map<String, Object>) sourceMap.get("properties");
|
||||||
|
for (Map.Entry<String, Object> entry : esMapping.entrySet()) {
|
||||||
|
Map<String, Object> value = (Map<String, Object>) entry.getValue();
|
||||||
|
if (value.containsKey("properties")) {
|
||||||
|
fieldType.put(entry.getKey(), "object");
|
||||||
|
} else {
|
||||||
|
fieldType.put(entry.getKey(), (String) value.get("type"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
esFieldTypes.put(key, fieldType);
|
||||||
|
|
||||||
|
return fieldType.get(fieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putRelationDataFromRS(ESMapping mapping, SchemaItem schemaItem, ResultSet resultSet,
|
||||||
|
Map<String, Object> esFieldData) {
|
||||||
|
// 添加父子文档关联信息
|
||||||
|
if (!mapping.getRelations().isEmpty()) {
|
||||||
|
mapping.getRelations().forEach((relationField, relationMapping) -> {
|
||||||
|
Map<String, Object> relations = new HashMap<>();
|
||||||
|
relations.put("name", relationMapping.getName());
|
||||||
|
if (StringUtils.isNotEmpty(relationMapping.getParent())) {
|
||||||
|
FieldItem parentFieldItem = schemaItem.getSelectFields().get(relationMapping.getParent());
|
||||||
|
Object parentVal;
|
||||||
|
try {
|
||||||
|
parentVal = getValFromRS(mapping,
|
||||||
|
resultSet,
|
||||||
|
parentFieldItem.getFieldName(),
|
||||||
|
parentFieldItem.getFieldName());
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
if (parentVal != null) {
|
||||||
|
relations.put("parent", parentVal.toString());
|
||||||
|
esFieldData.put("$parent_routing", parentVal.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
esFieldData.put(relationField, relations);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putRelationData(ESMapping mapping, SchemaItem schemaItem, Map<String, Object> dmlData,
|
||||||
|
Map<String, Object> esFieldData) {
|
||||||
|
// 添加父子文档关联信息
|
||||||
|
if (!mapping.getRelations().isEmpty()) {
|
||||||
|
mapping.getRelations().forEach((relationField, relationMapping) -> {
|
||||||
|
Map<String, Object> relations = new HashMap<>();
|
||||||
|
relations.put("name", relationMapping.getName());
|
||||||
|
if (StringUtils.isNotEmpty(relationMapping.getParent())) {
|
||||||
|
FieldItem parentFieldItem = schemaItem.getSelectFields().get(relationMapping.getParent());
|
||||||
|
String columnName = parentFieldItem.getColumnItems().iterator().next().getColumnName();
|
||||||
|
Object parentVal = getValFromData(mapping, dmlData, parentFieldItem.getFieldName(), columnName);
|
||||||
|
if (parentVal != null) {
|
||||||
|
relations.put("parent", parentVal.toString());
|
||||||
|
esFieldData.put("$parent_routing", parentVal.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
esFieldData.put(relationField, relations);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,399 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es8x.support;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.auth.AuthScope;
|
||||||
|
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||||
|
import org.apache.http.client.CredentialsProvider;
|
||||||
|
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||||
|
import org.elasticsearch.action.bulk.BulkItemResponse;
|
||||||
|
import org.elasticsearch.action.bulk.BulkRequest;
|
||||||
|
import org.elasticsearch.action.bulk.BulkRequestBuilder;
|
||||||
|
import org.elasticsearch.action.bulk.BulkResponse;
|
||||||
|
import org.elasticsearch.action.delete.DeleteRequest;
|
||||||
|
import org.elasticsearch.action.delete.DeleteRequestBuilder;
|
||||||
|
import org.elasticsearch.action.index.IndexRequest;
|
||||||
|
import org.elasticsearch.action.index.IndexRequestBuilder;
|
||||||
|
import org.elasticsearch.action.search.SearchRequest;
|
||||||
|
import org.elasticsearch.action.search.SearchRequestBuilder;
|
||||||
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
|
import org.elasticsearch.action.update.UpdateRequest;
|
||||||
|
import org.elasticsearch.action.update.UpdateRequestBuilder;
|
||||||
|
import org.elasticsearch.client.*;
|
||||||
|
import org.elasticsearch.client.indices.GetMappingsRequest;
|
||||||
|
import org.elasticsearch.client.indices.GetMappingsResponse;
|
||||||
|
import org.elasticsearch.cluster.metadata.MappingMetadata;
|
||||||
|
import org.elasticsearch.index.query.QueryBuilder;
|
||||||
|
import org.elasticsearch.rest.RestStatus;
|
||||||
|
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.core.support.ESBulkRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ES 连接器, 只支持 Rest 方式
|
||||||
|
*
|
||||||
|
* @author ymz 2023-03-02
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class ESConnection {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ESConnection.class);
|
||||||
|
|
||||||
|
private RestHighLevelClient restHighLevelClient;
|
||||||
|
|
||||||
|
public ESConnection(String[] hosts, Map<String, String> properties) throws UnknownHostException{
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
restHighLevelClient = new RestHighLevelClientBuilder(restClientBuilder.build()).setApiCompatibilityMode(true)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
restHighLevelClient.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingMetadata getMapping(String index) {
|
||||||
|
MappingMetadata mappingMetaData = null;
|
||||||
|
|
||||||
|
Map<String, MappingMetadata> mappings;
|
||||||
|
try {
|
||||||
|
GetMappingsRequest request = new GetMappingsRequest();
|
||||||
|
request.indices(index);
|
||||||
|
GetMappingsResponse response = restHighLevelClient.indices().getMapping(request, RequestOptions.DEFAULT);
|
||||||
|
|
||||||
|
mappings = response.mappings();
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
throw new IllegalArgumentException("Not found the mapping info of index: " + index);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
mappingMetaData = mappings.get(index);
|
||||||
|
|
||||||
|
return mappingMetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ES8xIndexRequest implements ESBulkRequest.ESIndexRequest {
|
||||||
|
|
||||||
|
private IndexRequestBuilder indexRequestBuilder;
|
||||||
|
|
||||||
|
private IndexRequest indexRequest;
|
||||||
|
|
||||||
|
public ES8xIndexRequest(String index, String id){
|
||||||
|
indexRequest = new IndexRequest(index);
|
||||||
|
indexRequest.id(id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ES8xIndexRequest setSource(Map<String, ?> source) {
|
||||||
|
|
||||||
|
indexRequest.source(source);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ES8xIndexRequest setRouting(String routing) {
|
||||||
|
|
||||||
|
indexRequest.routing(routing);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IndexRequestBuilder getIndexRequestBuilder() {
|
||||||
|
return indexRequestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIndexRequestBuilder(IndexRequestBuilder indexRequestBuilder) {
|
||||||
|
this.indexRequestBuilder = indexRequestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IndexRequest getIndexRequest() {
|
||||||
|
return indexRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIndexRequest(IndexRequest indexRequest) {
|
||||||
|
this.indexRequest = indexRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ES8xUpdateRequest implements ESBulkRequest.ESUpdateRequest {
|
||||||
|
|
||||||
|
private UpdateRequestBuilder updateRequestBuilder;
|
||||||
|
|
||||||
|
private UpdateRequest updateRequest;
|
||||||
|
|
||||||
|
public ES8xUpdateRequest(String index, String id){
|
||||||
|
|
||||||
|
updateRequest = new UpdateRequest(index, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ES8xUpdateRequest setDoc(Map source) {
|
||||||
|
|
||||||
|
updateRequest.doc(source);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ES8xUpdateRequest setDocAsUpsert(boolean shouldUpsertDoc) {
|
||||||
|
|
||||||
|
updateRequest.docAsUpsert(shouldUpsertDoc);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ES8xUpdateRequest setRouting(String routing) {
|
||||||
|
|
||||||
|
updateRequest.routing(routing);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdateRequestBuilder getUpdateRequestBuilder() {
|
||||||
|
return updateRequestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateRequestBuilder(UpdateRequestBuilder updateRequestBuilder) {
|
||||||
|
this.updateRequestBuilder = updateRequestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdateRequest getUpdateRequest() {
|
||||||
|
return updateRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateRequest(UpdateRequest updateRequest) {
|
||||||
|
this.updateRequest = updateRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ES8xDeleteRequest implements ESBulkRequest.ESDeleteRequest {
|
||||||
|
|
||||||
|
private DeleteRequestBuilder deleteRequestBuilder;
|
||||||
|
|
||||||
|
private DeleteRequest deleteRequest;
|
||||||
|
|
||||||
|
public ES8xDeleteRequest(String index, String id){
|
||||||
|
|
||||||
|
deleteRequest = new DeleteRequest(index, id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeleteRequestBuilder getDeleteRequestBuilder() {
|
||||||
|
return deleteRequestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeleteRequestBuilder(DeleteRequestBuilder deleteRequestBuilder) {
|
||||||
|
this.deleteRequestBuilder = deleteRequestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeleteRequest getDeleteRequest() {
|
||||||
|
return deleteRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeleteRequest(DeleteRequest deleteRequest) {
|
||||||
|
this.deleteRequest = deleteRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ESSearchRequest {
|
||||||
|
|
||||||
|
private SearchRequestBuilder searchRequestBuilder;
|
||||||
|
|
||||||
|
private SearchRequest searchRequest;
|
||||||
|
|
||||||
|
private SearchSourceBuilder sourceBuilder;
|
||||||
|
|
||||||
|
public ESSearchRequest(String index){
|
||||||
|
|
||||||
|
searchRequest = new SearchRequest(index);
|
||||||
|
sourceBuilder = new SearchSourceBuilder();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ESSearchRequest setQuery(QueryBuilder queryBuilder) {
|
||||||
|
|
||||||
|
sourceBuilder.query(queryBuilder);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ESSearchRequest size(int size) {
|
||||||
|
|
||||||
|
sourceBuilder.size(size);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchResponse getResponse() {
|
||||||
|
|
||||||
|
searchRequest.source(sourceBuilder);
|
||||||
|
try {
|
||||||
|
return restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchRequestBuilder getSearchRequestBuilder() {
|
||||||
|
return searchRequestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSearchRequestBuilder(SearchRequestBuilder searchRequestBuilder) {
|
||||||
|
this.searchRequestBuilder = searchRequestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchRequest getSearchRequest() {
|
||||||
|
return searchRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSearchRequest(SearchRequest searchRequest) {
|
||||||
|
this.searchRequest = searchRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ES8xBulkRequest implements ESBulkRequest {
|
||||||
|
|
||||||
|
private BulkRequestBuilder bulkRequestBuilder;
|
||||||
|
|
||||||
|
private BulkRequest bulkRequest;
|
||||||
|
|
||||||
|
public ES8xBulkRequest(){
|
||||||
|
|
||||||
|
bulkRequest = new BulkRequest();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetBulk() {
|
||||||
|
|
||||||
|
bulkRequest = new BulkRequest();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ES8xBulkRequest add(ESIndexRequest esIndexRequest) {
|
||||||
|
ES8xIndexRequest eir = (ES8xIndexRequest) esIndexRequest;
|
||||||
|
|
||||||
|
bulkRequest.add(eir.indexRequest);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ES8xBulkRequest add(ESUpdateRequest esUpdateRequest) {
|
||||||
|
ES8xUpdateRequest eur = (ES8xUpdateRequest) esUpdateRequest;
|
||||||
|
|
||||||
|
bulkRequest.add(eur.updateRequest);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ES8xBulkRequest add(ESDeleteRequest esDeleteRequest) {
|
||||||
|
ES8xDeleteRequest edr = (ES8xDeleteRequest) esDeleteRequest;
|
||||||
|
|
||||||
|
bulkRequest.add(edr.deleteRequest);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int numberOfActions() {
|
||||||
|
return bulkRequest.numberOfActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ESBulkResponse bulk() {
|
||||||
|
try {
|
||||||
|
BulkResponse responses = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
|
||||||
|
return new ES8xBulkResponse(responses);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkRequestBuilder getBulkRequestBuilder() {
|
||||||
|
return bulkRequestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBulkRequestBuilder(BulkRequestBuilder bulkRequestBuilder) {
|
||||||
|
this.bulkRequestBuilder = bulkRequestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkRequest getBulkRequest() {
|
||||||
|
return bulkRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBulkRequest(BulkRequest bulkRequest) {
|
||||||
|
this.bulkRequest = bulkRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ES8xBulkResponse implements ESBulkRequest.ESBulkResponse {
|
||||||
|
|
||||||
|
private BulkResponse bulkResponse;
|
||||||
|
|
||||||
|
public ES8xBulkResponse(BulkResponse bulkResponse){
|
||||||
|
this.bulkResponse = bulkResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFailures() {
|
||||||
|
return bulkResponse.hasFailures();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processFailBulkResponse(String errorMsg) {
|
||||||
|
for (BulkItemResponse itemResponse : bulkResponse.getItems()) {
|
||||||
|
if (!itemResponse.isFailed()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemResponse.getFailure().getStatus() == RestStatus.NOT_FOUND) {
|
||||||
|
logger.error(itemResponse.getFailureMessage());
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException(errorMsg + itemResponse.getFailureMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RestHighLevelClient getRestHighLevelClient() {
|
||||||
|
return restHighLevelClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRestHighLevelClient(RestHighLevelClient restHighLevelClient) {
|
||||||
|
this.restHighLevelClient = restHighLevelClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpHost createHttpHost(String uriStr) {
|
||||||
|
URI uri = URI.create(uriStr);
|
||||||
|
if (!org.springframework.util.StringUtils.hasLength(uri.getUserInfo())) {
|
||||||
|
return HttpHost.create(uri.toString());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return HttpHost.create(new URI(uri
|
||||||
|
.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment())
|
||||||
|
.toString());
|
||||||
|
} catch (URISyntaxException ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
es8=com.alibaba.otter.canal.client.adapter.es8x.ES8xAdapter
|
||||||
20
client-adapter/es8x/src/main/resources/es8/biz_order.yml
Normal file
20
client-adapter/es8x/src/main/resources/es8/biz_order.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
dataSourceKey: defaultDS
|
||||||
|
destination: example
|
||||||
|
groupId: g1
|
||||||
|
esMapping:
|
||||||
|
_index: customer
|
||||||
|
_id: _id
|
||||||
|
relations:
|
||||||
|
customer_order:
|
||||||
|
name: order
|
||||||
|
parent: customer_id
|
||||||
|
sql: "select concat('oid_', t.id) as _id,
|
||||||
|
t.customer_id,
|
||||||
|
t.id as order_id,
|
||||||
|
t.serial_code as order_serial,
|
||||||
|
t.c_time as order_time
|
||||||
|
from biz_order t"
|
||||||
|
skips:
|
||||||
|
- customer_id
|
||||||
|
etlCondition: "where t.c_time>={}"
|
||||||
|
commitBatch: 3000
|
||||||
46
client-adapter/es8x/src/main/resources/es8/customer.yml
Normal file
46
client-adapter/es8x/src/main/resources/es8/customer.yml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
dataSourceKey: defaultDS
|
||||||
|
destination: example
|
||||||
|
groupId: g1
|
||||||
|
esMapping:
|
||||||
|
_index: customer
|
||||||
|
_id: id
|
||||||
|
relations:
|
||||||
|
customer_order:
|
||||||
|
name: customer
|
||||||
|
sql: "select t.id, t.name, t.email from customer t"
|
||||||
|
etlCondition: "where t.c_time>={}"
|
||||||
|
commitBatch: 3000
|
||||||
|
|
||||||
|
|
||||||
|
#{
|
||||||
|
# "mappings":{
|
||||||
|
# "_doc":{
|
||||||
|
# "properties":{
|
||||||
|
# "id": {
|
||||||
|
# "type": "long"
|
||||||
|
# },
|
||||||
|
# "name": {
|
||||||
|
# "type": "text"
|
||||||
|
# },
|
||||||
|
# "email": {
|
||||||
|
# "type": "text"
|
||||||
|
# },
|
||||||
|
# "order_id": {
|
||||||
|
# "type": "long"
|
||||||
|
# },
|
||||||
|
# "order_serial": {
|
||||||
|
# "type": "text"
|
||||||
|
# },
|
||||||
|
# "order_time": {
|
||||||
|
# "type": "date"
|
||||||
|
# },
|
||||||
|
# "customer_order":{
|
||||||
|
# "type":"join",
|
||||||
|
# "relations":{
|
||||||
|
# "customer":"order"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#}
|
||||||
15
client-adapter/es8x/src/main/resources/es8/mytest_user.yml
Normal file
15
client-adapter/es8x/src/main/resources/es8/mytest_user.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
dataSourceKey: defaultDS
|
||||||
|
destination: example
|
||||||
|
groupId: g1
|
||||||
|
esMapping:
|
||||||
|
_index: mytest_user
|
||||||
|
_id: _id
|
||||||
|
# upsert: true
|
||||||
|
# pk: id
|
||||||
|
sql: "select a.id as _id, a.name, a.role_id, b.role_name,
|
||||||
|
a.c_time from user a
|
||||||
|
left join role b on b.id=a.role_id"
|
||||||
|
# objFields:
|
||||||
|
# _labels: array:;
|
||||||
|
etlCondition: "where a.c_time>={}"
|
||||||
|
commitBatch: 3000
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es8x.test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es8x.support.ESConnection;
|
||||||
|
import org.elasticsearch.cluster.metadata.MappingMetadata;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public class ESConnectionTest {
|
||||||
|
|
||||||
|
ESConnection esConnection;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() throws UnknownHostException {
|
||||||
|
String[] hosts = new String[]{"127.0.0.1:9200"};
|
||||||
|
Map<String, String> properties = new HashMap<>();
|
||||||
|
properties.put("cluster.name", "elasticsearch");
|
||||||
|
esConnection = new ESConnection(hosts, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test01() {
|
||||||
|
MappingMetadata mappingMetaData = esConnection.getMapping("mytest_user");
|
||||||
|
|
||||||
|
Map<String, Object> sourceMap = mappingMetaData.getSourceAsMap();
|
||||||
|
Map<String, Object> esMapping = (Map<String, Object>) sourceMap.get("properties");
|
||||||
|
for (Map.Entry<String, Object> entry : esMapping.entrySet()) {
|
||||||
|
Map<String, Object> value = (Map<String, Object>) entry.getValue();
|
||||||
|
if (value.containsKey("properties")) {
|
||||||
|
System.out.println(entry.getKey() + " object");
|
||||||
|
} else {
|
||||||
|
System.out.println(entry.getKey() + " " + value.get("type"));
|
||||||
|
Assert.notNull(entry.getKey(), "null column name");
|
||||||
|
Assert.notNull(value.get("type"), "null column type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es8x.test;
|
||||||
|
|
||||||
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
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 String esHosts = "127.0.0.1:9300";
|
||||||
|
public final static String clusterName = "elasticsearch";
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -21,7 +21,6 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.hbase</groupId>
|
<groupId>org.apache.hbase</groupId>
|
||||||
<artifactId>hbase-shaded-client</artifactId>
|
<artifactId>hbase-shaded-client</artifactId>
|
||||||
<version>1.1.2</version>
|
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -21,18 +21,15 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.kudu</groupId>
|
<groupId>org.apache.kudu</groupId>
|
||||||
<artifactId>kudu-client</artifactId>
|
<artifactId>kudu-client</artifactId>
|
||||||
<version>1.6.0</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<version>4.12</version>
|
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>mysql</groupId>
|
<groupId>mysql</groupId>
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
<version>5.1.48</version>
|
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-dependencies</artifactId>
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
<version>2.0.1.RELEASE</version>
|
<version>2.5.4</version>
|
||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
@@ -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>
|
||||||
@@ -128,6 +132,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.es8x</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>*</artifactId>
|
||||||
|
<groupId>*</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
<classifier>jar-with-dependencies</classifier>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>client-adapter.rdb</artifactId>
|
<artifactId>client-adapter.rdb</artifactId>
|
||||||
@@ -154,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>
|
||||||
|
|||||||
@@ -43,6 +43,13 @@
|
|||||||
<include>**/*</include>
|
<include>**/*</include>
|
||||||
</includes>
|
</includes>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>../es8x/src/main/resources/es8</directory>
|
||||||
|
<outputDirectory>/conf/es8</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>../hbase/src/main/resources/hbase</directory>
|
<directory>../hbase/src/main/resources/hbase</directory>
|
||||||
<outputDirectory>/conf/hbase</outputDirectory>
|
<outputDirectory>/conf/hbase</outputDirectory>
|
||||||
|
|||||||
@@ -44,6 +44,13 @@
|
|||||||
<include>**/*</include>
|
<include>**/*</include>
|
||||||
</includes>
|
</includes>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>../es8x/src/main/resources/es8</directory>
|
||||||
|
<outputDirectory>/conf/es8</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>../hbase/src/main/resources/hbase</directory>
|
<directory>../hbase/src/main/resources/hbase</directory>
|
||||||
<outputDirectory>/conf/hbase</outputDirectory>
|
<outputDirectory>/conf/hbase</outputDirectory>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.alibaba.otter.canal.adapter.launcher;
|
|||||||
import org.springframework.boot.Banner;
|
import org.springframework.boot.Banner;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动入口
|
* 启动入口
|
||||||
@@ -10,7 +11,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
* @author rewerma @ 2018-10-20
|
* @author rewerma @ 2018-10-20
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
@SpringBootApplication
|
@SpringBootApplication(exclude= { DataSourceAutoConfiguration.class})
|
||||||
public class CanalAdapterApplication {
|
public class CanalAdapterApplication {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
// 支持rocketmq client 配置日志路径
|
// 支持rocketmq client 配置日志路径
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import java.util.concurrent.ExecutorService;
|
|||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -23,36 +22,37 @@ import com.alibaba.otter.canal.connector.core.config.CanalConstants;
|
|||||||
import com.alibaba.otter.canal.connector.core.consumer.CommonMessage;
|
import com.alibaba.otter.canal.connector.core.consumer.CommonMessage;
|
||||||
import com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer;
|
import com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer;
|
||||||
import com.alibaba.otter.canal.connector.core.spi.ExtensionLoader;
|
import com.alibaba.otter.canal.connector.core.spi.ExtensionLoader;
|
||||||
|
import com.alibaba.otter.canal.connector.core.spi.ProxyCanalMsgConsumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 适配处理器
|
* 适配处理器
|
||||||
*
|
*
|
||||||
* @author rewerma 2020-02-01
|
* @author rewerma 2020-02-01
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
public class AdapterProcessor {
|
public class AdapterProcessor {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AdapterProcessor.class);
|
private static final Logger logger = LoggerFactory.getLogger(AdapterProcessor.class);
|
||||||
|
|
||||||
private static final String CONNECTOR_SPI_DIR = "/plugin";
|
private static final String CONNECTOR_SPI_DIR = "/plugin";
|
||||||
private static final String CONNECTOR_STANDBY_SPI_DIR = "/canal-adapter/plugin";
|
private static final String CONNECTOR_STANDBY_SPI_DIR = "/canal-adapter/plugin";
|
||||||
|
|
||||||
private CanalMsgConsumer canalMsgConsumer;
|
private CanalMsgConsumer canalMsgConsumer;
|
||||||
|
|
||||||
private String canalDestination; // canal实例
|
private String canalDestination; // canal实例
|
||||||
private String groupId = null; // groupId
|
private String groupId = null; // groupId
|
||||||
private List<List<OuterAdapter>> canalOuterAdapters; // 外部适配器
|
private List<List<OuterAdapter>> canalOuterAdapters; // 外部适配器
|
||||||
private CanalClientConfig canalClientConfig; // 配置
|
private CanalClientConfig canalClientConfig; // 配置
|
||||||
private ExecutorService groupInnerExecutorService; // 组内工作线程池
|
private ExecutorService groupInnerExecutorService; // 组内工作线程池
|
||||||
private volatile boolean running = false; // 是否运行中
|
private volatile boolean running = false; // 是否运行中
|
||||||
private Thread thread = null;
|
private Thread thread = null;
|
||||||
private Thread.UncaughtExceptionHandler handler = (t, e) -> logger
|
private Thread.UncaughtExceptionHandler handler = (t, e) -> logger
|
||||||
.error("parse events has an error", e);
|
.error("parse events has an error", e);
|
||||||
|
|
||||||
private SyncSwitch syncSwitch;
|
private SyncSwitch syncSwitch;
|
||||||
|
|
||||||
public AdapterProcessor(CanalClientConfig canalClientConfig, String destination, String groupId,
|
public AdapterProcessor(CanalClientConfig canalClientConfig, String destination, String groupId,
|
||||||
List<List<OuterAdapter>> canalOuterAdapters){
|
List<List<OuterAdapter>> canalOuterAdapters) {
|
||||||
this.canalClientConfig = canalClientConfig;
|
this.canalClientConfig = canalClientConfig;
|
||||||
this.canalDestination = destination;
|
this.canalDestination = destination;
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
@@ -63,17 +63,15 @@ public class AdapterProcessor {
|
|||||||
|
|
||||||
// load connector consumer
|
// load connector consumer
|
||||||
ExtensionLoader<CanalMsgConsumer> loader = new ExtensionLoader<>(CanalMsgConsumer.class);
|
ExtensionLoader<CanalMsgConsumer> loader = new ExtensionLoader<>(CanalMsgConsumer.class);
|
||||||
canalMsgConsumer = loader
|
canalMsgConsumer = new ProxyCanalMsgConsumer(loader
|
||||||
.getExtension(canalClientConfig.getMode().toLowerCase(),destination ,CONNECTOR_SPI_DIR, CONNECTOR_STANDBY_SPI_DIR);
|
.getExtension(canalClientConfig.getMode().toLowerCase(), destination, CONNECTOR_SPI_DIR,
|
||||||
|
CONNECTOR_STANDBY_SPI_DIR));
|
||||||
|
|
||||||
Properties properties = canalClientConfig.getConsumerProperties();
|
Properties properties = canalClientConfig.getConsumerProperties();
|
||||||
properties.put(CanalConstants.CANAL_MQ_FLAT_MESSAGE, canalClientConfig.getFlatMessage());
|
properties.put(CanalConstants.CANAL_MQ_FLAT_MESSAGE, canalClientConfig.getFlatMessage());
|
||||||
properties.put(CanalConstants.CANAL_ALIYUN_ACCESS_KEY, canalClientConfig.getAccessKey());
|
properties.put(CanalConstants.CANAL_ALIYUN_ACCESS_KEY, canalClientConfig.getAccessKey());
|
||||||
properties.put(CanalConstants.CANAL_ALIYUN_SECRET_KEY, canalClientConfig.getSecretKey());
|
properties.put(CanalConstants.CANAL_ALIYUN_SECRET_KEY, canalClientConfig.getSecretKey());
|
||||||
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
|
||||||
Thread.currentThread().setContextClassLoader(canalMsgConsumer.getClass().getClassLoader());
|
|
||||||
canalMsgConsumer.init(properties, canalDestination, groupId);
|
canalMsgConsumer.init(properties, canalDestination, groupId);
|
||||||
Thread.currentThread().setContextClassLoader(cl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
@@ -88,6 +86,7 @@ public class AdapterProcessor {
|
|||||||
public void writeOut(final List<CommonMessage> commonMessages) {
|
public void writeOut(final List<CommonMessage> commonMessages) {
|
||||||
List<Future<Boolean>> futures = new ArrayList<>();
|
List<Future<Boolean>> futures = new ArrayList<>();
|
||||||
// 组间适配器并行运行
|
// 组间适配器并行运行
|
||||||
|
// 当 canalOuterAdapters 初始化失败时,消息将会全部丢失
|
||||||
canalOuterAdapters.forEach(outerAdapters -> {
|
canalOuterAdapters.forEach(outerAdapters -> {
|
||||||
futures.add(groupInnerExecutorService.submit(() -> {
|
futures.add(groupInnerExecutorService.submit(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -171,7 +170,7 @@ public class AdapterProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int retry = canalClientConfig.getRetries() == null
|
int retry = canalClientConfig.getRetries() == null
|
||||||
|| canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries();
|
|| canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries();
|
||||||
if (retry == -1) {
|
if (retry == -1) {
|
||||||
// 重试次数-1代表异常时一直阻塞重试
|
// 重试次数-1代表异常时一直阻塞重试
|
||||||
retry = Integer.MAX_VALUE;
|
retry = Integer.MAX_VALUE;
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ import org.springframework.core.env.EnumerablePropertySource;
|
|||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.core.env.PropertySource;
|
import org.springframework.core.env.PropertySource;
|
||||||
import org.springframework.core.env.StandardEnvironment;
|
import org.springframework.core.env.StandardEnvironment;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.adapter.launcher.config.SpringContext;
|
import com.alibaba.otter.canal.adapter.launcher.config.SpringContext;
|
||||||
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.ProxyOuterAdapter;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
|
import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader;
|
import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
|
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
|
||||||
@@ -69,7 +71,13 @@ public class CanalAdapterLoader {
|
|||||||
loadAdapter(config, canalOuterAdapters);
|
loadAdapter(config, canalOuterAdapters);
|
||||||
}
|
}
|
||||||
canalOuterAdapterGroups.add(canalOuterAdapters);
|
canalOuterAdapterGroups.add(canalOuterAdapters);
|
||||||
|
// canalOuterAdapters 存在初始化失败的情况,导致canalOuterAdapters的数量,可能小于group.getOuterAdapters
|
||||||
|
// 由于group下的 所有OuterAdapter实例都会重复消费同一批消息,因此不允许部分adapter初始化成功,必须全部初始化成功才允许消费
|
||||||
|
if(CollectionUtils.isEmpty(canalOuterAdapters) || canalOuterAdapters.size() != group.getOuterAdapters().size() ){
|
||||||
|
String msg = String.format("instance=%s,groupId=%s Load OuterAdapters is Empty,pls check rdb.yml",
|
||||||
|
canalAdapter.getInstance(),group.getGroupId());
|
||||||
|
throw new RuntimeException(msg);
|
||||||
|
}
|
||||||
AdapterProcessor adapterProcessor = canalAdapterProcessors.computeIfAbsent(
|
AdapterProcessor adapterProcessor = canalAdapterProcessors.computeIfAbsent(
|
||||||
canalAdapter.getInstance() + "|" + StringUtils.trimToEmpty(group.getGroupId()),
|
canalAdapter.getInstance() + "|" + StringUtils.trimToEmpty(group.getGroupId()),
|
||||||
f -> new AdapterProcessor(canalClientConfig,
|
f -> new AdapterProcessor(canalClientConfig,
|
||||||
@@ -77,7 +85,6 @@ public class CanalAdapterLoader {
|
|||||||
group.getGroupId(),
|
group.getGroupId(),
|
||||||
canalOuterAdapterGroups));
|
canalOuterAdapterGroups));
|
||||||
adapterProcessor.start();
|
adapterProcessor.start();
|
||||||
|
|
||||||
logger.info("Start adapter for canal-client mq topic: {} succeed",
|
logger.info("Start adapter for canal-client mq topic: {} succeed",
|
||||||
canalAdapter.getInstance() + "-" + group.getGroupId());
|
canalAdapter.getInstance() + "-" + group.getGroupId());
|
||||||
}
|
}
|
||||||
@@ -87,11 +94,8 @@ public class CanalAdapterLoader {
|
|||||||
private void loadAdapter(OuterAdapterConfig config, List<OuterAdapter> canalOutConnectors) {
|
private void loadAdapter(OuterAdapterConfig config, List<OuterAdapter> canalOutConnectors) {
|
||||||
try {
|
try {
|
||||||
OuterAdapter adapter;
|
OuterAdapter adapter;
|
||||||
adapter = loader.getExtension(config.getName(), config.getKey());
|
adapter = new ProxyOuterAdapter(loader.getExtension(config.getName(), config.getKey()));
|
||||||
|
|
||||||
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
|
||||||
// 替换ClassLoader
|
|
||||||
Thread.currentThread().setContextClassLoader(adapter.getClass().getClassLoader());
|
|
||||||
Environment env = (Environment) SpringContext.getBean(Environment.class);
|
Environment env = (Environment) SpringContext.getBean(Environment.class);
|
||||||
Properties evnProperties = null;
|
Properties evnProperties = null;
|
||||||
if (env instanceof StandardEnvironment) {
|
if (env instanceof StandardEnvironment) {
|
||||||
@@ -109,7 +113,7 @@ public class CanalAdapterLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
adapter.init(config, evnProperties);
|
adapter.init(config, evnProperties);
|
||||||
Thread.currentThread().setContextClassLoader(cl);
|
// rdb文件解析异常时,canalOuterAdapters 无法正常加载
|
||||||
canalOutConnectors.add(adapter);
|
canalOutConnectors.add(adapter);
|
||||||
logger.info("Load canal adapter: {} succeed", config.getName());
|
logger.info("Load canal adapter: {} succeed", config.getName());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -104,3 +104,13 @@ canal.conf:
|
|||||||
# jdbc.url: jdbc:phoenix:127.0.0.1:2181:/hbase/db
|
# jdbc.url: jdbc:phoenix:127.0.0.1:2181:/hbase/db
|
||||||
# jdbc.username:
|
# jdbc.username:
|
||||||
# jdbc.password:
|
# jdbc.password:
|
||||||
|
# - name: clickhouse
|
||||||
|
# key: clickhouse1
|
||||||
|
# properties:
|
||||||
|
# jdbc.driverClassName: ru.yandex.clickhouse.ClickHouseDriver
|
||||||
|
# jdbc.url: jdbc:clickhouse://127.0.0.1:8123/default
|
||||||
|
# jdbc.username: default
|
||||||
|
# jdbc.password: 123456
|
||||||
|
# batchSize: 3000
|
||||||
|
# scheduleTime: 600 # second unit
|
||||||
|
# threads: 3 # parallel threads
|
||||||
@@ -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>
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -17,13 +17,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.phoenix</groupId>
|
<groupId>org.apache.phoenix</groupId>
|
||||||
<artifactId>phoenix-core</artifactId>
|
<artifactId>phoenix-core</artifactId>
|
||||||
<version>4.14.1-HBase-1.4</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.hbase</groupId>
|
<groupId>org.apache.hbase</groupId>
|
||||||
<artifactId>hbase-client</artifactId>
|
<artifactId>hbase-client</artifactId>
|
||||||
<version>1.4.8</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 强制指定protobuf版本 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.protobuf</groupId>
|
<groupId>com.google.protobuf</groupId>
|
||||||
<artifactId>protobuf-java</artifactId>
|
<artifactId>protobuf-java</artifactId>
|
||||||
@@ -71,7 +70,6 @@
|
|||||||
<artifactId>fastjson2</artifactId>
|
<artifactId>fastjson2</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>mysql</groupId>
|
<groupId>mysql</groupId>
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
package com.alibaba.otter.canal.client.adapter.phoenix.service;
|
package com.alibaba.otter.canal.client.adapter.phoenix.service;
|
||||||
|
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.client.adapter.phoenix.PhoenixAdapter;
|
import com.alibaba.otter.canal.client.adapter.phoenix.PhoenixAdapter;
|
||||||
import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig;
|
import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig;
|
||||||
import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig.DbMapping;
|
import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig.DbMapping;
|
||||||
@@ -10,16 +22,6 @@ import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
|||||||
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
|
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.Util;
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
import java.sql.*;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Phoenix ETL 操作业务类
|
* Phoenix ETL 操作业务类
|
||||||
@@ -41,16 +43,17 @@ public class PhoenixEtlService {
|
|||||||
if (srcDataSource == null) {
|
if (srcDataSource == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
return syncSchema(srcDataSource.getConnection(), targetDSConnection, config);
|
try (Connection conn = srcDataSource.getConnection()) {
|
||||||
|
return syncSchema(conn, targetDSConnection, config);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean syncSchema(DataSource srcDS,Connection targetDSConnection, MappingConfig config) {
|
private static boolean syncSchema(DataSource srcDS,Connection targetDSConnection, MappingConfig config) {
|
||||||
try {
|
try (Connection conn = srcDS.getConnection()) {
|
||||||
return syncSchema(srcDS.getConnection(),targetDSConnection, config);
|
return syncSchema(conn, targetDSConnection, config);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
@@ -31,10 +31,12 @@
|
|||||||
<module>rdb</module>
|
<module>rdb</module>
|
||||||
<module>es6x</module>
|
<module>es6x</module>
|
||||||
<module>es7x</module>
|
<module>es7x</module>
|
||||||
|
<module>es8x</module>
|
||||||
<module>escore</module>
|
<module>escore</module>
|
||||||
<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>
|
||||||
@@ -53,7 +55,7 @@
|
|||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
<repository>
|
||||||
<id>central</id>
|
<id>central</id>
|
||||||
<url>http://repo1.maven.org/maven2</url>
|
<url>https://repo1.maven.org/maven2</url>
|
||||||
<releases>
|
<releases>
|
||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
</releases>
|
</releases>
|
||||||
@@ -63,7 +65,7 @@
|
|||||||
</repository>
|
</repository>
|
||||||
<repository>
|
<repository>
|
||||||
<id>java.net</id>
|
<id>java.net</id>
|
||||||
<url>http://download.java.net/maven/2/</url>
|
<url>https://download.java.net/maven/2/</url>
|
||||||
<releases>
|
<releases>
|
||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
</releases>
|
</releases>
|
||||||
@@ -73,7 +75,7 @@
|
|||||||
</repository>
|
</repository>
|
||||||
<repository>
|
<repository>
|
||||||
<id>aliyun</id>
|
<id>aliyun</id>
|
||||||
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
|
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
|
||||||
<releases>
|
<releases>
|
||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
</releases>
|
</releases>
|
||||||
@@ -110,12 +112,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>org.springframework.cloud</groupId>
|
||||||
<artifactId>spring-cloud-context</artifactId>
|
<artifactId>spring-cloud-context</artifactId>
|
||||||
<version>2.0.0.RELEASE</version>
|
<version>3.0.6</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot</artifactId>
|
<artifactId>spring-boot</artifactId>
|
||||||
<version>2.0.1.RELEASE</version>
|
<version>2.5.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.h2database</groupId>
|
<groupId>com.h2database</groupId>
|
||||||
@@ -126,35 +128,35 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.rocketmq</groupId>
|
<groupId>org.apache.rocketmq</groupId>
|
||||||
<artifactId>rocketmq-client</artifactId>
|
<artifactId>rocketmq-client</artifactId>
|
||||||
<version>4.5.1</version>
|
<version>${rocketmq_version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- 单独引入kafka依赖 -->
|
<!-- 单独引入kafka依赖 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.kafka</groupId>
|
<groupId>org.apache.kafka</groupId>
|
||||||
<artifactId>kafka-clients</artifactId>
|
<artifactId>kafka-clients</artifactId>
|
||||||
<version>1.1.1</version>
|
<version>${kafka_version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- rabbotmq -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.rabbitmq</groupId>
|
<groupId>com.rabbitmq</groupId>
|
||||||
<artifactId>amqp-client</artifactId>
|
<artifactId>amqp-client</artifactId>
|
||||||
<version>5.5.0</version>
|
<version>${rabbitmq_version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.mq-amqp</groupId>
|
<groupId>com.alibaba.mq-amqp</groupId>
|
||||||
<artifactId>mq-amqp-client</artifactId>
|
<artifactId>mq-amqp-client</artifactId>
|
||||||
<version>1.0.3</version>
|
<version>${mq_amqp_client}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- jdbc -->
|
<!-- jdbc -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>mysql</groupId>
|
<groupId>mysql</groupId>
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
<version>5.1.48</version>
|
<version>${mysql_driver_version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
<artifactId>postgresql</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
<version>42.1.4</version>
|
<version>42.5.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.oracle.database.jdbc</groupId>
|
<groupId>com.oracle.database.jdbc</groupId>
|
||||||
@@ -166,7 +168,6 @@
|
|||||||
<artifactId>mssql-jdbc</artifactId>
|
<artifactId>mssql-jdbc</artifactId>
|
||||||
<version>7.0.0.jre8</version>
|
<version>7.0.0.jre8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.hbase</groupId>
|
<groupId>org.apache.hbase</groupId>
|
||||||
<artifactId>hbase-shaded-client</artifactId>
|
<artifactId>hbase-shaded-client</artifactId>
|
||||||
@@ -182,6 +183,42 @@
|
|||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.phoenix</groupId>
|
||||||
|
<artifactId>phoenix-core</artifactId>
|
||||||
|
<version>4.14.1-HBase-1.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.hbase</groupId>
|
||||||
|
<artifactId>hbase-client</artifactId>
|
||||||
|
<version>1.4.8</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.yandex.clickhouse</groupId>
|
||||||
|
<artifactId>clickhouse-jdbc</artifactId>
|
||||||
|
<version>0.3.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun.openservices</groupId>
|
||||||
|
<artifactId>tablestore</artifactId>
|
||||||
|
<version>5.10.3</version>
|
||||||
|
<classifier>jar-with-dependencies</classifier>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.google.protobuf</groupId>
|
||||||
|
<artifactId>protobuf-java</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpasyncclient</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.kudu</groupId>
|
||||||
|
<artifactId>kudu-client</artifactId>
|
||||||
|
<version>1.6.0</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.curator</groupId>
|
<groupId>org.apache.curator</groupId>
|
||||||
<artifactId>curator-recipes</artifactId>
|
<artifactId>curator-recipes</artifactId>
|
||||||
@@ -201,8 +238,9 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.yaml</groupId>
|
<groupId>org.yaml</groupId>
|
||||||
<artifactId>snakeyaml</artifactId>
|
<artifactId>snakeyaml</artifactId>
|
||||||
<version>1.19</version>
|
<version>1.29</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- test -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.powermock</groupId>
|
<groupId>org.powermock</groupId>
|
||||||
<artifactId>powermock-api-mockito</artifactId>
|
<artifactId>powermock-api-mockito</artifactId>
|
||||||
@@ -233,39 +271,6 @@
|
|||||||
<version>1.9.0</version>
|
<version>1.9.0</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.aliyun.openservices</groupId>
|
|
||||||
<artifactId>tablestore</artifactId>
|
|
||||||
<version>5.10.3</version>
|
|
||||||
<classifier>jar-with-dependencies</classifier>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>com.google.protobuf</groupId>
|
|
||||||
<artifactId>protobuf-java</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
|
||||||
<artifactId>httpasyncclient</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
|
||||||
<artifactId>log4j-core</artifactId>
|
|
||||||
<version>${log4j_version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
|
||||||
<artifactId>log4j-api</artifactId>
|
|
||||||
<version>${log4j_version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
|
||||||
<artifactId>log4j-to-slf4j</artifactId>
|
|
||||||
<version>${log4j_version}</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -42,7 +42,6 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<version>4.12</version>
|
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
package com.alibaba.otter.canal.client.adapter.rdb;
|
package com.alibaba.otter.canal.client.adapter.rdb;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.client.adapter.support.FileName2KeyMapping;
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Collections;
|
import java.util.*;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.apache.commons.lang.BooleanUtils;
|
import org.apache.commons.lang.BooleanUtils;
|
||||||
@@ -27,11 +22,7 @@ import com.alibaba.otter.canal.client.adapter.rdb.service.RdbEtlService;
|
|||||||
import com.alibaba.otter.canal.client.adapter.rdb.service.RdbMirrorDbSyncService;
|
import com.alibaba.otter.canal.client.adapter.rdb.service.RdbMirrorDbSyncService;
|
||||||
import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService;
|
import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService;
|
||||||
import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil;
|
import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
import com.alibaba.otter.canal.client.adapter.support.*;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
|
|
||||||
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
|
|
||||||
import com.alibaba.otter.canal.client.adapter.support.SPI;
|
|
||||||
import com.alibaba.otter.canal.client.adapter.support.Util;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RDB适配器实现类
|
* RDB适配器实现类
|
||||||
@@ -84,6 +75,7 @@ public class RdbAdapter implements OuterAdapter {
|
|||||||
// 从jdbc url获取db类型
|
// 从jdbc url获取db类型
|
||||||
Map<String, String> properties = configuration.getProperties();
|
Map<String, String> properties = configuration.getProperties();
|
||||||
String dbType = JdbcUtils.getDbType(properties.get("jdbc.url"), null);
|
String dbType = JdbcUtils.getDbType(properties.get("jdbc.url"), null);
|
||||||
|
// 当.yml文件编码格式存在问题,此处rdb yml文件构建 可能会抛出异常
|
||||||
Map<String, MappingConfig> rdbMappingTmp = ConfigLoader.load(envProperties);
|
Map<String, MappingConfig> rdbMappingTmp = ConfigLoader.load(envProperties);
|
||||||
// 过滤不匹配的key的配置
|
// 过滤不匹配的key的配置
|
||||||
rdbMappingTmp.forEach((key, config) -> {
|
rdbMappingTmp.forEach((key, config) -> {
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ public class RdbEtlService extends AbstractEtlService {
|
|||||||
Map<String, String> columnsMap = new LinkedHashMap<>();
|
Map<String, String> columnsMap = new LinkedHashMap<>();
|
||||||
Map<String, Integer> columnType = new LinkedHashMap<>();
|
Map<String, Integer> columnType = new LinkedHashMap<>();
|
||||||
DruidDataSource dataSource = (DruidDataSource) srcDS;
|
DruidDataSource dataSource = (DruidDataSource) srcDS;
|
||||||
|
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
|
||||||
|
|
||||||
Util.sqlRS(targetDS,
|
Util.sqlRS(targetDS,
|
||||||
"SELECT * FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " LIMIT 1 ",
|
"SELECT * FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " LIMIT 1 ",
|
||||||
@@ -91,7 +92,7 @@ public class RdbEtlService extends AbstractEtlService {
|
|||||||
.append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()))
|
.append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()))
|
||||||
.append(" (");
|
.append(" (");
|
||||||
columnsMap
|
columnsMap
|
||||||
.forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(","));
|
.forEach((targetColumnName, srcColumnName) -> insertSql.append(backtick).append(targetColumnName).append(backtick).append(","));
|
||||||
|
|
||||||
int len = insertSql.length();
|
int len = insertSql.length();
|
||||||
insertSql.delete(len - 1, len).append(") VALUES (");
|
insertSql.delete(len - 1, len).append(") VALUES (");
|
||||||
@@ -101,6 +102,7 @@ public class RdbEtlService extends AbstractEtlService {
|
|||||||
}
|
}
|
||||||
len = insertSql.length();
|
len = insertSql.length();
|
||||||
insertSql.delete(len - 1, len).append(")");
|
insertSql.delete(len - 1, len).append(")");
|
||||||
|
logger.info("executeSqlImport sql:{}",insertSql.toString());
|
||||||
try (Connection connTarget = targetDS.getConnection();
|
try (Connection connTarget = targetDS.getConnection();
|
||||||
PreparedStatement pstmt = connTarget.prepareStatement(insertSql.toString())) {
|
PreparedStatement pstmt = connTarget.prepareStatement(insertSql.toString())) {
|
||||||
connTarget.setAutoCommit(false);
|
connTarget.setAutoCommit(false);
|
||||||
@@ -114,7 +116,7 @@ public class RdbEtlService extends AbstractEtlService {
|
|||||||
Map<String, Object> pkVal = new LinkedHashMap<>();
|
Map<String, Object> pkVal = new LinkedHashMap<>();
|
||||||
StringBuilder deleteSql = new StringBuilder(
|
StringBuilder deleteSql = new StringBuilder(
|
||||||
"DELETE FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " WHERE ");
|
"DELETE FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " WHERE ");
|
||||||
appendCondition(dbMapping, deleteSql, pkVal, rs);
|
appendCondition(dbMapping, deleteSql, pkVal, rs, backtick);
|
||||||
try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) {
|
try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) {
|
||||||
int k = 1;
|
int k = 1;
|
||||||
for (Object val : pkVal.values()) {
|
for (Object val : pkVal.values()) {
|
||||||
@@ -132,7 +134,6 @@ public class RdbEtlService extends AbstractEtlService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Integer type = columnType.get(targetClolumnName.toLowerCase());
|
Integer type = columnType.get(targetClolumnName.toLowerCase());
|
||||||
|
|
||||||
Object value = rs.getObject(srcColumnName);
|
Object value = rs.getObject(srcColumnName);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
SyncUtil.setPStmt(type, pstmt, value, i);
|
SyncUtil.setPStmt(type, pstmt, value, i);
|
||||||
@@ -180,7 +181,7 @@ public class RdbEtlService extends AbstractEtlService {
|
|||||||
* 拼接目标表主键where条件
|
* 拼接目标表主键where条件
|
||||||
*/
|
*/
|
||||||
private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map<String, Object> values,
|
private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map<String, Object> values,
|
||||||
ResultSet rs) throws SQLException {
|
ResultSet rs, String backtick) throws SQLException {
|
||||||
// 拼接主键
|
// 拼接主键
|
||||||
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) {
|
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) {
|
||||||
String targetColumnName = entry.getKey();
|
String targetColumnName = entry.getKey();
|
||||||
@@ -188,7 +189,7 @@ public class RdbEtlService extends AbstractEtlService {
|
|||||||
if (srcColumnName == null) {
|
if (srcColumnName == null) {
|
||||||
srcColumnName = targetColumnName;
|
srcColumnName = targetColumnName;
|
||||||
}
|
}
|
||||||
sql.append(targetColumnName).append("=? AND ");
|
sql.append(backtick).append(targetColumnName).append(backtick).append("=? AND ");
|
||||||
values.put(targetColumnName, rs.getObject(srcColumnName));
|
values.put(targetColumnName, rs.getObject(srcColumnName));
|
||||||
}
|
}
|
||||||
int len = sql.length();
|
int len = sql.length();
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -30,7 +30,6 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun.openservices</groupId>
|
<groupId>com.aliyun.openservices</groupId>
|
||||||
<artifactId>tablestore</artifactId>
|
<artifactId>tablestore</artifactId>
|
||||||
<version>5.10.3</version>
|
|
||||||
<classifier>jar-with-dependencies</classifier>
|
<classifier>jar-with-dependencies</classifier>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
@@ -43,8 +42,6 @@
|
|||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -101,42 +101,44 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.rocketmq</groupId>
|
<groupId>org.apache.rocketmq</groupId>
|
||||||
<artifactId>rocketmq-client</artifactId>
|
<artifactId>rocketmq-client</artifactId>
|
||||||
<version>4.5.2</version>
|
<version>${rocketmq_version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.rocketmq</groupId>
|
<groupId>org.apache.rocketmq</groupId>
|
||||||
<artifactId>rocketmq-acl</artifactId>
|
<artifactId>rocketmq-acl</artifactId>
|
||||||
<version>4.5.2</version>
|
<version>${rocketmq_version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.rabbitmq</groupId>
|
<groupId>com.rabbitmq</groupId>
|
||||||
<artifactId>amqp-client</artifactId>
|
<artifactId>amqp-client</artifactId>
|
||||||
<version>5.5.0</version>
|
<version>${rabbitmq_version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.mq-amqp</groupId>
|
<groupId>com.alibaba.mq-amqp</groupId>
|
||||||
<artifactId>mq-amqp-client</artifactId>
|
<artifactId>mq-amqp-client</artifactId>
|
||||||
<version>1.0.3</version>
|
<version>${mq_amqp_client}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.kafka</groupId>
|
<groupId>org.apache.kafka</groupId>
|
||||||
<artifactId>kafka-clients</artifactId>
|
<artifactId>kafka-clients</artifactId>
|
||||||
<version>1.1.1</version>
|
<version>${kafka_version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- Pulsar -->
|
<!-- Pulsar -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.pulsar</groupId>
|
<groupId>org.apache.pulsar</groupId>
|
||||||
<artifactId>pulsar-client</artifactId>
|
<artifactId>pulsar-client</artifactId>
|
||||||
|
<version>${pulsar_version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.pulsar</groupId>
|
<groupId>org.apache.pulsar</groupId>
|
||||||
<artifactId>pulsar-client-admin</artifactId>
|
<artifactId>pulsar-client-admin</artifactId>
|
||||||
|
<version>${pulsar_version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|||||||
@@ -61,11 +61,11 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
batchSize = 100;
|
batchSize = 100;
|
||||||
}
|
}
|
||||||
properties.put("max.poll.records", batchSize.toString());
|
properties.put("max.poll.records", batchSize.toString());
|
||||||
properties.put("key.deserializer", StringDeserializer.class.getName());
|
properties.put("key.deserializer", StringDeserializer.class);
|
||||||
if (!flatMessage) {
|
if (!flatMessage) {
|
||||||
properties.put("value.deserializer", MessageDeserializer.class.getName());
|
properties.put("value.deserializer", MessageDeserializer.class);
|
||||||
} else {
|
} else {
|
||||||
properties.put("value.deserializer", StringDeserializer.class.getName());
|
properties.put("value.deserializer", StringDeserializer.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,14 +183,13 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
|
|
||||||
ConsumerRecords<String, Message> records = kafkaConsumer.poll(unit.toMillis(timeout));
|
ConsumerRecords<String, Message> records = kafkaConsumer.poll(unit.toMillis(timeout));
|
||||||
|
|
||||||
currentOffsets.clear();
|
|
||||||
for (TopicPartition topicPartition : records.partitions()) {
|
|
||||||
currentOffsets.put(topicPartition.partition(), kafkaConsumer.position(topicPartition));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!records.isEmpty()) {
|
if (!records.isEmpty()) {
|
||||||
|
currentOffsets.clear();
|
||||||
List<Message> messages = new ArrayList<>();
|
List<Message> messages = new ArrayList<>();
|
||||||
for (ConsumerRecord<String, Message> record : records) {
|
for (ConsumerRecord<String, Message> record : records) {
|
||||||
|
if (currentOffsets.get(record.partition()) == null) {
|
||||||
|
currentOffsets.put(record.partition(), record.offset());
|
||||||
|
}
|
||||||
messages.add(record.value());
|
messages.add(record.value());
|
||||||
}
|
}
|
||||||
return messages;
|
return messages;
|
||||||
@@ -221,14 +220,13 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
|
|
||||||
ConsumerRecords<String, String> records = kafkaConsumer2.poll(unit.toMillis(timeout));
|
ConsumerRecords<String, String> records = kafkaConsumer2.poll(unit.toMillis(timeout));
|
||||||
|
|
||||||
currentOffsets.clear();
|
|
||||||
for (TopicPartition topicPartition : records.partitions()) {
|
|
||||||
currentOffsets.put(topicPartition.partition(), kafkaConsumer2.position(topicPartition));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!records.isEmpty()) {
|
if (!records.isEmpty()) {
|
||||||
|
currentOffsets.clear();
|
||||||
List<FlatMessage> flatMessages = new ArrayList<>();
|
List<FlatMessage> flatMessages = new ArrayList<>();
|
||||||
for (ConsumerRecord<String, String> record : records) {
|
for (ConsumerRecord<String, String> record : records) {
|
||||||
|
if (currentOffsets.get(record.partition()) == null) {
|
||||||
|
currentOffsets.put(record.partition(), record.offset());
|
||||||
|
}
|
||||||
String flatMessageJson = record.value();
|
String flatMessageJson = record.value();
|
||||||
FlatMessage flatMessage = JSON.parseObject(flatMessageJson, FlatMessage.class);
|
FlatMessage flatMessage = JSON.parseObject(flatMessageJson, FlatMessage.class);
|
||||||
flatMessages.add(flatMessage);
|
flatMessages.add(flatMessage);
|
||||||
@@ -248,12 +246,14 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
// 回滚所有分区
|
// 回滚所有分区
|
||||||
if (kafkaConsumer != null) {
|
if (kafkaConsumer != null) {
|
||||||
for (Map.Entry<Integer, Long> entry : currentOffsets.entrySet()) {
|
for (Map.Entry<Integer, Long> entry : currentOffsets.entrySet()) {
|
||||||
kafkaConsumer.seek(new TopicPartition(topic, entry.getKey()), entry.getValue() - 1);
|
kafkaConsumer.seek(new TopicPartition(topic, entry.getKey()), currentOffsets.get(entry.getKey()));
|
||||||
|
kafkaConsumer.commitSync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (kafkaConsumer2 != null) {
|
if (kafkaConsumer2 != null) {
|
||||||
for (Map.Entry<Integer, Long> entry : currentOffsets.entrySet()) {
|
for (Map.Entry<Integer, Long> entry : currentOffsets.entrySet()) {
|
||||||
kafkaConsumer2.seek(new TopicPartition(topic, entry.getKey()), entry.getValue() - 1);
|
kafkaConsumer2.seek(new TopicPartition(topic, entry.getKey()), currentOffsets.get(entry.getKey()));
|
||||||
|
kafkaConsumer.commitSync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
private ServerRunningData serverData;
|
private ServerRunningData serverData;
|
||||||
// 当前实际运行的节点状态信息
|
// 当前实际运行的节点状态信息
|
||||||
private volatile ServerRunningData activeData;
|
private volatile ServerRunningData activeData;
|
||||||
private ScheduledExecutorService delayExector = Executors.newScheduledThreadPool(1);
|
private ScheduledExecutorService delayExecutor;
|
||||||
private int delayTime = 5;
|
private int delayTime = 5;
|
||||||
private ServerRunningListener listener;
|
private ServerRunningListener listener;
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
initRunning();
|
initRunning();
|
||||||
} else {
|
} else {
|
||||||
// 否则就是等待delayTime,避免因网络瞬端或者zk异常,导致出现频繁的切换操作
|
// 否则就是等待delayTime,避免因网络瞬端或者zk异常,导致出现频繁的切换操作
|
||||||
delayExector.schedule(() -> initRunning(), delayTime, TimeUnit.SECONDS);
|
delayExecutor.schedule(() -> initRunning(), delayTime, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +90,7 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
try {
|
try {
|
||||||
processStart();
|
processStart();
|
||||||
if (zkClient != null) {
|
if (zkClient != null) {
|
||||||
|
delayExecutor = Executors.newScheduledThreadPool(1);
|
||||||
// 如果需要尽可能释放instance资源,不需要监听running节点,不然即使stop了这台机器,另一台机器立马会start
|
// 如果需要尽可能释放instance资源,不需要监听running节点,不然即使stop了这台机器,另一台机器立马会start
|
||||||
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
|
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
|
||||||
zkClient.subscribeDataChanges(path, dataListener);
|
zkClient.subscribeDataChanges(path, dataListener);
|
||||||
@@ -122,6 +123,10 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
if (zkClient != null) {
|
if (zkClient != null) {
|
||||||
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
|
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
|
||||||
zkClient.unsubscribeDataChanges(path, dataListener);
|
zkClient.unsubscribeDataChanges(path, dataListener);
|
||||||
|
if (delayExecutor != null) {
|
||||||
|
delayExecutor.shutdown();
|
||||||
|
delayExecutor = null;
|
||||||
|
}
|
||||||
|
|
||||||
releaseRunning(); // 尝试一下release
|
releaseRunning(); // 尝试一下release
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -107,11 +107,11 @@ public class AviaterRegexFilter {
|
|||||||
private List<String> completionPattern(List<String> patterns) {
|
private List<String> completionPattern(List<String> patterns) {
|
||||||
List<String> result = new ArrayList<>();
|
List<String> result = new ArrayList<>();
|
||||||
for (String pattern : patterns) {
|
for (String pattern : patterns) {
|
||||||
StringBuffer stringBuffer = new StringBuffer();
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
stringBuffer.append("^");
|
stringBuilder.append("^");
|
||||||
stringBuffer.append(pattern);
|
stringBuilder.append(pattern);
|
||||||
stringBuffer.append("$");
|
stringBuilder.append("$");
|
||||||
result.add(stringBuffer.toString());
|
result.add(stringBuilder.toString());
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -374,6 +374,7 @@ public class MQMessageUtils {
|
|||||||
flatMessage.setEs(entry.getHeader().getExecuteTime());
|
flatMessage.setEs(entry.getHeader().getExecuteTime());
|
||||||
flatMessage.setTs(System.currentTimeMillis());
|
flatMessage.setTs(System.currentTimeMillis());
|
||||||
flatMessage.setSql(rowChange.getSql());
|
flatMessage.setSql(rowChange.getSql());
|
||||||
|
flatMessage.setGtid(entry.getHeader().getGtid());
|
||||||
|
|
||||||
if (!rowChange.getIsDdl()) {
|
if (!rowChange.getIsDdl()) {
|
||||||
Map<String, Integer> sqlType = new LinkedHashMap<>();
|
Map<String, Integer> sqlType = new LinkedHashMap<>();
|
||||||
@@ -432,9 +433,7 @@ public class MQMessageUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// update操作将记录修改前的值
|
// update操作将记录修改前的值
|
||||||
if (!rowOld.isEmpty()) {
|
old.add(rowOld);
|
||||||
old.add(rowOld);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!sqlType.isEmpty()) {
|
if (!sqlType.isEmpty()) {
|
||||||
@@ -526,6 +525,7 @@ public class MQMessageUtils {
|
|||||||
flatMessageTmp.setEs(flatMessage.getEs());
|
flatMessageTmp.setEs(flatMessage.getEs());
|
||||||
flatMessageTmp.setTs(flatMessage.getTs());
|
flatMessageTmp.setTs(flatMessage.getTs());
|
||||||
flatMessageTmp.setPkNames(flatMessage.getPkNames());
|
flatMessageTmp.setPkNames(flatMessage.getPkNames());
|
||||||
|
flatMessageTmp.setGtid(flatMessage.getGtid());
|
||||||
}
|
}
|
||||||
List<Map<String, String>> data = flatMessageTmp.getData();
|
List<Map<String, String>> data = flatMessageTmp.getData();
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.alibaba.otter.canal.connector.core.spi;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.connector.core.config.MQProperties;
|
||||||
|
import com.alibaba.otter.canal.connector.core.producer.MQDestination;
|
||||||
|
import com.alibaba.otter.canal.connector.core.util.Callback;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public class ProxyCanalMQProducer implements CanalMQProducer {
|
||||||
|
|
||||||
|
private CanalMQProducer canalMQProducer;
|
||||||
|
|
||||||
|
public ProxyCanalMQProducer(CanalMQProducer canalMQProducer) {
|
||||||
|
this.canalMQProducer = canalMQProducer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClassLoader changeCL() {
|
||||||
|
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||||
|
Thread.currentThread().setContextClassLoader(canalMQProducer.getClass().getClassLoader());
|
||||||
|
return cl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void revertCL(ClassLoader cl) {
|
||||||
|
Thread.currentThread().setContextClassLoader(cl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Properties properties) {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
canalMQProducer.init(properties);
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MQProperties getMqProperties() {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
return canalMQProducer.getMqProperties();
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(MQDestination canalDestination, Message message, Callback callback) {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
canalMQProducer.send(canalDestination, message, callback);
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
canalMQProducer.stop();
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package com.alibaba.otter.canal.connector.core.spi;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.connector.core.consumer.CommonMessage;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class ProxyCanalMsgConsumer implements CanalMsgConsumer {
|
||||||
|
|
||||||
|
private CanalMsgConsumer canalMsgConsumer;
|
||||||
|
|
||||||
|
public ProxyCanalMsgConsumer(CanalMsgConsumer canalMsgConsumer) {
|
||||||
|
this.canalMsgConsumer = canalMsgConsumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClassLoader changeCL() {
|
||||||
|
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||||
|
Thread.currentThread().setContextClassLoader(canalMsgConsumer.getClass().getClassLoader());
|
||||||
|
return cl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void revertCL(ClassLoader cl) {
|
||||||
|
Thread.currentThread().setContextClassLoader(cl);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Properties properties, String topic, String groupId) {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
canalMsgConsumer.init(properties, topic, groupId);
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect() {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
canalMsgConsumer.connect();
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CommonMessage> getMessage(Long timeout, TimeUnit unit) {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
return canalMsgConsumer.getMessage(timeout, unit);
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void ack() {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
canalMsgConsumer.ack();
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollback() {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
canalMsgConsumer.rollback();
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnect() {
|
||||||
|
ClassLoader cl = changeCL();
|
||||||
|
try {
|
||||||
|
canalMsgConsumer.disconnect();
|
||||||
|
} finally {
|
||||||
|
revertCL(cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.kafka</groupId>
|
<groupId>org.apache.kafka</groupId>
|
||||||
<artifactId>kafka_2.11</artifactId>
|
<artifactId>kafka-clients</artifactId>
|
||||||
<version>1.1.1</version>
|
<version>${kafka_version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
@@ -89,4 +89,4 @@
|
|||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
<repository>
|
||||||
<id>central</id>
|
<id>central</id>
|
||||||
<url>http://repo1.maven.org/maven2</url>
|
<url>https://repo1.maven.org/maven2</url>
|
||||||
<releases>
|
<releases>
|
||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
</releases>
|
</releases>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
</repository>
|
</repository>
|
||||||
<repository>
|
<repository>
|
||||||
<id>java.net</id>
|
<id>java.net</id>
|
||||||
<url>http://download.java.net/maven/2/</url>
|
<url>https://download.java.net/maven/2/</url>
|
||||||
<releases>
|
<releases>
|
||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
</releases>
|
</releases>
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
</repository>
|
</repository>
|
||||||
<repository>
|
<repository>
|
||||||
<id>aliyun</id>
|
<id>aliyun</id>
|
||||||
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
|
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
|
||||||
<releases>
|
<releases>
|
||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
</releases>
|
</releases>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -35,10 +35,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.pulsar</groupId>
|
<groupId>org.apache.pulsar</groupId>
|
||||||
<artifactId>pulsar-client</artifactId>
|
<artifactId>pulsar-client</artifactId>
|
||||||
|
<version>${pulsar_version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.pulsar</groupId>
|
<groupId>org.apache.pulsar</groupId>
|
||||||
<artifactId>pulsar-client-admin</artifactId>
|
<artifactId>pulsar-client-admin</artifactId>
|
||||||
|
<version>${pulsar_version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONWriter;
|
||||||
import com.alibaba.fastjson2.JSONWriter.Feature;
|
import com.alibaba.fastjson2.JSONWriter.Feature;
|
||||||
import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
|
import com.alibaba.otter.canal.common.utils.ExecutorTemplate;
|
||||||
import com.alibaba.otter.canal.common.utils.NamedThreadFactory;
|
import com.alibaba.otter.canal.common.utils.NamedThreadFactory;
|
||||||
@@ -330,7 +331,7 @@ public class CanalPulsarMQProducer extends AbstractMQProducer implements CanalMQ
|
|||||||
try {
|
try {
|
||||||
MessageId msgResultId = producer.newMessage()
|
MessageId msgResultId = producer.newMessage()
|
||||||
.property(MSG_PROPERTY_PARTITION_NAME, String.valueOf(partition))
|
.property(MSG_PROPERTY_PARTITION_NAME, String.valueOf(partition))
|
||||||
.value(JSON.toJSONBytes(f, Feature.WriteNulls))
|
.value(JSON.toJSONBytes(f, Feature.WriteNulls, JSONWriter.Feature.LargeObject))
|
||||||
.send()
|
.send()
|
||||||
//
|
//
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
@@ -29,12 +29,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.rabbitmq</groupId>
|
<groupId>com.rabbitmq</groupId>
|
||||||
<artifactId>amqp-client</artifactId>
|
<artifactId>amqp-client</artifactId>
|
||||||
<version>5.5.0</version>
|
<version>${rabbitmq_version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.mq-amqp</groupId>
|
<groupId>com.alibaba.mq-amqp</groupId>
|
||||||
<artifactId>mq-amqp-client</artifactId>
|
<artifactId>mq-amqp-client</artifactId>
|
||||||
<version>1.0.3</version>
|
<version>${mq_amqp_client}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
@@ -94,4 +94,4 @@
|
|||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ public class RabbitMQConstants {
|
|||||||
public static final String RABBITMQ_VIRTUAL_HOST = ROOT + "." + "virtual.host";
|
public static final String RABBITMQ_VIRTUAL_HOST = ROOT + "." + "virtual.host";
|
||||||
public static final String RABBITMQ_USERNAME = ROOT + "." + "username";
|
public static final String RABBITMQ_USERNAME = ROOT + "." + "username";
|
||||||
public static final String RABBITMQ_PASSWORD = ROOT + "." + "password";
|
public static final String RABBITMQ_PASSWORD = ROOT + "." + "password";
|
||||||
|
public static final String RABBITMQ_QUEUE = ROOT + "." + "queue";
|
||||||
|
public static final String RABBITMQ_ROUTING_KEY = ROOT + "." + "routingKey";
|
||||||
|
public static final String RABBITMQ_DELIVERY_MODE = ROOT + "." + "deliveryMode";
|
||||||
|
|
||||||
public static final String RABBITMQ_RESOURCE_OWNERID = ROOT + "." + "rabbitmq.resource.ownerId";
|
public static final String RABBITMQ_RESOURCE_OWNERID = ROOT + "." + "rabbitmq.resource.ownerId";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ public class RabbitMQProducerConfig extends MQProperties {
|
|||||||
private String exchange;
|
private String exchange;
|
||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
|
private String queue;
|
||||||
|
private String routingKey;
|
||||||
|
private String deliveryMode;
|
||||||
|
|
||||||
public String getHost() {
|
public String getHost() {
|
||||||
return host;
|
return host;
|
||||||
@@ -55,4 +58,28 @@ public class RabbitMQProducerConfig extends MQProperties {
|
|||||||
public void setPassword(String password) {
|
public void setPassword(String password) {
|
||||||
this.password = password;
|
this.password = password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getQueue() {
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQueue(String queue) {
|
||||||
|
this.queue = queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoutingKey() {
|
||||||
|
return routingKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoutingKey(String routingKey) {
|
||||||
|
this.routingKey = routingKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeliveryMode() {
|
||||||
|
return deliveryMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeliveryMode(String deliveryMode) {
|
||||||
|
this.deliveryMode = deliveryMode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ 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.PropertiesUtils;
|
|
||||||
import org.slf4j.Logger;
|
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.otter.canal.common.utils.PropertiesUtils;
|
||||||
import com.alibaba.otter.canal.connector.core.config.CanalConstants;
|
import com.alibaba.otter.canal.connector.core.config.CanalConstants;
|
||||||
import com.alibaba.otter.canal.connector.core.consumer.CommonMessage;
|
import com.alibaba.otter.canal.connector.core.consumer.CommonMessage;
|
||||||
import com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer;
|
import com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer;
|
||||||
@@ -24,13 +24,7 @@ import com.alibaba.otter.canal.connector.rabbitmq.config.RabbitMQConstants;
|
|||||||
import com.alibaba.otter.canal.connector.rabbitmq.producer.AliyunCredentialsProvider;
|
import com.alibaba.otter.canal.connector.rabbitmq.producer.AliyunCredentialsProvider;
|
||||||
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.rabbitmq.client.AMQP;
|
import com.rabbitmq.client.*;
|
||||||
import com.rabbitmq.client.Channel;
|
|
||||||
import com.rabbitmq.client.Connection;
|
|
||||||
import com.rabbitmq.client.ConnectionFactory;
|
|
||||||
import com.rabbitmq.client.Consumer;
|
|
||||||
import com.rabbitmq.client.DefaultConsumer;
|
|
||||||
import com.rabbitmq.client.Envelope;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RabbitMQ consumer SPI 实现
|
* RabbitMQ consumer SPI 实现
|
||||||
@@ -41,7 +35,8 @@ import com.rabbitmq.client.Envelope;
|
|||||||
@SPI("rabbitmq")
|
@SPI("rabbitmq")
|
||||||
public class CanalRabbitMQConsumer implements CanalMsgConsumer {
|
public class CanalRabbitMQConsumer implements CanalMsgConsumer {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CanalRabbitMQConsumer.class);
|
private static final Logger logger = LoggerFactory
|
||||||
|
.getLogger(CanalRabbitMQConsumer.class);
|
||||||
|
|
||||||
// 链接地址
|
// 链接地址
|
||||||
private String nameServer;
|
private String nameServer;
|
||||||
@@ -91,7 +86,7 @@ public class CanalRabbitMQConsumer implements CanalMsgConsumer {
|
|||||||
factory.setUsername(username);
|
factory.setUsername(username);
|
||||||
factory.setPassword(password);
|
factory.setPassword(password);
|
||||||
}
|
}
|
||||||
//解析出端口 modified by 16075140
|
// 解析出端口 modified by 16075140
|
||||||
if (nameServer != null && nameServer.contains(":")) {
|
if (nameServer != null && nameServer.contains(":")) {
|
||||||
String[] serverHostAndPort = nameServer.split(":");
|
String[] serverHostAndPort = nameServer.split(":");
|
||||||
factory.setHost(serverHostAndPort[0]);
|
factory.setHost(serverHostAndPort[0]);
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -26,10 +29,7 @@ import com.alibaba.otter.canal.connector.rabbitmq.config.RabbitMQConstants;
|
|||||||
import com.alibaba.otter.canal.connector.rabbitmq.config.RabbitMQProducerConfig;
|
import com.alibaba.otter.canal.connector.rabbitmq.config.RabbitMQProducerConfig;
|
||||||
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;
|
||||||
import com.rabbitmq.client.AlreadyClosedException;
|
import com.rabbitmq.client.*;
|
||||||
import com.rabbitmq.client.Channel;
|
|
||||||
import com.rabbitmq.client.Connection;
|
|
||||||
import com.rabbitmq.client.ConnectionFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RabbitMQ Producer SPI 实现
|
* RabbitMQ Producer SPI 实现
|
||||||
@@ -54,7 +54,13 @@ public class CanalRabbitMQProducer extends AbstractMQProducer implements CanalMQ
|
|||||||
|
|
||||||
ConnectionFactory factory = new ConnectionFactory();
|
ConnectionFactory factory = new ConnectionFactory();
|
||||||
String servers = rabbitMQProperties.getHost();
|
String servers = rabbitMQProperties.getHost();
|
||||||
if (servers.contains(":")) {
|
if (servers.startsWith("amqp")) {
|
||||||
|
try {
|
||||||
|
factory.setUri(servers);
|
||||||
|
} catch (URISyntaxException | NoSuchAlgorithmException | KeyManagementException ex) {
|
||||||
|
throw new CanalException("failed to parse host", ex);
|
||||||
|
}
|
||||||
|
} else if (servers.contains(":")) {
|
||||||
String[] serverHostAndPort = servers.split(":");
|
String[] serverHostAndPort = servers.split(":");
|
||||||
factory.setHost(serverHostAndPort[0]);
|
factory.setHost(serverHostAndPort[0]);
|
||||||
factory.setPort(Integer.parseInt(serverHostAndPort[1]));
|
factory.setPort(Integer.parseInt(serverHostAndPort[1]));
|
||||||
@@ -75,7 +81,13 @@ public class CanalRabbitMQProducer extends AbstractMQProducer implements CanalMQ
|
|||||||
try {
|
try {
|
||||||
connect = factory.newConnection();
|
connect = factory.newConnection();
|
||||||
channel = connect.createChannel();
|
channel = connect.createChannel();
|
||||||
// channel.exchangeDeclare(mqProperties.getExchange(), "topic");
|
channel.queueDeclare(rabbitMQProperties.getQueue(), true, false, false, null);
|
||||||
|
channel.exchangeDeclare(rabbitMQProperties
|
||||||
|
.getExchange(), rabbitMQProperties.getDeliveryMode(), true, false, false, null);
|
||||||
|
channel.queueBind(rabbitMQProperties.getQueue(),
|
||||||
|
rabbitMQProperties.getExchange(),
|
||||||
|
rabbitMQProperties.getRoutingKey());
|
||||||
|
|
||||||
} catch (IOException | TimeoutException ex) {
|
} catch (IOException | TimeoutException ex) {
|
||||||
throw new CanalException("Start RabbitMQ producer error", ex);
|
throw new CanalException("Start RabbitMQ producer error", ex);
|
||||||
}
|
}
|
||||||
@@ -106,6 +118,18 @@ public class CanalRabbitMQProducer extends AbstractMQProducer implements CanalMQ
|
|||||||
if (!StringUtils.isEmpty(password)) {
|
if (!StringUtils.isEmpty(password)) {
|
||||||
rabbitMQProperties.setPassword(password);
|
rabbitMQProperties.setPassword(password);
|
||||||
}
|
}
|
||||||
|
String queue = PropertiesUtils.getProperty(properties, RabbitMQConstants.RABBITMQ_QUEUE);
|
||||||
|
if (!StringUtils.isEmpty(queue)) {
|
||||||
|
rabbitMQProperties.setQueue(queue);
|
||||||
|
}
|
||||||
|
String routingKey = PropertiesUtils.getProperty(properties, RabbitMQConstants.RABBITMQ_ROUTING_KEY);
|
||||||
|
if (!StringUtils.isEmpty(routingKey)) {
|
||||||
|
rabbitMQProperties.setRoutingKey(routingKey);
|
||||||
|
}
|
||||||
|
String deliveryMode = PropertiesUtils.getProperty(properties, RabbitMQConstants.RABBITMQ_DELIVERY_MODE);
|
||||||
|
if (!StringUtils.isEmpty(deliveryMode)) {
|
||||||
|
rabbitMQProperties.setDeliveryMode(deliveryMode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -114,9 +138,8 @@ public class CanalRabbitMQProducer extends AbstractMQProducer implements CanalMQ
|
|||||||
try {
|
try {
|
||||||
if (!StringUtils.isEmpty(destination.getDynamicTopic())) {
|
if (!StringUtils.isEmpty(destination.getDynamicTopic())) {
|
||||||
// 动态topic
|
// 动态topic
|
||||||
Map<String, Message> messageMap = MQMessageUtils.messageTopics(message,
|
Map<String, Message> messageMap = MQMessageUtils
|
||||||
destination.getTopic(),
|
.messageTopics(message, destination.getTopic(), destination.getDynamicTopic());
|
||||||
destination.getDynamicTopic());
|
|
||||||
|
|
||||||
for (Map.Entry<String, com.alibaba.otter.canal.protocol.Message> entry : messageMap.entrySet()) {
|
for (Map.Entry<String, com.alibaba.otter.canal.protocol.Message> entry : messageMap.entrySet()) {
|
||||||
final String topicName = entry.getKey().replace('.', '_');
|
final String topicName = entry.getKey().replace('.', '_');
|
||||||
@@ -151,7 +174,8 @@ public class CanalRabbitMQProducer extends AbstractMQProducer implements CanalMQ
|
|||||||
// 串行分区
|
// 串行分区
|
||||||
List<FlatMessage> flatMessages = MQMessageUtils.messageConverter(datas, messageSub.getId());
|
List<FlatMessage> flatMessages = MQMessageUtils.messageConverter(datas, messageSub.getId());
|
||||||
for (FlatMessage flatMessage : flatMessages) {
|
for (FlatMessage flatMessage : flatMessages) {
|
||||||
byte[] message = JSON.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls);
|
byte[] message = JSON
|
||||||
|
.toJSONBytes(flatMessage, JSONWriter.Feature.WriteNulls, JSONWriter.Feature.LargeObject);
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("send message:{} to destination:{}", message, canalDestination.getCanalDestination());
|
logger.debug("send message:{} to destination:{}", message, canalDestination.getCanalDestination());
|
||||||
}
|
}
|
||||||
@@ -165,7 +189,10 @@ public class CanalRabbitMQProducer extends AbstractMQProducer implements CanalMQ
|
|||||||
// tips: 目前逻辑中暂不处理对exchange处理,请在Console后台绑定 才可使用routekey
|
// tips: 目前逻辑中暂不处理对exchange处理,请在Console后台绑定 才可使用routekey
|
||||||
try {
|
try {
|
||||||
RabbitMQProducerConfig rabbitMQProperties = (RabbitMQProducerConfig) this.mqProperties;
|
RabbitMQProducerConfig rabbitMQProperties = (RabbitMQProducerConfig) this.mqProperties;
|
||||||
channel.basicPublish(rabbitMQProperties.getExchange(), queueName, null, message);
|
channel.basicPublish(rabbitMQProperties.getExchange(),
|
||||||
|
queueName,
|
||||||
|
MessageProperties.PERSISTENT_TEXT_PLAIN,
|
||||||
|
message);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
@@ -13,10 +13,6 @@
|
|||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<name>canal connector rocketMQ module for otter ${project.version}</name>
|
<name>canal connector rocketMQ module for otter ${project.version}</name>
|
||||||
|
|
||||||
<properties>
|
|
||||||
<rocketmq_version>4.8.0</rocketmq_version>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -99,4 +95,4 @@
|
|||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user