mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
Compare commits
186 Commits
canal-1.1.
...
canal-1.1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8496ceaf15 | ||
|
|
cf23e0ffea | ||
|
|
4e9e3f5abe | ||
|
|
cbab3fb561 | ||
|
|
f7bf4ac6ff | ||
|
|
df4c66b9e2 | ||
|
|
a6ee87281a | ||
|
|
90dab6831b | ||
|
|
839d5415fc | ||
|
|
29f03bf398 | ||
|
|
ec235f3efd | ||
|
|
aa89453bf6 | ||
|
|
cc97f3b331 | ||
|
|
89ec3037d4 | ||
|
|
e711f1a091 | ||
|
|
ad18e0335d | ||
|
|
67cc73b8e2 | ||
|
|
638952fad1 | ||
|
|
56ec66f90e | ||
|
|
c0bbe530f1 | ||
|
|
4bfc476e08 | ||
|
|
5d71d50d4e | ||
|
|
26700f3005 | ||
|
|
a7be9c75f9 | ||
|
|
b6f1e0d13c | ||
|
|
50630cc934 | ||
|
|
43db5bec97 | ||
|
|
affd598470 | ||
|
|
178bea07a8 | ||
|
|
fcdf95b5ec | ||
|
|
991418ce6e | ||
|
|
d34bee3359 | ||
|
|
0a3c1645e8 | ||
|
|
9dc4d78b3b | ||
|
|
bc056288d4 | ||
|
|
8aea31232b | ||
|
|
fc6c871382 | ||
|
|
14c0e4535a | ||
|
|
7ee7ba0e05 | ||
|
|
7992b883d2 | ||
|
|
5bc0c3f06f | ||
|
|
b457ecd387 | ||
|
|
997646b2db | ||
|
|
915849b83a | ||
|
|
129118b483 | ||
|
|
aa68408c53 | ||
|
|
d396a0b65c | ||
|
|
827b872cf8 | ||
|
|
6f3cae5ddf | ||
|
|
31bf92ec8e | ||
|
|
68d8aee6c3 | ||
|
|
c376f0afd2 | ||
|
|
60e5554125 | ||
|
|
241c43a894 | ||
|
|
fecc112196 | ||
|
|
dd04577994 | ||
|
|
51a299259b | ||
|
|
0a0a39880c | ||
|
|
5ad9cf3f16 | ||
|
|
d29fe5b04e | ||
|
|
20062d861d | ||
|
|
73de8b32a9 | ||
|
|
8af1301062 | ||
|
|
8c3ade2d50 | ||
|
|
752a3cd66a | ||
|
|
154ba422f2 | ||
|
|
03a9522191 | ||
|
|
c4947cad3c | ||
|
|
3f8cdafea3 | ||
|
|
fcaf8e88f8 | ||
|
|
e7ab25f819 | ||
|
|
b45a27e231 | ||
|
|
2e5b7b89af | ||
|
|
66912d3d05 | ||
|
|
ec1ac6d11c | ||
|
|
7cca8857ff | ||
|
|
e7086415f8 | ||
|
|
4a27d81da1 | ||
|
|
739425e99c | ||
|
|
cc1574d913 | ||
|
|
d7ab6e2dc1 | ||
|
|
808bedf984 | ||
|
|
357eb885b2 | ||
|
|
417e04f655 | ||
|
|
2d41bb9960 | ||
|
|
7d7c82ce91 | ||
|
|
0c3f7a806e | ||
|
|
2a38bca34b | ||
|
|
c5bd42bbf1 | ||
|
|
8c1f15a49e | ||
|
|
f9778a0aff | ||
|
|
2ac84b4320 | ||
|
|
0677443ccf | ||
|
|
2c1f3e649a | ||
|
|
4faf5428b1 | ||
|
|
b71472661b | ||
|
|
6fa111866f | ||
|
|
f7ea8df89e | ||
|
|
041225c3f2 | ||
|
|
a8d708ea1b | ||
|
|
5ae7112192 | ||
|
|
2278340c7b | ||
|
|
87abaee8a7 | ||
|
|
8f2e2b9ccd | ||
|
|
d2ecb0aad9 | ||
|
|
1126d26574 | ||
|
|
b71e299161 | ||
|
|
572ea9f27a | ||
|
|
bc298bedd5 | ||
|
|
202557630c | ||
|
|
c27d5fa162 | ||
|
|
3090557eeb | ||
|
|
6de9f7efa7 | ||
|
|
f2b8d000f7 | ||
|
|
8ee546d34d | ||
|
|
5b94c69d10 | ||
|
|
8ae192f0a9 | ||
|
|
2e1a1c31e6 | ||
|
|
238da873b2 | ||
|
|
edb5f294ef | ||
|
|
5a76e2aa58 | ||
|
|
1e20cac0da | ||
|
|
d8e411462e | ||
|
|
59e65ed4a6 | ||
|
|
4319b5e888 | ||
|
|
0d9695c3a5 | ||
|
|
359aa1a074 | ||
|
|
e608f09b53 | ||
|
|
beed10901d | ||
|
|
dab6fa5aa1 | ||
|
|
30d20f824b | ||
|
|
80cf533b05 | ||
|
|
6ff57f53f8 | ||
|
|
650446de5b | ||
|
|
347601b9fe | ||
|
|
5c8a5998d9 | ||
|
|
391e040c6a | ||
|
|
8597d9e926 | ||
|
|
a895ce666e | ||
|
|
9d113dbc13 | ||
|
|
5cc2f8f9f7 | ||
|
|
5b9ada1f8c | ||
|
|
369059fa39 | ||
|
|
9473f4e8fd | ||
|
|
685053599f | ||
|
|
a9727cf267 | ||
|
|
bc399ba9f9 | ||
|
|
81f403a9f2 | ||
|
|
332f759f96 | ||
|
|
5d5aafb7cd | ||
|
|
f42f7cfe72 | ||
|
|
96b9484f70 | ||
|
|
3bb408194d | ||
|
|
865ba94605 | ||
|
|
bb883cd228 | ||
|
|
d3e1ddd234 | ||
|
|
b9c4884af1 | ||
|
|
a6b16eedfa | ||
|
|
9d04a82c42 | ||
|
|
c9ad18894d | ||
|
|
b2b44555ca | ||
|
|
14e094b522 | ||
|
|
0dea4b7944 | ||
|
|
94a3cfa9de | ||
|
|
ed1feb9502 | ||
|
|
a4856b9a73 | ||
|
|
ff3ccde62a | ||
|
|
2a2977eca7 | ||
|
|
1d9e2ccedd | ||
|
|
dd8243a56d | ||
|
|
2c9acb655d | ||
|
|
e1191b665e | ||
|
|
e4e23c9ecd | ||
|
|
4ce2b94241 | ||
|
|
11ac253c46 | ||
|
|
1d2e85672c | ||
|
|
82b8b56aa3 | ||
|
|
4e18d7800a | ||
|
|
7efd067bd5 | ||
|
|
4c7129115c | ||
|
|
0f14ed926b | ||
|
|
be5f1546ca | ||
|
|
f3160bf6cb | ||
|
|
fa79db65b7 | ||
|
|
d134aa0c20 | ||
|
|
b9f40f326f |
@@ -19,14 +19,20 @@ client-adapter分为适配器和启动器两部分, 适配器为多个fat jar,
|
|||||||
|
|
||||||
启动器为 SpringBoot 项目, 支持canal-client启动的同时提供相关REST管理接口, 运行目录结构为:
|
启动器为 SpringBoot 项目, 支持canal-client启动的同时提供相关REST管理接口, 运行目录结构为:
|
||||||
```
|
```
|
||||||
canal-adapter-launcher.jar
|
- bin
|
||||||
|
restart.sh
|
||||||
|
startup.bat
|
||||||
|
startup.sh
|
||||||
|
stop.sh
|
||||||
- lib
|
- lib
|
||||||
client-adapter.logger-1.1.1-jar-with-dependencies.jar
|
client-adapter.logger-1.1.1-jar-with-dependencies.jar
|
||||||
client-adapter.hbase-1.1.1-jar-with-dependencies.jar
|
client-adapter.hbase-1.1.1-jar-with-dependencies.jar
|
||||||
- config
|
...
|
||||||
|
- conf
|
||||||
application.yml
|
application.yml
|
||||||
- hbase
|
- hbase
|
||||||
mytest_person2.yml
|
mytest_person2.yml
|
||||||
|
- logs
|
||||||
```
|
```
|
||||||
以上目录结构最终会打包成 canal-adapter-launcher.tar.gz 压缩包
|
以上目录结构最终会打包成 canal-adapter-launcher.tar.gz 压缩包
|
||||||
|
|
||||||
@@ -96,7 +102,7 @@ curl http://127.0.0.1:8081/count/hbase/mytest_person2.yml
|
|||||||
```
|
```
|
||||||
### 2.3 启动canal-adapter示例
|
### 2.3 启动canal-adapter示例
|
||||||
#### 2.3.1 启动canal server (单机模式), 参考: [Canal QuickStart](https://github.com/alibaba/canal/wiki/QuickStart)
|
#### 2.3.1 启动canal server (单机模式), 参考: [Canal QuickStart](https://github.com/alibaba/canal/wiki/QuickStart)
|
||||||
#### 2.3.2 修改config/application.yml为:
|
#### 2.3.2 修改conf/application.yml为:
|
||||||
```
|
```
|
||||||
server:
|
server:
|
||||||
port: 8081
|
port: 8081
|
||||||
@@ -118,9 +124,9 @@ canal.conf:
|
|||||||
- outAdapters:
|
- outAdapters:
|
||||||
- name: logger
|
- name: logger
|
||||||
```
|
```
|
||||||
启动 canal-adapter-launcher.jar
|
启动
|
||||||
```
|
```
|
||||||
java -jar canal-adapter-launcher.jar
|
bin/startup.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## 三、HBase适配器
|
## 三、HBase适配器
|
||||||
@@ -140,6 +146,11 @@ spring:
|
|||||||
canal.conf:
|
canal.conf:
|
||||||
canalServerHost: 127.0.0.1:11111
|
canalServerHost: 127.0.0.1:11111
|
||||||
flatMessage: true
|
flatMessage: true
|
||||||
|
srcDataSources:
|
||||||
|
defaultDS:
|
||||||
|
url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true
|
||||||
|
username: root
|
||||||
|
password: 121212
|
||||||
canalInstances:
|
canalInstances:
|
||||||
- instance: example
|
- instance: example
|
||||||
adapterGroups:
|
adapterGroups:
|
||||||
@@ -149,19 +160,10 @@ canal.conf:
|
|||||||
hbase.zookeeper.quorum: slave1
|
hbase.zookeeper.quorum: slave1
|
||||||
hbase.zookeeper.property.clientPort: 2181
|
hbase.zookeeper.property.clientPort: 2181
|
||||||
zookeeper.znode.parent: /hbase
|
zookeeper.znode.parent: /hbase
|
||||||
|
|
||||||
adapter.conf:
|
|
||||||
datasourceConfigs:
|
|
||||||
defaultDS:
|
|
||||||
url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true
|
|
||||||
username: root
|
|
||||||
password: 121212
|
|
||||||
adapterConfigs:
|
|
||||||
- hbase/mytest_person.yml
|
|
||||||
```
|
```
|
||||||
其中指定了一个HBase表映射文件: mytest_person.yml
|
adapter将会自动加载 conf/hbase 下的所有.yml结尾的配置文件
|
||||||
### 3.2 适配器表映射文件
|
### 3.2 适配器表映射文件
|
||||||
修改 config/hbase/mytest_person.yml文件:
|
修改 conf/hbase/mytest_person.yml文件:
|
||||||
```
|
```
|
||||||
dataSourceKey: defaultDS # 对应application.yml中的datasourceConfigs下的配置
|
dataSourceKey: defaultDS # 对应application.yml中的datasourceConfigs下的配置
|
||||||
hbaseMapping: # mysql--HBase的单表映射配置
|
hbaseMapping: # mysql--HBase的单表映射配置
|
||||||
@@ -245,7 +247,299 @@ create 'MYTEST.PERSON', {NAME=>'CF'}
|
|||||||
```
|
```
|
||||||
#### 启动canal-adapter启动器
|
#### 启动canal-adapter启动器
|
||||||
```
|
```
|
||||||
java -jar canal-adapter-launcher.jar
|
bin/startup.sh
|
||||||
```
|
```
|
||||||
#### 验证
|
#### 验证
|
||||||
修改mysql mytest.person表的数据, 将会自动同步到HBase的MYTEST.PERSON表下面, 并会打出DML的log
|
修改mysql mytest.person表的数据, 将会自动同步到HBase的MYTEST.PERSON表下面, 并会打出DML的log
|
||||||
|
|
||||||
|
|
||||||
|
## 四、关系型数据库适配器
|
||||||
|
|
||||||
|
RDB adapter 用于适配mysql到任意关系型数据库(需支持jdbc)的数据同步及导入
|
||||||
|
|
||||||
|
### 4.1 修改启动器配置: application.yml, 这里以oracle目标库为例
|
||||||
|
```
|
||||||
|
server:
|
||||||
|
port: 8081
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.alibaba.otter.canal.client.adapter.rdb: DEBUG
|
||||||
|
......
|
||||||
|
|
||||||
|
canal.conf:
|
||||||
|
canalServerHost: 127.0.0.1:11111
|
||||||
|
srcDataSources:
|
||||||
|
defaultDS:
|
||||||
|
url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true
|
||||||
|
username: root
|
||||||
|
password: 121212
|
||||||
|
canalInstances:
|
||||||
|
- instance: example
|
||||||
|
groups:
|
||||||
|
- outAdapters:
|
||||||
|
- name: rdb # 指定为rdb类型同步
|
||||||
|
key: oracle1 # 指定adapter的唯一key, 与表映射配置中outerAdapterKey对应
|
||||||
|
properties:
|
||||||
|
jdbc.driverClassName: oracle.jdbc.OracleDriver # jdbc驱动名, jdbc的jar包需要自行放致lib目录下
|
||||||
|
jdbc.url: jdbc:oracle:thin:@localhost:49161:XE # jdbc url
|
||||||
|
jdbc.username: mytest # jdbc username
|
||||||
|
jdbc.password: m121212 # jdbc password
|
||||||
|
threads: 5 # 并行执行的线程数, 默认为1
|
||||||
|
commitSize: 3000 # 批次提交的最大行数
|
||||||
|
```
|
||||||
|
其中 outAdapter 的配置: name统一为rdb, key为对应的数据源的唯一标识需和下面的表映射文件中的outerAdapterKey对应, properties为目标库jdb的相关参数
|
||||||
|
adapter将会自动加载 conf/rdb 下的所有.yml结尾的表映射配置文件
|
||||||
|
|
||||||
|
### 4.2 适配器表映射文件
|
||||||
|
修改 conf/rdb/mytest_user.yml文件:
|
||||||
|
```
|
||||||
|
dataSourceKey: defaultDS # 源数据源的key, 对应上面配置的srcDataSources中的值
|
||||||
|
destination: example # cannal的instance或者MQ的topic
|
||||||
|
outerAdapterKey: oracle1 # adapter key, 对应上面配置outAdapters中的key
|
||||||
|
concurrent: true # 是否按主键hase并行同步, 并行同步的表必须保证主键不会更改及主键不能为其他同步表的外键!!
|
||||||
|
dbMapping:
|
||||||
|
database: mytest # 源数据源的database/shcema
|
||||||
|
table: user # 源数据源表名
|
||||||
|
targetTable: mytest.tb_user # 目标数据源的库名.表名
|
||||||
|
targetPk: # 主键映射
|
||||||
|
id: id # 如果是复合主键可以换行映射多个
|
||||||
|
# mapAll: true # 是否整表映射, 要求源表和目标表字段名一模一样 (如果targetColumns也配置了映射,则以targetColumns配置为准)
|
||||||
|
targetColumns: # 字段映射, 格式: 目标表字段: 源表字段, 如果字段名一样源表字段名可不填
|
||||||
|
id:
|
||||||
|
name:
|
||||||
|
role_id:
|
||||||
|
c_time:
|
||||||
|
test1:
|
||||||
|
```
|
||||||
|
导入的类型以目标表的元类型为准, 将自动转换
|
||||||
|
|
||||||
|
### 4.3 启动RDB数据同步
|
||||||
|
#### 将目标库的jdbc jar包放入lib文件夹, 这里放入ojdbc6.jar
|
||||||
|
|
||||||
|
#### 启动canal-adapter启动器
|
||||||
|
```
|
||||||
|
bin/startup.sh
|
||||||
|
```
|
||||||
|
#### 验证
|
||||||
|
修改mysql mytest.user表的数据, 将会自动同步到Oracle的MYTEST.TB_USER表下面, 并会打出DML的log
|
||||||
|
|
||||||
|
|
||||||
|
## 五、ElasticSearch适配器
|
||||||
|
### 5.1 修改启动器配置: application.yml
|
||||||
|
```
|
||||||
|
server:
|
||||||
|
port: 8081
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.alibaba.otter.canal.client.adapter.es: DEBUG
|
||||||
|
spring:
|
||||||
|
jackson:
|
||||||
|
date-format: yyyy-MM-dd HH:mm:ss
|
||||||
|
time-zone: GMT+8
|
||||||
|
default-property-inclusion: non_null
|
||||||
|
|
||||||
|
canal.conf:
|
||||||
|
canalServerHost: 127.0.0.1:11111
|
||||||
|
flatMessage: true
|
||||||
|
srcDataSources:
|
||||||
|
defaultDS:
|
||||||
|
url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true
|
||||||
|
username: root
|
||||||
|
password: 121212
|
||||||
|
canalInstances:
|
||||||
|
- instance: example
|
||||||
|
adapterGroups:
|
||||||
|
- outAdapters:
|
||||||
|
- name: es
|
||||||
|
hosts: 127.0.0.1:9300 # es 集群地址, 逗号分隔
|
||||||
|
properties:
|
||||||
|
cluster.name: elasticsearch # es cluster name
|
||||||
|
```
|
||||||
|
adapter将会自动加载 conf/es 下的所有.yml结尾的配置文件
|
||||||
|
### 5.2 适配器表映射文件
|
||||||
|
修改 conf/es/mytest_user.yml文件:
|
||||||
|
```
|
||||||
|
dataSourceKey: defaultDS # 源数据源的key, 对应上面配置的srcDataSources中的值
|
||||||
|
destination: example # cannal的instance或者MQ的topic
|
||||||
|
esMapping:
|
||||||
|
_index: mytest_user # es 的索引名称
|
||||||
|
_type: _doc # es 的doc名称
|
||||||
|
_id: _id # es 的_id, 如果不配置该项必须配置下面的pk项_id则会由es自动分配
|
||||||
|
# pk: id # 如果不需要_id, 则需要指定一个属性为主键属性
|
||||||
|
# sql映射
|
||||||
|
sql: "select a.id as _id, a.name as _name, a.role_id as _role_id, b.role_name as _role_name,
|
||||||
|
a.c_time as _c_time, c.labels as _labels from user a
|
||||||
|
left join role b on b.id=a.role_id
|
||||||
|
left join (select user_id, group_concat(label order by id desc separator ';') as labels from label
|
||||||
|
group by user_id) c on c.user_id=a.id"
|
||||||
|
# objFields:
|
||||||
|
# _labels: array:; # 数组或者对象属性, array:; 代表以;字段里面是以;分隔的
|
||||||
|
# _obj: obj:{"test":"123"}
|
||||||
|
etlCondition: "where a.c_time>='{0}'" # etl 的条件参数
|
||||||
|
commitBatch: 3000 # 提交批大小
|
||||||
|
```
|
||||||
|
sql映射说明:
|
||||||
|
|
||||||
|
sql支持多表关联自由组合, 但是有一定的限制:
|
||||||
|
1. 主表不能为子查询语句
|
||||||
|
2. 只能使用left outer join即最左表一定要是主表
|
||||||
|
3. 关联从表如果是子查询不能有多张表
|
||||||
|
4. 主sql中不能有where查询条件(从表子查询中可以有where条件但是不推荐, 可能会造成数据同步的不一致, 比如修改了where条件中的字段内容)
|
||||||
|
5. 关联条件只允许主外键的'='操作不能出现其他常量判断比如: on a.role_id=b.id and b.statues=1
|
||||||
|
6. 关联条件必须要有一个字段出现在主查询语句中比如: on a.role_id=b.id 其中的 a.role_id 或者 b.id 必须出现在主select语句中
|
||||||
|
|
||||||
|
|
||||||
|
Elastic Search的mapping 属性与sql的查询值将一一对应(不支持 select *), 比如: select a.id as _id, a.name, a.email as _email from user, 其中name将映射到es mapping的name field, _email将
|
||||||
|
映射到mapping的_email field, 这里以别名(如果有别名)作为最终的映射字段. 这里的_id可以填写到配置文件的 _id: _id映射.
|
||||||
|
|
||||||
|
#### 5.2.1.单表映射索引示例sql:
|
||||||
|
```
|
||||||
|
select a.id as _id, a.name, a.role_id, a.c_time from user a
|
||||||
|
```
|
||||||
|
该sql对应的es mapping示例:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"mytest_user": {
|
||||||
|
"mappings": {
|
||||||
|
"_doc": {
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"role_id": {
|
||||||
|
"type": "long"
|
||||||
|
},
|
||||||
|
"c_time": {
|
||||||
|
"type": "date"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.2.2.单表映射索引示例sql带函数或运算操作:
|
||||||
|
```
|
||||||
|
select a.id as _id, concat(a.name,'_test') as name, a.role_id+10000 as role_id, a.c_time from user a
|
||||||
|
```
|
||||||
|
函数字段后必须跟上别名, 该sql对应的es mapping示例:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"mytest_user": {
|
||||||
|
"mappings": {
|
||||||
|
"_doc": {
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"role_id": {
|
||||||
|
"type": "long"
|
||||||
|
},
|
||||||
|
"c_time": {
|
||||||
|
"type": "date"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.2.3.多表映射(一对一, 多对一)索引示例sql:
|
||||||
|
```
|
||||||
|
select a.id as _id, a.name, a.role_id, b.role_name, a.c_time from user a
|
||||||
|
left join role b on b.id = a.role_id
|
||||||
|
```
|
||||||
|
注:这里join操作只能是left outer join, 第一张表必须为主表!!
|
||||||
|
|
||||||
|
该sql对应的es mapping示例:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"mytest_user": {
|
||||||
|
"mappings": {
|
||||||
|
"_doc": {
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"role_id": {
|
||||||
|
"type": "long"
|
||||||
|
},
|
||||||
|
"role_name": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"c_time": {
|
||||||
|
"type": "date"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.2.4.多表映射(一对多)索引示例sql:
|
||||||
|
```
|
||||||
|
select a.id as _id, a.name, a.role_id, c.labels, a.c_time from user a
|
||||||
|
left join (select user_id, group_concat(label order by id desc separator ';') as labels from label
|
||||||
|
group by user_id) c on c.user_id=a.id
|
||||||
|
```
|
||||||
|
注:left join 后的子查询只允许一张表, 即子查询中不能再包含子查询或者关联!!
|
||||||
|
|
||||||
|
该sql对应的es mapping示例:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"mytest_user": {
|
||||||
|
"mappings": {
|
||||||
|
"_doc": {
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"role_id": {
|
||||||
|
"type": "long"
|
||||||
|
},
|
||||||
|
"c_time": {
|
||||||
|
"type": "date"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.2.5.其它类型的sql示例:
|
||||||
|
- geo type
|
||||||
|
```
|
||||||
|
select ... concat(IFNULL(a.latitude, 0), ',', IFNULL(a.longitude, 0)) AS location, ...
|
||||||
|
```
|
||||||
|
- 复合主键
|
||||||
|
```
|
||||||
|
select concat(a.id,'_',b.type) as _id, ... from user a left join role b on b.id=a.role_id
|
||||||
|
```
|
||||||
|
- 数组字段
|
||||||
|
```
|
||||||
|
select a.id as _id, a.name, a.role_id, c.labels, a.c_time from user a
|
||||||
|
left join (select user_id, group_concat(label order by id desc separator ';') as labels from label
|
||||||
|
group by user_id) c on c.user_id=a.id
|
||||||
|
```
|
||||||
|
配置中使用:
|
||||||
|
```
|
||||||
|
objFields:
|
||||||
|
labels: array:;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 启动ES数据同步
|
||||||
|
#### 启动canal-adapter启动器
|
||||||
|
```
|
||||||
|
bin/startup.sh
|
||||||
|
```
|
||||||
|
#### 验证
|
||||||
|
1. 新增mysql mytest.user表的数据, 将会自动同步到es的mytest_user索引下面, 并会打出DML的log
|
||||||
|
2. 修改mysql mytest.role表的role_name, 将会自动同步es的mytest_user索引中的role_name数据
|
||||||
|
3. 新增或者修改mysql mytest.label表的label, 将会自动同步es的mytest_user索引中的labels数据
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.1</version>
|
<version>1.1.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal.protocol</artifactId>
|
<artifactId>canal.protocol</artifactId>
|
||||||
<version>1.1.1</version>
|
<version>1.1.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>joda-time</groupId>
|
<groupId>joda-time</groupId>
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ public interface OuterAdapter {
|
|||||||
/**
|
/**
|
||||||
* 往适配器中同步数据
|
* 往适配器中同步数据
|
||||||
*
|
*
|
||||||
* @param dml 数据包
|
* @param dmls 数据包
|
||||||
*/
|
*/
|
||||||
void sync(Dml dml);
|
void sync(List<Dml> dmls);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 外部适配器销毁接口
|
* 外部适配器销毁接口
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
package com.alibaba.otter.canal.client.adapter.support;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 适配器配置集合, 用于配置加载, 线程不安全
|
|
||||||
*
|
|
||||||
* @author rewerma @ 2018-10-20
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
|
||||||
public class AdapterConfigs {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 类型下对应所有配置名, 如:
|
|
||||||
* hbase
|
|
||||||
* ┗━ mytest_person.yml
|
|
||||||
* ┗━ mytest_role.yml
|
|
||||||
* ┗━ mytest_department.yml
|
|
||||||
*/
|
|
||||||
private static Map<String, Set<String>> configs = new HashMap<>();
|
|
||||||
|
|
||||||
public static void put(String key, String value) {
|
|
||||||
Set<String> values = configs.get(key);
|
|
||||||
if (values == null) {
|
|
||||||
values = new LinkedHashSet<>();
|
|
||||||
}
|
|
||||||
values.add(value);
|
|
||||||
configs.put(key, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Set<String> get(String key) {
|
|
||||||
return configs.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clear() {
|
|
||||||
configs.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package com.alibaba.otter.canal.client.adapter.support;
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置信息类
|
* 配置信息类
|
||||||
@@ -11,17 +12,30 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class CanalClientConfig {
|
public class CanalClientConfig {
|
||||||
|
|
||||||
private String canalServerHost; // 单机模式下canal server的 ip:port
|
// 单机模式下canal server的ip:port
|
||||||
|
private String canalServerHost;
|
||||||
|
// 集群模式下的zk地址,如果配置了单机地址则以单机为准!!
|
||||||
|
private String zookeeperHosts;
|
||||||
|
// kafka or rocket mq 地址
|
||||||
|
private String mqServers;
|
||||||
|
// 是否已flatMessage模式传输,只适用于mq模式
|
||||||
|
private Boolean flatMessage = true;
|
||||||
|
// 批大小
|
||||||
|
private Integer batchSize;
|
||||||
|
// 同步分批提交大小
|
||||||
|
private Integer syncBatchSize = 1000;
|
||||||
|
// 重试次数
|
||||||
|
private Integer retries;
|
||||||
|
// 消费超时时间
|
||||||
|
private Long timeout;
|
||||||
|
// 模式 tcp kafka rocketMQ
|
||||||
|
private String mode = "tcp";
|
||||||
|
// aliyun ak/sk
|
||||||
|
private String accessKey;
|
||||||
|
private String secretKey;
|
||||||
|
|
||||||
private String zookeeperHosts; // 集群模式下的zk地址, 如果配置了单机地址则以单机为准!!
|
// canal adapters 配置
|
||||||
|
private List<CanalAdapter> canalAdapters;
|
||||||
private String bootstrapServers; // kafka or rocket mq 地址
|
|
||||||
|
|
||||||
private Boolean flatMessage = true; // 是否已flatMessage模式传输, 只适用于mq模式
|
|
||||||
|
|
||||||
private List<MQTopic> mqTopics; // mq topic 列表
|
|
||||||
|
|
||||||
private List<CanalInstance> canalInstances; // tcp 模式下 canal 实例列表, 与mq模式不能共存!!
|
|
||||||
|
|
||||||
public String getCanalServerHost() {
|
public String getCanalServerHost() {
|
||||||
return canalServerHost;
|
return canalServerHost;
|
||||||
@@ -39,20 +53,12 @@ public class CanalClientConfig {
|
|||||||
this.zookeeperHosts = zookeeperHosts;
|
this.zookeeperHosts = zookeeperHosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBootstrapServers() {
|
public String getMqServers() {
|
||||||
return bootstrapServers;
|
return mqServers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBootstrapServers(String bootstrapServers) {
|
public void setMqServers(String mqServers) {
|
||||||
this.bootstrapServers = bootstrapServers;
|
this.mqServers = mqServers;
|
||||||
}
|
|
||||||
|
|
||||||
public List<MQTopic> getMqTopics() {
|
|
||||||
return mqTopics;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMqTopics(List<MQTopic> mqTopics) {
|
|
||||||
this.mqTopics = mqTopics;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean getFlatMessage() {
|
public Boolean getFlatMessage() {
|
||||||
@@ -63,17 +69,73 @@ public class CanalClientConfig {
|
|||||||
this.flatMessage = flatMessage;
|
this.flatMessage = flatMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CanalInstance> getCanalInstances() {
|
public Integer getBatchSize() {
|
||||||
return canalInstances;
|
return batchSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCanalInstances(List<CanalInstance> canalInstances) {
|
public void setBatchSize(Integer batchSize) {
|
||||||
this.canalInstances = canalInstances;
|
this.batchSize = batchSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CanalInstance {
|
public Integer getRetries() {
|
||||||
|
return retries;
|
||||||
|
}
|
||||||
|
|
||||||
private String instance; // 实例名
|
public Integer getSyncBatchSize() {
|
||||||
|
return syncBatchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSyncBatchSize(Integer syncBatchSize) {
|
||||||
|
this.syncBatchSize = syncBatchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRetries(Integer retries) {
|
||||||
|
this.retries = retries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTimeout() {
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeout(Long timeout) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMode(String mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAccessKey() {
|
||||||
|
return accessKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccessKey(String accessKey) {
|
||||||
|
this.accessKey = accessKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSecretKey() {
|
||||||
|
return secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecretKey(String secretKey) {
|
||||||
|
this.secretKey = secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CanalAdapter> getCanalAdapters() {
|
||||||
|
return canalAdapters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanalAdapters(List<CanalAdapter> canalAdapters) {
|
||||||
|
this.canalAdapters = canalAdapters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CanalAdapter {
|
||||||
|
|
||||||
|
private String instance; // 实例名
|
||||||
|
|
||||||
private List<Group> groups; // 适配器分组列表
|
private List<Group> groups; // 适配器分组列表
|
||||||
|
|
||||||
@@ -94,60 +156,14 @@ public class CanalClientConfig {
|
|||||||
public void setGroups(List<Group> groups) {
|
public void setGroups(List<Group> groups) {
|
||||||
this.groups = groups;
|
this.groups = groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Group {
|
public static class Group {
|
||||||
|
|
||||||
private List<OuterAdapterConfig> outAdapters; // 适配器列表
|
// group id
|
||||||
|
private String groupId = "default";
|
||||||
public List<OuterAdapterConfig> getOutAdapters() {
|
private List<OuterAdapterConfig> outerAdapters; // 适配器列表
|
||||||
return outAdapters;
|
private Map<String, OuterAdapterConfig> outerAdaptersMap = new LinkedHashMap<>();
|
||||||
}
|
|
||||||
|
|
||||||
public void setOutAdapters(List<OuterAdapterConfig> outAdapters) {
|
|
||||||
this.outAdapters = outAdapters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class MQTopic {
|
|
||||||
|
|
||||||
private String mqMode; // mq模式 kafka or rocketMQ
|
|
||||||
|
|
||||||
private String topic; // topic名
|
|
||||||
|
|
||||||
private List<MQGroup> groups = new ArrayList<>(); // 分组列表
|
|
||||||
|
|
||||||
public String getMqMode() {
|
|
||||||
return mqMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMqMode(String mqMode) {
|
|
||||||
this.mqMode = mqMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTopic() {
|
|
||||||
return topic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTopic(String topic) {
|
|
||||||
this.topic = topic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<MQGroup> getGroups() {
|
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGroups(List<MQGroup> groups) {
|
|
||||||
this.groups = groups;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class MQGroup {
|
|
||||||
|
|
||||||
private String groupId; // group id
|
|
||||||
|
|
||||||
private List<OuterAdapterConfig> outAdapters; // 适配器配置列表
|
|
||||||
|
|
||||||
public String getGroupId() {
|
public String getGroupId() {
|
||||||
return groupId;
|
return groupId;
|
||||||
@@ -157,14 +173,20 @@ public class CanalClientConfig {
|
|||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OuterAdapterConfig> getOutAdapters() {
|
public List<OuterAdapterConfig> getOuterAdapters() {
|
||||||
return outAdapters;
|
return outerAdapters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOutAdapters(List<OuterAdapterConfig> outAdapters) {
|
public void setOuterAdapters(List<OuterAdapterConfig> outerAdapters) {
|
||||||
this.outAdapters = outAdapters;
|
this.outerAdapters = outerAdapters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, OuterAdapterConfig> getOuterAdaptersMap() {
|
||||||
|
return outerAdaptersMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOuterAdaptersMap(Map<String, OuterAdapterConfig> outerAdaptersMap) {
|
||||||
|
this.outerAdaptersMap = outerAdaptersMap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
public class Constant {
|
||||||
|
|
||||||
|
public static final String CONF_DIR = "conf";
|
||||||
|
}
|
||||||
@@ -1,15 +1,21 @@
|
|||||||
package com.alibaba.otter.canal.client.adapter.support;
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.*;
|
import java.util.Arrays;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -21,8 +27,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
*/
|
*/
|
||||||
public class ExtensionLoader<T> {
|
public class ExtensionLoader<T> {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory
|
private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
|
||||||
.getLogger(ExtensionLoader.class);
|
|
||||||
|
|
||||||
private static final String SERVICES_DIRECTORY = "META-INF/services/";
|
private static final String SERVICES_DIRECTORY = "META-INF/services/";
|
||||||
|
|
||||||
@@ -30,13 +35,14 @@ public class ExtensionLoader<T> {
|
|||||||
|
|
||||||
private static final String DEFAULT_CLASSLOADER_POLICY = "internal";
|
private static final String DEFAULT_CLASSLOADER_POLICY = "internal";
|
||||||
|
|
||||||
private static final Pattern NAME_SEPARATOR = Pattern
|
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
|
||||||
.compile("\\s*[,]+\\s*");
|
|
||||||
|
|
||||||
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
|
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
|
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static final ConcurrentMap<String, Object> EXTENSION_KEY_INSTANCE = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private final Class<?> type;
|
private final Class<?> type;
|
||||||
|
|
||||||
private final String classLoaderPolicy;
|
private final String classLoaderPolicy;
|
||||||
@@ -83,47 +89,6 @@ public class ExtensionLoader<T> {
|
|||||||
this.classLoaderPolicy = classLoaderPolicy;
|
this.classLoaderPolicy = classLoaderPolicy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getExtensionName(T extensionInstance) {
|
|
||||||
return getExtensionName(extensionInstance.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getExtensionName(Class<?> extensionClass) {
|
|
||||||
return cachedNames.get(extensionClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConcurrentHashMap<String, IllegalStateException> getExceptions() {
|
|
||||||
return exceptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回扩展点实例,如果没有指定的扩展点或是还没加载(即实例化)则返回<code>null</code>注意:此方法不会触发扩展点的加载
|
|
||||||
* <p/>
|
|
||||||
* 一般应该调用{@link #getExtension(String)}方法获得扩展,这个方法会触发扩展点加载
|
|
||||||
*
|
|
||||||
* @see #getExtension(String)
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public T getLoadedExtension(String name) {
|
|
||||||
if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null");
|
|
||||||
Holder<Object> holder = cachedInstances.get(name);
|
|
||||||
if (holder == null) {
|
|
||||||
cachedInstances.putIfAbsent(name, new Holder<>());
|
|
||||||
holder = cachedInstances.get(name);
|
|
||||||
}
|
|
||||||
return (T) holder.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回已经加载的扩展点的名字
|
|
||||||
* <p/>
|
|
||||||
* 一般应该调用{@link #getSupportedExtensions()}方法获得扩展,这个方法会返回所有的扩展点
|
|
||||||
*
|
|
||||||
* @see #getSupportedExtensions()
|
|
||||||
*/
|
|
||||||
public Set<String> getLoadedExtensions() {
|
|
||||||
return Collections.unmodifiableSet(new TreeSet<>(cachedInstances.keySet()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回指定名字的扩展
|
* 返回指定名字的扩展
|
||||||
*
|
*
|
||||||
@@ -154,6 +119,31 @@ public class ExtensionLoader<T> {
|
|||||||
return (T) instance;
|
return (T) instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T getExtension(String name, String key) {
|
||||||
|
if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null");
|
||||||
|
if ("true".equals(name)) {
|
||||||
|
return getDefaultExtension();
|
||||||
|
}
|
||||||
|
String extKey = name + "-" + StringUtils.trimToEmpty(key);
|
||||||
|
Holder<Object> holder = cachedInstances.get(extKey);
|
||||||
|
if (holder == null) {
|
||||||
|
cachedInstances.putIfAbsent(extKey, new Holder<>());
|
||||||
|
holder = cachedInstances.get(extKey);
|
||||||
|
}
|
||||||
|
Object instance = holder.get();
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (holder) {
|
||||||
|
instance = holder.get();
|
||||||
|
if (instance == null) {
|
||||||
|
instance = createExtension(name, key);
|
||||||
|
holder.set(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (T) instance;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回缺省的扩展,如果没有设置则返回<code>null</code>
|
* 返回缺省的扩展,如果没有设置则返回<code>null</code>
|
||||||
*/
|
*/
|
||||||
@@ -165,87 +155,6 @@ public class ExtensionLoader<T> {
|
|||||||
return getExtension(cachedDefaultName);
|
return getExtension(cachedDefaultName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasExtension(String name) {
|
|
||||||
if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null");
|
|
||||||
try {
|
|
||||||
return getExtensionClass(name) != null;
|
|
||||||
} catch (Throwable t) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getSupportedExtensions() {
|
|
||||||
Map<String, Class<?>> clazzes = getExtensionClasses();
|
|
||||||
return Collections.unmodifiableSet(new TreeSet<String>(clazzes.keySet()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回缺省的扩展点名,如果没有设置缺省则返回<code>null</code>
|
|
||||||
*/
|
|
||||||
public String getDefaultExtensionName() {
|
|
||||||
getExtensionClasses();
|
|
||||||
return cachedDefaultName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 编程方式添加新扩展点
|
|
||||||
*
|
|
||||||
* @param name 扩展点名
|
|
||||||
* @param clazz 扩展点类
|
|
||||||
* @throws IllegalStateException 要添加扩展点名已经存在
|
|
||||||
*/
|
|
||||||
public void addExtension(String name, Class<?> clazz) {
|
|
||||||
getExtensionClasses(); // load classes
|
|
||||||
|
|
||||||
if (!type.isAssignableFrom(clazz)) {
|
|
||||||
throw new IllegalStateException("Input type " + clazz + "not implement Extension " + type);
|
|
||||||
}
|
|
||||||
if (clazz.isInterface()) {
|
|
||||||
throw new IllegalStateException("Input type " + clazz + "can not be interface!");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name == null || "".equals(name)) {
|
|
||||||
throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
|
|
||||||
}
|
|
||||||
if (cachedClasses.get().containsKey(name)) {
|
|
||||||
throw new IllegalStateException("Extension name " + name + " already existed(Extension " + type + ")!");
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedNames.put(clazz, name);
|
|
||||||
cachedClasses.get().put(name, clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 编程方式添加替换已有扩展点
|
|
||||||
*
|
|
||||||
* @param name 扩展点名
|
|
||||||
* @param clazz 扩展点类
|
|
||||||
* @throws IllegalStateException 要添加扩展点名已经存在
|
|
||||||
* @deprecated 不推荐应用使用,一般只在测试时可以使用
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void replaceExtension(String name, Class<?> clazz) {
|
|
||||||
getExtensionClasses(); // load classes
|
|
||||||
|
|
||||||
if (!type.isAssignableFrom(clazz)) {
|
|
||||||
throw new IllegalStateException("Input type " + clazz + "not implement Extension " + type);
|
|
||||||
}
|
|
||||||
if (clazz.isInterface()) {
|
|
||||||
throw new IllegalStateException("Input type " + clazz + "can not be interface!");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name == null || "".equals(name)) {
|
|
||||||
throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
|
|
||||||
}
|
|
||||||
if (!cachedClasses.get().containsKey(name)) {
|
|
||||||
throw new IllegalStateException("Extension name " + name + " not existed(Extension " + type + ")!");
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedNames.put(clazz, name);
|
|
||||||
cachedClasses.get().put(name, clazz);
|
|
||||||
cachedInstances.remove(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private T createExtension(String name) {
|
private T createExtension(String name) {
|
||||||
Class<?> clazz = getExtensionClasses().get(name);
|
Class<?> clazz = getExtensionClasses().get(name);
|
||||||
@@ -262,18 +171,28 @@ public class ExtensionLoader<T> {
|
|||||||
return instance;
|
return instance;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
|
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
|
||||||
+ ") could not be instantiated: " + t.getMessage(),
|
+ ") could not be instantiated: " + t.getMessage(), t);
|
||||||
t);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Class<?> getExtensionClass(String name) {
|
@SuppressWarnings("unchecked")
|
||||||
if (type == null) throw new IllegalArgumentException("Extension type == null");
|
private T createExtension(String name, String key) {
|
||||||
if (name == null) throw new IllegalArgumentException("Extension name == null");
|
|
||||||
Class<?> clazz = getExtensionClasses().get(name);
|
Class<?> clazz = getExtensionClasses().get(name);
|
||||||
if (clazz == null)
|
if (clazz == null) {
|
||||||
throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!");
|
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
|
||||||
return clazz;
|
+ ") could not be instantiated: class could not be found");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
T instance = (T) EXTENSION_KEY_INSTANCE.get(name + "-" + key);
|
||||||
|
if (instance == null) {
|
||||||
|
EXTENSION_KEY_INSTANCE.putIfAbsent(name + "-" + key, clazz.newInstance());
|
||||||
|
instance = (T) EXTENSION_KEY_INSTANCE.get(name + "-" + key);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
|
||||||
|
+ ") could not be instantiated: " + t.getMessage(), t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Class<?>> getExtensionClasses() {
|
private Map<String, Class<?>> getExtensionClasses() {
|
||||||
@@ -287,15 +206,19 @@ public class ExtensionLoader<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return classes;
|
return classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getJarDirectoryPath() {
|
private String getJarDirectoryPath() {
|
||||||
URL url = Thread.currentThread().getContextClassLoader().getResource("");
|
URL url = Thread.currentThread().getContextClassLoader().getResource("");
|
||||||
if (url == null) {
|
String dirtyPath;
|
||||||
throw new IllegalStateException("failed to get class loader resource");
|
if (url != null) {
|
||||||
|
dirtyPath = url.toString();
|
||||||
|
} else {
|
||||||
|
File file = new File("");
|
||||||
|
dirtyPath = file.getAbsolutePath();
|
||||||
}
|
}
|
||||||
String dirtyPath = url.toString();
|
|
||||||
String jarPath = dirtyPath.replaceAll("^.*file:/", ""); // removes
|
String jarPath = dirtyPath.replaceAll("^.*file:/", ""); // removes
|
||||||
// file:/ and
|
// file:/ and
|
||||||
// everything
|
// everything
|
||||||
@@ -329,25 +252,20 @@ public class ExtensionLoader<T> {
|
|||||||
|
|
||||||
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
|
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
|
||||||
|
|
||||||
// 1. lib folder,customized extension classLoader (jar_dir/lib)
|
// 1. plugin folder,customized extension classLoader (jar_dir/plugin)
|
||||||
String dir = File.separator + this.getJarDirectoryPath() + File.separator + "lib";
|
String dir = File.separator + this.getJarDirectoryPath() + File.separator + "plugin";
|
||||||
logger.info("extension classpath dir: " + dir);
|
|
||||||
File externalLibDir = new File(dir);
|
File externalLibDir = new File(dir);
|
||||||
if (!externalLibDir.exists()) {
|
if (!externalLibDir.exists()) {
|
||||||
externalLibDir = new File(
|
externalLibDir = new File(File.separator + this.getJarDirectoryPath() + File.separator + "canal-adapter"
|
||||||
File.separator + this.getJarDirectoryPath() + File.separator + "canal_client" + File.separator + "lib");
|
+ File.separator + "plugin");
|
||||||
}
|
}
|
||||||
|
logger.info("extension classpath dir: " + externalLibDir.getAbsolutePath());
|
||||||
if (externalLibDir.exists()) {
|
if (externalLibDir.exists()) {
|
||||||
File[] files = externalLibDir.listFiles(new FilenameFilter() {
|
File[] files = externalLibDir.listFiles((dir1, name) -> name.endsWith(".jar"));
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean accept(File dir, String name) {
|
|
||||||
return name.endsWith(".jar");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (files != null) {
|
if (files != null) {
|
||||||
for (File f : files) {
|
for (File f : files) {
|
||||||
URL url = null;
|
URL url;
|
||||||
try {
|
try {
|
||||||
url = f.toURI().toURL();
|
url = f.toURI().toURL();
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
@@ -358,49 +276,7 @@ public class ExtensionLoader<T> {
|
|||||||
URLClassLoader localClassLoader;
|
URLClassLoader localClassLoader;
|
||||||
if (classLoaderPolicy == null || "".equals(classLoaderPolicy)
|
if (classLoaderPolicy == null || "".equals(classLoaderPolicy)
|
||||||
|| DEFAULT_CLASSLOADER_POLICY.equalsIgnoreCase(classLoaderPolicy)) {
|
|| DEFAULT_CLASSLOADER_POLICY.equalsIgnoreCase(classLoaderPolicy)) {
|
||||||
localClassLoader = new URLClassLoader(new URL[] { url }, parent) {
|
localClassLoader = new URLClassExtensionLoader(new URL[] { url });
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
|
||||||
Class<?> c = findLoadedClass(name);
|
|
||||||
if (c != null) {
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.startsWith("java.") || name.startsWith("org.slf4j.")
|
|
||||||
|| name.startsWith("org.apache.logging")
|
|
||||||
|| name.startsWith("org.apache.commons.logging.")) {
|
|
||||||
// || name.startsWith("org.apache.hadoop."))
|
|
||||||
// {
|
|
||||||
c = super.loadClass(name);
|
|
||||||
}
|
|
||||||
if (c != null) return c;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 先加载jar内的class,可避免jar冲突
|
|
||||||
c = findClass(name);
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
c = null;
|
|
||||||
}
|
|
||||||
if (c != null) {
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.loadClass(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Enumeration<URL> getResources(String name) throws IOException {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
|
|
||||||
|
|
||||||
tmp[0] = findResources(name); // local class
|
|
||||||
// path first
|
|
||||||
// tmp[1] = super.getResources(name);
|
|
||||||
|
|
||||||
return new CompoundEnumeration<>(tmp);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
localClassLoader = new URLClassLoader(new URL[] { url }, parent);
|
localClassLoader = new URLClassLoader(new URL[] { url }, parent);
|
||||||
}
|
}
|
||||||
@@ -410,48 +286,15 @@ public class ExtensionLoader<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 只加载外部spi, 不加载classpath
|
||||||
// 2. load inner extension class with default classLoader
|
// 2. load inner extension class with default classLoader
|
||||||
ClassLoader classLoader = findClassLoader();
|
// ClassLoader classLoader = findClassLoader();
|
||||||
loadFile(extensionClasses, CANAL_DIRECTORY, classLoader);
|
// loadFile(extensionClasses, CANAL_DIRECTORY, classLoader);
|
||||||
loadFile(extensionClasses, SERVICES_DIRECTORY, classLoader);
|
// loadFile(extensionClasses, SERVICES_DIRECTORY, classLoader);
|
||||||
|
|
||||||
return extensionClasses;
|
return extensionClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CompoundEnumeration<E> implements Enumeration<E> {
|
|
||||||
|
|
||||||
private Enumeration<E>[] enums;
|
|
||||||
private int index = 0;
|
|
||||||
|
|
||||||
public CompoundEnumeration(Enumeration<E>[] enums){
|
|
||||||
this.enums = enums;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean next() {
|
|
||||||
while (this.index < this.enums.length) {
|
|
||||||
if (this.enums[this.index] != null && this.enums[this.index].hasMoreElements()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
++this.index;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasMoreElements() {
|
|
||||||
return this.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
public E nextElement() {
|
|
||||||
if (!this.next()) {
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
} else {
|
|
||||||
return this.enums[this.index].nextElement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadFile(Map<String, Class<?>> extensionClasses, String dir, ClassLoader classLoader) {
|
private void loadFile(Map<String, Class<?>> extensionClasses, String dir, ClassLoader classLoader) {
|
||||||
String fileName = dir + type.getName();
|
String fileName = dir + type.getName();
|
||||||
try {
|
try {
|
||||||
@@ -487,10 +330,12 @@ public class ExtensionLoader<T> {
|
|||||||
// Class.forName(line, true,
|
// Class.forName(line, true,
|
||||||
// classLoader);
|
// classLoader);
|
||||||
if (!type.isAssignableFrom(clazz)) {
|
if (!type.isAssignableFrom(clazz)) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException("Error when load extension class(interface: "
|
||||||
"Error when load extension class(interface: " + type
|
+ type
|
||||||
+ ", class line: " + clazz.getName()
|
+ ", class line: "
|
||||||
+ "), class " + clazz.getName()
|
+ clazz.getName()
|
||||||
|
+ "), class "
|
||||||
|
+ clazz.getName()
|
||||||
+ "is not subtype of interface.");
|
+ "is not subtype of interface.");
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
@@ -508,9 +353,9 @@ public class ExtensionLoader<T> {
|
|||||||
extensionClasses.put(n, clazz);
|
extensionClasses.put(n, clazz);
|
||||||
} else if (c != clazz) {
|
} else if (c != clazz) {
|
||||||
cachedNames.remove(clazz);
|
cachedNames.remove(clazz);
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException("Duplicate extension "
|
||||||
"Duplicate extension " + type.getName() + " name "
|
+ type.getName()
|
||||||
+ n + " on "
|
+ " name " + n + " on "
|
||||||
+ c.getName() + " and "
|
+ c.getName() + " and "
|
||||||
+ clazz.getName());
|
+ clazz.getName());
|
||||||
}
|
}
|
||||||
@@ -520,9 +365,12 @@ public class ExtensionLoader<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
IllegalStateException e = new IllegalStateException(
|
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: "
|
||||||
"Failed to load extension class(interface: " + type + ", class line: "
|
+ type
|
||||||
+ line + ") in " + url
|
+ ", class line: "
|
||||||
|
+ line
|
||||||
|
+ ") in "
|
||||||
|
+ url
|
||||||
+ ", cause: "
|
+ ", cause: "
|
||||||
+ t.getMessage(),
|
+ t.getMessage(),
|
||||||
t);
|
t);
|
||||||
@@ -537,18 +385,17 @@ public class ExtensionLoader<T> {
|
|||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.error("Exception when load extension class(interface: " + type + ", class file: " + url
|
logger.error("Exception when load extension class(interface: " + type + ", class file: " + url
|
||||||
+ ") in " + url,
|
+ ") in " + url, t);
|
||||||
t);
|
|
||||||
}
|
}
|
||||||
} // end of while urls
|
} // end of while urls
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.error(
|
logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName
|
||||||
"Exception when load extension class(interface: " + type + ", description file: " + fileName + ").",
|
+ ").", t);
|
||||||
t);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
private static ClassLoader findClassLoader() {
|
private static ClassLoader findClassLoader() {
|
||||||
return ExtensionLoader.class.getClassLoader();
|
return ExtensionLoader.class.getClassLoader();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class MappingConfigsLoader {
|
||||||
|
|
||||||
|
public static Map<String, String> loadConfigs(String name) {
|
||||||
|
Map<String, String> configContentMap = new HashMap<>();
|
||||||
|
|
||||||
|
// 先取本地文件,再取类路径
|
||||||
|
File configDir = new File(".." + File.separator + Constant.CONF_DIR + File.separator + name);
|
||||||
|
if (!configDir.exists()) {
|
||||||
|
URL url = MappingConfigsLoader.class.getClassLoader().getResource("");
|
||||||
|
if (url != null) {
|
||||||
|
configDir = new File(url.getPath() + name + File.separator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File[] files = configDir.listFiles();
|
||||||
|
if (files != null) {
|
||||||
|
for (File file : files) {
|
||||||
|
String fileName = file.getName();
|
||||||
|
if (!fileName.endsWith(".yml")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try (InputStream in = new FileInputStream(file)) {
|
||||||
|
byte[] bytes = new byte[in.available()];
|
||||||
|
in.read(bytes);
|
||||||
|
String configContent = new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
configContentMap.put(fileName, configContent);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Read " + name + "mapping config: " + fileName + " error. ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return configContentMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String loadConfig(String name) {
|
||||||
|
// 先取本地文件,再取类路径
|
||||||
|
File filePath = new File(".." + File.separator + Constant.CONF_DIR + File.separator + name);
|
||||||
|
if (!filePath.exists()) {
|
||||||
|
URL url = MappingConfigsLoader.class.getClassLoader().getResource("");
|
||||||
|
if (url != null) {
|
||||||
|
filePath = new File(url.getPath() + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filePath.exists()) {
|
||||||
|
String fileName = filePath.getName();
|
||||||
|
if (!fileName.endsWith(".yml")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try (InputStream in = new FileInputStream(filePath)) {
|
||||||
|
byte[] bytes = new byte[in.available()];
|
||||||
|
in.read(bytes);
|
||||||
|
return new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Read mapping config: " + filePath.getAbsolutePath() + " error. ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,12 +19,12 @@ import com.alibaba.otter.canal.protocol.Message;
|
|||||||
*/
|
*/
|
||||||
public class MessageUtil {
|
public class MessageUtil {
|
||||||
|
|
||||||
public static void parse4Dml(String destination, Message message, Consumer<Dml> consumer) {
|
public static List<Dml> parse4Dml(String destination, Message message) {
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
List<CanalEntry.Entry> entries = message.getEntries();
|
List<CanalEntry.Entry> entries = message.getEntries();
|
||||||
|
List<Dml> dmls = new ArrayList<Dml>(entries.size());
|
||||||
for (CanalEntry.Entry entry : entries) {
|
for (CanalEntry.Entry entry : entries) {
|
||||||
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
|
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
|
||||||
|| entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
|
|| entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
|
||||||
@@ -49,6 +49,7 @@ public class MessageUtil {
|
|||||||
dml.setEs(entry.getHeader().getExecuteTime());
|
dml.setEs(entry.getHeader().getExecuteTime());
|
||||||
dml.setTs(System.currentTimeMillis());
|
dml.setTs(System.currentTimeMillis());
|
||||||
dml.setSql(rowChange.getSql());
|
dml.setSql(rowChange.getSql());
|
||||||
|
dmls.add(dml);
|
||||||
List<Map<String, Object>> data = new ArrayList<>();
|
List<Map<String, Object>> data = new ArrayList<>();
|
||||||
List<Map<String, Object>> old = new ArrayList<>();
|
List<Map<String, Object>> old = new ArrayList<>();
|
||||||
|
|
||||||
@@ -108,9 +109,21 @@ public class MessageUtil {
|
|||||||
dml.setOld(old);
|
dml.setOld(old);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
consumer.accept(dml);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return dmls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Dml> flatMessage2Dml(String destination, List<FlatMessage> flatMessages) {
|
||||||
|
List<Dml> dmls = new ArrayList<Dml>(flatMessages.size());
|
||||||
|
for (FlatMessage flatMessage : flatMessages) {
|
||||||
|
Dml dml = flatMessage2Dml(destination, flatMessage);
|
||||||
|
if (dml != null) {
|
||||||
|
dmls.add(dml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dmls;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Dml flatMessage2Dml(String destination, FlatMessage flatMessage) {
|
public static Dml flatMessage2Dml(String destination, FlatMessage flatMessage) {
|
||||||
@@ -165,9 +178,4 @@ public class MessageUtil {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Consumer<T> {
|
|
||||||
|
|
||||||
void accept(T t);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ public class OuterAdapterConfig {
|
|||||||
|
|
||||||
private String name; // 适配器名称, 如: logger, hbase, es
|
private String name; // 适配器名称, 如: logger, hbase, es
|
||||||
|
|
||||||
|
private String key; // 适配器唯一键
|
||||||
|
|
||||||
private String hosts; // 适配器内部的地址, 比如对应es该参数可以填写es的server地址
|
private String hosts; // 适配器内部的地址, 比如对应es该参数可以填写es的server地址
|
||||||
|
|
||||||
private String zkHosts; // 适配器内部的ZK地址, 比如对应HBase该参数可以填写HBase对应的ZK地址
|
private String zkHosts; // 适配器内部的ZK地址, 比如对应HBase该参数可以填写HBase对应的ZK地址
|
||||||
@@ -26,6 +28,14 @@ public class OuterAdapterConfig {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
public String getHosts() {
|
public String getHosts() {
|
||||||
return hosts;
|
return hosts;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
public class URLClassExtensionLoader extends URLClassLoader {
|
||||||
|
public URLClassExtensionLoader(URL[] urls) {
|
||||||
|
super(urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||||
|
Class<?> c = findLoadedClass(name);
|
||||||
|
if (c != null) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.startsWith("java.") || name.startsWith("org.slf4j.")
|
||||||
|
|| name.startsWith("org.apache.logging")
|
||||||
|
|| name.startsWith("org.apache.commons.logging.")) {
|
||||||
|
// || name.startsWith("org.apache.hadoop."))
|
||||||
|
// {
|
||||||
|
c = super.loadClass(name);
|
||||||
|
}
|
||||||
|
if (c != null) return c;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 先加载jar内的class,可避免jar冲突
|
||||||
|
c = findClass(name);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
c = null;
|
||||||
|
}
|
||||||
|
if (c != null) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.loadClass(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Enumeration<URL> getResources(String name) throws IOException {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
|
||||||
|
|
||||||
|
tmp[0] = findResources(name); // local class
|
||||||
|
// path first
|
||||||
|
// tmp[1] = super.getResources(name);
|
||||||
|
|
||||||
|
return new CompoundEnumeration<>(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CompoundEnumeration<E> implements Enumeration<E> {
|
||||||
|
|
||||||
|
private Enumeration<E>[] enums;
|
||||||
|
private int index = 0;
|
||||||
|
|
||||||
|
public CompoundEnumeration(Enumeration<E>[] enums){
|
||||||
|
this.enums = enums;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean next() {
|
||||||
|
while (this.index < this.enums.length) {
|
||||||
|
if (this.enums[this.index] != null && this.enums[this.index].hasMoreElements()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
++this.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasMoreElements() {
|
||||||
|
return this.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
public E nextElement() {
|
||||||
|
if (!this.next()) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
} else {
|
||||||
|
return this.enums[this.index].nextElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class Util {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(Util.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过DS执行sql
|
||||||
|
*/
|
||||||
|
public static Object sqlRS(DataSource ds, String sql, Function<ResultSet, Object> fun) throws SQLException {
|
||||||
|
Connection conn = null;
|
||||||
|
Statement stmt = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
conn = ds.getConnection();
|
||||||
|
stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
|
||||||
|
stmt.setFetchSize(Integer.MIN_VALUE);
|
||||||
|
rs = stmt.executeQuery(sql);
|
||||||
|
return fun.apply(rs);
|
||||||
|
} finally {
|
||||||
|
if (rs != null) {
|
||||||
|
try {
|
||||||
|
rs.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stmt != null) {
|
||||||
|
try {
|
||||||
|
stmt.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (conn != null) {
|
||||||
|
try {
|
||||||
|
conn.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sql执行获取resultSet
|
||||||
|
*
|
||||||
|
* @param conn sql connection
|
||||||
|
* @param sql sql
|
||||||
|
* @param consumer 回调方法
|
||||||
|
*/
|
||||||
|
public static void sqlRS(Connection conn, String sql, Consumer<ResultSet> consumer) {
|
||||||
|
Statement stmt = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
stmt = conn.createStatement();
|
||||||
|
rs = stmt.executeQuery(sql);
|
||||||
|
consumer.accept(rs);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
} finally {
|
||||||
|
if (rs != null) {
|
||||||
|
try {
|
||||||
|
rs.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stmt != null) {
|
||||||
|
try {
|
||||||
|
stmt.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getConfDirPath() {
|
||||||
|
return getConfDirPath("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getConfDirPath(String subConf) {
|
||||||
|
URL url = Util.class.getClassLoader().getResource("");
|
||||||
|
String path;
|
||||||
|
if (url != null) {
|
||||||
|
path = url.getPath();
|
||||||
|
} else {
|
||||||
|
path = new File("").getAbsolutePath();
|
||||||
|
}
|
||||||
|
File file = null;
|
||||||
|
if (path != null) {
|
||||||
|
file = new File(
|
||||||
|
path + ".." + File.separator + Constant.CONF_DIR + File.separator + StringUtils.trimToEmpty(subConf));
|
||||||
|
if (!file.exists()) {
|
||||||
|
file = new File(path + StringUtils.trimToEmpty(subConf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (file == null || !file.exists()) {
|
||||||
|
throw new RuntimeException("Config dir not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
95
client-adapter/elasticsearch/pom.xml
Normal file
95
client-adapter/elasticsearch/pom.xml
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<?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.2</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.elasticsearch</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>canal client adapter elasticsearch 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>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
<version>1.19</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.fastsql</groupId>
|
||||||
|
<artifactId>fastsql</artifactId>
|
||||||
|
<version>2.0.0_preview_644</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.elasticsearch</groupId>
|
||||||
|
<artifactId>elasticsearch</artifactId>
|
||||||
|
<version>6.2.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.elasticsearch.client</groupId>
|
||||||
|
<artifactId>transport</artifactId>
|
||||||
|
<version>6.2.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.12</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<version>2.4</version>
|
||||||
|
<configuration>
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>make-assembly</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-antrun-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>run</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<tasks>
|
||||||
|
<copy todir="${project.basedir}/../launcher/target/classes/es" overwrite="true">
|
||||||
|
<fileset dir="${project.basedir}/target/classes/es" erroronmissingdir="true">
|
||||||
|
<include name="*.yml" />
|
||||||
|
</fileset>
|
||||||
|
</copy>
|
||||||
|
</tasks>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
|
import org.elasticsearch.client.transport.TransportClient;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.common.transport.TransportAddress;
|
||||||
|
import org.elasticsearch.transport.client.PreBuiltTransportClient;
|
||||||
|
|
||||||
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfigLoader;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SqlParser;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.monitor.ESConfigMonitor;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.service.ESEtlService;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.service.ESSyncService;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.support.ESTemplate;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.SPI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ES外部适配器
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-10-20
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@SPI("es")
|
||||||
|
public class ESAdapter implements OuterAdapter {
|
||||||
|
|
||||||
|
private Map<String, ESSyncConfig> esSyncConfig = new ConcurrentHashMap<>(); // 文件名对应配置
|
||||||
|
private Map<String, Map<String, ESSyncConfig>> dbTableEsSyncConfig = new ConcurrentHashMap<>(); // schema-table对应配置
|
||||||
|
|
||||||
|
private TransportClient transportClient;
|
||||||
|
|
||||||
|
private ESSyncService esSyncService;
|
||||||
|
|
||||||
|
private ESConfigMonitor esConfigMonitor;
|
||||||
|
|
||||||
|
public TransportClient getTransportClient() {
|
||||||
|
return transportClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ESSyncService getEsSyncService() {
|
||||||
|
return esSyncService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, ESSyncConfig> getEsSyncConfig() {
|
||||||
|
return esSyncConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Map<String, ESSyncConfig>> getDbTableEsSyncConfig() {
|
||||||
|
return dbTableEsSyncConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(OuterAdapterConfig configuration) {
|
||||||
|
try {
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigTmp = ESSyncConfigLoader.load();
|
||||||
|
// 过滤不匹配的key的配置
|
||||||
|
esSyncConfigTmp.forEach((key, config) -> {
|
||||||
|
if ((config.getOuterAdapterKey() == null && configuration.getKey() == null)
|
||||||
|
|| (config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
|
||||||
|
.equalsIgnoreCase(configuration.getKey()))) {
|
||||||
|
esSyncConfig.put(key, config);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (Map.Entry<String, ESSyncConfig> entry : esSyncConfig.entrySet()) {
|
||||||
|
String configName = entry.getKey();
|
||||||
|
ESSyncConfig config = entry.getValue();
|
||||||
|
SchemaItem schemaItem = SqlParser.parse(config.getEsMapping().getSql());
|
||||||
|
config.getEsMapping().setSchemaItem(schemaItem);
|
||||||
|
|
||||||
|
DruidDataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
|
||||||
|
if (dataSource == null || dataSource.getUrl() == null) {
|
||||||
|
throw new RuntimeException("No data source found: " + config.getDataSourceKey());
|
||||||
|
}
|
||||||
|
Pattern pattern = Pattern.compile(".*:(.*)://.*/(.*)\\?.*$");
|
||||||
|
Matcher matcher = pattern.matcher(dataSource.getUrl());
|
||||||
|
if (!matcher.find()) {
|
||||||
|
throw new RuntimeException("Not found the schema of jdbc-url: " + config.getDataSourceKey());
|
||||||
|
}
|
||||||
|
String schema = matcher.group(2);
|
||||||
|
|
||||||
|
schemaItem.getAliasTableItems()
|
||||||
|
.values()
|
||||||
|
.forEach(tableItem -> {
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigMap = dbTableEsSyncConfig.computeIfAbsent(schema
|
||||||
|
+ "-"
|
||||||
|
+ tableItem.getTableName(),
|
||||||
|
k -> new HashMap<>());
|
||||||
|
esSyncConfigMap.put(configName, config);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> properties = configuration.getProperties();
|
||||||
|
Settings.Builder settingBuilder = Settings.builder();
|
||||||
|
properties.forEach(settingBuilder::put);
|
||||||
|
Settings settings = settingBuilder.build();
|
||||||
|
transportClient = new PreBuiltTransportClient(settings);
|
||||||
|
String[] hostArray = configuration.getHosts().split(",");
|
||||||
|
for (String host : hostArray) {
|
||||||
|
int i = host.indexOf(":");
|
||||||
|
transportClient.addTransportAddress(new TransportAddress(InetAddress.getByName(host.substring(0, i)),
|
||||||
|
Integer.parseInt(host.substring(i + 1))));
|
||||||
|
}
|
||||||
|
ESTemplate esTemplate = new ESTemplate(transportClient);
|
||||||
|
esSyncService = new ESSyncService(esTemplate);
|
||||||
|
|
||||||
|
esConfigMonitor = new ESConfigMonitor();
|
||||||
|
esConfigMonitor.init(this);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sync(List<Dml> dmls) {
|
||||||
|
for (Dml dml : dmls) {
|
||||||
|
sync(dml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sync(Dml dml) {
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> configMap = dbTableEsSyncConfig.get(database + "-" + table);
|
||||||
|
esSyncService.sync(configMap.values(), dml);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EtlResult etl(String task, List<String> params) {
|
||||||
|
EtlResult etlResult = new EtlResult();
|
||||||
|
ESSyncConfig config = esSyncConfig.get(task);
|
||||||
|
if (config != null) {
|
||||||
|
DataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
|
||||||
|
ESEtlService esEtlService = new ESEtlService(transportClient, config);
|
||||||
|
if (dataSource != null) {
|
||||||
|
return esEtlService.importData(params, false);
|
||||||
|
} else {
|
||||||
|
etlResult.setSucceeded(false);
|
||||||
|
etlResult.setErrorMessage("DataSource not found");
|
||||||
|
return etlResult;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
StringBuilder resultMsg = new StringBuilder();
|
||||||
|
boolean resSuccess = true;
|
||||||
|
// ds不为空说明传入的是datasourceKey
|
||||||
|
for (ESSyncConfig configTmp : esSyncConfig.values()) {
|
||||||
|
// 取所有的destination为task的配置
|
||||||
|
if (configTmp.getDestination().equals(task)) {
|
||||||
|
ESEtlService esEtlService = new ESEtlService(transportClient, configTmp);
|
||||||
|
EtlResult etlRes = esEtlService.importData(params, false);
|
||||||
|
if (!etlRes.getSucceeded()) {
|
||||||
|
resSuccess = false;
|
||||||
|
resultMsg.append(etlRes.getErrorMessage()).append("\n");
|
||||||
|
} else {
|
||||||
|
resultMsg.append(etlRes.getResultMessage()).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resultMsg.length() > 0) {
|
||||||
|
etlResult.setSucceeded(resSuccess);
|
||||||
|
if (resSuccess) {
|
||||||
|
etlResult.setResultMessage(resultMsg.toString());
|
||||||
|
} else {
|
||||||
|
etlResult.setErrorMessage(resultMsg.toString());
|
||||||
|
}
|
||||||
|
return etlResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
etlResult.setSucceeded(false);
|
||||||
|
etlResult.setErrorMessage("Task not found");
|
||||||
|
return etlResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> count(String task) {
|
||||||
|
ESSyncConfig config = esSyncConfig.get(task);
|
||||||
|
ESMapping mapping = config.getEsMapping();
|
||||||
|
SearchResponse response = transportClient.prepareSearch(mapping.get_index())
|
||||||
|
.setTypes(mapping.get_type())
|
||||||
|
.setSize(0)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
long rowCount = response.getHits().getTotalHits();
|
||||||
|
Map<String, Object> res = new LinkedHashMap<>();
|
||||||
|
res.put("esIndex", mapping.get_index());
|
||||||
|
res.put("count", rowCount);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
if (transportClient != null) {
|
||||||
|
transportClient.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDestination(String task) {
|
||||||
|
if (esConfigMonitor != null) {
|
||||||
|
esConfigMonitor.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
ESSyncConfig config = esSyncConfig.get(task);
|
||||||
|
if (config != null) {
|
||||||
|
return config.getDestination();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.config;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ES 映射配置
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-11-01
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class ESSyncConfig {
|
||||||
|
|
||||||
|
private String dataSourceKey; // 数据源key
|
||||||
|
|
||||||
|
private String outerAdapterKey; // adapter key
|
||||||
|
|
||||||
|
private String destination; // canal destination
|
||||||
|
|
||||||
|
private ESMapping esMapping;
|
||||||
|
|
||||||
|
public void validate() {
|
||||||
|
if (esMapping._index == null) {
|
||||||
|
throw new NullPointerException("esMapping._index");
|
||||||
|
}
|
||||||
|
if (esMapping._type == null) {
|
||||||
|
throw new NullPointerException("esMapping._type");
|
||||||
|
}
|
||||||
|
if (esMapping._id == null && esMapping.pk == null) {
|
||||||
|
throw new NullPointerException("esMapping._id and esMapping.pk");
|
||||||
|
}
|
||||||
|
if (esMapping.sql == null) {
|
||||||
|
throw new NullPointerException("esMapping.sql");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDataSourceKey() {
|
||||||
|
return dataSourceKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDataSourceKey(String dataSourceKey) {
|
||||||
|
this.dataSourceKey = dataSourceKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOuterAdapterKey() {
|
||||||
|
return outerAdapterKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOuterAdapterKey(String outerAdapterKey) {
|
||||||
|
this.outerAdapterKey = outerAdapterKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDestination() {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDestination(String destination) {
|
||||||
|
this.destination = destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ESMapping getEsMapping() {
|
||||||
|
return esMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEsMapping(ESMapping esMapping) {
|
||||||
|
this.esMapping = esMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ESMapping {
|
||||||
|
|
||||||
|
private String _index;
|
||||||
|
private String _type;
|
||||||
|
private String _id;
|
||||||
|
private String pk;
|
||||||
|
private String parent;
|
||||||
|
private String sql;
|
||||||
|
// 对象字段, 例: objFields:
|
||||||
|
// - _labels: array:;
|
||||||
|
private Map<String, String> objFields = new LinkedHashMap<>();
|
||||||
|
private List<String> skips = new ArrayList<>();
|
||||||
|
private int commitBatch = 1000;
|
||||||
|
private String etlCondition;
|
||||||
|
private boolean syncByTimestamp = false; // 是否按时间戳定时同步
|
||||||
|
private Long syncInterval; // 同步时间间隔
|
||||||
|
|
||||||
|
private SchemaItem schemaItem; // sql解析结果模型
|
||||||
|
|
||||||
|
public String get_index() {
|
||||||
|
return _index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set_index(String _index) {
|
||||||
|
this._index = _index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get_type() {
|
||||||
|
return _type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set_type(String _type) {
|
||||||
|
this._type = _type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get_id() {
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set_id(String _id) {
|
||||||
|
this._id = _id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPk() {
|
||||||
|
return pk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPk(String pk) {
|
||||||
|
this.pk = pk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParent(String parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getObjFields() {
|
||||||
|
return objFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setObjFields(Map<String, String> objFields) {
|
||||||
|
this.objFields = objFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getSkips() {
|
||||||
|
return skips;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSkips(List<String> skips) {
|
||||||
|
this.skips = skips;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSql() {
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSql(String sql) {
|
||||||
|
this.sql = sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCommitBatch() {
|
||||||
|
return commitBatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommitBatch(int commitBatch) {
|
||||||
|
this.commitBatch = commitBatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEtlCondition() {
|
||||||
|
return etlCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEtlCondition(String etlCondition) {
|
||||||
|
this.etlCondition = etlCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getSyncInterval() {
|
||||||
|
return syncInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSyncInterval(Long syncInterval) {
|
||||||
|
this.syncInterval = syncInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSyncByTimestamp() {
|
||||||
|
return syncByTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSyncByTimestamp(boolean syncByTimestamp) {
|
||||||
|
this.syncByTimestamp = syncByTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SchemaItem getSchemaItem() {
|
||||||
|
return schemaItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSchemaItem(SchemaItem schemaItem) {
|
||||||
|
this.schemaItem = schemaItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.config;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ES 配置装载器
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-11-01
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class ESSyncConfigLoader {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(ESSyncConfigLoader.class);
|
||||||
|
|
||||||
|
public static synchronized Map<String, ESSyncConfig> load() {
|
||||||
|
logger.info("## Start loading es mapping config ... ");
|
||||||
|
|
||||||
|
Map<String, ESSyncConfig> esSyncConfig = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("es");
|
||||||
|
configContentMap.forEach((fileName, content) -> {
|
||||||
|
ESSyncConfig config = new Yaml().loadAs(content, ESSyncConfig.class);
|
||||||
|
try {
|
||||||
|
config.validate();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("ERROR Config: " + fileName + " " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
esSyncConfig.put(fileName, config);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info("## ES mapping config loaded");
|
||||||
|
return esSyncConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.config;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ES 映射配置视图
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-11-01
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class SchemaItem {
|
||||||
|
|
||||||
|
private Map<String, TableItem> aliasTableItems = new LinkedHashMap<>(); // 别名对应表名
|
||||||
|
private Map<String, FieldItem> selectFields = new LinkedHashMap<>(); // 查询字段
|
||||||
|
private String sql;
|
||||||
|
|
||||||
|
private volatile Map<String, List<TableItem>> tableItemAliases;
|
||||||
|
private volatile Map<String, List<FieldItem>> columnFields;
|
||||||
|
private volatile Boolean allFieldsSimple;
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
this.getTableItemAliases();
|
||||||
|
this.getColumnFields();
|
||||||
|
this.isAllFieldsSimple();
|
||||||
|
aliasTableItems.values().forEach(tableItem -> {
|
||||||
|
tableItem.getRelationTableFields();
|
||||||
|
tableItem.getRelationSelectFieldItems();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, TableItem> getAliasTableItems() {
|
||||||
|
return aliasTableItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAliasTableItems(Map<String, TableItem> aliasTableItems) {
|
||||||
|
this.aliasTableItems = aliasTableItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSql() {
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSql(String sql) {
|
||||||
|
this.sql = sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, FieldItem> getSelectFields() {
|
||||||
|
return selectFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectFields(Map<String, FieldItem> selectFields) {
|
||||||
|
this.selectFields = selectFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<TableItem>> getTableItemAliases() {
|
||||||
|
if (tableItemAliases == null) {
|
||||||
|
synchronized (SchemaItem.class) {
|
||||||
|
if (tableItemAliases == null) {
|
||||||
|
tableItemAliases = new LinkedHashMap<>();
|
||||||
|
aliasTableItems.forEach((alias, tableItem) -> {
|
||||||
|
List<TableItem> aliases = tableItemAliases
|
||||||
|
.computeIfAbsent(tableItem.getTableName().toLowerCase(), k -> new ArrayList<>());
|
||||||
|
aliases.add(tableItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tableItemAliases;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<FieldItem>> getColumnFields() {
|
||||||
|
if (columnFields == null) {
|
||||||
|
synchronized (SchemaItem.class) {
|
||||||
|
if (columnFields == null) {
|
||||||
|
columnFields = new LinkedHashMap<>();
|
||||||
|
getSelectFields()
|
||||||
|
.forEach((fieldName, fieldItem) -> fieldItem.getColumnItems().forEach(columnItem -> {
|
||||||
|
TableItem tableItem = getAliasTableItems().get(columnItem.getOwner());
|
||||||
|
// if (!tableItem.isSubQuery()) {
|
||||||
|
List<FieldItem> fieldItems = columnFields.computeIfAbsent(
|
||||||
|
columnItem.getOwner() + "." + columnItem.getColumnName(),
|
||||||
|
k -> new ArrayList<>());
|
||||||
|
fieldItems.add(fieldItem);
|
||||||
|
// } else {
|
||||||
|
// tableItem.getSubQueryFields().forEach(subQueryField -> {
|
||||||
|
// List<FieldItem> fieldItems = columnFields.computeIfAbsent(
|
||||||
|
// columnItem.getOwner() + "." + subQueryField.getColumn().getColumnName(),
|
||||||
|
// k -> new ArrayList<>());
|
||||||
|
// fieldItems.add(fieldItem);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return columnFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAllFieldsSimple() {
|
||||||
|
if (allFieldsSimple == null) {
|
||||||
|
synchronized (SchemaItem.class) {
|
||||||
|
if (allFieldsSimple == null) {
|
||||||
|
allFieldsSimple = true;
|
||||||
|
|
||||||
|
for (FieldItem fieldItem : getSelectFields().values()) {
|
||||||
|
if (fieldItem.isMethod() || fieldItem.isBinaryOp()) {
|
||||||
|
allFieldsSimple = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allFieldsSimple;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableItem getMainTable() {
|
||||||
|
if (!aliasTableItems.isEmpty()) {
|
||||||
|
return aliasTableItems.values().iterator().next();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldItem getIdFieldItem(ESMapping mapping) {
|
||||||
|
if (mapping.get_id() != null) {
|
||||||
|
return getSelectFields().get(mapping.get_id());
|
||||||
|
} else {
|
||||||
|
return getSelectFields().get(mapping.getPk());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TableItem {
|
||||||
|
|
||||||
|
private SchemaItem schemaItem;
|
||||||
|
|
||||||
|
private String schema;
|
||||||
|
private String tableName;
|
||||||
|
private String alias;
|
||||||
|
private String subQuerySql;
|
||||||
|
private List<FieldItem> subQueryFields = new ArrayList<>();
|
||||||
|
private List<RelationFieldsPair> relationFields = new ArrayList<>();
|
||||||
|
|
||||||
|
private boolean main;
|
||||||
|
private boolean subQuery;
|
||||||
|
|
||||||
|
private volatile Map<FieldItem, List<FieldItem>> relationTableFields; // 当前表关联条件字段对应主表查询字段
|
||||||
|
private volatile List<FieldItem> relationSelectFieldItems; // 子表所在主表的查询字段
|
||||||
|
|
||||||
|
public TableItem(SchemaItem schemaItem){
|
||||||
|
this.schemaItem = schemaItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SchemaItem getSchemaItem() {
|
||||||
|
return schemaItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSchemaItem(SchemaItem schemaItem) {
|
||||||
|
this.schemaItem = schemaItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSchema() {
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSchema(String schema) {
|
||||||
|
this.schema = schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTableName() {
|
||||||
|
return tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTableName(String tableName) {
|
||||||
|
this.tableName = tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlias() {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlias(String alias) {
|
||||||
|
this.alias = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSubQuerySql() {
|
||||||
|
return subQuerySql;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubQuerySql(String subQuerySql) {
|
||||||
|
this.subQuerySql = subQuerySql;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMain() {
|
||||||
|
return main;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMain(boolean main) {
|
||||||
|
this.main = main;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSubQuery() {
|
||||||
|
return subQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubQuery(boolean subQuery) {
|
||||||
|
this.subQuery = subQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FieldItem> getSubQueryFields() {
|
||||||
|
return subQueryFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubQueryFields(List<FieldItem> subQueryFields) {
|
||||||
|
this.subQueryFields = subQueryFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RelationFieldsPair> getRelationFields() {
|
||||||
|
return relationFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRelationFields(List<RelationFieldsPair> relationFields) {
|
||||||
|
this.relationFields = relationFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<FieldItem, List<FieldItem>> getRelationTableFields() {
|
||||||
|
if (relationTableFields == null) {
|
||||||
|
synchronized (SchemaItem.class) {
|
||||||
|
if (relationTableFields == null) {
|
||||||
|
relationTableFields = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
getRelationFields().forEach(relationFieldsPair -> {
|
||||||
|
FieldItem leftFieldItem = relationFieldsPair.getLeftFieldItem();
|
||||||
|
FieldItem rightFieldItem = relationFieldsPair.getRightFieldItem();
|
||||||
|
FieldItem currentTableRelField = null;
|
||||||
|
if (getAlias().equals(leftFieldItem.getOwner())) {
|
||||||
|
currentTableRelField = leftFieldItem;
|
||||||
|
} else if (getAlias().equals(rightFieldItem.getOwner())) {
|
||||||
|
currentTableRelField = rightFieldItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentTableRelField != null) {
|
||||||
|
List<FieldItem> selectFieldItem = getSchemaItem().getColumnFields()
|
||||||
|
.get(leftFieldItem.getOwner() + "." + leftFieldItem.getColumn().getColumnName());
|
||||||
|
if (selectFieldItem != null && !selectFieldItem.isEmpty()) {
|
||||||
|
relationTableFields.put(currentTableRelField, selectFieldItem);
|
||||||
|
} else {
|
||||||
|
selectFieldItem = getSchemaItem().getColumnFields()
|
||||||
|
.get(rightFieldItem.getOwner() + "."
|
||||||
|
+ rightFieldItem.getColumn().getColumnName());
|
||||||
|
if (selectFieldItem != null && !selectFieldItem.isEmpty()) {
|
||||||
|
relationTableFields.put(currentTableRelField, selectFieldItem);
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"Relation condition column must in select columns.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return relationTableFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FieldItem> getRelationSelectFieldItems() {
|
||||||
|
if (relationSelectFieldItems == null) {
|
||||||
|
synchronized (SchemaItem.class) {
|
||||||
|
if (relationSelectFieldItems == null) {
|
||||||
|
relationSelectFieldItems = new ArrayList<>();
|
||||||
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
|
if (fieldItem.getOwners().contains(getAlias())) {
|
||||||
|
relationSelectFieldItems.add(fieldItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return relationSelectFieldItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RelationFieldsPair {
|
||||||
|
|
||||||
|
private FieldItem leftFieldItem;
|
||||||
|
private FieldItem rightFieldItem;
|
||||||
|
|
||||||
|
public RelationFieldsPair(FieldItem leftFieldItem, FieldItem rightFieldItem){
|
||||||
|
this.leftFieldItem = leftFieldItem;
|
||||||
|
this.rightFieldItem = rightFieldItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldItem getLeftFieldItem() {
|
||||||
|
return leftFieldItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLeftFieldItem(FieldItem leftFieldItem) {
|
||||||
|
this.leftFieldItem = leftFieldItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldItem getRightFieldItem() {
|
||||||
|
return rightFieldItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRightFieldItem(FieldItem rightFieldItem) {
|
||||||
|
this.rightFieldItem = rightFieldItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FieldItem {
|
||||||
|
|
||||||
|
private String fieldName;
|
||||||
|
private List<ColumnItem> columnItems = new ArrayList<>();
|
||||||
|
private List<String> owners = new ArrayList<>();
|
||||||
|
|
||||||
|
private boolean method;
|
||||||
|
private boolean binaryOp;
|
||||||
|
|
||||||
|
public String getFieldName() {
|
||||||
|
return fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFieldName(String fieldName) {
|
||||||
|
this.fieldName = fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ColumnItem> getColumnItems() {
|
||||||
|
return columnItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColumnItems(List<ColumnItem> columnItems) {
|
||||||
|
this.columnItems = columnItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMethod(boolean method) {
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBinaryOp() {
|
||||||
|
return binaryOp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBinaryOp(boolean binaryOp) {
|
||||||
|
this.binaryOp = binaryOp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getOwners() {
|
||||||
|
return owners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOwners(List<String> owners) {
|
||||||
|
this.owners = owners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addColumn(ColumnItem columnItem) {
|
||||||
|
columnItems.add(columnItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColumnItem getColumn() {
|
||||||
|
if (!columnItems.isEmpty()) {
|
||||||
|
return columnItems.get(0);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOwner() {
|
||||||
|
if (!owners.isEmpty()) {
|
||||||
|
return owners.get(0);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
FieldItem fieldItem = (FieldItem) o;
|
||||||
|
|
||||||
|
return fieldName != null ? fieldName.equals(fieldItem.fieldName) : fieldItem.fieldName == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return fieldName != null ? fieldName.hashCode() : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ColumnItem {
|
||||||
|
|
||||||
|
private String owner;
|
||||||
|
private String columnName;
|
||||||
|
|
||||||
|
public String getOwner() {
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOwner(String owner) {
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColumnName() {
|
||||||
|
return columnName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColumnName(String columnName) {
|
||||||
|
this.columnName = columnName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.config;
|
||||||
|
|
||||||
|
import static com.alibaba.fastsql.sql.ast.expr.SQLBinaryOperator.BooleanAnd;
|
||||||
|
import static com.alibaba.fastsql.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.SQLIdentifierExpr;
|
||||||
|
import com.alibaba.fastsql.sql.ast.expr.SQLMethodInvokeExpr;
|
||||||
|
import com.alibaba.fastsql.sql.ast.expr.SQLPropertyExpr;
|
||||||
|
import com.alibaba.fastsql.sql.ast.statement.*;
|
||||||
|
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.otter.canal.client.adapter.es.config.SchemaItem.ColumnItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.FieldItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.RelationFieldsPair;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.TableItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ES同步指定sql格式解析
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-10-26 下午03:45:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class SqlParser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析sql
|
||||||
|
*
|
||||||
|
* @param sql sql
|
||||||
|
* @return 视图对象
|
||||||
|
*/
|
||||||
|
public static SchemaItem parse(String sql) {
|
||||||
|
try {
|
||||||
|
SQLStatementParser parser = new MySqlStatementParser(sql);
|
||||||
|
SQLSelectStatement statement = (SQLSelectStatement) parser.parseStatement();
|
||||||
|
MySqlSelectQueryBlock sqlSelectQueryBlock = (MySqlSelectQueryBlock) statement.getSelect().getQuery();
|
||||||
|
|
||||||
|
SchemaItem schemaItem = new SchemaItem();
|
||||||
|
schemaItem.setSql(SQLUtils.toMySqlString(sqlSelectQueryBlock));
|
||||||
|
SQLTableSource sqlTableSource = sqlSelectQueryBlock.getFrom();
|
||||||
|
List<TableItem> tableItems = new ArrayList<>();
|
||||||
|
SqlParser.visitSelectTable(schemaItem, sqlTableSource, tableItems, null);
|
||||||
|
tableItems.forEach(tableItem -> schemaItem.getAliasTableItems().put(tableItem.getAlias(), tableItem));
|
||||||
|
|
||||||
|
List<FieldItem> fieldItems = collectSelectQueryFields(sqlSelectQueryBlock);
|
||||||
|
fieldItems.forEach(fieldItem -> schemaItem.getSelectFields().put(fieldItem.getFieldName(), fieldItem));
|
||||||
|
|
||||||
|
schemaItem.init();
|
||||||
|
|
||||||
|
if (schemaItem.getAliasTableItems().isEmpty() || schemaItem.getSelectFields().isEmpty()) {
|
||||||
|
throw new ParserException("Parse sql error");
|
||||||
|
}
|
||||||
|
return schemaItem;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ParserException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归集字段
|
||||||
|
*
|
||||||
|
* @param sqlSelectQueryBlock sqlSelectQueryBlock
|
||||||
|
* @return 字段属性列表
|
||||||
|
*/
|
||||||
|
private static List<FieldItem> collectSelectQueryFields(MySqlSelectQueryBlock sqlSelectQueryBlock) {
|
||||||
|
return sqlSelectQueryBlock.getSelectList().stream().map(selectItem -> {
|
||||||
|
FieldItem fieldItem = new FieldItem();
|
||||||
|
fieldItem.setFieldName(selectItem.getAlias());
|
||||||
|
visitColumn(selectItem.getExpr(), fieldItem);
|
||||||
|
return fieldItem;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析字段
|
||||||
|
*
|
||||||
|
* @param expr sql expr
|
||||||
|
* @param fieldItem 字段属性
|
||||||
|
*/
|
||||||
|
private static void visitColumn(SQLExpr expr, FieldItem fieldItem) {
|
||||||
|
if (expr instanceof SQLIdentifierExpr) {
|
||||||
|
// 无owner
|
||||||
|
SQLIdentifierExpr identifierExpr = (SQLIdentifierExpr) expr;
|
||||||
|
if (fieldItem.getFieldName() == null) {
|
||||||
|
fieldItem.setFieldName(identifierExpr.getName());
|
||||||
|
}
|
||||||
|
ColumnItem columnItem = new ColumnItem();
|
||||||
|
columnItem.setColumnName(identifierExpr.getName());
|
||||||
|
fieldItem.getOwners().add(null);
|
||||||
|
fieldItem.addColumn(columnItem);
|
||||||
|
} else if (expr instanceof SQLPropertyExpr) {
|
||||||
|
// 有owner
|
||||||
|
SQLPropertyExpr sqlPropertyExpr = (SQLPropertyExpr) expr;
|
||||||
|
if (fieldItem.getFieldName() == null) {
|
||||||
|
fieldItem.setFieldName(sqlPropertyExpr.getName());
|
||||||
|
}
|
||||||
|
fieldItem.getOwners().add(sqlPropertyExpr.getOwnernName());
|
||||||
|
ColumnItem columnItem = new ColumnItem();
|
||||||
|
columnItem.setColumnName(sqlPropertyExpr.getName());
|
||||||
|
columnItem.setOwner(sqlPropertyExpr.getOwnernName());
|
||||||
|
fieldItem.addColumn(columnItem);
|
||||||
|
} else if (expr instanceof SQLMethodInvokeExpr) {
|
||||||
|
SQLMethodInvokeExpr methodInvokeExpr = (SQLMethodInvokeExpr) expr;
|
||||||
|
fieldItem.setMethod(true);
|
||||||
|
for (SQLExpr sqlExpr : methodInvokeExpr.getArguments()) {
|
||||||
|
visitColumn(sqlExpr, fieldItem);
|
||||||
|
}
|
||||||
|
} else if (expr instanceof SQLBinaryOpExpr) {
|
||||||
|
SQLBinaryOpExpr sqlBinaryOpExpr = (SQLBinaryOpExpr) expr;
|
||||||
|
fieldItem.setBinaryOp(true);
|
||||||
|
visitColumn(sqlBinaryOpExpr.getLeft(), fieldItem);
|
||||||
|
visitColumn(sqlBinaryOpExpr.getRight(), fieldItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析表
|
||||||
|
*
|
||||||
|
* @param schemaItem 视图对象
|
||||||
|
* @param sqlTableSource sqlTableSource
|
||||||
|
* @param tableItems 表对象列表
|
||||||
|
* @param tableItemTmp 表对象(临时)
|
||||||
|
*/
|
||||||
|
private static void visitSelectTable(SchemaItem schemaItem, SQLTableSource sqlTableSource,
|
||||||
|
List<TableItem> tableItems, TableItem tableItemTmp) {
|
||||||
|
if (sqlTableSource instanceof SQLExprTableSource) {
|
||||||
|
SQLExprTableSource sqlExprTableSource = (SQLExprTableSource) sqlTableSource;
|
||||||
|
TableItem tableItem;
|
||||||
|
if (tableItemTmp != null) {
|
||||||
|
tableItem = tableItemTmp;
|
||||||
|
} else {
|
||||||
|
tableItem = new TableItem(schemaItem);
|
||||||
|
}
|
||||||
|
tableItem.setSchema(sqlExprTableSource.getSchema());
|
||||||
|
tableItem.setTableName(sqlExprTableSource.getTableName());
|
||||||
|
if (tableItem.getAlias() == null) {
|
||||||
|
tableItem.setAlias(sqlExprTableSource.getAlias());
|
||||||
|
}
|
||||||
|
if (tableItems.isEmpty()) {
|
||||||
|
// 第一张表为主表
|
||||||
|
tableItem.setMain(true);
|
||||||
|
}
|
||||||
|
tableItems.add(tableItem);
|
||||||
|
} else if (sqlTableSource instanceof SQLJoinTableSource) {
|
||||||
|
SQLJoinTableSource sqlJoinTableSource = (SQLJoinTableSource) sqlTableSource;
|
||||||
|
SQLTableSource leftTableSource = sqlJoinTableSource.getLeft();
|
||||||
|
visitSelectTable(schemaItem, leftTableSource, tableItems, null);
|
||||||
|
SQLTableSource rightTableSource = sqlJoinTableSource.getRight();
|
||||||
|
TableItem rightTableItem = new TableItem(schemaItem);
|
||||||
|
// 解析on条件字段
|
||||||
|
visitOnCondition(sqlJoinTableSource.getCondition(), rightTableItem);
|
||||||
|
visitSelectTable(schemaItem, rightTableSource, tableItems, rightTableItem);
|
||||||
|
|
||||||
|
} else if (sqlTableSource instanceof SQLSubqueryTableSource) {
|
||||||
|
SQLSubqueryTableSource subQueryTableSource = (SQLSubqueryTableSource) sqlTableSource;
|
||||||
|
MySqlSelectQueryBlock sqlSelectQuery = (MySqlSelectQueryBlock) subQueryTableSource.getSelect().getQuery();
|
||||||
|
TableItem tableItem;
|
||||||
|
if (tableItemTmp != null) {
|
||||||
|
tableItem = tableItemTmp;
|
||||||
|
} else {
|
||||||
|
tableItem = new TableItem(schemaItem);
|
||||||
|
}
|
||||||
|
tableItem.setAlias(subQueryTableSource.getAlias());
|
||||||
|
tableItem.setSubQuerySql(SQLUtils.toMySqlString(sqlSelectQuery));
|
||||||
|
tableItem.setSubQuery(true);
|
||||||
|
tableItem.setSubQueryFields(collectSelectQueryFields(sqlSelectQuery));
|
||||||
|
visitSelectTable(schemaItem, sqlSelectQuery.getFrom(), tableItems, tableItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析on条件
|
||||||
|
*
|
||||||
|
* @param expr sql expr
|
||||||
|
* @param tableItem 表对象
|
||||||
|
*/
|
||||||
|
private static void visitOnCondition(SQLExpr expr, TableItem tableItem) {
|
||||||
|
if (!(expr instanceof SQLBinaryOpExpr)) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
SQLBinaryOpExpr sqlBinaryOpExpr = (SQLBinaryOpExpr) expr;
|
||||||
|
if (sqlBinaryOpExpr.getOperator() == BooleanAnd) {
|
||||||
|
visitOnCondition(sqlBinaryOpExpr.getLeft(), tableItem);
|
||||||
|
visitOnCondition(sqlBinaryOpExpr.getRight(), tableItem);
|
||||||
|
} else if (sqlBinaryOpExpr.getOperator() == Equality) {
|
||||||
|
FieldItem leftFieldItem = new FieldItem();
|
||||||
|
visitColumn(sqlBinaryOpExpr.getLeft(), leftFieldItem);
|
||||||
|
if (leftFieldItem.getColumnItems().size() != 1 || leftFieldItem.isMethod() || leftFieldItem.isBinaryOp()) {
|
||||||
|
throw new UnsupportedOperationException("Unsupported for complex of on-condition");
|
||||||
|
}
|
||||||
|
FieldItem rightFieldItem = new FieldItem();
|
||||||
|
visitColumn(sqlBinaryOpExpr.getRight(), rightFieldItem);
|
||||||
|
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));
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedOperationException("Unsupported for complex of on-condition");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.monitor;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.apache.commons.io.filefilter.FileFilterUtils;
|
||||||
|
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
|
||||||
|
import org.apache.commons.io.monitor.FileAlterationMonitor;
|
||||||
|
import org.apache.commons.io.monitor.FileAlterationObserver;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SqlParser;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
|
|
||||||
|
public class ESConfigMonitor {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ESConfigMonitor.class);
|
||||||
|
|
||||||
|
private static final String adapterName = "es";
|
||||||
|
|
||||||
|
private ESAdapter esAdapter;
|
||||||
|
|
||||||
|
private FileAlterationMonitor fileMonitor;
|
||||||
|
|
||||||
|
public void init(ESAdapter esAdapter) {
|
||||||
|
this.esAdapter = esAdapter;
|
||||||
|
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());
|
||||||
|
ESSyncConfig config = new Yaml().loadAs(configContent, ESSyncConfig.class);
|
||||||
|
config.validate();
|
||||||
|
addConfigToCache(file, config);
|
||||||
|
|
||||||
|
logger.info("Add a new es 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 (esAdapter.getEsSyncConfig().containsKey(file.getName())) {
|
||||||
|
// 加载配置文件
|
||||||
|
String configContent = MappingConfigsLoader
|
||||||
|
.loadConfig(adapterName + File.separator + file.getName());
|
||||||
|
ESSyncConfig config = new Yaml().loadAs(configContent, ESSyncConfig.class);
|
||||||
|
config.validate();
|
||||||
|
if (esAdapter.getEsSyncConfig().containsKey(file.getName())) {
|
||||||
|
deleteConfigFromCache(file);
|
||||||
|
}
|
||||||
|
addConfigToCache(file, config);
|
||||||
|
|
||||||
|
logger.info("Change a es 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 (esAdapter.getEsSyncConfig().containsKey(file.getName())) {
|
||||||
|
deleteConfigFromCache(file);
|
||||||
|
|
||||||
|
logger.info("Delete a es mapping config: {} of canal adapter", file.getName());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addConfigToCache(File file, ESSyncConfig config) {
|
||||||
|
esAdapter.getEsSyncConfig().put(file.getName(), config);
|
||||||
|
|
||||||
|
SchemaItem schemaItem = SqlParser.parse(config.getEsMapping().getSql());
|
||||||
|
config.getEsMapping().setSchemaItem(schemaItem);
|
||||||
|
|
||||||
|
DruidDataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
|
||||||
|
if (dataSource == null || dataSource.getUrl() == null) {
|
||||||
|
throw new RuntimeException("No data source found: " + config.getDataSourceKey());
|
||||||
|
}
|
||||||
|
Pattern pattern = Pattern.compile(".*:(.*)://.*/(.*)\\?.*$");
|
||||||
|
Matcher matcher = pattern.matcher(dataSource.getUrl());
|
||||||
|
if (!matcher.find()) {
|
||||||
|
throw new RuntimeException("Not found the schema of jdbc-url: " + config.getDataSourceKey());
|
||||||
|
}
|
||||||
|
String schema = matcher.group(2);
|
||||||
|
|
||||||
|
schemaItem.getAliasTableItems().values().forEach(tableItem -> {
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigMap = esAdapter.getDbTableEsSyncConfig()
|
||||||
|
.computeIfAbsent(schema + "-" + tableItem.getTableName(), k -> new HashMap<>());
|
||||||
|
esSyncConfigMap.put(file.getName(), config);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteConfigFromCache(File file) {
|
||||||
|
esAdapter.getEsSyncConfig().remove(file.getName());
|
||||||
|
for (Map<String, ESSyncConfig> configMap : esAdapter.getDbTableEsSyncConfig().values()) {
|
||||||
|
if (configMap != null) {
|
||||||
|
configMap.remove(file.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.service;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.bulk.BulkItemResponse;
|
||||||
|
import org.elasticsearch.action.bulk.BulkRequestBuilder;
|
||||||
|
import org.elasticsearch.action.bulk.BulkResponse;
|
||||||
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
|
import org.elasticsearch.client.transport.TransportClient;
|
||||||
|
import org.elasticsearch.index.query.QueryBuilders;
|
||||||
|
import org.elasticsearch.rest.RestStatus;
|
||||||
|
import org.elasticsearch.search.SearchHit;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.FieldItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.support.ESSyncUtil;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.support.ESTemplate;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ES ETL Service
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-11-01
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class ESEtlService {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(ESEtlService.class);
|
||||||
|
|
||||||
|
private TransportClient transportClient;
|
||||||
|
private ESTemplate esTemplate;
|
||||||
|
private ESSyncConfig config;
|
||||||
|
|
||||||
|
public ESEtlService(TransportClient transportClient, ESSyncConfig config){
|
||||||
|
this.transportClient = transportClient;
|
||||||
|
this.esTemplate = new ESTemplate(transportClient);
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EtlResult importData(List<String> params, boolean bulk) {
|
||||||
|
EtlResult etlResult = new EtlResult();
|
||||||
|
AtomicLong impCount = new AtomicLong();
|
||||||
|
List<String> errMsg = new ArrayList<>();
|
||||||
|
String esIndex = "";
|
||||||
|
if (config == null) {
|
||||||
|
logger.warn("esSycnCofnig is null, etl go end !");
|
||||||
|
etlResult.setErrorMessage("esSycnCofnig is null, etl go end !");
|
||||||
|
return etlResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESMapping mapping = config.getEsMapping();
|
||||||
|
|
||||||
|
esIndex = mapping.get_index();
|
||||||
|
DruidDataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
|
||||||
|
Pattern pattern = Pattern.compile(".*:(.*)://.*/(.*)\\?.*$");
|
||||||
|
Matcher matcher = pattern.matcher(dataSource.getUrl());
|
||||||
|
if (!matcher.find()) {
|
||||||
|
throw new RuntimeException("Not found the schema of jdbc-url: " + config.getDataSourceKey());
|
||||||
|
}
|
||||||
|
String schema = matcher.group(2);
|
||||||
|
|
||||||
|
logger.info("etl from db: {}, to es index: {}", schema, esIndex);
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
String sql = mapping.getSql();
|
||||||
|
|
||||||
|
// 拼接条件
|
||||||
|
if (mapping.getEtlCondition() != null && params != null) {
|
||||||
|
String etlCondition = mapping.getEtlCondition();
|
||||||
|
int size = params.size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
etlCondition = etlCondition.replace("{" + i + "}", params.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += " " + etlCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("etl sql : {}", mapping.getSql());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bulk) {
|
||||||
|
// 获取总数
|
||||||
|
String countSql = "SELECT COUNT(1) FROM ( " + sql + ") _CNT ";
|
||||||
|
long cnt = (Long) ESSyncUtil.sqlRS(dataSource, 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 ? 0L : count;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 当大于1万条记录时开启多线程
|
||||||
|
if (cnt >= 10000) {
|
||||||
|
int threadCount = 3; // TODO 从配置读取默认为3
|
||||||
|
long perThreadCnt = cnt / threadCount;
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
|
||||||
|
List<Future<Boolean>> futures = new ArrayList<>(threadCount);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
Future<Boolean> future = executor
|
||||||
|
.submit(() -> executeSqlImport(dataSource, sqlFinal, mapping, impCount, errMsg));
|
||||||
|
futures.add(future);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Future<Boolean> future : futures) {
|
||||||
|
future.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
|
} else {
|
||||||
|
executeSqlImport(dataSource, sql, mapping, impCount, errMsg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("自动ETL,无需统计记录总条数,直接进行ETL, index: {}", esIndex);
|
||||||
|
executeSqlImport(dataSource, sql, mapping, impCount, errMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("数据全量导入完成,一共导入 {} 条数据, 耗时: {}", impCount.get(), System.currentTimeMillis() - start);
|
||||||
|
etlResult.setResultMessage("导入ES索引 " + esIndex + " 数据:" + impCount.get() + " 条");
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
errMsg.add(esIndex + " etl failed! ==>" + e.getMessage());
|
||||||
|
}
|
||||||
|
if (errMsg.isEmpty()) {
|
||||||
|
etlResult.setSucceeded(true);
|
||||||
|
} else {
|
||||||
|
etlResult.setErrorMessage(Joiner.on("\n").join(errMsg));
|
||||||
|
}
|
||||||
|
return etlResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processFailBulkResponse(BulkResponse bulkResponse, boolean hasParent) {
|
||||||
|
for (BulkItemResponse response : bulkResponse.getItems()) {
|
||||||
|
if (!response.isFailed()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.getFailure().getStatus() == RestStatus.NOT_FOUND) {
|
||||||
|
logger.warn(response.getFailureMessage());
|
||||||
|
} else {
|
||||||
|
logger.error("全量导入数据有误 {}", response.getFailureMessage());
|
||||||
|
throw new RuntimeException("全量数据 etl 异常: " + response.getFailureMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean executeSqlImport(DataSource ds, String sql, ESMapping mapping, AtomicLong impCount,
|
||||||
|
List<String> errMsg) {
|
||||||
|
try {
|
||||||
|
ESSyncUtil.sqlRS(ds, sql, rs -> {
|
||||||
|
int count = 0;
|
||||||
|
try {
|
||||||
|
BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
|
||||||
|
|
||||||
|
long batchBegin = System.currentTimeMillis();
|
||||||
|
while (rs.next()) {
|
||||||
|
Map<String, Object> esFieldData = new LinkedHashMap<>();
|
||||||
|
for (FieldItem fieldItem : mapping.getSchemaItem().getSelectFields().values()) {
|
||||||
|
|
||||||
|
// 如果是主键字段则不插入
|
||||||
|
if (fieldItem.getFieldName().equals(mapping.get_id())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String fieldName = fieldItem.getFieldName();
|
||||||
|
if (mapping.getSkips().contains(fieldName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object val = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName);
|
||||||
|
esFieldData.put(fieldName, val);
|
||||||
|
}
|
||||||
|
Object idVal = null;
|
||||||
|
if (mapping.get_id() != null) {
|
||||||
|
idVal = rs.getObject(mapping.get_id());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idVal != null) {
|
||||||
|
if (mapping.getParent() == null) {
|
||||||
|
bulkRequestBuilder.add(transportClient
|
||||||
|
.prepareIndex(mapping.get_index(), mapping.get_type(), idVal.toString())
|
||||||
|
.setSource(esFieldData));
|
||||||
|
} else {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
idVal = rs.getObject(mapping.getPk());
|
||||||
|
if (mapping.getParent() == null) {
|
||||||
|
// 删除pk对应的数据
|
||||||
|
SearchResponse response = transportClient.prepareSearch(mapping.get_index())
|
||||||
|
.setTypes(mapping.get_type())
|
||||||
|
.setQuery(QueryBuilders.termQuery(mapping.getPk(), idVal))
|
||||||
|
.get();
|
||||||
|
for (SearchHit hit : response.getHits()) {
|
||||||
|
bulkRequestBuilder.add(transportClient
|
||||||
|
.prepareDelete(mapping.get_index(), mapping.get_type(), hit.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bulkRequestBuilder
|
||||||
|
.add(transportClient.prepareIndex(mapping.get_index(), mapping.get_type())
|
||||||
|
.setSource(esFieldData));
|
||||||
|
} else {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bulkRequestBuilder.numberOfActions() % mapping.getCommitBatch() == 0
|
||||||
|
&& bulkRequestBuilder.numberOfActions() > 0) {
|
||||||
|
long esBatchBegin = System.currentTimeMillis();
|
||||||
|
BulkResponse rp = bulkRequestBuilder.execute().actionGet();
|
||||||
|
if (rp.hasFailures()) {
|
||||||
|
this.processFailBulkResponse(rp, Objects.nonNull(mapping.getParent()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("全量数据批量导入批次耗时: {}, es执行时间: {}, 批次大小: {}, index; {}",
|
||||||
|
(System.currentTimeMillis() - batchBegin),
|
||||||
|
(System.currentTimeMillis() - esBatchBegin),
|
||||||
|
bulkRequestBuilder.numberOfActions(),
|
||||||
|
mapping.get_index());
|
||||||
|
}
|
||||||
|
batchBegin = System.currentTimeMillis();
|
||||||
|
bulkRequestBuilder = transportClient.prepareBulk();
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
impCount.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bulkRequestBuilder.numberOfActions() > 0) {
|
||||||
|
long esBatchBegin = System.currentTimeMillis();
|
||||||
|
BulkResponse rp = bulkRequestBuilder.execute().actionGet();
|
||||||
|
if (rp.hasFailures()) {
|
||||||
|
this.processFailBulkResponse(rp, Objects.nonNull(mapping.getParent()));
|
||||||
|
}
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("全量数据批量导入最后批次耗时: {}, es执行时间: {}, 批次大小: {}, index; {}",
|
||||||
|
(System.currentTimeMillis() - batchBegin),
|
||||||
|
(System.currentTimeMillis() - esBatchBegin),
|
||||||
|
bulkRequestBuilder.numberOfActions(),
|
||||||
|
mapping.get_index());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
errMsg.add(mapping.get_index() + " etl failed! ==>" + e.getMessage());
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,862 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.service;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.ColumnItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.FieldItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.TableItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.support.ESSyncUtil;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.support.ESTemplate;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ES 同步 Service
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-11-01
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class ESSyncService {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(ESSyncService.class);
|
||||||
|
|
||||||
|
private ESTemplate esTemplate;
|
||||||
|
|
||||||
|
public ESSyncService(ESTemplate esTemplate){
|
||||||
|
this.esTemplate = esTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sync(Collection<ESSyncConfig> esSyncConfigs, Dml dml) {
|
||||||
|
long begin = System.currentTimeMillis();
|
||||||
|
if (esSyncConfigs != null) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Destination: {}, database:{}, table:{}, type:{}, effect index count: {}",
|
||||||
|
dml.getDestination(),
|
||||||
|
dml.getDatabase(),
|
||||||
|
dml.getTable(),
|
||||||
|
dml.getType(),
|
||||||
|
esSyncConfigs.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ESSyncConfig config : esSyncConfigs) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Prepared to sync index: {}, destination: {}",
|
||||||
|
config.getEsMapping().get_index(),
|
||||||
|
dml.getDestination());
|
||||||
|
}
|
||||||
|
this.sync(config, dml);
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Sync completed: {}, destination: {}",
|
||||||
|
config.getEsMapping().get_index(),
|
||||||
|
dml.getDestination());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Sync elapsed time: {} ms, effect index count:{}, destination: {}",
|
||||||
|
(System.currentTimeMillis() - begin),
|
||||||
|
esSyncConfigs.size(),
|
||||||
|
dml.getDestination());
|
||||||
|
}
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sync(ESSyncConfig config, Dml dml) {
|
||||||
|
try {
|
||||||
|
// 如果是按时间戳定时更新则返回
|
||||||
|
if (config.getEsMapping().isSyncByTimestamp()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long begin = System.currentTimeMillis();
|
||||||
|
|
||||||
|
String type = dml.getType();
|
||||||
|
if (type != null && type.equalsIgnoreCase("INSERT")) {
|
||||||
|
insert(config, dml);
|
||||||
|
} else if (type != null && type.equalsIgnoreCase("UPDATE")) {
|
||||||
|
update(config, dml);
|
||||||
|
} else if (type != null && type.equalsIgnoreCase("DELETE")) {
|
||||||
|
delete(config, dml);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Sync elapsed time: {} ms,destination: {}, es index: {}",
|
||||||
|
(System.currentTimeMillis() - begin),
|
||||||
|
dml.getDestination(),
|
||||||
|
config.getEsMapping().get_index());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("sync error, es index: {}, DML : {}", config.getEsMapping().get_index(), dml);
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入操作dml
|
||||||
|
*
|
||||||
|
* @param config es配置
|
||||||
|
* @param dml dml数据
|
||||||
|
*/
|
||||||
|
private void insert(ESSyncConfig config, Dml dml) {
|
||||||
|
List<Map<String, Object>> dataList = dml.getData();
|
||||||
|
if (dataList == null || dataList.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SchemaItem schemaItem = config.getEsMapping().getSchemaItem();
|
||||||
|
for (Map<String, Object> data : dataList) {
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schemaItem.getAliasTableItems().size() == 1 && schemaItem.isAllFieldsSimple()) {
|
||||||
|
// ------单表 & 所有字段都为简单字段------
|
||||||
|
singleTableSimpleFiledInsert(config, dml, data);
|
||||||
|
} else {
|
||||||
|
// ------是主表 查询sql来插入------
|
||||||
|
if (schemaItem.getMainTable().getTableName().equalsIgnoreCase(dml.getTable())) {
|
||||||
|
mainTableInsert(config, dml, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从表的操作
|
||||||
|
for (TableItem tableItem : schemaItem.getAliasTableItems().values()) {
|
||||||
|
if (tableItem.isMain()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!tableItem.getTableName().equals(dml.getTable())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 关联条件出现在主表查询条件是否为简单字段
|
||||||
|
boolean allFieldsSimple = true;
|
||||||
|
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
|
||||||
|
if (fieldItem.isMethod() || fieldItem.isBinaryOp()) {
|
||||||
|
allFieldsSimple = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 所有查询字段均为简单字段
|
||||||
|
if (allFieldsSimple) {
|
||||||
|
// 不是子查询
|
||||||
|
if (!tableItem.isSubQuery()) {
|
||||||
|
// ------关联表简单字段插入------
|
||||||
|
Map<String, Object> esFieldData = new LinkedHashMap<>();
|
||||||
|
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
|
||||||
|
Object value = esTemplate.getValFromData(config.getEsMapping(),
|
||||||
|
data,
|
||||||
|
fieldItem.getFieldName(),
|
||||||
|
fieldItem.getColumn().getColumnName());
|
||||||
|
esFieldData.put(fieldItem.getFieldName(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
joinTableSimpleFieldOperation(config, dml, data, tableItem, esFieldData);
|
||||||
|
} else {
|
||||||
|
// ------关联子表简单字段插入------
|
||||||
|
subTableSimpleFieldOperation(config, dml, data, null, tableItem);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ------关联子表复杂字段插入 执行全sql更新es------
|
||||||
|
wholeSqlOperation(config, dml, data, null, tableItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新操作dml
|
||||||
|
*
|
||||||
|
* @param config es配置
|
||||||
|
* @param dml dml数据
|
||||||
|
*/
|
||||||
|
private void update(ESSyncConfig config, Dml dml) {
|
||||||
|
List<Map<String, Object>> dataList = dml.getData();
|
||||||
|
List<Map<String, Object>> oldList = dml.getOld();
|
||||||
|
if (dataList == null || dataList.isEmpty() || oldList == null || oldList.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SchemaItem schemaItem = config.getEsMapping().getSchemaItem();
|
||||||
|
int i = 0;
|
||||||
|
for (Map<String, Object> data : dataList) {
|
||||||
|
Map<String, Object> old = oldList.get(i);
|
||||||
|
if (data == null || data.isEmpty() || old == null || old.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schemaItem.getAliasTableItems().size() == 1 && schemaItem.isAllFieldsSimple()) {
|
||||||
|
// ------单表 & 所有字段都为简单字段------
|
||||||
|
singleTableSimpleFiledUpdate(config, dml, data, old);
|
||||||
|
} else {
|
||||||
|
// ------主表 查询sql来更新------
|
||||||
|
if (schemaItem.getMainTable().getTableName().equalsIgnoreCase(dml.getTable())) {
|
||||||
|
ESMapping mapping = config.getEsMapping();
|
||||||
|
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
|
||||||
|
FieldItem idFieldItem = schemaItem.getSelectFields().get(idFieldName);
|
||||||
|
|
||||||
|
boolean idFieldSimple = true;
|
||||||
|
if (idFieldItem.isMethod() || idFieldItem.isBinaryOp()) {
|
||||||
|
idFieldSimple = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean allUpdateFieldSimple = true;
|
||||||
|
out: for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
|
for (ColumnItem columnItem : fieldItem.getColumnItems()) {
|
||||||
|
if (old.containsKey(columnItem.getColumnName())) {
|
||||||
|
if (fieldItem.isMethod() || fieldItem.isBinaryOp()) {
|
||||||
|
allUpdateFieldSimple = false;
|
||||||
|
break out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不支持主键更新!!
|
||||||
|
|
||||||
|
// 判断是否有外键更新
|
||||||
|
boolean fkChanged = false;
|
||||||
|
for (TableItem tableItem : schemaItem.getAliasTableItems().values()) {
|
||||||
|
if (tableItem.isMain()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
boolean changed = false;
|
||||||
|
for (List<FieldItem> fieldItems : tableItem.getRelationTableFields().values()) {
|
||||||
|
for (FieldItem fieldItem : fieldItems) {
|
||||||
|
if (old.containsKey(fieldItem.getColumn().getColumnName())) {
|
||||||
|
fkChanged = true;
|
||||||
|
changed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果外键有修改,则更新所对应该表的所有查询条件数据
|
||||||
|
if (changed) {
|
||||||
|
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
|
||||||
|
fieldItem.getColumnItems()
|
||||||
|
.forEach(columnItem -> old.put(columnItem.getColumnName(), null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断主键和所更新的字段是否全为简单字段
|
||||||
|
if (idFieldSimple && allUpdateFieldSimple && !fkChanged) {
|
||||||
|
singleTableSimpleFiledUpdate(config, dml, data, old);
|
||||||
|
} else {
|
||||||
|
mainTableUpdate(config, dml, data, old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从表的操作
|
||||||
|
for (TableItem tableItem : schemaItem.getAliasTableItems().values()) {
|
||||||
|
if (tableItem.isMain()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!tableItem.getTableName().equals(dml.getTable())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关联条件出现在主表查询条件是否为简单字段
|
||||||
|
boolean allFieldsSimple = true;
|
||||||
|
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
|
||||||
|
if (fieldItem.isMethod() || fieldItem.isBinaryOp()) {
|
||||||
|
allFieldsSimple = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有查询字段均为简单字段
|
||||||
|
if (allFieldsSimple) {
|
||||||
|
// 不是子查询
|
||||||
|
if (!tableItem.isSubQuery()) {
|
||||||
|
// ------关联表简单字段更新------
|
||||||
|
Map<String, Object> esFieldData = new LinkedHashMap<>();
|
||||||
|
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
|
||||||
|
if (old.containsKey(fieldItem.getColumn().getColumnName())) {
|
||||||
|
Object value = esTemplate.getValFromData(config.getEsMapping(),
|
||||||
|
data,
|
||||||
|
fieldItem.getFieldName(),
|
||||||
|
fieldItem.getColumn().getColumnName());
|
||||||
|
esFieldData.put(fieldItem.getFieldName(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
joinTableSimpleFieldOperation(config, dml, data, tableItem, esFieldData);
|
||||||
|
} else {
|
||||||
|
// ------关联子表简单字段更新------
|
||||||
|
subTableSimpleFieldOperation(config, dml, data, old, tableItem);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ------关联子表复杂字段更新 执行全sql更新es------
|
||||||
|
wholeSqlOperation(config, dml, data, old, tableItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除操作dml
|
||||||
|
*
|
||||||
|
* @param config es配置
|
||||||
|
* @param dml dml数据
|
||||||
|
*/
|
||||||
|
private void delete(ESSyncConfig config, Dml dml) {
|
||||||
|
List<Map<String, Object>> dataList = dml.getData();
|
||||||
|
if (dataList == null || dataList.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SchemaItem schemaItem = config.getEsMapping().getSchemaItem();
|
||||||
|
|
||||||
|
for (Map<String, Object> data : dataList) {
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESMapping mapping = config.getEsMapping();
|
||||||
|
|
||||||
|
// ------是主表------
|
||||||
|
if (schemaItem.getMainTable().getTableName().equalsIgnoreCase(dml.getTable())) {
|
||||||
|
FieldItem idFieldItem = schemaItem.getIdFieldItem(mapping);
|
||||||
|
// 主键为简单字段
|
||||||
|
if (!idFieldItem.isMethod() && !idFieldItem.isBinaryOp()) {
|
||||||
|
Object idVal = esTemplate.getValFromData(mapping,
|
||||||
|
data,
|
||||||
|
idFieldItem.getFieldName(),
|
||||||
|
idFieldItem.getColumn().getColumnName());
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Main table delete es index, destination:{}, table: {}, index: {}, id: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
idVal);
|
||||||
|
}
|
||||||
|
boolean result = esTemplate.delete(mapping, idVal);
|
||||||
|
if (!result) {
|
||||||
|
logger.error("Main table delete es index error, destination:{}, table: {}, index: {}, id: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
idVal);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ------主键带函数, 查询sql获取主键删除------
|
||||||
|
mainTableDelete(config, dml, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从表的操作
|
||||||
|
for (TableItem tableItem : schemaItem.getAliasTableItems().values()) {
|
||||||
|
if (tableItem.isMain()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!tableItem.getTableName().equals(dml.getTable())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关联条件出现在主表查询条件是否为简单字段
|
||||||
|
boolean allFieldsSimple = true;
|
||||||
|
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
|
||||||
|
if (fieldItem.isMethod() || fieldItem.isBinaryOp()) {
|
||||||
|
allFieldsSimple = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有查询字段均为简单字段
|
||||||
|
if (allFieldsSimple) {
|
||||||
|
// 不是子查询
|
||||||
|
if (!tableItem.isSubQuery()) {
|
||||||
|
// ------关联表简单字段更新为null------
|
||||||
|
Map<String, Object> esFieldData = new LinkedHashMap<>();
|
||||||
|
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
|
||||||
|
esFieldData.put(fieldItem.getFieldName(), null);
|
||||||
|
}
|
||||||
|
joinTableSimpleFieldOperation(config, dml, data, tableItem, esFieldData);
|
||||||
|
} else {
|
||||||
|
// ------关联子表简单字段更新------
|
||||||
|
subTableSimpleFieldOperation(config, dml, data, null, tableItem);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ------关联子表复杂字段更新 执行全sql更新es------
|
||||||
|
wholeSqlOperation(config, dml, data, null, tableItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单表简单字段insert
|
||||||
|
*
|
||||||
|
* @param config es配置
|
||||||
|
* @param dml dml信息
|
||||||
|
* @param data 单行dml数据
|
||||||
|
*/
|
||||||
|
private void singleTableSimpleFiledInsert(ESSyncConfig config, Dml dml, Map<String, Object> data) {
|
||||||
|
ESMapping mapping = config.getEsMapping();
|
||||||
|
Map<String, Object> esFieldData = new LinkedHashMap<>();
|
||||||
|
Object idVal = esTemplate.getESDataFromDmlData(mapping, data, esFieldData);
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Single table insert ot es index, destination:{}, table: {}, index: {}, id: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
idVal);
|
||||||
|
}
|
||||||
|
boolean result = esTemplate.insert(mapping, idVal, esFieldData);
|
||||||
|
if (!result) {
|
||||||
|
logger.error("Single table insert to es index error, destination:{}, table: {}, index: {}, id: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
idVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主表(单表)复杂字段insert
|
||||||
|
*
|
||||||
|
* @param config es配置
|
||||||
|
* @param dml dml信息
|
||||||
|
* @param data 单行dml数据
|
||||||
|
*/
|
||||||
|
private void mainTableInsert(ESSyncConfig config, Dml dml, Map<String, Object> data) {
|
||||||
|
ESMapping mapping = config.getEsMapping();
|
||||||
|
String sql = mapping.getSql();
|
||||||
|
String condition = ESSyncUtil.pkConditionSql(mapping, data);
|
||||||
|
sql = ESSyncUtil.appendCondition(sql, condition);
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Main table insert ot es index by query sql, destination:{}, table: {}, index: {}, sql: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
sql.replace("\n", " "));
|
||||||
|
}
|
||||||
|
ESSyncUtil.sqlRS(ds, sql, rs -> {
|
||||||
|
try {
|
||||||
|
while (rs.next()) {
|
||||||
|
Map<String, Object> esFieldData = new LinkedHashMap<>();
|
||||||
|
Object idVal = esTemplate.getESDataFromRS(mapping, rs, esFieldData);
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace(
|
||||||
|
"Main table insert ot es index by query sql, destination:{}, table: {}, index: {}, id: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
idVal);
|
||||||
|
}
|
||||||
|
boolean result = esTemplate.insert(mapping, idVal, esFieldData);
|
||||||
|
if (!result) {
|
||||||
|
logger.error(
|
||||||
|
"Main table insert to es index by query sql error, destination:{}, table: {}, index: {}, id: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
idVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mainTableDelete(ESSyncConfig config, Dml dml, Map<String, Object> data) {
|
||||||
|
ESMapping mapping = config.getEsMapping();
|
||||||
|
String sql = mapping.getSql();
|
||||||
|
String condition = ESSyncUtil.pkConditionSql(mapping, data);
|
||||||
|
sql = ESSyncUtil.appendCondition(sql, condition);
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Main table delete es index by query sql, destination:{}, table: {}, index: {}, sql: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
sql.replace("\n", " "));
|
||||||
|
}
|
||||||
|
ESSyncUtil.sqlRS(ds, sql, rs -> {
|
||||||
|
try {
|
||||||
|
while (rs.next()) {
|
||||||
|
Object idVal = esTemplate.getIdValFromRS(mapping, rs);
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace(
|
||||||
|
"Main table delete ot es index by query sql, destination:{}, table: {}, index: {}, id: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
idVal);
|
||||||
|
}
|
||||||
|
boolean result = esTemplate.delete(mapping, idVal);
|
||||||
|
if (!result) {
|
||||||
|
logger.error(
|
||||||
|
"Main table delete to es index by query sql error, destination:{}, table: {}, index: {}, id: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
idVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联表主表简单字段operation
|
||||||
|
*
|
||||||
|
* @param config es配置
|
||||||
|
* @param dml dml信息
|
||||||
|
* @param data 单行dml数据
|
||||||
|
* @param tableItem 当前表配置
|
||||||
|
*/
|
||||||
|
private void joinTableSimpleFieldOperation(ESSyncConfig config, Dml dml, Map<String, Object> data,
|
||||||
|
TableItem tableItem, Map<String, Object> esFieldData) {
|
||||||
|
ESMapping mapping = config.getEsMapping();
|
||||||
|
|
||||||
|
Map<String, Object> paramsTmp = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<FieldItem, List<FieldItem>> entry : tableItem.getRelationTableFields().entrySet()) {
|
||||||
|
for (FieldItem fieldItem : entry.getValue()) {
|
||||||
|
if (fieldItem.getColumnItems().size() == 1) {
|
||||||
|
Object value = esTemplate.getValFromData(mapping,
|
||||||
|
data,
|
||||||
|
fieldItem.getFieldName(),
|
||||||
|
entry.getKey().getColumn().getColumnName());
|
||||||
|
|
||||||
|
String fieldName = fieldItem.getFieldName();
|
||||||
|
// 判断是否是主键
|
||||||
|
if (fieldName.equals(mapping.get_id())) {
|
||||||
|
fieldName = "_id";
|
||||||
|
}
|
||||||
|
paramsTmp.put(fieldName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.trace("Join table update es index by foreign key, destination:{}, table: {}, index: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index());
|
||||||
|
}
|
||||||
|
boolean result = esTemplate.updateByQuery(config, paramsTmp, esFieldData);
|
||||||
|
if (!result) {
|
||||||
|
logger.error("Join table update es index by foreign key error, destination:{}, table: {}, index: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联子查询, 主表简单字段operation
|
||||||
|
*
|
||||||
|
* @param config es配置
|
||||||
|
* @param dml dml信息
|
||||||
|
* @param data 单行dml数据
|
||||||
|
* @param old 单行old数据
|
||||||
|
* @param tableItem 当前表配置
|
||||||
|
*/
|
||||||
|
private void subTableSimpleFieldOperation(ESSyncConfig config, Dml dml, Map<String, Object> data,
|
||||||
|
Map<String, Object> old, TableItem tableItem) {
|
||||||
|
ESMapping mapping = config.getEsMapping();
|
||||||
|
StringBuilder sql = new StringBuilder(
|
||||||
|
"SELECT * FROM (" + tableItem.getSubQuerySql() + ") " + tableItem.getAlias() + " WHERE ");
|
||||||
|
|
||||||
|
for (FieldItem fkFieldItem : tableItem.getRelationTableFields().keySet()) {
|
||||||
|
String columnName = fkFieldItem.getColumn().getColumnName();
|
||||||
|
Object value = esTemplate.getValFromData(mapping, data, fkFieldItem.getFieldName(), columnName);
|
||||||
|
ESSyncUtil.appendCondition(sql, value, tableItem.getAlias(), columnName);
|
||||||
|
}
|
||||||
|
int len = sql.length();
|
||||||
|
sql.delete(len - 5, len);
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Join table update es index by query sql, destination:{}, table: {}, index: {}, sql: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
sql.toString().replace("\n", " "));
|
||||||
|
}
|
||||||
|
ESSyncUtil.sqlRS(ds, sql.toString(), rs -> {
|
||||||
|
try {
|
||||||
|
while (rs.next()) {
|
||||||
|
Map<String, Object> esFieldData = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
|
||||||
|
if (old != null) {
|
||||||
|
out: for (FieldItem fieldItem1 : tableItem.getSubQueryFields()) {
|
||||||
|
for (ColumnItem columnItem0 : fieldItem.getColumnItems()) {
|
||||||
|
if (fieldItem1.getFieldName().equals(columnItem0.getColumnName()))
|
||||||
|
for (ColumnItem columnItem : fieldItem1.getColumnItems()) {
|
||||||
|
if (old.containsKey(columnItem.getColumnName())) {
|
||||||
|
Object val = esTemplate.getValFromRS(mapping,
|
||||||
|
rs,
|
||||||
|
fieldItem.getFieldName(),
|
||||||
|
fieldItem.getColumn().getColumnName());
|
||||||
|
esFieldData.put(fieldItem.getFieldName(), val);
|
||||||
|
break out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Object val = esTemplate.getValFromRS(mapping,
|
||||||
|
rs,
|
||||||
|
fieldItem.getFieldName(),
|
||||||
|
fieldItem.getColumn().getColumnName());
|
||||||
|
esFieldData.put(fieldItem.getFieldName(), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> paramsTmp = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<FieldItem, List<FieldItem>> entry : tableItem.getRelationTableFields().entrySet()) {
|
||||||
|
for (FieldItem fieldItem : entry.getValue()) {
|
||||||
|
if (fieldItem.getColumnItems().size() == 1) {
|
||||||
|
Object value = esTemplate.getValFromRS(mapping,
|
||||||
|
rs,
|
||||||
|
fieldItem.getFieldName(),
|
||||||
|
entry.getKey().getColumn().getColumnName());
|
||||||
|
String fieldName = fieldItem.getFieldName();
|
||||||
|
// 判断是否是主键
|
||||||
|
if (fieldName.equals(mapping.get_id())) {
|
||||||
|
fieldName = "_id";
|
||||||
|
}
|
||||||
|
paramsTmp.put(fieldName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.trace("Join table update es index by query sql, destination:{}, table: {}, index: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index());
|
||||||
|
}
|
||||||
|
boolean result = esTemplate.updateByQuery(config, paramsTmp, esFieldData);
|
||||||
|
if (!result) {
|
||||||
|
logger.error(
|
||||||
|
"Join table update es index by query sql error, destination:{}, table: {}, index: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联(子查询), 主表复杂字段operation, 全sql执行
|
||||||
|
*
|
||||||
|
* @param config es配置
|
||||||
|
* @param dml dml信息
|
||||||
|
* @param data 单行dml数据
|
||||||
|
* @param tableItem 当前表配置
|
||||||
|
*/
|
||||||
|
private void wholeSqlOperation(ESSyncConfig config, Dml dml, Map<String, Object> data, Map<String, Object> old,
|
||||||
|
TableItem tableItem) {
|
||||||
|
ESMapping mapping = config.getEsMapping();
|
||||||
|
StringBuilder sql = new StringBuilder(mapping.getSql() + " WHERE ");
|
||||||
|
|
||||||
|
for (FieldItem fkFieldItem : tableItem.getRelationTableFields().keySet()) {
|
||||||
|
String columnName = fkFieldItem.getColumn().getColumnName();
|
||||||
|
Object value = esTemplate.getValFromData(mapping, data, fkFieldItem.getFieldName(), columnName);
|
||||||
|
ESSyncUtil.appendCondition(sql, value, tableItem.getAlias(), columnName);
|
||||||
|
}
|
||||||
|
int len = sql.length();
|
||||||
|
sql.delete(len - 5, len);
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Join table update es index by query whole sql, destination:{}, table: {}, index: {}, sql: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
sql.toString().replace("\n", " "));
|
||||||
|
}
|
||||||
|
ESSyncUtil.sqlRS(ds, sql.toString(), rs -> {
|
||||||
|
try {
|
||||||
|
while (rs.next()) {
|
||||||
|
Map<String, Object> esFieldData = new LinkedHashMap<>();
|
||||||
|
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
|
||||||
|
if (old != null) {
|
||||||
|
// 从表子查询
|
||||||
|
out: for (FieldItem fieldItem1 : tableItem.getSubQueryFields()) {
|
||||||
|
for (ColumnItem columnItem0 : fieldItem.getColumnItems()) {
|
||||||
|
if (fieldItem1.getFieldName().equals(columnItem0.getColumnName()))
|
||||||
|
for (ColumnItem columnItem : fieldItem1.getColumnItems()) {
|
||||||
|
if (old.containsKey(columnItem.getColumnName())) {
|
||||||
|
Object val = esTemplate.getValFromRS(mapping,
|
||||||
|
rs,
|
||||||
|
fieldItem.getFieldName(),
|
||||||
|
fieldItem.getFieldName());
|
||||||
|
esFieldData.put(fieldItem.getFieldName(), val);
|
||||||
|
break out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 从表非子查询
|
||||||
|
for (FieldItem fieldItem1 : tableItem.getRelationSelectFieldItems()) {
|
||||||
|
if (fieldItem1.equals(fieldItem)) {
|
||||||
|
for (ColumnItem columnItem : fieldItem1.getColumnItems()) {
|
||||||
|
if (old.containsKey(columnItem.getColumnName())) {
|
||||||
|
Object val = esTemplate.getValFromRS(mapping,
|
||||||
|
rs,
|
||||||
|
fieldItem.getFieldName(),
|
||||||
|
fieldItem.getFieldName());
|
||||||
|
esFieldData.put(fieldItem.getFieldName(), val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Object val = esTemplate
|
||||||
|
.getValFromRS(mapping, rs, fieldItem.getFieldName(), fieldItem.getFieldName());
|
||||||
|
esFieldData.put(fieldItem.getFieldName(), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> paramsTmp = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<FieldItem, List<FieldItem>> entry : tableItem.getRelationTableFields().entrySet()) {
|
||||||
|
for (FieldItem fieldItem : entry.getValue()) {
|
||||||
|
Object value = esTemplate
|
||||||
|
.getValFromRS(mapping, rs, fieldItem.getFieldName(), fieldItem.getFieldName());
|
||||||
|
String fieldName = fieldItem.getFieldName();
|
||||||
|
// 判断是否是主键
|
||||||
|
if (fieldName.equals(mapping.get_id())) {
|
||||||
|
fieldName = "_id";
|
||||||
|
}
|
||||||
|
paramsTmp.put(fieldName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.trace(
|
||||||
|
"Join table update es index by query whole sql, destination:{}, table: {}, index: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index());
|
||||||
|
}
|
||||||
|
boolean result = esTemplate.updateByQuery(config, paramsTmp, esFieldData);
|
||||||
|
if (!result) {
|
||||||
|
logger.error(
|
||||||
|
"Join table update es index by query whole sql error, destination:{}, table: {}, index: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单表简单字段update
|
||||||
|
*
|
||||||
|
* @param config es配置
|
||||||
|
* @param dml dml信息
|
||||||
|
* @param data 单行data数据
|
||||||
|
* @param old 单行old数据
|
||||||
|
*/
|
||||||
|
private void singleTableSimpleFiledUpdate(ESSyncConfig config, Dml dml, Map<String, Object> data,
|
||||||
|
Map<String, Object> old) {
|
||||||
|
ESMapping mapping = config.getEsMapping();
|
||||||
|
Map<String, Object> esFieldData = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
Object idVal = esTemplate.getESDataFromDmlData(mapping, data, old, esFieldData);
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Main table update ot es index, destination:{}, table: {}, index: {}, id: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
idVal);
|
||||||
|
}
|
||||||
|
boolean result = esTemplate.update(mapping, idVal, esFieldData);
|
||||||
|
if (!result) {
|
||||||
|
logger.error("Main table update to es index error, destination:{}, table: {}, index: {}, id: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
idVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主表(单表)复杂字段update
|
||||||
|
*
|
||||||
|
* @param config es配置
|
||||||
|
* @param dml dml信息
|
||||||
|
* @param data 单行dml数据
|
||||||
|
*/
|
||||||
|
private void mainTableUpdate(ESSyncConfig config, Dml dml, Map<String, Object> data, Map<String, Object> old) {
|
||||||
|
ESMapping mapping = config.getEsMapping();
|
||||||
|
String sql = mapping.getSql();
|
||||||
|
String condition = ESSyncUtil.pkConditionSql(mapping, data);
|
||||||
|
sql = ESSyncUtil.appendCondition(sql, condition);
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Main table update ot es index by query sql, destination:{}, table: {}, index: {}, sql: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
sql.replace("\n", " "));
|
||||||
|
}
|
||||||
|
ESSyncUtil.sqlRS(ds, sql, rs -> {
|
||||||
|
try {
|
||||||
|
while (rs.next()) {
|
||||||
|
Map<String, Object> esFieldData = new LinkedHashMap<>();
|
||||||
|
Object idVal = esTemplate.getESDataFromRS(mapping, rs, old, esFieldData);
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace(
|
||||||
|
"Main table update ot es index by query sql, destination:{}, table: {}, index: {}, id: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
idVal);
|
||||||
|
}
|
||||||
|
boolean result = esTemplate.update(mapping, idVal, esFieldData);
|
||||||
|
if (!result) {
|
||||||
|
logger.error(
|
||||||
|
"Main table update to es index by query sql error, destination:{}, table: {}, index: {}, id: {}",
|
||||||
|
config.getDestination(),
|
||||||
|
dml.getTable(),
|
||||||
|
mapping.get_index(),
|
||||||
|
idVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.support;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.ColumnItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.TableItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ES 同步工具同类
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-11-01
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class ESSyncUtil {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(ESSyncUtil.class);
|
||||||
|
|
||||||
|
public static Object convertToEsObj(Object val, String fieldInfo) {
|
||||||
|
if (val == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (fieldInfo.startsWith("array:")) {
|
||||||
|
String separator = fieldInfo.substring("array:".length()).trim();
|
||||||
|
String[] values = val.toString().split(separator);
|
||||||
|
return Arrays.asList(values);
|
||||||
|
} else if (fieldInfo.startsWith("object")) {
|
||||||
|
return JSON.parse(val.toString());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型转换为Mapping中对应的类型
|
||||||
|
*/
|
||||||
|
public static Object typeConvert(Object val, String esType) {
|
||||||
|
if (val == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (esType == null) {
|
||||||
|
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");
|
||||||
|
} 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+08:00");
|
||||||
|
} else {
|
||||||
|
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss+08:00");
|
||||||
|
}
|
||||||
|
} 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+08:00");
|
||||||
|
} else {
|
||||||
|
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss+08:00");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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+08:00");
|
||||||
|
} else {
|
||||||
|
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss+08:00");
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
DateTime dateTime = new DateTime(dt);
|
||||||
|
if (dateTime.getMillisOfSecond() != 0) {
|
||||||
|
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss.SSS+08:00");
|
||||||
|
} else {
|
||||||
|
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss+08:00");
|
||||||
|
}
|
||||||
|
} else if (v.length() == 10 && v.charAt(4) == '-' && v.charAt(7) == '-') {
|
||||||
|
DateTime dateTime = new DateTime(v);
|
||||||
|
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 = "-";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blob转byte[]
|
||||||
|
*/
|
||||||
|
private static byte[] blobToBytes(Blob blob) {
|
||||||
|
try (InputStream is = blob.getBinaryStream()) {
|
||||||
|
byte[] b = new byte[(int) blob.length()];
|
||||||
|
is.read(b);
|
||||||
|
return b;
|
||||||
|
} catch (IOException | SQLException e) {
|
||||||
|
logger.error(e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拼接主键条件
|
||||||
|
*
|
||||||
|
* @param mapping
|
||||||
|
* @param data
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String pkConditionSql(ESMapping mapping, Map<String, Object> data) {
|
||||||
|
Set<ColumnItem> idColumns = new LinkedHashSet<>();
|
||||||
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
|
|
||||||
|
TableItem mainTable = schemaItem.getMainTable();
|
||||||
|
|
||||||
|
for (ColumnItem idColumnItem : schemaItem.getIdFieldItem(mapping).getColumnItems()) {
|
||||||
|
if ((mainTable.getAlias() == null && idColumnItem.getOwner() == null)
|
||||||
|
|| (mainTable.getAlias() != null && mainTable.getAlias().equals(idColumnItem.getOwner()))) {
|
||||||
|
idColumns.add(idColumnItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idColumns.isEmpty()) {
|
||||||
|
throw new RuntimeException("Not found primary key field in main table");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接condition
|
||||||
|
StringBuilder condition = new StringBuilder(" ");
|
||||||
|
for (ColumnItem idColumn : idColumns) {
|
||||||
|
Object idVal = data.get(idColumn.getColumnName());
|
||||||
|
if (mainTable.getAlias() != null) condition.append(mainTable.getAlias()).append(".");
|
||||||
|
condition.append(idColumn.getColumnName()).append("=");
|
||||||
|
if (idVal instanceof String) {
|
||||||
|
condition.append("'").append(idVal).append("' AND ");
|
||||||
|
} else {
|
||||||
|
condition.append(idVal).append(" AND ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (condition.toString().endsWith("AND ")) {
|
||||||
|
int len2 = condition.length();
|
||||||
|
condition.delete(len2 - 4, len2);
|
||||||
|
}
|
||||||
|
return condition.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String appendCondition(String sql, String condition) {
|
||||||
|
return sql + " WHERE " + condition + " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void appendCondition(StringBuilder sql, Object value, String owner, String columnName) {
|
||||||
|
if (value instanceof String) {
|
||||||
|
sql.append(owner).append(".").append(columnName).append("='").append(value).append("' AND ");
|
||||||
|
} else {
|
||||||
|
sql.append(owner).append(".").append(columnName).append("=").append(value).append(" AND ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行查询sql
|
||||||
|
*/
|
||||||
|
public static Object sqlRS(DataSource ds, String sql, Function<ResultSet, Object> fun) {
|
||||||
|
Connection conn = null;
|
||||||
|
Statement smt = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
conn = ds.getConnection();
|
||||||
|
smt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
|
||||||
|
smt.setFetchSize(Integer.MIN_VALUE);
|
||||||
|
rs = smt.executeQuery(sql);
|
||||||
|
|
||||||
|
return fun.apply(rs);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error("sqlRs has error, sql: {} ", sql);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
if (rs != null) {
|
||||||
|
try {
|
||||||
|
rs.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error("error to close result set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (smt != null) {
|
||||||
|
try {
|
||||||
|
smt.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error("error to close statement");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (conn != null) {
|
||||||
|
try {
|
||||||
|
conn.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error("error to close db connection");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,526 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.support;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import org.elasticsearch.action.bulk.BulkItemResponse;
|
||||||
|
import org.elasticsearch.action.bulk.BulkRequestBuilder;
|
||||||
|
import org.elasticsearch.action.bulk.BulkResponse;
|
||||||
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
|
import org.elasticsearch.client.transport.TransportClient;
|
||||||
|
import org.elasticsearch.cluster.metadata.MappingMetaData;
|
||||||
|
import org.elasticsearch.common.collect.ImmutableOpenMap;
|
||||||
|
import org.elasticsearch.index.query.BoolQueryBuilder;
|
||||||
|
import org.elasticsearch.index.query.QueryBuilder;
|
||||||
|
import org.elasticsearch.index.query.QueryBuilders;
|
||||||
|
import org.elasticsearch.index.reindex.BulkByScrollResponse;
|
||||||
|
import org.elasticsearch.index.reindex.UpdateByQueryAction;
|
||||||
|
import org.elasticsearch.index.reindex.UpdateByQueryRequestBuilder;
|
||||||
|
import org.elasticsearch.rest.RestStatus;
|
||||||
|
import org.elasticsearch.script.Script;
|
||||||
|
import org.elasticsearch.script.ScriptType;
|
||||||
|
import org.elasticsearch.search.SearchHit;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.ColumnItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.FieldItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ES 操作模板
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-11-01
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class ESTemplate {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ESTemplate.class);
|
||||||
|
|
||||||
|
private static final int MAX_BATCH_SIZE = 1000;
|
||||||
|
|
||||||
|
private TransportClient transportClient;
|
||||||
|
|
||||||
|
public ESTemplate(TransportClient transportClient){
|
||||||
|
this.transportClient = transportClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入数据
|
||||||
|
*
|
||||||
|
* @param mapping
|
||||||
|
* @param pkVal
|
||||||
|
* @param esFieldData
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean insert(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
|
||||||
|
BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
|
||||||
|
if (mapping.get_id() != null) {
|
||||||
|
bulkRequestBuilder
|
||||||
|
.add(transportClient.prepareIndex(mapping.get_index(), mapping.get_type(), pkVal.toString())
|
||||||
|
.setSource(esFieldData));
|
||||||
|
} else {
|
||||||
|
SearchResponse response = transportClient.prepareSearch(mapping.get_index())
|
||||||
|
.setTypes(mapping.get_type())
|
||||||
|
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
|
||||||
|
.setSize(MAX_BATCH_SIZE)
|
||||||
|
.get();
|
||||||
|
for (SearchHit hit : response.getHits()) {
|
||||||
|
bulkRequestBuilder
|
||||||
|
.add(transportClient.prepareDelete(mapping.get_index(), mapping.get_type(), hit.getId()));
|
||||||
|
}
|
||||||
|
bulkRequestBuilder
|
||||||
|
.add(transportClient.prepareIndex(mapping.get_index(), mapping.get_type()).setSource(esFieldData));
|
||||||
|
}
|
||||||
|
return commitBulkRequest(bulkRequestBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据主键更新数据
|
||||||
|
*
|
||||||
|
* @param mapping
|
||||||
|
* @param pkVal
|
||||||
|
* @param esFieldData
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean update(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
|
||||||
|
BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
|
||||||
|
append4Update(bulkRequestBuilder, mapping, pkVal, esFieldData);
|
||||||
|
return commitBulkRequest(bulkRequestBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void append4Update(BulkRequestBuilder bulkRequestBuilder, ESMapping mapping, Object pkVal,
|
||||||
|
Map<String, Object> esFieldData) {
|
||||||
|
if (mapping.get_id() != null) {
|
||||||
|
bulkRequestBuilder
|
||||||
|
.add(transportClient.prepareUpdate(mapping.get_index(), mapping.get_type(), pkVal.toString())
|
||||||
|
.setDoc(esFieldData));
|
||||||
|
} else {
|
||||||
|
SearchResponse response = transportClient.prepareSearch(mapping.get_index())
|
||||||
|
.setTypes(mapping.get_type())
|
||||||
|
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
|
||||||
|
.setSize(MAX_BATCH_SIZE)
|
||||||
|
.get();
|
||||||
|
for (SearchHit hit : response.getHits()) {
|
||||||
|
bulkRequestBuilder
|
||||||
|
.add(transportClient.prepareUpdate(mapping.get_index(), mapping.get_type(), hit.getId())
|
||||||
|
.setDoc(esFieldData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update by query
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* @param paramsTmp
|
||||||
|
* @param esFieldData
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean updateByQuery(ESSyncConfig config, Map<String, Object> paramsTmp, Map<String, Object> esFieldData) {
|
||||||
|
if (paramsTmp.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ESMapping mapping = config.getEsMapping();
|
||||||
|
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
|
||||||
|
paramsTmp.forEach((fieldName, value) -> queryBuilder.must(QueryBuilders.termsQuery(fieldName, value)));
|
||||||
|
|
||||||
|
SearchResponse response = transportClient.prepareSearch(mapping.get_index())
|
||||||
|
.setTypes(mapping.get_type())
|
||||||
|
.setSize(0)
|
||||||
|
.setQuery(queryBuilder)
|
||||||
|
.get();
|
||||||
|
long count = response.getHits().getTotalHits();
|
||||||
|
// 如果更新量大于Max, 查询sql批量更新
|
||||||
|
if (count > MAX_BATCH_SIZE) {
|
||||||
|
BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
|
||||||
|
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
|
||||||
|
// 查询sql更新
|
||||||
|
StringBuilder sql = new StringBuilder("SELECT * FROM (" + mapping.getSql() + ") _v WHERE ");
|
||||||
|
paramsTmp.forEach(
|
||||||
|
(fieldName, value) -> sql.append("_v.").append(fieldName).append("=").append(value).append(" AND "));
|
||||||
|
int len = sql.length();
|
||||||
|
sql.delete(len - 4, len);
|
||||||
|
ESSyncUtil.sqlRS(ds, sql.toString(), rs -> {
|
||||||
|
int exeCount = 1;
|
||||||
|
try {
|
||||||
|
BulkRequestBuilder bulkRequestBuilderTmp = bulkRequestBuilder;
|
||||||
|
while (rs.next()) {
|
||||||
|
Object idVal = getIdValFromRS(mapping, rs);
|
||||||
|
append4Update(bulkRequestBuilderTmp, mapping, idVal, esFieldData);
|
||||||
|
|
||||||
|
if (exeCount % mapping.getCommitBatch() == 0 && bulkRequestBuilderTmp.numberOfActions() > 0) {
|
||||||
|
commitBulkRequest(bulkRequestBuilderTmp);
|
||||||
|
bulkRequestBuilderTmp = transportClient.prepareBulk();
|
||||||
|
}
|
||||||
|
exeCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bulkRequestBuilder.numberOfActions() > 0) {
|
||||||
|
commitBulkRequest(bulkRequestBuilderTmp);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return updateByQuery(mapping, queryBuilder, esFieldData, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean updateByQuery(ESMapping mapping, QueryBuilder queryBuilder, Map<String, Object> esFieldData,
|
||||||
|
int counter) {
|
||||||
|
if (CollectionUtils.isEmpty(esFieldData)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
esFieldData.forEach((key, value) -> {
|
||||||
|
if (value instanceof Map) {
|
||||||
|
HashMap mapValue = (HashMap) value;
|
||||||
|
if (mapValue.containsKey("lon") && mapValue.containsKey("lat") && mapValue.size() == 2) {
|
||||||
|
sb.append("ctx._source")
|
||||||
|
.append("['")
|
||||||
|
.append(key)
|
||||||
|
.append("']")
|
||||||
|
.append(" = [")
|
||||||
|
.append(mapValue.get("lon"))
|
||||||
|
.append(", ")
|
||||||
|
.append(mapValue.get("lat"))
|
||||||
|
.append("];");
|
||||||
|
} else {
|
||||||
|
sb.append("ctx._source").append("[\"").append(key).append("\"]").append(" = ");
|
||||||
|
sb.append(JSON.toJSONString(value));
|
||||||
|
sb.append(";");
|
||||||
|
}
|
||||||
|
} else if (value instanceof List) {
|
||||||
|
sb.append("ctx._source").append("[\"").append(key).append("\"]").append(" = ");
|
||||||
|
sb.append(JSON.toJSONString(value));
|
||||||
|
sb.append(";");
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
sb.append("ctx._source")
|
||||||
|
.append("['")
|
||||||
|
.append(key)
|
||||||
|
.append("']")
|
||||||
|
.append(" = '")
|
||||||
|
.append(value)
|
||||||
|
.append("';");
|
||||||
|
} else {
|
||||||
|
sb.append("ctx._source").append("['").append(key).append("']").append(" = ").append(value).append(";");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
String scriptLine = sb.toString();
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace(scriptLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateByQueryRequestBuilder updateByQuery = UpdateByQueryAction.INSTANCE.newRequestBuilder(transportClient);
|
||||||
|
updateByQuery.source(mapping.get_index())
|
||||||
|
.abortOnVersionConflict(false)
|
||||||
|
.filter(queryBuilder)
|
||||||
|
.script(new Script(ScriptType.INLINE, "painless", scriptLine, Collections.emptyMap()));
|
||||||
|
|
||||||
|
BulkByScrollResponse response = updateByQuery.get();
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("updateByQuery response: {}", response.getStatus());
|
||||||
|
}
|
||||||
|
if (!CollectionUtils.isEmpty(response.getSearchFailures())) {
|
||||||
|
logger.error("script update_for_search has search error: " + response.getBulkFailures());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CollectionUtils.isEmpty(response.getBulkFailures())) {
|
||||||
|
logger.error("script update_for_search has update error: " + response.getBulkFailures());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.getStatus().getVersionConflicts() > 0) {
|
||||||
|
if (counter >= 3) {
|
||||||
|
logger.error("第 {} 次执行updateByQuery, 依旧存在分片版本冲突,不再继续重试。", counter);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logger.warn("本次updateByQuery存在分片版本冲突,准备重新执行...");
|
||||||
|
try {
|
||||||
|
TimeUnit.SECONDS.sleep(1);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return updateByQuery(mapping, queryBuilder, esFieldData, ++counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过主键删除数据
|
||||||
|
*
|
||||||
|
* @param mapping
|
||||||
|
* @param pkVal
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean delete(ESMapping mapping, Object pkVal) {
|
||||||
|
BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
|
||||||
|
if (mapping.get_id() != null) {
|
||||||
|
bulkRequestBuilder
|
||||||
|
.add(transportClient.prepareDelete(mapping.get_index(), mapping.get_type(), pkVal.toString()));
|
||||||
|
} else {
|
||||||
|
SearchResponse response = transportClient.prepareSearch(mapping.get_index())
|
||||||
|
.setTypes(mapping.get_type())
|
||||||
|
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
|
||||||
|
.setSize(MAX_BATCH_SIZE)
|
||||||
|
.get();
|
||||||
|
for (SearchHit hit : response.getHits()) {
|
||||||
|
bulkRequestBuilder
|
||||||
|
.add(transportClient.prepareDelete(mapping.get_index(), mapping.get_type(), hit.getId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commitBulkRequest(bulkRequestBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量提交
|
||||||
|
*
|
||||||
|
* @param bulkRequestBuilder
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static boolean commitBulkRequest(BulkRequestBuilder bulkRequestBuilder) {
|
||||||
|
if (bulkRequestBuilder.numberOfActions() > 0) {
|
||||||
|
BulkResponse response = bulkRequestBuilder.execute().actionGet();
|
||||||
|
if (response.hasFailures()) {
|
||||||
|
for (BulkItemResponse itemResponse : response.getItems()) {
|
||||||
|
if (!itemResponse.isFailed()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemResponse.getFailure().getStatus() == RestStatus.NOT_FOUND) {
|
||||||
|
logger.warn(itemResponse.getFailureMessage());
|
||||||
|
} else {
|
||||||
|
logger.error("ES sync commit error: {}", itemResponse.getFailureMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !response.hasFailures();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValFromRS(ESMapping mapping, ResultSet resultSet, String fieldName,
|
||||||
|
String columnName) throws SQLException {
|
||||||
|
String esType = getEsType(mapping, fieldName);
|
||||||
|
|
||||||
|
Object value = resultSet.getObject(columnName);
|
||||||
|
if (value instanceof Boolean) {
|
||||||
|
if (!"boolean".equals(esType)) {
|
||||||
|
value = resultSet.getByte(columnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是对象类型
|
||||||
|
if (mapping.getObjFields().containsKey(fieldName)) {
|
||||||
|
return ESSyncUtil.convertToEsObj(value, mapping.getObjFields().get(fieldName));
|
||||||
|
} else {
|
||||||
|
return ESSyncUtil.typeConvert(value, esType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet,
|
||||||
|
Map<String, Object> esFieldData) throws SQLException {
|
||||||
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
|
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
|
||||||
|
Object resultIdVal = null;
|
||||||
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
|
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
|
||||||
|
|
||||||
|
if (fieldItem.getFieldName().equals(idFieldName)) {
|
||||||
|
resultIdVal = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fieldItem.getFieldName().equals(mapping.get_id())
|
||||||
|
&& !mapping.getSkips().contains(fieldItem.getFieldName())) {
|
||||||
|
esFieldData.put(fieldItem.getFieldName(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultIdVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getIdValFromRS(ESMapping mapping, ResultSet resultSet) throws SQLException {
|
||||||
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
|
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
|
||||||
|
Object resultIdVal = null;
|
||||||
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
|
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
|
||||||
|
|
||||||
|
if (fieldItem.getFieldName().equals(idFieldName)) {
|
||||||
|
resultIdVal = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultIdVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld,
|
||||||
|
Map<String, Object> esFieldData) throws SQLException {
|
||||||
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
|
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
|
||||||
|
Object resultIdVal = null;
|
||||||
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
|
if (fieldItem.getFieldName().equals(idFieldName)) {
|
||||||
|
resultIdVal = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ColumnItem columnItem : fieldItem.getColumnItems()) {
|
||||||
|
if (dmlOld.containsKey(columnItem.getColumnName())
|
||||||
|
&& !mapping.getSkips().contains(fieldItem.getFieldName())) {
|
||||||
|
esFieldData.put(fieldItem.getFieldName(),
|
||||||
|
getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultIdVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValFromData(ESMapping mapping, Map<String, Object> dmlData, String fieldName, String columnName) {
|
||||||
|
String esType = getEsType(mapping, fieldName);
|
||||||
|
Object value = dmlData.get(columnName);
|
||||||
|
if (value instanceof Byte) {
|
||||||
|
if ("boolean".equals(esType)) {
|
||||||
|
value = ((Byte) value).intValue() != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是对象类型
|
||||||
|
if (mapping.getObjFields().containsKey(fieldName)) {
|
||||||
|
return ESSyncUtil.convertToEsObj(value, mapping.getObjFields().get(fieldName));
|
||||||
|
} else {
|
||||||
|
return ESSyncUtil.typeConvert(value, esType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将dml的data转换为es的data
|
||||||
|
*
|
||||||
|
* @param mapping 配置mapping
|
||||||
|
* @param dmlData dml data
|
||||||
|
* @param esFieldData es data
|
||||||
|
* @return 返回 id 值
|
||||||
|
*/
|
||||||
|
public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData,
|
||||||
|
Map<String, Object> esFieldData) {
|
||||||
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
|
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
|
||||||
|
Object resultIdVal = null;
|
||||||
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
|
String columnName = fieldItem.getColumnItems().iterator().next().getColumnName();
|
||||||
|
Object value = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName);
|
||||||
|
|
||||||
|
if (fieldItem.getFieldName().equals(idFieldName)) {
|
||||||
|
resultIdVal = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fieldItem.getFieldName().equals(mapping.get_id())
|
||||||
|
&& !mapping.getSkips().contains(fieldItem.getFieldName())) {
|
||||||
|
esFieldData.put(fieldItem.getFieldName(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultIdVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将dml的data, old转换为es的data
|
||||||
|
*
|
||||||
|
* @param mapping 配置mapping
|
||||||
|
* @param dmlData dml data
|
||||||
|
* @param esFieldData es data
|
||||||
|
* @return 返回 id 值
|
||||||
|
*/
|
||||||
|
public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> dmlOld,
|
||||||
|
Map<String, Object> esFieldData) {
|
||||||
|
SchemaItem schemaItem = mapping.getSchemaItem();
|
||||||
|
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
|
||||||
|
Object resultIdVal = null;
|
||||||
|
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
|
||||||
|
String columnName = fieldItem.getColumnItems().iterator().next().getColumnName();
|
||||||
|
|
||||||
|
if (fieldItem.getFieldName().equals(idFieldName)) {
|
||||||
|
resultIdVal = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dmlOld.get(columnName) != null && !mapping.getSkips().contains(fieldItem.getFieldName())) {
|
||||||
|
esFieldData.put(fieldItem.getFieldName(),
|
||||||
|
getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultIdVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* es 字段类型本地缓存
|
||||||
|
*/
|
||||||
|
private static ConcurrentMap<String, Map<String, String>> esFieldTypes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取es mapping中的属性类型
|
||||||
|
*
|
||||||
|
* @param mapping mapping配置
|
||||||
|
* @param fieldName 属性名
|
||||||
|
* @return 类型
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private String getEsType(ESMapping mapping, String fieldName) {
|
||||||
|
String key = mapping.get_index() + "-" + mapping.get_type();
|
||||||
|
Map<String, String> fieldType = esFieldTypes.get(key);
|
||||||
|
if (fieldType == null) {
|
||||||
|
ImmutableOpenMap<String, MappingMetaData> mappings;
|
||||||
|
try {
|
||||||
|
mappings = transportClient.admin()
|
||||||
|
.cluster()
|
||||||
|
.prepareState()
|
||||||
|
.execute()
|
||||||
|
.actionGet()
|
||||||
|
.getState()
|
||||||
|
.getMetaData()
|
||||||
|
.getIndices()
|
||||||
|
.get(mapping.get_index())
|
||||||
|
.getMappings();
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
throw new IllegalArgumentException("Not found the mapping info of index: " + mapping.get_index());
|
||||||
|
}
|
||||||
|
MappingMetaData mappingMetaData = mappings.get(mapping.get_type());
|
||||||
|
if (mappingMetaData == null) {
|
||||||
|
throw new IllegalArgumentException("Not found the mapping info of index: " + mapping.get_index());
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldType = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
Map<String, Object> sourceMap = mappingMetaData.getSourceAsMap();
|
||||||
|
Map<String, Object> esMapping = (Map<String, Object>) sourceMap.get("properties");
|
||||||
|
for (Map.Entry<String, Object> entry : esMapping.entrySet()) {
|
||||||
|
Map<String, Object> value = (Map<String, Object>) entry.getValue();
|
||||||
|
if (value.containsKey("properties")) {
|
||||||
|
fieldType.put(entry.getKey(), "object");
|
||||||
|
} else {
|
||||||
|
fieldType.put(entry.getKey(), (String) value.get("type"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
esFieldTypes.put(key, fieldType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldType.get(fieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
es=com.alibaba.otter.canal.client.adapter.es.ESAdapter
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
dataSourceKey: defaultDS
|
||||||
|
destination: example
|
||||||
|
esMapping:
|
||||||
|
_index: mytest_user
|
||||||
|
_type: _doc
|
||||||
|
_id: _id
|
||||||
|
# pk: id
|
||||||
|
sql: "select a.id as _id, a.name as _name, a.role_id as _role_id, b.role_name as _role_name,
|
||||||
|
a.c_time as _c_time, c.labels as _labels from user a
|
||||||
|
left join role b on b.id=a.role_id
|
||||||
|
left join (select user_id, group_concat(label order by id desc separator ';') as labels from label
|
||||||
|
group by user_id) c on c.user_id=a.id"
|
||||||
|
# objFields:
|
||||||
|
# _labels: array:;
|
||||||
|
etlCondition: "where a.c_time>='{0}'"
|
||||||
|
commitBatch: 3000
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.test;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfigLoader;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
|
||||||
|
public class ConfigLoadTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
// AdapterConfigs.put("es", "mytest_user.yml");
|
||||||
|
// 加载数据源连接池
|
||||||
|
DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoad() {
|
||||||
|
Map<String, ESSyncConfig> configMap = ESSyncConfigLoader.load();
|
||||||
|
ESSyncConfig config = configMap.get("mytest_user.yml");
|
||||||
|
Assert.assertNotNull(config);
|
||||||
|
Assert.assertEquals("defaultDS", config.getDataSourceKey());
|
||||||
|
ESSyncConfig.ESMapping esMapping = config.getEsMapping();
|
||||||
|
Assert.assertEquals("mytest_user", esMapping.get_index());
|
||||||
|
Assert.assertEquals("_doc", esMapping.get_type());
|
||||||
|
Assert.assertEquals("id", esMapping.get_id());
|
||||||
|
Assert.assertNotNull(esMapping.getSql());
|
||||||
|
|
||||||
|
// Map<String, List<ESSyncConfig>> dbTableEsSyncConfig =
|
||||||
|
// ESSyncConfigLoader.getDbTableEsSyncConfig();
|
||||||
|
// Assert.assertFalse(dbTableEsSyncConfig.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.FieldItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.TableItem;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.SqlParser;
|
||||||
|
|
||||||
|
public class SqlParseTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseTest() {
|
||||||
|
String sql = "select a.id, concat(a.name,'_test') as name, a.role_id, b.name as role_name, c.labels from user a "
|
||||||
|
+ "left join role b on a.role_id=b.id "
|
||||||
|
+ "left join (select user_id, group_concat(label,',') as labels from user_label "
|
||||||
|
+ "group by user_id) c on c.user_id=a.id";
|
||||||
|
SchemaItem schemaItem = SqlParser.parse(sql);
|
||||||
|
|
||||||
|
// 通过表名找 TableItem
|
||||||
|
List<TableItem> tableItems = schemaItem.getTableItemAliases().get("user_label".toLowerCase());
|
||||||
|
tableItems.forEach(tableItem -> Assert.assertEquals("c", tableItem.getAlias()));
|
||||||
|
|
||||||
|
TableItem tableItem = tableItems.get(0);
|
||||||
|
Assert.assertFalse(tableItem.isMain());
|
||||||
|
Assert.assertTrue(tableItem.isSubQuery());
|
||||||
|
// 通过字段名找 FieldItem
|
||||||
|
List<FieldItem> fieldItems = schemaItem.getColumnFields().get(tableItem.getAlias() + ".label".toLowerCase());
|
||||||
|
fieldItems.forEach(
|
||||||
|
fieldItem -> Assert.assertEquals("c.labels", fieldItem.getOwner() + "." + fieldItem.getFieldName()));
|
||||||
|
|
||||||
|
// 获取当前表关联条件字段
|
||||||
|
Map<FieldItem, List<FieldItem>> relationTableFields = tableItem.getRelationTableFields();
|
||||||
|
relationTableFields.keySet()
|
||||||
|
.forEach(fieldItem -> Assert.assertEquals("user_id", fieldItem.getColumn().getColumnName()));
|
||||||
|
|
||||||
|
// 获取关联字段在select中的对应字段
|
||||||
|
// List<FieldItem> relationSelectFieldItem =
|
||||||
|
// tableItem.getRelationKeyFieldItems();
|
||||||
|
// relationSelectFieldItem.forEach(fieldItem -> Assert.assertEquals("c.labels",
|
||||||
|
// fieldItem.getOwner() + "." + fieldItem.getColumn().getColumnName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.test;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
|
|
||||||
|
public class TestConstant {
|
||||||
|
|
||||||
|
public final static String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true";
|
||||||
|
public final static String jdbcUser = "root";
|
||||||
|
public final static String jdbcPassword = "121212";
|
||||||
|
|
||||||
|
public final static String esHosts = "127.0.0.1:9300";
|
||||||
|
public final static String clusterNmae = "elasticsearch";
|
||||||
|
|
||||||
|
public static DruidDataSource dataSource;
|
||||||
|
|
||||||
|
static {
|
||||||
|
dataSource = new DruidDataSource();
|
||||||
|
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
|
||||||
|
dataSource.setUrl(jdbcUrl);
|
||||||
|
dataSource.setUsername(jdbcUser);
|
||||||
|
dataSource.setPassword(jdbcPassword);
|
||||||
|
dataSource.setInitialSize(1);
|
||||||
|
dataSource.setMinIdle(1);
|
||||||
|
dataSource.setMaxActive(1);
|
||||||
|
dataSource.setMaxWait(60000);
|
||||||
|
dataSource.setTimeBetweenEvictionRunsMillis(60000);
|
||||||
|
dataSource.setMinEvictableIdleTimeMillis(300000);
|
||||||
|
dataSource.setPoolPreparedStatements(false);
|
||||||
|
dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
|
||||||
|
dataSource.setValidationQuery("select 1");
|
||||||
|
try {
|
||||||
|
dataSource.init();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.test.sync;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.test.TestConstant;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
|
||||||
|
|
||||||
|
public class Common {
|
||||||
|
|
||||||
|
public static ESAdapter init() {
|
||||||
|
DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource);
|
||||||
|
|
||||||
|
OuterAdapterConfig outerAdapterConfig = new OuterAdapterConfig();
|
||||||
|
outerAdapterConfig.setName("es");
|
||||||
|
outerAdapterConfig.setHosts(TestConstant.esHosts);
|
||||||
|
Map<String, String> properties = new HashMap<>();
|
||||||
|
properties.put("cluster.name", TestConstant.clusterNmae);
|
||||||
|
outerAdapterConfig.setProperties(properties);
|
||||||
|
|
||||||
|
ESAdapter esAdapter = new ESAdapter();
|
||||||
|
esAdapter.init(outerAdapterConfig);
|
||||||
|
return esAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sqlExe(DataSource dataSource, String sql) {
|
||||||
|
Connection conn = null;
|
||||||
|
Statement stmt = null;
|
||||||
|
try {
|
||||||
|
conn = dataSource.getConnection();
|
||||||
|
conn.setAutoCommit(false);
|
||||||
|
stmt = conn.createStatement();
|
||||||
|
stmt.execute(sql);
|
||||||
|
conn.commit();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (conn != null) {
|
||||||
|
try {
|
||||||
|
conn.rollback();
|
||||||
|
} catch (SQLException e1) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
if (stmt != null) {
|
||||||
|
try {
|
||||||
|
stmt.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (conn != null) {
|
||||||
|
try {
|
||||||
|
conn.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.test.sync;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.get.GetResponse;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
public class LabelSyncJoinSub2Test {
|
||||||
|
|
||||||
|
private ESAdapter esAdapter;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
// AdapterConfigs.put("es", "mytest_user_join_sub2.yml");
|
||||||
|
esAdapter = Common.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带函数子查询从表插入
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test01() {
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
|
||||||
|
Common.sqlExe(ds, "delete from label where id=1 or id=2");
|
||||||
|
Common.sqlExe(ds, "insert into label (id,user_id,label) values (1,1,'a')");
|
||||||
|
Common.sqlExe(ds, "insert into label (id,user_id,label) values (2,1,'b')");
|
||||||
|
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("INSERT");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("label");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 2L);
|
||||||
|
data.put("user_id", 1L);
|
||||||
|
data.put("label", "b");
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("b;a_", response.getSource().get("_labels"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带函数子查询从表更新
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test02() {
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
|
||||||
|
Common.sqlExe(ds, "update label set label='aa' where id=1");
|
||||||
|
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("UPDATE");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("label");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("user_id", 1L);
|
||||||
|
data.put("label", "aa");
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
List<Map<String, Object>> oldList = new ArrayList<>();
|
||||||
|
Map<String, Object> old = new LinkedHashMap<>();
|
||||||
|
oldList.add(old);
|
||||||
|
old.put("label", "v");
|
||||||
|
dml.setOld(oldList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("b;aa_", response.getSource().get("_labels"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带函数子查询从表删除
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test03() {
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
|
||||||
|
Common.sqlExe(ds, "delete from label where id=1");
|
||||||
|
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("DELETE");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("label");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("user_id", 1L);
|
||||||
|
data.put("label", "a");
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("b_", response.getSource().get("_labels"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.test.sync;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.get.GetResponse;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
public class LabelSyncJoinSubTest {
|
||||||
|
|
||||||
|
private ESAdapter esAdapter;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
// AdapterConfigs.put("es", "mytest_user_join_sub.yml");
|
||||||
|
esAdapter = Common.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子查询从表插入
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test01() {
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
|
||||||
|
Common.sqlExe(ds, "delete from label where id=1 or id=2");
|
||||||
|
Common.sqlExe(ds, "insert into label (id,user_id,label) values (1,1,'a')");
|
||||||
|
Common.sqlExe(ds, "insert into label (id,user_id,label) values (2,1,'b')");
|
||||||
|
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("INSERT");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("label");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 2L);
|
||||||
|
data.put("user_id", 1L);
|
||||||
|
data.put("label", "b");
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("b;a", response.getSource().get("_labels"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子查询从表更新
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test02() {
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
|
||||||
|
Common.sqlExe(ds, "update label set label='aa' where id=1");
|
||||||
|
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("UPDATE");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("label");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("user_id", 1L);
|
||||||
|
data.put("label", "aa");
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
List<Map<String, Object>> oldList = new ArrayList<>();
|
||||||
|
Map<String, Object> old = new LinkedHashMap<>();
|
||||||
|
oldList.add(old);
|
||||||
|
old.put("label", "a");
|
||||||
|
dml.setOld(oldList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("b;aa", response.getSource().get("_labels"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子查询从表删除
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test03() {
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
|
||||||
|
Common.sqlExe(ds, "delete from label where id=1");
|
||||||
|
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("DELETE");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("label");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("user_id", 1L);
|
||||||
|
data.put("label", "a");
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("b", response.getSource().get("_labels"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.test.sync;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.get.GetResponse;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
public class RoleSyncJoinOne2Test {
|
||||||
|
|
||||||
|
private ESAdapter esAdapter;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
// AdapterConfigs.put("es", "mytest_user_join_one2.yml");
|
||||||
|
esAdapter = Common.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带函数非子查询从表插入
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test01() {
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
|
||||||
|
Common.sqlExe(ds, "delete from role where id=1");
|
||||||
|
Common.sqlExe(ds, "insert into role (id,role_name) values (1,'admin')");
|
||||||
|
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("INSERT");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("role");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("role_name", "admin");
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("admin_", response.getSource().get("_role_name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带函数非子查询从表更新
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test02() {
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
|
||||||
|
Common.sqlExe(ds, "update role set role_name='admin3' where id=1");
|
||||||
|
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("UPDATE");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("role");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("role_name", "admin3");
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
List<Map<String, Object>> oldList = new ArrayList<>();
|
||||||
|
Map<String, Object> old = new LinkedHashMap<>();
|
||||||
|
oldList.add(old);
|
||||||
|
old.put("role_name", "admin");
|
||||||
|
dml.setOld(oldList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("admin3_", response.getSource().get("_role_name"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.test.sync;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.get.GetResponse;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
public class RoleSyncJoinOneTest {
|
||||||
|
|
||||||
|
private ESAdapter esAdapter;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
// AdapterConfigs.put("es", "mytest_user_join_one.yml");
|
||||||
|
esAdapter = Common.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 非子查询从表插入
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test01() {
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
|
||||||
|
Common.sqlExe(ds, "delete from role where id=1");
|
||||||
|
Common.sqlExe(ds, "insert into role (id,role_name) values (1,'admin')");
|
||||||
|
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("INSERT");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("role");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("role_name", "admin");
|
||||||
|
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("admin", response.getSource().get("_role_name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 非子查询从表更新
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test02() {
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
|
||||||
|
Common.sqlExe(ds, "update role set role_name='admin2' where id=1");
|
||||||
|
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("UPDATE");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("role");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("role_name", "admin2");
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
List<Map<String, Object>> oldList = new ArrayList<>();
|
||||||
|
Map<String, Object> old = new LinkedHashMap<>();
|
||||||
|
oldList.add(old);
|
||||||
|
old.put("role_name", "admin");
|
||||||
|
dml.setOld(oldList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("admin2", response.getSource().get("_role_name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主表更新外键值
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test03() {
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
|
||||||
|
Common.sqlExe(ds, "delete from role where id=2");
|
||||||
|
Common.sqlExe(ds, "insert into role (id,role_name) values (2,'operator')");
|
||||||
|
Common.sqlExe(ds, "update user set role_id=2 where id=1");
|
||||||
|
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("UPDATE");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("user");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("role_id", 2L);
|
||||||
|
dml.setData(dataList);
|
||||||
|
List<Map<String, Object>> oldList = new ArrayList<>();
|
||||||
|
Map<String, Object> old = new LinkedHashMap<>();
|
||||||
|
oldList.add(old);
|
||||||
|
old.put("role_id", 1L);
|
||||||
|
dml.setOld(oldList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("operator", response.getSource().get("_role_name"));
|
||||||
|
|
||||||
|
Common.sqlExe(ds, "update user set role_id=1 where id=1");
|
||||||
|
|
||||||
|
Dml dml2 = new Dml();
|
||||||
|
dml2.setDestination("example");
|
||||||
|
dml2.setTs(new Date().getTime());
|
||||||
|
dml2.setType("UPDATE");
|
||||||
|
dml2.setDatabase("mytest");
|
||||||
|
dml2.setTable("user");
|
||||||
|
List<Map<String, Object>> dataList2 = new ArrayList<>();
|
||||||
|
Map<String, Object> data2 = new LinkedHashMap<>();
|
||||||
|
dataList2.add(data2);
|
||||||
|
data2.put("id", 1L);
|
||||||
|
data2.put("role_id", 1L);
|
||||||
|
dml2.setData(dataList2);
|
||||||
|
List<Map<String, Object>> oldList2 = new ArrayList<>();
|
||||||
|
Map<String, Object> old2 = new LinkedHashMap<>();
|
||||||
|
oldList2.add(old2);
|
||||||
|
old2.put("role_id", 2L);
|
||||||
|
dml2.setOld(oldList2);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml2);
|
||||||
|
|
||||||
|
GetResponse response2 = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("admin2", response2.getSource().get("_role_name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 非子查询从表删除
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test04() {
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
|
||||||
|
Common.sqlExe(ds, "delete from role where id=1");
|
||||||
|
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("DELETE");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("role");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("role_name", "admin");
|
||||||
|
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertNull(response.getSource().get("_role_name"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.test.sync;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.get.GetResponse;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
public class UserSyncJoinOneTest {
|
||||||
|
|
||||||
|
private ESAdapter esAdapter;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
// AdapterConfigs.put("es", "mytest_user_join_one.yml");
|
||||||
|
esAdapter = Common.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主表带函数插入
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test01() {
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
|
||||||
|
Common.sqlExe(ds, "delete from user where id=1");
|
||||||
|
Common.sqlExe(ds, "insert into user (id,name,role_id) values (1,'Eric',1)");
|
||||||
|
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("INSERT");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("user");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("name", "Eric");
|
||||||
|
data.put("role_id", 1L);
|
||||||
|
data.put("c_time", new Date());
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("Eric_", response.getSource().get("_name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主表带函数更新
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test02() {
|
||||||
|
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
|
||||||
|
Common.sqlExe(ds, "update user set name='Eric2' where id=1");
|
||||||
|
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("UPDATE");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("user");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("name", "Eric2");
|
||||||
|
dml.setData(dataList);
|
||||||
|
List<Map<String, Object>> oldList = new ArrayList<>();
|
||||||
|
Map<String, Object> old = new LinkedHashMap<>();
|
||||||
|
oldList.add(old);
|
||||||
|
old.put("name", "Eric");
|
||||||
|
dml.setOld(oldList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("Eric2_", response.getSource().get("_name"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.es.test.sync;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.get.GetResponse;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
public class UserSyncSingleTest {
|
||||||
|
|
||||||
|
private ESAdapter esAdapter;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
// AdapterConfigs.put("es", "mytest_user_single.yml");
|
||||||
|
esAdapter = Common.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单表插入
|
||||||
|
*/
|
||||||
|
@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", 1L);
|
||||||
|
data.put("name", "Eric");
|
||||||
|
data.put("role_id", 1L);
|
||||||
|
data.put("c_time", new Date());
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("Eric", response.getSource().get("_name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单表更新
|
||||||
|
*/
|
||||||
|
@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", 1L);
|
||||||
|
data.put("name", "Eric2");
|
||||||
|
dml.setData(dataList);
|
||||||
|
List<Map<String, Object>> oldList = new ArrayList<>();
|
||||||
|
Map<String, Object> old = new LinkedHashMap<>();
|
||||||
|
oldList.add(old);
|
||||||
|
old.put("name", "Eric");
|
||||||
|
dml.setOld(oldList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertEquals("Eric2", response.getSource().get("_name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单表删除
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test03() {
|
||||||
|
Dml dml = new Dml();
|
||||||
|
dml.setDestination("example");
|
||||||
|
dml.setTs(new Date().getTime());
|
||||||
|
dml.setType("DELETE");
|
||||||
|
dml.setDatabase("mytest");
|
||||||
|
dml.setTable("user");
|
||||||
|
List<Map<String, Object>> dataList = new ArrayList<>();
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
dataList.add(data);
|
||||||
|
data.put("id", 1L);
|
||||||
|
data.put("name", "Eric");
|
||||||
|
data.put("role_id", 1L);
|
||||||
|
data.put("c_time", new Date());
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
|
||||||
|
|
||||||
|
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
|
||||||
|
|
||||||
|
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
|
||||||
|
Assert.assertNull(response.getSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
// @After
|
||||||
|
// public void after() {
|
||||||
|
// esAdapter.destroy();
|
||||||
|
// DatasourceConfig.DATA_SOURCES.values().forEach(DruidDataSource::close);
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for label
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `label`;
|
||||||
|
CREATE TABLE `label` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` bigint(20) NOT NULL,
|
||||||
|
`label` varchar(30) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for role
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `role`;
|
||||||
|
CREATE TABLE `role` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||||
|
`role_name` varchar(30) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for user
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `user`;
|
||||||
|
CREATE TABLE `user` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(30) NOT NULL,
|
||||||
|
`c_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`role_id` bigint(20) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
|
||||||
|
|
||||||
|
insert into user (id,name,role_id) values (1,'Eric',1);
|
||||||
|
insert into role (id,role_name) values (1,'admin');
|
||||||
|
insert into role (id,role_name) values (2,'operator');
|
||||||
|
insert into label (id,user_id,label) values (1,1,'a');
|
||||||
|
insert into label (id,user_id,label) values (2,1,'b');
|
||||||
|
commit;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"_doc": {
|
||||||
|
"properties": {
|
||||||
|
"_name": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"_role_id": {
|
||||||
|
"type": "long"
|
||||||
|
},
|
||||||
|
"_role_name": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"_labels": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"_c_time": {
|
||||||
|
"type": "date"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
dataSourceKey: defaultDS
|
||||||
|
destination: example
|
||||||
|
esMapping:
|
||||||
|
_index: mytest_user
|
||||||
|
_type: _doc
|
||||||
|
_id: _id
|
||||||
|
sql: "select a.id as _id, a.name as _name, a.role_id as _role_id, a.c_time as _c_time from user a"
|
||||||
|
commitBatch: 3000
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration status="WARN">
|
||||||
|
<Appenders>
|
||||||
|
<Console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
|
||||||
|
</Console>
|
||||||
|
</Appenders>
|
||||||
|
<Loggers>
|
||||||
|
<Root level="ERROR">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Root>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<configuration scan="true" scanPeriod=" 5 seconds">
|
||||||
|
<jmxConfigurator />
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
|
||||||
|
</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="TRACE">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.1</version>
|
<version>1.1.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -62,6 +62,26 @@
|
|||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</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/hbase" overwrite="true">
|
||||||
|
<fileset dir="${project.basedir}/target/classes/hbase" erroronmissingdir="true">
|
||||||
|
<include name="*.yml" />
|
||||||
|
</fileset>
|
||||||
|
</copy>
|
||||||
|
</tasks>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.util.HashMap;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
@@ -12,8 +13,12 @@ import org.apache.commons.lang.StringUtils;
|
|||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.hbase.HBaseConfiguration;
|
import org.apache.hadoop.hbase.HBaseConfiguration;
|
||||||
import org.apache.hadoop.hbase.TableName;
|
import org.apache.hadoop.hbase.TableName;
|
||||||
import org.apache.hadoop.hbase.client.*;
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
|
import org.apache.hadoop.hbase.client.HTable;
|
||||||
import org.apache.hadoop.hbase.client.Result;
|
import org.apache.hadoop.hbase.client.Result;
|
||||||
|
import org.apache.hadoop.hbase.client.ResultScanner;
|
||||||
|
import org.apache.hadoop.hbase.client.Scan;
|
||||||
import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
|
import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -21,10 +26,15 @@ import org.slf4j.LoggerFactory;
|
|||||||
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
||||||
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
|
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
|
||||||
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfigLoader;
|
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfigLoader;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.hbase.monitor.HbaseConfigMonitor;
|
||||||
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseEtlService;
|
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseEtlService;
|
||||||
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService;
|
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService;
|
||||||
import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate;
|
import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.*;
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.SPI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HBase外部适配器
|
* HBase外部适配器
|
||||||
@@ -35,46 +45,68 @@ import com.alibaba.otter.canal.client.adapter.support.*;
|
|||||||
@SPI("hbase")
|
@SPI("hbase")
|
||||||
public class HbaseAdapter implements OuterAdapter {
|
public class HbaseAdapter implements OuterAdapter {
|
||||||
|
|
||||||
private static Logger logger = LoggerFactory.getLogger(HbaseAdapter.class);
|
private static Logger logger = LoggerFactory.getLogger(HbaseAdapter.class);
|
||||||
|
|
||||||
private static volatile Map<String, MappingConfig> hbaseMapping = null; // 文件名对应配置
|
private Map<String, MappingConfig> hbaseMapping = new ConcurrentHashMap<>(); // 文件名对应配置
|
||||||
private static volatile Map<String, MappingConfig> mappingConfigCache = null; // 库名-表名对应配置
|
private Map<String, Map<String, MappingConfig>> mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置
|
||||||
|
|
||||||
private Connection conn;
|
private Connection conn;
|
||||||
private HbaseSyncService hbaseSyncService;
|
private HbaseSyncService hbaseSyncService;
|
||||||
private HbaseTemplate hbaseTemplate;
|
private HbaseTemplate hbaseTemplate;
|
||||||
|
|
||||||
|
private HbaseConfigMonitor configMonitor;
|
||||||
|
|
||||||
|
public Map<String, MappingConfig> getHbaseMapping() {
|
||||||
|
return hbaseMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Map<String, MappingConfig>> getMappingConfigCache() {
|
||||||
|
return mappingConfigCache;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(OuterAdapterConfig configuration) {
|
public void init(OuterAdapterConfig configuration) {
|
||||||
try {
|
try {
|
||||||
if (mappingConfigCache == null) {
|
Map<String, MappingConfig> hbaseMappingTmp = MappingConfigLoader.load();
|
||||||
synchronized (MappingConfig.class) {
|
// 过滤不匹配的key的配置
|
||||||
if (mappingConfigCache == null) {
|
hbaseMappingTmp.forEach((key, mappingConfig) -> {
|
||||||
hbaseMapping = MappingConfigLoader.load();
|
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|
||||||
mappingConfigCache = new HashMap<>();
|
|| (mappingConfig.getOuterAdapterKey() != null
|
||||||
for (MappingConfig mappingConfig : hbaseMapping.values()) {
|
&& mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) {
|
||||||
mappingConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getHbaseMapping().getDestination())
|
hbaseMapping.put(key, mappingConfig);
|
||||||
+ "." + mappingConfig.getHbaseMapping().getDatabase() + "."
|
|
||||||
+ mappingConfig.getHbaseMapping().getTable(),
|
|
||||||
mappingConfig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
for (Map.Entry<String, MappingConfig> entry : hbaseMapping.entrySet()) {
|
||||||
|
String configName = entry.getKey();
|
||||||
|
MappingConfig mappingConfig = entry.getValue();
|
||||||
|
String k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
|
||||||
|
+ mappingConfig.getHbaseMapping().getDatabase() + "."
|
||||||
|
+ mappingConfig.getHbaseMapping().getTable();
|
||||||
|
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(k, k1 -> new HashMap<>());
|
||||||
|
configMap.put(configName, mappingConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> propertites = configuration.getProperties();
|
Map<String, String> properties = configuration.getProperties();
|
||||||
|
|
||||||
Configuration hbaseConfig = HBaseConfiguration.create();
|
Configuration hbaseConfig = HBaseConfiguration.create();
|
||||||
propertites.forEach(hbaseConfig::set);
|
properties.forEach(hbaseConfig::set);
|
||||||
conn = ConnectionFactory.createConnection(hbaseConfig);
|
conn = ConnectionFactory.createConnection(hbaseConfig);
|
||||||
hbaseTemplate = new HbaseTemplate(conn);
|
hbaseTemplate = new HbaseTemplate(conn);
|
||||||
hbaseSyncService = new HbaseSyncService(hbaseTemplate);
|
hbaseSyncService = new HbaseSyncService(hbaseTemplate);
|
||||||
|
|
||||||
|
configMonitor = new HbaseConfigMonitor();
|
||||||
|
configMonitor.init(this);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void sync(List<Dml> dmls) {
|
||||||
|
for (Dml dml : dmls) {
|
||||||
|
sync(dml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void sync(Dml dml) {
|
public void sync(Dml dml) {
|
||||||
if (dml == null) {
|
if (dml == null) {
|
||||||
return;
|
return;
|
||||||
@@ -82,8 +114,8 @@ public class HbaseAdapter implements OuterAdapter {
|
|||||||
String destination = StringUtils.trimToEmpty(dml.getDestination());
|
String destination = StringUtils.trimToEmpty(dml.getDestination());
|
||||||
String database = dml.getDatabase();
|
String database = dml.getDatabase();
|
||||||
String table = dml.getTable();
|
String table = dml.getTable();
|
||||||
MappingConfig config = mappingConfigCache.get(destination + "." + database + "." + table);
|
Map<String, MappingConfig> configMap = mappingConfigCache.get(destination + "." + database + "." + table);
|
||||||
hbaseSyncService.sync(config, dml);
|
configMap.values().forEach(config -> hbaseSyncService.sync(config, dml));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -100,33 +132,34 @@ public class HbaseAdapter implements OuterAdapter {
|
|||||||
return etlResult;
|
return etlResult;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DataSource dataSource = DatasourceConfig.DATA_SOURCES.get(task);
|
StringBuilder resultMsg = new StringBuilder();
|
||||||
if (dataSource != null) {
|
boolean resSucc = true;
|
||||||
StringBuilder resultMsg = new StringBuilder();
|
// ds不为空说明传入的是datasourceKey
|
||||||
boolean resSucc = true;
|
for (MappingConfig configTmp : hbaseMapping.values()) {
|
||||||
// ds不为空说明传入的是datasourceKey
|
// 取所有的destination为task的配置
|
||||||
for (MappingConfig configTmp : hbaseMapping.values()) {
|
if (configTmp.getDestination().equals(task)) {
|
||||||
// 取所有的datasourceKey为task的配置
|
DataSource dataSource = DatasourceConfig.DATA_SOURCES.get(configTmp.getDataSourceKey());
|
||||||
if (configTmp.getDataSourceKey().equals(task)) {
|
if (dataSource == null) {
|
||||||
EtlResult etlRes = HbaseEtlService.importData(dataSource, hbaseTemplate, configTmp, params);
|
continue;
|
||||||
if (!etlRes.getSucceeded()) {
|
|
||||||
resSucc = false;
|
|
||||||
resultMsg.append(etlRes.getErrorMessage()).append("\n");
|
|
||||||
} else {
|
|
||||||
resultMsg.append(etlRes.getResultMessage()).append("\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
EtlResult etlRes = HbaseEtlService.importData(dataSource, hbaseTemplate, configTmp, params);
|
||||||
if (resultMsg.length() > 0) {
|
if (!etlRes.getSucceeded()) {
|
||||||
etlResult.setSucceeded(resSucc);
|
resSucc = false;
|
||||||
if (resSucc) {
|
resultMsg.append(etlRes.getErrorMessage()).append("\n");
|
||||||
etlResult.setResultMessage(resultMsg.toString());
|
|
||||||
} else {
|
} else {
|
||||||
etlResult.setErrorMessage(resultMsg.toString());
|
resultMsg.append(etlRes.getResultMessage()).append("\n");
|
||||||
}
|
}
|
||||||
return etlResult;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (resultMsg.length() > 0) {
|
||||||
|
etlResult.setSucceeded(resSucc);
|
||||||
|
if (resSucc) {
|
||||||
|
etlResult.setResultMessage(resultMsg.toString());
|
||||||
|
} else {
|
||||||
|
etlResult.setErrorMessage(resultMsg.toString());
|
||||||
|
}
|
||||||
|
return etlResult;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
etlResult.setSucceeded(false);
|
etlResult.setSucceeded(false);
|
||||||
etlResult.setErrorMessage("Task not found");
|
etlResult.setErrorMessage("Task not found");
|
||||||
@@ -157,6 +190,9 @@ public class HbaseAdapter implements OuterAdapter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
|
if (configMonitor != null) {
|
||||||
|
configMonitor.destroy();
|
||||||
|
}
|
||||||
if (conn != null) {
|
if (conn != null) {
|
||||||
try {
|
try {
|
||||||
conn.close();
|
conn.close();
|
||||||
@@ -170,7 +206,7 @@ public class HbaseAdapter implements OuterAdapter {
|
|||||||
public String getDestination(String task) {
|
public String getDestination(String task) {
|
||||||
MappingConfig config = hbaseMapping.get(task);
|
MappingConfig config = hbaseMapping.get(task);
|
||||||
if (config != null && config.getHbaseMapping() != null) {
|
if (config != null && config.getHbaseMapping() != null) {
|
||||||
return config.getHbaseMapping().getDestination();
|
return config.getDestination();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,13 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
public class MappingConfig {
|
public class MappingConfig {
|
||||||
|
|
||||||
private String dataSourceKey; // 数据源key
|
private String dataSourceKey; // 数据源key
|
||||||
|
|
||||||
private HbaseMapping hbaseMapping; // hbase映射配置
|
private String outerAdapterKey; // adapter key
|
||||||
|
|
||||||
|
private String destination; // canal实例或MQ的topic
|
||||||
|
|
||||||
|
private HbaseMapping hbaseMapping; // hbase映射配置
|
||||||
|
|
||||||
public String getDataSourceKey() {
|
public String getDataSourceKey() {
|
||||||
return dataSourceKey;
|
return dataSourceKey;
|
||||||
@@ -22,6 +26,22 @@ public class MappingConfig {
|
|||||||
this.dataSourceKey = dataSourceKey;
|
this.dataSourceKey = dataSourceKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getOuterAdapterKey() {
|
||||||
|
return outerAdapterKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOuterAdapterKey(String outerAdapterKey) {
|
||||||
|
this.outerAdapterKey = outerAdapterKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDestination() {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDestination(String destination) {
|
||||||
|
this.destination = destination;
|
||||||
|
}
|
||||||
|
|
||||||
public HbaseMapping getHbaseMapping() {
|
public HbaseMapping getHbaseMapping() {
|
||||||
return hbaseMapping;
|
return hbaseMapping;
|
||||||
}
|
}
|
||||||
@@ -66,6 +86,7 @@ public class MappingConfig {
|
|||||||
public static class ColumnItem {
|
public static class ColumnItem {
|
||||||
|
|
||||||
private boolean isRowKey = false;
|
private boolean isRowKey = false;
|
||||||
|
private Integer rowKeyLen;
|
||||||
private String column;
|
private String column;
|
||||||
private String family;
|
private String family;
|
||||||
private String qualifier;
|
private String qualifier;
|
||||||
@@ -79,6 +100,14 @@ public class MappingConfig {
|
|||||||
isRowKey = rowKey;
|
isRowKey = rowKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getRowKeyLen() {
|
||||||
|
return rowKeyLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRowKeyLen(Integer rowKeyLen) {
|
||||||
|
this.rowKeyLen = rowKeyLen;
|
||||||
|
}
|
||||||
|
|
||||||
public String getColumn() {
|
public String getColumn() {
|
||||||
return column;
|
return column;
|
||||||
}
|
}
|
||||||
@@ -143,7 +172,6 @@ public class MappingConfig {
|
|||||||
public static class HbaseMapping {
|
public static class HbaseMapping {
|
||||||
|
|
||||||
private Mode mode = Mode.STRING; // hbase默认转换格式
|
private Mode mode = Mode.STRING; // hbase默认转换格式
|
||||||
private String destination; // canal实例或MQ的topic
|
|
||||||
private String database; // 数据库名或schema名
|
private String database; // 数据库名或schema名
|
||||||
private String table; // 表面名
|
private String table; // 表面名
|
||||||
private String hbaseTable; // hbase表名
|
private String hbaseTable; // hbase表名
|
||||||
@@ -169,14 +197,6 @@ public class MappingConfig {
|
|||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDestination() {
|
|
||||||
return destination;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDestination(String destination) {
|
|
||||||
this.destination = destination;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDatabase() {
|
public String getDatabase() {
|
||||||
return database;
|
return database;
|
||||||
}
|
}
|
||||||
@@ -263,7 +283,16 @@ public class MappingConfig {
|
|||||||
ColumnItem columnItem = new ColumnItem();
|
ColumnItem columnItem = new ColumnItem();
|
||||||
columnItem.setColumn(columnField.getKey());
|
columnItem.setColumn(columnField.getKey());
|
||||||
columnItem.setType(type);
|
columnItem.setType(type);
|
||||||
if ("rowKey".equalsIgnoreCase(field)) {
|
if (field != null && field.toUpperCase().startsWith("ROWKEY")) {
|
||||||
|
int idx = field.toUpperCase().indexOf("LEN:");
|
||||||
|
if (idx > -1) {
|
||||||
|
String len = field.substring(idx + 4);
|
||||||
|
try {
|
||||||
|
columnItem.setRowKeyLen(Integer.parseInt(len));
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
columnItem.setRowKey(true);
|
columnItem.setRowKey(true);
|
||||||
rowKeyColumn = columnItem;
|
rowKeyColumn = columnItem;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
package com.alibaba.otter.canal.client.adapter.hbase.config;
|
package com.alibaba.otter.canal.client.adapter.hbase.config;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.yaml.snakeyaml.Yaml;
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs;
|
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HBase表映射配置加载器
|
* HBase表映射配置加载器
|
||||||
@@ -23,9 +17,7 @@ import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs;
|
|||||||
*/
|
*/
|
||||||
public class MappingConfigLoader {
|
public class MappingConfigLoader {
|
||||||
|
|
||||||
private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class);
|
private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class);
|
||||||
|
|
||||||
private static final String BASE_PATH = "hbase";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载HBase表映射配置
|
* 加载HBase表映射配置
|
||||||
@@ -33,117 +25,22 @@ public class MappingConfigLoader {
|
|||||||
* @return 配置名/配置文件名--对象
|
* @return 配置名/配置文件名--对象
|
||||||
*/
|
*/
|
||||||
public static Map<String, MappingConfig> load() {
|
public static Map<String, MappingConfig> load() {
|
||||||
logger.info("## Start loading mapping config ... ");
|
logger.info("## Start loading hbase mapping config ... ");
|
||||||
|
|
||||||
Map<String, MappingConfig> result = new LinkedHashMap<>();
|
Map<String, MappingConfig> result = new LinkedHashMap<>();
|
||||||
|
|
||||||
Collection<String> configs = AdapterConfigs.get("hbase");
|
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("hbase");
|
||||||
for (String c : configs) {
|
configContentMap.forEach((fileName, content) -> {
|
||||||
if (c == null) {
|
MappingConfig config = new Yaml().loadAs(content, MappingConfig.class);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
c = c.trim();
|
|
||||||
if (c.equals("") || c.startsWith("#")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
MappingConfig config;
|
|
||||||
String configContent = null;
|
|
||||||
|
|
||||||
if (c.endsWith(".yml")) {
|
|
||||||
configContent = readConfigContent(BASE_PATH + "/" + c);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 简单配置database.table@datasourcekey?rowKey=key1,key2
|
|
||||||
if (StringUtils.isEmpty(configContent)) {
|
|
||||||
String[] mapping = c.split("\\?");
|
|
||||||
String params = mapping.length == 2 ? mapping[1] : null;
|
|
||||||
String rowKey = null;
|
|
||||||
String srcMeta = mapping[0];
|
|
||||||
//
|
|
||||||
if (params != null) {
|
|
||||||
for (String entry : params.split("&")) {
|
|
||||||
if ("rowKey".equals(entry.split("=")[0])) {
|
|
||||||
rowKey = entry.split("=")[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String dsKey = srcMeta.split("@").length == 2 ? srcMeta.split("@")[1] : null;
|
|
||||||
String[] dbTable;
|
|
||||||
if (dsKey == null) {
|
|
||||||
dbTable = srcMeta.split("\\.");
|
|
||||||
|
|
||||||
} else {
|
|
||||||
dbTable = srcMeta.split("@")[0].split("\\.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dbTable.length == 2) {
|
|
||||||
config = new MappingConfig();
|
|
||||||
|
|
||||||
MappingConfig.HbaseMapping hbaseMapping = new MappingConfig.HbaseMapping();
|
|
||||||
hbaseMapping.setHbaseTable(dbTable[0].toUpperCase() + "." + dbTable[1].toUpperCase());
|
|
||||||
hbaseMapping.setAutoCreateTable(true);
|
|
||||||
hbaseMapping.setDatabase(dbTable[0]);
|
|
||||||
hbaseMapping.setTable(dbTable[1]);
|
|
||||||
hbaseMapping.setMode(MappingConfig.Mode.PHOENIX);
|
|
||||||
hbaseMapping.setRowKey(rowKey);
|
|
||||||
// 有定义rowKey
|
|
||||||
if (rowKey != null) {
|
|
||||||
MappingConfig.ColumnItem columnItem = new MappingConfig.ColumnItem();
|
|
||||||
columnItem.setRowKey(true);
|
|
||||||
columnItem.setColumn(rowKey);
|
|
||||||
hbaseMapping.setRowKeyColumn(columnItem);
|
|
||||||
}
|
|
||||||
config.setHbaseMapping(hbaseMapping);
|
|
||||||
config.setDataSourceKey(dsKey);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException(String.format("配置项[%s]内容为空, 或格式不符合database.table", c));
|
|
||||||
}
|
|
||||||
|
|
||||||
} else { // 配置文件配置
|
|
||||||
config = new Yaml().loadAs(configContent, MappingConfig.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
config.validate();
|
config.validate();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("ERROR Config: " + c + " " + e.getMessage(), e);
|
throw new RuntimeException("ERROR load Config: " + fileName + " " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
result.put(c, config);
|
result.put(fileName, config);
|
||||||
}
|
});
|
||||||
|
|
||||||
logger.info("## Mapping config loaded");
|
logger.info("## Hbase mapping config loaded");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String readConfigContent(String config) {
|
|
||||||
InputStream in = null;
|
|
||||||
try {
|
|
||||||
// 先取本地文件,再取类路径
|
|
||||||
File configFile = new File("config/" + config);
|
|
||||||
if (configFile.exists()) {
|
|
||||||
in = new FileInputStream(configFile);
|
|
||||||
} else {
|
|
||||||
in = MappingConfigLoader.class.getClassLoader().getResourceAsStream(config);
|
|
||||||
}
|
|
||||||
if (in == null) {
|
|
||||||
throw new RuntimeException("Config file not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] bytes = new byte[in.available()];
|
|
||||||
in.read(bytes);
|
|
||||||
return new String(bytes, "UTF-8");
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Read ds-config.yml or hbase-mappings.conf error. ", e);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
if (in != null) {
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.monitor;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
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 org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class HbaseConfigMonitor {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(HbaseConfigMonitor.class);
|
||||||
|
|
||||||
|
private static final String adapterName = "hbase";
|
||||||
|
|
||||||
|
private HbaseAdapter hbaseAdapter;
|
||||||
|
|
||||||
|
private FileAlterationMonitor fileMonitor;
|
||||||
|
|
||||||
|
public void init(HbaseAdapter hbaseAdapter) {
|
||||||
|
this.hbaseAdapter = hbaseAdapter;
|
||||||
|
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 = new Yaml().loadAs(configContent, MappingConfig.class);
|
||||||
|
config.validate();
|
||||||
|
addConfigToCache(file, config);
|
||||||
|
|
||||||
|
logger.info("Add a new hbase 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 (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) {
|
||||||
|
// 加载配置文件
|
||||||
|
String configContent = MappingConfigsLoader
|
||||||
|
.loadConfig(adapterName + File.separator + file.getName());
|
||||||
|
MappingConfig config = new Yaml().loadAs(configContent, MappingConfig.class);
|
||||||
|
config.validate();
|
||||||
|
if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) {
|
||||||
|
deleteConfigFromCache(file);
|
||||||
|
}
|
||||||
|
addConfigToCache(file, config);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFileDelete(File file) {
|
||||||
|
super.onFileDelete(file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) {
|
||||||
|
deleteConfigFromCache(file);
|
||||||
|
|
||||||
|
logger.info("Delete a hbase mapping config: {} of canal adapter", file.getName());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addConfigToCache(File file, MappingConfig config) {
|
||||||
|
hbaseAdapter.getHbaseMapping().put(file.getName(), config);
|
||||||
|
Map<String, MappingConfig> configMap = hbaseAdapter.getMappingConfigCache()
|
||||||
|
.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "."
|
||||||
|
+ config.getHbaseMapping().getDatabase() + "." + config.getHbaseMapping().getTable(),
|
||||||
|
k1 -> new HashMap<>());
|
||||||
|
configMap.put(file.getName(), config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteConfigFromCache(File file) {
|
||||||
|
|
||||||
|
hbaseAdapter.getHbaseMapping().remove(file.getName());
|
||||||
|
for (Map<String, MappingConfig> configMap : hbaseAdapter.getMappingConfigCache().values()) {
|
||||||
|
if (configMap != null) {
|
||||||
|
configMap.remove(file.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import java.util.function.Function;
|
|||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -34,43 +35,6 @@ public class HbaseEtlService {
|
|||||||
|
|
||||||
private static Logger logger = LoggerFactory.getLogger(HbaseEtlService.class);
|
private static Logger logger = LoggerFactory.getLogger(HbaseEtlService.class);
|
||||||
|
|
||||||
public static Object sqlRS(DataSource ds, String sql, Function<ResultSet, Object> fun) throws SQLException {
|
|
||||||
Connection conn = null;
|
|
||||||
Statement stmt = null;
|
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
|
||||||
conn = ds.getConnection();
|
|
||||||
stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
|
|
||||||
stmt.setFetchSize(Integer.MIN_VALUE);
|
|
||||||
rs = stmt.executeQuery(sql);
|
|
||||||
return fun.apply(rs);
|
|
||||||
} finally {
|
|
||||||
if (rs != null) {
|
|
||||||
try {
|
|
||||||
rs.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (stmt != null) {
|
|
||||||
try {
|
|
||||||
stmt.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (conn != null) {
|
|
||||||
try {
|
|
||||||
conn.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rs = null;
|
|
||||||
stmt = null;
|
|
||||||
conn = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 建表
|
* 建表
|
||||||
@@ -138,7 +102,7 @@ public class HbaseEtlService {
|
|||||||
if (params != null && params.size() == 1 && hbaseMapping.getEtlCondition() == null) {
|
if (params != null && params.size() == 1 && hbaseMapping.getEtlCondition() == null) {
|
||||||
AtomicBoolean stExists = new AtomicBoolean(false);
|
AtomicBoolean stExists = new AtomicBoolean(false);
|
||||||
// 验证是否有SYS_TIME字段
|
// 验证是否有SYS_TIME字段
|
||||||
sqlRS(ds, sql, rs -> {
|
Util.sqlRS(ds, sql, rs -> {
|
||||||
try {
|
try {
|
||||||
ResultSetMetaData rsmd = rs.getMetaData();
|
ResultSetMetaData rsmd = rs.getMetaData();
|
||||||
int cnt = rsmd.getColumnCount();
|
int cnt = rsmd.getColumnCount();
|
||||||
@@ -169,7 +133,7 @@ public class HbaseEtlService {
|
|||||||
|
|
||||||
// 获取总数
|
// 获取总数
|
||||||
String countSql = "SELECT COUNT(1) FROM ( " + sql + ") _CNT ";
|
String countSql = "SELECT COUNT(1) FROM ( " + sql + ") _CNT ";
|
||||||
long cnt = (Long) sqlRS(ds, countSql, rs -> {
|
long cnt = (Long) Util.sqlRS(ds, countSql, rs -> {
|
||||||
Long count = null;
|
Long count = null;
|
||||||
try {
|
try {
|
||||||
if (rs.next()) {
|
if (rs.next()) {
|
||||||
@@ -199,8 +163,8 @@ public class HbaseEtlService {
|
|||||||
} else {
|
} else {
|
||||||
sqlFinal = sql + " LIMIT " + offset + "," + cnt;
|
sqlFinal = sql + " LIMIT " + offset + "," + cnt;
|
||||||
}
|
}
|
||||||
Future<Boolean> future = executor
|
Future<Boolean> future = executor.submit(
|
||||||
.submit(() -> executeSqlImport(ds, sqlFinal, hbaseMapping, hbaseTemplate, successCount, errMsg));
|
() -> executeSqlImport(ds, sqlFinal, hbaseMapping, hbaseTemplate, successCount, errMsg));
|
||||||
futures.add(future);
|
futures.add(future);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,8 +177,8 @@ public class HbaseEtlService {
|
|||||||
executeSqlImport(ds, sql, hbaseMapping, hbaseTemplate, successCount, errMsg);
|
executeSqlImport(ds, sql, hbaseMapping, hbaseTemplate, successCount, errMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(hbaseMapping.getHbaseTable() + " etl completed in: "
|
||||||
hbaseMapping.getHbaseTable() + " etl completed in: " + (System.currentTimeMillis() - start) / 1000 + "s!");
|
+ (System.currentTimeMillis() - start) / 1000 + "s!");
|
||||||
|
|
||||||
etlResult.setResultMessage("导入HBase表 " + hbaseMapping.getHbaseTable() + " 数据:" + successCount.get() + " 条");
|
etlResult.setResultMessage("导入HBase表 " + hbaseMapping.getHbaseTable() + " 数据:" + successCount.get() + " 条");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -244,7 +208,7 @@ public class HbaseEtlService {
|
|||||||
private static boolean executeSqlImport(DataSource ds, String sql, MappingConfig.HbaseMapping hbaseMapping,
|
private static boolean executeSqlImport(DataSource ds, String sql, MappingConfig.HbaseMapping hbaseMapping,
|
||||||
HbaseTemplate hbaseTemplate, AtomicLong successCount, List<String> errMsg) {
|
HbaseTemplate hbaseTemplate, AtomicLong successCount, List<String> errMsg) {
|
||||||
try {
|
try {
|
||||||
sqlRS(ds, sql, rs -> {
|
Util.sqlRS(ds, sql, rs -> {
|
||||||
int i = 1;
|
int i = 1;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -332,18 +296,49 @@ public class HbaseEtlService {
|
|||||||
|
|
||||||
byte[] valBytes = Bytes.toBytes(val.toString());
|
byte[] valBytes = Bytes.toBytes(val.toString());
|
||||||
if (columnItem.isRowKey()) {
|
if (columnItem.isRowKey()) {
|
||||||
row.setRowKey(valBytes);
|
if (columnItem.getRowKeyLen() != null) {
|
||||||
|
valBytes = Bytes.toBytes(limitLenNum(columnItem.getRowKeyLen(), val));
|
||||||
|
row.setRowKey(valBytes);
|
||||||
|
} else {
|
||||||
|
row.setRowKey(valBytes);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
row.addCell(columnItem.getFamily(), columnItem.getQualifier(), valBytes);
|
row.addCell(columnItem.getFamily(), columnItem.getQualifier(), valBytes);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
PhType phType = PhType.getType(columnItem.getType());
|
if (MappingConfig.Mode.STRING == hbaseMapping.getMode()) {
|
||||||
if (columnItem.isRowKey()) {
|
byte[] valBytes = Bytes.toBytes(val.toString());
|
||||||
row.setRowKey(PhTypeUtil.toBytes(val, phType));
|
if (columnItem.isRowKey()) {
|
||||||
} else {
|
if (columnItem.getRowKeyLen() != null) {
|
||||||
row.addCell(columnItem.getFamily(),
|
valBytes = Bytes.toBytes(limitLenNum(columnItem.getRowKeyLen(), val));
|
||||||
columnItem.getQualifier(),
|
}
|
||||||
PhTypeUtil.toBytes(val, phType));
|
row.setRowKey(valBytes);
|
||||||
|
} else {
|
||||||
|
row.addCell(columnItem.getFamily(), columnItem.getQualifier(), valBytes);
|
||||||
|
}
|
||||||
|
} else if (MappingConfig.Mode.NATIVE == hbaseMapping.getMode()) {
|
||||||
|
Type type = Type.getType(columnItem.getType());
|
||||||
|
if (columnItem.isRowKey()) {
|
||||||
|
if (columnItem.getRowKeyLen() != null) {
|
||||||
|
String v = limitLenNum(columnItem.getRowKeyLen(), val);
|
||||||
|
row.setRowKey(Bytes.toBytes(v));
|
||||||
|
} else {
|
||||||
|
row.setRowKey(TypeUtil.toBytes(val, type));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
row.addCell(columnItem.getFamily(),
|
||||||
|
columnItem.getQualifier(),
|
||||||
|
TypeUtil.toBytes(val, type));
|
||||||
|
}
|
||||||
|
} else if (MappingConfig.Mode.PHOENIX == hbaseMapping.getMode()) {
|
||||||
|
PhType phType = PhType.getType(columnItem.getType());
|
||||||
|
if (columnItem.isRowKey()) {
|
||||||
|
row.setRowKey(PhTypeUtil.toBytes(val, phType));
|
||||||
|
} else {
|
||||||
|
row.addCell(columnItem.getFamily(),
|
||||||
|
columnItem.getQualifier(),
|
||||||
|
PhTypeUtil.toBytes(val, phType));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,4 +377,16 @@ public class HbaseEtlService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String limitLenNum(int len, Object val) {
|
||||||
|
if (val == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (val instanceof Number) {
|
||||||
|
return String.format("%0" + len + "d", (Number) ((Number) val).longValue());
|
||||||
|
} else if (val instanceof String) {
|
||||||
|
return String.format("%0" + len + "d", Long.parseLong((String) val));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import org.apache.hadoop.hbase.util.Bytes;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||||
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
|
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
|
||||||
import com.alibaba.otter.canal.client.adapter.hbase.support.*;
|
import com.alibaba.otter.canal.client.adapter.hbase.support.*;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
@@ -38,8 +40,7 @@ public class HbaseSyncService {
|
|||||||
delete(config, dml);
|
delete(config, dml);
|
||||||
}
|
}
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
String res = dml.toString();
|
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
||||||
logger.debug(res);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -132,7 +133,21 @@ public class HbaseSyncService {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (columnItem.isRowKey()) {
|
if (columnItem.isRowKey()) {
|
||||||
// row.put("rowKey", bytes);
|
if (columnItem.getRowKeyLen() != null && entry.getValue() != null) {
|
||||||
|
if (entry.getValue() instanceof Number) {
|
||||||
|
String v = String.format("%0" + columnItem.getRowKeyLen() + "d",
|
||||||
|
((Number) entry.getValue()).longValue());
|
||||||
|
bytes = Bytes.toBytes(v);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
String v = String.format("%0" + columnItem.getRowKeyLen() + "d",
|
||||||
|
Integer.parseInt((String) entry.getValue()));
|
||||||
|
bytes = Bytes.toBytes(v);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
hRow.setRowKey(bytes);
|
hRow.setRowKey(bytes);
|
||||||
} else {
|
} else {
|
||||||
hRow.addCell(columnItem.getFamily(), columnItem.getQualifier(), bytes);
|
hRow.addCell(columnItem.getFamily(), columnItem.getQualifier(), bytes);
|
||||||
@@ -146,8 +161,8 @@ public class HbaseSyncService {
|
|||||||
/**
|
/**
|
||||||
* 更新操作
|
* 更新操作
|
||||||
*
|
*
|
||||||
* @param config
|
* @param config 配置对象
|
||||||
* @param dml
|
* @param dml dml对象
|
||||||
*/
|
*/
|
||||||
private void update(MappingConfig config, Dml dml) {
|
private void update(MappingConfig config, Dml dml) {
|
||||||
List<Map<String, Object>> data = dml.getData();
|
List<Map<String, Object>> data = dml.getData();
|
||||||
@@ -192,14 +207,15 @@ public class HbaseSyncService {
|
|||||||
Map<String, Object> rowKey = data.get(0);
|
Map<String, Object> rowKey = data.get(0);
|
||||||
rowKeyBytes = typeConvert(null, hbaseMapping, rowKey.values().iterator().next());
|
rowKeyBytes = typeConvert(null, hbaseMapping, rowKey.values().iterator().next());
|
||||||
} else {
|
} else {
|
||||||
rowKeyBytes = typeConvert(rowKeyColumn, hbaseMapping, r.get(rowKeyColumn.getColumn()));
|
rowKeyBytes = getRowKeyBytes(hbaseMapping, rowKeyColumn, r);
|
||||||
}
|
}
|
||||||
if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空");
|
if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空");
|
||||||
|
|
||||||
Map<String, MappingConfig.ColumnItem> columnItems = hbaseMapping.getColumnItems();
|
Map<String, MappingConfig.ColumnItem> columnItems = hbaseMapping.getColumnItems();
|
||||||
HRow hRow = new HRow(rowKeyBytes);
|
HRow hRow = new HRow(rowKeyBytes);
|
||||||
for (String updateColumn : old.get(index).keySet()) {
|
for (String updateColumn : old.get(index).keySet()) {
|
||||||
if (hbaseMapping.getExcludeColumns() != null && hbaseMapping.getExcludeColumns().contains(updateColumn)) {
|
if (hbaseMapping.getExcludeColumns() != null
|
||||||
|
&& hbaseMapping.getExcludeColumns().contains(updateColumn)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
MappingConfig.ColumnItem columnItem = columnItems.get(updateColumn);
|
MappingConfig.ColumnItem columnItem = columnItems.get(updateColumn);
|
||||||
@@ -276,8 +292,7 @@ public class HbaseSyncService {
|
|||||||
Map<String, Object> rowKey = data.get(0);
|
Map<String, Object> rowKey = data.get(0);
|
||||||
rowKeyBytes = typeConvert(null, hbaseMapping, rowKey.values().iterator().next());
|
rowKeyBytes = typeConvert(null, hbaseMapping, rowKey.values().iterator().next());
|
||||||
} else {
|
} else {
|
||||||
Object val = r.get(rowKeyColumn.getColumn());
|
rowKeyBytes = getRowKeyBytes(hbaseMapping, rowKeyColumn, r);
|
||||||
rowKeyBytes = typeConvert(rowKeyColumn, hbaseMapping, val);
|
|
||||||
}
|
}
|
||||||
if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空");
|
if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空");
|
||||||
rowKeys.add(rowKeyBytes);
|
rowKeys.add(rowKeyBytes);
|
||||||
@@ -423,4 +438,22 @@ public class HbaseSyncService {
|
|||||||
return rowKeyValue.toString();
|
return rowKeyValue.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static byte[] getRowKeyBytes(MappingConfig.HbaseMapping hbaseMapping, MappingConfig.ColumnItem rowKeyColumn,
|
||||||
|
Map<String, Object> rowData) {
|
||||||
|
Object val = rowData.get(rowKeyColumn.getColumn());
|
||||||
|
String v = null;
|
||||||
|
if (rowKeyColumn.getRowKeyLen() != null) {
|
||||||
|
if (val instanceof Number) {
|
||||||
|
v = String.format("%0" + rowKeyColumn.getRowKeyLen() + "d", (Number) ((Number) val).longValue());
|
||||||
|
} else if (val instanceof String) {
|
||||||
|
v = String.format("%0" + rowKeyColumn.getRowKeyLen() + "d", Long.parseLong((String) val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (v != null) {
|
||||||
|
return Bytes.toBytes(v);
|
||||||
|
} else {
|
||||||
|
return typeConvert(rowKeyColumn, hbaseMapping, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
dataSourceKey: defaultDS
|
dataSourceKey: defaultDS
|
||||||
|
destination: example
|
||||||
hbaseMapping:
|
hbaseMapping:
|
||||||
mode: PHOENIX #NATIVE #STRING
|
mode: STRING #NATIVE #PHOENIX
|
||||||
destination: example
|
|
||||||
database: mytest # 数据库名
|
database: mytest # 数据库名
|
||||||
table: person2 # 数据库表名
|
table: person2 # 数据库表名
|
||||||
hbaseTable: MYTEST.PERSON2 # HBase表名
|
hbaseTable: MYTEST.PERSON2 # HBase表名
|
||||||
@@ -11,14 +11,14 @@ hbaseMapping:
|
|||||||
#rowKey: id,type # 复合字段rowKey不能和columns中的rowKey重复
|
#rowKey: id,type # 复合字段rowKey不能和columns中的rowKey重复
|
||||||
columns:
|
columns:
|
||||||
# 数据库字段:HBase对应字段
|
# 数据库字段:HBase对应字段
|
||||||
id: ROWKEY$UNSIGNED_LONG
|
id: ROWKEY LEN:15
|
||||||
name: NAME
|
name: NAME
|
||||||
email: EMAIL
|
email: EMAIL
|
||||||
type: $DECIMAL
|
type:
|
||||||
c_time: C_TIME$UNSIGNED_TIMESTAMP
|
c_time: C_TIME
|
||||||
birthday: BIRTHDAY$DATE
|
birthday: BIRTHDAY
|
||||||
excludeColumns:
|
# excludeColumns:
|
||||||
- lat # 忽略字段
|
# - lat # 忽略字段
|
||||||
|
|
||||||
# -- NATIVE类型
|
# -- NATIVE类型
|
||||||
# $DEFAULT
|
# $DEFAULT
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.1</version>
|
<version>1.1.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal.client</artifactId>
|
<artifactId>canal.client</artifactId>
|
||||||
<version>1.1.1</version>
|
<version>1.1.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.yaml</groupId>
|
<groupId>org.yaml</groupId>
|
||||||
@@ -46,7 +46,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
<optional>true</optional>
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-context</artifactId>
|
||||||
|
<version>2.0.0.RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.curator</groupId>
|
<groupId>org.apache.curator</groupId>
|
||||||
@@ -66,6 +71,23 @@
|
|||||||
<version>1.1.1</version>
|
<version>1.1.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- jdbc -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>42.1.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.oracle</groupId>
|
||||||
|
<artifactId>ojdbc6</artifactId>
|
||||||
|
<version>11.2.0.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.microsoft.sqlserver</groupId>
|
||||||
|
<artifactId>mssql-jdbc</artifactId>
|
||||||
|
<version>7.0.0.jre8</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- outer adapter jar with dependencies-->
|
<!-- outer adapter jar with dependencies-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -78,7 +100,7 @@
|
|||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
<classifier>jar-with-dependencies</classifier>
|
<classifier>jar-with-dependencies</classifier>
|
||||||
<optional>true</optional>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -91,62 +113,38 @@
|
|||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
<classifier>jar-with-dependencies</classifier>
|
<classifier>jar-with-dependencies</classifier>
|
||||||
<optional>true</optional>
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.elasticsearch</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>*</artifactId>
|
||||||
|
<groupId>*</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
<classifier>jar-with-dependencies</classifier>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.rdb</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>*</artifactId>
|
||||||
|
<groupId>*</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
<classifier>jar-with-dependencies</classifier>
|
||||||
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<finalName>canal-adapter-launcher</finalName>
|
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
|
||||||
<version>2.0.1.RELEASE</version>
|
|
||||||
<configuration>
|
|
||||||
<excludes>
|
|
||||||
<exclude>
|
|
||||||
<groupId>com.alibaba.otter</groupId>
|
|
||||||
<artifactId>client-adapter.hbase</artifactId>
|
|
||||||
</exclude>
|
|
||||||
<exclude>
|
|
||||||
<groupId>com.alibaba.otter</groupId>
|
|
||||||
<artifactId>client-adapter.logger</artifactId>
|
|
||||||
</exclude>
|
|
||||||
</excludes>
|
|
||||||
</configuration>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<goals>
|
|
||||||
<goal>repackage</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}/target/config" overwrite="true">
|
|
||||||
<fileset dir="${project.basedir}/src/main/resources" erroronmissingdir="true">
|
|
||||||
<include name="*.yml" />
|
|
||||||
</fileset>
|
|
||||||
</copy>
|
|
||||||
<copy todir="${project.basedir}/target/config/hbase" overwrite="true">
|
|
||||||
<fileset dir="${project.basedir}/../hbase/src/main/resources/hbase" erroronmissingdir="true">
|
|
||||||
<include name="*.yml" />
|
|
||||||
</fileset>
|
|
||||||
</copy>
|
|
||||||
</tasks>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-dependency-plugin</artifactId>
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
@@ -160,15 +158,81 @@
|
|||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<includeClassifiers>jar-with-dependencies</includeClassifiers>
|
<includeClassifiers>jar-with-dependencies</includeClassifiers>
|
||||||
<outputDirectory>${project.basedir}/target/lib</outputDirectory>
|
<outputDirectory>${project.basedir}/target/canal-adapter/plugin</outputDirectory>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<version>2.2.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>assemble</id>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
<phase>package</phase>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<appendAssemblyId>false</appendAssemblyId>
|
||||||
|
<attach>false</attach>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.0.2</version>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<addClasspath>true</addClasspath>
|
||||||
|
<classpathPrefix>lib/</classpathPrefix>
|
||||||
|
<mainClass>com.alibaba.otter.canal.adapter.launcher.CanalAdapterApplication</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/*.properties</exclude>
|
||||||
|
<exclude>**/*.xml</exclude>
|
||||||
|
<exclude>**/*.yml</exclude>
|
||||||
|
<exclude>static/**</exclude>
|
||||||
|
<exclude>templates/**</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>dev</id>
|
||||||
|
<activation>
|
||||||
|
<activeByDefault>true</activeByDefault>
|
||||||
|
<property>
|
||||||
|
<name>env</name>
|
||||||
|
<value>!release</value>
|
||||||
|
</property>
|
||||||
|
</activation>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<descriptors>
|
||||||
|
<descriptor>${basedir}/src/main/assembly/dev.xml</descriptor>
|
||||||
|
</descriptors>
|
||||||
|
<finalName>canal-adapter</finalName>
|
||||||
|
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</profile>
|
||||||
|
|
||||||
<profile>
|
<profile>
|
||||||
<id>release</id>
|
<id>release</id>
|
||||||
<activation>
|
<activation>
|
||||||
@@ -183,22 +247,12 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<appendAssemblyId>false</appendAssemblyId>
|
|
||||||
<descriptors>
|
<descriptors>
|
||||||
<descriptor>${basedir}/src/main/assembly/release.xml</descriptor>
|
<descriptor>${basedir}/src/main/assembly/release.xml</descriptor>
|
||||||
</descriptors>
|
</descriptors>
|
||||||
<finalName>${project.artifactId}-${project.version}</finalName>
|
<finalName>canal.adapter-${project.version}</finalName>
|
||||||
<outputDirectory>${project.basedir}/../../target</outputDirectory>
|
<outputDirectory>${project.basedir}/../../target</outputDirectory>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>make-assembly</id>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>single</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|||||||
69
client-adapter/launcher/src/main/assembly/dev.xml
Normal file
69
client-adapter/launcher/src/main/assembly/dev.xml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
|
||||||
|
<id>dist</id>
|
||||||
|
<formats>
|
||||||
|
<format>dir</format>
|
||||||
|
</formats>
|
||||||
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
|
<fileSets>
|
||||||
|
<fileSet>
|
||||||
|
<directory>.</directory>
|
||||||
|
<outputDirectory>/</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>README*</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>./src/main/bin</directory>
|
||||||
|
<outputDirectory>bin</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
<fileMode>0755</fileMode>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>./src/main/resources</directory>
|
||||||
|
<outputDirectory>/conf</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>../elasticsearch/src/main/resources/es</directory>
|
||||||
|
<outputDirectory>/conf/es</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>../hbase/src/main/resources/hbase</directory>
|
||||||
|
<outputDirectory>/conf/hbase</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>../rdb/src/main/resources/</directory>
|
||||||
|
<outputDirectory>/conf</outputDirectory>
|
||||||
|
<excludes>
|
||||||
|
<exclude>META-INF/**</exclude>
|
||||||
|
</excludes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>target</directory>
|
||||||
|
<outputDirectory>logs</outputDirectory>
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/*</exclude>
|
||||||
|
</excludes>
|
||||||
|
</fileSet>
|
||||||
|
</fileSets>
|
||||||
|
<dependencySets>
|
||||||
|
<dependencySet>
|
||||||
|
<outputDirectory>lib</outputDirectory>
|
||||||
|
<excludes>
|
||||||
|
<exclude>junit:junit</exclude>
|
||||||
|
</excludes>
|
||||||
|
</dependencySet>
|
||||||
|
</dependencySets>
|
||||||
|
</assembly>
|
||||||
@@ -1,34 +1,74 @@
|
|||||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
|
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
|
||||||
<id>canal-adapter</id>
|
<id>dist</id>
|
||||||
<formats>
|
<formats>
|
||||||
<format>tar.gz</format>
|
<format>tar.gz</format>
|
||||||
</formats>
|
</formats>
|
||||||
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
<fileSets>
|
<fileSets>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>${project.build.directory}</directory>
|
<directory>.</directory>
|
||||||
<outputDirectory>/</outputDirectory>
|
<outputDirectory>/</outputDirectory>
|
||||||
<includes>
|
<includes>
|
||||||
<include>*.jar</include>
|
<include>README*</include>
|
||||||
</includes>
|
</includes>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
|
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>${project.build.directory}/lib</directory>
|
<directory>./src/main/bin</directory>
|
||||||
<outputDirectory>/lib/</outputDirectory>
|
<outputDirectory>bin</outputDirectory>
|
||||||
<includes>
|
<includes>
|
||||||
<include>*.jar</include>
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
<fileMode>0755</fileMode>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>./src/main/resources</directory>
|
||||||
|
<outputDirectory>/conf</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
|
||||||
</includes>
|
</includes>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
|
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>${project.build.directory}/config</directory>
|
<directory>../elasticsearch/src/main/resources/es</directory>
|
||||||
<outputDirectory>/config/</outputDirectory>
|
<outputDirectory>/conf/es</outputDirectory>
|
||||||
<includes>
|
<includes>
|
||||||
<include>**</include>
|
<include>**/*</include>
|
||||||
</includes>
|
</includes>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>../hbase/src/main/resources/hbase</directory>
|
||||||
|
<outputDirectory>/conf/hbase</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>../rdb/src/main/resources/</directory>
|
||||||
|
<outputDirectory>/conf</outputDirectory>
|
||||||
|
<excludes>
|
||||||
|
<exclude>META-INF/**</exclude>
|
||||||
|
</excludes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>target</directory>
|
||||||
|
<outputDirectory>logs</outputDirectory>
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/*</exclude>
|
||||||
|
</excludes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>${project.basedir}/target/canal-adapter/plugin</directory>
|
||||||
|
<outputDirectory>/plugin/</outputDirectory>
|
||||||
|
</fileSet>
|
||||||
</fileSets>
|
</fileSets>
|
||||||
<baseDirectory>/</baseDirectory>
|
<dependencySets>
|
||||||
|
<dependencySet>
|
||||||
|
<outputDirectory>lib</outputDirectory>
|
||||||
|
<excludes>
|
||||||
|
<exclude>junit:junit</exclude>
|
||||||
|
</excludes>
|
||||||
|
</dependencySet>
|
||||||
|
</dependencySets>
|
||||||
</assembly>
|
</assembly>
|
||||||
5
client-adapter/launcher/src/main/bin/restart.sh
Normal file
5
client-adapter/launcher/src/main/bin/restart.sh
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
sh stop.sh
|
||||||
|
|
||||||
|
sh startup.sh
|
||||||
22
client-adapter/launcher/src/main/bin/startup.bat
Executable file
22
client-adapter/launcher/src/main/bin/startup.bat
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
@echo off
|
||||||
|
@if not "%ECHO%" == "" echo %ECHO%
|
||||||
|
@if "%OS%" == "Windows_NT" setlocal
|
||||||
|
|
||||||
|
set ENV_PATH=.\
|
||||||
|
if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0%
|
||||||
|
|
||||||
|
set conf_dir=%ENV_PATH%\..\conf
|
||||||
|
|
||||||
|
set CLASSPATH=%conf_dir%
|
||||||
|
set CLASSPATH=%conf_dir%\..\lib\*;%CLASSPATH%
|
||||||
|
|
||||||
|
set JAVA_MEM_OPTS= -Xms128m -Xmx512m -XX:PermSize=128m
|
||||||
|
set JAVA_OPTS_EXT= -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dapplication.codeset=UTF-8 -Dfile.encoding=UTF-8
|
||||||
|
set ADAPTER_OPTS= -DappName=canal-adapter
|
||||||
|
|
||||||
|
set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %ADAPTER_OPTS%
|
||||||
|
|
||||||
|
set CMD_STR= java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.adapter.launcher.CanalAdapterApplication
|
||||||
|
echo start cmd : %CMD_STR%
|
||||||
|
|
||||||
|
java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.adapter.launcher.CanalAdapterApplication
|
||||||
67
client-adapter/launcher/src/main/bin/startup.sh
Normal file
67
client-adapter/launcher/src/main/bin/startup.sh
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
current_path=`pwd`
|
||||||
|
case "`uname`" in
|
||||||
|
Linux)
|
||||||
|
bin_abs_path=$(readlink -f $(dirname $0))
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
bin_abs_path=`cd $(dirname $0); pwd`
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
base=${bin_abs_path}/..
|
||||||
|
export LANG=en_US.UTF-8
|
||||||
|
export BASE=$base
|
||||||
|
|
||||||
|
if [ -f $base/bin/adapter.pid ] ; then
|
||||||
|
echo "found adapter.pid , Please run stop.sh first ,then startup.sh" 2>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d $base/logs ] ; then
|
||||||
|
mkdir -p $base/logs
|
||||||
|
fi
|
||||||
|
|
||||||
|
## set java path
|
||||||
|
if [ -z "$JAVA" ] ; then
|
||||||
|
JAVA=$(which java)
|
||||||
|
fi
|
||||||
|
|
||||||
|
ALIBABA_JAVA="/usr/alibaba/java/bin/java"
|
||||||
|
TAOBAO_JAVA="/opt/taobao/java/bin/java"
|
||||||
|
if [ -z "$JAVA" ]; then
|
||||||
|
if [ -f $ALIBABA_JAVA ] ; then
|
||||||
|
JAVA=$ALIBABA_JAVA
|
||||||
|
elif [ -f $TAOBAO_JAVA ] ; then
|
||||||
|
JAVA=$TAOBAO_JAVA
|
||||||
|
else
|
||||||
|
echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH." 2>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
str=`file -L $JAVA | grep 64-bit`
|
||||||
|
if [ -n "$str" ]; then
|
||||||
|
JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -Xss256k -XX:-UseAdaptiveSizePolicy -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError"
|
||||||
|
else
|
||||||
|
JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m "
|
||||||
|
fi
|
||||||
|
|
||||||
|
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"
|
||||||
|
ADAPTER_OPTS="-DappName=canal-adapter"
|
||||||
|
|
||||||
|
for i in $base/lib/*;
|
||||||
|
do CLASSPATH=$i:"$CLASSPATH";
|
||||||
|
done
|
||||||
|
|
||||||
|
CLASSPATH="$base/conf:$CLASSPATH";
|
||||||
|
|
||||||
|
echo "cd to $bin_abs_path for workaround relative path"
|
||||||
|
cd $bin_abs_path
|
||||||
|
|
||||||
|
echo CLASSPATH :$CLASSPATH
|
||||||
|
$JAVA $JAVA_OPTS $ADAPTER_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.adapter.launcher.CanalAdapterApplication 1>>$base/logs/adapter.log 2>&1 &
|
||||||
|
echo $! > $base/bin/adapter.pid
|
||||||
|
|
||||||
|
echo "cd to $current_path for continue"
|
||||||
|
cd $current_path
|
||||||
65
client-adapter/launcher/src/main/bin/stop.sh
Normal file
65
client-adapter/launcher/src/main/bin/stop.sh
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cygwin=false;
|
||||||
|
linux=false;
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN*)
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Linux*)
|
||||||
|
linux=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
get_pid() {
|
||||||
|
STR=$1
|
||||||
|
PID=$2
|
||||||
|
if $cygwin; then
|
||||||
|
JAVA_CMD="$JAVA_HOME\bin\java"
|
||||||
|
JAVA_CMD=`cygpath --path --unix $JAVA_CMD`
|
||||||
|
JAVA_PID=`ps |grep $JAVA_CMD |awk '{print $1}'`
|
||||||
|
else
|
||||||
|
if $linux; then
|
||||||
|
if [ ! -z "$PID" ]; then
|
||||||
|
JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'`
|
||||||
|
else
|
||||||
|
JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep -v grep|awk '{print $2}'`
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ ! -z "$PID" ]; then
|
||||||
|
JAVA_PID=`ps aux |grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'`
|
||||||
|
else
|
||||||
|
JAVA_PID=`ps aux |grep "$STR"|grep -v grep|awk '{print $2}'`
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo $JAVA_PID;
|
||||||
|
}
|
||||||
|
|
||||||
|
base=`dirname $0`/..
|
||||||
|
pidfile=$base/bin/adapter.pid
|
||||||
|
if [ ! -f "$pidfile" ];then
|
||||||
|
echo "canal-adapter is not running. exists"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
pid=`cat $pidfile`
|
||||||
|
if [ "$pid" == "" ] ; then
|
||||||
|
pid=`get_pid "appName=canal-adapter"`
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "`hostname`: stopping canal $pid ... "
|
||||||
|
kill $pid
|
||||||
|
|
||||||
|
LOOPS=0
|
||||||
|
while (true);
|
||||||
|
do
|
||||||
|
gpid=`get_pid "appName=canal-adapter" "$pid"`
|
||||||
|
if [ "$gpid" == "" ] ; then
|
||||||
|
echo "Oook! cost:$LOOPS"
|
||||||
|
`rm $pidfile`
|
||||||
|
break;
|
||||||
|
fi
|
||||||
|
let LOOPS=LOOPS+1
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package com.alibaba.otter.canal.adapter.launcher;
|
package com.alibaba.otter.canal.adapter.launcher;
|
||||||
|
|
||||||
|
import org.springframework.boot.Banner;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动入口
|
* 启动入口
|
||||||
@@ -13,6 +14,8 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
|
|||||||
public class CanalAdapterApplication {
|
public class CanalAdapterApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
new SpringApplicationBuilder(CanalAdapterApplication.class).run(args);
|
SpringApplication application = new SpringApplication(CanalAdapterApplication.class);
|
||||||
|
application.setBannerMode(Banner.Mode.OFF);
|
||||||
|
application.run(args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,11 +64,12 @@ public class SyncSwitch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("resource")
|
||||||
private synchronized void startListen(String destination, BooleanMutex mutex) {
|
private synchronized void startListen(String destination, BooleanMutex mutex) {
|
||||||
try {
|
try {
|
||||||
String path = SYN_SWITCH_ZK_NODE + destination;
|
String path = SYN_SWITCH_ZK_NODE + destination;
|
||||||
CuratorFramework curator = curatorClient.getCurator();
|
CuratorFramework curator = curatorClient.getCurator();
|
||||||
final NodeCache nodeCache = new NodeCache(curator, path);
|
NodeCache nodeCache = new NodeCache(curator, path);
|
||||||
nodeCache.start();
|
nodeCache.start();
|
||||||
nodeCache.getListenable().addListener(() -> initMutex(curator, destination, mutex));
|
nodeCache.getListenable().addListener(() -> initMutex(curator, destination, mutex));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
package com.alibaba.otter.canal.adapter.launcher.config;
|
package com.alibaba.otter.canal.adapter.launcher.config;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
|
import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* canal 的相关配置类
|
* canal 的相关配置类
|
||||||
@@ -19,32 +23,56 @@ import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
|
|||||||
@ConfigurationProperties(prefix = "canal.conf")
|
@ConfigurationProperties(prefix = "canal.conf")
|
||||||
public class AdapterCanalConfig extends CanalClientConfig {
|
public class AdapterCanalConfig extends CanalClientConfig {
|
||||||
|
|
||||||
public final Set<String> DESTINATIONS = new LinkedHashSet<>();
|
public final Set<String> DESTINATIONS = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
private Map<String, DatasourceConfig> srcDataSources;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCanalInstances(List<CanalInstance> canalInstances) {
|
public void setCanalAdapters(List<CanalAdapter> canalAdapters) {
|
||||||
super.setCanalInstances(canalInstances);
|
super.setCanalAdapters(canalAdapters);
|
||||||
|
|
||||||
if (canalInstances != null) {
|
if (canalAdapters != null) {
|
||||||
synchronized (DESTINATIONS) {
|
synchronized (DESTINATIONS) {
|
||||||
DESTINATIONS.clear();
|
DESTINATIONS.clear();
|
||||||
for (CanalInstance canalInstance : canalInstances) {
|
for (CanalAdapter canalAdapter : canalAdapters) {
|
||||||
DESTINATIONS.add(canalInstance.getInstance());
|
if (canalAdapter.getInstance() != null) {
|
||||||
|
DESTINATIONS.add(canalAdapter.getInstance());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public Map<String, DatasourceConfig> getSrcDataSources() {
|
||||||
public void setMqTopics(List<MQTopic> mqTopics) {
|
return srcDataSources;
|
||||||
super.setMqTopics(mqTopics);
|
}
|
||||||
|
|
||||||
if (mqTopics != null) {
|
@SuppressWarnings("resource")
|
||||||
synchronized (DESTINATIONS) {
|
public void setSrcDataSources(Map<String, DatasourceConfig> srcDataSources) {
|
||||||
DESTINATIONS.clear();
|
this.srcDataSources = srcDataSources;
|
||||||
for (MQTopic mqTopic : mqTopics) {
|
|
||||||
DESTINATIONS.add(mqTopic.getTopic());
|
if (srcDataSources != null) {
|
||||||
|
for (Map.Entry<String, DatasourceConfig> entry : srcDataSources.entrySet()) {
|
||||||
|
DatasourceConfig datasourceConfig = entry.getValue();
|
||||||
|
// 加载数据源连接池
|
||||||
|
DruidDataSource ds = new DruidDataSource();
|
||||||
|
ds.setDriverClassName(datasourceConfig.getDriver());
|
||||||
|
ds.setUrl(datasourceConfig.getUrl());
|
||||||
|
ds.setUsername(datasourceConfig.getUsername());
|
||||||
|
ds.setPassword(datasourceConfig.getPassword());
|
||||||
|
ds.setInitialSize(1);
|
||||||
|
ds.setMinIdle(1);
|
||||||
|
ds.setMaxActive(datasourceConfig.getMaxActive());
|
||||||
|
ds.setMaxWait(60000);
|
||||||
|
ds.setTimeBetweenEvictionRunsMillis(60000);
|
||||||
|
ds.setMinEvictableIdleTimeMillis(300000);
|
||||||
|
ds.setValidationQuery("select 1");
|
||||||
|
try {
|
||||||
|
ds.init();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
DatasourceConfig.DATA_SOURCES.put(entry.getKey(), ds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
package com.alibaba.otter.canal.adapter.launcher.config;
|
|
||||||
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import com.alibaba.druid.pool.DruidDataSource;
|
|
||||||
import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs;
|
|
||||||
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 适配器数据源及配置文件列表配置类
|
|
||||||
*
|
|
||||||
* @author rewerma @ 2018-10-20
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@ConfigurationProperties(prefix = "adapter.conf")
|
|
||||||
public class AdapterConfig {
|
|
||||||
|
|
||||||
private static Logger logger = LoggerFactory.getLogger(AdapterConfig.class);
|
|
||||||
|
|
||||||
private Map<String, DatasourceConfig> datasourceConfigs;
|
|
||||||
|
|
||||||
private List<String> adapterConfigs;
|
|
||||||
|
|
||||||
public List<String> getAdapterConfigs() {
|
|
||||||
return adapterConfigs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, DatasourceConfig> getDatasourceConfigs() {
|
|
||||||
return datasourceConfigs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDatasourceConfigs(Map<String, DatasourceConfig> datasourceConfigs) {
|
|
||||||
this.datasourceConfigs = datasourceConfigs;
|
|
||||||
|
|
||||||
if (datasourceConfigs != null) {
|
|
||||||
for (Map.Entry<String, DatasourceConfig> entry : datasourceConfigs.entrySet()) {
|
|
||||||
DatasourceConfig datasourceConfig = entry.getValue();
|
|
||||||
// 加载数据源连接池
|
|
||||||
DruidDataSource ds = new DruidDataSource();
|
|
||||||
ds.setDriverClassName(datasourceConfig.getDriver());
|
|
||||||
ds.setUrl(datasourceConfig.getUrl());
|
|
||||||
ds.setUsername(datasourceConfig.getUsername());
|
|
||||||
ds.setPassword(datasourceConfig.getPassword());
|
|
||||||
ds.setInitialSize(1);
|
|
||||||
ds.setMinIdle(1);
|
|
||||||
ds.setMaxActive(datasourceConfig.getMaxActive());
|
|
||||||
ds.setMaxWait(60000);
|
|
||||||
ds.setTimeBetweenEvictionRunsMillis(60000);
|
|
||||||
ds.setMinEvictableIdleTimeMillis(300000);
|
|
||||||
ds.setPoolPreparedStatements(false);
|
|
||||||
ds.setMaxPoolPreparedStatementPerConnectionSize(20);
|
|
||||||
ds.setValidationQuery("select 1");
|
|
||||||
try {
|
|
||||||
ds.init();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logger.error("ERROR ## failed to initial datasource: " + datasourceConfig.getUrl(), e);
|
|
||||||
}
|
|
||||||
DatasourceConfig.DATA_SOURCES.put(entry.getKey(), ds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAdapterConfigs(List<String> adapterConfigs) {
|
|
||||||
this.adapterConfigs = adapterConfigs;
|
|
||||||
|
|
||||||
if (adapterConfigs != null) {
|
|
||||||
AdapterConfigs.clear();
|
|
||||||
for (String adapterConfig : adapterConfigs) {
|
|
||||||
int idx = adapterConfig.indexOf("/");
|
|
||||||
if (idx > -1) {
|
|
||||||
String type = adapterConfig.substring(0, idx);
|
|
||||||
String ymlFile = adapterConfig.substring(idx + 1);
|
|
||||||
AdapterConfigs.put(type, ymlFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,13 +6,16 @@ import java.util.concurrent.ExecutionException;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch;
|
import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch;
|
||||||
import com.alibaba.otter.canal.adapter.launcher.config.SpringContext;
|
import com.alibaba.otter.canal.adapter.launcher.config.SpringContext;
|
||||||
|
import com.alibaba.otter.canal.client.CanalMQConnector;
|
||||||
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.MessageUtil;
|
import com.alibaba.otter.canal.client.adapter.support.MessageUtil;
|
||||||
import com.alibaba.otter.canal.protocol.FlatMessage;
|
import com.alibaba.otter.canal.protocol.FlatMessage;
|
||||||
@@ -30,6 +33,7 @@ public abstract class AbstractCanalAdapterWorker {
|
|||||||
|
|
||||||
protected String canalDestination; // canal实例
|
protected String canalDestination; // canal实例
|
||||||
protected List<List<OuterAdapter>> canalOuterAdapters; // 外部适配器
|
protected List<List<OuterAdapter>> canalOuterAdapters; // 外部适配器
|
||||||
|
protected CanalClientConfig canalClientConfig; // 配置
|
||||||
protected ExecutorService groupInnerExecutorService; // 组内工作线程池
|
protected ExecutorService groupInnerExecutorService; // 组内工作线程池
|
||||||
protected volatile boolean running = false; // 是否运行中
|
protected volatile boolean running = false; // 是否运行中
|
||||||
protected Thread thread = null;
|
protected Thread thread = null;
|
||||||
@@ -53,7 +57,49 @@ public abstract class AbstractCanalAdapterWorker {
|
|||||||
// 组内适配器穿行运行,尽量不要配置组内适配器
|
// 组内适配器穿行运行,尽量不要配置组内适配器
|
||||||
adapters.forEach(adapter -> {
|
adapters.forEach(adapter -> {
|
||||||
long begin = System.currentTimeMillis();
|
long begin = System.currentTimeMillis();
|
||||||
MessageUtil.parse4Dml(canalDestination, message, adapter::sync);
|
List<Dml> dmls = MessageUtil.parse4Dml(canalDestination, message);
|
||||||
|
if (dmls != null) {
|
||||||
|
batchSync(dmls, adapter);
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("{} elapsed time: {}",
|
||||||
|
adapter.getClass().getName(),
|
||||||
|
(System.currentTimeMillis() - begin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 等待所有适配器写入完成
|
||||||
|
// 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成
|
||||||
|
futures.forEach(future -> {
|
||||||
|
try {
|
||||||
|
if (!future.get()) {
|
||||||
|
logger.error("Outer adapter write failed");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeOut(final List<FlatMessage> flatMessages) {
|
||||||
|
List<Future<Boolean>> futures = new ArrayList<>();
|
||||||
|
// 组间适配器并行运行
|
||||||
|
canalOuterAdapters.forEach(outerAdapters -> {
|
||||||
|
futures.add(groupInnerExecutorService.submit(() -> {
|
||||||
|
try {
|
||||||
|
// 组内适配器穿行运行,尽量不要配置组内适配器
|
||||||
|
outerAdapters.forEach(adapter -> {
|
||||||
|
long begin = System.currentTimeMillis();
|
||||||
|
List<Dml> dmls = MessageUtil.flatMessage2Dml(canalDestination, flatMessages);
|
||||||
|
batchSync(dmls, adapter);
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("{} elapsed time: {}",
|
logger.debug("{} elapsed time: {}",
|
||||||
adapter.getClass().getName(),
|
adapter.getClass().getName(),
|
||||||
@@ -80,41 +126,80 @@ public abstract class AbstractCanalAdapterWorker {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void writeOut(final FlatMessage flatMessage) {
|
@SuppressWarnings("unchecked")
|
||||||
List<Future<Boolean>> futures = new ArrayList<>();
|
protected void mqWriteOutData(int retry, long timeout, final boolean flatMessage, CanalMQConnector connector,
|
||||||
// 组间适配器并行运行
|
ExecutorService workerExecutor) {
|
||||||
canalOuterAdapters.forEach(outerAdapters -> {
|
for (int i = 0; i < retry; i++) {
|
||||||
futures.add(groupInnerExecutorService.submit(() -> {
|
try {
|
||||||
try {
|
List<?> messages;
|
||||||
// 组内适配器穿行运行,尽量不要配置组内适配器
|
if (!flatMessage) {
|
||||||
outerAdapters.forEach(adapter -> {
|
messages = connector.getListWithoutAck(100L, TimeUnit.MILLISECONDS);
|
||||||
long begin = System.currentTimeMillis();
|
} else {
|
||||||
Dml dml = MessageUtil.flatMessage2Dml(canalDestination, flatMessage);
|
messages = connector.getFlatListWithoutAck(100L, TimeUnit.MILLISECONDS);
|
||||||
adapter.sync(dml);
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("{} elapsed time: {}",
|
|
||||||
adapter.getClass().getName(),
|
|
||||||
(System.currentTimeMillis() - begin));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}));
|
if (messages != null) {
|
||||||
|
Future<Boolean> future = workerExecutor.submit(() -> {
|
||||||
|
if (flatMessage) {
|
||||||
|
// batch write
|
||||||
|
writeOut((List<FlatMessage>) messages);
|
||||||
|
} else {
|
||||||
|
for (final Object message : messages) {
|
||||||
|
writeOut((Message) message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
// 等待所有适配器写入完成
|
try {
|
||||||
// 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成
|
future.get(timeout, TimeUnit.MILLISECONDS);
|
||||||
futures.forEach(future -> {
|
} catch (Exception e) {
|
||||||
try {
|
future.cancel(true);
|
||||||
if (!future.get()) {
|
throw e;
|
||||||
logger.error("Outer adapter write failed");
|
|
||||||
}
|
}
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
}
|
||||||
|
connector.ack();
|
||||||
|
break;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (i == retry - 1) {
|
||||||
|
connector.ack();
|
||||||
|
} else {
|
||||||
|
connector.rollback();
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
try {
|
||||||
|
TimeUnit.SECONDS.sleep(1L);
|
||||||
|
} catch (InterruptedException e1) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分批同步
|
||||||
|
*
|
||||||
|
* @param dmls
|
||||||
|
* @param adapter
|
||||||
|
*/
|
||||||
|
private void batchSync(List<Dml> dmls, OuterAdapter adapter) {
|
||||||
|
// 分批同步
|
||||||
|
if (dmls.size() <= canalClientConfig.getSyncBatchSize()) {
|
||||||
|
adapter.sync(dmls);
|
||||||
|
} else {
|
||||||
|
int len = 0;
|
||||||
|
List<Dml> dmlsBatch = new ArrayList<>();
|
||||||
|
for (Dml dml : dmls) {
|
||||||
|
dmlsBatch.add(dml);
|
||||||
|
len += dml.getData().size();
|
||||||
|
if (len >= canalClientConfig.getSyncBatchSize()) {
|
||||||
|
adapter.sync(dmlsBatch);
|
||||||
|
dmlsBatch.clear();
|
||||||
|
len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adapter.sync(dmlsBatch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
package com.alibaba.otter.canal.adapter.launcher.loader;
|
package com.alibaba.otter.canal.adapter.launcher.loader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.apache.kafka.clients.consumer.CommitFailedException;
|
|
||||||
import org.apache.kafka.common.errors.WakeupException;
|
import org.apache.kafka.common.errors.WakeupException;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
|
||||||
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector;
|
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector;
|
||||||
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors;
|
|
||||||
import com.alibaba.otter.canal.protocol.FlatMessage;
|
|
||||||
import com.alibaba.otter.canal.protocol.Message;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* kafka对应的client适配器工作线程
|
* kafka对应的client适配器工作线程
|
||||||
@@ -24,20 +23,30 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker {
|
|||||||
private String topic;
|
private String topic;
|
||||||
private boolean flatMessage;
|
private boolean flatMessage;
|
||||||
|
|
||||||
public CanalAdapterKafkaWorker(String bootstrapServers, String topic, String groupId,
|
public CanalAdapterKafkaWorker(CanalClientConfig canalClientConfig, String bootstrapServers, String topic,
|
||||||
List<List<OuterAdapter>> canalOuterAdapters, boolean flatMessage){
|
String groupId, List<List<OuterAdapter>> canalOuterAdapters, boolean flatMessage){
|
||||||
super(canalOuterAdapters);
|
super(canalOuterAdapters);
|
||||||
|
this.canalClientConfig = canalClientConfig;
|
||||||
this.topic = topic;
|
this.topic = topic;
|
||||||
this.canalDestination = topic;
|
this.canalDestination = topic;
|
||||||
this.flatMessage = flatMessage;
|
this.flatMessage = flatMessage;
|
||||||
this.connector = KafkaCanalConnectors.newKafkaConnector(bootstrapServers, topic, null, groupId, flatMessage);
|
this.connector = new KafkaCanalConnector(bootstrapServers,
|
||||||
// connector.setSessionTimeout(1L, TimeUnit.MINUTES);
|
topic,
|
||||||
|
null,
|
||||||
|
groupId,
|
||||||
|
canalClientConfig.getBatchSize(),
|
||||||
|
flatMessage);
|
||||||
|
connector.setSessionTimeout(30L, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void process() {
|
protected void process() {
|
||||||
while (!running)
|
while (!running)
|
||||||
;
|
;
|
||||||
|
ExecutorService workerExecutor = Executors.newSingleThreadExecutor();
|
||||||
|
int retry = canalClientConfig.getRetries() == null || canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries();
|
||||||
|
long timeout = canalClientConfig.getTimeout() == null ? 30000 : canalClientConfig.getTimeout(); // 默认超时30秒
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
try {
|
try {
|
||||||
syncSwitch.get(canalDestination);
|
syncSwitch.get(canalDestination);
|
||||||
@@ -47,35 +56,12 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker {
|
|||||||
connector.subscribe();
|
connector.subscribe();
|
||||||
logger.info("=============> Subscribe topic: {} succeed <=============", this.topic);
|
logger.info("=============> Subscribe topic: {} succeed <=============", this.topic);
|
||||||
while (running) {
|
while (running) {
|
||||||
try {
|
Boolean status = syncSwitch.status(canalDestination);
|
||||||
Boolean status = syncSwitch.status(canalDestination);
|
if (status != null && !status) {
|
||||||
if (status != null && !status) {
|
connector.disconnect();
|
||||||
connector.disconnect();
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<?> messages;
|
|
||||||
if (!flatMessage) {
|
|
||||||
messages = connector.getListWithoutAck(100L, TimeUnit.MILLISECONDS);
|
|
||||||
} else {
|
|
||||||
messages = connector.getFlatListWithoutAck(100L, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
if (messages != null) {
|
|
||||||
for (final Object message : messages) {
|
|
||||||
if (message instanceof FlatMessage) {
|
|
||||||
writeOut((FlatMessage) message);
|
|
||||||
} else {
|
|
||||||
writeOut((Message) message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connector.ack();
|
|
||||||
} catch (CommitFailedException e) {
|
|
||||||
logger.warn(e.getMessage());
|
|
||||||
} catch (Throwable e) {
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
TimeUnit.SECONDS.sleep(1L);
|
|
||||||
}
|
}
|
||||||
|
mqWriteOutData(retry, timeout, flatMessage, connector, workerExecutor);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -53,61 +54,82 @@ public class CanalAdapterLoader {
|
|||||||
}
|
}
|
||||||
String zkHosts = this.canalClientConfig.getZookeeperHosts();
|
String zkHosts = this.canalClientConfig.getZookeeperHosts();
|
||||||
|
|
||||||
// 初始化canal-client的适配器
|
if ("tcp".equalsIgnoreCase(canalClientConfig.getMode())) {
|
||||||
if (canalClientConfig.getCanalInstances() != null) {
|
// 初始化canal-client的适配器
|
||||||
for (CanalClientConfig.CanalInstance instance : canalClientConfig.getCanalInstances()) {
|
for (CanalClientConfig.CanalAdapter canalAdapter : canalClientConfig.getCanalAdapters()) {
|
||||||
List<List<OuterAdapter>> canalOuterAdapterGroups = new ArrayList<>();
|
List<List<OuterAdapter>> canalOuterAdapterGroups = new ArrayList<>();
|
||||||
|
|
||||||
for (CanalClientConfig.Group connectorGroup : instance.getGroups()) {
|
for (CanalClientConfig.Group connectorGroup : canalAdapter.getGroups()) {
|
||||||
List<OuterAdapter> canalOutConnectors = new ArrayList<>();
|
List<OuterAdapter> canalOutConnectors = new ArrayList<>();
|
||||||
for (OuterAdapterConfig c : connectorGroup.getOutAdapters()) {
|
for (OuterAdapterConfig c : connectorGroup.getOuterAdapters()) {
|
||||||
loadConnector(c, canalOutConnectors);
|
loadConnector(c, canalOutConnectors);
|
||||||
}
|
}
|
||||||
canalOuterAdapterGroups.add(canalOutConnectors);
|
canalOuterAdapterGroups.add(canalOutConnectors);
|
||||||
}
|
}
|
||||||
CanalAdapterWorker worker;
|
CanalAdapterWorker worker;
|
||||||
if (sa != null) {
|
if (sa != null) {
|
||||||
worker = new CanalAdapterWorker(instance.getInstance(), sa, canalOuterAdapterGroups);
|
worker = new CanalAdapterWorker(canalClientConfig,
|
||||||
|
canalAdapter.getInstance(),
|
||||||
|
sa,
|
||||||
|
canalOuterAdapterGroups);
|
||||||
} else if (zkHosts != null) {
|
} else if (zkHosts != null) {
|
||||||
worker = new CanalAdapterWorker(instance.getInstance(), zkHosts, canalOuterAdapterGroups);
|
worker = new CanalAdapterWorker(canalClientConfig,
|
||||||
|
canalAdapter.getInstance(),
|
||||||
|
zkHosts,
|
||||||
|
canalOuterAdapterGroups);
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("No canal server connector found");
|
throw new RuntimeException("No canal server connector found");
|
||||||
}
|
}
|
||||||
canalWorkers.put(instance.getInstance(), worker);
|
canalWorkers.put(canalAdapter.getInstance(), worker);
|
||||||
worker.start();
|
worker.start();
|
||||||
logger.info("Start adapter for canal instance: {} succeed", instance.getInstance());
|
logger.info("Start adapter for canal instance: {} succeed", canalAdapter.getInstance());
|
||||||
}
|
}
|
||||||
}
|
} else if ("kafka".equalsIgnoreCase(canalClientConfig.getMode())) {
|
||||||
|
// 初始化canal-client-kafka的适配器
|
||||||
// 初始化canal-client-mq的适配器
|
for (CanalClientConfig.CanalAdapter canalAdapter : canalClientConfig.getCanalAdapters()) {
|
||||||
if (canalClientConfig.getMqTopics() != null) {
|
for (CanalClientConfig.Group group : canalAdapter.getGroups()) {
|
||||||
for (CanalClientConfig.MQTopic topic : canalClientConfig.getMqTopics()) {
|
|
||||||
for (CanalClientConfig.MQGroup group : topic.getGroups()) {
|
|
||||||
List<List<OuterAdapter>> canalOuterAdapterGroups = new ArrayList<>();
|
List<List<OuterAdapter>> canalOuterAdapterGroups = new ArrayList<>();
|
||||||
List<OuterAdapter> canalOuterAdapters = new ArrayList<>();
|
List<OuterAdapter> canalOuterAdapters = new ArrayList<>();
|
||||||
for (OuterAdapterConfig config : group.getOutAdapters()) {
|
for (OuterAdapterConfig config : group.getOuterAdapters()) {
|
||||||
loadConnector(config, canalOuterAdapters);
|
loadConnector(config, canalOuterAdapters);
|
||||||
}
|
}
|
||||||
canalOuterAdapterGroups.add(canalOuterAdapters);
|
canalOuterAdapterGroups.add(canalOuterAdapters);
|
||||||
if (StringUtils.isBlank(topic.getMqMode()) || "rocketmq".equalsIgnoreCase(topic.getMqMode())) {
|
|
||||||
CanalAdapterRocketMQWorker rocketMQWorker = new CanalAdapterRocketMQWorker(canalClientConfig.getBootstrapServers(),
|
CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(canalClientConfig,
|
||||||
topic.getTopic(),
|
canalClientConfig.getMqServers(),
|
||||||
group.getGroupId(),
|
canalAdapter.getInstance(),
|
||||||
canalOuterAdapterGroups,
|
group.getGroupId(),
|
||||||
canalClientConfig.getFlatMessage());
|
canalOuterAdapterGroups,
|
||||||
canalMQWorker.put(topic.getTopic() + "-rocketmq-" + group.getGroupId(), rocketMQWorker);
|
canalClientConfig.getFlatMessage());
|
||||||
rocketMQWorker.start();
|
canalMQWorker.put(canalAdapter.getInstance() + "-kafka-" + group.getGroupId(), canalKafkaWorker);
|
||||||
} else if ("kafka".equalsIgnoreCase(topic.getMqMode())) {
|
canalKafkaWorker.start();
|
||||||
CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(canalClientConfig.getBootstrapServers(),
|
logger.info("Start adapter for canal-client mq topic: {} succeed", canalAdapter.getInstance() + "-"
|
||||||
topic.getTopic(),
|
+ group.getGroupId());
|
||||||
group.getGroupId(),
|
}
|
||||||
canalOuterAdapterGroups,
|
}
|
||||||
canalClientConfig.getFlatMessage());
|
} else if ("rocketMQ".equalsIgnoreCase(canalClientConfig.getMode())) {
|
||||||
canalMQWorker.put(topic.getTopic() + "-kafka-" + group.getGroupId(), canalKafkaWorker);
|
// 初始化canal-client-rocketMQ的适配器
|
||||||
canalKafkaWorker.start();
|
for (CanalClientConfig.CanalAdapter canalAdapter : canalClientConfig.getCanalAdapters()) {
|
||||||
|
for (CanalClientConfig.Group group : canalAdapter.getGroups()) {
|
||||||
|
List<List<OuterAdapter>> canalOuterAdapterGroups = new ArrayList<>();
|
||||||
|
List<OuterAdapter> canalOuterAdapters = new ArrayList<>();
|
||||||
|
for (OuterAdapterConfig config : group.getOuterAdapters()) {
|
||||||
|
loadConnector(config, canalOuterAdapters);
|
||||||
}
|
}
|
||||||
logger.info("Start adapter for canal-client mq topic: {} succeed",
|
canalOuterAdapterGroups.add(canalOuterAdapters);
|
||||||
topic.getTopic() + "-" + group.getGroupId());
|
CanalAdapterRocketMQWorker rocketMQWorker = new CanalAdapterRocketMQWorker(canalClientConfig,
|
||||||
|
canalClientConfig.getMqServers(),
|
||||||
|
canalAdapter.getInstance(),
|
||||||
|
group.getGroupId(),
|
||||||
|
canalOuterAdapterGroups,
|
||||||
|
canalClientConfig.getAccessKey(),
|
||||||
|
canalClientConfig.getSecretKey(),
|
||||||
|
canalClientConfig.getFlatMessage());
|
||||||
|
canalMQWorker.put(canalAdapter.getInstance() + "-rocketmq-" + group.getGroupId(), rocketMQWorker);
|
||||||
|
rocketMQWorker.start();
|
||||||
|
|
||||||
|
logger.info("Start adapter for canal-client mq topic: {} succeed", canalAdapter.getInstance() + "-"
|
||||||
|
+ group.getGroupId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +137,9 @@ public class CanalAdapterLoader {
|
|||||||
|
|
||||||
private void loadConnector(OuterAdapterConfig config, List<OuterAdapter> canalOutConnectors) {
|
private void loadConnector(OuterAdapterConfig config, List<OuterAdapter> canalOutConnectors) {
|
||||||
try {
|
try {
|
||||||
OuterAdapter adapter = loader.getExtension(config.getName());
|
OuterAdapter adapter;
|
||||||
|
adapter = loader.getExtension(config.getName(), StringUtils.trimToEmpty(config.getKey()));
|
||||||
|
|
||||||
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||||
// 替换ClassLoader
|
// 替换ClassLoader
|
||||||
Thread.currentThread().setContextClassLoader(adapter.getClass().getClassLoader());
|
Thread.currentThread().setContextClassLoader(adapter.getClass().getClassLoader());
|
||||||
@@ -132,19 +156,41 @@ public class CanalAdapterLoader {
|
|||||||
* 销毁所有适配器 为防止canal实例太多造成销毁阻塞, 并行销毁
|
* 销毁所有适配器 为防止canal实例太多造成销毁阻塞, 并行销毁
|
||||||
*/
|
*/
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
if (canalWorkers.size() > 0) {
|
if (!canalWorkers.isEmpty()) {
|
||||||
ExecutorService stopExecutorService = Executors.newFixedThreadPool(canalWorkers.size());
|
ExecutorService stopExecutorService = Executors.newFixedThreadPool(canalWorkers.size());
|
||||||
|
List<Future<Boolean>> futures = new ArrayList<>();
|
||||||
for (CanalAdapterWorker canalAdapterWorker : canalWorkers.values()) {
|
for (CanalAdapterWorker canalAdapterWorker : canalWorkers.values()) {
|
||||||
stopExecutorService.submit(canalAdapterWorker::stop);
|
futures.add(stopExecutorService.submit(() -> {
|
||||||
|
canalAdapterWorker.stop();
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
futures.forEach(future -> {
|
||||||
|
try {
|
||||||
|
future.get();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
});
|
||||||
stopExecutorService.shutdown();
|
stopExecutorService.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canalMQWorker.size() > 0) {
|
if (!canalMQWorker.isEmpty()) {
|
||||||
ExecutorService stopMQWorkerService = Executors.newFixedThreadPool(canalMQWorker.size());
|
ExecutorService stopMQWorkerService = Executors.newFixedThreadPool(canalMQWorker.size());
|
||||||
|
List<Future<Boolean>> futures = new ArrayList<>();
|
||||||
for (AbstractCanalAdapterWorker canalAdapterMQWorker : canalMQWorker.values()) {
|
for (AbstractCanalAdapterWorker canalAdapterMQWorker : canalMQWorker.values()) {
|
||||||
stopMQWorkerService.submit(canalAdapterMQWorker::stop);
|
futures.add(stopMQWorkerService.submit(() -> {
|
||||||
|
canalAdapterMQWorker.stop();
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
futures.forEach(future -> {
|
||||||
|
try {
|
||||||
|
future.get();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
});
|
||||||
stopMQWorkerService.shutdown();
|
stopMQWorkerService.shutdown();
|
||||||
}
|
}
|
||||||
logger.info("All canal adapters destroyed");
|
logger.info("All canal adapters destroyed");
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
package com.alibaba.otter.canal.adapter.launcher.loader;
|
package com.alibaba.otter.canal.adapter.launcher.loader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import org.apache.kafka.common.errors.WakeupException;
|
import org.apache.kafka.common.errors.WakeupException;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
|
||||||
import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnector;
|
import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnector;
|
||||||
import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnectors;
|
|
||||||
import com.alibaba.otter.canal.protocol.FlatMessage;
|
|
||||||
import com.alibaba.otter.canal.protocol.Message;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* rocketmq对应的client适配器工作线程
|
* rocketmq对应的client适配器工作线程
|
||||||
@@ -22,13 +21,15 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker {
|
|||||||
private String topic;
|
private String topic;
|
||||||
private boolean flatMessage;
|
private boolean flatMessage;
|
||||||
|
|
||||||
public CanalAdapterRocketMQWorker(String nameServers, String topic, String groupId,
|
public CanalAdapterRocketMQWorker(CanalClientConfig canalClientConfig, String nameServers, String topic,
|
||||||
List<List<OuterAdapter>> canalOuterAdapters, boolean flatMessage){
|
String groupId, List<List<OuterAdapter>> canalOuterAdapters, String accessKey,
|
||||||
|
String secretKey, boolean flatMessage){
|
||||||
super(canalOuterAdapters);
|
super(canalOuterAdapters);
|
||||||
|
this.canalClientConfig = canalClientConfig;
|
||||||
this.topic = topic;
|
this.topic = topic;
|
||||||
this.flatMessage = flatMessage;
|
this.flatMessage = flatMessage;
|
||||||
this.canalDestination = topic;
|
this.canalDestination = topic;
|
||||||
this.connector = RocketMQCanalConnectors.newRocketMQConnector(nameServers, topic, groupId, flatMessage);
|
this.connector = new RocketMQCanalConnector(nameServers, topic, groupId, accessKey, secretKey, flatMessage);
|
||||||
logger.info("RocketMQ consumer config topic:{}, nameServer:{}, groupId:{}", topic, nameServers, groupId);
|
logger.info("RocketMQ consumer config topic:{}, nameServer:{}, groupId:{}", topic, nameServers, groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +37,11 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker {
|
|||||||
protected void process() {
|
protected void process() {
|
||||||
while (!running)
|
while (!running)
|
||||||
;
|
;
|
||||||
|
|
||||||
|
ExecutorService workerExecutor = Executors.newSingleThreadExecutor();
|
||||||
|
int retry = canalClientConfig.getRetries() == null || canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries();
|
||||||
|
long timeout = canalClientConfig.getTimeout() == null ? 30000 : canalClientConfig.getTimeout(); // 默认超时30秒
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
try {
|
try {
|
||||||
syncSwitch.get(canalDestination);
|
syncSwitch.get(canalDestination);
|
||||||
@@ -45,33 +51,12 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker {
|
|||||||
connector.subscribe();
|
connector.subscribe();
|
||||||
logger.info("=============> Subscribe topic: {} succeed<=============", this.topic);
|
logger.info("=============> Subscribe topic: {} succeed<=============", this.topic);
|
||||||
while (running) {
|
while (running) {
|
||||||
try {
|
Boolean status = syncSwitch.status(canalDestination);
|
||||||
Boolean status = syncSwitch.status(canalDestination);
|
if (status != null && !status) {
|
||||||
if (status != null && !status) {
|
connector.disconnect();
|
||||||
connector.disconnect();
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<?> messages;
|
|
||||||
if (!flatMessage) {
|
|
||||||
messages = connector.getListWithoutAck(100L, TimeUnit.MILLISECONDS);
|
|
||||||
} else {
|
|
||||||
messages = connector.getFlatListWithoutAck(100L, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
if (messages != null) {
|
|
||||||
for (final Object message : messages) {
|
|
||||||
if (message instanceof FlatMessage) {
|
|
||||||
writeOut((FlatMessage) message);
|
|
||||||
} else {
|
|
||||||
writeOut((Message) message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connector.ack();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
TimeUnit.SECONDS.sleep(1L);
|
|
||||||
}
|
}
|
||||||
|
mqWriteOutData(retry, timeout, flatMessage, connector, workerExecutor);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import javax.annotation.PostConstruct;
|
|||||||
import javax.annotation.PreDestroy;
|
import javax.annotation.PreDestroy;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch;
|
|
||||||
import com.alibaba.otter.canal.adapter.launcher.config.SpringContext;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import com.alibaba.druid.pool.DruidDataSource;
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
|
import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch;
|
||||||
import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig;
|
import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig;
|
||||||
import com.alibaba.otter.canal.adapter.launcher.config.AdapterConfig;
|
import com.alibaba.otter.canal.adapter.launcher.config.SpringContext;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,44 +22,51 @@ import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
|||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
|
@RefreshScope
|
||||||
public class CanalAdapterService {
|
public class CanalAdapterService {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CanalAdapterService.class);
|
private static final Logger logger = LoggerFactory.getLogger(CanalAdapterService.class);
|
||||||
|
|
||||||
private static CanalAdapterLoader adapterLoader;
|
private CanalAdapterLoader adapterLoader;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private AdapterCanalConfig adapterCanalConfig;
|
private AdapterCanalConfig adapterCanalConfig;
|
||||||
|
|
||||||
// 注入bean保证优先注册
|
// 注入bean保证优先注册
|
||||||
@Resource
|
@Resource
|
||||||
private AdapterConfig adapterConfig;
|
private SpringContext springContext;
|
||||||
@Resource
|
@Resource
|
||||||
private SpringContext springContext;
|
private SyncSwitch syncSwitch;
|
||||||
@Resource
|
|
||||||
private SyncSwitch syncSwitch;
|
private volatile boolean running = false;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public synchronized void init() {
|
||||||
if (adapterLoader == null) {
|
if (running) {
|
||||||
try {
|
return;
|
||||||
logger.info("## start the canal client adapters.");
|
}
|
||||||
adapterLoader = new CanalAdapterLoader(adapterCanalConfig);
|
try {
|
||||||
adapterLoader.init();
|
logger.info("## start the canal client adapters.");
|
||||||
logger.info("## the canal client adapters are running now ......");
|
adapterLoader = new CanalAdapterLoader(adapterCanalConfig);
|
||||||
} catch (Throwable e) {
|
adapterLoader.init();
|
||||||
logger.error("## something goes wrong when starting up the canal client adapters:", e);
|
running = true;
|
||||||
System.exit(0);
|
logger.info("## the canal client adapters are running now ......");
|
||||||
}
|
} catch (Exception e) {
|
||||||
|
logger.error("## something goes wrong when starting up the canal client adapters:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
public void destroy() {
|
public synchronized void destroy() {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
|
running = false;
|
||||||
logger.info("## stop the canal client adapters");
|
logger.info("## stop the canal client adapters");
|
||||||
if (adapterLoader != null) {
|
if (adapterLoader != null) {
|
||||||
adapterLoader.destroy();
|
adapterLoader.destroy();
|
||||||
|
adapterLoader = null;
|
||||||
}
|
}
|
||||||
for (DruidDataSource druidDataSource : DatasourceConfig.DATA_SOURCES.values()) {
|
for (DruidDataSource druidDataSource : DatasourceConfig.DATA_SOURCES.values()) {
|
||||||
try {
|
try {
|
||||||
@@ -68,6 +75,7 @@ public class CanalAdapterService {
|
|||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DatasourceConfig.DATA_SOURCES.clear();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.warn("## something goes wrong when stopping canal client adapters:", e);
|
logger.warn("## something goes wrong when stopping canal client adapters:", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ package com.alibaba.otter.canal.adapter.launcher.loader;
|
|||||||
|
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import com.alibaba.otter.canal.client.CanalConnector;
|
import com.alibaba.otter.canal.client.CanalConnector;
|
||||||
import com.alibaba.otter.canal.client.CanalConnectors;
|
import com.alibaba.otter.canal.client.CanalConnectors;
|
||||||
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
|
||||||
import com.alibaba.otter.canal.client.impl.ClusterCanalConnector;
|
import com.alibaba.otter.canal.client.impl.ClusterCanalConnector;
|
||||||
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;
|
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;
|
||||||
import com.alibaba.otter.canal.protocol.Message;
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
@@ -20,10 +20,10 @@ import com.alibaba.otter.canal.protocol.Message;
|
|||||||
*/
|
*/
|
||||||
public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
||||||
|
|
||||||
private static final int BATCH_SIZE = 50;
|
private static final int BATCH_SIZE = 50;
|
||||||
private static final int SO_TIMEOUT = 0;
|
private static final int SO_TIMEOUT = 0;
|
||||||
|
|
||||||
private CanalConnector connector;
|
private CanalConnector connector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单台client适配器worker的构造方法
|
* 单台client适配器worker的构造方法
|
||||||
@@ -32,9 +32,10 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
|||||||
* @param address canal-server地址
|
* @param address canal-server地址
|
||||||
* @param canalOuterAdapters 外部适配器组
|
* @param canalOuterAdapters 外部适配器组
|
||||||
*/
|
*/
|
||||||
public CanalAdapterWorker(String canalDestination, SocketAddress address,
|
public CanalAdapterWorker(CanalClientConfig canalClientConfig, String canalDestination, SocketAddress address,
|
||||||
List<List<OuterAdapter>> canalOuterAdapters){
|
List<List<OuterAdapter>> canalOuterAdapters){
|
||||||
super(canalOuterAdapters);
|
super(canalOuterAdapters);
|
||||||
|
this.canalClientConfig = canalClientConfig;
|
||||||
this.canalDestination = canalDestination;
|
this.canalDestination = canalDestination;
|
||||||
connector = CanalConnectors.newSingleConnector(address, canalDestination, "", "");
|
connector = CanalConnectors.newSingleConnector(address, canalDestination, "", "");
|
||||||
}
|
}
|
||||||
@@ -46,10 +47,11 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
|||||||
* @param zookeeperHosts zookeeper地址
|
* @param zookeeperHosts zookeeper地址
|
||||||
* @param canalOuterAdapters 外部适配器组
|
* @param canalOuterAdapters 外部适配器组
|
||||||
*/
|
*/
|
||||||
public CanalAdapterWorker(String canalDestination, String zookeeperHosts,
|
public CanalAdapterWorker(CanalClientConfig canalClientConfig, String canalDestination, String zookeeperHosts,
|
||||||
List<List<OuterAdapter>> canalOuterAdapters){
|
List<List<OuterAdapter>> canalOuterAdapters){
|
||||||
super(canalOuterAdapters);
|
super(canalOuterAdapters);
|
||||||
this.canalDestination = canalDestination;
|
this.canalDestination = canalDestination;
|
||||||
|
this.canalClientConfig = canalClientConfig;
|
||||||
connector = CanalConnectors.newClusterConnector(zookeeperHosts, canalDestination, "", "");
|
connector = CanalConnectors.newClusterConnector(zookeeperHosts, canalDestination, "", "");
|
||||||
((ClusterCanalConnector) connector).setSoTimeout(SO_TIMEOUT);
|
((ClusterCanalConnector) connector).setSoTimeout(SO_TIMEOUT);
|
||||||
}
|
}
|
||||||
@@ -58,6 +60,16 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
|||||||
protected void process() {
|
protected void process() {
|
||||||
while (!running)
|
while (!running)
|
||||||
; // waiting until running == true
|
; // waiting until running == true
|
||||||
|
|
||||||
|
ExecutorService workerExecutor = Executors.newSingleThreadExecutor();
|
||||||
|
int retry = canalClientConfig.getRetries() == null
|
||||||
|
|| canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries();
|
||||||
|
long timeout = canalClientConfig.getTimeout() == null ? 300000 : canalClientConfig.getTimeout(); // 默认超时5分钟
|
||||||
|
Integer batchSize = canalClientConfig.getBatchSize();
|
||||||
|
if (batchSize == null) {
|
||||||
|
batchSize = BATCH_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
try {
|
try {
|
||||||
syncSwitch.get(canalDestination);
|
syncSwitch.get(canalDestination);
|
||||||
@@ -77,35 +89,50 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// server配置canal.instance.network.soTimeout(默认: 30s)
|
for (int i = 0; i < retry; i++) {
|
||||||
// 范围内未与server交互,server将关闭本次socket连接
|
Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
|
||||||
Message message = connector.getWithoutAck(BATCH_SIZE); // 获取指定数量的数据
|
long batchId = message.getId();
|
||||||
long batchId = message.getId();
|
try {
|
||||||
try {
|
int size = message.getEntries().size();
|
||||||
int size = message.getEntries().size();
|
if (batchId == -1 || size == 0) {
|
||||||
if (batchId == -1 || size == 0) {
|
Thread.sleep(500);
|
||||||
Thread.sleep(1000);
|
} else {
|
||||||
} else {
|
Future<Boolean> future = workerExecutor.submit(() -> {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("destination: {} batchId: {} batchSize: {} ",
|
logger.debug("destination: {} batchId: {} batchSize: {} ",
|
||||||
this.canalDestination,
|
canalDestination,
|
||||||
batchId,
|
batchId,
|
||||||
size);
|
size);
|
||||||
|
}
|
||||||
|
long begin = System.currentTimeMillis();
|
||||||
|
writeOut(message);
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("destination: {} batchId: {} elapsed time: {} ms",
|
||||||
|
canalDestination,
|
||||||
|
batchId,
|
||||||
|
System.currentTimeMillis() - begin);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
future.get(timeout, TimeUnit.MILLISECONDS);
|
||||||
|
} catch (Exception e) {
|
||||||
|
future.cancel(true);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
long begin = System.currentTimeMillis();
|
connector.ack(batchId); // 提交确认
|
||||||
writeOut(message);
|
break;
|
||||||
if (logger.isDebugEnabled()) {
|
} catch (Exception e) {
|
||||||
logger.debug("destination: {} batchId: {} elapsed time: {} ms",
|
if (i != retry - 1) {
|
||||||
this.canalDestination,
|
connector.rollback(batchId); // 处理失败, 回滚数据
|
||||||
batchId,
|
} else {
|
||||||
System.currentTimeMillis() - begin);
|
connector.ack(batchId);
|
||||||
}
|
}
|
||||||
|
logger.error("sync error!", e);
|
||||||
|
Thread.sleep(500);
|
||||||
}
|
}
|
||||||
connector.ack(batchId); // 提交确认
|
|
||||||
} catch (Exception e) {
|
|
||||||
connector.rollback(batchId); // 处理失败, 回滚数据
|
|
||||||
logger.error("sync error!", e);
|
|
||||||
Thread.sleep(500);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +151,8 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
workerExecutor.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.alibaba.otter.canal.adapter.launcher.monitor;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.annotation.PreDestroy;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
import org.apache.commons.io.filefilter.FileFilterUtils;
|
||||||
|
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
|
||||||
|
import org.apache.commons.io.monitor.FileAlterationMonitor;
|
||||||
|
import org.apache.commons.io.monitor.FileAlterationObserver;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.cloud.context.refresh.ContextRefresher;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.adapter.launcher.loader.CanalAdapterService;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ApplicationConfigMonitor {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ApplicationConfigMonitor.class);
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ContextRefresher contextRefresher;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CanalAdapterService canalAdapterService;
|
||||||
|
|
||||||
|
private FileAlterationMonitor fileMonitor;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
File confDir = Util.getConfDirPath();
|
||||||
|
try {
|
||||||
|
FileAlterationObserver observer = new FileAlterationObserver(confDir,
|
||||||
|
FileFilterUtils.and(FileFilterUtils.fileFileFilter(),
|
||||||
|
FileFilterUtils.prefixFileFilter("application"),
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void destroy() {
|
||||||
|
try {
|
||||||
|
fileMonitor.stop();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FileListener extends FileAlterationListenerAdaptor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFileChange(File file) {
|
||||||
|
super.onFileChange(file);
|
||||||
|
try {
|
||||||
|
// 检查yml格式
|
||||||
|
new Yaml().loadAs(new FileReader(file), Map.class);
|
||||||
|
|
||||||
|
canalAdapterService.destroy();
|
||||||
|
|
||||||
|
// refresh context
|
||||||
|
contextRefresher.refresh();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
canalAdapterService.init();
|
||||||
|
logger.info("## adapter application config reloaded.");
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,18 +46,21 @@ public class CommonRest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ETL curl http://127.0.0.1:8081/etl/hbase/mytest_person2.yml -X POST
|
* ETL curl http://127.0.0.1:8081/etl/rdb/oracle1/mytest_user.yml -X POST
|
||||||
*
|
*
|
||||||
* @param type 类型 hbase, es
|
* @param type 类型 hbase, es
|
||||||
* @param task 任务名对应配置文件名 mytest_person2.yml
|
* @param key adapter key
|
||||||
|
* @param task 任务名对应配置文件名 mytest_user.yml
|
||||||
* @param params etl where条件参数, 为空全部导入
|
* @param params etl where条件参数, 为空全部导入
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
@PostMapping("/etl/{type}/{task}")
|
@PostMapping("/etl/{type}/{key}/{task}")
|
||||||
public EtlResult etl(@PathVariable String type, @PathVariable String task,
|
public EtlResult etl(@PathVariable String type, @PathVariable String key, @PathVariable String task,
|
||||||
@RequestParam(name = "params", required = false) String params) {
|
@RequestParam(name = "params", required = false) String params) {
|
||||||
|
OuterAdapter adapter = loader.getExtension(type, key);
|
||||||
|
String destination = adapter.getDestination(task);
|
||||||
|
String lockKey = destination == null ? task : destination;
|
||||||
|
|
||||||
boolean locked = etlLock.tryLock(ETL_LOCK_ZK_NODE + type + "-" + task);
|
boolean locked = etlLock.tryLock(ETL_LOCK_ZK_NODE + type + "-" + lockKey);
|
||||||
if (!locked) {
|
if (!locked) {
|
||||||
EtlResult result = new EtlResult();
|
EtlResult result = new EtlResult();
|
||||||
result.setSucceeded(false);
|
result.setSucceeded(false);
|
||||||
@@ -65,30 +68,65 @@ public class CommonRest {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
OuterAdapter adapter = loader.getExtension(type);
|
|
||||||
String destination = adapter.getDestination(task);
|
Boolean oriSwitchStatus;
|
||||||
Boolean oriSwithcStatus = null;
|
|
||||||
if (destination != null) {
|
if (destination != null) {
|
||||||
oriSwithcStatus = syncSwitch.status(destination);
|
oriSwitchStatus = syncSwitch.status(destination);
|
||||||
syncSwitch.off(destination);
|
if (oriSwitchStatus != null && oriSwitchStatus) {
|
||||||
|
syncSwitch.off(destination);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// task可能为destination,直接锁task
|
||||||
|
oriSwitchStatus = syncSwitch.status(task);
|
||||||
|
if (oriSwitchStatus != null && oriSwitchStatus) {
|
||||||
|
syncSwitch.off(task);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
List<String> paramArr = null;
|
List<String> paramArray = null;
|
||||||
if (params != null) {
|
if (params != null) {
|
||||||
String[] parmaArray = params.trim().split(";");
|
paramArray = Arrays.asList(params.trim().split(";"));
|
||||||
paramArr = Arrays.asList(parmaArray);
|
|
||||||
}
|
}
|
||||||
return adapter.etl(task, paramArr);
|
return adapter.etl(task, paramArray);
|
||||||
} finally {
|
} finally {
|
||||||
if (destination != null && oriSwithcStatus != null && oriSwithcStatus) {
|
if (destination != null && oriSwitchStatus != null && oriSwitchStatus) {
|
||||||
syncSwitch.on(destination);
|
syncSwitch.on(destination);
|
||||||
|
} else if (destination == null && oriSwitchStatus != null && oriSwitchStatus) {
|
||||||
|
syncSwitch.on(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
etlLock.unlock(ETL_LOCK_ZK_NODE + type + "-" + task);
|
etlLock.unlock(ETL_LOCK_ZK_NODE + type + "-" + lockKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ETL curl http://127.0.0.1:8081/etl/hbase/mytest_person2.yml -X POST
|
||||||
|
*
|
||||||
|
* @param type 类型 hbase, es
|
||||||
|
* @param task 任务名对应配置文件名 mytest_person2.yml
|
||||||
|
* @param params etl where条件参数, 为空全部导入
|
||||||
|
*/
|
||||||
|
@PostMapping("/etl/{type}/{task}")
|
||||||
|
public EtlResult etl(@PathVariable String type, @PathVariable String task,
|
||||||
|
@RequestParam(name = "params", required = false) String params) {
|
||||||
|
return etl(type, null, task, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计总数 curl http://127.0.0.1:8081/count/rdb/oracle1/mytest_user.yml
|
||||||
|
*
|
||||||
|
* @param type 类型 hbase, es
|
||||||
|
* @param key adapter key
|
||||||
|
* @param task 任务名对应配置文件名 mytest_person2.yml
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/count/{type}/{key}/{task}")
|
||||||
|
public Map<String, Object> count(@PathVariable String type, @PathVariable String key, @PathVariable String task) {
|
||||||
|
OuterAdapter adapter = loader.getExtension(type, key);
|
||||||
|
return adapter.count(task);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统计总数 curl http://127.0.0.1:8081/count/hbase/mytest_person2.yml
|
* 统计总数 curl http://127.0.0.1:8081/count/hbase/mytest_person2.yml
|
||||||
*
|
*
|
||||||
@@ -98,8 +136,7 @@ public class CommonRest {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/count/{type}/{task}")
|
@GetMapping("/count/{type}/{task}")
|
||||||
public Map<String, Object> count(@PathVariable String type, @PathVariable String task) {
|
public Map<String, Object> count(@PathVariable String type, @PathVariable String task) {
|
||||||
OuterAdapter adapter = loader.getExtension(type);
|
return count(type, null, task);
|
||||||
return adapter.count(task);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,53 +2,61 @@ server:
|
|||||||
port: 8081
|
port: 8081
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
|
org.springframework: WARN
|
||||||
com.alibaba.otter.canal.client.adapter.hbase: DEBUG
|
com.alibaba.otter.canal.client.adapter.hbase: DEBUG
|
||||||
|
com.alibaba.otter.canal.client.adapter.es: DEBUG
|
||||||
|
com.alibaba.otter.canal.client.adapter.rdb: DEBUG
|
||||||
spring:
|
spring:
|
||||||
jackson:
|
jackson:
|
||||||
date-format: yyyy-MM-dd HH:mm:ss
|
date-format: yyyy-MM-dd HH:mm:ss
|
||||||
time-zone: GMT+8
|
time-zone: GMT+8
|
||||||
default-property-inclusion: non_null
|
default-property-inclusion: non_null
|
||||||
|
|
||||||
|
|
||||||
hbasezookeeper.quorum: 127.0.0.1
|
|
||||||
hbase.zookeeper.property.clientPort: 2181
|
|
||||||
hbase.zookeeper.znode.parent: /hbase
|
|
||||||
|
|
||||||
canal.conf:
|
canal.conf:
|
||||||
canalServerHost: 127.0.0.1:11111
|
canalServerHost: 127.0.0.1:11111
|
||||||
# zookeeperHosts: slave1:2181
|
# zookeeperHosts: slave1:2181
|
||||||
# bootstrapServers: slave1:6667 #or rocketmq nameservers:host1:9876;host2:9876
|
# mqServers: slave1:6667 #or rocketmq
|
||||||
flatMessage: true
|
# flatMessage: true
|
||||||
canalInstances:
|
batchSize: 500
|
||||||
- instance: example
|
syncBatchSize: 1000
|
||||||
groups:
|
retries: 0
|
||||||
- outAdapters:
|
timeout:
|
||||||
- name: logger
|
accessKey:
|
||||||
# - name: hbase
|
secretKey:
|
||||||
# properties:
|
mode: tcp # kafka rocketMQ
|
||||||
# hbase.zookeeper.quorum: ${hbase.zookeeper.quorum}
|
# srcDataSources:
|
||||||
# hbase.zookeeper.property.clientPort: ${hbase.zookeeper.property.clientPort}
|
|
||||||
# zookeeper.znode.parent: ${hbase.zookeeper.znode.parent}
|
|
||||||
# mqTopics:
|
|
||||||
# - mqMode: kafka
|
|
||||||
# topic: example
|
|
||||||
# groups:
|
|
||||||
# - groupId: g2
|
|
||||||
# outAdapters:
|
|
||||||
# - name: logger
|
|
||||||
# mqTopics:
|
|
||||||
# - mqMode: rocketmq
|
|
||||||
# topic: example
|
|
||||||
# groups:
|
|
||||||
# - groupId: g2
|
|
||||||
# outAdapters:
|
|
||||||
# - name: logger
|
|
||||||
|
|
||||||
#adapter.conf:
|
|
||||||
# datasourceConfigs:
|
|
||||||
# defaultDS:
|
# defaultDS:
|
||||||
# url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true
|
# url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true
|
||||||
# username: root
|
# username: root
|
||||||
# password: 121212
|
# password: 121212
|
||||||
# adapterConfigs:
|
canalAdapters:
|
||||||
# - hbase/mytest_person2.yml
|
- instance: example # canal instance Name or mq topic name
|
||||||
|
groups:
|
||||||
|
- groupId: g1
|
||||||
|
outerAdapters:
|
||||||
|
- name: logger
|
||||||
|
# - name: rdb
|
||||||
|
# key: oracle1
|
||||||
|
# properties:
|
||||||
|
# jdbc.driverClassName: oracle.jdbc.OracleDriver
|
||||||
|
# jdbc.url: jdbc:oracle:thin:@localhost:49161:XE
|
||||||
|
# jdbc.username: mytest
|
||||||
|
# jdbc.password: m121212
|
||||||
|
# - name: rdb
|
||||||
|
# key: postgres1
|
||||||
|
# properties:
|
||||||
|
# jdbc.driverClassName: org.postgresql.Driver
|
||||||
|
# jdbc.url: jdbc:postgresql://localhost:5432/postgres
|
||||||
|
# jdbc.username: postgres
|
||||||
|
# jdbc.password: 121212
|
||||||
|
# threads: 1
|
||||||
|
# commitSize: 3000
|
||||||
|
# - name: hbase
|
||||||
|
# properties:
|
||||||
|
# hbase.zookeeper.quorum: 127.0.0.1
|
||||||
|
# hbase.zookeeper.property.clientPort: 2181
|
||||||
|
# zookeeper.znode.parent: /hbase
|
||||||
|
# - name: es
|
||||||
|
# hosts: 127.0.0.1:9300
|
||||||
|
# properties:
|
||||||
|
# cluster.name: elasticsearch
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<version>1.1.1</version>
|
<version>1.1.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package com.alibaba.otter.canal.client.adapter.logger;
|
package com.alibaba.otter.canal.client.adapter.logger;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||||
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
|
|
||||||
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
|
||||||
import com.alibaba.otter.canal.client.adapter.support.SPI;
|
import com.alibaba.otter.canal.client.adapter.support.SPI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,9 +29,14 @@ public class LoggerAdapterExample implements OuterAdapter {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void sync(List<Dml> dmls) {
|
||||||
|
for (Dml dml : dmls) {
|
||||||
|
sync(dml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void sync(Dml dml) {
|
public void sync(Dml dml) {
|
||||||
logger.info(dml.toString());
|
logger.info("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal.client-adapter</artifactId>
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
<version>1.1.1</version>
|
<version>1.1.2</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>canal client adapter module for otter ${project.version}</name>
|
<name>canal client adapter module for otter ${project.version}</name>
|
||||||
|
|
||||||
@@ -14,16 +14,30 @@
|
|||||||
<java_source_version>1.8</java_source_version>
|
<java_source_version>1.8</java_source_version>
|
||||||
<java_target_version>1.8</java_target_version>
|
<java_target_version>1.8</java_target_version>
|
||||||
<file_encoding>UTF-8</file_encoding>
|
<file_encoding>UTF-8</file_encoding>
|
||||||
<canal_version>1.1.1-SNAPSHOT</canal_version>
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>common</module>
|
<module>common</module>
|
||||||
<module>logger</module>
|
<module>logger</module>
|
||||||
<module>hbase</module>
|
<module>hbase</module>
|
||||||
|
<module>elasticsearch</module>
|
||||||
<module>launcher</module>
|
<module>launcher</module>
|
||||||
|
<module>rdb</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>central</id>
|
||||||
|
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|||||||
97
client-adapter/rdb/pom.xml
Normal file
97
client-adapter/rdb/pom.xml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<?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.2</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.rdb</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>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
<version>1.19</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>42.1.4</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.oracle</groupId>
|
||||||
|
<artifactId>ojdbc6</artifactId>
|
||||||
|
<version>11.2.0.3</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.microsoft.sqlserver</groupId>
|
||||||
|
<artifactId>mssql-jdbc</artifactId>
|
||||||
|
<version>7.0.0.jre8</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.12</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<version>2.4</version>
|
||||||
|
<configuration>
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>make-assembly</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-antrun-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>run</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<tasks>
|
||||||
|
<copy todir="${project.basedir}/../launcher/target/classes/rdb" overwrite="true">
|
||||||
|
<fileset dir="${project.basedir}/target/classes/rdb" erroronmissingdir="true">
|
||||||
|
<include name="*.yml" />
|
||||||
|
</fileset>
|
||||||
|
</copy>
|
||||||
|
</tasks>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.config.ConfigLoader;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.monitor.RdbConfigMonitor;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.service.RdbEtlService;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.*;
|
||||||
|
|
||||||
|
@SPI("rdb")
|
||||||
|
public class RdbAdapter implements OuterAdapter {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class);
|
||||||
|
|
||||||
|
private Map<String, MappingConfig> rdbMapping = new HashMap<>(); // 文件名对应配置
|
||||||
|
private Map<String, Map<String, MappingConfig>> mappingConfigCache = new HashMap<>(); // 库名-表名对应配置
|
||||||
|
|
||||||
|
private DruidDataSource dataSource;
|
||||||
|
|
||||||
|
private RdbSyncService rdbSyncService;
|
||||||
|
|
||||||
|
private ExecutorService executor = Executors.newFixedThreadPool(1);
|
||||||
|
|
||||||
|
private RdbConfigMonitor rdbConfigMonitor;
|
||||||
|
|
||||||
|
public Map<String, MappingConfig> getRdbMapping() {
|
||||||
|
return rdbMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Map<String, MappingConfig>> getMappingConfigCache() {
|
||||||
|
return mappingConfigCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(OuterAdapterConfig configuration) {
|
||||||
|
Map<String, MappingConfig> rdbMappingTmp = ConfigLoader.load();
|
||||||
|
// 过滤不匹配的key的配置
|
||||||
|
rdbMappingTmp.forEach((key, mappingConfig) -> {
|
||||||
|
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|
||||||
|
|| (mappingConfig.getOuterAdapterKey() != null
|
||||||
|
&& mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) {
|
||||||
|
rdbMapping.put(key, mappingConfig);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (Map.Entry<String, MappingConfig> entry : rdbMapping.entrySet()) {
|
||||||
|
String configName = entry.getKey();
|
||||||
|
MappingConfig mappingConfig = entry.getValue();
|
||||||
|
Map<String, MappingConfig> configMap = mappingConfigCache
|
||||||
|
.computeIfAbsent(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
|
||||||
|
+ mappingConfig.getDbMapping().getDatabase() + "."
|
||||||
|
+ mappingConfig.getDbMapping().getTable(),
|
||||||
|
k1 -> new HashMap<>());
|
||||||
|
configMap.put(configName, mappingConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> properties = configuration.getProperties();
|
||||||
|
dataSource = new DruidDataSource();
|
||||||
|
dataSource.setDriverClassName(properties.get("jdbc.driverClassName"));
|
||||||
|
dataSource.setUrl(properties.get("jdbc.url"));
|
||||||
|
dataSource.setUsername(properties.get("jdbc.username"));
|
||||||
|
dataSource.setPassword(properties.get("jdbc.password"));
|
||||||
|
dataSource.setInitialSize(1);
|
||||||
|
dataSource.setMinIdle(1);
|
||||||
|
dataSource.setMaxActive(20);
|
||||||
|
dataSource.setMaxWait(60000);
|
||||||
|
dataSource.setTimeBetweenEvictionRunsMillis(60000);
|
||||||
|
dataSource.setMinEvictableIdleTimeMillis(300000);
|
||||||
|
|
||||||
|
try {
|
||||||
|
dataSource.init();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error("ERROR ## failed to initial datasource: " + properties.get("jdbc.url"), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
String threads = properties.get("threads");
|
||||||
|
// String commitSize = properties.get("commitSize");
|
||||||
|
|
||||||
|
rdbSyncService = new RdbSyncService(mappingConfigCache,
|
||||||
|
dataSource,
|
||||||
|
threads != null ? Integer.valueOf(threads) : null);
|
||||||
|
|
||||||
|
rdbConfigMonitor = new RdbConfigMonitor();
|
||||||
|
rdbConfigMonitor.init(configuration.getKey(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sync(List<Dml> dmls) {
|
||||||
|
rdbSyncService.sync(dmls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EtlResult etl(String task, List<String> params) {
|
||||||
|
EtlResult etlResult = new EtlResult();
|
||||||
|
MappingConfig config = rdbMapping.get(task);
|
||||||
|
if (config != null) {
|
||||||
|
DataSource srcDataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
|
||||||
|
if (srcDataSource != null) {
|
||||||
|
return RdbEtlService.importData(srcDataSource, dataSource, 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 : rdbMapping.values()) {
|
||||||
|
// 取所有的destination为task的配置
|
||||||
|
if (configTmp.getDestination().equals(task)) {
|
||||||
|
DataSource srcDataSource = DatasourceConfig.DATA_SOURCES.get(configTmp.getDataSourceKey());
|
||||||
|
if (srcDataSource == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
EtlResult etlRes = RdbEtlService.importData(srcDataSource, dataSource, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> count(String task) {
|
||||||
|
MappingConfig config = rdbMapping.get(task);
|
||||||
|
MappingConfig.DbMapping dbMapping = config.getDbMapping();
|
||||||
|
String sql = "SELECT COUNT(1) AS cnt FROM " + dbMapping.getTargetTable();
|
||||||
|
Connection conn = null;
|
||||||
|
Map<String, Object> res = new LinkedHashMap<>();
|
||||||
|
try {
|
||||||
|
conn = dataSource.getConnection();
|
||||||
|
Util.sqlRS(conn, sql, rs -> {
|
||||||
|
try {
|
||||||
|
if (rs.next()) {
|
||||||
|
Long rowCount = rs.getLong("cnt");
|
||||||
|
res.put("count", rowCount);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
} finally {
|
||||||
|
if (conn != null) {
|
||||||
|
try {
|
||||||
|
conn.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.put("targetTable", dbMapping.getTargetTable());
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDestination(String task) {
|
||||||
|
MappingConfig config = rdbMapping.get(task);
|
||||||
|
if (config != null) {
|
||||||
|
return config.getDestination();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
if (rdbConfigMonitor != null) {
|
||||||
|
rdbConfigMonitor.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rdbSyncService != null) {
|
||||||
|
rdbSyncService.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
|
|
||||||
|
if (dataSource != null) {
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb.config;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RDB表映射配置加载器
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-11-07 下午02:41:34
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class ConfigLoader {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(ConfigLoader.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载HBase表映射配置
|
||||||
|
*
|
||||||
|
* @return 配置名/配置文件名--对象
|
||||||
|
*/
|
||||||
|
public static Map<String, MappingConfig> load() {
|
||||||
|
logger.info("## Start loading rdb mapping config ... ");
|
||||||
|
|
||||||
|
Map<String, MappingConfig> result = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("rdb");
|
||||||
|
configContentMap.forEach((fileName, content) -> {
|
||||||
|
MappingConfig config = new Yaml().loadAs(content, MappingConfig.class);
|
||||||
|
try {
|
||||||
|
config.validate();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("ERROR Config: " + fileName + " " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
result.put(fileName, config);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info("## Rdb mapping config loaded");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb.config;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RDB表映射配置
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-11-07 下午02:41:34
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class MappingConfig {
|
||||||
|
|
||||||
|
private String dataSourceKey; // 数据源key
|
||||||
|
|
||||||
|
private String destination; // canal实例或MQ的topic
|
||||||
|
|
||||||
|
private String outerAdapterKey; // 对应适配器的key
|
||||||
|
|
||||||
|
private Boolean concurrent; // 是否并行同步
|
||||||
|
|
||||||
|
private DbMapping dbMapping; // db映射配置
|
||||||
|
|
||||||
|
public String getDataSourceKey() {
|
||||||
|
return dataSourceKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDataSourceKey(String dataSourceKey) {
|
||||||
|
this.dataSourceKey = dataSourceKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOuterAdapterKey() {
|
||||||
|
return outerAdapterKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOuterAdapterKey(String outerAdapterKey) {
|
||||||
|
this.outerAdapterKey = outerAdapterKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getConcurrent() {
|
||||||
|
return concurrent == null ? false : 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 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; // 目标表主键字段
|
||||||
|
private boolean mapAll = false; // 映射所有字段
|
||||||
|
private String targetTable; // 目标表名
|
||||||
|
private Map<String, String> targetColumns; // 目标表字段映射
|
||||||
|
|
||||||
|
private String etlCondition; // etl条件sql
|
||||||
|
|
||||||
|
private Set<String> families = new LinkedHashSet<>(); // column family列表
|
||||||
|
private int readBatch = 5000;
|
||||||
|
private int commitBatch = 5000; // etl等批量提交大小
|
||||||
|
|
||||||
|
// private volatile Map<String, String> allColumns; // mapAll为true,自动设置改字段
|
||||||
|
|
||||||
|
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 isMapAll() {
|
||||||
|
return mapAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMapAll(boolean mapAll) {
|
||||||
|
this.mapAll = mapAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 String getEtlCondition() {
|
||||||
|
return etlCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEtlCondition(String etlCondition) {
|
||||||
|
this.etlCondition = etlCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getFamilies() {
|
||||||
|
return families;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFamilies(Set<String> families) {
|
||||||
|
this.families = families;
|
||||||
|
}
|
||||||
|
|
||||||
|
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> getAllColumns() {
|
||||||
|
// return allColumns;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public void setAllColumns(Map<String, String> allColumns) {
|
||||||
|
// this.allColumns = allColumns;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb.monitor;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
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 org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
|
|
||||||
|
public class RdbConfigMonitor {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RdbConfigMonitor.class);
|
||||||
|
|
||||||
|
private static final String adapterName = "rdb";
|
||||||
|
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
private RdbAdapter rdbAdapter;
|
||||||
|
|
||||||
|
private FileAlterationMonitor fileMonitor;
|
||||||
|
|
||||||
|
public void init(String key, RdbAdapter rdbAdapter) {
|
||||||
|
this.key = key;
|
||||||
|
this.rdbAdapter = rdbAdapter;
|
||||||
|
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 = new Yaml().loadAs(configContent, MappingConfig.class);
|
||||||
|
config.validate();
|
||||||
|
if ((key == null && config.getOuterAdapterKey() == null)
|
||||||
|
|| (key != null && key.equals(config.getOuterAdapterKey()))) {
|
||||||
|
addConfigToCache(file, config);
|
||||||
|
|
||||||
|
logger.info("Add a new rdb mapping config: {} to canal adapter", file.getName());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFileChange(File file) {
|
||||||
|
super.onFileChange(file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (rdbAdapter.getRdbMapping().containsKey(file.getName())) {
|
||||||
|
// 加载配置文件
|
||||||
|
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
|
||||||
|
MappingConfig config = new Yaml().loadAs(configContent, MappingConfig.class);
|
||||||
|
config.validate();
|
||||||
|
if ((key == null && config.getOuterAdapterKey() == null)
|
||||||
|
|| (key != null && key.equals(config.getOuterAdapterKey()))) {
|
||||||
|
if (rdbAdapter.getRdbMapping().containsKey(file.getName())) {
|
||||||
|
deleteConfigFromCache(file);
|
||||||
|
}
|
||||||
|
addConfigToCache(file, config);
|
||||||
|
} else {
|
||||||
|
// 不能修改outerAdapterKey
|
||||||
|
throw new RuntimeException("Outer adapter key not allowed modify");
|
||||||
|
}
|
||||||
|
logger.info("Change a rdb 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 (rdbAdapter.getRdbMapping().containsKey(file.getName())) {
|
||||||
|
deleteConfigFromCache(file);
|
||||||
|
|
||||||
|
logger.info("Delete a rdb mapping config: {} of canal adapter", file.getName());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addConfigToCache(File file, MappingConfig config) {
|
||||||
|
rdbAdapter.getRdbMapping().put(file.getName(), config);
|
||||||
|
Map<String, MappingConfig> configMap = rdbAdapter.getMappingConfigCache()
|
||||||
|
.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "."
|
||||||
|
+ config.getDbMapping().getDatabase() + "." + config.getDbMapping().getTable(),
|
||||||
|
k1 -> new HashMap<>());
|
||||||
|
configMap.put(file.getName(), config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteConfigFromCache(File file) {
|
||||||
|
|
||||||
|
rdbAdapter.getRdbMapping().remove(file.getName());
|
||||||
|
for (Map<String, MappingConfig> configMap : rdbAdapter.getMappingConfigCache().values()) {
|
||||||
|
if (configMap != null) {
|
||||||
|
configMap.remove(file.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb.service;
|
||||||
|
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RDB ETL 操作业务类
|
||||||
|
*
|
||||||
|
* @author rewerma @ 2018-11-7
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class RdbEtlService {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RdbEtlService.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入数据
|
||||||
|
*/
|
||||||
|
public static EtlResult importData(DataSource srcDS, DataSource targetDS, 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;
|
||||||
|
}
|
||||||
|
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 = Executors.newFixedThreadPool(threadCount);
|
||||||
|
List<Future<Boolean>> futures = new ArrayList<>(threadCount);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
Future<Boolean> future = executor
|
||||||
|
.submit(() -> executeSqlImport(srcDS, targetDS, sqlFinal, dbMapping, successCount, errMsg));
|
||||||
|
futures.add(future);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Future<Boolean> future : futures) {
|
||||||
|
future.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
|
} else {
|
||||||
|
executeSqlImport(srcDS, targetDS, sql.toString(), dbMapping, successCount, errMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
dbMapping.getTable() + " etl completed in: " + (System.currentTimeMillis() - start) / 1000 + "s!");
|
||||||
|
|
||||||
|
etlResult.setResultMessage("导入目标表 " + dbMapping.getTargetTable() + " 数据:" + 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) throws SQLException {
|
||||||
|
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) {
|
||||||
|
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, DataSource targetDS, String sql, DbMapping dbMapping,
|
||||||
|
AtomicLong successCount, List<String> errMsg) {
|
||||||
|
try {
|
||||||
|
Util.sqlRS(srcDS, sql, rs -> {
|
||||||
|
int idx = 1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean completed = false;
|
||||||
|
|
||||||
|
Map<String, Integer> columnType = new LinkedHashMap<>();
|
||||||
|
ResultSetMetaData rsd = rs.getMetaData();
|
||||||
|
int columnCount = rsd.getColumnCount();
|
||||||
|
List<String> columns = new ArrayList<>();
|
||||||
|
for (int i = 1; i <= columnCount; i++) {
|
||||||
|
columnType.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i));
|
||||||
|
columns.add(rsd.getColumnName(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, columns);
|
||||||
|
// if (dbMapping.isMapAll()) {
|
||||||
|
// columnsMap = dbMapping.getAllColumns();
|
||||||
|
// } else {
|
||||||
|
// columnsMap = dbMapping.getTargetColumns();
|
||||||
|
// }
|
||||||
|
|
||||||
|
StringBuilder insertSql = new StringBuilder();
|
||||||
|
insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" (");
|
||||||
|
columnsMap
|
||||||
|
.forEach((targetColumnName, srcColumnName) -> insertSql.append(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();
|
||||||
|
PreparedStatement pstmt = connTarget.prepareStatement(insertSql.toString())) {
|
||||||
|
connTarget.setAutoCommit(false);
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
pstmt.clearParameters();
|
||||||
|
|
||||||
|
// 删除数据
|
||||||
|
Map<String, Object> values = new LinkedHashMap<>();
|
||||||
|
StringBuilder deleteSql = new StringBuilder(
|
||||||
|
"DELETE FROM " + dbMapping.getTargetTable() + " 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
for (Map.Entry<String, String> entry : columnsMap.entrySet()) {
|
||||||
|
String targetClolumnName = entry.getKey();
|
||||||
|
String srcColumnName = entry.getValue();
|
||||||
|
if (srcColumnName == null) {
|
||||||
|
srcColumnName = targetClolumnName;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer type = columnType.get(targetClolumnName.toLowerCase());
|
||||||
|
|
||||||
|
Object value = rs.getObject(srcColumnName);
|
||||||
|
if (value != null) {
|
||||||
|
SyncUtil.setPStmt(type, pstmt, value, i);
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
pstmt.execute();
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Insert into target table, sql: {}", insertSql);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx % dbMapping.getCommitBatch() == 0) {
|
||||||
|
connTarget.commit();
|
||||||
|
completed = true;
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
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(targetColumnName).append("=? AND ");
|
||||||
|
values.put(targetColumnName, rs.getObject(srcColumnName));
|
||||||
|
}
|
||||||
|
int len = sql.length();
|
||||||
|
sql.delete(len - 4, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,409 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb.service;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSetMetaData;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.support.BatchExecutor;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.support.SingleDml;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RDB同步操作业务
|
||||||
|
*
|
||||||
|
* @author rewerma 2018-11-7 下午06:45:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class RdbSyncService {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RdbSyncService.class);
|
||||||
|
|
||||||
|
private final Map<String, Map<String, Integer>> COLUMNS_TYPE_CACHE = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private Map<String, Map<String, MappingConfig>> mappingConfigCache; // 库名-表名对应配置
|
||||||
|
|
||||||
|
private int threads = 3;
|
||||||
|
|
||||||
|
private List<SyncItem>[] dmlsPartition;
|
||||||
|
private BatchExecutor[] batchExecutors;
|
||||||
|
private ExecutorService[] executorThreads;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public RdbSyncService(Map<String, Map<String, MappingConfig>> mappingConfigCache, DataSource dataSource,
|
||||||
|
Integer threads){
|
||||||
|
try {
|
||||||
|
if (threads != null) {
|
||||||
|
this.threads = threads;
|
||||||
|
}
|
||||||
|
this.mappingConfigCache = mappingConfigCache;
|
||||||
|
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(dataSource.getConnection());
|
||||||
|
executorThreads[i] = Executors.newSingleThreadExecutor();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sync(List<Dml> dmls) {
|
||||||
|
try {
|
||||||
|
for (Dml dml : dmls) {
|
||||||
|
String destination = StringUtils.trimToEmpty(dml.getDestination());
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
Map<String, MappingConfig> configMap = mappingConfigCache
|
||||||
|
.get(destination + "." + database + "." + table);
|
||||||
|
|
||||||
|
for (MappingConfig config : configMap.values()) {
|
||||||
|
|
||||||
|
if (config.getConcurrent()) {
|
||||||
|
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml);
|
||||||
|
singleDmls.forEach(singleDml -> {
|
||||||
|
int hash = pkHash(config.getDbMapping(), singleDml.getData());
|
||||||
|
SyncItem syncItem = new SyncItem(config, singleDml);
|
||||||
|
dmlsPartition[hash].add(syncItem);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
int hash = Math.abs(Math.abs(config.getDbMapping().getTargetTable().hashCode()) % threads);
|
||||||
|
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml);
|
||||||
|
singleDmls.forEach(singleDml -> {
|
||||||
|
SyncItem syncItem = new SyncItem(config, singleDml);
|
||||||
|
dmlsPartition[hash].add(syncItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<Future> futures = new ArrayList<>();
|
||||||
|
for (int i = 0; i < threads; i++) {
|
||||||
|
int j = i;
|
||||||
|
futures.add(executorThreads[i].submit(() -> {
|
||||||
|
dmlsPartition[j].forEach(syncItem -> sync(batchExecutors[j], syncItem.config, syncItem.singleDml));
|
||||||
|
batchExecutors[j].commit();
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
futures.forEach(future -> {
|
||||||
|
try {
|
||||||
|
future.get();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int i = 0; i < threads; i++) {
|
||||||
|
dmlsPartition[i].clear();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sync(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) {
|
||||||
|
try {
|
||||||
|
if (config != null) {
|
||||||
|
String type = dml.getType();
|
||||||
|
if (type != null && type.equalsIgnoreCase("INSERT")) {
|
||||||
|
insert(batchExecutor, config, dml);
|
||||||
|
} else if (type != null && type.equalsIgnoreCase("UPDATE")) {
|
||||||
|
update(batchExecutor, config, dml);
|
||||||
|
} else if (type != null && type.equalsIgnoreCase("DELETE")) {
|
||||||
|
delete(batchExecutor, config, dml);
|
||||||
|
}
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入操作
|
||||||
|
*
|
||||||
|
* @param config 配置项
|
||||||
|
* @param dml DML数据
|
||||||
|
*/
|
||||||
|
private void insert(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) {
|
||||||
|
Map<String, Object> data = dml.getData();
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DbMapping dbMapping = config.getDbMapping();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data);
|
||||||
|
|
||||||
|
StringBuilder insertSql = new StringBuilder();
|
||||||
|
insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" (");
|
||||||
|
|
||||||
|
columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(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(")");
|
||||||
|
|
||||||
|
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
|
||||||
|
|
||||||
|
List<Map<String, ?>> values = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, String> entry : columnsMap.entrySet()) {
|
||||||
|
String targetClolumnName = entry.getKey();
|
||||||
|
String srcColumnName = entry.getValue();
|
||||||
|
if (srcColumnName == null) {
|
||||||
|
srcColumnName = targetClolumnName;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer type = ctype.get(targetClolumnName.toLowerCase());
|
||||||
|
|
||||||
|
Object value = data.get(srcColumnName);
|
||||||
|
|
||||||
|
BatchExecutor.setValue(values, type, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
batchExecutor.execute(insertSql.toString(), values);
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Insert into target table, sql: {}", insertSql);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新操作
|
||||||
|
*
|
||||||
|
* @param config 配置项
|
||||||
|
* @param dml DML数据
|
||||||
|
*/
|
||||||
|
private void update(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) {
|
||||||
|
Map<String, Object> data = dml.getData();
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> old = dml.getOld();
|
||||||
|
if (old == null || old.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DbMapping dbMapping = config.getDbMapping();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data);
|
||||||
|
|
||||||
|
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
|
||||||
|
|
||||||
|
StringBuilder updateSql = new StringBuilder();
|
||||||
|
updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET ");
|
||||||
|
List<Map<String, ?>> values = new ArrayList<>();
|
||||||
|
for (String srcColumnName : old.keySet()) {
|
||||||
|
List<String> targetColumnNames = new ArrayList<>();
|
||||||
|
columnsMap.forEach((targetColumn, srcColumn) -> {
|
||||||
|
if (srcColumnName.toLowerCase().equals(srcColumn)) {
|
||||||
|
targetColumnNames.add(targetColumn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!targetColumnNames.isEmpty()) {
|
||||||
|
|
||||||
|
for (String targetColumnName : targetColumnNames) {
|
||||||
|
updateSql.append(targetColumnName).append("=?, ");
|
||||||
|
Integer type = ctype.get(targetColumnName.toLowerCase());
|
||||||
|
BatchExecutor.setValue(values, type, data.get(srcColumnName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int len = updateSql.length();
|
||||||
|
updateSql.delete(len - 2, len).append(" WHERE ");
|
||||||
|
|
||||||
|
// 拼接主键
|
||||||
|
appendCondition(dbMapping, updateSql, ctype, values, data, old);
|
||||||
|
|
||||||
|
batchExecutor.execute(updateSql.toString(), values);
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Update target table, sql: {}", updateSql);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除操作
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* @param dml
|
||||||
|
*/
|
||||||
|
private void delete(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) {
|
||||||
|
Map<String, Object> data = dml.getData();
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DbMapping dbMapping = config.getDbMapping();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
|
||||||
|
|
||||||
|
StringBuilder sql = new StringBuilder();
|
||||||
|
sql.append("DELETE FROM ").append(dbMapping.getTargetTable()).append(" WHERE ");
|
||||||
|
|
||||||
|
List<Map<String, ?>> values = new ArrayList<>();
|
||||||
|
// 拼接主键
|
||||||
|
appendCondition(dbMapping, sql, ctype, values, data);
|
||||||
|
|
||||||
|
batchExecutor.execute(sql.toString(), values);
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Delete from target table, sql: {}", sql);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取目标字段类型
|
||||||
|
*
|
||||||
|
* @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 = COLUMNS_TYPE_CACHE.get(cacheKey);
|
||||||
|
if (columnType == null) {
|
||||||
|
synchronized (RdbSyncService.class) {
|
||||||
|
columnType = COLUMNS_TYPE_CACHE.get(cacheKey);
|
||||||
|
if (columnType == null) {
|
||||||
|
columnType = new LinkedHashMap<>();
|
||||||
|
final Map<String, Integer> columnTypeTmp = columnType;
|
||||||
|
String sql = "SELECT * FROM " + dbMapping.getTargetTable() + " WHERE 1=2";
|
||||||
|
Util.sqlRS(conn, sql, rs -> {
|
||||||
|
try {
|
||||||
|
ResultSetMetaData rsd = rs.getMetaData();
|
||||||
|
int columnCount = rsd.getColumnCount();
|
||||||
|
for (int i = 1; i <= columnCount; i++) {
|
||||||
|
columnTypeTmp.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i));
|
||||||
|
}
|
||||||
|
COLUMNS_TYPE_CACHE.put(cacheKey, columnTypeTmp);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return columnType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拼接主键 where条件
|
||||||
|
*/
|
||||||
|
private void appendCondition(MappingConfig.DbMapping dbMapping, StringBuilder sql, Map<String, Integer> ctype,
|
||||||
|
List<Map<String, ?>> values, Map<String, Object> d) {
|
||||||
|
appendCondition(dbMapping, sql, ctype, values, d, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendCondition(MappingConfig.DbMapping dbMapping, StringBuilder sql, Map<String, Integer> ctype,
|
||||||
|
List<Map<String, ?>> values, Map<String, Object> d, Map<String, Object> o) {
|
||||||
|
// 拼接主键
|
||||||
|
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) {
|
||||||
|
String targetColumnName = entry.getKey();
|
||||||
|
String srcColumnName = entry.getValue();
|
||||||
|
if (srcColumnName == null) {
|
||||||
|
srcColumnName = targetColumnName;
|
||||||
|
}
|
||||||
|
sql.append(targetColumnName).append("=? AND ");
|
||||||
|
Integer type = ctype.get(targetColumnName.toLowerCase());
|
||||||
|
// 如果有修改主键的情况
|
||||||
|
if (o != null && o.containsKey(srcColumnName)) {
|
||||||
|
BatchExecutor.setValue(values, type, o.get(srcColumnName));
|
||||||
|
} else {
|
||||||
|
BatchExecutor.setValue(values, type, d.get(srcColumnName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int len = sql.length();
|
||||||
|
sql.delete(len - 4, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SyncItem {
|
||||||
|
|
||||||
|
private MappingConfig config;
|
||||||
|
private SingleDml singleDml;
|
||||||
|
|
||||||
|
private SyncItem(MappingConfig config, SingleDml singleDml){
|
||||||
|
this.config = config;
|
||||||
|
this.singleDml = singleDml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取主键hash
|
||||||
|
*/
|
||||||
|
private int pkHash(DbMapping dbMapping, Map<String, Object> d) {
|
||||||
|
return pkHash(dbMapping, d, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int pkHash(DbMapping dbMapping, Map<String, Object> d, Map<String, Object> o) {
|
||||||
|
int hash = 0;
|
||||||
|
// 取主键
|
||||||
|
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) {
|
||||||
|
String targetColumnName = entry.getKey();
|
||||||
|
String srcColumnName = entry.getValue();
|
||||||
|
if (srcColumnName == null) {
|
||||||
|
srcColumnName = targetColumnName;
|
||||||
|
}
|
||||||
|
Object value;
|
||||||
|
if (o != null && o.containsKey(srcColumnName)) {
|
||||||
|
value = o.get(srcColumnName);
|
||||||
|
} else {
|
||||||
|
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++) {
|
||||||
|
batchExecutors[i].close();
|
||||||
|
executorThreads[i].shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb.support;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class BatchExecutor implements Closeable {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(BatchExecutor.class);
|
||||||
|
|
||||||
|
private Integer key;
|
||||||
|
private Connection conn;
|
||||||
|
private AtomicInteger idx = new AtomicInteger(0);
|
||||||
|
|
||||||
|
public BatchExecutor(Connection conn){
|
||||||
|
this(1, conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BatchExecutor(Integer key, Connection conn){
|
||||||
|
this.key = key;
|
||||||
|
this.conn = conn;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.conn.setAutoCommit(false);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Connection getConn() {
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setValue(List<Map<String, ?>> values, int type, Object value) {
|
||||||
|
Map<String, Object> valueItem = new HashMap<>();
|
||||||
|
valueItem.put("type", type);
|
||||||
|
valueItem.put("value", value);
|
||||||
|
values.add(valueItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute(String sql, List<Map<String, ?>> values) {
|
||||||
|
try (PreparedStatement pstmt = conn.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();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void commit() {
|
||||||
|
try {
|
||||||
|
conn.commit();
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Batch executor: " + key + " commit " + idx.get() + " rows");
|
||||||
|
}
|
||||||
|
idx.set(0);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (conn != null) {
|
||||||
|
try {
|
||||||
|
conn.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb.support;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
int size = dml.getData().size();
|
||||||
|
List<SingleDml> singleDmls = new ArrayList<>(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);
|
||||||
|
}
|
||||||
|
return singleDmls;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb.support;
|
||||||
|
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
|
||||||
|
|
||||||
|
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.isMapAll()) {
|
||||||
|
columnsMap = new LinkedHashMap<>();
|
||||||
|
for (String srcColumn : columns) {
|
||||||
|
boolean flag = true;
|
||||||
|
if (dbMapping.getTargetColumns() != null) {
|
||||||
|
for (Map.Entry<String, String> entry : dbMapping.getTargetColumns().entrySet()) {
|
||||||
|
if (srcColumn.equals(entry.getValue())) {
|
||||||
|
columnsMap.put(entry.getKey(), srcColumn);
|
||||||
|
flag = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (flag) {
|
||||||
|
columnsMap.put(srcColumn, srcColumn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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 {
|
||||||
|
pstmt.setString(i, value.toString());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.TINYINT:
|
||||||
|
if (value instanceof Byte || value instanceof Short || value instanceof Integer) {
|
||||||
|
pstmt.setByte(i, (byte) value);
|
||||||
|
} else 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 Byte || value instanceof Short || value instanceof Integer) {
|
||||||
|
pstmt.setShort(i, (short) value);
|
||||||
|
} else 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 Byte || value instanceof Short || value instanceof Integer
|
||||||
|
|| value instanceof Long) {
|
||||||
|
pstmt.setInt(i, (int) value);
|
||||||
|
} else 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 Byte || value instanceof Short || value instanceof Integer
|
||||||
|
|| value instanceof Long) {
|
||||||
|
pstmt.setLong(i, (long) value);
|
||||||
|
} else 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, (int) value);
|
||||||
|
} else if (value instanceof Short) {
|
||||||
|
pstmt.setInt(i, (int) value);
|
||||||
|
} else if (value instanceof Integer) {
|
||||||
|
pstmt.setInt(i, (int) 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 {
|
||||||
|
pstmt.setBigDecimal(i, new BigDecimal(value.toString()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.REAL:
|
||||||
|
if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long
|
||||||
|
|| value instanceof Float || value instanceof Double) {
|
||||||
|
pstmt.setFloat(i, (float) value);
|
||||||
|
} else 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 Byte || value instanceof Short || value instanceof Integer || value instanceof Long
|
||||||
|
|| value instanceof Float || value instanceof Double) {
|
||||||
|
pstmt.setDouble(i, (double) value);
|
||||||
|
} else 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 java.sql.Date) {
|
||||||
|
pstmt.setDate(i, (java.sql.Date) value);
|
||||||
|
} else if (value instanceof java.util.Date) {
|
||||||
|
pstmt.setDate(i, new java.sql.Date(((java.util.Date) value).getTime()));
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
String v = (String) value;
|
||||||
|
if (!v.startsWith("0000-00-00")) {
|
||||||
|
v = v.trim().replace(" ", "T");
|
||||||
|
DateTime dt = new DateTime(v);
|
||||||
|
pstmt.setDate(i, new Date(dt.toDate().getTime()));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.TIME:
|
||||||
|
if (value instanceof java.sql.Time) {
|
||||||
|
pstmt.setTime(i, (java.sql.Time) value);
|
||||||
|
} else if (value instanceof java.util.Date) {
|
||||||
|
pstmt.setTime(i, new java.sql.Time(((java.util.Date) value).getTime()));
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
String v = (String) value;
|
||||||
|
v = "T" + v;
|
||||||
|
DateTime dt = new DateTime(v);
|
||||||
|
pstmt.setTime(i, new Time(dt.toDate().getTime()));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.TIMESTAMP:
|
||||||
|
if (value instanceof java.sql.Timestamp) {
|
||||||
|
pstmt.setTimestamp(i, (java.sql.Timestamp) value);
|
||||||
|
} else if (value instanceof java.util.Date) {
|
||||||
|
pstmt.setTimestamp(i, new java.sql.Timestamp(((java.util.Date) value).getTime()));
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
String v = (String) value;
|
||||||
|
if (!v.startsWith("0000-00-00")) {
|
||||||
|
v = v.trim().replace(" ", "T");
|
||||||
|
DateTime dt = new DateTime(v);
|
||||||
|
pstmt.setTimestamp(i, new Timestamp(dt.toDate().getTime()));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(i, type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pstmt.setObject(i, value, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rdb=com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter
|
||||||
17
client-adapter/rdb/src/main/resources/rdb/mytest_user.yml
Normal file
17
client-adapter/rdb/src/main/resources/rdb/mytest_user.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
dataSourceKey: defaultDS
|
||||||
|
destination: example
|
||||||
|
outerAdapterKey: oracle1
|
||||||
|
concurrent: true
|
||||||
|
dbMapping:
|
||||||
|
database: mytest
|
||||||
|
table: user
|
||||||
|
targetTable: mytest.tb_user
|
||||||
|
targetPk:
|
||||||
|
id: id
|
||||||
|
mapAll: true
|
||||||
|
# targetColumns:
|
||||||
|
# id:
|
||||||
|
# name:
|
||||||
|
# role_id:
|
||||||
|
# c_time:
|
||||||
|
# test1:
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb.test;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.config.ConfigLoader;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
|
||||||
|
public class ConfigLoadTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
// 加载数据源连接池
|
||||||
|
DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoad() {
|
||||||
|
Map<String, MappingConfig> configMap = ConfigLoader.load();
|
||||||
|
|
||||||
|
Assert.assertFalse(configMap.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb.test;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.sql.Clob;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
|
|
||||||
|
public class DBTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test01() throws SQLException {
|
||||||
|
DruidDataSource dataSource = new DruidDataSource();
|
||||||
|
// dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
|
||||||
|
// dataSource.setUrl("jdbc:oracle:thin:@127.0.0.1:49161:XE");
|
||||||
|
// dataSource.setUsername("mytest");
|
||||||
|
// dataSource.setPassword("m121212");
|
||||||
|
|
||||||
|
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
|
||||||
|
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true");
|
||||||
|
dataSource.setUsername("root");
|
||||||
|
dataSource.setPassword("121212");
|
||||||
|
|
||||||
|
dataSource.setInitialSize(1);
|
||||||
|
dataSource.setMinIdle(1);
|
||||||
|
dataSource.setMaxActive(2);
|
||||||
|
dataSource.setMaxWait(60000);
|
||||||
|
dataSource.setTimeBetweenEvictionRunsMillis(60000);
|
||||||
|
dataSource.setMinEvictableIdleTimeMillis(300000);
|
||||||
|
|
||||||
|
dataSource.init();
|
||||||
|
|
||||||
|
Connection conn = dataSource.getConnection();
|
||||||
|
|
||||||
|
conn.setAutoCommit(false);
|
||||||
|
PreparedStatement pstmt = conn
|
||||||
|
.prepareStatement("insert into user (id,name,role_id,c_time,test1,test2) values (?,?,?,?,?,?)");
|
||||||
|
|
||||||
|
java.util.Date now = new java.util.Date();
|
||||||
|
for (int i = 1; i <= 100000; i++) {
|
||||||
|
pstmt.clearParameters();
|
||||||
|
pstmt.setLong(1, (long) i);
|
||||||
|
pstmt.setString(2, "test_" + i);
|
||||||
|
pstmt.setLong(3, (long) i % 4 + 1);
|
||||||
|
pstmt.setDate(4, new java.sql.Date(now.getTime()));
|
||||||
|
pstmt.setString(5, null);
|
||||||
|
pstmt.setBytes(6, null);
|
||||||
|
|
||||||
|
pstmt.execute();
|
||||||
|
if (i % 5000 == 0) {
|
||||||
|
conn.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn.commit();
|
||||||
|
|
||||||
|
pstmt.close();
|
||||||
|
|
||||||
|
// Statement stmt = conn.createStatement();
|
||||||
|
// ResultSet rs = stmt.executeQuery("select * from user t where 1=2");
|
||||||
|
//
|
||||||
|
// ResultSetMetaData rsm = rs.getMetaData();
|
||||||
|
// int cnt = rsm.getColumnCount();
|
||||||
|
// for (int i = 1; i <= cnt; i++) {
|
||||||
|
// System.out.println(rsm.getColumnName(i) + " " + rsm.getColumnType(i));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// rs.close();
|
||||||
|
// stmt.close();
|
||||||
|
|
||||||
|
// PreparedStatement pstmt = conn
|
||||||
|
// .prepareStatement("insert into tb_user (id,name,role_id,c_time,test1,test2)
|
||||||
|
// values (?,?,?,?,?,?)");
|
||||||
|
// pstmt.setBigDecimal(1, new BigDecimal("5"));
|
||||||
|
// pstmt.setString(2, "test");
|
||||||
|
// pstmt.setBigDecimal(3, new BigDecimal("1"));
|
||||||
|
// pstmt.setDate(4, new Date(new java.util.Date().getTime()));
|
||||||
|
// byte[] a = { (byte) 1, (byte) 2 };
|
||||||
|
// pstmt.setBytes(5, a);
|
||||||
|
// pstmt.setBytes(6, a);
|
||||||
|
// pstmt.execute();
|
||||||
|
//
|
||||||
|
// pstmt.close();
|
||||||
|
|
||||||
|
conn.close();
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String clob2Str(Clob clob) {
|
||||||
|
String content = "";
|
||||||
|
try {
|
||||||
|
Reader is = clob.getCharacterStream();
|
||||||
|
BufferedReader buff = new BufferedReader(is);
|
||||||
|
String line = buff.readLine();
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
while (line != null) {
|
||||||
|
sb.append(line);
|
||||||
|
line = buff.readLine();
|
||||||
|
}
|
||||||
|
content = sb.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb.test;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
|
|
||||||
|
public class TestConstant {
|
||||||
|
|
||||||
|
public final static String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true";
|
||||||
|
public final static String jdbcUser = "root";
|
||||||
|
public final static String jdbcPassword = "121212";
|
||||||
|
|
||||||
|
public static DruidDataSource dataSource;
|
||||||
|
|
||||||
|
static {
|
||||||
|
dataSource = new DruidDataSource();
|
||||||
|
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
|
||||||
|
dataSource.setUrl(jdbcUrl);
|
||||||
|
dataSource.setUsername(jdbcUser);
|
||||||
|
dataSource.setPassword(jdbcPassword);
|
||||||
|
dataSource.setInitialSize(1);
|
||||||
|
dataSource.setMinIdle(1);
|
||||||
|
dataSource.setMaxActive(1);
|
||||||
|
dataSource.setMaxWait(60000);
|
||||||
|
dataSource.setTimeBetweenEvictionRunsMillis(60000);
|
||||||
|
dataSource.setMinEvictableIdleTimeMillis(300000);
|
||||||
|
dataSource.setPoolPreparedStatements(false);
|
||||||
|
dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
|
||||||
|
dataSource.setValidationQuery("select 1");
|
||||||
|
try {
|
||||||
|
dataSource.init();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb.test.sync;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.test.TestConstant;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
|
||||||
|
|
||||||
|
public class Common {
|
||||||
|
|
||||||
|
public static RdbAdapter init() {
|
||||||
|
DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource);
|
||||||
|
|
||||||
|
OuterAdapterConfig outerAdapterConfig = new OuterAdapterConfig();
|
||||||
|
outerAdapterConfig.setName("rdb");
|
||||||
|
outerAdapterConfig.setKey("oracle1");
|
||||||
|
Map<String, String> properties = new HashMap<>();
|
||||||
|
properties.put("jdbc.driveClassName", "oracle.jdbc.OracleDriver");
|
||||||
|
properties.put("jdbc.url", "jdbc:oracle:thin:@127.0.0.1:49161:XE");
|
||||||
|
properties.put("jdbc.username", "mytest");
|
||||||
|
properties.put("jdbc.password", "m121212");
|
||||||
|
outerAdapterConfig.setProperties(properties);
|
||||||
|
|
||||||
|
RdbAdapter adapter = new RdbAdapter();
|
||||||
|
adapter.init(outerAdapterConfig);
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.rdb.test.sync;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
public class OracleSyncTest {
|
||||||
|
|
||||||
|
private RdbAdapter rdbAdapter;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
rdbAdapter = Common.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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", 1L);
|
||||||
|
data.put("name", "Eric");
|
||||||
|
data.put("role_id", 1L);
|
||||||
|
data.put("c_time", new Date());
|
||||||
|
data.put("test1", "sdfasdfawe中国asfwef");
|
||||||
|
dml.setData(dataList);
|
||||||
|
|
||||||
|
rdbAdapter.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", 1L);
|
||||||
|
data.put("name", "Eric2");
|
||||||
|
dml.setData(dataList);
|
||||||
|
List<Map<String, Object>> oldList = new ArrayList<>();
|
||||||
|
Map<String, Object> old = new LinkedHashMap<>();
|
||||||
|
oldList.add(old);
|
||||||
|
old.put("name", "Eric");
|
||||||
|
dml.setOld(oldList);
|
||||||
|
|
||||||
|
rdbAdapter.sync(Collections.singletonList(dml));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
client-adapter/rdb/src/test/resources/log4j2-test.xml
Normal file
13
client-adapter/rdb/src/test/resources/log4j2-test.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration status="WARN">
|
||||||
|
<Appenders>
|
||||||
|
<Console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
|
||||||
|
</Console>
|
||||||
|
</Appenders>
|
||||||
|
<Loggers>
|
||||||
|
<Root level="ERROR">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Root>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
||||||
13
client-adapter/rdb/src/test/resources/logback-test.xml
Normal file
13
client-adapter/rdb/src/test/resources/logback-test.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<configuration scan="true" scanPeriod=" 5 seconds">
|
||||||
|
<jmxConfigurator />
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
|
||||||
|
</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="TRACE">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
17
client-adapter/rdb/src/test/resources/rdb/mytest_user.yml
Normal file
17
client-adapter/rdb/src/test/resources/rdb/mytest_user.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
dataSourceKey: defaultDS
|
||||||
|
destination: example
|
||||||
|
outerAdapterKey: oracle1
|
||||||
|
concurrent: true
|
||||||
|
dbMapping:
|
||||||
|
database: mytest
|
||||||
|
table: user
|
||||||
|
targetTable: mytest.tb_user
|
||||||
|
targetPk:
|
||||||
|
id: id
|
||||||
|
mapAll: true
|
||||||
|
# targetColumns:
|
||||||
|
# id:
|
||||||
|
# name:
|
||||||
|
# role_id:
|
||||||
|
# c_time:
|
||||||
|
# test1:
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.1.1</version>
|
<version>1.1.2</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -101,12 +101,9 @@
|
|||||||
<version>${spring_version}</version>
|
<version>${spring_version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- 客户端要使用请单独引入rocketmq-client依赖 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.rocketmq</groupId>
|
<groupId>org.apache.rocketmq</groupId>
|
||||||
<artifactId>rocketmq-client</artifactId>
|
<artifactId>rocketmq-client</artifactId>
|
||||||
<version>4.3.0</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- 客户端要使用请单独引入kafka-clients依赖 -->
|
<!-- 客户端要使用请单独引入kafka-clients依赖 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -122,6 +119,10 @@
|
|||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun.openservices</groupId>
|
||||||
|
<artifactId>aliware-apache-rocketmq-cloud</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -149,6 +150,7 @@
|
|||||||
<exclude>commons-logging:commons-logging</exclude>
|
<exclude>commons-logging:commons-logging</exclude>
|
||||||
<exclude>aopalliance:aopalliance</exclude>
|
<exclude>aopalliance:aopalliance</exclude>
|
||||||
<exclude>com.google.protobuf:protobuf-java</exclude>
|
<exclude>com.google.protobuf:protobuf-java</exclude>
|
||||||
|
<exclude>com.google.code.findbugs:jsr305</exclude>
|
||||||
<exclude>io.netty:*</exclude>
|
<exclude>io.netty:*</exclude>
|
||||||
<exclude>junit:junit</exclude>
|
<exclude>junit:junit</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
|
|||||||
@@ -32,10 +32,12 @@ public class CanalMessageDeserializer {
|
|||||||
if (lazyParseEntry) {
|
if (lazyParseEntry) {
|
||||||
// byteString
|
// byteString
|
||||||
result.setRawEntries(messages.getMessagesList());
|
result.setRawEntries(messages.getMessagesList());
|
||||||
|
result.setRaw(true);
|
||||||
} else {
|
} else {
|
||||||
for (ByteString byteString : messages.getMessagesList()) {
|
for (ByteString byteString : messages.getMessagesList()) {
|
||||||
result.addEntry(CanalEntry.Entry.parseFrom(byteString));
|
result.addEntry(CanalEntry.Entry.parseFrom(byteString));
|
||||||
}
|
}
|
||||||
|
result.setRaw(false);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
runningMonitor.stop();
|
runningMonitor.stop();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
doDisconnnect();
|
doDisconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doDisconnnect() throws CanalClientException {
|
private void doDisconnect() throws CanalClientException {
|
||||||
if (readableChannel != null) {
|
if (readableChannel != null) {
|
||||||
quietlyClose(readableChannel);
|
quietlyClose(readableChannel);
|
||||||
readableChannel = null;
|
readableChannel = null;
|
||||||
@@ -434,7 +434,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
public void processActiveExit() {
|
public void processActiveExit() {
|
||||||
mutex.set(false);
|
mutex.set(false);
|
||||||
doDisconnnect();
|
doDisconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
private volatile boolean running = false;
|
private volatile boolean running = false;
|
||||||
private boolean flatMessage;
|
private boolean flatMessage;
|
||||||
|
|
||||||
public KafkaCanalConnector(String servers, String topic, Integer partition, String groupId, boolean flatMessage){
|
public KafkaCanalConnector(String servers, String topic, Integer partition, String groupId, Integer batchSize,
|
||||||
|
boolean flatMessage){
|
||||||
this.topic = topic;
|
this.topic = topic;
|
||||||
this.partition = partition;
|
this.partition = partition;
|
||||||
this.flatMessage = flatMessage;
|
this.flatMessage = flatMessage;
|
||||||
@@ -55,7 +56,10 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
properties.put("auto.offset.reset", "latest"); // 如果没有offset则从最后的offset开始读
|
properties.put("auto.offset.reset", "latest"); // 如果没有offset则从最后的offset开始读
|
||||||
properties.put("request.timeout.ms", "40000"); // 必须大于session.timeout.ms的设置
|
properties.put("request.timeout.ms", "40000"); // 必须大于session.timeout.ms的设置
|
||||||
properties.put("session.timeout.ms", "30000"); // 默认为30秒
|
properties.put("session.timeout.ms", "30000"); // 默认为30秒
|
||||||
properties.put("max.poll.records", "100");
|
if (batchSize == null) {
|
||||||
|
batchSize = 100;
|
||||||
|
}
|
||||||
|
properties.put("max.poll.records", batchSize.toString());
|
||||||
properties.put("key.deserializer", StringDeserializer.class.getName());
|
properties.put("key.deserializer", StringDeserializer.class.getName());
|
||||||
if (!flatMessage) {
|
if (!flatMessage) {
|
||||||
properties.put("value.deserializer", MessageDeserializer.class.getName());
|
properties.put("value.deserializer", MessageDeserializer.class.getName());
|
||||||
@@ -170,7 +174,7 @@ public class KafkaCanalConnector implements CanalMQConnector {
|
|||||||
return Lists.newArrayList();
|
return Lists.newArrayList();
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsumerRecords<String, Message> records = kafkaConsumer.poll(unit.toMillis(timeout)); // 基于配置,最多只能poll到一条数据
|
ConsumerRecords<String, Message> records = kafkaConsumer.poll(unit.toMillis(timeout));
|
||||||
|
|
||||||
if (!records.isEmpty()) {
|
if (!records.isEmpty()) {
|
||||||
List<Message> messages = new ArrayList<>();
|
List<Message> messages = new ArrayList<>();
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
package com.alibaba.otter.canal.client.kafka;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* canal kafka connectors创建工具类
|
|
||||||
*
|
|
||||||
* @author machengyuan @ 2018-6-12
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
|
||||||
public class KafkaCanalConnectors {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建kafka客户端链接,独立运行不注册zk信息
|
|
||||||
*
|
|
||||||
* @param servers
|
|
||||||
* @param topic
|
|
||||||
* @param partition
|
|
||||||
* @param groupId
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static KafkaCanalConnector newKafkaConnector(String servers, String topic, Integer partition, String groupId) {
|
|
||||||
return new KafkaCanalConnector(servers, topic, partition, groupId, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建kafka客户端链接,独立运行不注册zk信息
|
|
||||||
*
|
|
||||||
* @param servers
|
|
||||||
* @param topic
|
|
||||||
* @param groupId
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static KafkaCanalConnector newKafkaConnector(String servers, String topic, String groupId) {
|
|
||||||
return new KafkaCanalConnector(servers, topic, null, groupId, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建kafka客户端链接
|
|
||||||
*
|
|
||||||
* @param servers
|
|
||||||
* @param topic
|
|
||||||
* @param partition
|
|
||||||
* @param groupId
|
|
||||||
* @param flatMessage
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static KafkaCanalConnector newKafkaConnector(String servers, String topic, Integer partition,
|
|
||||||
String groupId, boolean flatMessage) {
|
|
||||||
return new KafkaCanalConnector(servers, topic, partition, groupId, flatMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.alibaba.otter.canal.client.rocketmq;
|
package com.alibaba.otter.canal.client.rocketmq;
|
||||||
|
|
||||||
|
import com.aliyun.openservices.apache.api.impl.authority.SessionCredentials;
|
||||||
|
import com.aliyun.openservices.apache.api.impl.rocketmq.ClientRPCHook;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
@@ -10,8 +12,10 @@ import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
|
|||||||
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
|
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
|
||||||
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
|
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
|
||||||
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
|
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
|
||||||
|
import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely;
|
||||||
import org.apache.rocketmq.client.exception.MQClientException;
|
import org.apache.rocketmq.client.exception.MQClientException;
|
||||||
import org.apache.rocketmq.common.message.MessageExt;
|
import org.apache.rocketmq.common.message.MessageExt;
|
||||||
|
import org.apache.rocketmq.remoting.RPCHook;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -47,6 +51,9 @@ public class RocketMQCanalConnector implements CanalMQConnector {
|
|||||||
private long batchProcessTimeout = 60 * 1000;
|
private long batchProcessTimeout = 60 * 1000;
|
||||||
private boolean flatMessage;
|
private boolean flatMessage;
|
||||||
private volatile ConsumerBatchMessage lastGetBatchMessage = null;
|
private volatile ConsumerBatchMessage lastGetBatchMessage = null;
|
||||||
|
private String accessKey;
|
||||||
|
private String secretKey;
|
||||||
|
|
||||||
|
|
||||||
public RocketMQCanalConnector(String nameServer, String topic, String groupName, boolean flatMessage){
|
public RocketMQCanalConnector(String nameServer, String topic, String groupName, boolean flatMessage){
|
||||||
this.nameServer = nameServer;
|
this.nameServer = nameServer;
|
||||||
@@ -56,8 +63,25 @@ public class RocketMQCanalConnector implements CanalMQConnector {
|
|||||||
this.messageBlockingQueue = new LinkedBlockingQueue<>(1024);
|
this.messageBlockingQueue = new LinkedBlockingQueue<>(1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RocketMQCanalConnector(String nameServer, String topic, String groupName,
|
||||||
|
String accessKey, String secretKey, boolean flatMessage){
|
||||||
|
this(nameServer, topic, groupName, flatMessage);
|
||||||
|
this.accessKey = accessKey;
|
||||||
|
this.secretKey = secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
public void connect() throws CanalClientException {
|
public void connect() throws CanalClientException {
|
||||||
rocketMQConsumer = new DefaultMQPushConsumer(groupName);
|
|
||||||
|
RPCHook rpcHook = null;
|
||||||
|
if(null != accessKey && accessKey.length() > 0
|
||||||
|
&& null != secretKey && secretKey.length() > 0){
|
||||||
|
SessionCredentials sessionCredentials = new SessionCredentials();
|
||||||
|
sessionCredentials.setAccessKey(accessKey);
|
||||||
|
sessionCredentials.setSecretKey(secretKey);
|
||||||
|
rpcHook = new ClientRPCHook(sessionCredentials);
|
||||||
|
}
|
||||||
|
rocketMQConsumer = new DefaultMQPushConsumer(groupName, rpcHook, new AllocateMessageQueueAveragely());
|
||||||
|
rocketMQConsumer.setVipChannelEnabled(false);
|
||||||
if (!StringUtils.isBlank(nameServer)) {
|
if (!StringUtils.isBlank(nameServer)) {
|
||||||
rocketMQConsumer.setNamesrvAddr(nameServer);
|
rocketMQConsumer.setNamesrvAddr(nameServer);
|
||||||
}
|
}
|
||||||
@@ -214,9 +238,13 @@ public class RocketMQCanalConnector implements CanalMQConnector {
|
|||||||
@Override
|
@Override
|
||||||
public void ack() throws CanalClientException {
|
public void ack() throws CanalClientException {
|
||||||
try {
|
try {
|
||||||
this.lastGetBatchMessage.ack();
|
if (this.lastGetBatchMessage != null) {
|
||||||
|
this.lastGetBatchMessage.ack();
|
||||||
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
this.lastGetBatchMessage.fail();
|
if (this.lastGetBatchMessage != null) {
|
||||||
|
this.lastGetBatchMessage.fail();
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.lastGetBatchMessage = null;
|
this.lastGetBatchMessage = null;
|
||||||
}
|
}
|
||||||
@@ -225,7 +253,9 @@ public class RocketMQCanalConnector implements CanalMQConnector {
|
|||||||
@Override
|
@Override
|
||||||
public void rollback() throws CanalClientException {
|
public void rollback() throws CanalClientException {
|
||||||
try {
|
try {
|
||||||
this.lastGetBatchMessage.fail();
|
if (this.lastGetBatchMessage != null) {
|
||||||
|
this.lastGetBatchMessage.fail();
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.lastGetBatchMessage = null;
|
this.lastGetBatchMessage = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
package com.alibaba.otter.canal.client.rocketmq;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RocketMQ connector provider.
|
|
||||||
*/
|
|
||||||
public class RocketMQCanalConnectors {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create RocketMQ connector
|
|
||||||
*
|
|
||||||
* @param nameServers name servers for RocketMQ
|
|
||||||
* @param topic
|
|
||||||
* @param groupId
|
|
||||||
* @return {@link RocketMQCanalConnector}
|
|
||||||
*/
|
|
||||||
public static RocketMQCanalConnector newRocketMQConnector(String nameServers, String topic, String groupId) {
|
|
||||||
return new RocketMQCanalConnector(nameServers, topic, groupId, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RocketMQCanalConnector newRocketMQConnector(String nameServers, String topic, String groupId,
|
|
||||||
boolean flatMessage) {
|
|
||||||
return new RocketMQCanalConnector(nameServers, topic, groupId, flatMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector;
|
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector;
|
||||||
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors;
|
|
||||||
import com.alibaba.otter.canal.protocol.Message;
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,12 +35,13 @@ public class CanalKafkaClientExample {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public CanalKafkaClientExample(String zkServers, String servers, String topic, Integer partition, String groupId){
|
public CanalKafkaClientExample(String zkServers, String servers, String topic, Integer partition, String groupId){
|
||||||
connector = KafkaCanalConnectors.newKafkaConnector(servers, topic, partition, groupId, false);
|
connector = new KafkaCanalConnector(servers, topic, partition, groupId, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
try {
|
try {
|
||||||
final CanalKafkaClientExample kafkaCanalClientExample = new CanalKafkaClientExample(AbstractKafkaTest.zkServers,
|
final CanalKafkaClientExample kafkaCanalClientExample = new CanalKafkaClientExample(
|
||||||
|
AbstractKafkaTest.zkServers,
|
||||||
AbstractKafkaTest.servers,
|
AbstractKafkaTest.servers,
|
||||||
AbstractKafkaTest.topic,
|
AbstractKafkaTest.topic,
|
||||||
AbstractKafkaTest.partition,
|
AbstractKafkaTest.partition,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector;
|
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector;
|
||||||
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors;
|
|
||||||
import com.alibaba.otter.canal.protocol.Message;
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,7 +29,7 @@ public class KafkaClientRunningTest extends AbstractKafkaTest {
|
|||||||
public void testKafkaConsumer() {
|
public void testKafkaConsumer() {
|
||||||
final ExecutorService executor = Executors.newFixedThreadPool(1);
|
final ExecutorService executor = Executors.newFixedThreadPool(1);
|
||||||
|
|
||||||
final KafkaCanalConnector connector = KafkaCanalConnectors.newKafkaConnector(servers, topic, partition, groupId);
|
final KafkaCanalConnector connector = new KafkaCanalConnector(servers, topic, partition, groupId, null, false);
|
||||||
|
|
||||||
executor.submit(new Runnable() {
|
executor.submit(new Runnable() {
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user