Compare commits

..

72 Commits

Author SHA1 Message Date
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
364102729
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
Chad2li
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 Schneider
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
spark
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
ChenZhangg
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
agapple
64862503ba [maven-release-plugin] prepare release canal-1.1.5 2021-04-19 15:48:09 +08:00
agapple
f9e0dc7daa fixed testcase 2021-04-19 15:15:10 +08:00
agapple
0c399509a9 upgrade druid 1.2.6 2021-04-19 12:57:21 +08:00
agapple
a09cd9adb5 fixed issue #3110 , json escapse 2021-04-19 11:28:45 +08:00
agapple
1369418c6a fixed code format & merge 2021-04-19 10:55:43 +08:00
995586041
75174a63af 增加rabbitmq消息持久化deliveryMode配置 (#3300) 2021-04-19 10:29:23 +08:00
zoemak
17f4ee13e7 自动注册时可自定义node server名称,canal.properties#canal.admin.register.name,为空则使用canal.register.ip (#3459) 2021-04-19 10:18:01 +08:00
Fei Yang
f0417502f8 fix: unit_present typo (#3461)
https://github.com/alibaba/canal/issues/3434
2021-04-19 10:16:17 +08:00
renyansongno1
a7720b51e4 支持rocketmq的消息按照tag去发送 (#3438)
* 支持rocketmq的消息按照tag去发送
并对应修改了配置文件

* 将tag的设计转移到Rocketmq的单独配置项
2021-04-19 10:14:27 +08:00
suman-lee
21904ef1dc 基于明确的I/U/D是否过滤 (#3452)
* dmls filter

* dmls filters

* 基于明确的I/U/D是否过滤

* 基于明确的I/U/D是否过滤
2021-04-19 10:10:54 +08:00
agapple
94425223bd fixed issue #3454 , concurrent env bugfix 2021-04-17 10:57:25 +08:00
agapple
7857df9701 add sql parser testcase 2021-04-17 10:49:17 +08:00
agapple
64ea4ff222 fixed issue #3267 , fixed rockemq send null message 2021-04-17 10:25:49 +08:00
agapple
85df89f777 fixed issue #3377 , escapse schema name 2021-04-16 23:11:41 +08:00
AlphaKitty
a0f8e182ec 修复当host中存在冒号(比如https:)抛UnknownHostException问题 (#3223)
* 修复当host中存在冒号(比如https:)抛UnknownHostException问题

* 更优雅地解析url
2021-04-16 22:11:21 +08:00
温绍锦
33721d0394 update druid version (#3146)
* replace fastsql with druid

* update druid version
2021-04-16 22:09:40 +08:00
Treeyh
91bddfeed6 修复接入阿里云RocketMq no route info for this topic 问题 (#3220)
Co-authored-by: hai.yu <hai.yu@snowballtech.com>
2021-04-16 22:08:28 +08:00
16075140
25feab4af8 support rabbitmq port by config (#3236) 2021-04-16 22:07:12 +08:00
binbin
f66baeabf1 修复最近位置被remove掉,导致重复消息的问题 (#3276) 2021-04-16 22:03:51 +08:00
agapple
b1dc3821ea fixed config url auth 2021-04-16 21:52:23 +08:00
Jump-Boy
f5f79104eb fix centos 6.10 yum bug and update jdk8.rpm download url (#3398) 2021-04-16 21:41:55 +08:00
ltamber
e8d138d3bd fix: uuidset combine fix (#3432) 2021-04-16 21:37:55 +08:00
zoemak
32d9e58915 升级rocketmq到4.8.0,并且支持环境变量优先配置 (#3450) 2021-04-16 21:35:11 +08:00
许道杰
58448e559d fix: issue #2649 client-adapter启动时抛LinkageError异常、#3073 client-adapter通过启动参数传入canal.tcp.zookeeper.hosts为空值时抛异常 (#3074) 2021-04-16 21:25:14 +08:00
许道杰
fede3b3d1d fix #3126 将hbase-client依赖修改为hbase-shaded-client (#3127) 2021-04-16 21:24:44 +08:00
许道杰
5d8ec31bc4 fix #3119 (#3121) 2021-04-16 21:22:31 +08:00
WangCong
82b18ebe62 feat unsigned bug (#3104) 2021-04-16 21:17:43 +08:00
hupeng
66408782b0 process()方法中,在retry循环中处理成功应该break (#3080)
process()方法中,在retry循环中处理成功应该break
2021-04-16 21:14:43 +08:00
许道杰
846c8870ae fix: issue #3076 (#3077) 2021-04-16 21:12:12 +08:00
ljcoder
8cfb07f07e 数据库名称如何存在中划线"-"情况下,SQL执行异常 (#3228)
Co-authored-by: liguowang <liguowang@aerozhonghaun.com>
2020-12-31 11:33:01 +08:00
文哥
d741425895 升级guava 至 22.0 (#3184)
* fix issue:#561 增加一个外部参数控制,允许位点不存在时自动重置到当前位点

* Add MariaDB GTID support

* remove client pom bad ns

* upgrade guava version to 22.0
2020-11-20 13:47:26 +08:00
文哥
20add85ff6 统一Canal 依赖的Spring 版本,升级ibatis 至mybatis (#3182)
* fix issue:#561 增加一个外部参数控制,允许位点不存在时自动重置到当前位点

* Add MariaDB GTID support

* 1. fix maven central repository url access error.(Update to https)
2. upgrade spring version to 5.0.5.RELEASE
3. upgrade ibatis to mybatis

* fix unit test run error
2020-11-20 09:24:56 +08:00
agapple
a5c09c521e Update README.md 2020-11-02 12:54:54 +08:00
agapple
c94276b1ac Update README.md 2020-11-02 12:53:35 +08:00
agapple
272d575c78 Update README.md 2020-11-02 12:44:59 +08:00
温绍锦
693f8d501a replace fastsql with druid (#3132) 2020-10-31 13:19:03 +08:00
温绍锦
bb1b6b9862 Update README.md 2020-10-23 14:41:31 +08:00
温绍锦
8ed82caaef Update README.md 2020-09-29 16:18:23 +08:00
文哥
de1dd70609 1. Add MariaDB gtid support 2. fix issue:#561 增加一个外部参数控制,允许位点不存在时自动重置到当前位点 (#3025)
* fix issue:#561 增加一个外部参数控制,允许位点不存在时自动重置到当前位点

* Add MariaDB GTID support
2020-09-24 17:56:40 +08:00
zavakid
09429c241f = inner class MessageEventFactory to be static 2020-09-23 11:37:17 +08:00
zavakid
9a332940d7 = remove unused import 2020-09-23 11:34:45 +08:00
zavakid
362974b1c2 = code cleanup: remove unnessary boxing 2020-09-18 00:54:23 +08:00
zavakid
435db30b7b = performance improve
- using bulk operation instead of iteration if possible
  - terminate loop after condition is met
2020-09-18 00:51:53 +08:00
zavakid
e7de8289cd = code cleanup : modified inner class to 'static' if possible 2020-09-18 00:49:48 +08:00
zavakid
5065fea963 = fix BooleanMutex's inner Sync to static class and add test case 2020-09-18 00:44:25 +08:00
zavakid
6989eb3d2b + fix logging call placeholders number 2020-09-17 23:26:02 +08:00
zavakid
df24596092 = code cleanup
- for loop replaceable with enhanced 'for' loop
  - using diamond in generic type
  - collapse try catch exception
  - using try-with-resource
  - using  List#sort instead of Collections#sort
  - replaced lambda with method reference
  - using native compare method to compare numbers
  - using switch instead of if for in ESSyncUtil
2020-09-17 23:22:25 +08:00
zavakid
43c23ffb97 = remove unused improt 2020-09-17 22:40:06 +08:00
zavakid
e7658879bc = cold cleanup
* using lambda if possible (we are already building in Java8)
  * using diamond when new generic instance
  * remove used improt
2020-09-17 17:46:20 +08:00
zavakid
df60eda679 = code cleanup 2020-09-16 14:18:05 +08:00
328 changed files with 9409 additions and 2807 deletions

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)

View File

@@ -90,10 +90,13 @@ canal 作为 MySQL binlog 增量获取和解析工具,可将变更记录投递
- 参考文档: [Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart)
## 相关开源
## 相关开源&产品
- [canal 消费端开源项目: Otter](http://github.com/alibaba/otter)
- [阿里巴巴去 Oracle 数据迁移同步工具: yugong](http://github.com/alibaba/yugong)
- [阿里巴巴离线同步开源项目 DataX](https://github.com/alibaba/datax)
- [阿里巴巴数据库连接池开源项目 Druid](https://github.com/alibaba/druid)
- [阿里巴巴实时数据同步工具 DTS](https://www.aliyun.com/product/dts)
## 问题反馈
- 报告 issue: [github issues](https://github.com/alibaba/canal/issues)

View File

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

View File

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

View File

@@ -1,5 +1,13 @@
#!/bin/bash
sh stop.sh
case $(uname) in
Linux)
bin_abs_path=$(readlink -f $(dirname $0))
;;
*)
bin_abs_path=$(cd $(dirname $0) ||exit ; pwd)
;;
esac
sh startup.sh
sh "$bin_abs_path"/stop.sh
sh "$bin_abs_path"/startup.sh

View File

@@ -12,6 +12,6 @@ public class Threads {
pool,
0L,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(pool * 20),
new ArrayBlockingQueue<>(pool * 20),
DaemonThreadFactory.daemonThreadFactory);
}

View File

@@ -84,7 +84,9 @@ public class WebConfig implements WebMvcConfigurer {
}
})
.addPathPatterns("/api/**")
.excludePathPatterns("/api/**/config/**")
.excludePathPatterns("/api/**/config/server_polling")
.excludePathPatterns("/api/**/config/instances_polling")
.excludePathPatterns("/api/**/config/instance_polling/**")
.excludePathPatterns("/api/**/user/login")
.excludePathPatterns("/api/**/user/logout")
.excludePathPatterns("/api/**/user/info");

View File

@@ -108,9 +108,7 @@ public class SimpleAdminConnector implements AdminConnector {
}
connected = true;
} catch (IOException e) {
throw new ServiceException(e);
} catch (NoSuchAlgorithmException e) {
} catch (IOException | NoSuchAlgorithmException e) {
throw new ServiceException(e);
}
}

View File

@@ -46,14 +46,15 @@ public class PollingConfigController {
public BaseModel<CanalConfig> canalConfigPoll(@RequestHeader String user, @RequestHeader String passwd,
@RequestParam String ip, @RequestParam Integer port,
@RequestParam String md5, @RequestParam boolean register,
@RequestParam String cluster, @PathVariable String env) {
@RequestParam String cluster, @RequestParam String name,
@PathVariable String env) {
if (!auth(user, passwd)) {
throw new RuntimeException("auth :" + user + " is failed");
}
if (StringUtils.isEmpty(md5) && register) {
// do something
pollingConfigService.autoRegister(ip, port, cluster);
pollingConfigService.autoRegister(ip, port, cluster, StringUtils.trimToNull(name));
}
CanalConfig canalConfig = pollingConfigService.getChangedConfig(ip, port, md5);

View File

@@ -9,7 +9,7 @@ public class Pager<T> implements Serializable {
private static final long serialVersionUID = -986577815091763517L;
private Long count = 0L;
private List<T> items = new ArrayList<T>();
private List<T> items = new ArrayList<>();
private Integer page = 1;
private Integer size = 20;
private Long offset = 0L;

View File

@@ -5,7 +5,7 @@ import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
public interface PollingConfigService {
public boolean autoRegister(String ip, Integer adminPort, String cluster);
public boolean autoRegister(String ip, Integer adminPort, String cluster, String name);
CanalConfig getChangedConfig(String ip, Integer port, String md5);

View File

@@ -2,6 +2,7 @@ package com.alibaba.otter.canal.admin.service.impl;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
@@ -28,11 +29,11 @@ public class PollingConfigServiceImpl implements PollingConfigService {
@Autowired
CanalClusterService canalClusterService;
public boolean autoRegister(String ip, Integer adminPort, String cluster) {
public boolean autoRegister(String ip, Integer adminPort, String cluster, String name) {
NodeServer server = NodeServer.find.query().where().eq("ip", ip).eq("adminPort", adminPort).findOne();
if (server == null) {
server = new NodeServer();
server.setName(ip);
server.setName(Optional.ofNullable(name).orElse(ip));
server.setIp(ip);
server.setAdminPort(adminPort);
server.setTcpPort(adminPort + 1);

View File

@@ -16,6 +16,10 @@ canal.metrics.pull.port = 11112
canal.admin.port = 11110
canal.admin.user = admin
canal.admin.passwd = 4ACFE3202A5FF5CF467898FC58AAB1D615029441
# admin auto register
#canal.admin.register.auto = true
#canal.admin.register.cluster =
#canal.admin.register.name =
canal.zkServers =
# flush data to zk
@@ -60,6 +64,9 @@ canal.instance.filter.query.ddl = false
canal.instance.filter.table.error = false
canal.instance.filter.rows = false
canal.instance.filter.transaction.entry = false
canal.instance.filter.dml.insert = false
canal.instance.filter.dml.update = false
canal.instance.filter.dml.delete = false
# binlog format/image check
canal.instance.binlog.format = ROW,STATEMENT,MIXED
@@ -95,6 +102,9 @@ canal.conf.dir = ../conf
# auto scan instance dir add/remove and start/stop instance
canal.auto.scan = true
canal.auto.scan.interval = 5
# set this value to 'true' means that when binlog pos not found, skip to latest.
# WARN: pls keep 'false' in production env, or if you know what you want.
canal.auto.reset.latest.pos.mode = false
canal.instance.tsdb.spring.xml = classpath:spring/tsdb/h2-tsdb.xml
#canal.instance.tsdb.spring.xml = classpath:spring/tsdb/mysql-tsdb.xml
@@ -151,6 +161,7 @@ rocketmq.namespace =
rocketmq.namesrv.addr = 127.0.0.1:9876
rocketmq.retry.times.when.send.failed = 0
rocketmq.vip.channel.enabled = false
rocketmq.tag =
##################################################
######### RabbitMQ #############
@@ -159,4 +170,5 @@ rabbitmq.host =
rabbitmq.virtual.host =
rabbitmq.exchange =
rabbitmq.username =
rabbitmq.password =
rabbitmq.password =
rabbitmq.deliveryMode =

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.admin</artifactId>
<version>1.1.5-SNAPSHOT</version>
<version>1.1.6-SNAPSHOT</version>
<packaging>pom</packaging>
<name>canal admin module for otter ${project.version}</name>
<modules>

View File

@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5-SNAPSHOT</version>
<version>1.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -140,7 +140,7 @@ public class YmlConfigBinder {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(propertySource);
PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>(target);
PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<>(target);
factory.setPropertySources(propertySources);
factory.setIgnoreInvalidFields(true);
factory.setIgnoreUnknownFields(true);

View File

@@ -25,7 +25,7 @@ class DefaultPropertyNamePatternsMatcher implements PropertyNamePatternsMatcher
}
protected DefaultPropertyNamePatternsMatcher(char[] delimiters, boolean ignoreCase, String... names){
this(delimiters, ignoreCase, new HashSet<String>(Arrays.asList(names)));
this(delimiters, ignoreCase, new HashSet<>(Arrays.asList(names)));
}
DefaultPropertyNamePatternsMatcher(char[] delimiters, boolean ignoreCase, Set<String> names){
@@ -70,8 +70,8 @@ class DefaultPropertyNamePatternsMatcher implements PropertyNamePatternsMatcher
return false;
}
}
for (int i = 0; i < match.length; i++) {
if (match[i]) {
for (boolean b : match) {
if (b) {
return true;
}
}

View File

@@ -277,7 +277,7 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>, Applic
}
private Set<String> getNames(Iterable<String> prefixes) {
Set<String> names = new LinkedHashSet<String>();
Set<String> names = new LinkedHashSet<>();
if (this.target != null) {
PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(this.target.getClass());
for (PropertyDescriptor descriptor : descriptors) {
@@ -318,7 +318,7 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>, Applic
// We can filter properties to those starting with the target name, but
// we can't do a complete filter since we need to trigger the
// unknown fields check
Set<String> relaxedNames = new HashSet<String>();
Set<String> relaxedNames = new HashSet<>();
for (String relaxedTargetName : relaxedTargetNames) {
relaxedNames.add(relaxedTargetName);
}

View File

@@ -9,23 +9,9 @@ package com.alibaba.otter.canal.client.adapter.config.bind;
*/
interface PropertyNamePatternsMatcher {
PropertyNamePatternsMatcher ALL = new PropertyNamePatternsMatcher() {
PropertyNamePatternsMatcher ALL = propertyName -> true;
@Override
public boolean matches(String propertyName) {
return true;
}
};
PropertyNamePatternsMatcher NONE = new PropertyNamePatternsMatcher() {
@Override
public boolean matches(String propertyName) {
return false;
}
};
PropertyNamePatternsMatcher NONE = propertyName -> false;
/**
* Return {@code true} of the property name matches.

View File

@@ -37,9 +37,9 @@ public class PropertySourcesPropertyValues implements PropertyValues {
private final PropertyNamePatternsMatcher includes;
private final Map<String, PropertyValue> propertyValues = new LinkedHashMap<String, PropertyValue>();
private final Map<String, PropertyValue> propertyValues = new LinkedHashMap<>();
private final ConcurrentHashMap<String, PropertySource<?>> collectionOwners = new ConcurrentHashMap<String, PropertySource<?>>();
private final ConcurrentHashMap<String, PropertySource<?>> collectionOwners = new ConcurrentHashMap<>();
private final boolean resolvePlaceholders;

View File

@@ -32,7 +32,7 @@ public class RelaxedDataBinder extends DataBinder {
private static final Set<Class<?>> EXCLUDED_EDITORS;
static {
Set<Class<?>> excluded = new HashSet<Class<?>>();
Set<Class<?>> excluded = new HashSet<>();
excluded.add(FileEditor.class);
EXCLUDED_EDITORS = Collections.unmodifiableSet(excluded);
}
@@ -43,7 +43,7 @@ public class RelaxedDataBinder extends DataBinder {
private boolean ignoreNestedProperties;
private MultiValueMap<String, String> nameAliases = new LinkedMultiValueMap<String, String>();
private MultiValueMap<String, String> nameAliases = new LinkedMultiValueMap<>();
/**
* Create a new {@link RelaxedDataBinder} instance.
@@ -89,7 +89,7 @@ public class RelaxedDataBinder extends DataBinder {
* @param aliases a map of property name to aliases
*/
public void setNameAliases(Map<String, List<String>> aliases) {
this.nameAliases = new LinkedMultiValueMap<String, String>(aliases);
this.nameAliases = new LinkedMultiValueMap<>(aliases);
}
/**
@@ -129,8 +129,8 @@ public class RelaxedDataBinder extends DataBinder {
BeanWrapper wrapper = new BeanWrapperImpl(target);
wrapper.setConversionService(new RelaxedConversionService(getConversionService()));
wrapper.setAutoGrowNestedPaths(true);
List<PropertyValue> sortedValues = new ArrayList<PropertyValue>();
Set<String> modifiedNames = new HashSet<String>();
List<PropertyValue> sortedValues = new ArrayList<>();
Set<String> modifiedNames = new HashSet<>();
List<String> sortedNames = getSortedPropertyNames(propertyValues);
for (String name : sortedNames) {
PropertyValue propertyValue = propertyValues.getPropertyValue(name);
@@ -143,7 +143,7 @@ public class RelaxedDataBinder extends DataBinder {
}
private List<String> getSortedPropertyNames(MutablePropertyValues propertyValues) {
List<String> names = new LinkedList<String>();
List<String> names = new LinkedList<>();
for (PropertyValue propertyValue : propertyValues.getPropertyValueList()) {
names.add(propertyValue.getName());
}
@@ -161,7 +161,7 @@ public class RelaxedDataBinder extends DataBinder {
* @param names the names to sort
*/
private void sortPropertyNames(List<String> names) {
for (String name : new ArrayList<String>(names)) {
for (String name : new ArrayList<>(names)) {
int propertyIndex = names.indexOf(name);
RelaxedDataBinder.BeanPath path = new RelaxedDataBinder.BeanPath(name);
for (String prefix : path.prefixes()) {
@@ -319,7 +319,7 @@ public class RelaxedDataBinder extends DataBinder {
}
Object extend = new LinkedHashMap<String, Object>();
if (!elementDescriptor.isMap() && path.isArrayIndex(index)) {
extend = new ArrayList<Object>();
extend = new ArrayList<>();
}
wrapper.setPropertyValue(path.prefix(index + 1), extend);
}
@@ -347,7 +347,7 @@ public class RelaxedDataBinder extends DataBinder {
}
Object extend = new LinkedHashMap<String, Object>();
if (descriptor.isCollection()) {
extend = new ArrayList<Object>();
extend = new ArrayList<>();
}
if (descriptor.getType().equals(Object.class) && path.isLastNode(index)) {
extend = BLANK;
@@ -411,7 +411,7 @@ public class RelaxedDataBinder extends DataBinder {
if (aliases == null) {
return Collections.singleton(name);
}
List<String> nameAndAliases = new ArrayList<String>(aliases.size() + 1);
List<String> nameAndAliases = new ArrayList<>(aliases.size() + 1);
nameAndAliases.add(name);
nameAndAliases.addAll(aliases);
return nameAndAliases;
@@ -473,7 +473,7 @@ public class RelaxedDataBinder extends DataBinder {
}
public List<String> prefixes() {
List<String> prefixes = new ArrayList<String>();
List<String> prefixes = new ArrayList<>();
for (int index = 1; index < this.nodes.size(); index++) {
prefixes.add(prefix(index));
}
@@ -485,7 +485,7 @@ public class RelaxedDataBinder extends DataBinder {
}
private List<PathNode> splitPath(String path) {
List<PathNode> nodes = new ArrayList<PathNode>();
List<PathNode> nodes = new ArrayList<>();
String current = extractIndexedPaths(path, nodes);
for (String name : StringUtils.delimitedListToStringArray(current, ".")) {
if (StringUtils.hasText(name)) {
@@ -518,7 +518,7 @@ public class RelaxedDataBinder extends DataBinder {
}
public void collapseKeys(int index) {
List<PathNode> revised = new ArrayList<PathNode>();
List<PathNode> revised = new ArrayList<>();
for (int i = 0; i < index; i++) {
revised.add(this.nodes.get(i));
}
@@ -668,7 +668,7 @@ public class RelaxedDataBinder extends DataBinder {
private static final Set<String> BENIGN_PROPERTY_SOURCE_NAMES;
static {
Set<String> names = new HashSet<String>();
Set<String> names = new HashSet<>();
names.add(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
names.add(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
BENIGN_PROPERTY_SOURCE_NAMES = Collections.unmodifiableSet(names);

View File

@@ -24,7 +24,7 @@ public final class RelaxedNames implements Iterable<String> {
private final String name;
private final Set<String> values = new LinkedHashSet<String>();
private final Set<String> values = new LinkedHashSet<>();
/**
* Create a new {@link RelaxedNames} instance.

View File

@@ -22,7 +22,7 @@ import org.springframework.util.StringUtils;
*/
public class CompositePropertySource extends EnumerablePropertySource<Object> {
private final Set<PropertySource<?>> propertySources = new LinkedHashSet<PropertySource<?>>();
private final Set<PropertySource<?>> propertySources = new LinkedHashSet<>();
/**
* Create a new {@code CompositePropertySource}.
@@ -56,7 +56,7 @@ public class CompositePropertySource extends EnumerablePropertySource<Object> {
@Override
public String[] getPropertyNames() {
Set<String> names = new LinkedHashSet<String>();
Set<String> names = new LinkedHashSet<>();
for (PropertySource<?> propertySource : this.propertySources) {
if (!(propertySource instanceof EnumerablePropertySource)) {
throw new IllegalStateException(
@@ -83,7 +83,7 @@ public class CompositePropertySource extends EnumerablePropertySource<Object> {
* @since 4.1
*/
public void addFirstPropertySource(PropertySource<?> propertySource) {
List<PropertySource<?>> existing = new ArrayList<PropertySource<?>>(this.propertySources);
List<PropertySource<?>> existing = new ArrayList<>(this.propertySources);
this.propertySources.clear();
this.propertySources.add(propertySource);
this.propertySources.addAll(existing);

View File

@@ -28,7 +28,7 @@ public class MutablePropertySources implements PropertySources {
private final Log logger;
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>();
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
/**
* Create a new {@link MutablePropertySources}

View File

@@ -31,7 +31,7 @@ public class SpringProfileDocumentMatcher implements YamlProcessor.DocumentMatch
}
public void addActiveProfiles(String... profiles) {
LinkedHashSet<String> set = new LinkedHashSet<String>(Arrays.asList(this.activeProfiles));
LinkedHashSet<String> set = new LinkedHashSet<>(Arrays.asList(this.activeProfiles));
Collections.addAll(set, profiles);
this.activeProfiles = set.toArray(new String[set.size()]);
}
@@ -67,14 +67,14 @@ public class SpringProfileDocumentMatcher implements YamlProcessor.DocumentMatch
private ProfilesMatcher getProfilesMatcher() {
return (this.activeProfiles.length != 0 ? new ActiveProfilesMatcher(
new HashSet<String>(Arrays.asList(this.activeProfiles))) : new EmptyProfilesMatcher());
new HashSet<>(Arrays.asList(this.activeProfiles))) : new EmptyProfilesMatcher());
}
private Set<String> extractProfiles(List<String> profiles, ProfileType type) {
if (CollectionUtils.isEmpty(profiles)) {
return null;
}
Set<String> extractedProfiles = new HashSet<String>();
Set<String> extractedProfiles = new HashSet<>();
for (String candidate : profiles) {
ProfileType candidateType = ProfileType.POSITIVE;
if (candidate.startsWith("!")) {
@@ -167,7 +167,7 @@ public class SpringProfileDocumentMatcher implements YamlProcessor.DocumentMatch
*/
static class SpringProperties {
private List<String> profiles = new ArrayList<String>();
private List<String> profiles = new ArrayList<>();
public List<String> getProfiles() {
return this.profiles;

View File

@@ -130,8 +130,7 @@ public abstract class YamlProcessor {
if (logger.isDebugEnabled()) {
logger.debug("Loading from YAML: " + resource);
}
Reader reader = new UnicodeReader(resource.getInputStream());
try {
try (Reader reader = new UnicodeReader(resource.getInputStream())) {
for (Object object : yaml.loadAll(reader)) {
if (object != null && process(asMap(object), callback)) {
count++;
@@ -142,10 +141,8 @@ public abstract class YamlProcessor {
}
if (logger.isDebugEnabled()) {
logger.debug(
"Loaded " + count + " document" + (count > 1 ? "s" : "") + " from YAML resource: " + resource);
"Loaded " + count + " document" + (count > 1 ? "s" : "") + " from YAML resource: " + resource);
}
} finally {
reader.close();
}
} catch (IOException ex) {
handleProcessError(resource, ex);
@@ -166,7 +163,7 @@ public abstract class YamlProcessor {
@SuppressWarnings("unchecked")
private Map<String, Object> asMap(Object object) {
// YAML can have numbers as keys
Map<String, Object> result = new LinkedHashMap<String, Object>();
Map<String, Object> result = new LinkedHashMap<>();
if (!(object instanceof Map)) {
// A document can be a text literal
result.put("document", object);
@@ -249,7 +246,7 @@ public abstract class YamlProcessor {
* @since 4.1.3
*/
protected final Map<String, Object> getFlattenedMap(Map<String, Object> source) {
Map<String, Object> result = new LinkedHashMap<String, Object>();
Map<String, Object> result = new LinkedHashMap<>();
buildFlattenedMap(result, source, null);
return result;
}

View File

@@ -3,7 +3,6 @@ package com.alibaba.otter.canal.client.adapter.config.common;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;
import org.springframework.util.ClassUtils;
@@ -72,14 +71,8 @@ public class YamlPropertySourceLoader implements PropertySourceLoader {
}
public Map<String, Object> process() {
final Map<String, Object> result = new LinkedHashMap<String, Object>();
process(new MatchCallback() {
@Override
public void process(Properties properties, Map<String, Object> map) {
result.putAll(getFlattenedMap(map));
}
});
final Map<String, Object> result = new LinkedHashMap<>();
process((properties, map) -> result.putAll(getFlattenedMap(map)));
return result;
}

View File

@@ -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; // 实例名

View File

@@ -6,6 +6,7 @@ import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
@@ -259,7 +260,7 @@ public class ExtensionLoader<T> {
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// 1. plugin folder,customized extension classLoader (jar_dir/plugin)
String dir = File.separator + this.getJarDirectoryPath() + File.separator + "plugin";
@@ -317,10 +318,8 @@ public class ExtensionLoader<T> {
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
try {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
String line = null;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
final int ci = line.indexOf('#');
if (ci >= 0) line = line.substring(0, ci);
@@ -340,10 +339,10 @@ public class ExtensionLoader<T> {
// classLoader);
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException(
"Error when load extension class(interface: " + type
+ ", class line: " + clazz.getName()
+ "), class " + clazz.getName()
+ "is not subtype of interface.");
"Error when load extension class(interface: " + type
+ ", class line: " + clazz.getName()
+ "), class " + clazz.getName()
+ "is not subtype of interface.");
} else {
try {
clazz.getConstructor(type);
@@ -361,10 +360,10 @@ public class ExtensionLoader<T> {
} else if (c != clazz) {
cachedNames.remove(clazz);
throw new IllegalStateException(
"Duplicate extension " + type.getName() + " name "
+ n + " on "
+ c.getName() + " and "
+ clazz.getName());
"Duplicate extension " + type.getName() + " name "
+ n + " on "
+ c.getName() + " and "
+ clazz.getName());
}
}
}
@@ -373,19 +372,15 @@ public class ExtensionLoader<T> {
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException(
"Failed to load extension class(interface: " + type + ", class line: "
+ line + ") in " + url
+ ", cause: "
+ t.getMessage(),
t);
"Failed to load extension class(interface: " + type + ", class line: "
+ line + ") in " + url
+ ", cause: "
+ t.getMessage(),
t);
exceptions.put(line, e);
}
}
} // end of while read lines
} finally {
if (reader != null) {
reader.close();
}
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " + type + ", class file: " + url

View File

@@ -4,7 +4,6 @@ import java.util.*;
import com.alibaba.otter.canal.connector.core.consumer.CommonMessage;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.FlatMessage;
import com.alibaba.otter.canal.protocol.Message;
/**
@@ -20,7 +19,7 @@ public class MessageUtil {
return null;
}
List<CanalEntry.Entry> entries = message.getEntries();
List<Dml> dmls = new ArrayList<Dml>(entries.size());
List<Dml> dmls = new ArrayList<>(entries.size());
for (CanalEntry.Entry entry : entries) {
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
|| entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
@@ -136,7 +135,7 @@ public class MessageUtil {
if (commonMessages == null) {
return new ArrayList<>();
}
List<Dml> dmls = new ArrayList<Dml>(commonMessages.size());
List<Dml> dmls = new ArrayList<>(commonMessages.size());
for (CommonMessage commonMessage : commonMessages) {
Dml dml = flatMessage2Dml(destination, groupId, commonMessage);
if (dml != null) {

View File

@@ -7,7 +7,8 @@ import java.util.Enumeration;
import java.util.NoSuchElementException;
public class URLClassExtensionLoader extends URLClassLoader {
public URLClassExtensionLoader(URL[] urls) {
public URLClassExtensionLoader(URL[] urls){
super(urls);
}
@@ -18,9 +19,11 @@ public class URLClassExtensionLoader extends URLClassLoader {
return c;
}
if (name.startsWith("java.") || name.startsWith("org.slf4j.")
|| name.startsWith("org.apache.logging")
|| name.startsWith("org.apache.commons.logging.")) {
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("com.alibaba.druid")
) {
// || name.startsWith("org.apache.hadoop."))
// {
c = super.loadClass(name);

View File

@@ -1,24 +1,13 @@
package com.alibaba.otter.canal.client.adapter.support;
import static org.mockito.AdditionalMatchers.or;
import static org.mockito.Matchers.isA;
import static org.mockito.Matchers.isNull;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import com.alibaba.otter.canal.client.adapter.support.JdbcTypeUtil;
import com.alibaba.otter.canal.client.adapter.support.Util;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.lang.reflect.Array;
@RunWith(PowerMockRunner.class)
public class JdbcTypeUtilTest {

View File

@@ -3,9 +3,7 @@ package com.alibaba.otter.canal.client.adapter.support;
import static org.mockito.AdditionalMatchers.or;
import static org.mockito.Matchers.isA;
import static org.mockito.Matchers.isNull;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import com.alibaba.otter.canal.client.adapter.support.Util;
import com.diffblue.deeptestutils.mock.DTUMemberMatcher;
import org.apache.commons.lang.StringUtils;
import org.junit.Assert;

View File

@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5-SNAPSHOT</version>
<version>1.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -2,7 +2,10 @@ package com.alibaba.otter.canal.client.adapter.es6x.support;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
@@ -78,14 +81,7 @@ public class ESConnection {
Integer.parseInt(host.substring(i + 1))));
}
} else {
HttpHost[] httpHosts = new HttpHost[hosts.length];
for (int i = 0; i < hosts.length; i++) {
String host = hosts[i];
int j = host.indexOf(":");
HttpHost httpHost = new HttpHost(InetAddress.getByName(host.substring(0, j)),
Integer.parseInt(host.substring(j + 1)));
httpHosts[i] = httpHost;
}
HttpHost[] httpHosts = Arrays.stream(hosts).map(this::createHttpHost).toArray(HttpHost[]::new);
RestClientBuilder restClientBuilder = RestClient.builder(httpHosts);
String nameAndPwd = properties.get("security.auth");
if (StringUtils.isNotEmpty(nameAndPwd) && nameAndPwd.contains(":")) {
@@ -507,4 +503,17 @@ public class ESConnection {
public void setRestHighLevelClient(RestHighLevelClient restHighLevelClient) {
this.restHighLevelClient = restHighLevelClient;
}
private HttpHost createHttpHost(String uriStr) {
URI uri = URI.create(uriStr);
if (!org.springframework.util.StringUtils.hasLength(uri.getUserInfo())) {
return HttpHost.create(uri.toString());
}
try {
return HttpHost.create(new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(),
uri.getQuery(), uri.getFragment()).toString());
} catch (URISyntaxException ex) {
throw new IllegalStateException(ex);
}
}
}

View File

@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5-SNAPSHOT</version>
<version>1.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -2,7 +2,10 @@ package com.alibaba.otter.canal.client.adapter.es7x.support;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
@@ -77,14 +80,7 @@ public class ESConnection {
Integer.parseInt(host.substring(i + 1))));
}
} else {
HttpHost[] httpHosts = new HttpHost[hosts.length];
for (int i = 0; i < hosts.length; i++) {
String host = hosts[i];
int j = host.indexOf(":");
HttpHost httpHost = new HttpHost(InetAddress.getByName(host.substring(0, j)),
Integer.parseInt(host.substring(j + 1)));
httpHosts[i] = httpHost;
}
HttpHost[] httpHosts = Arrays.stream(hosts).map(this::createHttpHost).toArray(HttpHost[]::new);
RestClientBuilder restClientBuilder = RestClient.builder(httpHosts);
String nameAndPwd = properties.get("security.auth");
if (StringUtils.isNotEmpty(nameAndPwd) && nameAndPwd.contains(":")) {
@@ -501,4 +497,17 @@ public class ESConnection {
public void setRestHighLevelClient(RestHighLevelClient restHighLevelClient) {
this.restHighLevelClient = restHighLevelClient;
}
private HttpHost createHttpHost(String uriStr) {
URI uri = URI.create(uriStr);
if (!org.springframework.util.StringUtils.hasLength(uri.getUserInfo())) {
return HttpHost.create(uri.toString());
}
try {
return HttpHost.create(new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(),
uri.getQuery(), uri.getFragment()).toString());
} catch (URISyntaxException ex) {
throw new IllegalStateException(ex);
}
}
}

View File

@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5-SNAPSHOT</version>
<version>1.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -22,8 +20,8 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.fastsql</groupId>
<artifactId>fastsql</artifactId>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>

View File

@@ -1,31 +1,31 @@
package com.alibaba.otter.canal.client.adapter.es.core.config;
import static com.alibaba.fastsql.sql.ast.expr.SQLBinaryOperator.BooleanAnd;
import static com.alibaba.fastsql.sql.ast.expr.SQLBinaryOperator.Equality;
import static com.alibaba.druid.sql.ast.expr.SQLBinaryOperator.BooleanAnd;
import static com.alibaba.druid.sql.ast.expr.SQLBinaryOperator.Equality;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import com.alibaba.fastsql.sql.SQLUtils;
import com.alibaba.fastsql.sql.ast.SQLExpr;
import com.alibaba.fastsql.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.fastsql.sql.ast.expr.SQLCaseExpr;
import com.alibaba.fastsql.sql.ast.expr.SQLCharExpr;
import com.alibaba.fastsql.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.fastsql.sql.ast.expr.SQLMethodInvokeExpr;
import com.alibaba.fastsql.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.fastsql.sql.ast.statement.SQLExprTableSource;
import com.alibaba.fastsql.sql.ast.statement.SQLJoinTableSource;
import com.alibaba.fastsql.sql.ast.statement.SQLSelectGroupByClause;
import com.alibaba.fastsql.sql.ast.statement.SQLSelectItem;
import com.alibaba.fastsql.sql.ast.statement.SQLSelectStatement;
import com.alibaba.fastsql.sql.ast.statement.SQLSubqueryTableSource;
import com.alibaba.fastsql.sql.ast.statement.SQLTableSource;
import com.alibaba.fastsql.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.alibaba.fastsql.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.fastsql.sql.parser.ParserException;
import com.alibaba.fastsql.sql.parser.SQLStatementParser;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLCaseExpr;
import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelectGroupByClause;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.sql.parser.SQLStatementParser;
import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem.ColumnItem;
import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem.FieldItem;
import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem.RelationFieldsPair;
@@ -230,8 +230,7 @@ public class SqlParser {
}
FieldItem rightFieldItem = new FieldItem();
visitColumn(sqlBinaryOpExpr.getRight(), rightFieldItem);
if (rightFieldItem.getColumnItems().size() != 1 || rightFieldItem.isMethod()
|| rightFieldItem.isBinaryOp()) {
if (rightFieldItem.getColumnItems().size() != 1 || rightFieldItem.isMethod() || rightFieldItem.isBinaryOp()) {
throw new UnsupportedOperationException("Unsupported for complex of on-condition");
}
tableItem.getRelationFields().add(new RelationFieldsPair(leftFieldItem, rightFieldItem));

View File

@@ -13,7 +13,7 @@ import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastsql.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
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;

View File

@@ -63,172 +63,187 @@ public class ESSyncUtil {
return val;
}
Object res = null;
if ("integer".equals(esType)) {
if (val instanceof Number) {
res = ((Number) val).intValue();
} else {
res = Integer.parseInt(val.toString());
}
} else if ("long".equals(esType)) {
if (val instanceof Number) {
res = ((Number) val).longValue();
} else {
res = Long.parseLong(val.toString());
}
} else if ("short".equals(esType)) {
if (val instanceof Number) {
res = ((Number) val).shortValue();
} else {
res = Short.parseShort(val.toString());
}
} else if ("byte".equals(esType)) {
if (val instanceof Number) {
res = ((Number) val).byteValue();
} else {
res = Byte.parseByte(val.toString());
}
} else if ("double".equals(esType)) {
if (val instanceof Number) {
res = ((Number) val).doubleValue();
} else {
res = Double.parseDouble(val.toString());
}
} else if ("float".equals(esType) || "half_float".equals(esType) || "scaled_float".equals(esType)) {
if (val instanceof Number) {
res = ((Number) val).floatValue();
} else {
res = Float.parseFloat(val.toString());
}
} else if ("boolean".equals(esType)) {
if (val instanceof Boolean) {
res = val;
} else if (val instanceof Number) {
int v = ((Number) val).intValue();
res = v != 0;
} else {
res = Boolean.parseBoolean(val.toString());
}
} else if ("date".equals(esType)) {
if (val instanceof java.sql.Time) {
DateTime dateTime = new DateTime(((java.sql.Time) val).getTime());
if (dateTime.getMillisOfSecond() != 0) {
res = dateTime.toString("HH:mm:ss.SSS");
switch (esType) {
case "integer":
if (val instanceof Number) {
res = ((Number) val).intValue();
} else {
res = dateTime.toString("HH:mm:ss");
res = Integer.parseInt(val.toString());
}
} else if (val instanceof java.sql.Timestamp) {
DateTime dateTime = new DateTime(((java.sql.Timestamp) val).getTime());
if (dateTime.getMillisOfSecond() != 0) {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss.SSS" + Util.timeZone);
break;
case "long":
if (val instanceof Number) {
res = ((Number) val).longValue();
} else {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss" + Util.timeZone);
res = Long.parseLong(val.toString());
}
} else if (val instanceof java.sql.Date || val instanceof Date) {
DateTime dateTime;
if (val instanceof java.sql.Date) {
dateTime = new DateTime(((java.sql.Date) val).getTime());
break;
case "short":
if (val instanceof Number) {
res = ((Number) val).shortValue();
} else {
dateTime = new DateTime(((Date) val).getTime());
res = Short.parseShort(val.toString());
}
if (dateTime.getHourOfDay() == 0 && dateTime.getMinuteOfHour() == 0 && dateTime.getSecondOfMinute() == 0
&& dateTime.getMillisOfSecond() == 0) {
res = dateTime.toString("yyyy-MM-dd");
break;
case "byte":
if (val instanceof Number) {
res = ((Number) val).byteValue();
} else {
res = Byte.parseByte(val.toString());
}
break;
case "double":
if (val instanceof Number) {
res = ((Number) val).doubleValue();
} else {
res = Double.parseDouble(val.toString());
}
break;
case "float":
case "half_float":
case "scaled_float":
if (val instanceof Number) {
res = ((Number) val).floatValue();
} else {
res = Float.parseFloat(val.toString());
}
break;
case "boolean":
if (val instanceof Boolean) {
res = val;
} else if (val instanceof Number) {
int v = ((Number) val).intValue();
res = v != 0;
} else {
res = Boolean.parseBoolean(val.toString());
}
break;
case "date":
if (val instanceof java.sql.Time) {
DateTime dateTime = new DateTime(((java.sql.Time) val).getTime());
if (dateTime.getMillisOfSecond() != 0) {
res = dateTime.toString("HH:mm:ss.SSS");
} else {
res = dateTime.toString("HH:mm:ss");
}
} else if (val instanceof java.sql.Timestamp) {
DateTime dateTime = new DateTime(((java.sql.Timestamp) val).getTime());
if (dateTime.getMillisOfSecond() != 0) {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss.SSS" + Util.timeZone);
} else {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss" + Util.timeZone);
}
}
} else if (val instanceof Long) {
DateTime dateTime = new DateTime(((Long) val).longValue());
if (dateTime.getHourOfDay() == 0 && dateTime.getMinuteOfHour() == 0 && dateTime.getSecondOfMinute() == 0
&& dateTime.getMillisOfSecond() == 0) {
res = dateTime.toString("yyyy-MM-dd");
} else if (dateTime.getMillisOfSecond() != 0) {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss.SSS" + Util.timeZone);
} else {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss" + Util.timeZone);
}
} else if (val instanceof String) {
String v = ((String) val).trim();
if (v.length() > 18 && v.charAt(4) == '-' && v.charAt(7) == '-' && v.charAt(10) == ' '
&& v.charAt(13) == ':' && v.charAt(16) == ':') {
String dt = v.substring(0, 10) + "T" + v.substring(11);
Date date = Util.parseDate(dt);
if (date != null) {
DateTime dateTime = new DateTime(date);
} else if (val instanceof java.sql.Date || val instanceof Date) {
DateTime dateTime;
if (val instanceof java.sql.Date) {
dateTime = new DateTime(((java.sql.Date) val).getTime());
} else {
dateTime = new DateTime(((Date) val).getTime());
}
if (dateTime.getHourOfDay() == 0 && dateTime.getMinuteOfHour() == 0 && dateTime.getSecondOfMinute() == 0
&& dateTime.getMillisOfSecond() == 0) {
res = dateTime.toString("yyyy-MM-dd");
} else {
if (dateTime.getMillisOfSecond() != 0) {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss.SSS" + Util.timeZone);
} else {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss" + Util.timeZone);
}
}
} else if (v.length() == 10 && v.charAt(4) == '-' && v.charAt(7) == '-') {
Date date = Util.parseDate(v);
if (date != null) {
DateTime dateTime = new DateTime(date);
} else if (val instanceof Long) {
DateTime dateTime = new DateTime(((Long) val).longValue());
if (dateTime.getHourOfDay() == 0 && dateTime.getMinuteOfHour() == 0 && dateTime.getSecondOfMinute() == 0
&& dateTime.getMillisOfSecond() == 0) {
res = dateTime.toString("yyyy-MM-dd");
} else if (dateTime.getMillisOfSecond() != 0) {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss.SSS" + Util.timeZone);
} else {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss" + Util.timeZone);
}
} else if (val instanceof String) {
String v = ((String) val).trim();
if (v.length() > 18 && v.charAt(4) == '-' && v.charAt(7) == '-' && v.charAt(10) == ' '
&& v.charAt(13) == ':' && v.charAt(16) == ':') {
String dt = v.substring(0, 10) + "T" + v.substring(11);
Date date = Util.parseDate(dt);
if (date != null) {
DateTime dateTime = new DateTime(date);
if (dateTime.getMillisOfSecond() != 0) {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss.SSS" + Util.timeZone);
} else {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss" + Util.timeZone);
}
}
} else if (v.length() == 10 && v.charAt(4) == '-' && v.charAt(7) == '-') {
Date date = Util.parseDate(v);
if (date != null) {
DateTime dateTime = new DateTime(date);
res = dateTime.toString("yyyy-MM-dd");
}
}
}
}
} else if ("binary".equals(esType)) {
if (val instanceof byte[]) {
Base64 base64 = new Base64();
res = base64.encodeAsString((byte[]) val);
} else if (val instanceof Blob) {
byte[] b = blobToBytes((Blob) val);
Base64 base64 = new Base64();
res = base64.encodeAsString(b);
} else if (val instanceof String) {
// 对应canal中的单字节编码
byte[] b = ((String) val).getBytes(StandardCharsets.ISO_8859_1);
Base64 base64 = new Base64();
res = base64.encodeAsString(b);
}
} else if ("geo_point".equals(esType)) {
if (!(val instanceof String)) {
logger.error("es type is geo_point, but source type is not String");
return val;
}
if (!((String) val).contains(",")) {
logger.error("es type is geo_point, source value not contains ',' separator");
return val;
}
String[] point = ((String) val).split(",");
Map<String, Double> location = new HashMap<>();
location.put("lat", Double.valueOf(point[0].trim()));
location.put("lon", Double.valueOf(point[1].trim()));
return location;
} else if ("array".equals(esType)) {
if ("".equals(val.toString().trim())) {
res = new ArrayList<>();
} else {
String value = val.toString();
String separator = ",";
if (!value.contains(",")) {
if (value.contains(";")) {
separator = ";";
} else if (value.contains("|")) {
separator = "\\|";
} else if (value.contains("-")) {
separator = "-";
}
break;
case "binary":
if (val instanceof byte[]) {
Base64 base64 = new Base64();
res = base64.encodeAsString((byte[]) val);
} else if (val instanceof Blob) {
byte[] b = blobToBytes((Blob) val);
Base64 base64 = new Base64();
res = base64.encodeAsString(b);
} else if (val instanceof String) {
// 对应canal中的单字节编码
byte[] b = ((String) val).getBytes(StandardCharsets.ISO_8859_1);
Base64 base64 = new Base64();
res = base64.encodeAsString(b);
}
String[] values = value.split(separator);
return Arrays.asList(values);
}
} else if ("object".equals(esType)) {
if ("".equals(val.toString().trim())) {
res = new HashMap<>();
} else {
res = JSON.parseObject(val.toString(), Map.class);
}
} else {
// 其他类全以字符串处理
res = val.toString();
break;
case "geo_point":
if (!(val instanceof String)) {
logger.error("es type is geo_point, but source type is not String");
return val;
}
if (!((String) val).contains(",")) {
logger.error("es type is geo_point, source value not contains ',' separator");
return val;
}
String[] point = ((String) val).split(",");
Map<String, Double> location = new HashMap<>();
location.put("lat", Double.valueOf(point[0].trim()));
location.put("lon", Double.valueOf(point[1].trim()));
return location;
case "array":
if ("".equals(val.toString().trim())) {
res = new ArrayList<>();
} else {
String value = val.toString();
String separator = ",";
if (!value.contains(",")) {
if (value.contains(";")) {
separator = ";";
} else if (value.contains("|")) {
separator = "\\|";
} else if (value.contains("-")) {
separator = "-";
}
}
String[] values = value.split(separator);
return Arrays.asList(values);
}
break;
case "object":
if ("".equals(val.toString().trim())) {
res = new HashMap<>();
} else {
res = JSON.parseObject(val.toString(), Map.class);
}
break;
default:
// 其他类全以字符串处理
res = val.toString();
break;
}
return res;

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5-SNAPSHOT</version>
<version>1.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -20,7 +20,7 @@
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<artifactId>hbase-shaded-client</artifactId>
<version>1.1.2</version>
<exclusions>
<exclusion>

View File

@@ -1,9 +1,10 @@
package com.alibaba.otter.canal.client.adapter.hbase.monitor;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
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 org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
@@ -13,11 +14,10 @@ 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.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.HashMap;
import java.util.Map;
import java.util.Properties;
public class HbaseConfigMonitor {
@@ -125,8 +125,8 @@ public class HbaseConfigMonitor {
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(),
.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "_"
+ config.getHbaseMapping().getDatabase() + "-" + config.getHbaseMapping().getTable(),
k1 -> new HashMap<>());
configMap.put(file.getName(), config);
}

View File

@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5-SNAPSHOT</version>
<version>1.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -73,7 +71,7 @@
<tasks>
<copy todir="${project.basedir}/../launcher/target/classes/kudu" overwrite="true">
<fileset dir="${project.basedir}/target/classes/kudu" erroronmissingdir="true">
<include name="*.yml"/>
<include name="*.yml" />
</fileset>
</copy>
</tasks>

View File

@@ -88,45 +88,40 @@ public class KuduEtlService extends AbstractEtlService {
Map<String, Object> data = new HashMap<>();
for (Map.Entry<String, String> entry : columnsMap.entrySet()) {
String mysqlColumnName = entry.getKey();// mysql字段名
String kuduColumnName = entry.getValue();// kudu字段名
if (kuduColumnName == null) {
kuduColumnName = mysqlColumnName;
}
Object value = rs.getObject(kuduColumnName);
if (value != null) {
data.put(kuduColumnName, value);
} else {
data.put(kuduColumnName, null);
}
}
dataList.add(data);
idx++;
impCount.incrementAndGet();
if (logger.isDebugEnabled()) {
logger.debug("successful import count:" + impCount.get());
}
if (idx % kuduMapping.getCommitBatch() == 0) {
kuduTemplate.upsert(kuduMapping.getTargetTable(), dataList);
dataList.clear();
}
}
if (!dataList.isEmpty()) {
kuduTemplate.upsert(kuduMapping.getTargetTable(), dataList);
}
return true;
String kuduColumnName = entry.getValue();// kudu字段名
if (kuduColumnName == null) {
kuduColumnName = mysqlColumnName;
}
Object value = rs.getObject(kuduColumnName);
if (value != null) {
data.put(kuduColumnName, value);
} else {
data.put(kuduColumnName, null);
}
}
dataList.add(data);
idx++;
impCount.incrementAndGet();
if (logger.isDebugEnabled()) {
logger.debug("successful import count:" + impCount.get());
}
if (idx % kuduMapping.getCommitBatch() == 0) {
kuduTemplate.upsert(kuduMapping.getTargetTable(), dataList);
dataList.clear();
}
}
if (!dataList.isEmpty()) {
kuduTemplate.upsert(kuduMapping.getTargetTable(), dataList);
}
return true;
} catch (SQLException e) {
e.printStackTrace();
logger.error(kuduMapping.getTargetTable() + " etl failed! ==>" + e.getMessage(), e);
errMsg.add(kuduMapping.getTargetTable() + " etl failed! ==>" + e.getMessage());
return false;
} catch (KuduException e) {
e.printStackTrace();
logger.error(kuduMapping.getTargetTable() + " etl failed! ==>" + e.getMessage(), e);
errMsg.add(kuduMapping.getTargetTable() + " etl failed! ==>" + e.getMessage());
return false;
}
} );
} catch (SQLException | KuduException e) {
e.printStackTrace();
logger.error(kuduMapping.getTargetTable() + " etl failed! ==>" + e.getMessage(), e);
errMsg.add(kuduMapping.getTargetTable() + " etl failed! ==>" + e.getMessage());
return false;
}
});
return true;
} catch (Exception e) {
logger.error(e.getMessage(), e);

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5-SNAPSHOT</version>
<version>1.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -69,6 +69,12 @@
<artifactId>mssql-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-shaded-client</artifactId>
<scope>runtime</scope>
</dependency>
<!-- outer adapter jar with dependencies-->
<dependency>
<groupId>com.alibaba.otter</groupId>
@@ -135,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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,5 +1,13 @@
#!/bin/bash
sh stop.sh
case $(uname) in
Linux)
bin_abs_path=$(readlink -f $(dirname $0))
;;
*)
bin_abs_path=$(cd $(dirname $0) ||exit ; pwd)
;;
esac
sh startup.sh
sh "$bin_abs_path"/stop.sh
sh "$bin_abs_path"/startup.sh

View File

@@ -12,8 +12,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
*/
@SpringBootApplication
public class CanalAdapterApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(CanalAdapterApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.run(args);

View File

@@ -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());
@@ -211,13 +212,21 @@ public class AdapterProcessor {
canalDestination,
System.currentTimeMillis() - begin);
}
break;
} catch (Exception e) {
if (i != retry - 1) {
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);
}

View File

@@ -94,4 +94,11 @@ canal.conf:
# - name: kudu
# key: kudu
# properties:
# kudu.master.address: 127.0.0.1 # ',' split multi address
# 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:

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5-SNAPSHOT</version>
<version>1.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,154 @@
<?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.6-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>
<version>1.7.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
<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>

View File

@@ -0,0 +1,291 @@
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;
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;
Map<String, MappingConfig> phoenixMappingTmp = ConfigLoader.load(envProperties);
// 过滤不匹配的key的配置
phoenixMappingTmp.forEach((key, mappingConfig) -> {
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|| (mappingConfig.getOuterAdapterKey() != null
&& mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) {
phoenixMapping.put(key, mappingConfig);
}
});
if (phoenixMapping.isEmpty()) {
throw new RuntimeException("No phoenix adapter found for config key: " + configuration.getKey());
} else {
logger.info("[{}]phoenix config mapping: {}", this, phoenixMapping.keySet());
}
for (Map.Entry<String, MappingConfig> entry : phoenixMapping.entrySet()) {
String configName = entry.getKey();
MappingConfig mappingConfig = entry.getValue();
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);
}
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();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,167 @@
package com.alibaba.otter.canal.client.adapter.phoenix.monitor;
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.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util;
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;
/**
* 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();
if ((key == null && config.getOuterAdapterKey() == null)
|| (key != null && key.equals(config.getOuterAdapterKey()))) {
addConfigToCache(file, config);
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();
if ((key == null && config.getOuterAdapterKey() == null)
|| (key != null && key.equals(config.getOuterAdapterKey()))) {
if (phoenixAdapter.getPhoenixMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
} else {
// 不能修改outerAdapterKey
throw new RuntimeException("Outer adapter key not allowed modify");
}
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())) {
deleteConfigFromCache(file);
logger.info("Delete a phoenix mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
private void addConfigToCache(File file, MappingConfig mappingConfig) {
if (mappingConfig == null || mappingConfig.getDbMapping() == null) {
return;
}
phoenixAdapter.getPhoenixMapping().put(file.getName(), mappingConfig);
Map<String, MappingConfig> configMap = phoenixAdapter.getMappingConfigCache()
.computeIfAbsent(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-"
+ mappingConfig.getDbMapping().getTable().toLowerCase(),
k1 -> new HashMap<>());
configMap.put(file.getName(), mappingConfig);
}
private void deleteConfigFromCache(File file) {
logger.info("deleteConfigFromCache: {}", file.getName());
MappingConfig mappingConfig = phoenixAdapter.getPhoenixMapping().remove(file.getName());
if (mappingConfig == null || mappingConfig.getDbMapping() == null) {
return;
}
for (Map<String, MappingConfig> configMap : phoenixAdapter.getMappingConfigCache().values()) {
if (configMap != null) {
configMap.remove(file.getName());
}
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
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, SerializerFeature.WriteMapNullValue));
}
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, SerializerFeature.WriteMapNullValue));
}
} 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, SerializerFeature.WriteMapNullValue));
}
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();
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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";
}
}

View File

@@ -0,0 +1 @@
phoenix=com.alibaba.otter.canal.client.adapter.phoenix.PhoenixAdapter

View File

@@ -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>

View File

@@ -0,0 +1,2 @@
# ͬ<><CDAC><EFBFBD>߳<EFBFBD><DFB3><EFBFBD>
threads =3

View File

@@ -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 # 允许修改表结构(默认true,mapAll=true时可以新增,drop=true时可以删除字段)
drop: false # 允许删除字段(默认false)
skipMissing: false # 是否跳过缺失的字段(默认false,允许新增字段时会自动同步缺失的字段;true时跳过缺失的字段)
limit: false # 是否限与数据长度限制一致(默认false,不限制长度避免修改长度而无法修改)
targetColumns:
id: ID
name: NAME
excludeColumns: # 排除字段
- password

View File

@@ -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();
}
}
}

View File

@@ -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());
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -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));
}
}

View File

@@ -4,12 +4,12 @@
<parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId>
<version>1.1.5-SNAPSHOT</version>
<version>1.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client-adapter</artifactId>
<version>1.1.5-SNAPSHOT</version>
<version>1.1.6-SNAPSHOT</version>
<packaging>pom</packaging>
<name>canal client adapter module for otter ${project.version}</name>
@@ -32,6 +32,8 @@
<module>es7x</module>
<module>escore</module>
<module>kudu</module>
<module>phoenix</module>
<module>tablestore</module>
</modules>
<licenses>
@@ -192,7 +194,7 @@
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<artifactId>hbase-shaded-client</artifactId>
<version>1.1.2</version>
<exclusions>
<exclusion>
@@ -250,6 +252,25 @@
<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>
</dependencies>
</dependencyManagement>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.5-SNAPSHOT</version>
<version>1.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,27 +1,22 @@
package com.alibaba.otter.canal.client.adapter.rdb.support;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.support.Util;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.sql.*;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.support.Util;
public class SyncUtil {
private static final Logger logger = LoggerFactory.getLogger(SyncUtil.class);
public static Map<String, String> getColumnsMap(MappingConfig.DbMapping dbMapping, Map<String, Object> data) {
return getColumnsMap(dbMapping, data.keySet());
@@ -93,15 +88,7 @@ public class SyncUtil {
}
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:
// 向上提升一级,处理unsigned情况
if (value instanceof Number) {
pstmt.setShort(i, ((Number) value).shortValue());
} else if (value instanceof String) {
@@ -110,7 +97,7 @@ public class SyncUtil {
pstmt.setNull(i, type);
}
break;
case Types.INTEGER:
case Types.SMALLINT:
if (value instanceof Number) {
pstmt.setInt(i, ((Number) value).intValue());
} else if (value instanceof String) {
@@ -119,7 +106,7 @@ public class SyncUtil {
pstmt.setNull(i, type);
}
break;
case Types.BIGINT:
case Types.INTEGER:
if (value instanceof Number) {
pstmt.setLong(i, ((Number) value).longValue());
} else if (value instanceof String) {
@@ -128,6 +115,15 @@ public class SyncUtil {
pstmt.setNull(i, type);
}
break;
case Types.BIGINT:
if (value instanceof Number) {
pstmt.setBigDecimal(i, new BigDecimal(value.toString()));
} else if (value instanceof String) {
pstmt.setBigDecimal(i, new BigDecimal(value.toString()));
} else {
pstmt.setNull(i, type);
}
break;
case Types.DECIMAL:
case Types.NUMERIC:
if (value instanceof BigDecimal) {
@@ -262,9 +258,9 @@ public class SyncUtil {
public static String getDbTableName(MappingConfig.DbMapping dbMapping) {
String result = "";
if (StringUtils.isNotEmpty(dbMapping.getTargetDb())) {
result += dbMapping.getTargetDb() + ".";
result += ("`" + dbMapping.getTargetDb() + "`.");
}
result += dbMapping.getTargetTable();
result += ("`" + dbMapping.getTargetTable() + "`");
return result;
}
}

View File

@@ -1,14 +1,14 @@
//wpackage com.alibaba.otter.canal.client.adapter.rdb.test;
//
//import com.alibaba.fastsql.sql.ast.SQLName;
//import com.alibaba.fastsql.sql.ast.SQLStatement;
//import com.alibaba.fastsql.sql.ast.statement.SQLCreateTableStatement;
//import com.alibaba.fastsql.sql.ast.statement.SQLExprTableSource;
//import com.alibaba.fastsql.sql.dialect.mysql.parser.MySqlCreateTableParser;
//import com.alibaba.fastsql.sql.dialect.mysql.parser.MySqlStatementParser;
//import com.alibaba.fastsql.sql.dialect.mysql.visitor.MySqlOutputVisitor;
//import com.alibaba.fastsql.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
//import com.alibaba.fastsql.sql.parser.SQLStatementParser;
//import com.alibaba.druid.sql.ast.SQLName;
//import com.alibaba.druid.sql.ast.SQLStatement;
//import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement;
//import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
//import com.alibaba.druid.sql.dialect.mysql.parser.MySqlCreateTableParser;
//import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
//import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlOutputVisitor;
//import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
//import com.alibaba.druid.sql.parser.SQLStatementParser;
//
//import java.io.StringWriter;
//

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.6-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>

View File

@@ -0,0 +1,318 @@
package com.alibaba.otter.canal.client.adapter.tablestore;
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, mappingConfig) -> {
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|| (mappingConfig.getOuterAdapterKey() != null && mappingConfig.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey()))) {
tablestoreMapping.put(key, mappingConfig);
mappingConfig.getDbMapping().init(mappingConfig);
}
});
if (tablestoreMapping.isEmpty()) {
throw new RuntimeException("No tablestore adapter found for config key: " + configuration.getKey());
}
Map<String, String> properties = configuration.getProperties();
for (Map.Entry<String, MappingConfig> entry : tablestoreMapping.entrySet()) {
String configName = entry.getKey();
MappingConfig mappingConfig = entry.getValue();
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);
}
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();
}
}
}
}
}
}

View File

@@ -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";
}

View File

@@ -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;
}
}

View File

@@ -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);
}
});
}
}
}

View File

@@ -0,0 +1,9 @@
package com.alibaba.otter.canal.client.adapter.tablestore.enums;
public enum TablestoreFieldType {
INT,
DOUBLE,
BOOL,
STRING,
BINARY
}

View File

@@ -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);
}
}
}

View File

@@ -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.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
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, SerializerFeature.WriteMapNullValue));
}
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() {
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1 @@
tablestore=com.alibaba.otter.canal.client.adapter.tablestore.TablestoreAdapter

View File

@@ -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 # 批量提交的大小

View File

@@ -1,9 +1,9 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" ">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" ">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId>
<version>1.1.5-SNAPSHOT</version>
<version>1.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.alibaba.otter</groupId>
@@ -132,6 +132,12 @@
<version>1.1.1</version>
<scope>provided</scope>
</dependency>
<!-- Pulsar -->
<dependency>
<groupId>org.apache.pulsar</groupId>
<artifactId>pulsar-client</artifactId>
<scope>provided</scope>
</dependency>
<!-- junit -->
<dependency>

View File

@@ -28,19 +28,14 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
private IZkChildListener childListener; // 监听所有的服务器列表
private IZkDataListener dataListener; // 监听当前的工作节点
private ZkClientx zkClient;
private volatile List<InetSocketAddress> currentAddress = new ArrayList<InetSocketAddress>();
private volatile List<InetSocketAddress> currentAddress = new ArrayList<>();
private volatile InetSocketAddress runningAddress = null;
public ClusterNodeAccessStrategy(String destination, ZkClientx zkClient){
this.destination = destination;
this.zkClient = zkClient;
childListener = new IZkChildListener() {
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
initClusters(currentChilds);
}
};
// handleChildChange
childListener = (parentPath, currentChilds) -> initClusters(currentChilds);
dataListener = new IZkDataListener() {
@@ -80,9 +75,9 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
private void initClusters(List<String> currentChilds) {
if (currentChilds == null || currentChilds.isEmpty()) {
currentAddress = new ArrayList<InetSocketAddress>();
currentAddress = new ArrayList<>();
} else {
List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>();
List<InetSocketAddress> addresses = new ArrayList<>();
for (String address : currentChilds) {
String[] strs = StringUtils.split(address, ":");
if (strs != null && strs.length == 2) {

View File

@@ -64,7 +64,7 @@ public class SimpleCanalConnector implements CanalConnector {
private SocketChannel channel;
private ReadableByteChannel readableChannel;
private WritableByteChannel writableChannel;
private List<Compression> supportedCompressions = new ArrayList<Compression>();
private List<Compression> supportedCompressions = new ArrayList<>();
private ClientIdentity clientIdentity;
private ClientRunningMonitor runningMonitor; // 运行控制
private ZkClientx zkClientx;

View File

@@ -14,7 +14,7 @@ import com.alibaba.otter.canal.client.CanalNodeAccessStrategy;
*/
public class SimpleNodeAccessStrategy implements CanalNodeAccessStrategy {
private List<SocketAddress> nodes = new ArrayList<SocketAddress>();
private List<SocketAddress> nodes = new ArrayList<>();
private int index = 0;
public SimpleNodeAccessStrategy(List<? extends SocketAddress> nodes){

View File

@@ -72,12 +72,7 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
initRunning();
} else {
// 否则就是等待delayTime,避免因网络瞬端或者zk异常,导致出现频繁的切换操作
delayExector.schedule(new Runnable() {
public void run() {
initRunning();
}
}, delayTime, TimeUnit.SECONDS);
delayExector.schedule(() -> initRunning(), delayTime, TimeUnit.SECONDS);
}
}

View File

@@ -80,11 +80,11 @@ public class KafkaCanalConnector implements CanalMQConnector {
connected = true;
if (kafkaConsumer == null && !flatMessage) {
kafkaConsumer = new KafkaConsumer<String, Message>(properties);
kafkaConsumer = new KafkaConsumer<>(properties);
}
if (kafkaConsumer2 == null && flatMessage) {
kafkaConsumer2 = new KafkaConsumer<String, String>(properties);
kafkaConsumer2 = new KafkaConsumer<>(properties);
}
}

View File

@@ -6,7 +6,6 @@ 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<>();
}
/**

View File

@@ -0,0 +1,458 @@
package com.alibaba.otter.canal.client.pulsarmq;
import com.alibaba.fastjson.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");
}
}

View File

@@ -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);

View File

@@ -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;
@@ -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) {

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