Compare commits

...
76 Commits
Author SHA1 Message Date
jianghang.loujh a96db39420 add polardb-x parser test 2022-06-13 09:39:29 +08:00
温绍锦 bac1d543f1 update fastjson & druid version (#4224)
* 修复升级2.0.4导致兼容的问题

* update druid & fastjson version
2022-06-13 09:31:46 +08:00
jianghang.loujh 753fad6639 fixed issue #4216 , support polardb-x show tables 2022-06-10 13:36:39 +08:00
jianghang.loujh bc4dceea1a fixed issue #4216 , support polardb-x show databases 2022-06-10 11:37:52 +08:00
温绍锦 7aaee7b439 修复升级2.0.4导致兼容的问题 (#4202) 2022-05-30 10:29:38 +08:00
温绍锦 e19bf1ecff Fastjson2 compatible fix (#4189)
* update fastjson2 version, 2.0.2 -> 2.0.3

* update fastjson & druid version

* bug fix for fastjson2 compatible
2022-05-25 10:22:26 +08:00
jianghang.loujh a99dc0cbe8 fixed testcase 2022-05-24 11:18:40 +08:00
jianghang.loujh 7b8ff04d20 [maven-release-plugin] prepare for next development iteration 2022-05-24 10:26:12 +08:00
jianghang.loujh 7ee246ca5a [maven-release-plugin] prepare release canal-1.1.6 2022-05-24 10:26:06 +08:00
jianghang.loujh 54c645f315 fixed test failed 2022-05-24 10:12:26 +08:00
jianghang.loujh 6c81ab6fdc fixed compiler error 2022-05-24 10:08:02 +08:00
jianghang.loujh 99aefaa118 support mysql 8.0 transaction_payload_event 2022-05-23 23:23:52 +08:00
jianghang.loujh e2248b3d35 update phoenix pom.xml 2022-05-23 19:30:19 +08:00
jianghang.loujh eef1ee85fb fixed issue #4014, support java8 & java11 G1 GC 2022-05-23 18:23:31 +08:00
温绍锦 7dfec19c68 update druid & fastjson2 version (#4183)
* update fastjson2 version, 2.0.2 -> 2.0.3

* update fastjson & druid version
2022-05-23 10:03:41 +08:00
jianghang.loujh 05a52f1bd3 upgrade log4j version 2022-05-20 16:58:20 +08:00
jianghang.loujh fb0f274ba8 fixed logger 2022-05-20 16:09:53 +08:00
jianghang.loujh d71a208123 fixed issue #4178 , support -00:00:00.12 2022-05-20 10:37:53 +08:00
jianghang.loujh 656a1d23c2 instance.properties demo 2022-05-19 19:56:50 +08:00
jianghang.loujh a574b91fdf fixed #3976 , support LogEvent.MYSQL_TYPE_TYPED_ARRAY 2022-05-19 19:53:54 +08:00
jianghang.loujh e41e903984 fixed issue #3538 , compatiable mysql8.0 filed meta 2022-05-17 17:17:32 +08:00
jianghang.loujh 4a22d72ef4 fixed issue #4018, fixed CanalInstanceWithManager thread-safe 2022-05-16 19:46:34 +08:00
jianghang.loujh b7eff723a3 fixed issue #4027, CanalServerWithEmbedded.get support newList 2022-05-16 19:41:46 +08:00
jianghang.loujh 9b9cfe4037 fixed issue #4152, TableMetaTSDB support canal.instance.filter.query.ddl=true 2022-05-16 17:25:43 +08:00
jianghang.loujh ee51f338e0 fixed outAdapterkey autoGen 2022-05-13 15:13:14 +08:00
zhuchao941andagapple 4315f203a2 repair issue #3401 (#3923)
* 解决OutAdapter单例导致的问题,如 1.多个es7 outAdapter的多线程问题(共享同一个es bulk request),2.多个es7 outAdapter共用一份es配置问题

* 1. 优化adapter和监听器之间的代码结构
2. 监听器只处理匹配的配置文件
3. bugfix-es adapter配置文件回调时漏写esVersion
理论上一个adapter实例对应一个config配置文件

Co-authored-by: agapple <jianghang.loujh@alibaba-inc.com>
2022-05-13 15:02:06 +08:00
jianghang.loujh b12945d4a9 format yml 2022-05-13 14:39:57 +08:00
885f05fb6a 支持定义rocketmq client日志路径,及修复一些bug (#3229)
* 修复接入阿里云RocketMq no route info for this topic 问题

* 支持deployer同步rocketmq时定义rocketmq client日志路径

* 支持deployer同步rocketmq时定义rocketmq client日志路径

* 回退guava版本号,解决adapter的java.lang.NoSuchMethodError: com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor()异常

* 解决adapter启动报com.alibaba.druid.pool.DruidDataSource cannot be cast to com.alibaba.druid.pool.DruidDataSource异常。

* 支持rocketmq client 配置日志路径

Co-authored-by: hai.yu <hai.yu@snowballtech.com>
Co-authored-by: agapple <jianghang.loujh@alibaba-inc.com>
2022-05-13 14:39:29 +08:00
ChengTie(鐵手) 591126ee6b update (#3326) 2022-05-13 14:37:17 +08:00
He Wang af432bf10a fix data truncated for column issue for type year (#3866) 2022-05-13 14:33:06 +08:00
jumao b532e90ff4 ESAdapter Support the same instance cross -library query (#4161)
* ESAdapter Support the same instance cross -library query

* fix null point ex
2022-05-13 14:31:09 +08:00
xiaoyu.zhang e48bc35f35 通过索引别名获取mapping,返回数据为真实索引,而不是别名,导致mappings.get(index).get(type)报空指针异常 (#4122) 2022-05-13 14:28:53 +08:00
He Wang 11a5f2a0fa add druid stat filter (#3985) 2022-05-13 14:24:37 +08:00
He Wangandagapple c775478a56 set backtick by DbType (#3984)
Co-authored-by: agapple <jianghang.loujh@alibaba-inc.com>
2022-05-13 14:23:40 +08:00
He Wang bd1f91cd9c fix sync order in RdbMirrorDbSyncService (#3928) 2022-05-13 14:19:40 +08:00
jianghang.loujh 67e85d201c remove client adapter Docker 2022-05-13 12:46:12 +08:00
wanglei 6965726dfe fix bug #2023 (#3871)
* fix bug #2023

* 配置canal-adapter,docker生成脚本
2022-05-13 12:39:43 +08:00
willmao c0001afe8f use hostname if register name not set (#3968) 2022-05-13 12:24:33 +08:00
xiayingfeng 977031a666 Improve MAVEN build Performance (#4055) 2022-05-13 12:23:33 +08:00
zhangshaoning bd3224eea1 Upgrade reference log4j package version (#4017) 2022-05-13 12:22:45 +08:00
jianghang.loujh 33733aef25 fixed compatible and code format 2022-05-13 11:59:57 +08:00
zhangjukai 25be0259a4 Some tweaks for pulsarmq-connector (#4060)
* 1. Add the com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer file to the META-INF/canal directory under the pulsarmq-connector project
2. Introduce pulsar-client-admin for Canal to automatically create multi-partition topics
3. Add the judgment that roleToken is null
4. The disconnect method in CanalPulsarMQConsumer removes this.pulsarMQConsumer.unsubscribe();, this code will cause data loss during stop
5. When getting Pulsar messages, they are all processed as flat messages, because CanalMessageSerializerUtil.deserializer(data) will deserialize exceptions
6. Use groupId as subscriptName, without the pulsarmq.subscriptName parameter, the entire adapter will be the same subscriber name using pulsarmq.subscriptName

* 逻辑优化

* 代码优化

* 针对pulsar的调整

* 恢复代码

* 恢复代码

* 处理Producer失效的情况
2022-05-13 11:41:50 +08:00
liteng2430 8114d77a20 Add verification: the user can set a password with a length of less than 6 digits when updating the password, but the password length must be greater than or equal to 6 digits when logging in to canal admin, so the length verification function when updating the login password is added (#4103) 2022-05-13 11:34:36 +08:00
ZhangJian He 571e7f4e25 fix ojdbc6 wrong corrinator (#4132) 2022-05-13 11:33:42 +08:00
温绍锦 ead91433d7 fastjson update 1.x to 2.0.2 (#4160) 2022-05-13 11:32:47 +08:00
xiaoma20082008 bac016f89e <feat>: support visibility columns (#3976) 2022-05-13 11:30:19 +08:00
willmao 8804647c30 avoid print password when logging (#3969) 2022-05-13 11:28:05 +08:00
mclubing 2623d08b8c rdb配置不支持yaml后缀文件 (#3964)
增加rdb配置支持yaml后缀文件
2022-05-13 11:26:04 +08:00
wuare 622fed84ce fixed logger name (#3887) 2022-05-13 11:24:00 +08:00
He Wang 9f5e8fd1c4 rollback guava version in adapter module (#3880) 2022-05-13 11:23:30 +08:00
Junwang Zhao 88d472ef1e move protocol test to protocol test directory (#3864)
1. move the protocol test to right directory
2. fix wrong string format params count

Signed-off-by: 元组 <zhaojunwang.zjw@alibaba-inc.com>
2022-05-13 11:16:06 +08:00
Aiden.Ao 2987d08161 fix issue #3144 (#3712) 2022-05-13 11:09:09 +08:00
tuple b54bea5e33 polish the code thus make it more stable (#3855)
Signed-off-by: 元组 <zhaojunwang.zjw@alibaba-inc.com>
2021-10-11 14:46:22 +08:00
jianghang.loujh c42ba95eed fixed pulsarmq package 2021-10-09 18:06:34 +08:00
jianghang.loujh b48c434ffd fixed phoenix version 2021-10-09 17:52:13 +08:00
jianghang.loujh 2c892fce9b fixed issue #3484 , fixed bat script 2021-10-09 17:39:22 +08:00
jianghang.loujh 3bb49e19d2 fixed issue #3576, stop exporter 2021-10-09 17:29:23 +08:00
jianghang.loujh f26b84ffc2 fixed issue #3829 , kafka connector support k8s env 2021-10-09 16:31:59 +08:00
364102729andagapple 0a2254eeaf add tablestore adapter (#3754)
* add tablestore adapter

* add tablestore adapter

* fix bug add add log when etl fail

* fix bug,tinyint(1)以及rowupdatechange

Co-authored-by: agapple <jianghang115@gmail.com>
2021-10-09 16:06:13 +08:00
jianghang.loujh 40d840375f fixed MR 2021-10-09 15:59:12 +08:00
Chad2liandagapple 5b6fd8094a 增加Apache Pulsar MQ支持 (#3791)
* done pulsarmq

* del pulsar consumer

* pulsar partition

* done pulsar consumer&connector

* update pulsar-client version to 2.8.1

Co-authored-by: agapple <jianghang115@gmail.com>
2021-10-09 15:51:45 +08:00
mikoto 4ce401d219 fix: 多destination的场景下只能生效一个 (#3772) 2021-10-09 15:38:26 +08:00
Jonathan SchneiderandModerne de95a6a128 refactor: Use Java standard library instead of Guava (#3708)
Co-authored-by: Moderne <team@moderne.io>

Co-authored-by: Moderne <team@moderne.io>
2021-10-09 15:33:37 +08:00
happyrita 9e11c3c018 定时保存snapshot到DB的逻辑中,schema名称获取错误,在有配置canal.instance.filter.regex时,由于对应的table无法被匹配中,导致compareTableMetaDbAndMemory方法不被执行 (#3693)
* 避免重复订阅zk变更状态

* Update DatabaseTableMeta.java

获取schema的名称

* Update CanalController.java
2021-10-09 15:32:20 +08:00
arthasking123 d67bea8f3d repair issue #3678,#3680 (#3684)
* repair issue #3678,#3680

* repair issue #3678,#3680
2021-10-09 15:30:32 +08:00
kkjinping 997957d6e0 新增配置 canal.mq.enableDynamicQueuePartition,获取topic对应的队列的数量为分区的数量,以支持动态队列的场景(如阿里云的rocketmq,无法人工设置队列数量,可能会动态伸缩) (#3670) 2021-10-09 15:26:23 +08:00
tanpenggood ea20076a32 fix: rowChange typo (#3593) 2021-10-09 15:23:36 +08:00
dinosaur 2550d8eb65 Connection is already closed fix (#3587) 2021-10-09 14:54:05 +08:00
nick 31a0fdc807 add note (#3550) 2021-10-09 14:53:28 +08:00
lanicc d79431c7e5 fix typo (#3532) 2021-10-09 14:53:00 +08:00
ruanjl 911711248c 修复rds高可用主从切换和oss相关问题 (#3480)
* 修复rds oos 拉取的binlog 的消费逻辑以及本地消费到直连消费的逻辑衔接

* 去除无用代码
2021-10-09 14:51:41 +08:00
sparkandlihuav2017 63407dc570 canal adapter phoenix (#3290)
* canal adapter phoenix

* canal adapter phoenix

* canal adapter phoenix

* canal adapter phoenix

* canal adapter phoenix

* canal adapter phoenix

* canal adapter phoenix

Co-authored-by: lihuav2017 <lihua@able-elec.com>
2021-10-09 14:37:11 +08:00
flowell 3c20fd09ad fix issue #3839: 修复alter table character set类型DDL语句解析BUG (#3840) 2021-10-09 14:28:56 +08:00
ChenZhanggandChen Zhang eef3bd92ba Improve MAVEN build Performance (#3851)
Co-authored-by: Chen Zhang <340355960@qq.com>
2021-10-09 14:26:26 +08:00
agapple 474063af3b fixed issue #3494 , polardb-x 2.0 cdc support implicit_id/varaibles 2021-04-28 13:42:55 +08:00
agapple 5cc85ff2de [maven-release-plugin] prepare for next development iteration 2021-04-19 15:48:20 +08:00
197 changed files with 8183 additions and 1183 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ cache:
install: true
script:
- travis_wait 30 ./mvnw clean install -DskipTests=false -Dcheckstyle.skip=true -Dmaven.javadoc.skip=true
- travis_wait 30 ./mvnw -T 1C clean install -DskipTests=false -Dcheckstyle.skip=true -Dmaven.javadoc.skip=true
after_success:
- bash <(curl -s https://codecov.io/bash)
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.admin</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.admin</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>canal.admin-web</artifactId>
+17 -5
View File
@@ -56,14 +56,26 @@ in
exit;;
esac
JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'`
str=`file -L $JAVA | grep 64-bit`
if [ -n "$str" ]; then
JAVA_OPTS="-server -Xms2048m -Xmx3072m"
JAVA_OPTS="$JAVA_OPTS -Xss256k -XX:+AggressiveOpts -XX:-UseBiasedLocking -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$base/logs"
if [ $JavaVersion -ge 11 ] ; then
#JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$base_log/gc.log:time "
JAVA_OPTS="$JAVA_OPTS"
else
JAVA_OPTS="-server -Xms1024m -Xmx1024m"
#JAVA_OPTS="$JAVA_OPTS -Xloggc:$base/logs/canal/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime"
JAVA_OPTS="$JAVA_OPTS -XX:+UseFastAccessorMethods -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution"
fi
if [ -n "$str" ]; then
# JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC $JAVA_OPTS"
# For G1
JAVA_OPTS="-server -Xms2g -Xmx3g -XX:+UseG1GC -XX:MaxGCPauseMillis=250 -XX:+UseGCOverheadLimit -XX:+ExplicitGCInvokesConcurrent $JAVA_OPTS"
else
JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m $JAVA_OPTS"
fi
JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=250 -XX:+UseGCOverheadLimit -XX:+ExplicitGCInvokesConcurrent -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution"
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"
CANAL_OPTS="-DappName=canal-admin"
@@ -81,4 +93,4 @@ $JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba
echo $! > $base/bin/admin.pid
echo "cd to $current_path for continue"
cd $current_path
cd $current_path
@@ -2,6 +2,7 @@ package com.alibaba.otter.canal.admin.connector;
import java.util.function.Function;
import org.assertj.core.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
@@ -26,7 +27,8 @@ public class SimpleAdminConnectors {
connector.connect();
return function.apply(connector);
} catch (Exception e) {
logger.error("connect to ip:{},port:{},user:{},password:{}, failed", ip, port, user, passwd);
logger.error("connect to ip:{},port:{},user:{},password:{}, failed",
ip, port, user, getDesensitizationPassword(passwd));
logger.error(e.getMessage());
} finally {
connector.disconnect();
@@ -34,4 +36,14 @@ public class SimpleAdminConnectors {
return null;
}
private static String getDesensitizationPassword(String password) {
String defaultPassword = "******";
if (Strings.isNullOrEmpty(password) || password.length() < 6) {
return defaultPassword;
} else {
return String.format("%s******", password.substring(0, 3));
}
}
}
@@ -13,8 +13,8 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.CanalCluster;
import com.alibaba.otter.canal.admin.model.NodeServer;
@@ -21,6 +21,8 @@ public class UserServiceImpl implements UserService {
private static byte[] seeds = "canal is best!".getBytes();
private static final Integer PASSWORD_LENGTH = 6;
public User find4Login(String username, String password) {
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
return null;
@@ -43,6 +45,9 @@ public class UserServiceImpl implements UserService {
}
public void update(User user) {
if (user.getPassword().length() < PASSWORD_LENGTH) {
throw new ServiceException("The new password is too short,must more than 6 digits");
}
User userTmp = User.find.query().where().eq("username", user.getUsername()).findOne();
if (userTmp == null) {
throw new ServiceException();
+17 -1
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.admin</artifactId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
<packaging>pom</packaging>
<name>canal admin module for otter ${project.version}</name>
<modules>
@@ -18,6 +18,7 @@
<java_source_version>1.8</java_source_version>
<java_target_version>1.8</java_target_version>
<file_encoding>UTF-8</file_encoding>
<log4j_version>2.17.0</log4j_version>
</properties>
<dependencyManagement>
@@ -91,6 +92,21 @@
<artifactId>ebean</artifactId>
<version>11.41.1</version>
</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>
</dependencyManagement>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -54,6 +54,8 @@ public class CanalClientConfig {
// canal adapters 配置
private List<CanalAdapter> canalAdapters;
private Boolean terminateOnException = false;
public String getCanalServerHost() {
return canalServerHost;
}
@@ -222,6 +224,14 @@ public class CanalClientConfig {
this.namespace = namespace;
}
public Boolean getTerminateOnException() {
return terminateOnException;
}
public void setTerminateOnException(Boolean terminateOnException) {
this.terminateOnException = terminateOnException;
}
public static class CanalAdapter {
private String instance; // 实例名
@@ -0,0 +1,28 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by @author zhuchao on @date 2021/11/11.
*/
public class FileName2KeyMapping {
private static Map<String, String> MAP = new ConcurrentHashMap<>();
public static void register(String type, String fileName, String key) {
MAP.putIfAbsent(join(type, fileName), key);
}
public static void unregister(String type, String fileName) {
MAP.remove(join(type, fileName));
}
public static String getKey(String type, String fileName) {
return MAP.get(join(type, fileName));
}
private static String join(String type, String fileName) {
return type + "|" + fileName;
}
}
@@ -55,7 +55,7 @@ public class MappingConfigsLoader {
}
if (filePath.exists()) {
String fileName = filePath.getName();
if (!fileName.endsWith(".yml")) {
if (!(fileName.endsWith(".yml") || fileName.endsWith(".yaml"))) {
return null;
}
try (InputStream in = new FileInputStream(filePath)) {
@@ -21,7 +21,9 @@ public class URLClassExtensionLoader extends URLClassLoader {
if (name.startsWith("java.") || name.startsWith("org.slf4j.") || name.startsWith("org.apache.logging")
|| name.startsWith("org.apache.zookeeper.") || name.startsWith("org.I0Itec.zkclient.")
|| name.startsWith("org.apache.commons.logging.")) {
|| name.startsWith("org.apache.commons.logging.")
|| name.startsWith("com.alibaba.druid")
) {
// || name.startsWith("org.apache.hadoop."))
// {
c = super.loadClass(name);
@@ -30,6 +30,8 @@ public class Util {
private static final Logger logger = LoggerFactory.getLogger(Util.class);
public static final String AUTO_GENERATED_PREFIX = "AUTO_GENERATED_";
/**
* 通过DS执行sql
*/
+5 -5
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -27,22 +27,22 @@
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.4.3</version>
<version>6.8.22</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>6.4.3</version>
<version>6.8.22</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>6.4.3</version>
<version>6.8.22</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.4.3</version>
<version>6.8.22</version>
</dependency>
<dependency>
@@ -316,13 +316,17 @@ public class ES6xTemplate implements ESTemplate {
}
@Override
public Object getESDataFromDmlData(ESSyncConfig.ESMapping mapping, Map<String, Object> dmlData,
public Object getESDataFromDmlData(ESSyncConfig.ESMapping mapping,String owner, Map<String, Object> dmlData,
Map<String, Object> dmlOld, Map<String, Object> esFieldData) {
SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
String columnName = fieldItem.getColumnItems().iterator().next().getColumnName();
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);
@@ -150,7 +150,13 @@ public class ESConnection {
logger.error(e.getMessage(), e);
return null;
}
mappingMetaData = mappings.get(index).get(type);
//通过别名查询mapping返回的是真实索引名称,mappings.get(index)返回null,为兼容别名情况修改如下:
ImmutableOpenMap<String, MappingMetaData> esIndex = mappings.get(index);
if(esIndex == null){
esIndex = mappings.valuesIt().next();
}
mappingMetaData = esIndex.get(type);
}
return mappingMetaData;
}
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -287,8 +287,7 @@ public class ES7xTemplate implements ESTemplate {
}
@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();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
Object resultIdVal = null;
@@ -312,13 +311,17 @@ public class ES7xTemplate implements ESTemplate {
}
@Override
public Object getESDataFromDmlData(ESMapping mapping, 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) {
SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
String columnName = fieldItem.getColumnItems().iterator().next().getColumnName();
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);
+2 -1
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -22,6 +22,7 @@
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
@@ -18,10 +18,7 @@ import com.alibaba.otter.canal.client.adapter.es.core.config.SqlParser;
import com.alibaba.otter.canal.client.adapter.es.core.monitor.ESConfigMonitor;
import com.alibaba.otter.canal.client.adapter.es.core.service.ESSyncService;
import com.alibaba.otter.canal.client.adapter.es.core.support.ESTemplate;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
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 com.alibaba.otter.canal.client.adapter.support.*;
/**
* ES外部适配器
@@ -42,6 +39,8 @@ public abstract class ESAdapter implements OuterAdapter {
protected Properties envProperties;
protected OuterAdapterConfig configuration;
public ESSyncService getEsSyncService() {
return esSyncService;
}
@@ -58,23 +57,13 @@ public abstract class ESAdapter implements OuterAdapter {
public void init(OuterAdapterConfig configuration, Properties envProperties) {
try {
this.envProperties = envProperties;
this.configuration = configuration;
Map<String, ESSyncConfig> esSyncConfigTmp = ESSyncConfigLoader.load(envProperties);
// 过滤不匹配的key的配置
esSyncConfigTmp.forEach((key, config) -> {
if ((config.getOuterAdapterKey() == null && configuration.getKey() == null)
|| (config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey()))) {
esSyncConfig.put(key, config);
}
addConfig(key, config);
});
for (Map.Entry<String, ESSyncConfig> entry : esSyncConfig.entrySet()) {
String configName = entry.getKey();
ESSyncConfig config = entry.getValue();
addSyncConfigToCache(configName, config);
}
esSyncService = new ESSyncService(esTemplate);
esConfigMonitor = new ESConfigMonitor();
@@ -103,12 +92,12 @@ public abstract class ESAdapter implements OuterAdapter {
String table = dml.getTable();
Map<String, ESSyncConfig> configMap;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
configMap = dbTableEsSyncConfig.get(StringUtils.trimToEmpty(dml.getDestination()) + "-"
+ StringUtils.trimToEmpty(dml.getGroupId()) + "_" + database + "-"
+ table);
configMap = dbTableEsSyncConfig
.get(StringUtils.trimToEmpty(dml.getDestination()) + "-" + StringUtils.trimToEmpty(dml.getGroupId())
+ "_" + database + "-" + table);
} else {
configMap = dbTableEsSyncConfig.get(StringUtils.trimToEmpty(dml.getDestination()) + "_" + database + "-"
+ table);
configMap = dbTableEsSyncConfig
.get(StringUtils.trimToEmpty(dml.getDestination()) + "_" + database + "-" + table);
}
if (configMap != null && !configMap.values().isEmpty()) {
@@ -138,7 +127,7 @@ public abstract class ESAdapter implements OuterAdapter {
return null;
}
public void addSyncConfigToCache(String configName, ESSyncConfig config) {
private void addSyncConfigToCache(String configName, ESSyncConfig config) {
Properties envProperties = this.envProperties;
SchemaItem schemaItem = SqlParser.parse(config.getEsMapping().getSql());
config.getEsMapping().setSchemaItem(schemaItem);
@@ -153,30 +142,60 @@ public abstract class ESAdapter implements OuterAdapter {
throw new RuntimeException("Not found the schema of jdbc-url: " + config.getDataSourceKey());
}
String schema = matcher.group(2);
schemaItem.getAliasTableItems()
.values()
.forEach(tableItem -> {
Map<String, ESSyncConfig> esSyncConfigMap;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
esSyncConfigMap = dbTableEsSyncConfig.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination())
+ "-"
+ StringUtils.trimToEmpty(config.getGroupId())
+ "_"
+ schema
+ "-"
+ tableItem.getTableName(),
schemaItem.getAliasTableItems().values().forEach(tableItem -> {
Map<String, ESSyncConfig> esSyncConfigMap;
String schemaKey = tableItem.getSchema() == null ? schema : tableItem.getSchema();
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
esSyncConfigMap = dbTableEsSyncConfig
.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "-"
+ StringUtils.trimToEmpty(config.getGroupId()) + "_" + schemaKey + "-"
+ tableItem.getTableName(),
k -> new ConcurrentHashMap<>());
} else {
esSyncConfigMap = dbTableEsSyncConfig.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination())
+ "_"
+ schema
+ "-"
+ tableItem.getTableName(),
k -> new ConcurrentHashMap<>());
}
} else {
esSyncConfigMap = dbTableEsSyncConfig.computeIfAbsent(
StringUtils.trimToEmpty(config.getDestination()) + "_" + schemaKey + "-" + tableItem.getTableName(),
k -> new ConcurrentHashMap<>());
}
esSyncConfigMap.put(configName, config);
});
esSyncConfigMap.put(configName, config);
});
}
public boolean addConfig(String fileName, ESSyncConfig config) {
if (match(config)) {
esSyncConfig.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, ESSyncConfig config) {
if (config.getOuterAdapterKey() != null && !config.getOuterAdapterKey().equals(configuration.getKey())) {
// 理论上不允许改这个 因为本身就是通过这个关联起Adapter和Config的
throw new RuntimeException("not allow to change outAdapterKey");
}
esSyncConfig.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
esSyncConfig.remove(fileName);
for (Map<String, ESSyncConfig> configMap : dbTableEsSyncConfig.values()) {
if (configMap != null) {
configMap.remove(fileName);
}
}
FileName2KeyMapping.unregister(getClass().getAnnotation(SPI.class).value(), fileName);
}
private boolean match(ESSyncConfig 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;
}
}
@@ -1,21 +1,18 @@
package com.alibaba.otter.canal.client.adapter.es.core.monitor;
import java.io.File;
import java.util.Map;
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.config.YmlConfigBinder;
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.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util;
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;
public class ESConfigMonitor {
@@ -36,7 +33,7 @@ public class ESConfigMonitor {
File confDir = Util.getConfDirPath(adapterName);
try {
FileAlterationObserver observer = new FileAlterationObserver(confDir,
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileListener listener = new FileListener();
observer.addListener(listener);
fileMonitor = new FileAlterationMonitor(3000, observer);
@@ -69,9 +66,14 @@ public class ESConfigMonitor {
null,
envProperties);
if (config != null) {
// 这里要记得设置esVersion bugfix
config.setEsVersion(adapterName);
config.validate();
addConfigToCache(file, config);
logger.info("Add a new es mapping config: {} to canal adapter", file.getName());
boolean result = esAdapter.addConfig(file.getName(), config);
if (result) {
logger.info("Add a new es mapping config: {} to canal adapter",
file.getName());
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
@@ -99,12 +101,10 @@ public class ESConfigMonitor {
if (config == null) {
return;
}
// 这里要记得设置esVersion bugfix
config.setEsVersion(adapterName);
config.validate();
if (esAdapter.getEsSyncConfig().containsKey(file.getName())) {
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
esAdapter.updateConfig(file.getName(), config);
logger.info("Change a es mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
@@ -118,29 +118,12 @@ public class ESConfigMonitor {
try {
if (esAdapter.getEsSyncConfig().containsKey(file.getName())) {
deleteConfigFromCache(file);
esAdapter.deleteConfig(file.getName());
logger.info("Delete a es mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
private void addConfigToCache(File file, ESSyncConfig config) {
esAdapter.getEsSyncConfig().put(file.getName(), config);
esAdapter.addSyncConfigToCache(file.getName(), config);
}
private void deleteConfigFromCache(File file) {
esAdapter.getEsSyncConfig().remove(file.getName());
for (Map<String, ESSyncConfig> configMap : esAdapter.getDbTableEsSyncConfig().values()) {
if (configMap != null) {
configMap.remove(file.getName());
}
}
}
}
}
@@ -1,19 +1,15 @@
package com.alibaba.otter.canal.client.adapter.es.core.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;
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;
@@ -79,7 +75,7 @@ public class ESSyncService {
esSyncConfigs
.forEach(esSyncConfig -> configIndexes.append(esSyncConfig.getEsMapping().get_index()).append(" "));
logger.debug("DML: {} \nAffected indexes: {}",
JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue),
JSON.toJSONString(dml, JSONWriter.Feature.WriteNulls),
configIndexes.toString());
}
}
@@ -209,7 +205,7 @@ public class ESSyncService {
if (schemaItem.getAliasTableItems().size() == 1 && schemaItem.isAllFieldsSimple()) {
// ------单表 & 所有字段都为简单字段------
singleTableSimpleFiledUpdate(config, dml, data, old);
singleTableSimpleFiledUpdate(config, schemaItem.getMainTable().getAlias(), dml, data, old);
} else {
// ------主表 查询sql来更新------
if (schemaItem.getMainTable().getTableName().equalsIgnoreCase(dml.getTable())) {
@@ -263,7 +259,7 @@ public class ESSyncService {
// 判断主键和所更新的字段是否全为简单字段
if (idFieldSimple && allUpdateFieldSimple && !fkChanged) {
singleTableSimpleFiledUpdate(config, dml, data, old);
singleTableSimpleFiledUpdate(config, schemaItem.getMainTable().getAlias(), dml, data, old);
} else {
mainTableUpdate(config, dml, data, old);
}
@@ -810,12 +806,12 @@ public class ESSyncService {
* @param data 单行data数据
* @param old 单行old数据
*/
private void singleTableSimpleFiledUpdate(ESSyncConfig config, Dml dml, Map<String, Object> data,
private void singleTableSimpleFiledUpdate(ESSyncConfig config, String owner, Dml dml, Map<String, Object> data,
Map<String, Object> old) {
ESMapping mapping = config.getEsMapping();
Map<String, Object> esFieldData = new LinkedHashMap<>();
Object idVal = esTemplate.getESDataFromDmlData(mapping, data, old, esFieldData);
Object idVal = esTemplate.getESDataFromDmlData(mapping, owner, data, old, esFieldData);
if (logger.isTraceEnabled()) {
logger.trace("Main table update to es index, destination:{}, table: {}, index: {}, id: {}",
@@ -18,7 +18,7 @@ import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSON;
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;
@@ -57,13 +57,11 @@ public interface ESTemplate {
Object getIdValFromRS(ESMapping mapping, ResultSet resultSet) throws SQLException;
Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld,
Map<String, Object> esFieldData) throws SQLException;
Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld, Map<String, Object> esFieldData) throws SQLException;
Object getValFromData(ESMapping mapping, Map<String, Object> dmlData, String fieldName, String columnName);
Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> esFieldData);
Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> dmlOld,
Map<String, Object> esFieldData);
Object getESDataFromDmlData(ESMapping mapping,String owner, Map<String, Object> dmlData, Map<String, Object> dmlOld, Map<String, Object> esFieldData);
}
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -1,9 +1,25 @@
package com.alibaba.otter.canal.client.adapter.hbase;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfigLoader;
import com.alibaba.otter.canal.client.adapter.hbase.monitor.HbaseConfigMonitor;
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseEtlService;
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService;
import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate;
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.FileName2KeyMapping;
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;
import java.io.IOException;
import java.util.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
@@ -16,18 +32,6 @@ import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfigLoader;
import com.alibaba.otter.canal.client.adapter.hbase.monitor.HbaseConfigMonitor;
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseEtlService;
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService;
import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate;
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 com.alibaba.otter.canal.client.adapter.support.SPI;
/**
* HBase外部适配器
*
@@ -49,6 +53,8 @@ public class HbaseAdapter implements OuterAdapter {
private Properties envProperties;
private OuterAdapterConfig configuration;
public Map<String, MappingConfig> getHbaseMapping() {
return hbaseMapping;
}
@@ -61,33 +67,12 @@ public class HbaseAdapter implements OuterAdapter {
public void init(OuterAdapterConfig configuration, Properties envProperties) {
try {
this.envProperties = envProperties;
this.configuration = configuration;
Map<String, MappingConfig> hbaseMappingTmp = MappingConfigLoader.load(envProperties);
// 过滤不匹配的key的配置
hbaseMappingTmp.forEach((key, mappingConfig) -> {
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|| (mappingConfig.getOuterAdapterKey() != null
&& mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) {
hbaseMapping.put(key, mappingConfig);
}
hbaseMappingTmp.forEach((key, config) -> {
addConfig(key, config);
});
for (Map.Entry<String, MappingConfig> entry : hbaseMapping.entrySet()) {
String configName = entry.getKey();
MappingConfig mappingConfig = entry.getValue();
String k;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getHbaseMapping().getDatabase() + "-"
+ mappingConfig.getHbaseMapping().getTable();
} else {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getHbaseMapping().getDatabase() + "-"
+ mappingConfig.getHbaseMapping().getTable();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(k,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
Map<String, String> properties = configuration.getProperties();
@@ -223,4 +208,62 @@ public class HbaseAdapter implements OuterAdapter {
}
return null;
}
private void addSyncConfigToCache(String configName, MappingConfig mappingConfig) {
String k;
if (envProperties != null && !"tcp"
.equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-" + StringUtils
.trimToEmpty(mappingConfig.getGroupId()) + "_" + mappingConfig.getHbaseMapping()
.getDatabase() + "-" + mappingConfig.getHbaseMapping().getTable();
} else {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_" + mappingConfig
.getHbaseMapping().getDatabase() + "-" + mappingConfig.getHbaseMapping()
.getTable();
}
Map<String, MappingConfig> configMap = mappingConfigCache
.computeIfAbsent(k, k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
public boolean addConfig(String fileName, MappingConfig config) {
if (match(config)) {
hbaseMapping.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");
}
hbaseMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
hbaseMapping.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;
}
}
@@ -5,20 +5,15 @@ import com.alibaba.otter.canal.client.adapter.hbase.HbaseAdapter;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util;
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.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class HbaseConfigMonitor {
private static final Logger logger = LoggerFactory.getLogger(HbaseConfigMonitor.class);
@@ -37,7 +32,7 @@ public class HbaseConfigMonitor {
File confDir = Util.getConfDirPath(adapterName);
try {
FileAlterationObserver observer = new FileAlterationObserver(confDir,
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileListener listener = new FileListener();
observer.addListener(listener);
fileMonitor = new FileAlterationMonitor(3000, observer);
@@ -70,9 +65,11 @@ public class HbaseConfigMonitor {
return;
}
config.validate();
addConfigToCache(file, config);
logger.info("Add a new hbase mapping config: {} to canal adapter", file.getName());
boolean result = hbaseAdapter.addConfig(file.getName(), config);
if (result) {
logger.info("Add a new hbase mapping config: {} to canal adapter",
file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
@@ -97,10 +94,7 @@ public class HbaseConfigMonitor {
return;
}
config.validate();
if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
hbaseAdapter.updateConfig(file.getName(), config);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
@@ -113,33 +107,12 @@ public class HbaseConfigMonitor {
try {
if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
hbaseAdapter.deleteConfig(file.getName());
logger.info("Delete a hbase mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
private void addConfigToCache(File file, MappingConfig config) {
hbaseAdapter.getHbaseMapping().put(file.getName(), config);
Map<String, MappingConfig> configMap = hbaseAdapter.getMappingConfigCache()
.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "_"
+ config.getHbaseMapping().getDatabase() + "-" + config.getHbaseMapping().getTable(),
k1 -> new HashMap<>());
configMap.put(file.getName(), config);
}
private void deleteConfigFromCache(File file) {
hbaseAdapter.getHbaseMapping().remove(file.getName());
for (Map<String, MappingConfig> configMap : hbaseAdapter.getMappingConfigCache().values()) {
if (configMap != null) {
configMap.remove(file.getName());
}
}
}
}
}
@@ -6,8 +6,8 @@ import org.apache.hadoop.hbase.util.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.hbase.support.*;
import com.alibaba.otter.canal.client.adapter.support.Dml;
@@ -39,7 +39,7 @@ public class HbaseSyncService {
delete(config, dml);
}
if (logger.isDebugEnabled()) {
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -1,16 +1,5 @@
package com.alibaba.otter.canal.client.adapter.kudu;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfig;
import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfigLoader;
@@ -20,8 +9,19 @@ import com.alibaba.otter.canal.client.adapter.kudu.service.KuduSyncService;
import com.alibaba.otter.canal.client.adapter.kudu.support.KuduTemplate;
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.FileName2KeyMapping;
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;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author liuyadong
@@ -35,8 +35,6 @@ public class KuduAdapter implements OuterAdapter {
private Map<String, KuduMappingConfig> kuduMapping = new ConcurrentHashMap<>(); // 文件名对应配置
private Map<String, Map<String, KuduMappingConfig>> mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置
private String dataSourceKey;
private KuduTemplate kuduTemplate;
private KuduSyncService kuduSyncService;
@@ -45,6 +43,8 @@ public class KuduAdapter implements OuterAdapter {
private Properties envProperties;
private OuterAdapterConfig configuration;
public Map<String, KuduMappingConfig> getKuduMapping() {
return kuduMapping;
}
@@ -56,36 +56,17 @@ public class KuduAdapter implements OuterAdapter {
@Override
public void init(OuterAdapterConfig configuration, Properties envProperties) {
this.envProperties = envProperties;
this.configuration = configuration;
Map<String, KuduMappingConfig> kuduMappingTmp = KuduMappingConfigLoader.load(envProperties);
// 过滤不匹配的key的配置,获取连接key,key为配置文件名称
kuduMappingTmp.forEach((key, mappingConfig) -> {
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|| (mappingConfig.getOuterAdapterKey() != null && mappingConfig.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey()))) {
kuduMapping.put(key, mappingConfig);
dataSourceKey = mappingConfig.getDataSourceKey();
}
kuduMappingTmp.forEach((key, config) -> {
addConfig(key, config);
});
// 判断目标字段是否为空
if (kuduMapping.isEmpty()) {
throw new RuntimeException("No kudu adapter found for config key: " + configuration.getKey());
}
for (Map.Entry<String, KuduMappingConfig> entry : kuduMapping.entrySet()) {
String configName = entry.getKey();
KuduMappingConfig mappingConfig = entry.getValue();
String k;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getKuduMapping().getDatabase() + "-" + mappingConfig.getKuduMapping().getTable();
} else {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getKuduMapping().getDatabase() + "-" + mappingConfig.getKuduMapping().getTable();
}
Map<String, KuduMappingConfig> configMap = mappingConfigCache.computeIfAbsent(k,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
Map<String, String> properties = configuration.getProperties();
String kudu_master = properties.get("kudu.master.address");
@@ -203,4 +184,60 @@ public class KuduAdapter implements OuterAdapter {
}
return null;
}
private void addSyncConfigToCache(String configName, KuduMappingConfig mappingConfig) {
String k;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getKuduMapping().getDatabase() + "-" + mappingConfig.getKuduMapping().getTable();
} else {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getKuduMapping().getDatabase() + "-" + mappingConfig.getKuduMapping().getTable();
}
Map<String, KuduMappingConfig> configMap = mappingConfigCache.computeIfAbsent(k,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
public boolean addConfig(String fileName, KuduMappingConfig config) {
if (match(config)) {
kuduMapping.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, KuduMappingConfig config) {
if (config.getOuterAdapterKey() != null && !config.getOuterAdapterKey()
.equals(configuration.getKey())) {
// 理论上不允许改这个 因为本身就是通过这个关联起Adapter和Config的
throw new RuntimeException("not allow to change outAdapterKey");
}
kuduMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
kuduMapping.remove(fileName);
for (Map<String, KuduMappingConfig> configMap : mappingConfigCache.values()) {
if (configMap != null) {
configMap.remove(fileName);
}
}
FileName2KeyMapping.unregister(getClass().getAnnotation(SPI.class).value(), fileName);
}
private boolean match(KuduMappingConfig 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;
}
}
@@ -1,23 +1,18 @@
package com.alibaba.otter.canal.client.adapter.kudu.monitor;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
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.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.kudu.KuduAdapter;
import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfig;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util;
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;
/**
* @author liuyadong
@@ -41,7 +36,7 @@ public class KuduConfigMonitor {
File confDir = Util.getConfDirPath(adapterName);
try {
FileAlterationObserver observer = new FileAlterationObserver(confDir,
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileListener listener = new FileListener();
observer.addListener(listener);
fileMonitor = new FileAlterationMonitor(3000, observer);
@@ -83,9 +78,11 @@ public class KuduConfigMonitor {
return;
}
config.validate();
addConfigToCache(file, config);
logger.info("Add a new kudu mapping config: {} to canal adapter", file.getName());
boolean result = kuduAdapter.addConfig(file.getName(), config);
if (result) {
logger.info("Add a new kudu mapping config: {} to canal adapter",
file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
@@ -113,10 +110,7 @@ public class KuduConfigMonitor {
return;
}
config.validate();
if (kuduAdapter.getKuduMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
kuduAdapter.updateConfig(file.getName(), config);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
@@ -129,42 +123,12 @@ public class KuduConfigMonitor {
try {
if (kuduAdapter.getKuduMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
kuduAdapter.deleteConfig(file.getName());
logger.info("Delete a hbase mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
/**
* 添加配置文件信息到缓存
*
* @param file
* @param config
*/
private void addConfigToCache(File file, KuduMappingConfig config) {
kuduAdapter.getKuduMapping().put(file.getName(), config);
Map<String, KuduMappingConfig> configMap = kuduAdapter.getMappingConfigCache()
.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "."
+ config.getKuduMapping().getDatabase() + "." + config.getKuduMapping().getTable(),
k1 -> new HashMap<>());
configMap.put(file.getName(), config);
}
/**
* 从缓存中删除配置
*
* @param file 文件
*/
private void deleteConfigFromCache(File file) {
kuduAdapter.getKuduMapping().remove(file.getName());
for (Map<String, KuduMappingConfig> configMap : kuduAdapter.getMappingConfigCache().values()) {
if (configMap != null) {
configMap.remove(file.getName());
}
}
}
}
}
@@ -10,8 +10,8 @@ import org.apache.kudu.client.KuduException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfig;
import com.alibaba.otter.canal.client.adapter.kudu.support.KuduTemplate;
import com.alibaba.otter.canal.client.adapter.support.Dml;
@@ -55,7 +55,7 @@ public class KuduSyncService {
delete(config, dml);
}
if (logger.isDebugEnabled()) {
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
}
}
@@ -118,7 +118,7 @@ public class KuduSyncService {
}
} catch (KuduException e) {
logger.error(e.getMessage());
logger.error("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.error("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
}
}
@@ -159,7 +159,7 @@ public class KuduSyncService {
}
} catch (KuduException e) {
logger.error(e.getMessage());
logger.error("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.error("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
}
@@ -201,7 +201,7 @@ public class KuduSyncService {
}
} catch (KuduException e) {
logger.error(e.getMessage());
logger.error("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.error("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
}
}
+28 -2
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -61,7 +61,7 @@
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc6</artifactId>
</dependency>
<dependency>
@@ -141,6 +141,19 @@
<classifier>jar-with-dependencies</classifier>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.tablestore</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<artifactId>*</artifactId>
<groupId>*</groupId>
</exclusion>
</exclusions>
<classifier>jar-with-dependencies</classifier>
<scope>provided</scope>
</dependency>
<!-- connector plugin -->
<dependency>
<groupId>com.alibaba.otter</groupId>
@@ -194,6 +207,19 @@
<classifier>jar-with-dependencies</classifier>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>connector.pulsarmq</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<artifactId>*</artifactId>
<groupId>*</groupId>
</exclusion>
</exclusions>
<classifier>jar-with-dependencies</classifier>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
@@ -64,6 +64,13 @@
<exclude>META-INF/**</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>../tablestore/src/main/resources/</directory>
<outputDirectory>/conf</outputDirectory>
<excludes>
<exclude>META-INF/**</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>target</directory>
<outputDirectory>logs</outputDirectory>
@@ -65,6 +65,13 @@
<exclude>META-INF/**</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>../tablestore/src/main/resources/</directory>
<outputDirectory>/conf</outputDirectory>
<excludes>
<exclude>META-INF/**</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>target</directory>
<outputDirectory>logs</outputDirectory>
@@ -56,11 +56,24 @@ in
exit;;
esac
JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'`
str=`file -L $JAVA | grep 64-bit`
if [ -n "$str" ]; then
JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -Xss256k -XX:-UseAdaptiveSizePolicy -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError"
JAVA_OPTS="$JAVA_OPTS -Xss256k -XX:+AggressiveOpts -XX:-UseBiasedLocking -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$base/logs"
if [ $JavaVersion -ge 11 ] ; then
#JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$base_log/gc.log:time "
JAVA_OPTS="$JAVA_OPTS"
else
JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m "
#JAVA_OPTS="$JAVA_OPTS -Xloggc:$base/logs/canal/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime"
JAVA_OPTS="$JAVA_OPTS -XX:+UseFastAccessorMethods -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution"
fi
if [ -n "$str" ]; then
# JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC $JAVA_OPTS"
# For G1
JAVA_OPTS="-server -Xms2g -Xmx3g -XX:+UseG1GC -XX:MaxGCPauseMillis=250 -XX:+UseGCOverheadLimit -XX:+ExplicitGCInvokesConcurrent $JAVA_OPTS"
else
JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m $JAVA_OPTS"
fi
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"
@@ -12,8 +12,10 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
*/
@SpringBootApplication
public class CanalAdapterApplication {
public static void main(String[] args) {
// 支持rocketmq client 配置日志路径
System.setProperty("rocketmq.client.logUseSlf4j","true");
SpringApplication application = new SpringApplication(CanalAdapterApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
@@ -0,0 +1,23 @@
package com.alibaba.otter.canal.adapter.launcher.config;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.support.http.StatViewServlet;
@Configuration
public class DruidConfig {
@Bean
public ServletRegistrationBean<StatViewServlet> statViewServlet(){
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>( new StatViewServlet(),"/druid/*");
Map<String,String> initParams = new HashMap<>();
initParams.put("allow","");
bean.setInitParameters(initParams);
return bean;
}
}
@@ -7,6 +7,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -63,7 +64,7 @@ public class AdapterProcessor {
// load connector consumer
ExtensionLoader<CanalMsgConsumer> loader = new ExtensionLoader<>(CanalMsgConsumer.class);
canalMsgConsumer = loader
.getExtension(canalClientConfig.getMode().toLowerCase(), CONNECTOR_SPI_DIR, CONNECTOR_STANDBY_SPI_DIR);
.getExtension(canalClientConfig.getMode().toLowerCase(),destination ,CONNECTOR_SPI_DIR, CONNECTOR_STANDBY_SPI_DIR);
Properties properties = canalClientConfig.getConsumerProperties();
properties.put(CanalConstants.CANAL_MQ_FLAT_MESSAGE, canalClientConfig.getFlatMessage());
@@ -217,8 +218,15 @@ public class AdapterProcessor {
canalMsgConsumer.rollback(); // 处理失败, 回滚数据
logger.error(e.getMessage() + " Error sync and rollback, execute times: " + (i + 1));
} else {
canalMsgConsumer.ack();
logger.error(e.getMessage() + " Error sync but ACK!");
if (canalClientConfig.getTerminateOnException()) {
canalMsgConsumer.rollback();
logger.error("Retry fail, turn switch off and abort data transfer.");
syncSwitch.off(canalDestination);
logger.error("finish turn off switch of destination:" + canalDestination);
} else {
canalMsgConsumer.ack();
logger.error(e.getMessage() + " Error sync but ACK!");
}
}
Thread.sleep(500);
}
@@ -22,6 +22,7 @@ import com.alibaba.otter.canal.client.adapter.OuterAdapter;
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.OuterAdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.Util;
/**
* 外部适配器的加载器
@@ -50,158 +51,43 @@ public class CanalAdapterLoader {
for (CanalClientConfig.CanalAdapter canalAdapter : canalClientConfig.getCanalAdapters()) {
for (CanalClientConfig.Group group : canalAdapter.getGroups()) {
int autoGenId = 0;
List<List<OuterAdapter>> canalOuterAdapterGroups = new CopyOnWriteArrayList<>();
List<OuterAdapter> canalOuterAdapters = new CopyOnWriteArrayList<>();
for (OuterAdapterConfig config : group.getOuterAdapters()) {
// 保证一定有key
if (StringUtils.isEmpty(config.getKey())) {
String key = StringUtils.join(
new String[] { Util.AUTO_GENERATED_PREFIX, canalAdapter.getInstance(), group.getGroupId(),
String.valueOf(autoGenId) },
'-');
//gen keyId
config.setKey(key);
}
autoGenId++;
loadAdapter(config, canalOuterAdapters);
}
canalOuterAdapterGroups.add(canalOuterAdapters);
AdapterProcessor adapterProcessor = canalAdapterProcessors.computeIfAbsent(canalAdapter.getInstance()
+ "|"
+ StringUtils.trimToEmpty(group.getGroupId()),
AdapterProcessor adapterProcessor = canalAdapterProcessors.computeIfAbsent(
canalAdapter.getInstance() + "|" + StringUtils.trimToEmpty(group.getGroupId()),
f -> new AdapterProcessor(canalClientConfig,
canalAdapter.getInstance(),
group.getGroupId(),
canalOuterAdapterGroups));
adapterProcessor.start();
logger.info("Start adapter for canal-client mq topic: {} succeed", canalAdapter.getInstance() + "-"
+ group.getGroupId());
logger.info("Start adapter for canal-client mq topic: {} succeed",
canalAdapter.getInstance() + "-" + group.getGroupId());
}
}
// if ("tcp".equalsIgnoreCase(canalClientConfig.getMode())) {
// // 初始化canal-client的适配器
// for (CanalClientConfig.CanalAdapter canalAdapter :
// canalClientConfig.getCanalAdapters()) {
// List<List<OuterAdapter>> canalOuterAdapterGroups = new
// CopyOnWriteArrayList<>();
//
// for (CanalClientConfig.Group connectorGroup :
// canalAdapter.getGroups()) {
// List<OuterAdapter> canalOutConnectors = new CopyOnWriteArrayList<>();
// for (OuterAdapterConfig c : connectorGroup.getOuterAdapters()) {
// loadAdapter(c, canalOutConnectors);
// }
// canalOuterAdapterGroups.add(canalOutConnectors);
// }
// CanalAdapterWorker worker;
// if (StringUtils.isNotEmpty(canalServerHost)) {
// worker = new CanalAdapterWorker(canalClientConfig,
// canalAdapter.getInstance(),
// canalServerHost,
// zkHosts,
// canalOuterAdapterGroups);
// } else if (zkHosts != null) {
// worker = new CanalAdapterWorker(canalClientConfig,
// canalAdapter.getInstance(),
// zkHosts,
// canalOuterAdapterGroups);
// } else {
// throw new RuntimeException("No canal server connector found");
// }
// canalWorkers.put(canalAdapter.getInstance(), worker);
// worker.start();
// logger.info("Start adapter for canal instance: {} succeed",
// canalAdapter.getInstance());
// }
// } else if ("kafka".equalsIgnoreCase(canalClientConfig.getMode())) {
// // 初始化canal-client-kafka的适配器
// for (CanalClientConfig.CanalAdapter canalAdapter :
// canalClientConfig.getCanalAdapters()) {
// for (CanalClientConfig.Group group : canalAdapter.getGroups()) {
// List<List<OuterAdapter>> canalOuterAdapterGroups = new
// CopyOnWriteArrayList<>();
// List<OuterAdapter> canalOuterAdapters = new CopyOnWriteArrayList<>();
// for (OuterAdapterConfig config : group.getOuterAdapters()) {
// loadAdapter(config, canalOuterAdapters);
// }
// canalOuterAdapterGroups.add(canalOuterAdapters);
//
// CanalAdapterKafkaWorker canalKafkaWorker = new
// CanalAdapterKafkaWorker(canalClientConfig,
// canalClientConfig.getMqServers(),
// canalAdapter.getInstance(),
// group.getGroupId(),
// canalOuterAdapterGroups,
// canalClientConfig.getFlatMessage());
// canalMQWorker.put(canalAdapter.getInstance() + "-kafka-" +
// group.getGroupId(), canalKafkaWorker);
// canalKafkaWorker.start();
// logger.info("Start adapter for canal-client mq topic: {} succeed",
// canalAdapter.getInstance() + "-" + group.getGroupId());
// }
// }
// } else if ("rocketMQ".equalsIgnoreCase(canalClientConfig.getMode()))
// {
// // 初始化canal-client-rocketMQ的适配器
// for (CanalClientConfig.CanalAdapter canalAdapter :
// canalClientConfig.getCanalAdapters()) {
// for (CanalClientConfig.Group group : canalAdapter.getGroups()) {
// List<List<OuterAdapter>> canalOuterAdapterGroups = new
// CopyOnWriteArrayList<>();
// List<OuterAdapter> canalOuterAdapters = new CopyOnWriteArrayList<>();
// for (OuterAdapterConfig config : group.getOuterAdapters()) {
// loadAdapter(config, canalOuterAdapters);
// }
// canalOuterAdapterGroups.add(canalOuterAdapters);
// CanalAdapterRocketMQWorker rocketMQWorker = new
// CanalAdapterRocketMQWorker(canalClientConfig,
// canalClientConfig.getMqServers(),
// canalAdapter.getInstance(),
// group.getGroupId(),
// canalOuterAdapterGroups,
// canalClientConfig.getAccessKey(),
// canalClientConfig.getSecretKey(),
// canalClientConfig.getFlatMessage(),
// canalClientConfig.isEnableMessageTrace(),
// canalClientConfig.getCustomizedTraceTopic(),
// canalClientConfig.getAccessChannel(),
// canalClientConfig.getNamespace());
// canalMQWorker.put(canalAdapter.getInstance() + "-rocketmq-" +
// group.getGroupId(), rocketMQWorker);
// rocketMQWorker.start();
//
// logger.info("Start adapter for canal-client mq topic: {} succeed",
// canalAdapter.getInstance() + "-" + group.getGroupId());
// }
// }
// } else if ("rabbitMQ".equalsIgnoreCase(canalClientConfig.getMode()))
// {
// // 初始化canal-client-rabbitMQ的适配器
// for (CanalClientConfig.CanalAdapter canalAdapter :
// canalClientConfig.getCanalAdapters()) {
// for (CanalClientConfig.Group group : canalAdapter.getGroups()) {
// List<List<OuterAdapter>> canalOuterAdapterGroups = new
// CopyOnWriteArrayList<>();
// List<OuterAdapter> canalOuterAdapters = new CopyOnWriteArrayList<>();
// for (OuterAdapterConfig config : group.getOuterAdapters()) {
// loadAdapter(config, canalOuterAdapters);
// }
// canalOuterAdapterGroups.add(canalOuterAdapters);
// CanalAdapterRabbitMQWorker rabbitMQWork = new
// CanalAdapterRabbitMQWorker(canalClientConfig,
// canalOuterAdapterGroups,
// canalAdapter.getInstance(),
// group.getGroupId(),
// canalClientConfig.getFlatMessage());
// canalMQWorker.put(canalAdapter.getInstance() + "-rabbitmq-" +
// group.getGroupId(), rabbitMQWork);
// rabbitMQWork.start();
//
// logger.info("Start adapter for canal-client mq topic: {} succeed",
// canalAdapter.getInstance() + "-" + group.getGroupId());
// }
// }
// // CanalAdapterRabbitMQWork
// }
}
private void loadAdapter(OuterAdapterConfig config, List<OuterAdapter> canalOutConnectors) {
try {
OuterAdapter adapter;
adapter = loader.getExtension(config.getName(), StringUtils.trimToEmpty(config.getKey()));
adapter = loader.getExtension(config.getName(), config.getKey());
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// 替换ClassLoader
@@ -1,15 +1,21 @@
package com.alibaba.otter.canal.adapter.launcher.rest;
import com.alibaba.otter.canal.adapter.launcher.common.EtlLock;
import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch;
import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader;
import com.alibaba.otter.canal.client.adapter.support.FileName2KeyMapping;
import com.alibaba.otter.canal.client.adapter.support.Result;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
@@ -19,14 +25,6 @@ import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.otter.canal.adapter.launcher.common.EtlLock;
import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch;
import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader;
import com.alibaba.otter.canal.client.adapter.support.Result;
/**
* 适配器操作Rest
*
@@ -66,6 +64,9 @@ public class CommonRest {
@PostMapping("/etl/{type}/{key}/{task}")
public EtlResult etl(@PathVariable String type, @PathVariable String key, @PathVariable String task,
@RequestParam(name = "params", required = false) String params) {
if (key == null) {
key = FileName2KeyMapping.getKey(type, task);
}
OuterAdapter adapter = loader.getExtension(type, key);
String destination = adapter.getDestination(task);
String lockKey = destination == null ? task : destination;
@@ -133,6 +134,9 @@ public class CommonRest {
*/
@GetMapping("/count/{type}/{key}/{task}")
public Map<String, Object> count(@PathVariable String type, @PathVariable String key, @PathVariable String task) {
if (key == null) {
key = FileName2KeyMapping.getKey(type, task);
}
OuterAdapter adapter = loader.getExtension(type, key);
return adapter.count(task);
}
@@ -11,7 +11,7 @@ canal.conf:
flatMessage: true
zookeeperHosts:
syncBatchSize: 1000
retries: 0
retries: -1
timeout:
accessKey:
secretKey:
@@ -64,6 +64,8 @@ canal.conf:
# jdbc.url: jdbc:mysql://127.0.0.1:3306/mytest2?useUnicode=true
# jdbc.username: root
# jdbc.password: 121212
# druid.stat.enable: false
# druid.stat.slowSqlMillis: 1000
# - name: rdb
# key: oracle1
# properties:
@@ -91,7 +93,14 @@ canal.conf:
# mode: transport # or rest
# # security.auth: test:123456 # only used for rest mode
# cluster.name: elasticsearch
# - name: kudu
# key: kudu
# properties:
# kudu.master.address: 127.0.0.1 # ',' split multi address
# - name: kudu
# key: kudu
# properties:
# kudu.master.address: 127.0.0.1 # ',' split multi address
# - name: phoenix
# key: phoenix
# properties:
# jdbc.driverClassName: org.apache.phoenix.jdbc.PhoenixDriver
# jdbc.url: jdbc:phoenix:127.0.0.1:2181:/hbase/db
# jdbc.username:
# jdbc.password:
@@ -1,6 +1,6 @@
#canal:
# manager:
# jdbc:
# url: jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8
# username: root
# password: 121212
canal:
manager:
jdbc:
url: jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8
username: canal
password: canal
@@ -34,6 +34,23 @@
</sift>
</appender>
<appender name="RocketmqClientAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>../logs/adapter/rocketmq_client.log</file>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>../logs/adapter/%d{yyyy-MM-dd}/rocketmq_client-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 100MB -->
<maxFileSize>512MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder charset="UTF-8">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.alibaba.otter.canal.client.adapter.hbase" additivity="false">
<level value="DEBUG" />
<appender-ref ref="STDOUT"/>
@@ -54,6 +71,10 @@
<appender-ref ref="STDOUT"/>
<appender-ref ref="CANAL-ROOT" />
</logger>
<logger name="RocketmqClient" additivity="false">
<level value="INFO" />
<appender-ref ref="RocketmqClientAppender" />
</logger>
<root level="INFO">
<appender-ref ref="STDOUT"/>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -6,8 +6,8 @@ import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
@@ -36,7 +36,7 @@ public class LoggerAdapterExample implements OuterAdapter {
}
public void sync(Dml dml) {
logger.info("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.info("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
@Override
+144
View File
@@ -0,0 +1,144 @@
<?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.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.phoenix</artifactId>
<packaging>jar</packaging>
<name>canal client adapter phoenix module for otter ${project.version}</name>
<dependencies>
<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>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.common</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>provided</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/phoenix" overwrite="true">-->
<!-- <fileset dir="${project.basedir}/target/classes/phoenix" erroronmissingdir="true">-->
<!-- <include name="*.yml" />-->
<!-- </fileset>-->
<!-- </copy>-->
<!-- </tasks>-->
<tasks>
<copy todir="${project.basedir}/../launcher/target/canal-adapter/conf/phoenix" overwrite="true">
<fileset dir="${project.basedir}/target/classes/phoenix" erroronmissingdir="true">
<include name="*.yml" />
</fileset>
</copy>
<copy todir="${project.basedir}/../launcher/target/canal-adapter/plugin" overwrite="true">
<fileset dir="${project.basedir}/target/" erroronmissingdir="true">
<include name="*with-dependencies.jar" />
</fileset>
</copy>
<copy todir="${project.basedir}/../launcher/target/classes/phoenix" overwrite="true">
<fileset dir="${project.basedir}/target/classes/phoenix" erroronmissingdir="true">
<include name="*.yml" />
</fileset>
</copy>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,326 @@
package com.alibaba.otter.canal.client.adapter.phoenix;
import com.alibaba.otter.canal.client.adapter.phoenix.config.ConfigLoader;
import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.phoenix.monitor.PhoenixConfigMonitor;
import com.alibaba.otter.canal.client.adapter.phoenix.service.PhoenixEtlService;
import com.alibaba.otter.canal.client.adapter.phoenix.service.PhoenixSyncService;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.phoenix.support.SyncUtil;
import com.alibaba.otter.canal.client.adapter.support.*;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author: lihua
* @date: 2021/1/5 15:01
* @Description: Phoenix适配器实现类
*/
@SPI("phoenix")
public class PhoenixAdapter implements OuterAdapter {
private static Logger logger = LoggerFactory.getLogger(PhoenixAdapter.class);
private Map<String, MappingConfig> phoenixMapping = new ConcurrentHashMap<>(); // 文件名对应配置
private Map<String, Map<String, MappingConfig>> mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置
private static String DriverClass;
private static String PhoenixUrl;
private static Properties phoenixPro = new Properties();
private PhoenixSyncService phoenixSyncService;
private PhoenixConfigMonitor phoenixConfigMonitor;
private Properties envProperties;
private OuterAdapterConfig configuration;
public Map<String, MappingConfig> getPhoenixMapping() {
return phoenixMapping;
}
public Map<String, Map<String, MappingConfig>> getMappingConfigCache() {
return mappingConfigCache;
}
public PhoenixAdapter() {
logger.info("PhoenixAdapter create: {} {}", this, Thread.currentThread().getStackTrace());
}
/**
* 初始化方法
*
* @param configuration 外部适配器配置信息
*/
@Override
public void init(OuterAdapterConfig configuration, Properties envProperties) {
this.envProperties = envProperties;
this.configuration = configuration;
Map<String, MappingConfig> phoenixMappingTmp = ConfigLoader.load(envProperties);
// 过滤不匹配的key的配置
phoenixMappingTmp.forEach((key, config) -> {
addConfig(key, config);
});
if (phoenixMapping.isEmpty()) {
throw new RuntimeException("No phoenix adapter found for config key: " + configuration.getKey());
} else {
logger.info("[{}]phoenix config mapping: {}", this, phoenixMapping.keySet());
}
Map<String, String> properties = configuration.getProperties();
DriverClass= properties.get("jdbc.driverClassName");
PhoenixUrl=properties.get("jdbc.url");
try {
//phoenix内部本身有连接池,不需要使用Druid初始化
phoenixPro.setProperty("hbase.rpc.timeout","600000");
phoenixPro.setProperty("hbase.client.scanner.timeout.period","600000");
phoenixPro.setProperty("dfs.client.socket-timeout","600000");
phoenixPro.setProperty("phoenix.query.keepAliveMs","600000");
phoenixPro.setProperty("phoenix.query.timeoutMs","3600000");
Class.forName(DriverClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
String threads = properties.get("threads");
phoenixSyncService = new PhoenixSyncService(
threads != null ? Integer.valueOf(threads) : null
);
phoenixConfigMonitor = new PhoenixConfigMonitor();
phoenixConfigMonitor.init(configuration.getKey(), this, envProperties);
}
/**
* 获取phoenix连接
* @return
*/
public static Connection getPhoenixConnection() {
try {
return DriverManager.getConnection(PhoenixUrl,phoenixPro);
} catch (SQLException e) {
logger.error("getPhoenixConnection Exception"+e.getMessage());
}
return null;
}
/**
* 同步方法
*
* @param dmls 数据包
*/
@Override
public void sync(List<Dml> dmls) {
if (dmls == null || dmls.isEmpty()) {
return;
}
try {
phoenixSyncService.sync(mappingConfigCache, dmls, envProperties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* ETL方法
*
* @param task 任务名, 对应配置名
* @param params etl筛选条件
* @return ETL结果
*/
@Override
public EtlResult etl(String task, List<String> params) {
EtlResult etlResult = new EtlResult();
MappingConfig config = phoenixMapping.get(task);
if (config != null) {
DataSource srcDataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
if (srcDataSource != null) {
return PhoenixEtlService.importData(srcDataSource, getPhoenixConnection(), config, params);
} else {
etlResult.setSucceeded(false);
etlResult.setErrorMessage("DataSource not found");
return etlResult;
}
} else {
StringBuilder resultMsg = new StringBuilder();
boolean resSucc = true;
// ds不为空说明传入的是destination
for (MappingConfig configTmp : phoenixMapping.values()) {
// 取所有的destination为task的配置
if (configTmp.getDestination().equals(task)) {
DataSource srcDataSource = DatasourceConfig.DATA_SOURCES.get(configTmp.getDataSourceKey());
if (srcDataSource == null) {
continue;
}
EtlResult etlRes = PhoenixEtlService.importData(srcDataSource,getPhoenixConnection(), configTmp, 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) {
Map<String, Object> res = new LinkedHashMap<>();
MappingConfig config = phoenixMapping.get(task);
if (config == null) {
logger.info("[{}]phoenix config mapping: {}", this, phoenixMapping.keySet());
res.put("succeeded", false);
res.put("errorMessage", "Task[" + task + "] not found");
res.put("tasks", phoenixMapping.keySet());
return res;
}
MappingConfig.DbMapping dbMapping = config.getDbMapping();
String sql = "SELECT COUNT(1) AS cnt FROM " + SyncUtil.getDbTableName(dbMapping);
Connection conn = null;
try {
//conn = dataSource.getConnection();
conn = getPhoenixConnection();
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 (Exception 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));
return res;
}
/**
* 获取对应canal instance name 或 mq topic
*
* @param task 任务名, 对应配置名
* @return destination
*/
@Override
public String getDestination(String task) {
MappingConfig config = phoenixMapping.get(task);
if (config != null) {
return config.getDestination();
}
return null;
}
/**
* 销毁方法
*/
@Override
public void destroy() {
if (phoenixConfigMonitor != null) {
phoenixConfigMonitor.destroy();
}
if (phoenixSyncService != null) {
phoenixSyncService.close();
}
}
private void addSyncConfigToCache(String configName, MappingConfig mappingConfig) {
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().toLowerCase();
} else {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable().toLowerCase();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
public boolean addConfig(String fileName, MappingConfig config) {
if (match(config)) {
phoenixMapping.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");
}
phoenixMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
phoenixMapping.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,48 @@
package com.alibaba.otter.canal.client.adapter.phoenix.config;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
/**
* Phoenix表映射配置加载器
*/
public class ConfigLoader {
private static Logger logger = LoggerFactory.getLogger(ConfigLoader.class);
/**
* 加载Phoenix表映射配置
*
* @return 配置名/配置文件名--对象
*/
public static Map<String, MappingConfig> load(Properties envProperties) {
logger.info("## Start loading phoenix mapping config ... ");
Map<String, MappingConfig> result = new LinkedHashMap<>();
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("phoenix");
configContentMap.forEach((fileName, content) -> {
MappingConfig config = YmlConfigBinder
.bindYmlToObj(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("## Phoenix mapping config loaded");
logger.info("## Phoenix sync threads: " + ConfigurationManager.getInteger("threads"));
return result;
}
}
@@ -0,0 +1,49 @@
package com.alibaba.otter.canal.client.adapter.phoenix.config;
import java.io.InputStream;
import java.util.Properties;
/**
* 配置管理组件
* @author Administrator
*
*/
public class ConfigurationManager {
private static Properties prop = new Properties();
static {
try {
InputStream in = ConfigurationManager.class
.getClassLoader().getResourceAsStream("phoenix/phoenix_common.properties");
prop.load(in);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取指定key对应的value
*
* @param key
* @return 返回value是字符串
*/
public static String getProperty(String key) {
return prop.getProperty(key);
}
/**
* 获取整数类型的配置项
* @param key StringKye
* @return value
*/
public static Integer getInteger(String key) {
String value = getProperty(key);
try {
return Integer.valueOf(value);
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}
@@ -0,0 +1,288 @@
package com.alibaba.otter.canal.client.adapter.phoenix.config;
import org.apache.commons.lang.StringUtils;
import java.util.*;
/**
* Phoenix表映射配置
*/
@SuppressWarnings("unused")
public class MappingConfig {
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映射配置
private boolean debug = false; // 调试
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 boolean isDebug() {
return debug;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
public void validate() {
if (dbMapping.database == null || dbMapping.database.isEmpty()) {
throw new NullPointerException("dbMapping.database");
}
if ((dbMapping.table == null || dbMapping.table.isEmpty())) {
throw new NullPointerException("dbMapping.table");
}
if ((dbMapping.targetTable == null || dbMapping.targetTable.isEmpty())) {
throw new NullPointerException("dbMapping.targetTable");
}
}
public static class DbMapping {
private String database; // 数据库名或schema名
private String table; // 表名
private Map<String, String> targetPk = new LinkedHashMap<>(); // 目标表主键字段
private boolean mapAll = true; // 映射所有字段
private boolean alter = true; // 是否允许修改表
private boolean drop = false; // 是否允许删除字段
private boolean limit = false; // 是否限制字段长度
private boolean skipMissing = false; // 是否跳过丢失的字段
private boolean escapeUpper = true; // 字段默认大写加双引号
private String targetDb; // 目标库名
private String targetTable; // 目标表名
private Map<String, String> targetColumns; // 目标表字段映射
private List<String> excludeColumns; // 不映射的字段
private String etlCondition; // etl条件sql
private int readBatch = 5000;
private int commitBatch = 5000; // etl等批量提交大小
private Map<String, String> allMapColumns;
public String escape(String name) {
if (escapeUpper) {
return "\"" + name.toUpperCase() + "\"";
} else {
return name;
}
}
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 boolean isAlter() {
return alter;
}
public void setAlter(boolean alter) {
this.alter = alter;
}
public boolean isLimit() {
return limit;
}
public void setLimit(boolean limit) {
this.limit = limit;
}
public boolean isDrop() {
return drop;
}
public void setDrop(boolean drop) {
this.drop = drop;
}
public boolean isSkipMissing() {
return skipMissing;
}
public void setSkipMissing(boolean skipMissing) {
this.skipMissing = skipMissing;
}
public boolean isEscapeUpper() {
return escapeUpper;
}
public void setEscapeUpper(boolean escapeUpper) {
this.escapeUpper = escapeUpper;
}
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);
}
});
} else {
targetColumns = new HashMap<>();
}
return targetColumns;
}
public void setTargetColumns(Map<String, String> targetColumns) {
this.targetColumns = targetColumns;
}
public void addTargetColumn(String key, String value) {
if (targetColumns == null) {
targetColumns = new HashMap<>();
}
targetColumns.put(key, value);
if (allMapColumns != null) {
allMapColumns.put(key, value);
}
}
public void removeTargetColumn(String key) {
if (targetColumns != null) {
targetColumns.remove(key);
}
if (allMapColumns != null) {
allMapColumns.remove(key);
}
}
public List<String> getExcludeColumns() {
if (excludeColumns == null) {
excludeColumns = new ArrayList<>();
}
return excludeColumns;
}
public void setExcludeColumns(List<String> excludeColumns) {
this.excludeColumns = excludeColumns;
}
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,125 @@
package com.alibaba.otter.canal.client.adapter.phoenix.monitor;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
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.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util;
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;
/**
* phoenix config monitor
*/
public class PhoenixConfigMonitor {
private static final Logger logger = LoggerFactory.getLogger(PhoenixConfigMonitor.class);
private static final String adapterName = "phoenix"; //相应组件名字
private String key;
private PhoenixAdapter phoenixAdapter; //相应适配器名实现类
private Properties envProperties;
private FileAlterationMonitor fileMonitor;
public void init(String key, PhoenixAdapter phoenixAdapter, Properties envProperties) {
this.key = key;
this.phoenixAdapter = phoenixAdapter;
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 = YmlConfigBinder
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}
config.validate();
boolean result = phoenixAdapter.addConfig(file.getName(), config);
if (result) {
logger.info("Add a new phoenix 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 (phoenixAdapter.getPhoenixMapping().containsKey(file.getName())) {
// 加载配置文件
String configContent = MappingConfigsLoader
.loadConfig(adapterName + File.separator + file.getName());
if (configContent == null) {
onFileDelete(file);
return;
}
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}
config.validate();
phoenixAdapter.updateConfig(file.getName(), config);
logger.info("Change a phoenix 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 (phoenixAdapter.getPhoenixMapping().containsKey(file.getName())) {
phoenixAdapter.deleteConfig(file.getName());
logger.info("Delete a phoenix mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
}
@@ -0,0 +1,429 @@
package com.alibaba.otter.canal.client.adapter.phoenix.service;
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.DbMapping;
import com.alibaba.otter.canal.client.adapter.phoenix.support.PhoenixSupportUtil;
import com.alibaba.otter.canal.client.adapter.phoenix.support.SyncUtil;
import com.alibaba.otter.canal.client.adapter.phoenix.support.TypeUtil;
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.Util;
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 操作业务类
*/
public class PhoenixEtlService {
private static final Logger logger = LoggerFactory.getLogger(PhoenixEtlService.class);
private static String[] splitNotEmpty(String s) {
if (s != null && s.trim().length() > 0) {
return s.trim().split(",");
}
return new String[]{};
}
static boolean syncSchema(Connection targetDSConnection, MappingConfig config) {
DataSource srcDataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
if (srcDataSource == null) {
return false;
}
try {
return syncSchema(srcDataSource.getConnection(), targetDSConnection, config);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
private static boolean syncSchema(DataSource srcDS,Connection targetDSConnection, MappingConfig config) {
try {
return syncSchema(srcDS.getConnection(),targetDSConnection, config);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
private static boolean syncSchema(Connection srcDS, Connection targetDS, MappingConfig config) {
DbMapping dbMapping = config.getDbMapping();
if (dbMapping.getMapAll() && dbMapping.isAlter()) { // 检查字段是否缺失
Map<String, Integer> targetColumnType = new LinkedHashMap<>();
String targetTable = SyncUtil.getDbTableName(dbMapping);
try {
Util.sqlRS(targetDS, "SELECT * FROM " + targetTable + " LIMIT 1", rs -> {
try {
ResultSetMetaData rsd = rs.getMetaData();
int columnCount = rsd.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
targetColumnType.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i));
}
} catch (Exception e) {
logger.error(dbMapping.getTable() + " etl failed! ==>" + e.getMessage(), e);
}
});
} catch (RuntimeException e) {
if (!e.getCause().getClass().getName().endsWith("TableNotFoundException")) {
throw e;
}
}
StringBuilder missing = new StringBuilder();
StringBuilder constraint = new StringBuilder();
Util.sqlRS(srcDS, "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '" + dbMapping.getDatabase() + "' AND TABLE_NAME = '" + dbMapping.getTable() + "'", rs -> {
try {
List<String> excludeColumns = config.getDbMapping().getExcludeColumns();
while (rs.next()) {
String name = rs.getString("COLUMN_NAME");
String lower = name.toLowerCase();
String colType = rs.getString("COLUMN_TYPE");
if (targetColumnType.get(lower) == null && !excludeColumns.contains(lower)) {
boolean isPri = rs.getString("COLUMN_KEY").equals("PRI");
String[] args = splitNotEmpty(colType.replaceAll("^\\w+(?:\\(([^)]*)\\))?[\\s\\S]*$", "$1"));
missing.append(dbMapping.escape(name)).append(" ").append(TypeUtil.getPhoenixType(
rs.getString("DATA_TYPE").toUpperCase(),
args,
colType.contains("unsigned"),
dbMapping.isLimit()
));
if (isPri) {
if (args.length > 0 && dbMapping.isLimit() || rs.getString("IS_NULLABLE").equals("NO")) {
missing.append(" NOT NULL");
}
constraint.append(dbMapping.escape(name)).append(',');
}
missing.append(',');
}
}
} catch (Exception e) {
logger.error(dbMapping.getDatabase() + "." + dbMapping.getTable() + " schema failed! ==>" + e.getMessage(), e);
throw new RuntimeException(e);
}
});
if (missing.length() > 0) {
String sql;
if (targetColumnType.isEmpty()) {
if (constraint.length() > 0) {
constraint.deleteCharAt(constraint.length() - 1);
missing.append("CONSTRAINT pk PRIMARY KEY(").append(constraint.toString()).append(")");
} else {
missing.deleteCharAt(missing.length() - 1);
}
sql = "CREATE TABLE " + targetTable + " (" + missing.toString() + ")";
} else {
missing.deleteCharAt(missing.length() - 1);
sql = "ALTER TABLE " + targetTable + " ADD " + missing.toString();
}
logger.info("schema missing: {} {}", targetColumnType, sql);
try (PreparedStatement pstmt = targetDS.prepareStatement(sql)) {
pstmt.executeUpdate();
} catch (SQLException e) {
logger.error("sync schema error: " + e.getMessage(), e);
}
} else {
logger.debug("schema ok: {}", targetColumnType);
}
return true;
}
return false;
}
/**
* 导入数据
*/
public static EtlResult importData(DataSource srcDS, Connection targetDSConnection, MappingConfig config,
List<String> params) {
EtlResult etlResult = new EtlResult();
AtomicLong successCount = new AtomicLong();
List<String> errMsg = new ArrayList<>();
String hbaseTable = "";
try {
if (config == null) {
logger.error("Config is null!");
etlResult.setSucceeded(false);
etlResult.setErrorMessage("Config is null!");
return etlResult;
}
boolean debug = params != null && params.get(0).equals("_debug");
if (debug) {
params = params.subList(1, params.size());
}
syncSchema(srcDS, targetDSConnection, config);
DbMapping dbMapping = config.getDbMapping();
long start = System.currentTimeMillis();
// 拼接sql
StringBuilder sql = new StringBuilder(
"SELECT * FROM " + dbMapping.getDatabase() + "." + dbMapping.getTable());
// 拼接条件
appendCondition(params, dbMapping, srcDS, sql);
// 获取总数
String countSql = "SELECT COUNT(1) FROM ( " + sql + ") _CNT ";
long cnt = (Long) Util.sqlRS(srcDS, countSql, rs -> {
Long count = null;
try {
if (rs.next()) {
count = ((Number) rs.getObject(1)).longValue();
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return count == null ? 0 : count;
});
// 当大于1万条记录时开启多线程
if (cnt >= 10000) {
int threadCount = 3;
long perThreadCnt = cnt / threadCount;
ExecutorService executor = Util.newFixedThreadPool(threadCount, 5000L);
for (int i = 0; i < threadCount; i++) {
long offset = i * perThreadCnt;
Long size = null;
if (i != threadCount - 1) {
size = perThreadCnt;
}
String sqlFinal;
if (size != null) {
sqlFinal = sql + " LIMIT " + offset + "," + size;
} else {
sqlFinal = sql + " LIMIT " + offset + "," + cnt;
}
executor
.execute(() -> executeSqlImport(srcDS, targetDSConnection, sqlFinal, dbMapping, successCount, errMsg, debug));
}
executor.shutdown();
//noinspection StatementWithEmptyBody
while (!executor.awaitTermination(3, TimeUnit.SECONDS)) ;
} else {
executeSqlImport(srcDS, targetDSConnection, sql.toString(), dbMapping, successCount, errMsg, debug);
}
logger.info(
dbMapping.getTable() + " etl completed in: " + (System.currentTimeMillis() - start) / 1000 + "s!");
etlResult
.setResultMessage("导入目标表 " + SyncUtil.getDbTableName(dbMapping) + " 数据:" + successCount.get() + "");
} catch (Exception e) {
logger.error(e.getMessage(), e);
errMsg.add(hbaseTable + " etl failed! ==>" + e.getMessage());
}
if (errMsg.isEmpty()) {
etlResult.setSucceeded(true);
} else {
etlResult.setErrorMessage(Joiner.on("\n").join(errMsg));
}
return etlResult;
}
private static void appendCondition(List<String> params, DbMapping dbMapping, DataSource ds,
StringBuilder sql) {
if (params != null && params.size() == 1 && dbMapping.getEtlCondition() == null) {
AtomicBoolean stExists = new AtomicBoolean(false);
// 验证是否有SYS_TIME字段
Util.sqlRS(ds, sql.toString(), rs -> {
try {
ResultSetMetaData rsmd = rs.getMetaData();
int cnt = rsmd.getColumnCount();
for (int i = 1; i <= cnt; i++) {
String columnName = rsmd.getColumnName(i);
if ("SYS_TIME".equalsIgnoreCase(columnName)) {
stExists.set(true);
break;
}
}
} catch (Exception e) {
// ignore
}
return null;
});
if (stExists.get()) {
sql.append(" WHERE SYS_TIME >= '").append(params.get(0)).append("' ");
}
} else if (dbMapping.getEtlCondition() != null && params != null && params.size() > 0) {
String etlCondition = dbMapping.getEtlCondition();
int size = params.size();
for (int i = 0; i < size; i++) {
etlCondition = etlCondition.replace("{" + i + "}", params.get(i));
}
sql.append(" ").append(etlCondition);
}
}
/**
* 执行导入
*/
private static boolean executeSqlImport(DataSource srcDS, Connection targetDSConnection, String sql, DbMapping dbMapping,
AtomicLong successCount, List<String> errMsg, boolean debug) {
try {
Map<String, String> columnsMap = new LinkedHashMap<>();
Map<String, Integer> columnType = new LinkedHashMap<>();
PhoenixSupportUtil.sqlRS(targetDSConnection, "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping) + " LIMIT 1 ", rs -> {
try {
ResultSetMetaData rsd = rs.getMetaData();
int columnCount = rsd.getColumnCount();
List<String> columns = new ArrayList<>();
List<String> excludeColumns = dbMapping.getExcludeColumns();
for (int i = 1; i <= columnCount; i++) {
String lower = rsd.getColumnName(i).toLowerCase();
if (!excludeColumns.contains(lower)) {
columnType.put(lower, rsd.getColumnType(i));
columns.add(lower);
}
}
columnsMap.putAll(SyncUtil.getColumnsMap(dbMapping, columns));
return true;
} catch (Exception e) {
logger.error(e.getMessage(), e);
return false;
}
});
Util.sqlRS(srcDS, sql, rs -> {
int idx = 1;
try {
boolean completed = false;
// if (dbMapping.isMapAll()) {
// columnsMap = dbMapping.getAllColumns();
// } else {
// columnsMap = dbMapping.getTargetColumns();
// }
StringBuilder insertSql = new StringBuilder();
insertSql.append("UPSERT INTO ").append(SyncUtil.getDbTableName(dbMapping)).append(" (");
columnsMap
.forEach((targetColumnName, srcColumnName) -> insertSql.append(dbMapping.escape(targetColumnName)).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(")");
try (
//Connection connTarget = targetDS.getConnection();
Connection connTarget =PhoenixAdapter.getPhoenixConnection();
PreparedStatement pstmt = connTarget.prepareStatement(insertSql.toString())) {
connTarget.setAutoCommit(false);
while (rs.next()) {
completed = false;
pstmt.clearParameters();
// 删除数据
Map<String, Object> values = new LinkedHashMap<>();
StringBuilder deleteSql = new StringBuilder(
"DELETE FROM " + SyncUtil.getDbTableName(dbMapping) + " WHERE ");
appendCondition(dbMapping, deleteSql, values, rs);
try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) {
int k = 1;
for (Object val : values.values()) {
pstmt2.setObject(k++, val);
}
pstmt2.execute();
}
Map<String, Object> insertValues = new HashMap<>();
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());
try {
Object value = rs.getObject(srcColumnName);
insertValues.put(srcColumnName, value);
if (value != null) {
SyncUtil.setPStmt(type, pstmt, value, i);
} else {
pstmt.setNull(i, type);
}
} catch (SQLException e) {
insertValues.put(srcColumnName, null);
pstmt.setNull(i, type);
}
i++;
}
if (debug) {
logger.info("insert sql: {} {} {}", insertSql, insertValues, pstmt);
}
pstmt.execute();
if (logger.isTraceEnabled()) {
logger.trace("Insert into target table, sql: {}", insertSql);
}
if (idx % dbMapping.getCommitBatch() == 0) {
connTarget.commit();
completed = true;
}
idx++;
successCount.incrementAndGet();
if (logger.isDebugEnabled()) {
logger.debug("successful import count:" + successCount.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) 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(dbMapping.escape(targetColumnName)).append("=? AND ");
values.put(targetColumnName, rs.getObject(srcColumnName));
}
int len = sql.length();
sql.delete(len - 4, len);
}
}
@@ -0,0 +1,644 @@
package com.alibaba.otter.canal.client.adapter.phoenix.service;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.*;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.util.JdbcConstants;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.phoenix.config.ConfigurationManager;
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.support.BatchExecutor;
import com.alibaba.otter.canal.client.adapter.phoenix.support.SingleDml;
import com.alibaba.otter.canal.client.adapter.phoenix.support.SyncUtil;
import com.alibaba.otter.canal.client.adapter.phoenix.support.TypeUtil;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import com.alibaba.otter.canal.client.adapter.support.Util;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Function;
/**
* Phoenix同步操作业务
*/
public class PhoenixSyncService {
private static final Logger logger = LoggerFactory.getLogger(PhoenixSyncService.class);
// 源库表字段类型缓存: instance.schema.table -> <columnName, jdbcType>
private Map<String, Map<String, Integer>> columnsTypeCache;
//同步线程数
//默认开启3个线程同步,此处配置了自定义同步线程数
private int threads = ConfigurationManager.getInteger("threads");
private List<SyncItem>[] dmlsPartition;
private BatchExecutor[] batchExecutors;
private ExecutorService[] executorThreads;
public PhoenixSyncService(Integer threads) {
this(threads, new ConcurrentHashMap<>());
}
@SuppressWarnings("unchecked")
private PhoenixSyncService(Integer threads, Map<String, Map<String, Integer>> columnsTypeCache) {
this.columnsTypeCache = columnsTypeCache;
try {
if (threads != null) {
this.threads = threads;
}
this.dmlsPartition = new List[this.threads];
this.batchExecutors = new BatchExecutor[this.threads];
this.executorThreads = new ExecutorService[this.threads];
for (int i = 0; i < this.threads; i++) {
dmlsPartition[i] = new ArrayList<>();
batchExecutors[i] = new BatchExecutor();
//创建单个线程,用来操作一个无界的队列任务,不会使用额外的线程。如果线程崩溃会重新创建一个,直到任务完成。
executorThreads[i] = Executors.newSingleThreadExecutor();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 批量同步回调
*
* @param dmls 批量 DML
* @param function 回调方法
*/
private void sync(List<Dml> dmls, Function<Dml, Boolean> function) {
try {
boolean toExecute = false;
for (Dml dml : dmls) {
if (!toExecute) {
toExecute = function.apply(dml);
} else {
function.apply(dml);
}
}
if (toExecute) {
List<Future<Boolean>> futures = new ArrayList<>();
for (int i = 0; i < threads; i++) {
int j = i;
if (dmlsPartition[j].isEmpty()) {
// bypass
continue;
}
futures.add(executorThreads[i].submit(() -> {
try {
dmlsPartition[j].forEach(syncItem -> sync(batchExecutors[j],
syncItem.config,
syncItem.singleDml));
//相对于RDB同步 少了 dmlsPartition[j].clear();
//在 try catch中获取异常后再次执行一次batchExecutors[j].commit()
batchExecutors[j].commit();
return true;
} catch (Throwable e) {
batchExecutors[j].rollback();
if (!e.getClass().getName().endsWith("ColumnNotFoundException")
&& !e.getClass().getName().endsWith("TableNotFoundException")) {
throw new RuntimeException(e);
}
logger.info("table or column not found: " + e.getMessage());
boolean synced = false;
for (SyncItem syncItem : dmlsPartition[j]) {
if (PhoenixEtlService.syncSchema(batchExecutors[j].getConn(), syncItem.config)) {
synced = true;
}
}
if (!synced) {
throw new RuntimeException(e);
}
dmlsPartition[j].forEach(syncItem -> sync(batchExecutors[j],
syncItem.config,
syncItem.singleDml));
try {
batchExecutors[j].commit();
return true;
} catch (Throwable e1) {
batchExecutors[j].rollback();
throw new RuntimeException(e1);
}
} finally {
dmlsPartition[j].clear();
}
}));
}
futures.forEach(future -> {
try {
future.get();
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
});
}
} finally {
for (BatchExecutor batchExecutor : batchExecutors) {
if (batchExecutor != null) {
batchExecutor.close();
}
}
}
}
/**
* 批量同步 :将批量DML进行解析并放入List<SingleDml> --> dmlsPartition[hash].add(syncItem);
* @param mappingConfig 配置集合
* @param dmls 批量 DML
*/
public void sync(Map<String, Map<String, MappingConfig>> mappingConfig, List<Dml> dmls, Properties envProperties) {
sync(dmls, dml -> {
String destination = StringUtils.trimToEmpty(dml.getDestination());
String groupId = StringUtils.trimToEmpty(dml.getGroupId());
String database = dml.getDatabase();
String table = dml.getTable().toLowerCase();
Map<String, MappingConfig> configMap;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
//tcp 模式
configMap = mappingConfig.get(destination + "-" + groupId + "_" + database + "-" + table);
} else {
//kafka 模式 或者 RocketMQ模式
configMap = mappingConfig.get(destination + "_" + database + "-" + table);
}
if (configMap == null) {
if (logger.isTraceEnabled()) {
logger.trace("no config map: destination={},groupId={}, database={}, table={}, keys={}", destination, groupId, database, table, mappingConfig.keySet());
}
return false;
}
if (configMap.values().isEmpty()) {
logger.info("config map empty: destination={},groupId={}, database={}, table={}, keys={}", destination, groupId, database, table, mappingConfig.keySet());
return false;
}
if (dml.getIsDdl() != null && dml.getIsDdl() && StringUtils.isNotEmpty(dml.getSql())) {
// DDL
columnsTypeCache.remove(dml.getDestination() + "." + dml.getDatabase() + "." + dml.getTable());
List<SQLStatement> stmtList;
try {
stmtList = SQLUtils.parseStatements(dml.getSql(), JdbcConstants.MYSQL, false);
} catch (ParserException e) {
// 可能存在一些SQL是不支持的,比如存储过程
logger.info("parse sql error: " + dml.getSql(), e);
return false;
}
for (Map.Entry<String, MappingConfig> entry : configMap.entrySet()) {
try {
alter(batchExecutors[0], entry.getValue(), dml, stmtList, entry.getKey());
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return false;
} else {
// DML
for (Map.Entry<String, MappingConfig> entry : configMap.entrySet()) {
MappingConfig config = entry.getValue();
if (config.isDebug()) {
logger.info("DML: {} {}", entry.getKey(), JSON.toJSONString(dml, Feature.WriteNulls));
}
if (config.getConcurrent()) {
//并行同步
//将一批DML转成SingleDml
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml);
singleDmls.forEach(singleDml -> {
//取主键hash
int hash = pkHash(config.getDbMapping(), singleDml.getData());
SyncItem syncItem = new SyncItem(config, singleDml);
//相同的主键数据的顺序是可以保证的
dmlsPartition[hash].add(syncItem);
});
} else {
//不并行同步
int hash = 0;
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml);
singleDmls.forEach(singleDml -> {
SyncItem syncItem = new SyncItem(config, singleDml);
//这里线程默认是3个,如果不并行,则会出现2个线程空跑
dmlsPartition[hash].add(syncItem);
});
}
}
return true;
}
});
}
/**
* 单条 dml 同步
*
* @param batchExecutor 批量事务执行器
* @param config 对应配置对象
* @param dml DML
*/
private void sync(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) {
if (config != null) {
try {
String type = dml.getType();
if (type != null && type.equalsIgnoreCase("INSERT")) {
insert(batchExecutor, config, dml);
} else if (type != null && type.equalsIgnoreCase("UPDATE")) {
insert(batchExecutor, config, dml);
} else if (type != null && type.equalsIgnoreCase("DELETE")) {
delete(batchExecutor, config, dml);
} else if (type != null && type.equalsIgnoreCase("TRUNCATE")) {
truncate(batchExecutor, config);
} else if (logger.isInfoEnabled()){
logger.info("SingleDml: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
} catch (SQLException e) {
logger.error("sync error: " + e.getMessage(), e);
throw new RuntimeException(e);
}
}
}
private void alter(BatchExecutor batchExecutor, MappingConfig config, Dml dml, List<SQLStatement> stmtList, String configFile) throws SQLException {
if (config.isDebug()) {
logger.info("DML: {} {}", configFile, JSON.toJSONString(dml, Feature.WriteNulls));
}
DbMapping dbMapping = config.getDbMapping();
if (!dbMapping.isAlter()) {
logger.info("not alterable table: {} {}", dml.getTable(), configFile);
return;
}
Map<String, String> columnsMap = dbMapping.getTargetColumns();
Map<String, String> columnsMap1 = new HashMap<>();
for (Map.Entry<String, String> entry : columnsMap.entrySet()) {
columnsMap1.put(entry.getValue(), entry.getKey());
}
String targetTable = SyncUtil.getDbTableName(dbMapping);
Map<String, String> defValues = new HashMap<>();
for (SQLStatement statement : stmtList) {
if (statement instanceof SQLAlterTableStatement) {
SQLAlterTableStatement alterTable = (SQLAlterTableStatement) statement;
for (SQLAlterTableItem item : alterTable.getItems()) {
if (item instanceof SQLAlterTableDropColumnItem) {
SQLAlterTableDropColumnItem dropColumnItem = (SQLAlterTableDropColumnItem) item;
if (!dbMapping.isDrop()) {
logger.info("drop table column disabled: {} {}", targetTable, dropColumnItem.getColumns());
continue;
}
for (SQLName sqlName : dropColumnItem.getColumns()) {
String name = Util.cleanColumn(sqlName.getSimpleName());
String sql = "ALTER TABLE " + targetTable + " DROP COLUMN IF EXISTS " +
dbMapping.escape(columnsMap1.getOrDefault(name, name));
try {
logger.info("drop table column: {} {}", sql, batchExecutor.executeUpdate(sql));
dbMapping.removeTargetColumn(name);
} catch (Exception e) {
logger.warn("drop table column error: " + sql, e);
}
}
} else if (item instanceof SQLAlterTableAddColumn) {
SQLAlterTableAddColumn addColumn = (SQLAlterTableAddColumn) item;
if (!dbMapping.getMapAll()) {
logger.info("add table column disabled: {} {}", targetTable, addColumn.getColumns());
continue;
}
for (SQLColumnDefinition definition : addColumn.getColumns()) {
String name = Util.cleanColumn(definition.getNameAsString());
if (dbMapping.getExcludeColumns().contains(name)) {
continue;
}
String sql = "ALTER TABLE " + targetTable +
" ADD IF NOT EXISTS " +
dbMapping.escape(name) + " " + TypeUtil.getPhoenixType(definition, dbMapping.isLimit());
try {
logger.info("add table column: {} {}", sql, batchExecutor.executeUpdate(sql));
dbMapping.addTargetColumn(name, name);
if (definition.getDefaultExpr() != null) {
String defVal = definition.getDefaultExpr().toString();
if (!defVal.equalsIgnoreCase("NULL") && !defVal.equalsIgnoreCase("NOT NULL") && name.length() > 0) {
defValues.put(name, defVal);
}
}
} catch (Exception e) {
logger.error("add table column error: " + sql, e);
throw e;
}
}
}
}
}
}
if (!defValues.isEmpty()) {
StringBuilder defSql = new StringBuilder();
defSql.append("UPSERT INTO ").append(targetTable).append("(");
Set<Map.Entry<String, String>> pkSet = dbMapping.getTargetPk().entrySet();
Set<Map.Entry<String, String>> defSet = defValues.entrySet();
for (Map.Entry<String, String> entry : pkSet) {
defSql.append(dbMapping.escape(entry.getKey())).append(",");
}
for (Map.Entry<String, String> entry : defSet) {
defSql.append(dbMapping.escape(entry.getKey())).append(",");
}
defSql.deleteCharAt(defSql.length() - 1).append(") SELECT ");
for (Map.Entry<String, String> entry : pkSet) {
defSql.append(dbMapping.escape(entry.getKey())).append(",");
}
for (Map.Entry<String, String> entry : defSet) {
defSql.append(entry.getValue()).append(",");
}
defSql.deleteCharAt(defSql.length() - 1).append(" FROM ").append(targetTable);
try {
logger.info("set column default value: {} {}", defSql, batchExecutor.executeUpdate(defSql.toString()));
batchExecutor.commit();
} catch (SQLException e) {
logger.error("set column default value error: {}", defSql, e);
batchExecutor.rollback();
throw e;
}
}
}
/**
* 插入操作
*
* @param config 配置项
* @param dml DML数据
*/
private void insert(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, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data);
StringBuilder insertSql = new StringBuilder();
insertSql.append("UPSERT INTO ").append(SyncUtil.getDbTableName(dbMapping)).append(" (");
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
int mapLen = columnsMap.size();
List<Map<String, ?>> values = new ArrayList<>();
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) {
if (dbMapping.isSkipMissing()) {
logger.warn("Target missing field: {}", targetColumnName);
mapLen -= 1;
continue;
} else if (dbMapping.getMapAll() && dbMapping.isAlter() && PhoenixEtlService.syncSchema(batchExecutor.getConn(), config)) {
columnsTypeCache.remove(config.getDestination() + "." + dbMapping.getDatabase() + "." + dbMapping.getTable());
ctype = getTargetColumnType(batchExecutor.getConn(), config);
type = ctype.get(Util.cleanColumn(targetColumnName).toLowerCase());
}
if (type == null) {
throw new RuntimeException("Target column: " + targetColumnName + " not matched");
}
}
insertSql.append(dbMapping.escape(targetColumnName)).append(",");
Object value = data.get(srcColumnName);
BatchExecutor.setValue(values, type, value);
}
int len = insertSql.length();
insertSql.delete(len - 1, len).append(") VALUES (");
for (int i = 0; i < mapLen; i++) {
insertSql.append("?,");
}
len = insertSql.length();
insertSql.delete(len - 1, len).append(")");
Map<String, Object> old = dml.getOld();
try {
if (old != null && !old.isEmpty()) {
boolean keyChanged = false;
List<Map<String, ?>> delValues = new ArrayList<>();
StringBuilder deleteSql = new StringBuilder();
deleteSql.append("DELETE FROM ").append(SyncUtil.getDbTableName(dbMapping)).append(" WHERE ");
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().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) {
deleteSql.append(dbMapping.escape(targetColumnName)).append("=? AND ");
// 如果有修改主键的情况
if (old.containsKey(srcColumnName)) {
keyChanged = true;
BatchExecutor.setValue(delValues, type, old.get(srcColumnName));
} else {
BatchExecutor.setValue(delValues, type, data.get(srcColumnName));
}
}
}
if (keyChanged) {
if (config.isDebug()) {
logger.info("insert into table: {} {}", deleteSql, delValues);
}
batchExecutor.execute(deleteSql.toString(), delValues);
}
}
if (config.isDebug()) {
logger.info("insert into table: {} {}", insertSql, values);
}
batchExecutor.execute(insertSql.toString(), values);
} catch (SQLException | RuntimeException e) {
logger.warn("Insert into target table, sql: {} {}", insertSql, values ,e);
throw e;
}
if (logger.isTraceEnabled()) {
logger.trace("Insert into target table, sql: {}", insertSql);
}
}
/**
* 删除操作 没有改动
*
* @param config MappingConfig
* @param dml Single 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("DELETE FROM ").append(SyncUtil.getDbTableName(dbMapping)).append(" WHERE ");
List<Map<String, ?>> values = new ArrayList<>();
// 拼接主键
appendCondition(dbMapping, sql, ctype, values, data);
try {
batchExecutor.execute(sql.toString(), values);
if (logger.isTraceEnabled()) {
logger.trace("Delete from target table, sql: {}", sql);
}
} catch (SQLException e) {
logger.warn("Delete from target error, sql: {} {}", sql, values);
throw e;
}
}
/**
* truncate操作 没有改动
*
* @param config MappingConfig
*/
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));
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 (PhoenixSyncService.class) {
columnType = columnsTypeCache.get(cacheKey);
if (columnType == null) {
columnType = new LinkedHashMap<>();
final Map<String, Integer> columnTypeTmp = columnType;
String sql = "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping) + " WHERE 1=2";
try {
Util.sqlRS(conn, sql, rs -> {
try {
ResultSetMetaData rsd = rs.getMetaData();
int columnCount = rsd.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
columnTypeTmp.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i));
}
columnsTypeCache.put(cacheKey, columnTypeTmp);
} catch (SQLException e) {
logger.error(e.getMessage(), e);
}
});
} catch (RuntimeException e) {
//新增catch 里面做了操作
if (!e.getCause().getClass().getName().endsWith("TableNotFoundException")) {
throw e;
}
if (!PhoenixEtlService.syncSchema(conn, config)) {
throw e;
}
Util.sqlRS(conn, sql, rs -> {
try {
ResultSetMetaData rsd = rs.getMetaData();
int columnCount = rsd.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
columnTypeTmp.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i));
}
columnsTypeCache.put(cacheKey, columnTypeTmp);
} catch (SQLException e1) {
logger.error(e1.getMessage(), e1);
}
});
}
}
}
}
return columnType;
}
/**
* 拼接主键 where条件
*/
private void appendCondition(DbMapping dbMapping, StringBuilder sql, Map<String, Integer> ctype,
List<Map<String, ?>> values, Map<String, Object> d) {
// 拼接主键
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(dbMapping.escape(targetColumnName)).append("=? AND ");
Integer type = ctype.get(Util.cleanColumn(targetColumnName).toLowerCase());
if (type == null) {
throw new RuntimeException("Target column: " + targetColumnName + " not matched");
}
BatchExecutor.setValue(values, type, d.get(srcColumnName));
}
int len = sql.length();
sql.delete(len - 4, len);
}
public static class SyncItem {
private MappingConfig config;
private SingleDml singleDml;
SyncItem(MappingConfig config, SingleDml singleDml) {
this.config = config;
this.singleDml = singleDml;
}
}
/**
* 取主键hash
*/
private int pkHash(DbMapping dbMapping, Map<String, Object> d) {
int hash = 0;
// 取主键
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) {
String targetColumnName = entry.getKey();
String srcColumnName = entry.getValue();
if (srcColumnName == null) {
srcColumnName = Util.cleanColumn(targetColumnName);
}
Object value = null;
if (d != null) {
value = d.get(srcColumnName);
}
if (value != null) {
hash += value.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,108 @@
package com.alibaba.otter.canal.client.adapter.phoenix.support;
import com.alibaba.otter.canal.client.adapter.phoenix.PhoenixAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.io.Closeable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
* sql批量执行器 基本没有变动
*/
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 BatchExecutor(){
}
public Connection getConn() {
if (conn == null) {
try {
conn=PhoenixAdapter.getPhoenixConnection();
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 int executeUpdate(String sql) throws SQLException {
logger.debug("execute: {}", sql);
Statement statement = getConn().createStatement();
int ret = statement.executeUpdate(sql);
statement.close();
return ret;
}
public void execute(String sql, List<Map<String, ?>> values) throws SQLException {
if (logger.isDebugEnabled()) {
logger.debug("execute: {} {}", sql, Arrays.toString(values.toArray()));
}
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 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,97 @@
package com.alibaba.otter.canal.client.adapter.phoenix.support;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.function.Function;
/**
* @author: lihua
* @date: 2020/12/30 18:43
* @Description:
*/
public class PhoenixSupportUtil {
public static final Logger logger = LoggerFactory.getLogger(PhoenixSupportUtil.class);
public static Object sqlRS(Connection dsConnection, String sql, Function<ResultSet, Object> fun) {
try {
Connection conn = dsConnection;
Throwable var4 = null;
Object var9;
try {
Statement stmt = conn.createStatement(1003, 1007);
Throwable var6 = null;
try {
stmt.setFetchSize(-2147483648);
ResultSet rs = stmt.executeQuery(sql);
Throwable var8 = null;
try {
var9 = fun.apply(rs);
} catch (Throwable var56) {
var9 = var56;
var8 = var56;
throw var56;
} finally {
if (rs != null) {
if (var8 != null) {
try {
rs.close();
} catch (Throwable var55) {
var8.addSuppressed(var55);
}
} else {
rs.close();
}
}
}
} catch (Throwable var58) {
var6 = var58;
throw var58;
} finally {
if (stmt != null) {
if (var6 != null) {
try {
stmt.close();
} catch (Throwable var54) {
var6.addSuppressed(var54);
}
} else {
stmt.close();
}
}
}
} catch (Throwable var60) {
var4 = var60;
throw var60;
} finally {
if (conn != null) {
if (var4 != null) {
try {
conn.close();
} catch (Throwable var53) {
var4.addSuppressed(var53);
}
} else {
conn.close();
}
}
}
return var9;
} catch (Exception var62) {
logger.error("sqlRs has error, sql: {} ", sql);
throw new RuntimeException(var62);
}
}
}
@@ -0,0 +1,97 @@
package com.alibaba.otter.canal.client.adapter.phoenix.support;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 没有改动 单个DML类
*/
@SuppressWarnings({"unused", "WeakerAccess"})
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) {
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());
singleDml.setData(dml.getData().get(i));
if (dml.getOld() != null) {
singleDml.setOld(dml.getOld().get(i));
}
singleDmls.add(singleDml);
}
//MaxWell 中没有对TRUNCATE的DML操作进行解析
} 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;
}
}
@@ -0,0 +1,274 @@
package com.alibaba.otter.canal.client.adapter.phoenix.support;
import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.support.Util;
import org.apache.commons.lang.StringUtils;
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;
public class SyncUtil {
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;
}
}
}
//新增 排除掉getExcludeColumns() 去除的列
if (flag && !dbMapping.getExcludeColumns().contains(srcColumn)) {
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:
if (value instanceof Number) {
pstmt.setByte(i, ((Number) value).byteValue());
} else if (value instanceof String) {
pstmt.setByte(i, Byte.parseByte((String) value));
} else {
pstmt.setNull(i, type);
}
break;
case Types.SMALLINT:
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.INTEGER:
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.BIGINT:
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.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 result = "";
if (StringUtils.isNotEmpty(dbMapping.getTargetDb())) {
if (dbMapping.isEscapeUpper()) {
//新增 字段默认大写加双引号
result += "\"" + dbMapping.getTargetDb() + "\".";
} else {
result += dbMapping.getTargetDb() + ".";
}
}
if (dbMapping.isEscapeUpper()) {
//新增 字段默认大写加双引号
result += "\"" + dbMapping.getTargetTable().replaceAll("\\.", "\".\"") + "\"";
} else {
result += dbMapping.getTargetTable();
}
return result;
}
}
@@ -0,0 +1,142 @@
package com.alibaba.otter.canal.client.adapter.phoenix.support;
import com.alibaba.druid.sql.ast.SQLDataType;
import com.alibaba.druid.sql.ast.SQLDataTypeImpl;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import org.apache.commons.lang.StringUtils;
/**
* Phoenix类型 此类型完全新增
*/
public class TypeUtil {
private static String joinArgs(String type, Object[] args) {
if (args.length > 0) {
return type + "(" + StringUtils.join(args, ",") + ")";
}
return type;
}
/**
* 根据SQL的定义返回Phoenix的类型定义
* @see "https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-type-conversions.html"
* @param definition SQL的字段定义
* @param limit 是否启用字段长度限制
* @return Phoenix字段类型定义
*/
public static String getPhoenixType(SQLColumnDefinition definition, boolean limit) {
if (definition == null) return "VARCHAR";
SQLDataType sqlDataType = definition.getDataType();
SQLDataTypeImpl sqlDataType1 = sqlDataType instanceof SQLDataTypeImpl ? (SQLDataTypeImpl)sqlDataType : null;
boolean isUnsigned = sqlDataType1 != null && sqlDataType1.isUnsigned();
return getPhoenixType(sqlDataType.getName().toUpperCase(), sqlDataType.getArguments().toArray(), isUnsigned, limit);
}
//MySQL类型和Phoenix类型一一对应
public static String getPhoenixType(String name, Object[] args, boolean isUnsigned, boolean limit) {
switch (name) {
case "BIT":
if (limit) {
return joinArgs("BINARY", args);
}
return "BINARY";
case "TINYINT":
if (isUnsigned) {
return "UNSIGNED_TINYINT";
}
return "TINYINT";
case "BOOLEAN":
case "BOOL":
return "BOOLEAN";
case "SMALLINT":
if (isUnsigned) {
return "UNSIGNED_SMALLINT";
}
return "SMALLINT";
case "MEDIUMINT":
return "INTEGER";
case "INT":
case "INTEGER":
if (isUnsigned) {
return "UNSIGNED_INT";
}
return "INTEGER";
case "BIGINT":
if (isUnsigned) {
return "UNSIGNED_LONG";
}
return "BIGINT";
case "FLOAT":
if (isUnsigned) {
return "UNSIGNED_FLOAT";
}
return "FLOAT";
case "DOUBLE":
if (isUnsigned) {
return "UNSIGNED_DOUBLE";
}
return "DOUBLE";
case "DECIMAL":
if (limit) {
return joinArgs("DECIMAL", args);
}
return "DECIMAL";
case "DATE":
if (isUnsigned) {
return "UNSIGNED_DATE";
}
return "DATE";
case "DATETIME":
case "TIMESTAMP":
if (isUnsigned) {
return "UNSIGNED_TIMESTAMP";
}
return "TIMESTAMP";
case "TIME":
if (isUnsigned) {
return "UNSIGNED_TIME";
}
return "TIME";
case "YEAR":
return "INTEGER";
case "CHAR":
if (limit) {
return joinArgs(name, args);
}
return "VARCHAR";
case "VARCHAR":
if (limit) {
return joinArgs(name, args);
}
return "VARCHAR";
case "BINARY":
if (limit) {
return joinArgs(name, args);
}
return "VARBINARY";
case "VARBINARY":
return "VARBINARY";
case "TINYBLOB":
return "VARBINARY";
case "TINYTEXT":
return "VARCHAR";
case "BLOB":
return "VARBINARY";
case "TEXT":
return "VARCHAR";
case "MEDIUMBLOB":
return "VARBINARY";
case "MEDIUMTEXT":
return "VARCHAR";
case "LONGBLOB":
return "VARBINARY";
case "LONGTEXT":
return "VARCHAR";
case "ENUM":
case "SET":
return "VARCHAR";
}
return "VARCHAR";
}
}
@@ -0,0 +1 @@
phoenix=com.alibaba.otter.canal.client.adapter.phoenix.PhoenixAdapter
@@ -0,0 +1,99 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<!--
/**
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-->
<configuration>
<!--支持NAMESPACE映射 -->
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
<property>
<name>phoenix.schema.mapSystemTablesToNamespace</name>
<value>true</value>
</property>
<!--支持phoenix的二级索引 -->
<property>
<name>hbase.regionserver.wal.codec</name>
<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
<property>
<name>hbase.table.sanity.checks</name>
<value>false</value>
</property>
<!--启动压缩文件检查 -->
<property>
<name>hbase.regionserver.codecs</name>
<value>snappy,lzo</value>
</property>
<!-- rpc 超时时长 -->
<property>
<name>hbase.rpc.timeout</name>
<value>1200000</value>
</property>
<property>
<name>hbase.client.operation.timeout</name>
<value>600000</value>
</property>
<property>
<name>hbase.client.scanner.timeout.period</name>
<value>1200000</value>
</property>
<property>
<name>hbase.client.scanner.timeout.period</name>
<value>1200000</value>
</property>
<property>
<name>phoenix.query.timeoutMs</name>
<value>1800000</value>
</property>
<property>
<name>phoenix.query.keepAliveMs</name>
<value>600000</value>
</property>
<property>
<name>hbase.client.ipc.pool.type</name>
<value>RoundRobinPool</value>
</property>
<property>
<name>hbase.client.ipc.pool.size</name>
<value>10</value>
</property>
<property>
<name>index.builder.threads.keepalivetime</name>
<value>1200000</value>
</property>
<property>
<name>index.write.threads.keepalivetime</name>
<value>1200000</value>
</property>
<property>
<name>hbase.htable.threads.keepalivetime</name>
<value>1200000</value>
</property>
<!--允许写入自定义WAL编辑,确保索引更新的正确性 -->
<property>
<name>hbase.regionserver.wal.codec</name>
<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
</configuration>
@@ -0,0 +1,2 @@
# 同步线程数
threads =3
@@ -0,0 +1,22 @@
dataSourceKey: defaultDS
destination: example
groupId: g1
outerAdapterKey: phoenix
concurrent: true
dbMapping:
database: mytest
table: user
targetTable: mytest.user
escapeUpper: true # 字段默认大写,并用双引号引起来
targetPk:
id: ID
mapAll: true # 映射所有字段(默认true,不包含排除的字段)
alter: true # 允许修改表结构(默认truemapAll=true时可以新增,drop=true时可以删除字段)
drop: false # 允许删除字段(默认false)
skipMissing: false # 是否跳过缺失的字段(默认false,允许新增字段时会自动同步缺失的字段;true时跳过缺失的字段)
limit: false # 是否限与数据长度限制一致(默认false,不限制长度避免修改长度而无法修改)
targetColumns:
id: ID
name: NAME
excludeColumns: # 排除字段
- password
@@ -0,0 +1,32 @@
package com.alibaba.otter.canal.client.adapter.phoenix.test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;
/**
* @author: lihua
* @date: 2021/1/5 16:58
* @Description:
*/
public class PhoenixConnectionTest {
public static void main(String[] args) {
Properties phoenixPro = new Properties();
//phoenix内部本身有连接池,不需要使用Druid初始化
phoenixPro.setProperty("hbase.rpc.timeout","600000");
phoenixPro.setProperty("hbase.client.scanner.timeout.period","600000");
phoenixPro.setProperty("dfs.client.socket-timeout","600000");
phoenixPro.setProperty("phoenix.query.keepAliveMs","600000");
phoenixPro.setProperty("phoenix.query.timeoutMs","3600000");
try {
Class.forName("org.apache.phoenix.jdbc.PhoenixDriver");
Connection connection = DriverManager.getConnection("jdbc:phoenix:zookeeper01,zookeeper02,zookeeper03:2181:/hbase/db", phoenixPro);
System.out.println(connection);
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,29 @@
package com.alibaba.otter.canal.client.adapter.phoenix.test;
import com.alibaba.otter.canal.client.adapter.phoenix.config.ConfigLoader;
import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.util.Map;
/**
* @author: lihua
* @date: 2021/1/5 17:07
* @Description:
*/
public class TestConfigLoad {
@Before
public void before() {
// 加载数据源连接池
DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource);
}
@Test
public void testLoad() {
Map<String, MappingConfig> configMap = ConfigLoader.load(null);
Assert.assertFalse(configMap.isEmpty());
}
}
@@ -0,0 +1,40 @@
package com.alibaba.otter.canal.client.adapter.phoenix.test;
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.SQLException;
/**
* @author: lihua
* @date: 2021/1/5 17:09
* @Description:
*/
public class TestConstant {
public final static String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/canal_adapter?useUnicode=true";
public final static String jdbcUser = "root";
public final static String jdbcPassword = "!123456";
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,36 @@
package com.alibaba.otter.canal.client.adapter.phoenix.test.sync;
import com.alibaba.otter.canal.client.adapter.phoenix.PhoenixAdapter;
import com.alibaba.otter.canal.client.adapter.phoenix.test.TestConstant;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
import java.util.HashMap;
import java.util.Map;
/**
* @author: lihua
* @date: 2021/1/5 23:15
* @Description:
*/
public class Common {
public static PhoenixAdapter init() {
DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource);
OuterAdapterConfig outerAdapterConfig = new OuterAdapterConfig();
outerAdapterConfig.setName("phoenix");
outerAdapterConfig.setKey("phoenix");
Map<String, String> properties = new HashMap<>();
properties.put("jdbc.driverClassName", "org.apache.phoenix.jdbc.PhoenixDriver");
properties.put("jdbc.url", "jdbc:phoenix:zookeeper01,zookeeper02,zookeeper03:2181:/hbase/db");
outerAdapterConfig.setProperties(properties);
PhoenixAdapter adapter = new PhoenixAdapter();
adapter.init(outerAdapterConfig, null);
return adapter;
}
public static void main(String[] args) {
init();
}
}
@@ -0,0 +1,73 @@
package com.alibaba.otter.canal.client.adapter.phoenix.test.sync;
import com.alibaba.otter.canal.client.adapter.phoenix.PhoenixAdapter;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import org.junit.Before;
import org.junit.Test;
import java.util.*;
/**
* @author: lihua
* @date: 2021/1/5 23:16
* @Description:
*/
public class PhoenixSyncTest {
private PhoenixAdapter phoenixAdapter;
@Before
public void init() {
phoenixAdapter = Common.init();
}
@Test
public void testEtl() {
List<String> param = new ArrayList<>();
phoenixAdapter.etl("phoenixtest_user.yml", param);
}
@Test
public void testCount() {
phoenixAdapter.count("phoenixtest_user.yml");
}
@Test
public void test01() {
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", 1);
data.put("name", "sixPulseExcalibur");
data.put("password", "123456");
dml.setData(dataList);
phoenixAdapter.sync(Collections.singletonList(dml));
}
@Test
public void test02() {
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", 1);
data.put("name", "sixPulseExcalibur2");
dml.setData(dataList);
List<Map<String, Object>> oldList = new ArrayList<>();
Map<String, Object> old = new LinkedHashMap<>();
oldList.add(old);
old.put("name", "sixPulseExcalibur");
dml.setOld(oldList);
phoenixAdapter.sync(Collections.singletonList(dml));
}
}
+46 -4
View File
@@ -4,12 +4,12 @@
<parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client-adapter</artifactId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
<packaging>pom</packaging>
<name>canal client adapter module for otter ${project.version}</name>
@@ -20,6 +20,7 @@
<java_source_version>1.8</java_source_version>
<java_target_version>1.8</java_target_version>
<file_encoding>UTF-8</file_encoding>
<log4j_version>2.17.0</log4j_version>
</properties>
<modules>
@@ -32,6 +33,8 @@
<module>es7x</module>
<module>escore</module>
<module>kudu</module>
<module>phoenix</module>
<module>tablestore</module>
</modules>
<licenses>
@@ -180,9 +183,9 @@
<version>42.1.4</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
<version>11.2.0.4</version>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
@@ -210,6 +213,12 @@
<artifactId>curator-recipes</artifactId>
<version>2.10.0</version>
</dependency>
<!-- 单独指定guava版本,兼容curator-client -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
@@ -250,6 +259,39 @@
<version>1.9.0</version>
<scope>test</scope>
</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>
</dependencyManagement>
+2 -2
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -30,7 +30,7 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc6</artifactId>
<scope>test</scope>
</dependency>
@@ -1,7 +1,9 @@
package com.alibaba.otter.canal.client.adapter.rdb;
import com.alibaba.otter.canal.client.adapter.support.FileName2KeyMapping;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -13,7 +15,9 @@ 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.rdb.config.ConfigLoader;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
@@ -53,6 +57,8 @@ public class RdbAdapter implements OuterAdapter {
private Properties envProperties;
private OuterAdapterConfig configuration;
public Map<String, MappingConfig> getRdbMapping() {
return rdbMapping;
}
@@ -73,46 +79,22 @@ public class RdbAdapter implements OuterAdapter {
@Override
public void init(OuterAdapterConfig configuration, Properties envProperties) {
this.envProperties = envProperties;
this.configuration = configuration;
// 从jdbc url获取db类型
Map<String, String> properties = configuration.getProperties();
String dbType = JdbcUtils.getDbType(properties.get("jdbc.url"), null);
Map<String, MappingConfig> rdbMappingTmp = ConfigLoader.load(envProperties);
// 过滤不匹配的key的配置
rdbMappingTmp.forEach((key, mappingConfig) -> {
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|| (mappingConfig.getOuterAdapterKey() != null && mappingConfig.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey()))) {
rdbMapping.put(key, mappingConfig);
}
rdbMappingTmp.forEach((key, config) -> {
addConfig(key, config);
});
if (rdbMapping.isEmpty()) {
throw new RuntimeException("No rdb adapter found for config key: " + configuration.getKey());
}
for (Map.Entry<String, MappingConfig> entry : rdbMapping.entrySet()) {
String configName = entry.getKey();
MappingConfig mappingConfig = entry.getValue();
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));
}
}
// 初始化连接池
Map<String, String> properties = configuration.getProperties();
dataSource = new DruidDataSource();
dataSource.setDriverClassName(properties.get("jdbc.driverClassName"));
dataSource.setUrl(properties.get("jdbc.url"));
@@ -125,10 +107,20 @@ public class RdbAdapter implements OuterAdapter {
dataSource.setTimeBetweenEvictionRunsMillis(60000);
dataSource.setMinEvictableIdleTimeMillis(300000);
dataSource.setUseUnfairLock(true);
dataSource.setDbType(dbType);
// List<String> array = new ArrayList<>();
// array.add("set names utf8mb4;");
// dataSource.setConnectionInitSqls(array);
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) {
@@ -165,7 +157,9 @@ public class RdbAdapter implements OuterAdapter {
return;
}
try {
rdbSyncService.sync(mappingConfigCache, dmls, envProperties);
if (!mappingConfigCache.isEmpty()) {
rdbSyncService.sync(mappingConfigCache, dmls, envProperties);
}
rdbMirrorDbSyncService.sync(dmls);
} catch (Exception e) {
throw new RuntimeException(e);
@@ -226,7 +220,7 @@ public class RdbAdapter implements OuterAdapter {
public Map<String, Object> count(String task) {
MappingConfig config = rdbMapping.get(task);
MappingConfig.DbMapping dbMapping = config.getDbMapping();
String sql = "SELECT COUNT(1) AS cnt FROM " + SyncUtil.getDbTableName(dbMapping);
String sql = "SELECT COUNT(1) AS cnt FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType());
Connection conn = null;
Map<String, Object> res = new LinkedHashMap<>();
try {
@@ -252,7 +246,7 @@ public class RdbAdapter implements OuterAdapter {
}
}
}
res.put("targetTable", SyncUtil.getDbTableName(dbMapping));
res.put("targetTable", SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()));
return res;
}
@@ -289,4 +283,67 @@ public class RdbAdapter implements OuterAdapter {
dataSource.close();
}
}
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)) {
rdbMapping.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");
}
rdbMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
rdbMapping.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;
}
}
@@ -1,24 +1,18 @@
package com.alibaba.otter.canal.client.adapter.rdb.monitor;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
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.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.rdb.config.MirrorDbConfig;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util;
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;
public class RdbConfigMonitor {
@@ -41,7 +35,7 @@ public class RdbConfigMonitor {
File confDir = Util.getConfDirPath(adapterName);
try {
FileAlterationObserver observer = new FileAlterationObserver(confDir,
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileListener listener = new FileListener();
observer.addListener(listener);
fileMonitor = new FileAlterationMonitor(3000, observer);
@@ -69,16 +63,15 @@ public class RdbConfigMonitor {
// 加载新增的配置文件
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}
config.validate();
if ((key == null && config.getOuterAdapterKey() == null)
|| (key != null && key.equals(config.getOuterAdapterKey()))) {
addConfigToCache(file, config);
logger.info("Add a new rdb mapping config: {} to canal adapter", file.getName());
boolean result = rdbAdapter.addConfig(file.getName(), config);
if (result) {
logger.info("Add a new rdb mapping config: {} to canal adapter",
file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
@@ -92,28 +85,19 @@ public class RdbConfigMonitor {
try {
if (rdbAdapter.getRdbMapping().containsKey(file.getName())) {
// 加载配置文件
String configContent = MappingConfigsLoader
.loadConfig(adapterName + File.separator + file.getName());
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
if (configContent == null) {
onFileDelete(file);
return;
}
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
.bindYmlToObj(null, configContent, MappingConfig.class, null,
envProperties);
if (config == null) {
return;
}
config.validate();
if ((key == null && config.getOuterAdapterKey() == null)
|| (key != null && key.equals(config.getOuterAdapterKey()))) {
if (rdbAdapter.getRdbMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
} else {
// 不能修改outerAdapterKey
throw new RuntimeException("Outer adapter key not allowed modify");
}
rdbAdapter.updateConfig(file.getName(), config);
logger.info("Change a rdb mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
@@ -127,7 +111,7 @@ public class RdbConfigMonitor {
try {
if (rdbAdapter.getRdbMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
rdbAdapter.deleteConfig(file.getName());
logger.info("Delete a rdb mapping config: {} of canal adapter", file.getName());
}
@@ -135,47 +119,5 @@ public class RdbConfigMonitor {
logger.error(e.getMessage(), e);
}
}
private void addConfigToCache(File file, MappingConfig mappingConfig) {
if (mappingConfig == null || mappingConfig.getDbMapping() == null) {
return;
}
rdbAdapter.getRdbMapping().put(file.getName(), mappingConfig);
if (!mappingConfig.getDbMapping().getMirrorDb()) {
Map<String, MappingConfig> configMap = rdbAdapter.getMappingConfigCache()
.computeIfAbsent(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-"
+ mappingConfig.getDbMapping().getTable(),
k1 -> new HashMap<>());
configMap.put(file.getName(), mappingConfig);
} else {
Map<String, MirrorDbConfig> mirrorDbConfigCache = rdbAdapter.getMirrorDbConfigCache();
mirrorDbConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
+ mappingConfig.getDbMapping().getDatabase(),
MirrorDbConfig.create(file.getName(), mappingConfig));
}
}
private void deleteConfigFromCache(File file) {
MappingConfig mappingConfig = rdbAdapter.getRdbMapping().remove(file.getName());
if (mappingConfig == null || mappingConfig.getDbMapping() == null) {
return;
}
if (!mappingConfig.getDbMapping().getMirrorDb()) {
for (Map<String, MappingConfig> configMap : rdbAdapter.getMappingConfigCache().values()) {
if (configMap != null) {
configMap.remove(file.getName());
}
}
} else {
rdbAdapter.getMirrorDbConfigCache().forEach((key, mirrorDbConfig) -> {
if (mirrorDbConfig.getFileName().equals(file.getName())) {
rdbAdapter.getMirrorDbConfigCache().remove(key);
}
});
}
}
}
}
@@ -13,6 +13,7 @@ import java.util.concurrent.atomic.AtomicLong;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping;
import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil;
@@ -56,8 +57,11 @@ public class RdbEtlService extends AbstractEtlService {
DbMapping dbMapping = (DbMapping) mapping;
Map<String, String> columnsMap = new LinkedHashMap<>();
Map<String, Integer> columnType = new LinkedHashMap<>();
DruidDataSource dataSource = (DruidDataSource) srcDS;
Util.sqlRS(targetDS, "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping) + " LIMIT 1 ", rs -> {
Util.sqlRS(targetDS,
"SELECT * FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " LIMIT 1 ",
rs -> {
try {
ResultSetMetaData rsd = rs.getMetaData();
@@ -83,7 +87,9 @@ public class RdbEtlService extends AbstractEtlService {
boolean completed = false;
StringBuilder insertSql = new StringBuilder();
insertSql.append("INSERT INTO ").append(SyncUtil.getDbTableName(dbMapping)).append(" (");
insertSql.append("INSERT INTO ")
.append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()))
.append(" (");
columnsMap
.forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(","));
@@ -107,7 +113,7 @@ public class RdbEtlService extends AbstractEtlService {
// 删除数据
Map<String, Object> pkVal = new LinkedHashMap<>();
StringBuilder deleteSql = new StringBuilder(
"DELETE FROM " + SyncUtil.getDbTableName(dbMapping) + " WHERE ");
"DELETE FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " WHERE ");
appendCondition(dbMapping, deleteSql, pkVal, rs);
try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) {
int k = 1;
@@ -7,17 +7,17 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.rdb.config.MirrorDbConfig;
import com.alibaba.otter.canal.client.adapter.rdb.support.SingleDml;
import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil;
import com.alibaba.otter.canal.client.adapter.support.Dml;
/**
@@ -31,10 +31,10 @@ public class RdbMirrorDbSyncService {
private static final Logger logger = LoggerFactory.getLogger(RdbMirrorDbSyncService.class);
private Map<String, MirrorDbConfig> mirrorDbConfigCache; // 镜像库配置
private DataSource dataSource;
private DruidDataSource dataSource;
private RdbSyncService rdbSyncService; // rdbSyncService代理
public RdbMirrorDbSyncService(Map<String, MirrorDbConfig> mirrorDbConfigCache, DataSource dataSource,
public RdbMirrorDbSyncService(Map<String, MirrorDbConfig> mirrorDbConfigCache, DruidDataSource dataSource,
Integer threads, Map<String, Map<String, Integer>> columnsTypeCache,
boolean skipDupException){
this.mirrorDbConfigCache = mirrorDbConfigCache;
@@ -45,7 +45,7 @@ public class RdbMirrorDbSyncService {
/**
* 批量同步方法
*
* @param dmls 批量 DML
* @param dmls 批量 DML,包含DDL
*/
public void sync(List<Dml> dmls) {
List<Dml> dmlList = new ArrayList<>();
@@ -66,9 +66,13 @@ public class RdbMirrorDbSyncService {
}
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, SerializerFeature.WriteMapNullValue));
logger.debug("DDL: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
executeDdl(mirrorDbConfig, dml);
rdbSyncService.getColumnsTypeCache().remove(destination + "." + database + "." + dml.getTable());
@@ -79,38 +83,32 @@ public class RdbMirrorDbSyncService {
dmlList.add(dml);
}
}
if (!dmlList.isEmpty()) {
rdbSyncService.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);
syncDml(dmlList);
}
if (config == null) {
return false;
}
// 是否区分大小写
boolean caseInsensitive = config.getDbMapping().isCaseInsensitive();
if (config.getConcurrent()) {
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
int hash = rdbSyncService.pkHash(config.getDbMapping(), singleDml.getData());
RdbSyncService.SyncItem syncItem = new RdbSyncService.SyncItem(config, singleDml);
rdbSyncService.getDmlsPartition()[hash].add(syncItem);
});
} else {
int hash = 0;
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
RdbSyncService.SyncItem syncItem = new RdbSyncService.SyncItem(config, singleDml);
rdbSyncService.getDmlsPartition()[hash].add(syncItem);
});
}
return true;
});
/**
* 批量同步Dml
*
* @param dmlList Dml列表,不包含DDL
*/
private void syncDml(List<Dml> dmlList) {
if (dmlList == null || dmlList.isEmpty()) {
return;
}
rdbSyncService.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;
}
rdbSyncService.appendDmlPartition(config, dml);
return true;
});
}
/**
@@ -153,7 +151,13 @@ public class RdbMirrorDbSyncService {
*/
private void executeDdl(MirrorDbConfig mirrorDbConfig, Dml ddl) {
try (Connection conn = dataSource.getConnection(); Statement statement = conn.createStatement()) {
statement.execute(ddl.getSql());
// 替换反引号
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()) {
@@ -3,6 +3,7 @@ package com.alibaba.otter.canal.client.adapter.rdb.service;
import java.sql.Connection;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -15,14 +16,13 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Function;
import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping;
import com.alibaba.otter.canal.client.adapter.rdb.support.BatchExecutor;
@@ -41,6 +41,7 @@ public class RdbSyncService {
private static final Logger logger = LoggerFactory.getLogger(RdbSyncService.class);
private DruidDataSource dataSource;
// 源库表字段类型缓存: instance.schema.table -> <columnName, jdbcType>
private Map<String, Map<String, Integer>> columnsTypeCache;
@@ -59,13 +60,14 @@ public class RdbSyncService {
return columnsTypeCache;
}
public RdbSyncService(DataSource dataSource, Integer threads, boolean skipDupException){
public RdbSyncService(DruidDataSource dataSource, Integer threads, boolean skipDupException){
this(dataSource, threads, new ConcurrentHashMap<>(), skipDupException);
}
@SuppressWarnings("unchecked")
public RdbSyncService(DataSource dataSource, Integer threads, Map<String, Map<String, Integer>> columnsTypeCache,
public RdbSyncService(DruidDataSource dataSource, Integer threads, Map<String, Map<String, Integer>> columnsTypeCache,
boolean skipDupException){
this.dataSource = dataSource;
this.columnsTypeCache = columnsTypeCache;
this.skipDupException = skipDupException;
try {
@@ -177,28 +179,38 @@ public class RdbSyncService {
}
for (MappingConfig config : configMap.values()) {
boolean caseInsensitive = config.getDbMapping().isCaseInsensitive();
if (config.getConcurrent()) {
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
int hash = pkHash(config.getDbMapping(), singleDml.getData());
SyncItem syncItem = new SyncItem(config, singleDml);
dmlsPartition[hash].add(syncItem);
});
} else {
int hash = 0;
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
SyncItem syncItem = new SyncItem(config, singleDml);
dmlsPartition[hash].add(syncItem);
});
}
appendDmlPartition(config, dml);
}
return true;
}
} );
}
/**
* 将Dml加入 {@link #dmlsPartition}
*
* @param config 表映射配置
* @param dml Dml对象
*/
public void appendDmlPartition(MappingConfig config, Dml dml) {
boolean caseInsensitive = config.getDbMapping().isCaseInsensitive();
if (config.getConcurrent()) {
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
int hash = pkHash(config.getDbMapping(), singleDml.getData());
SyncItem syncItem = new SyncItem(config, singleDml);
dmlsPartition[hash].add(syncItem);
});
} else {
int hash = 0;
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
SyncItem syncItem = new SyncItem(config, singleDml);
dmlsPartition[hash].add(syncItem);
});
}
}
/**
* 单条 dml 同步
*
@@ -220,7 +232,7 @@ public class RdbSyncService {
truncate(batchExecutor, config);
}
if (logger.isDebugEnabled()) {
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
} catch (SQLException e) {
throw new RuntimeException(e);
@@ -241,15 +253,15 @@ public class RdbSyncService {
}
DbMapping dbMapping = config.getDbMapping();
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data);
StringBuilder insertSql = new StringBuilder();
insertSql.append("INSERT INTO ").append(SyncUtil.getDbTableName(dbMapping)).append(" (");
insertSql.append("INSERT INTO ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())).append(" (");
columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append("`")
columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(backtick)
.append(targetColumnName)
.append("`")
.append(backtick)
.append(","));
int len = insertSql.length();
insertSql.delete(len - 1, len).append(") VALUES (");
@@ -313,13 +325,13 @@ public class RdbSyncService {
}
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("UPDATE ").append(SyncUtil.getDbTableName(dbMapping)).append(" SET ");
updateSql.append("UPDATE ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())).append(" SET ");
List<Map<String, ?>> values = new ArrayList<>();
boolean hasMatched = false;
for (String srcColumnName : old.keySet()) {
@@ -332,7 +344,7 @@ public class RdbSyncService {
if (!targetColumnNames.isEmpty()) {
hasMatched = true;
for (String targetColumnName : targetColumnNames) {
updateSql.append("`").append(targetColumnName).append("`").append("=?, ");
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");
@@ -369,11 +381,10 @@ public class RdbSyncService {
}
DbMapping dbMapping = config.getDbMapping();
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
StringBuilder sql = new StringBuilder();
sql.append("DELETE FROM ").append(SyncUtil.getDbTableName(dbMapping)).append(" WHERE ");
sql.append("DELETE FROM ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())).append(" WHERE ");
List<Map<String, ?>> values = new ArrayList<>();
// 拼接主键
@@ -392,7 +403,7 @@ public class RdbSyncService {
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));
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);
@@ -416,13 +427,18 @@ public class RdbSyncService {
if (columnType == null) {
columnType = new LinkedHashMap<>();
final Map<String, Integer> columnTypeTmp = columnType;
String sql = "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping) + " WHERE 1=2";
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++) {
columnTypeTmp.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(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) {
@@ -445,6 +461,8 @@ public class RdbSyncService {
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();
@@ -452,7 +470,7 @@ public class RdbSyncService {
if (srcColumnName == null) {
srcColumnName = Util.cleanColumn(targetColumnName);
}
sql.append("`").append(targetColumnName).append("`").append("=? AND ");
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");
@@ -1,5 +1,6 @@
package com.alibaba.otter.canal.client.adapter.rdb.support;
import com.alibaba.druid.DbType;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.support.Util;
import org.apache.commons.lang.StringUtils;
@@ -255,12 +256,36 @@ public class SyncUtil {
}
}
public static String getDbTableName(MappingConfig.DbMapping dbMapping) {
public static String getDbTableName(MappingConfig.DbMapping dbMapping, String dbType) {
String result = "";
String backtick = getBacktickByDbType(dbType);
if (StringUtils.isNotEmpty(dbMapping.getTargetDb())) {
result += ("`" + dbMapping.getTargetDb() + "`.");
result += (backtick + dbMapping.getTargetDb() + backtick + ".");
}
result += ("`" + dbMapping.getTargetTable() + "`");
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 "";
}
}
}
+93
View 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.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.tablestore</artifactId>
<packaging>jar</packaging>
<name>canal client adapter rdb 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>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</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>
</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/tablestore" overwrite="true">
<fileset dir="${project.basedir}/target/classes/tablestore" erroronmissingdir="true">
<include name="*.yml" />
</fileset>
</copy>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,352 @@
package com.alibaba.otter.canal.client.adapter.tablestore;
import com.alibaba.otter.canal.client.adapter.support.FileName2KeyMapping;
import com.alibaba.otter.canal.client.adapter.support.Util;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
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 com.alibaba.otter.canal.client.adapter.support.SPI;
import com.alibaba.otter.canal.client.adapter.tablestore.common.PropertyConstants;
import com.alibaba.otter.canal.client.adapter.tablestore.config.ConfigLoader;
import com.alibaba.otter.canal.client.adapter.tablestore.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.tablestore.service.TablestoreEtlService;
import com.alibaba.otter.canal.client.adapter.tablestore.service.TablestoreSyncService;
import com.alicloud.openservices.tablestore.DefaultTableStoreWriter;
import com.alicloud.openservices.tablestore.TableStoreWriter;
import com.alicloud.openservices.tablestore.core.auth.DefaultCredentials;
import com.alicloud.openservices.tablestore.core.auth.ServiceCredentials;
import com.alicloud.openservices.tablestore.writer.WriterConfig;
import com.alicloud.openservices.tablestore.writer.WriterResult;
import com.alicloud.openservices.tablestore.writer.enums.BatchRequestType;
import com.alicloud.openservices.tablestore.writer.enums.DispatchMode;
import com.alicloud.openservices.tablestore.writer.enums.WriteMode;
import com.alicloud.openservices.tablestore.writer.enums.WriterRetryStrategy;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
@SPI("tablestore")
public class TablestoreAdapter implements OuterAdapter {
private static final Logger logger = LoggerFactory.getLogger(TablestoreAdapter.class);
private Map<String, MappingConfig> tablestoreMapping = new ConcurrentHashMap<>(); // 文件名对应配置
private Map<String, Map<String, MappingConfig>> mappingConfigCache = new ConcurrentHashMap<>();
private Map<String, Map<String, TableStoreWriter>> writerCache = new ConcurrentHashMap<>();
private TablestoreSyncService tablestoreSyncService;
private Properties envProperties;
private OuterAdapterConfig configuration;
@Override
public void init(OuterAdapterConfig configuration, Properties envProperties) {
this.envProperties = envProperties;
this.configuration = configuration;
Map<String, MappingConfig> tablestoreMappingTmp = ConfigLoader.load(envProperties);
// 过滤不匹配的key的配置
tablestoreMappingTmp.forEach((key, config) -> {
addConfig(key, config);
});
if (tablestoreMapping.isEmpty()) {
throw new RuntimeException("No tablestore adapter found for config key: " + configuration.getKey());
}
tablestoreSyncService = new TablestoreSyncService();
}
/**
* 根据配置文件获得tablestorewriter的WriterConfig信息
* @param mappingConfig
* @return
*/
private WriterConfig getWriterConfig(MappingConfig mappingConfig) {
WriterConfig config = new WriterConfig();
MappingConfig.DbMapping mapping = mappingConfig.getDbMapping();
config.setMaxBatchRowsCount(mapping.getCommitBatch());
config.setConcurrency(mappingConfig.getThreads());
config.setDispatchMode(DispatchMode.HASH_PRIMARY_KEY);
config.setWriteMode(WriteMode.SEQUENTIAL);
config.setBatchRequestType(BatchRequestType.BULK_IMPORT);
config.setBucketCount(mappingConfig.getThreads());
config.setWriterRetryStrategy(WriterRetryStrategy.CERTAIN_ERROR_CODE_NOT_RETRY);
config.setAllowDuplicatedRowInBatchRequest(false);
return config;
}
@Override
public void sync(List<Dml> dmls) {
if (dmls == null || dmls.isEmpty()) {
return;
}
try {
Set<TableStoreWriter> writerSet = new HashSet<>();
List<Future<WriterResult>> futureList = new ArrayList<>();
for (Dml dml : dmls) {
String destination = StringUtils.trimToEmpty(dml.getDestination());
String groupId = StringUtils.trimToEmpty(dml.getGroupId());
String database = dml.getDatabase();
String table = dml.getTable();
String key;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
key = destination + "-" + groupId + "_" + database + "-" + table;
} else {
key = destination + "_" + database + "-" + table;
}
Map<String, MappingConfig> configMap = mappingConfigCache.get(key);
if (configMap == null) {
// 可能有dml中涉及到的表并没有出现在配置中,说明此类dml并不需要同步
continue;
}
Map<String, TableStoreWriter> writerMap = writerCache.get(key);
for (Map.Entry<String, MappingConfig> entry : configMap.entrySet()) {
TableStoreWriter w = writerMap.get(entry.getKey());
// 拿到所有future用于判定失败的记录
Future<WriterResult> futureTemp = tablestoreSyncService.sync(entry.getValue(), dml, w);
if (futureTemp != null) {
writerSet.add(w);
futureList.add(futureTemp);
}
}
}
if (writerSet.isEmpty()) {
return;
}
writerSet.forEach(e -> e.flush());
List<WriterResult.RowChangeStatus> totalFailedRows = new ArrayList<>();
for (Future<WriterResult> future : futureList) {
try {
WriterResult result = future.get();
List<WriterResult.RowChangeStatus> failedRows = result.getFailedRows();
if (!CollectionUtils.isEmpty(failedRows)) {
totalFailedRows.addAll(failedRows);
}
} catch (InterruptedException e) {
logger.info("InterruptedException", e);
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
if (!CollectionUtils.isEmpty(totalFailedRows)) {
// 认为有失败的请求
List<String> msgs = totalFailedRows.stream().map(e -> buildErrorMsgForFailedRowChange(e)).collect(Collectors.toList());
throw new RuntimeException("Failed rows:" + org.springframework.util.StringUtils.collectionToDelimitedString(msgs, ",", "[", "]"));
}
} catch (Exception e) {
throw e;
}
}
/**
* 组装失败记录的信息
* @param rowChangeStatus
* @return
*/
public static String buildErrorMsgForFailedRowChange(WriterResult.RowChangeStatus rowChangeStatus) {
StringBuilder sb = new StringBuilder("{Exception:");
sb.append(rowChangeStatus.getException().getMessage()).append(",Table:")
.append(rowChangeStatus.getRowChange().getTableName()).append(",PrimaryKey:")
.append("{").append(rowChangeStatus.getRowChange().getPrimaryKey().toString())
.append("}}");
return sb.toString();
}
@Override
public EtlResult etl(String task, List<String> params) {
EtlResult etlResult = new EtlResult();
MappingConfig config = tablestoreMapping.get(task);
if (config == null) {
etlResult.setErrorMessage("can not find config for " + task);
etlResult.setSucceeded(false);
return etlResult;
}
TableStoreWriter writer = null;
try {
writer = buildEtlWriter(configuration, config);
TablestoreEtlService rdbEtlService = new TablestoreEtlService(writer, config);
rdbEtlService.importData(params);
etlResult.setSucceeded(true);
return etlResult;
} catch (Exception e) {
logger.error("Error while etl for task " + task, e);
etlResult.setSucceeded(false);
etlResult.setErrorMessage(e.getMessage());
return etlResult;
} finally {
if (writer != null) {
writer.close();
}
}
}
/**
* 构造批量导入的writer
* @param configuration
* @param mappingConfig
* @return
*/
private TableStoreWriter buildEtlWriter(OuterAdapterConfig configuration, MappingConfig mappingConfig) {
Map<String, String> properties = configuration.getProperties();
ServiceCredentials credentials = new DefaultCredentials(
properties.get(PropertyConstants.TABLESTORE_ACCESSSECRETID),
properties.get(PropertyConstants.TABLESTORE_ACCESSSECRETKEY)
);
WriterConfig config = getWriterConfig(mappingConfig);
config.setBucketCount(3);
config.setAllowDuplicatedRowInBatchRequest(true);
config.setConcurrency(8);
config.setWriteMode(WriteMode.PARALLEL);
TableStoreWriter writer = new DefaultTableStoreWriter(
properties.get(PropertyConstants.TABLESTORE_ENDPOINT),
credentials,
properties.get(PropertyConstants.TABLESTORE_INSTANCENAME),
mappingConfig.getDbMapping().getTargetTable(),
config,
null
);
return writer;
}
@Override
public Map<String, Object> count(String task) {
throw new RuntimeException("count is not supportted in tablestore");
}
@Override
public String getDestination(String task) {
MappingConfig config = tablestoreMapping.get(task);
if (config != null) {
return config.getDestination();
}
return null;
}
@Override
public void destroy() {
if (tablestoreSyncService != null) {
tablestoreSyncService.close();
}
if (writerCache != null) {
for (Map<String, TableStoreWriter> tmpMap : writerCache.values()) {
if (tmpMap != null) {
for (TableStoreWriter writer : tmpMap.values()) {
writer.close();
}
}
}
}
}
private void addSyncConfigToCache(String configName, MappingConfig mappingConfig) {
Map<String, String> properties = configuration.getProperties();
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);
// 构建对应的 TableStoreWriter
ServiceCredentials credentials = new DefaultCredentials(
properties.get(PropertyConstants.TABLESTORE_ACCESSSECRETID),
properties.get(PropertyConstants.TABLESTORE_ACCESSSECRETKEY)
);
WriterConfig config = getWriterConfig(mappingConfig);
TableStoreWriter writer = new DefaultTableStoreWriter(
properties.get(PropertyConstants.TABLESTORE_ENDPOINT),
credentials,
properties.get(PropertyConstants.TABLESTORE_INSTANCENAME),
mappingConfig.getDbMapping().getTargetTable(),
config,
null
);
Map<String, TableStoreWriter> config2writerMap = writerCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
config2writerMap.put(configName, writer);
}
public boolean addConfig(String fileName, MappingConfig config) {
if (match(config)) {
tablestoreMapping.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");
}
tablestoreMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
tablestoreMapping.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,14 @@
package com.alibaba.otter.canal.client.adapter.tablestore.common;
public class PropertyConstants {
public static final String TABLESTORE_ACCESSSECRETID = "tablestore.accessSecretId";
public static final String TABLESTORE_ACCESSSECRETKEY = "tablestore.accessSecretKey";
public static final String TABLESTORE_ENDPOINT = "tablestore.endpoint";
public static final String TABLESTORE_INSTANCENAME = "tablestore.instanceName";
}
@@ -0,0 +1,51 @@
package com.alibaba.otter.canal.client.adapter.tablestore.config;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
/**
* RDB表映射配置加载器
*
* @author rewerma 2018-11-07 下午02:41:34
* @version 1.0.0
*/
public class ConfigLoader {
private static Logger logger = LoggerFactory.getLogger(ConfigLoader.class);
/**
* 加载RDB表映射配置
*
* @return 配置名/配置文件名--对象
*/
public static Map<String, MappingConfig> load(Properties envProperties) {
logger.info("## Start loading tablestore mapping config ... ");
Map<String, MappingConfig> result = new LinkedHashMap<>();
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("tablestore");
configContentMap.forEach((fileName, content) -> {
MappingConfig config = YmlConfigBinder
.bindYmlToObj(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("## Tablestore mapping config loaded:" + StringUtils.collectionToCommaDelimitedString(result.keySet()));
return result;
}
}
@@ -0,0 +1,318 @@
package com.alibaba.otter.canal.client.adapter.tablestore.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.otter.canal.client.adapter.support.AdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
import com.alibaba.otter.canal.client.adapter.support.Util;
import com.alibaba.otter.canal.client.adapter.tablestore.enums.TablestoreFieldType;
import com.alibaba.otter.canal.client.adapter.tablestore.support.SyncUtil;
import org.apache.commons.lang.StringUtils;
import java.sql.ResultSetMetaData;
import java.util.*;
/**
* RDB表映射配置
*
* @author rewerma 2018-11-07 下午02:41:34
* @version 1.0.0
*/
public class MappingConfig implements AdapterConfig {
private String dataSourceKey; // 数据源key
private String destination; // canal实例或MQ的topic
private String groupId; // groupId
private String outerAdapterKey; // 对应适配器的key
private DbMapping dbMapping; // db映射配置
private Boolean updateChangeColumns = false;
private Integer threads = 8;
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 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 Boolean getUpdateChangeColumns() {
return updateChangeColumns;
}
public void setUpdateChangeColumns(Boolean updateChangeColumns) {
this.updateChangeColumns = updateChangeColumns;
}
public Integer getThreads() {
return threads;
}
public void setThreads(Integer threads) {
this.threads = threads;
}
public void validate() {
if (dbMapping.database == null || dbMapping.database.isEmpty()) {
throw new NullPointerException("dbMapping.database");
}
if (dbMapping.table == null || dbMapping.table.isEmpty()) {
throw new NullPointerException("dbMapping.table");
}
if (dbMapping.targetTable == null || dbMapping.targetTable.isEmpty()) {
throw new NullPointerException("dbMapping.targetTable");
}
}
public static class ColumnItem {
private String targetColumn;
private String column;
private TablestoreFieldType type;
public String getColumn() {
return column;
}
public void setColumn(String column) {
this.column = column;
}
public TablestoreFieldType getType() {
return type;
}
public void setType(TablestoreFieldType type) {
this.type = type;
}
public String getTargetColumn() {
return targetColumn;
}
public void setTargetColumn(String targetColumn) {
this.targetColumn = targetColumn;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ColumnItem that = (ColumnItem) o;
return Objects.equals(column, that.column);
}
@Override
public int hashCode() {
return Objects.hash(column);
}
}
public static class DbMapping implements AdapterMapping {
private String database; // 数据库名或schema名
private String table; // 表名
private LinkedHashMap<String, String> targetPk = new LinkedHashMap<>(); // 目标表主键字段
// private boolean mapAll = false; // 映射所有字段
private String targetTable; // 目标表名
private Map<String, String> targetColumns; // 目标表字段映射
private Map<String, String> targetColumnsParsed;
private String etlCondition; // etl条件sql
private int readBatch = 5000;
private int commitBatch = 5000; // etl等批量提交大小
private Map<String, ColumnItem> columnItems = new LinkedHashMap<>(); // 转换后的字段映射列表
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 LinkedHashMap<String, String> getTargetPk() {
return targetPk;
}
public void setTargetPk(LinkedHashMap<String, String> targetPk) {
this.targetPk = targetPk;
}
public String getTargetTable() {
return targetTable;
}
public void setTargetTable(String targetTable) {
this.targetTable = targetTable;
}
public Map<String, String> getTargetColumns() {
return targetColumns;
}
public void setTargetColumns(Map<String, String> targetColumns) {
this.targetColumns = targetColumns;
}
public Map<String, String> getTargetColumnsParsed() {
return targetColumnsParsed;
}
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, ColumnItem> getColumnItems() {
return columnItems;
}
public void setColumnItems(Map<String, ColumnItem> columnItems) {
this.columnItems = columnItems;
}
public void init(MappingConfig config) {
String splitBy = "$";
if (targetColumns != null) {
boolean needTypeInference = false;
for (Map.Entry<String, String> columnField : targetColumns.entrySet()) {
String field = columnField.getValue();
String type = null;
if (field != null) {
// 解析类型
int i = field.indexOf(splitBy);
if (i > -1) {
type = field.substring(i + 1);
field = field.substring(0, i);
}
}
ColumnItem columnItem = new ColumnItem();
columnItem.setColumn(columnField.getKey());
columnItem.setTargetColumn(StringUtils.isBlank(field) ? columnField.getKey() : field);
TablestoreFieldType fieldType = SyncUtil.getTablestoreType(type);
if (fieldType == null) {
needTypeInference = true;
}
columnItem.setType(fieldType);
columnItems.put(columnField.getKey(), columnItem);
}
if (needTypeInference) {
// 认为有field没有配置映射类型,需要进行类型推断
DruidDataSource sourceDS = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
Util.sqlRS(sourceDS, "SELECT * FROM " + SyncUtil.getDbTableName(database, table) + " LIMIT 1 ", rs -> {
try {
ResultSetMetaData rsd = rs.getMetaData();
int columnCount = rsd.getColumnCount();
List<String> columns = new ArrayList<>();
for (int i = 1; i <= columnCount; i++) {
String columnName = rsd.getColumnName(i);
if (columnItems.containsKey(columnName) && columnItems.get(columnName).getType() == null) {
int columnType = rsd.getColumnType(i);
columnItems.get(columnName).setType(SyncUtil.getDefaultTablestoreType(columnType));
}
}
return true;
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
} else {
this.targetColumns = new LinkedHashMap<>();
}
targetColumnsParsed = new HashMap<>();
targetColumns.forEach((key, value) -> {
if (StringUtils.isEmpty(value)) {
targetColumnsParsed.put(key, key);
} else if (value.contains(splitBy) && columnItems.containsKey(key)) {
targetColumnsParsed.put(key, columnItems.get(key).targetColumn);
} else {
targetColumnsParsed.put(key, value);
}
});
}
}
}
@@ -0,0 +1,9 @@
package com.alibaba.otter.canal.client.adapter.tablestore.enums;
public enum TablestoreFieldType {
INT,
DOUBLE,
BOOL,
STRING,
BINARY
}
@@ -0,0 +1,134 @@
package com.alibaba.otter.canal.client.adapter.tablestore.service;
import com.alibaba.otter.canal.client.adapter.support.*;
import com.alibaba.otter.canal.client.adapter.tablestore.TablestoreAdapter;
import com.alibaba.otter.canal.client.adapter.tablestore.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.tablestore.support.SyncUtil;
import com.alicloud.openservices.tablestore.TableStoreWriter;
import com.alicloud.openservices.tablestore.model.RowChange;
import com.alicloud.openservices.tablestore.writer.WriterResult;
import org.springframework.util.CollectionUtils;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
public class TablestoreEtlService extends AbstractEtlService {
private TableStoreWriter writer;
private MappingConfig config;
private TablestoreSyncService syncService;
public TablestoreEtlService(TableStoreWriter writer, MappingConfig config){
super("Tablestore", config);
this.writer = writer;
this.config = config;
syncService = new TablestoreSyncService();
}
public EtlResult importData(List<String> params) {
MappingConfig.DbMapping dbMapping = config.getDbMapping();
String sql = "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping.getDatabase(), dbMapping.getTable());
return importData(sql, params);
}
@Override
protected boolean executeSqlImport(DataSource srcDS, String sql, List<Object> values,
AdapterConfig.AdapterMapping mapping, AtomicLong impCount, List<String> errMsg) {
try {
MappingConfig.DbMapping dbMapping = (MappingConfig.DbMapping) mapping;
Map<String, String> columnsMap = dbMapping.getTargetColumnsParsed();
Util.sqlRS(srcDS, sql, values, rs -> {
int idx = 0;
List<Future<WriterResult>> futureList = new ArrayList<>();
while (true) {
try {
if (!rs.next()) break;
} catch (SQLException throwables) {
logger.error("Error while get data from srcDs", throwables);
break;
}
Dml dml = getDMLByRs(columnsMap, rs);
List<RowChange> rowChanges = syncService.getRowChanges(dml, config);
if (CollectionUtils.isEmpty(rowChanges)) {
return null;
}
Future<WriterResult> future = writer.addRowChangeWithFuture(rowChanges);
if (future != null) {
futureList.add(future);
}
}
writer.flush();
for (Future<WriterResult> future : futureList) {
try {
WriterResult result = future.get();
if (result != null && result.isAllSucceed()) {
impCount.incrementAndGet();
idx++;
} else if (result != null && !result.isAllSucceed()) {
List<WriterResult.RowChangeStatus> totalFailedRows = result.getFailedRows();
List<String> msgs = totalFailedRows.stream().map(e -> TablestoreAdapter.buildErrorMsgForFailedRowChange(e)).collect(Collectors.toList());
logger.error("Failed rows when ETL:" + org.springframework.util.StringUtils.collectionToDelimitedString(msgs, ",", "[", "]"));
}
} catch (InterruptedException e) {
logger.info("InterruptedException", e);
errMsg.add(e.getMessage());
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
errMsg.add(e.getMessage());
throw new RuntimeException(e);
}
}
return idx;
});
return true;
} catch (Exception e) {
logger.error(e.getMessage(), e);
return false;
}
}
private Dml getDMLByRs(Map<String, String> columnsMap, ResultSet rs) {
try {
Dml dml = new Dml();
dml.setType("INSERT");
Map<String, Object> dataMap = new HashMap<>();
List<Map<String, Object>> dataList = new ArrayList<>();
dataList.add(dataMap);
dml.setData(dataList);
for (String key : columnsMap.keySet()) {
dataMap.put(key, rs.getObject(key));
}
return dml;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,397 @@
package com.alibaba.otter.canal.client.adapter.tablestore.service;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.tablestore.enums.TablestoreFieldType;
import com.alibaba.otter.canal.client.adapter.tablestore.support.SyncUtil;
import com.alicloud.openservices.tablestore.TableStoreWriter;
import com.alicloud.openservices.tablestore.model.*;
import com.alicloud.openservices.tablestore.writer.WriterResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.tablestore.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import org.springframework.util.CollectionUtils;
/**
* RDB同步操作业务
*
* @author rewerma 2018-11-7 下午06:45:49
* @version 1.0.0
*/
public class TablestoreSyncService {
private static final Logger logger = LoggerFactory.getLogger(TablestoreSyncService.class);
private Map<String, Map<String, Integer>> columnsTypeCache;
public Map<String, Map<String, Integer>> getColumnsTypeCache() {
return columnsTypeCache;
}
public TablestoreSyncService(){
this(new ConcurrentHashMap<>());
}
@SuppressWarnings("unchecked")
public TablestoreSyncService(Map<String, Map<String, Integer>> columnsTypeCache){
this.columnsTypeCache = columnsTypeCache;
}
public Future<WriterResult> sync(MappingConfig mappingConfig,
Dml dml,
TableStoreWriter writer) {
List<RowChange> rowChanges = getRowChanges(dml, mappingConfig);
if (CollectionUtils.isEmpty(rowChanges)) {
return null;
}
Future<WriterResult> future = writer.addRowChangeWithFuture(rowChanges);
if (logger.isDebugEnabled()) {
logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
return future;
}
public List<RowChange> getRowChanges(Dml dml, MappingConfig config) {
String type = dml.getType();
boolean updateColume = config.getUpdateChangeColumns();
if (type != null && type.equalsIgnoreCase("INSERT")) {
return getInsertChanges(dml, updateColume, config);
} else if (type != null && type.equalsIgnoreCase("UPDATE")) {
return getUpdateChanges(dml, updateColume, config);
} else if (type != null && type.equalsIgnoreCase("DELETE")) {
return getDeleteChanges(dml, updateColume, config);
} else {
return null;
}
}
/**
* Update 类型下构造rowChangeList
* @param dml
* @param isColumnUpdate
* @param config
* @return
*/
private List<RowChange> getUpdateChanges(Dml dml, boolean isColumnUpdate, MappingConfig config) {
List<RowChange> changeList = new ArrayList<>();
Map<String, String> columnMap = config.getDbMapping().getTargetColumnsParsed();
Map<String, MappingConfig.ColumnItem> typeMap = SyncUtil.getTypeMap(config);
MappingConfig.DbMapping dbMapping = config.getDbMapping();
if (isColumnUpdate) {
// 列更新
for (int i = 0; i < dml.getData().size(); i++) {
Map<String, Object> map = dml.getData().get(i);
Map<String, Object> old = dml.getOld().get(i);
boolean isPrimaryKeyChange = isPrimaryKeyChange(old, dbMapping.getTargetPk());
if (isPrimaryKeyChange) {
// 如果发现主键修改用put delete操作
// 先 delete
RowUpdateChange change = new RowUpdateChange(dbMapping.getTargetTable());
PrimaryKey primaryKey = buildOldPrimaryKey(map, typeMap, columnMap, dbMapping.getTargetPk(), old);
change.setPrimaryKey(primaryKey);
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (dbMapping.getTargetPk().containsKey(entry.getKey())) {
// 这是个主键, 不需要再次处理
continue;
}
if (!dbMapping.getTargetColumns().containsKey(entry.getKey())) {
// 可能是没有配置的字段
continue;
}
// 非主键
String targetColumn = columnMap.get(entry.getKey());
change.deleteColumns(targetColumn);
}
changeList.add(change);
// 然后再put
change = new RowUpdateChange(dbMapping.getTargetTable());
primaryKey = buildPrimaryKey(map, typeMap, columnMap, dbMapping.getTargetPk());
change.setPrimaryKey(primaryKey);
List<Column> columnList = getColumnsWhenPut(columnMap, dbMapping, map, typeMap);
if (!CollectionUtils.isEmpty(columnList)) {
change.put(columnList);
changeList.add(change);
}
} else {
//否则用update
RowUpdateChange change = new RowUpdateChange(dbMapping.getTargetTable());
PrimaryKey primaryKey = buildPrimaryKey(map, typeMap, columnMap, dbMapping.getTargetPk());
change.setPrimaryKey(primaryKey);
// 部分update 部分delete
boolean validData = false;
for (Map.Entry<String, Object> entry : old.entrySet()) {
if (dbMapping.getTargetPk().containsKey(entry.getKey())) {
// 这是个主键, 不需要再次处理
continue;
}
if (!dbMapping.getTargetColumns().containsKey(entry.getKey())) {
// 可能是没有配置的字段
continue;
}
// 非主键
String targetColumn = columnMap.get(entry.getKey());
Object value = map.get(entry.getKey());
validData = true;
if (value == null) {
change.deleteColumns(targetColumn);
} else {
TablestoreFieldType type = typeMap.get(entry.getKey()).getType();
ColumnValue columnValue = SyncUtil.getColumnValue(value, type);
change.put(targetColumn, columnValue);
}
}
if (validData) {
changeList.add(change);
}
}
}
} else {
// 列覆盖
for (int i = 0; i < dml.getData().size(); i++) {
Map<String, Object> map = dml.getData().get(i);
RowPutChange change = new RowPutChange(dbMapping.getTargetTable());
PrimaryKey primaryKey = buildPrimaryKey(map, typeMap, columnMap, dbMapping.getTargetPk());
change.setPrimaryKey(primaryKey);
List<Column> columnList = getColumnsWhenPut(columnMap, dbMapping, map, typeMap);
if (!CollectionUtils.isEmpty(columnList)) {
change.addColumns(columnList);
changeList.add(change);
}
if (dml.getOld() != null) {
// 如果主键发生修改,需要在tablestore中删除对应的原记录
Map<String, Object> old = dml.getOld().get(i);
if (isPrimaryKeyChange(old, dbMapping.getTargetPk())) {
RowDeleteChange delete = new RowDeleteChange(dbMapping.getTargetTable());
PrimaryKey primaryKeyDelete = buildOldPrimaryKey(map, typeMap, columnMap, dbMapping.getTargetPk(), old);
delete.setPrimaryKey(primaryKeyDelete);
changeList.add(delete);
}
}
}
}
return changeList;
}
/**
* Delete 类型下构造rowChangeList
* @param dml
* @param isColumnUpdate
* @param config
* @return
*/
private List<RowChange> getDeleteChanges(Dml dml, boolean isColumnUpdate, MappingConfig config) {
List<RowChange> changeList = new ArrayList<>();
Map<String, String> columnMap = config.getDbMapping().getTargetColumnsParsed();
Map<String, MappingConfig.ColumnItem> typeMap = SyncUtil.getTypeMap(config);
MappingConfig.DbMapping dbMapping = config.getDbMapping();
if (isColumnUpdate) {
// 列更新
for (Map<String, Object> map : dml.getData()) {
RowUpdateChange change = new RowUpdateChange(dbMapping.getTargetTable());
PrimaryKey primaryKey = buildPrimaryKey(map, typeMap, columnMap, dbMapping.getTargetPk());
change.setPrimaryKey(primaryKey);
boolean validData = false;
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (dbMapping.getTargetPk().containsKey(entry.getKey())) {
// 这是个主键, 不需要再次处理
continue;
}
if (!dbMapping.getTargetColumns().containsKey(entry.getKey())) {
// 可能是没有配置的字段
continue;
}
// 非主键
validData = true;
String targetColumn = columnMap.get(entry.getKey());
change.deleteColumns(targetColumn);
}
if (validData) {
changeList.add(change);
}
}
} else {
// 列覆盖
for (Map<String, Object> map : dml.getData()) {
RowDeleteChange change = new RowDeleteChange(dbMapping.getTargetTable());
PrimaryKey primaryKey = buildPrimaryKey(map, typeMap, columnMap, dbMapping.getTargetPk());
change.setPrimaryKey(primaryKey);
changeList.add(change);
}
}
return changeList;
}
/**
* Insert 类型下构造rowChangeList
* @param dml
* @param isColumnUpdate
* @param config
* @return
*/
private List<RowChange> getInsertChanges(Dml dml, boolean isColumnUpdate, MappingConfig config) {
List<RowChange> changeList = new ArrayList<>();
Map<String, String> columnMap = config.getDbMapping().getTargetColumnsParsed();
Map<String, MappingConfig.ColumnItem> typeMap = SyncUtil.getTypeMap(config);
MappingConfig.DbMapping dbMapping = config.getDbMapping();
if (isColumnUpdate) {
// 列更新
for (Map<String, Object> map : dml.getData()) {
RowUpdateChange change = new RowUpdateChange(dbMapping.getTargetTable());
PrimaryKey primaryKey = buildPrimaryKey(map, typeMap, columnMap, dbMapping.getTargetPk());
change.setPrimaryKey(primaryKey);
List<Column> columnList = getColumnsWhenPut(columnMap, dbMapping, map, typeMap);
if (!CollectionUtils.isEmpty(columnList)) {
change.put(columnList);
changeList.add(change);
}
}
} else {
// 列覆盖
for (Map<String, Object> map : dml.getData()) {
RowPutChange change = new RowPutChange(dbMapping.getTargetTable());
PrimaryKey primaryKey = buildPrimaryKey(map, typeMap, columnMap, dbMapping.getTargetPk());
change.setPrimaryKey(primaryKey);
List<Column> columnList = getColumnsWhenPut(columnMap, dbMapping, map, typeMap);
if (!CollectionUtils.isEmpty(columnList)) {
change.addColumns(columnList);
}
changeList.add(change);
}
}
return changeList;
}
/**
* 组装rowChange的主键
* @param map 数据map
* @param typeMap 类型映射
* @param columnMap 字段名称映射
* @param targetPk 主键map
* @return
*/
private PrimaryKey buildPrimaryKey(Map<String, Object> map, Map<String, MappingConfig.ColumnItem> typeMap, Map<String, String> columnMap, LinkedHashMap<String, String> targetPk) {
List primaryKeyList = new ArrayList<>();
for (Map.Entry<String, String> entry : targetPk.entrySet()) {
// build primary key
String targetColumn = columnMap.get(entry.getKey());
Object value = map.get(entry.getKey());
TablestoreFieldType type = typeMap.get(entry.getKey()).getType();
PrimaryKeyValue keyValue = SyncUtil.getPrimaryKeyValue(value, type);
PrimaryKeyColumn primaryKeyColumn = new PrimaryKeyColumn(targetColumn, keyValue);
primaryKeyList.add(primaryKeyColumn);
}
return new PrimaryKey(primaryKeyList);
}
private PrimaryKey buildOldPrimaryKey(Map<String, Object> map,
Map<String, MappingConfig.ColumnItem> typeMap,
Map<String, String> columnMap,
LinkedHashMap<String, String> targetPk,
Map<String, Object> old) {
List primaryKeyList = new ArrayList<>();
for (Map.Entry<String, String> entry : targetPk.entrySet()) {
// build primary key
String targetColumn = columnMap.get(entry.getKey());
Object value = old != null && old.containsKey(entry.getKey()) && !old.get(entry.getKey()).equals(map.get(entry.getKey())) ? old.get(entry.getKey()) : map.get(entry.getKey());
TablestoreFieldType type = typeMap.get(entry.getKey()).getType();
PrimaryKeyValue keyValue = SyncUtil.getPrimaryKeyValue(value, type);
PrimaryKeyColumn primaryKeyColumn = new PrimaryKeyColumn(targetColumn, keyValue);
primaryKeyList.add(primaryKeyColumn);
}
return new PrimaryKey(primaryKeyList);
}
/**
* 用于获得全量覆盖时的非主键列对应的columnlist
* @param columnMap
* @param dbMapping
* @param map
* @param typeMap
* @return
*/
private List<Column> getColumnsWhenPut(Map<String, String> columnMap,
MappingConfig.DbMapping dbMapping,
Map<String, Object> map,
Map<String, MappingConfig.ColumnItem> typeMap) {
List<Column> columnList = new ArrayList<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (dbMapping.getTargetPk().containsKey(entry.getKey())) {
// 这是个主键, 不需要再次处理
continue;
}
if (!dbMapping.getTargetColumns().containsKey(entry.getKey())) {
// 可能是没有配置的字段
continue;
}
// 非主键
String targetColumn = columnMap.get(entry.getKey());
Object value = entry.getValue();
if (value == null) {
// insert时空值过滤掉
continue;
}
TablestoreFieldType type = typeMap.get(entry.getKey()).getType();
ColumnValue columnValue = SyncUtil.getColumnValue(value, type);
columnList.add(new Column(targetColumn, columnValue));
}
return columnList;
}
/**
* 检查是否主键被修改
* @param old old中的数据
* @param targetPk 主键map
* @return
*/
private boolean isPrimaryKeyChange(Map<String, Object> old, Map<String, String> targetPk) {
for (String pkCol : targetPk.keySet()) {
if (old.containsKey(pkCol)) {
return true;
}
}
return false;
}
public void close() {
}
}
@@ -0,0 +1,211 @@
package com.alibaba.otter.canal.client.adapter.tablestore.support;
import com.alibaba.otter.canal.client.adapter.tablestore.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.tablestore.enums.TablestoreFieldType;
import com.alicloud.openservices.tablestore.model.ColumnValue;
import com.alicloud.openservices.tablestore.model.PrimaryKeyValue;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.util.Map;
public class SyncUtil {
private static final Logger logger = LoggerFactory.getLogger(SyncUtil.class);
public static PrimaryKeyValue getPrimaryKeyValue(Object value, TablestoreFieldType fieldType) {
Object tablestoreValue;
switch (fieldType) {
case STRING:
tablestoreValue = getTablestoreValue(value, fieldType);
return PrimaryKeyValue.fromString((String)tablestoreValue);
case INT:
tablestoreValue = getTablestoreValue(value, fieldType);
return PrimaryKeyValue.fromLong((long)tablestoreValue);
case BINARY:
tablestoreValue = getTablestoreValue(value, fieldType);
return PrimaryKeyValue.fromBinary((byte[])tablestoreValue);
default:
return PrimaryKeyValue.fromString(value.toString());
}
}
private static Object getTablestoreValue(Object value, TablestoreFieldType type) {
switch (type) {
case STRING:
if (value instanceof byte[]) {
return new String(((byte[])value));
}
return value.toString();
case INT:
if (value instanceof Number) {
return ((Number) value).longValue();
} else if (value instanceof Timestamp) {
return ((Timestamp) value).getTime();
} else if (value instanceof String) {
try {
return Long.parseLong((String) value);
} catch (NumberFormatException e) {
logger.error("Error while parse long:" + value.toString(), e);
throw e;
}
} else if (value instanceof Date) {
return ((Date) value).getTime();
} else if (value instanceof Time) {
return ((Time) value).getTime();
} else if (value instanceof java.util.Date) {
return ((java.util.Date) value).getTime();
} else if (value instanceof Boolean) {
Boolean ob = ((Boolean)value);
return ob ? 1L : 0L;
}
return null;
case BINARY:
if (value instanceof byte[]) {
return value;
} else if (value instanceof Blob) {
Blob item = ((Blob) value);
int length;
try {
length = (int) item.length();
return item.getBytes(1, length);
} catch (SQLException e) {
logger.error("Error while convert blob to binary, blob:" + item.toString(), e);
throw new RuntimeException(e);
}
} else if (value instanceof String) {
return ((String) value).getBytes(StandardCharsets.ISO_8859_1);
} else if (value instanceof Clob) {
return value.toString().getBytes(StandardCharsets.ISO_8859_1);
}
return null;
case BOOL:
if (value instanceof Boolean) {
return value;
} else if (value instanceof String) {
return !value.equals("0");
} else if (value instanceof Number) {
return ((Number) value).intValue() != 0;
}
return null;
case DOUBLE:
if (value instanceof Number) {
return ((Number)value).doubleValue();
} else if (value instanceof String) {
try {
return Double.parseDouble((String) value);
} catch (NumberFormatException e) {
logger.error("Error while parse double:" + value.toString(), e);
throw e;
}
}
return null;
default:
return value;
}
}
/**
* 解析配置的字段映射类型
* @param type
* @return
*/
public static TablestoreFieldType getTablestoreType(String type) {
if (type != null) {
if (type.equalsIgnoreCase("string")) {
return TablestoreFieldType.STRING;
} else if (type.equalsIgnoreCase("int") || type.equalsIgnoreCase("integer")) {
return TablestoreFieldType.INT;
} else if (type.equalsIgnoreCase("bool") || type.equalsIgnoreCase("boolean")) {
return TablestoreFieldType.BOOL;
} else if (type.equalsIgnoreCase("binary")) {
return TablestoreFieldType.BINARY;
} else if (type.equalsIgnoreCase("double") || type.equalsIgnoreCase("float") || type.equalsIgnoreCase("decimal")) {
return TablestoreFieldType.DOUBLE;
} else {
return null;
}
} else {
return null;
}
}
public static ColumnValue getColumnValue(Object value, TablestoreFieldType fieldType) {
Object tablestoreValue;
switch (fieldType) {
case STRING:
tablestoreValue = getTablestoreValue(value, fieldType);
return ColumnValue.fromString((String)tablestoreValue);
case INT:
tablestoreValue = getTablestoreValue(value, fieldType);
return ColumnValue.fromLong((long)tablestoreValue);
case BINARY:
tablestoreValue = getTablestoreValue(value, fieldType);
return ColumnValue.fromBinary((byte[])tablestoreValue);
case DOUBLE:
tablestoreValue = getTablestoreValue(value, fieldType);
return ColumnValue.fromDouble((double)tablestoreValue);
case BOOL:
tablestoreValue = getTablestoreValue(value, fieldType);
return ColumnValue.fromBoolean((boolean)tablestoreValue);
default:
return ColumnValue.fromString(value.toString());
}
}
public static Map<String, MappingConfig.ColumnItem> getTypeMap(MappingConfig config) {
return config.getDbMapping().getColumnItems();
}
public static TablestoreFieldType getDefaultTablestoreType(int sqlType) {
switch (sqlType) {
case Types.BIT:
case Types.BOOLEAN:
return TablestoreFieldType.BOOL;
case Types.BINARY:
case Types.VARBINARY:
case Types.LONGVARBINARY:
case Types.BLOB:
return TablestoreFieldType.BINARY;
case Types.TINYINT:
case Types.SMALLINT:
case Types.INTEGER:
case Types.BIGINT:
return TablestoreFieldType.INT;
case Types.DECIMAL:
case Types.NUMERIC:
case Types.REAL:
case Types.FLOAT:
case Types.DOUBLE:
return TablestoreFieldType.DOUBLE;
case Types.CHAR:
case Types.NCHAR:
case Types.VARCHAR:
case Types.LONGVARCHAR:
case Types.CLOB:
case Types.DATE:
case Types.TIME:
case Types.TIMESTAMP:
return TablestoreFieldType.STRING;
default:
return TablestoreFieldType.STRING;
}
}
public static String getDbTableName(String db,String table) {
String result = "";
if (StringUtils.isNotEmpty(db)) {
result += ("`" + db + "`.");
}
result += ("`" + table + "`");
return result;
}
}
@@ -0,0 +1 @@
tablestore=com.alibaba.otter.canal.client.adapter.tablestore.TablestoreAdapter
@@ -0,0 +1,21 @@
dataSourceKey: defaultDS
destination: test_ots
groupId: g1
outerAdapterKey: mysql1
threads: 1
updateChangeColumns: false
dbMapping:
database: test_ots
table: test
targetTable: canal_target
targetPk:
oId: oId
targetColumns:
oId:
c_id:
p_price:
p_count:
etlCondition:
commitBatch: 1 # 批量提交的大小
+12 -5
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.alibaba.otter</groupId>
@@ -50,10 +50,6 @@
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
@@ -132,6 +128,17 @@
<version>1.1.1</version>
<scope>provided</scope>
</dependency>
<!-- Pulsar -->
<dependency>
<groupId>org.apache.pulsar</groupId>
<artifactId>pulsar-client</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.pulsar</groupId>
<artifactId>pulsar-client-admin</artifactId>
<scope>provided</scope>
</dependency>
<!-- junit -->
<dependency>
@@ -39,6 +39,7 @@ public class ClusterCanalConnector implements CanalConnector {
this.accessStrategy = accessStrategy;
}
@Override
public void connect() throws CanalClientException {
while (currentConnector == null) {
int times = 0;
@@ -85,10 +86,12 @@ public class ClusterCanalConnector implements CanalConnector {
}
}
@Override
public boolean checkValid() {
return currentConnector != null && currentConnector.checkValid();
}
@Override
public void disconnect() throws CanalClientException {
if (currentConnector != null) {
currentConnector.disconnect();
@@ -96,10 +99,12 @@ public class ClusterCanalConnector implements CanalConnector {
}
}
@Override
public void subscribe() throws CanalClientException {
subscribe(""); // 传递空字符即可
}
@Override
public void subscribe(String filter) throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -125,6 +130,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to subscribe after " + times + " times retry.");
}
@Override
public void unsubscribe() throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -142,6 +148,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to unsubscribe after " + times + " times retry.");
}
@Override
public Message get(int batchSize) throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -159,6 +166,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to fetch the data after " + times + " times retry");
}
@Override
public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -176,6 +184,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to fetch the data after " + times + " times retry");
}
@Override
public Message getWithoutAck(int batchSize) throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -193,6 +202,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to fetch the data after " + times + " times retry");
}
@Override
public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -210,6 +220,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to fetch the data after " + times + " times retry");
}
@Override
public void rollback(long batchId) throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -227,6 +238,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to rollback after " + times + " times retry");
}
@Override
public void rollback() throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -245,6 +257,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to rollback after " + times + " times retry");
}
@Override
public void ack(long batchId) throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -63,6 +63,7 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
return nextNode();
}
@Override
public SocketAddress nextNode() {
if (runningAddress != null) {// 如果服务已经启动,直接选择当前正在工作的节点
return runningAddress;
@@ -81,7 +82,7 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
for (String address : currentChilds) {
String[] strs = StringUtils.split(address, ":");
if (strs != null && strs.length == 2) {
addresses.add(new InetSocketAddress(strs[0], Integer.valueOf(strs[1])));
addresses.add(new InetSocketAddress(strs[0], Integer.parseInt(strs[1])));
}
}
@@ -98,7 +99,7 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
ServerRunningData runningData = JsonUtils.unmarshalFromByte((byte[]) data, ServerRunningData.class);
String[] strs = StringUtils.split(runningData.getAddress(), ':');
if (strs.length == 2) {
runningAddress = new InetSocketAddress(strs[0], Integer.valueOf(strs[1]));
runningAddress = new InetSocketAddress(strs[0], Integer.parseInt(strs[1]));
}
}
@@ -98,6 +98,7 @@ public class SimpleCanalConnector implements CanalConnector {
this.clientIdentity = new ClientIdentity(destination, (short) 1001);
}
@Override
public void connect() throws CanalClientException {
if (connected) {
return;
@@ -124,6 +125,7 @@ public class SimpleCanalConnector implements CanalConnector {
connected = true;
}
@Override
public void disconnect() throws CanalClientException {
if (rollbackOnDisConnect && channel.isConnected()) {
rollback();
@@ -222,10 +224,12 @@ public class SimpleCanalConnector implements CanalConnector {
}
}
@Override
public void subscribe() throws CanalClientException {
subscribe(""); // 传递空字符即可
}
@Override
public void subscribe(String filter) throws CanalClientException {
waitClientRunning();
if (!running) {
@@ -255,6 +259,7 @@ public class SimpleCanalConnector implements CanalConnector {
}
}
@Override
public void unsubscribe() throws CanalClientException {
waitClientRunning();
if (!running) {
@@ -281,20 +286,24 @@ public class SimpleCanalConnector implements CanalConnector {
}
}
@Override
public Message get(int batchSize) throws CanalClientException {
return get(batchSize, null, null);
}
@Override
public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
Message message = getWithoutAck(batchSize, timeout, unit);
ack(message.getId());
return message;
}
@Override
public Message getWithoutAck(int batchSize) throws CanalClientException {
return getWithoutAck(batchSize, null, null);
}
@Override
public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
waitClientRunning();
if (!running) {
@@ -331,6 +340,7 @@ public class SimpleCanalConnector implements CanalConnector {
return CanalMessageDeserializer.deserializer(data, lazyParseEntry);
}
@Override
public void ack(long batchId) throws CanalClientException {
waitClientRunning();
if (!running) {
@@ -352,6 +362,7 @@ public class SimpleCanalConnector implements CanalConnector {
}
}
@Override
public void rollback(long batchId) throws CanalClientException {
waitClientRunning();
ClientRollback ca = ClientRollback.newBuilder()
@@ -370,9 +381,10 @@ public class SimpleCanalConnector implements CanalConnector {
}
}
@Override
public void rollback() throws CanalClientException {
waitClientRunning();
rollback(0);// 0代笔未设置
rollback(0); // 0 代表未设置
}
// ==================== helper method ====================
@@ -24,6 +24,7 @@ public class SimpleNodeAccessStrategy implements CanalNodeAccessStrategy {
this.nodes.addAll(nodes);
}
@Override
public SocketAddress nextNode() {
try {
return nodes.get(index);
@@ -10,7 +10,7 @@ import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.client.CanalMQConnector;
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;
import com.alibaba.otter.canal.protocol.FlatMessage;
@@ -1,12 +1,11 @@
package com.alibaba.otter.canal.client.kafka;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.client.kafka.protocol.KafkaFlatMessage;
import com.alibaba.otter.canal.client.kafka.protocol.KafkaMessage;
import com.alibaba.otter.canal.protocol.FlatMessage;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
@@ -43,7 +42,7 @@ public class KafkaOffsetCanalConnector extends KafkaCanalConnector {
public List<KafkaMessage> getListWithoutAck(Long timeout, TimeUnit unit, long offset) throws CanalClientException {
waitClientRunning();
if (!running) {
return Lists.newArrayList();
return new ArrayList<>();
}
if (offset > -1) {
@@ -61,7 +60,7 @@ public class KafkaOffsetCanalConnector extends KafkaCanalConnector {
}
return messages;
}
return Lists.newArrayList();
return new ArrayList<>();
}
/**
@@ -76,7 +75,7 @@ public class KafkaOffsetCanalConnector extends KafkaCanalConnector {
public List<KafkaFlatMessage> getFlatListWithoutAck(Long timeout, TimeUnit unit, long offset) throws CanalClientException {
waitClientRunning();
if (!running) {
return Lists.newArrayList();
return new ArrayList<>();
}
if (offset > -1) {
@@ -96,7 +95,7 @@ public class KafkaOffsetCanalConnector extends KafkaCanalConnector {
return flatMessages;
}
return Lists.newArrayList();
return new ArrayList<>();
}
/**
@@ -0,0 +1,458 @@
package com.alibaba.otter.canal.client.pulsarmq;
import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.client.CanalMQConnector;
import com.alibaba.otter.canal.client.CanalMessageDeserializer;
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;
import com.alibaba.otter.canal.common.utils.MQUtil;
import com.alibaba.otter.canal.protocol.FlatMessage;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.apache.pulsar.client.api.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* PulsarMQ的连接
*
* <pre>
* 注意点:
* 1. 相比于canal {@linkplain SimpleCanalConnector}, 这里get和ack操作不能有并发, 必须是一个线程执行get后,内存里执行完毕ack后再取下一个get
* </pre>
* todo 重复消费的概率相当高。一次批处理中,只要有一个消息处理失败,则该批次全部重试
*
* @since 1.1.1
*/
public class PulsarMQCanalConnector implements CanalMQConnector {
private static final Logger logger = LoggerFactory.getLogger(PulsarMQCanalConnector.class);
private volatile Messages<byte[]> lastGetBatchMessage;
/**
* 连接pulsar客户端
*/
private PulsarClient pulsarClient;
/**
* 消费者
*/
private Consumer<byte[]> consumer;
/**
* 是否扁平化Canal消息内容
*/
private boolean isFlatMessage = false;
/**
* 主题名称
*/
private String topic;
/**
* 环境连接URL
*/
private String serviceUrl;
/**
* 角色认证token
*/
private String roleToken;
/**
* 订阅客户端名称
*/
private String subscriptName;
/**
* 每次批量获取数据的最大条目数,默认30
*/
private int batchSize = 30;
/**
* 与{@code batchSize}一起决定批量获取的数据大小
* 当:
* <p>
* 1. {@code batchSize} 条消息未消费时<br/>
* 2. 距上一次批量消费时间达到{@code batchTimeoutSeconds}秒时
* </p>
* 任一条件满足,即执行批量消费
*/
private int getBatchTimeoutSeconds = 30;
/**
* 批量处理消息时,一次批量处理的超时时间秒数
* <p>
* 该时间应该根据{@code batchSize}和{@code batchTimeoutSeconds}合理设置
* </p>
*/
private int batchProcessTimeoutSeconds = 60;
/**
* 消费失败后的重试秒数,默认60秒
*/
private int redeliveryDelaySeconds = 60;
/**
* 当客户端接收到消息,30秒还没有返回ack给服务端时,ack超时,会重新消费该消息
*/
private int ackTimeoutSeconds = 30;
/**
* 是否开启消息失败重试功能,默认开启
*/
private boolean isRetry = true;
/**
* <p>
* true重试(-RETRY)和死信队列(-DLQ)后缀为大写,有些地方创建的为小写,需确保正确
* </p>
*/
private boolean isRetryDLQUpperCase = false;
/**
* 最大重试次数
*/
private int maxRedeliveryCount = 128;
/**
* 连接标识位,在连接或关闭连接后改变值
*/
private boolean connected = false;
/**
* 除必要参数外,其他参数使用默认值
* <p>
* 由于pulsar会根据subscriptName来区分消费实例,并且已经分配的指定实例的消息会固定到该实例的retry(重试)和dlq(死信)队列中,
* 所以subscriptName必传,且必须跟之前的一致,否则会导致之前消费失败的消息不会重消费。
* </p>
*
* @param isFlatMessage true使用扁平消息
* @param serviceUrl pulsar服务连接地址,通常为:pulsar:host:ip或http://host:ip
* @param roleToken 有对应topic的消费者权限的角色token
* @param topic 订阅主题
* @param subscriptName 订阅和客户端名称,同一个订阅名视为同一个消费实例
* @date 2021/9/18 08:54
* @author chad
* @since 1 by chad at 2021/9/18 完善
*/
public PulsarMQCanalConnector(boolean isFlatMessage, String serviceUrl, String roleToken, String topic
, String subscriptName) {
this.isFlatMessage = isFlatMessage;
this.serviceUrl = serviceUrl;
this.roleToken = roleToken;
this.topic = topic;
this.subscriptName = subscriptName;
if (StringUtils.isEmpty(this.subscriptName)) {
throw new RuntimeException("Pulsar Consumer subscriptName required");
}
}
/**
* 完全自定义的消费实例参数
*
* @date 2021/9/18 10:20
* @author chad
* @since 1 by chad at 2021/9/18 完善
*/
public PulsarMQCanalConnector(boolean isFlatMessage, String serviceUrl, String roleToken, String topic
, String subscriptName, int batchSize, int getBatchTimeoutSeconds, int batchProcessTimeoutSeconds
, int redeliveryDelaySeconds, int ackTimeoutSeconds, boolean isRetry, boolean isRetryDLQUpperCase
, int maxRedeliveryCount) {
this.isFlatMessage = isFlatMessage;
this.serviceUrl = serviceUrl;
this.roleToken = roleToken;
this.topic = topic;
this.subscriptName = subscriptName;
if (StringUtils.isEmpty(this.subscriptName)) {
throw new RuntimeException("Pulsar Consumer subscriptName required");
}
this.batchSize = batchSize;
this.getBatchTimeoutSeconds = getBatchTimeoutSeconds;
this.batchProcessTimeoutSeconds = batchProcessTimeoutSeconds;
this.redeliveryDelaySeconds = redeliveryDelaySeconds;
this.ackTimeoutSeconds = ackTimeoutSeconds;
this.isRetry = isRetry;
this.isRetryDLQUpperCase = isRetryDLQUpperCase;
this.maxRedeliveryCount = maxRedeliveryCount;
}
@Override
public void connect() throws CanalClientException {
// 连接创建客户端
try {
pulsarClient = PulsarClient.builder()
.serviceUrl(serviceUrl)
.authentication(AuthenticationFactory.token(roleToken))
.build();
} catch (PulsarClientException e) {
throw new RuntimeException(e);
}
}
@Override
public void disconnect() throws CanalClientException {
try {
if (null != this.consumer && this.consumer.isConnected()) {
this.consumer.close();
}
} catch (PulsarClientException e) {
logger.error("close pulsar consumer error", e);
}
try {
if (null != this.pulsarClient) {
this.pulsarClient.close();
}
} catch (PulsarClientException e) {
logger.error("close pulsar client error", e);
}
this.connected = false;
}
@Override
public boolean checkValid() throws CanalClientException {
return connected;
}
@Override
public synchronized void subscribe(String filter) throws CanalClientException {
if (connected) {
return;
}
ConsumerBuilder<byte[]> builder = pulsarClient.newConsumer();
if (MQUtil.isPatternTopic(this.topic)) {
// 正则
builder.topicsPattern(this.topic);
} else {// 多个topic
builder.topic(this.topic);
}
// 为保证消息的有序性,仅支持单消费实例模式
// 灾备模式,一个分区只能有一个消费者,如果当前消费者不可用,自动切换到其他消费者
builder.subscriptionType(SubscriptionType.Failover);
builder
// 调用consumer.negativeAcknowledge(message) (即nack)来表示消费失败的消息
// 在指定的时间进行重新消费,默认是1分钟。
.negativeAckRedeliveryDelay(this.redeliveryDelaySeconds, TimeUnit.SECONDS)
.subscriptionName(this.subscriptName)
;
if (this.isRetry) {
DeadLetterPolicy.DeadLetterPolicyBuilder dlqBuilder = DeadLetterPolicy.builder()
// 最大重试次数
.maxRedeliverCount(this.maxRedeliveryCount);
// 指定重试队列,不是多个或通配符topic才能判断重试队列
if (!MQUtil.isPatternTag(this.topic)) {
String retryTopic = this.topic + (this.isRetryDLQUpperCase ? "-RETRY" : "-retry");
dlqBuilder.retryLetterTopic(retryTopic);
String dlqTopic = this.topic + (this.isRetryDLQUpperCase ? "-DLQ" : "-dlq");
dlqBuilder.deadLetterTopic(dlqTopic);
}
//默认关闭,如果需要重试则开启
builder.enableRetry(true)
.deadLetterPolicy(dlqBuilder.build());
}
// ack超时
builder.ackTimeout(this.ackTimeoutSeconds, TimeUnit.SECONDS);
// pulsar批量获取消息设置
builder.batchReceivePolicy(new BatchReceivePolicy.Builder()
.maxNumMessages(this.batchSize)
.timeout(this.getBatchTimeoutSeconds, TimeUnit.SECONDS)
.build());
try {
this.consumer = builder.subscribe();
} catch (PulsarClientException e) {
throw new RuntimeException(e);
}
connected = true;
}
@Override
public void subscribe() throws CanalClientException {
this.subscribe(null);
}
@Override
public void unsubscribe() throws CanalClientException {
try {
if (null != this.consumer) {
this.consumer.unsubscribe();
}
} catch (PulsarClientException e) {
throw new CanalClientException(e.getMessage(), e);
}
}
/**
* 不关注业务执行结果,只要收到消息即认识消费成功,自动ack
*
* @param timeout 阻塞获取消息的超时时间
* @param unit 时间单位
* @return java.util.List<com.alibaba.otter.canal.protocol.Message>
* @date 2021/9/13 22:24
* @author chad
* @since 1 by chad at 2021/9/13 添加注释
*/
@Override
public List<Message> getList(Long timeout, TimeUnit unit) throws CanalClientException {
List<Message> messages = getListWithoutAck(timeout, unit);
if (messages != null && !messages.isEmpty()) {
ack();
}
return messages;
}
/**
* 关心业务执行结果,业务侧根据执行结果调用 {@link PulsarMQCanalConnector#ack()}或{@link PulsarMQCanalConnector#rollback()}
* <p>
* 本方法示支持多线程,在MQ保障顺序的前提下,也无法提供单Topic多线程
* </p>
*
* @param timeout 阻塞获取消息的超时时间
* @param unit 时间单位
* @return java.util.List<com.alibaba.otter.canal.protocol.Message>
* @date 2021/9/13 22:26
* @author chad
* @since 1 by chad at 2021/9/13 添加注释
*/
@Override
public List<Message> getListWithoutAck(Long timeout, TimeUnit unit) throws CanalClientException {
return getListWithoutAck();
}
@Override
public List<FlatMessage> getFlatList(Long timeout, TimeUnit unit) throws CanalClientException {
List<FlatMessage> messages = getFlatListWithoutAck(timeout, unit);
if (messages != null && !messages.isEmpty()) {
ack();
}
return messages;
}
@Override
public List<FlatMessage> getFlatListWithoutAck(Long timeout, TimeUnit unit) throws CanalClientException {
return getListWithoutAck();
}
/**
* 获取泛型数据,供其他方法调用
* <p>
* 不支持多线程调用
* </p>
*
* @return java.util.List<T>
* @date 2021/9/14 15:20
* @author chad
* @since 1 by chad at 2021/9/14 供{@link PulsarMQCanalConnector#getListWithoutAck(Long, TimeUnit)}
* 和{@link PulsarMQCanalConnector#getFlatListWithoutAck(Long, TimeUnit)}调用
*/
private <T> List<T> getListWithoutAck() {
if (null != this.lastGetBatchMessage) {
throw new CanalClientException("mq get/ack not support concurrent & async ack");
}
List messageList = Lists.newArrayList();
try {
this.lastGetBatchMessage = consumer.batchReceive();
if (null == this.lastGetBatchMessage || this.lastGetBatchMessage.size() < 1) {
this.lastGetBatchMessage = null;
return messageList;
}
} catch (PulsarClientException e) {
logger.error("Receiver Pulsar MQ message error", e);
throw new CanalClientException(e);
}
for (org.apache.pulsar.client.api.Message<byte[]> msgExt : this.lastGetBatchMessage) {
byte[] data = msgExt.getData();
if (data == null) {
logger.warn("Received message data is null");
continue;
}
try {
if (isFlatMessage) {
FlatMessage flatMessage = JSON.parseObject(data, FlatMessage.class);
messageList.add(flatMessage);
} else {
Message message = CanalMessageDeserializer.deserializer(data);
messageList.add(message);
}
} catch (Exception ex) {
logger.error("Add message error", ex);
throw new CanalClientException(ex);
}
}
return messageList;
}
/**
* 当业务侧执行成功时,需要手动执行消息的ack操作
*
* @return void
* @date 2021/9/13 22:27
* @author chad
* @since 1 by chad at 2021/9/13 添加注释
*/
@Override
public void ack() throws CanalClientException {
// 为什么要一个批次要么全部成功要么全部失败
try {
if (this.lastGetBatchMessage != null) {
this.consumer.acknowledge(this.lastGetBatchMessage);
}
} catch (Throwable e) {
if (this.lastGetBatchMessage != null) {
this.consumer.negativeAcknowledge(this.lastGetBatchMessage);
}
} finally {
this.lastGetBatchMessage = null;
}
}
/**
* 当业务侧执行失败时,需要手动执行消息的rollback操作,从而让消息重新消费
*
* @return void
* @date 2021/9/13 22:28
* @author chad
* @since 1 by chad at 2021/9/13 添加注释
*/
@Override
public void rollback() throws CanalClientException {
try {
if (this.lastGetBatchMessage != null) {
this.consumer.negativeAcknowledge(this.lastGetBatchMessage);
}
} finally {
this.lastGetBatchMessage = null;
}
}
@Override
public Message get(int batchSize) throws CanalClientException {
throw new CanalClientException("mq not support this method");
}
@Override
public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
throw new CanalClientException("mq not support this method");
}
@Override
public Message getWithoutAck(int batchSize) throws CanalClientException {
throw new CanalClientException("mq not support this method");
}
@Override
public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
throw new CanalClientException("mq not support this method");
}
@Override
public void ack(long batchId) throws CanalClientException {
throw new CanalClientException("mq not support this method");
}
@Override
public void rollback(long batchId) throws CanalClientException {
throw new CanalClientException("mq not support this method");
}
}
@@ -1,6 +1,6 @@
package com.alibaba.otter.canal.client.rabbitmq;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.client.CanalMQConnector;
import com.alibaba.otter.canal.client.CanalMessageDeserializer;
import com.alibaba.otter.canal.client.ConsumerBatchMessage;
@@ -13,6 +13,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@@ -239,7 +240,7 @@ public class RabbitMQCanalConnector implements CanalMQConnector {
if (logger.isDebugEnabled()) {
logger.debug("Get Message: {}", new String(messageData));
}
List messageList = Lists.newArrayList();
List messageList = new ArrayList<>();
if (!flatMessage) {
Message message = CanalMessageDeserializer.deserializer(messageData);
messageList.add(message);
@@ -1,5 +1,6 @@
package com.alibaba.otter.canal.client.rocketmq;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@@ -21,7 +22,7 @@ import org.apache.rocketmq.remoting.RPCHook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.client.CanalMQConnector;
import com.alibaba.otter.canal.client.CanalMessageDeserializer;
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;
@@ -165,7 +166,7 @@ public class RocketMQCanalConnector implements CanalMQConnector {
if (logger.isDebugEnabled()) {
logger.debug("Get Message: {}", messageExts);
}
List messageList = Lists.newArrayList();
List messageList = new ArrayList<>();
for (MessageExt messageExt : messageExts) {
byte[] data = messageExt.getBody();
if (data != null) {
+3 -3
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId>
<version>1.1.5</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>canal.common</artifactId>
@@ -39,8 +39,8 @@
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
@@ -1,21 +1,15 @@
package com.alibaba.otter.canal.common.utils;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.ObjectSerializer;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializeWriter;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson2.*;
import com.alibaba.fastjson2.filter.Filter;
import com.alibaba.fastjson2.filter.PropertyFilter;
import com.alibaba.fastjson2.writer.ObjectWriter;
/**
* 字节处理相关工具类
@@ -23,46 +17,41 @@ import com.alibaba.fastjson.serializer.SerializerFeature;
* @author jianghang
*/
public class JsonUtils {
static {
SerializeConfig.getGlobalInstance().put(InetAddress.class, InetAddressSerializer.instance);
SerializeConfig.getGlobalInstance().put(Inet4Address.class, InetAddressSerializer.instance);
SerializeConfig.getGlobalInstance().put(Inet6Address.class, InetAddressSerializer.instance);
// ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
ParserConfig.getGlobalInstance().addAccept("com.alibaba.otter.");
ParserConfig.getGlobalInstance().addAccept("com.taobao.tddl.dbsync.");
}
static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(
"com.alibaba.otter.",
"com.taobao.tddl.dbsync."
);
public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) {
return (T) JSON.parseObject(bytes, targetClass);// 默认为UTF-8
return (T) JSON.parseObject(bytes, targetClass, AUTO_TYPE_FILTER);// 默认为UTF-8
}
public static <T> T unmarshalFromByte(byte[] bytes, TypeReference<T> type) {
return (T) JSON.parseObject(bytes, type.getType());
return (T) JSON.parseObject(bytes, type.getType(), AUTO_TYPE_FILTER);
}
public static byte[] marshalToByte(Object obj) {
return JSON.toJSONBytes(obj); // 默认为UTF-8
}
public static byte[] marshalToByte(Object obj, SerializerFeature... features) {
public static byte[] marshalToByte(Object obj, JSONWriter.Feature... features) {
return JSON.toJSONBytes(obj, features); // 默认为UTF-8
}
public static <T> T unmarshalFromString(String json, Class<T> targetClass) {
return (T) JSON.parseObject(json, targetClass);// 默认为UTF-8
return (T) JSON.parseObject(json, targetClass, AUTO_TYPE_FILTER);// 默认为UTF-8
}
public static <T> T unmarshalFromString(String json, TypeReference<T> type) {
return (T) JSON.parseObject(json, type);// 默认为UTF-8
return (T) JSON.parseObject(json, type.getType(), AUTO_TYPE_FILTER);// 默认为UTF-8
}
public static String marshalToString(Object obj) {
return JSON.toJSONString(obj); // 默认为UTF-8
}
public static String marshalToString(Object obj, SerializerFeature... features) {
public static String marshalToString(Object obj, JSONWriter.Feature... features) {
return JSON.toJSONString(obj, features); // 默认为UTF-8
}
@@ -71,29 +60,29 @@ public class JsonUtils {
*/
public static String marshalToString(Object obj, String... fliterFields) {
final List<String> propertyFliters = Arrays.asList(fliterFields);
try (SerializeWriter out = new SerializeWriter()) {
JSONSerializer serializer = new JSONSerializer(out);
serializer.getPropertyFilters().add((source, name, value) -> !propertyFliters.contains(name));
serializer.write(obj);
return out.toString();
}
return JSON.toJSONString(obj, new PropertyFilter() {
@Override
public boolean apply(Object object, String name, Object value) {
return !propertyFliters.contains(name);
}
});
}
public static class InetAddressSerializer implements ObjectSerializer {
public static class InetAddressWriter implements ObjectWriter {
public static InetAddressSerializer instance = new InetAddressSerializer();
public static InetAddressWriter instance = new InetAddressWriter();
@Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features)
throws IOException {
public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) {
if (object == null) {
serializer.writeNull();
jsonWriter.writeNull();
return;
}
InetAddress address = (InetAddress) object;
// 优先使用name
serializer.write(address.getHostName());
jsonWriter.writeString(address.getHostName());
}
}
}

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