mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
Compare commits
317 Commits
v1.0.25
...
canal-1.1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82f8a9fc98 | ||
|
|
f5bf1667a9 | ||
|
|
a9cccb3017 | ||
|
|
47ac10ca18 | ||
|
|
d4a70d5eea | ||
|
|
e78de8568e | ||
|
|
728550cac3 | ||
|
|
81c216f1c1 | ||
|
|
17f345bfe5 | ||
|
|
ebf9139762 | ||
|
|
7de40091a3 | ||
|
|
ee5775ce05 | ||
|
|
21ef2e5dd4 | ||
|
|
283956ee93 | ||
|
|
06275a46ed | ||
|
|
ea81b1dc92 | ||
|
|
fd5cfe23db | ||
|
|
bcf30a9a05 | ||
|
|
d5122dcf50 | ||
|
|
b5e05c8c77 | ||
|
|
9434ed513e | ||
|
|
6fe3ef43d8 | ||
|
|
c1f92858a2 | ||
|
|
00b5ade787 | ||
|
|
6b94f396ff | ||
|
|
b7de1e5070 | ||
|
|
4fe6c36c49 | ||
|
|
899592ce2a | ||
|
|
8585761f42 | ||
|
|
1aa2e1a409 | ||
|
|
9e3d4a7ffe | ||
|
|
fc3de4b7fd | ||
|
|
e7f07d7d3e | ||
|
|
6e07021223 | ||
|
|
3c80b6c1f7 | ||
|
|
20b0b39b14 | ||
|
|
de59d22429 | ||
|
|
1bef1b72bc | ||
|
|
47348e2e96 | ||
|
|
0f3c427e28 | ||
|
|
9bde69b6d6 | ||
|
|
dd8b1ce955 | ||
|
|
cb884094d5 | ||
|
|
f567abdaee | ||
|
|
02757693b4 | ||
|
|
35797ca6ff | ||
|
|
5588da59b9 | ||
|
|
fbb3b814c0 | ||
|
|
2784f33b52 | ||
|
|
2656d1d2af | ||
|
|
b6359d014f | ||
|
|
a4445043c1 | ||
|
|
59169876eb | ||
|
|
d06ecdef20 | ||
|
|
4a10986b5f | ||
|
|
e81b4279b8 | ||
|
|
a6abe7fa82 | ||
|
|
10a80b2752 | ||
|
|
0131c3ad13 | ||
|
|
62f975deeb | ||
|
|
2accfbf5db | ||
|
|
7ed6fcdf04 | ||
|
|
156fc0edbe | ||
|
|
c421ff718b | ||
|
|
cc31e2db7c | ||
|
|
10114f4a9f | ||
|
|
e879216fa4 | ||
|
|
f0ea893bf4 | ||
|
|
3053b9635a | ||
|
|
a1588e5f98 | ||
|
|
1fd623311e | ||
|
|
a480ef60ea | ||
|
|
4776552605 | ||
|
|
efb7adc967 | ||
|
|
feb2af060f | ||
|
|
15526aab8c | ||
|
|
679fd464f4 | ||
|
|
c8a32b578c | ||
|
|
84f3d92790 | ||
|
|
13291c8d7b | ||
|
|
8a74504fe3 | ||
|
|
11d4761bcb | ||
|
|
72213bb80d | ||
|
|
e8aee3a276 | ||
|
|
a3bf1d59e6 | ||
|
|
d3f52c6d3d | ||
|
|
fb1126cf39 | ||
|
|
ec00792475 | ||
|
|
d7bbb0d1a7 | ||
|
|
dfd58f4aa1 | ||
|
|
179afb0340 | ||
|
|
fec9803aae | ||
|
|
75de57c102 | ||
|
|
2084b1fc37 | ||
|
|
07d4d2023a | ||
|
|
fac278150e | ||
|
|
da5fb30191 | ||
|
|
01e74c73e2 | ||
|
|
782ef7fe14 | ||
|
|
bd14848600 | ||
|
|
1c245280c7 | ||
|
|
d7f48f534b | ||
|
|
8ce320148c | ||
|
|
3eef7b583d | ||
|
|
89a32aaabd | ||
|
|
b53131553e | ||
|
|
68d8afae3e | ||
|
|
92f24bd02e | ||
|
|
61c7f7738d | ||
|
|
9e816bc48f | ||
|
|
377a72e1c0 | ||
|
|
b1dc5bd59f | ||
|
|
23f7b3b5d8 | ||
|
|
09339333bb | ||
|
|
f36ce647e6 | ||
|
|
6d3584b6fb | ||
|
|
4006cedaad | ||
|
|
cd0bf60824 | ||
|
|
c4c45d9e0b | ||
|
|
02868d8fec | ||
|
|
7d9e2dc69b | ||
|
|
7c7ce43455 | ||
|
|
119c5a6971 | ||
|
|
8a952d3a5f | ||
|
|
764e1b6dc0 | ||
|
|
4e5e2f84e0 | ||
|
|
3c716e2a43 | ||
|
|
872dad8ce8 | ||
|
|
bd6ab1cc80 | ||
|
|
7fd958a490 | ||
|
|
da1ce51a0f | ||
|
|
26edbeaa28 | ||
|
|
93ebc7b973 | ||
|
|
50d1ec583b | ||
|
|
3d5f13fec8 | ||
|
|
fe12a8481f | ||
|
|
1eaf198acf | ||
|
|
c0a40499b0 | ||
|
|
8cfb70e080 | ||
|
|
d624358cb5 | ||
|
|
72b907a75f | ||
|
|
698573b14a | ||
|
|
129051a56e | ||
|
|
e608e1d601 | ||
|
|
22a0d88fe9 | ||
|
|
ccd1e52411 | ||
|
|
aaf18ccd7e | ||
|
|
62b1c1ea93 | ||
|
|
20807e58c8 | ||
|
|
89eb3d6078 | ||
|
|
7b52568575 | ||
|
|
d520260fe8 | ||
|
|
77c1c65b83 | ||
|
|
fe38bc16d2 | ||
|
|
b4c6490c94 | ||
|
|
67f2e55d38 | ||
|
|
b9d7f0b6d0 | ||
|
|
b880d96a86 | ||
|
|
b9156936ae | ||
|
|
617f9bbc62 | ||
|
|
b19eaeb89a | ||
|
|
5582a38930 | ||
|
|
c6eee47af6 | ||
|
|
10bb800f85 | ||
|
|
89726a6365 | ||
|
|
8e856acac5 | ||
|
|
df8fe4a9eb | ||
|
|
33a2a6121a | ||
|
|
441d2d2449 | ||
|
|
bef6a66eaf | ||
|
|
c1805a4831 | ||
|
|
38eecd9aef | ||
|
|
cef431c8af | ||
|
|
3efd589cb1 | ||
|
|
92c570e967 | ||
|
|
cf7af17aec | ||
|
|
3308bf0662 | ||
|
|
be605617c3 | ||
|
|
bb8fec963b | ||
|
|
da8bf59d5b | ||
|
|
54168af790 | ||
|
|
f0bed4c762 | ||
|
|
4d4d97419c | ||
|
|
41eef9a090 | ||
|
|
9ae6c0c863 | ||
|
|
5f47809c8e | ||
|
|
f2e3ff2dae | ||
|
|
92778be5be | ||
|
|
5da8d391f8 | ||
|
|
f867b183ab | ||
|
|
ea555df315 | ||
|
|
4af1e5fb84 | ||
|
|
bf02db4cbc | ||
|
|
967f8d6b0f | ||
|
|
4f4b016da3 | ||
|
|
a4255a6377 | ||
|
|
dca1b20043 | ||
|
|
025d36604b | ||
|
|
e23a19cd85 | ||
|
|
593902d349 | ||
|
|
d014167226 | ||
|
|
df668a6f72 | ||
|
|
cc5eb1075b | ||
|
|
19f4c5ae1b | ||
|
|
4e41ebf746 | ||
|
|
31cbdb1824 | ||
|
|
1b6efdf463 | ||
|
|
cb6c7ef3e8 | ||
|
|
4bd2a79905 | ||
|
|
345e22bd2c | ||
|
|
9964bb75b1 | ||
|
|
ec072a45d8 | ||
|
|
efb7b32694 | ||
|
|
74bc6368f2 | ||
|
|
b96e4dd23a | ||
|
|
d991e6be60 | ||
|
|
e7846085fc | ||
|
|
6faa3fd586 | ||
|
|
fa34eb0e59 | ||
|
|
3e97e6b771 | ||
|
|
330da50a5e | ||
|
|
e35a06743c | ||
|
|
0d6757c257 | ||
|
|
e4cde2f6a1 | ||
|
|
9b7d62717e | ||
|
|
b306bde3ec | ||
|
|
31f72e1aac | ||
|
|
ad201660a6 | ||
|
|
77f1dfd324 | ||
|
|
11467fca2a | ||
|
|
cc8d5b4263 | ||
|
|
4022aa8b5a | ||
|
|
187b98fbda | ||
|
|
0f33c8c89c | ||
|
|
b6967dc0dc | ||
|
|
b734a32ae6 | ||
|
|
1f16310b7d | ||
|
|
5349a5c49b | ||
|
|
a2241e2521 | ||
|
|
fedc07352b | ||
|
|
e251de79f3 | ||
|
|
7076e3124d | ||
|
|
fca7da6bba | ||
|
|
57c2cc4c4e | ||
|
|
3c913d1e22 | ||
|
|
4141049bb9 | ||
|
|
912a7a12be | ||
|
|
5c2ed38d2f | ||
|
|
c8f1d54fa1 | ||
|
|
e0fa8e0f72 | ||
|
|
7eb6fb3e8a | ||
|
|
debfdb8873 | ||
|
|
eda7e4ca2b | ||
|
|
70e6bdde02 | ||
|
|
21f3f8c973 | ||
|
|
f46133d116 | ||
|
|
9b7de00efb | ||
|
|
c340bd6565 | ||
|
|
511734c3d8 | ||
|
|
af002f4e24 | ||
|
|
28ad12d0ea | ||
|
|
6229a8593f | ||
|
|
256056e14f | ||
|
|
499be8bd8c | ||
|
|
cd87ef00cc | ||
|
|
6b7109c66b | ||
|
|
b90c9fac41 | ||
|
|
88c79544f0 | ||
|
|
fddaff1bd9 | ||
|
|
847ebf9bf3 | ||
|
|
3e7e7b7f6e | ||
|
|
06e7f44114 | ||
|
|
8ad603fea4 | ||
|
|
863a61d626 | ||
|
|
7b38542183 | ||
|
|
0af789b1c7 | ||
|
|
2d49b709ca | ||
|
|
826a45c105 | ||
|
|
03452dcdda | ||
|
|
514876f60f | ||
|
|
89994c52f9 | ||
|
|
edc2fbe1dc | ||
|
|
cfa90af33c | ||
|
|
32d4174083 | ||
|
|
bb0f3b3a7e | ||
|
|
dc0010823f | ||
|
|
b1431f8827 | ||
|
|
b9e3629459 | ||
|
|
31ed4efe3a | ||
|
|
6da1507405 | ||
|
|
0b82ee8d10 | ||
|
|
00890f87e5 | ||
|
|
91ac0e30aa | ||
|
|
9316a3df6d | ||
|
|
2d31e6bf91 | ||
|
|
a965d2cd04 | ||
|
|
93be9d1909 | ||
|
|
7cf9375ac6 | ||
|
|
af289cd9fb | ||
|
|
d1f1c0716b | ||
|
|
0c7aed197a | ||
|
|
325d16bc66 | ||
|
|
3104b4cc24 | ||
|
|
9595e6abbd | ||
|
|
c6f4de9f88 | ||
|
|
d66839c965 | ||
|
|
ac773009f8 | ||
|
|
8b03f172ed | ||
|
|
5e206e4f7e | ||
|
|
448fc8bcde | ||
|
|
c60b04424b | ||
|
|
5aed505429 | ||
|
|
912f5db2c7 | ||
|
|
8b57b9a0a5 | ||
|
|
d1c83ea7bb | ||
|
|
7d485efd71 | ||
|
|
fa059804ce |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -14,3 +14,5 @@ jtester.properties
|
|||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
*.tar.gz
|
||||||
|
*.rpm
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -5,6 +5,8 @@
|
|||||||
<ol>
|
<ol>
|
||||||
<li>canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。</li>
|
<li>canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。</li>
|
||||||
<li>canal消费端项目开源: Otter(分布式数据库同步系统),地址:<a href="https://github.com/alibaba/otter">https://github.com/alibaba/otter</a></li>
|
<li>canal消费端项目开源: Otter(分布式数据库同步系统),地址:<a href="https://github.com/alibaba/otter">https://github.com/alibaba/otter</a></li>
|
||||||
|
<li>Canal已在阿里云推出商业化版本 <a href="https://www.aliyun.com/product/dts?spm=a2c4g.11186623.cloudEssentials.80.srdwr7">数据传输服务DTS</a>, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。<a href="https://help.aliyun.com/document_detail/26592.html?spm=a2c4g.11174283.6.539.t1Y91E">DTS产品使用文档</a></li>
|
||||||
|
DTS支持阿里云RDS&DRDS的Binlog日志实时订阅,现推出首月免费体验,限时限量,<a href="https://common-buy.aliyun.com/?commodityCode=dtspre&request=%7b%22dts_function%22%3a%22data_subscribe%22%7d">立即体验>>></a>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<h1>背景</h1>
|
<h1>背景</h1>
|
||||||
@@ -36,7 +38,7 @@
|
|||||||
<li>slave重做中继日志中的事件,将改变反映它自己的数据。</li>
|
<li>slave重做中继日志中的事件,将改变反映它自己的数据。</li>
|
||||||
</ol>
|
</ol>
|
||||||
<h3>canal的工作原理:</h3>
|
<h3>canal的工作原理:</h3>
|
||||||
<p><img width="590" src="https://camo.githubusercontent.com/46c626b4cde399db43b2634a7911a04aecf273a0/687474703a2f2f646c2e69746579652e636f6d2f75706c6f61642f6174746163686d656e742f303038302f333130372f63383762363762612d333934632d333038362d393537372d3964623035626530346339352e6a7067" alt="" height="273">
|
<p><img width="590" src="http://dl.iteye.com/upload/attachment/0080/3107/c87b67ba-394c-3086-9577-9db05be04c95.jpg" alt="" height="273">
|
||||||
<p>原理相对比较简单:</p>
|
<p>原理相对比较简单:</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li>canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议</li>
|
<li>canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议</li>
|
||||||
@@ -73,6 +75,14 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
|
|||||||
<li>阿里巴巴去Oracle数据迁移同步工具(目标支持MySQL/DRDS):<a href="http://github.com/alibaba/yugong">http://github.com/alibaba/yugong</a></li>
|
<li>阿里巴巴去Oracle数据迁移同步工具(目标支持MySQL/DRDS):<a href="http://github.com/alibaba/yugong">http://github.com/alibaba/yugong</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
|
<h1>相关产品</h1>
|
||||||
|
<ol>
|
||||||
|
<li><a href="https://www.aliyun.com/product/drds?spm=5176.55326.cloudEssentials.71.69fd227dRPZj9K">阿里云分布式数据库DRDS</a></li>
|
||||||
|
<li><a href="https://www.aliyun.com/product/dts?spm=5176.7947010.cloudEssentials.80.33f734f4JOAxSP">阿里云数据传输服务DTS</a></li>
|
||||||
|
<li><a href="https://www.aliyun.com/product/dbs?spm=5176.54487.cloudEssentials.83.34b851a8GmVZg6">阿里云数据库备份服务DBS</a></li>
|
||||||
|
<li><a href="https://www.aliyun.com/product/dms?spm=5176.169464.cloudEssentials.81.2e1066feC1sBBL">阿里云数据管理服务DMS</a></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
<h1>问题反馈</h1>
|
<h1>问题反馈</h1>
|
||||||
<ol>
|
<ol>
|
||||||
<li>qq交流群: 161559791 </li>
|
<li>qq交流群: 161559791 </li>
|
||||||
|
|||||||
33
client-adapter/common/pom.xml
Normal file
33
client-adapter/common/pom.xml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?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.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.common</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>canal client adapter common module for otter ${project.version}</name>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.protocol</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>1.7.12</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>joda-time</groupId>
|
||||||
|
<artifactId>joda-time</artifactId>
|
||||||
|
<version>2.9.4</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.SPI;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部适配器接口
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-18 下午10:14:02
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@SPI("logger")
|
||||||
|
public interface CanalOuterAdapter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部适配器初始化接口
|
||||||
|
*
|
||||||
|
* @param configuration 外部适配器配置信息
|
||||||
|
*/
|
||||||
|
void init(CanalOuterAdapterConfiguration configuration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 往适配器中写入数据
|
||||||
|
*
|
||||||
|
* @param message message数据包
|
||||||
|
*/
|
||||||
|
void writeOut(Message message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部适配器销毁接口
|
||||||
|
*/
|
||||||
|
void destroy();
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置信息类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-18 下午10:40:12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalClientConfig {
|
||||||
|
|
||||||
|
private String canalServerHost;
|
||||||
|
|
||||||
|
private String zookeeperHosts;
|
||||||
|
|
||||||
|
private Properties properties;
|
||||||
|
|
||||||
|
private String bootstrapServers;
|
||||||
|
|
||||||
|
private List<KafkaTopic> kafkaTopics;
|
||||||
|
|
||||||
|
private List<CanalInstance> canalInstances;
|
||||||
|
|
||||||
|
public String getCanalServerHost() {
|
||||||
|
return canalServerHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanalServerHost(String canalServerHost) {
|
||||||
|
this.canalServerHost = canalServerHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getZookeeperHosts() {
|
||||||
|
return zookeeperHosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZookeeperHosts(String zookeeperHosts) {
|
||||||
|
this.zookeeperHosts = zookeeperHosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Properties getProperties() {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProperties(Properties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBootstrapServers() {
|
||||||
|
return bootstrapServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBootstrapServers(String bootstrapServers) {
|
||||||
|
this.bootstrapServers = bootstrapServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<KafkaTopic> getKafkaTopics() {
|
||||||
|
return kafkaTopics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKafkaTopics(List<KafkaTopic> kafkaTopics) {
|
||||||
|
this.kafkaTopics = kafkaTopics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CanalInstance> getCanalInstances() {
|
||||||
|
return canalInstances;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanalInstances(List<CanalInstance> canalInstances) {
|
||||||
|
this.canalInstances = canalInstances;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CanalInstance {
|
||||||
|
|
||||||
|
private String instance;
|
||||||
|
|
||||||
|
private List<AdapterGroup> adapterGroups;
|
||||||
|
|
||||||
|
public String getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstance(String instance) {
|
||||||
|
if (instance != null) {
|
||||||
|
this.instance = instance.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AdapterGroup> getAdapterGroups() {
|
||||||
|
return adapterGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAdapterGroups(List<AdapterGroup> adapterGroups) {
|
||||||
|
this.adapterGroups = adapterGroups;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AdapterGroup {
|
||||||
|
|
||||||
|
private List<CanalOuterAdapterConfiguration> outAdapters;
|
||||||
|
|
||||||
|
public List<CanalOuterAdapterConfiguration> getOutAdapters() {
|
||||||
|
return outAdapters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutAdapters(List<CanalOuterAdapterConfiguration> outAdapters) {
|
||||||
|
this.outAdapters = outAdapters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class KafkaTopic {
|
||||||
|
|
||||||
|
private String topic;
|
||||||
|
|
||||||
|
private List<Group> groups = new ArrayList<>();
|
||||||
|
|
||||||
|
public String getTopic() {
|
||||||
|
return topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTopic(String topic) {
|
||||||
|
this.topic = topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Group> getGroups() {
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroups(List<Group> groups) {
|
||||||
|
this.groups = groups;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Group {
|
||||||
|
|
||||||
|
private String groupId;
|
||||||
|
|
||||||
|
// private List<Adaptor> adapters = new ArrayList<>();
|
||||||
|
|
||||||
|
private List<CanalOuterAdapterConfiguration> outAdapters;
|
||||||
|
|
||||||
|
public String getGroupId() {
|
||||||
|
return groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupId(String groupId) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CanalOuterAdapterConfiguration> getOutAdapters() {
|
||||||
|
return outAdapters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutAdapters(List<CanalOuterAdapterConfiguration> outAdapters) {
|
||||||
|
this.outAdapters = outAdapters;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public List<Adaptor> getAdapters() {
|
||||||
|
// return adapters;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public void setAdapters(List<Adaptor> adapters) {
|
||||||
|
// this.adapters = adapters;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static class Adaptor {
|
||||||
|
// private List<CanalOuterAdapterConfiguration> outAdapters;
|
||||||
|
//
|
||||||
|
// public List<CanalOuterAdapterConfiguration> getOutAdapters() {
|
||||||
|
// return outAdapters;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public void setOutAdapters(List<CanalOuterAdapterConfiguration> outAdapters)
|
||||||
|
// {
|
||||||
|
// this.outAdapters = outAdapters;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部适配器配置信息类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-18 下午10:15:12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalOuterAdapterConfiguration {
|
||||||
|
|
||||||
|
private String name; // 适配器名称, 如: logger, hbase, es
|
||||||
|
|
||||||
|
private String hosts; // 适配器内部的地址, 比如对应es该参数可以填写es的server地址
|
||||||
|
|
||||||
|
private String zkHosts; // 适配器内部的ZK地址, 比如对应HBase该参数可以填写HBase对应的ZK地址
|
||||||
|
|
||||||
|
private Properties properties; // 其余参数, 可填写适配器中的所需的配置信息
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHosts() {
|
||||||
|
return hosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHosts(String hosts) {
|
||||||
|
this.hosts = hosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Properties getProperties() {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProperties(Properties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getZkHosts() {
|
||||||
|
return zkHosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZkHosts(String zkHosts) {
|
||||||
|
this.zkHosts = zkHosts;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DML操作转换对象
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午11:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class Dml implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 2611556444074013268L;
|
||||||
|
|
||||||
|
private String database;
|
||||||
|
private String table;
|
||||||
|
private String type;
|
||||||
|
private Long ts;
|
||||||
|
private String sql;
|
||||||
|
private List<Map<String, Object>> data;
|
||||||
|
private List<Map<String, Object>> old;
|
||||||
|
|
||||||
|
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 Long getTs() {
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTs(Long ts) {
|
||||||
|
this.ts = ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSql() {
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSql(String sql) {
|
||||||
|
this.sql = sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<String, Object>> getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(List<Map<String, Object>> data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<String, Object>> getOld() {
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOld(List<Map<String, Object>> old) {
|
||||||
|
this.old = old;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
database = null;
|
||||||
|
table = null;
|
||||||
|
type = null;
|
||||||
|
ts = null;
|
||||||
|
data = null;
|
||||||
|
old = null;
|
||||||
|
sql = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Dml{" + "database='" + database + '\'' + ", table='" + table + '\'' + ", type='" + type + '\'' + ", ts="
|
||||||
|
+ ts + ", sql='" + sql + '\'' + ", data=" + data + ", old=" + old + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,563 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SPI 类加载器
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午11:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class ExtensionLoader<T> {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory
|
||||||
|
.getLogger(ExtensionLoader.class);
|
||||||
|
|
||||||
|
private static final String SERVICES_DIRECTORY = "META-INF/services/";
|
||||||
|
|
||||||
|
private static final String CANAL_DIRECTORY = "META-INF/canal/";
|
||||||
|
|
||||||
|
private static final String DEFAULT_CLASSLOADER_POLICY = "internal";
|
||||||
|
|
||||||
|
private static final Pattern NAME_SEPARATOR = Pattern
|
||||||
|
.compile("\\s*[,]+\\s*");
|
||||||
|
|
||||||
|
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final Class<?> type;
|
||||||
|
|
||||||
|
private final String classLoaderPolicy;
|
||||||
|
|
||||||
|
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
|
||||||
|
|
||||||
|
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private String cachedDefaultName;
|
||||||
|
|
||||||
|
private ConcurrentHashMap<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static <T> boolean withExtensionAnnotation(Class<T> type) {
|
||||||
|
return type.isAnnotationPresent(SPI.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
|
||||||
|
return getExtensionLoader(type, DEFAULT_CLASSLOADER_POLICY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type, String classLoaderPolicy) {
|
||||||
|
if (type == null) throw new IllegalArgumentException("Extension type == null");
|
||||||
|
if (!type.isInterface()) {
|
||||||
|
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
|
||||||
|
}
|
||||||
|
if (!withExtensionAnnotation(type)) {
|
||||||
|
throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @"
|
||||||
|
+ SPI.class.getSimpleName() + " Annotation!");
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
|
||||||
|
if (loader == null) {
|
||||||
|
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type, classLoaderPolicy));
|
||||||
|
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
|
||||||
|
}
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExtensionLoader(Class<?> type, String classLoaderPolicy){
|
||||||
|
this.type = type;
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回指定名字的扩展
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T getExtension(String name) {
|
||||||
|
if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null");
|
||||||
|
if ("true".equals(name)) {
|
||||||
|
return getDefaultExtension();
|
||||||
|
}
|
||||||
|
Holder<Object> holder = cachedInstances.get(name);
|
||||||
|
if (holder == null) {
|
||||||
|
cachedInstances.putIfAbsent(name, new Holder<>());
|
||||||
|
holder = cachedInstances.get(name);
|
||||||
|
}
|
||||||
|
Object instance = holder.get();
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (holder) {
|
||||||
|
instance = holder.get();
|
||||||
|
if (instance == null) {
|
||||||
|
instance = createExtension(name);
|
||||||
|
holder.set(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (T) instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回缺省的扩展,如果没有设置则返回<code>null</code>
|
||||||
|
*/
|
||||||
|
public T getDefaultExtension() {
|
||||||
|
getExtensionClasses();
|
||||||
|
if (null == cachedDefaultName || cachedDefaultName.length() == 0 || "true".equals(cachedDefaultName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
private T createExtension(String name) {
|
||||||
|
Class<?> clazz = getExtensionClasses().get(name);
|
||||||
|
if (clazz == null) {
|
||||||
|
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
|
||||||
|
+ ") could not be instantiated: class could not be found");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
T instance = (T) EXTENSION_INSTANCES.get(clazz);
|
||||||
|
if (instance == null) {
|
||||||
|
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
|
||||||
|
instance = (T) EXTENSION_INSTANCES.get(clazz);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
|
||||||
|
+ ") could not be instantiated: " + t.getMessage(),
|
||||||
|
t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class<?> getExtensionClass(String name) {
|
||||||
|
if (type == null) throw new IllegalArgumentException("Extension type == null");
|
||||||
|
if (name == null) throw new IllegalArgumentException("Extension name == null");
|
||||||
|
Class<?> clazz = getExtensionClasses().get(name);
|
||||||
|
if (clazz == null)
|
||||||
|
throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!");
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Class<?>> getExtensionClasses() {
|
||||||
|
Map<String, Class<?>> classes = cachedClasses.get();
|
||||||
|
if (classes == null) {
|
||||||
|
synchronized (cachedClasses) {
|
||||||
|
classes = cachedClasses.get();
|
||||||
|
if (classes == null) {
|
||||||
|
classes = loadExtensionClasses();
|
||||||
|
cachedClasses.set(classes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getJarDirectoryPath() {
|
||||||
|
URL url = Thread.currentThread().getContextClassLoader().getResource("");
|
||||||
|
if (url == null) {
|
||||||
|
throw new IllegalStateException("failed to get class loader resource");
|
||||||
|
}
|
||||||
|
String dirtyPath = url.toString();
|
||||||
|
String jarPath = dirtyPath.replaceAll("^.*file:/", ""); // removes file:/ and everything before it
|
||||||
|
jarPath = jarPath.replaceAll("jar!.*", "jar"); // removes everything after .jar, if .jar exists in dirtyPath
|
||||||
|
jarPath = jarPath.replaceAll("%20", " "); // necessary if path has spaces within
|
||||||
|
if (!jarPath.endsWith(".jar")) { // this is needed if you plan to run the app using Spring Tools Suit play
|
||||||
|
// button.
|
||||||
|
jarPath = jarPath.replaceAll("/classes/.*", "/classes/");
|
||||||
|
}
|
||||||
|
return Paths.get(jarPath).getParent().toString(); // Paths - from java 8
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Class<?>> loadExtensionClasses() {
|
||||||
|
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
|
||||||
|
if (defaultAnnotation != null) {
|
||||||
|
String value = defaultAnnotation.value();
|
||||||
|
if ((value = value.trim()).length() > 0) {
|
||||||
|
String[] names = NAME_SEPARATOR.split(value);
|
||||||
|
if (names.length > 1) {
|
||||||
|
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
|
||||||
|
+ ": " + Arrays.toString(names));
|
||||||
|
}
|
||||||
|
if (names.length == 1) cachedDefaultName = names[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
|
||||||
|
|
||||||
|
// 1. lib folder,customized extension classLoader (jar_dir/lib)
|
||||||
|
String dir = File.separator + this.getJarDirectoryPath() + File.separator + "lib";
|
||||||
|
logger.info("extension classpath dir: " + dir);
|
||||||
|
File externalLibDir = new File(dir);
|
||||||
|
if (!externalLibDir.exists()) {
|
||||||
|
externalLibDir = new File(
|
||||||
|
File.separator + this.getJarDirectoryPath() + File.separator + "canal_client" + File.separator + "lib");
|
||||||
|
}
|
||||||
|
if (externalLibDir.exists()) {
|
||||||
|
File[] files = externalLibDir.listFiles(new FilenameFilter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(File dir, String name) {
|
||||||
|
return name.endsWith(".jar");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (files != null) {
|
||||||
|
for (File f : files) {
|
||||||
|
URL url = null;
|
||||||
|
try {
|
||||||
|
url = f.toURI().toURL();
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new RuntimeException("load extension jar failed!", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassLoader parent = Thread.currentThread().getContextClassLoader();
|
||||||
|
URLClassLoader localClassLoader;
|
||||||
|
if (classLoaderPolicy == null || "".equals(classLoaderPolicy)
|
||||||
|
|| DEFAULT_CLASSLOADER_POLICY.equalsIgnoreCase(classLoaderPolicy)) {
|
||||||
|
localClassLoader = new URLClassLoader(new URL[] { url }, parent) {
|
||||||
|
|
||||||
|
@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 {
|
||||||
|
localClassLoader = new URLClassLoader(new URL[] { url }, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadFile(extensionClasses, CANAL_DIRECTORY, localClassLoader);
|
||||||
|
loadFile(extensionClasses, SERVICES_DIRECTORY, localClassLoader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2. load inner extension class with default classLoader
|
||||||
|
ClassLoader classLoader = findClassLoader();
|
||||||
|
loadFile(extensionClasses, CANAL_DIRECTORY, classLoader);
|
||||||
|
loadFile(extensionClasses, SERVICES_DIRECTORY, classLoader);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
String fileName = dir + type.getName();
|
||||||
|
try {
|
||||||
|
Enumeration<URL> urls;
|
||||||
|
if (classLoader != null) {
|
||||||
|
urls = classLoader.getResources(fileName);
|
||||||
|
} else {
|
||||||
|
urls = ClassLoader.getSystemResources(fileName);
|
||||||
|
}
|
||||||
|
if (urls != null) {
|
||||||
|
while (urls.hasMoreElements()) {
|
||||||
|
URL url = urls.nextElement();
|
||||||
|
try {
|
||||||
|
BufferedReader reader = null;
|
||||||
|
try {
|
||||||
|
reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
|
||||||
|
String line = null;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
final int ci = line.indexOf('#');
|
||||||
|
if (ci >= 0) line = line.substring(0, ci);
|
||||||
|
line = line.trim();
|
||||||
|
if (line.length() > 0) {
|
||||||
|
try {
|
||||||
|
String name = null;
|
||||||
|
int i = line.indexOf('=');
|
||||||
|
if (i > 0) {
|
||||||
|
name = line.substring(0, i).trim();
|
||||||
|
line = line.substring(i + 1).trim();
|
||||||
|
}
|
||||||
|
if (line.length() > 0) {
|
||||||
|
Class<?> clazz = classLoader.loadClass(line);
|
||||||
|
// Class<?> clazz = Class.forName(line, true, classLoader);
|
||||||
|
if (!type.isAssignableFrom(clazz)) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Error when load extension class(interface: " + type
|
||||||
|
+ ", class line: " + clazz.getName()
|
||||||
|
+ "), class " + clazz.getName()
|
||||||
|
+ "is not subtype of interface.");
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
clazz.getConstructor(type);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
clazz.getConstructor();
|
||||||
|
String[] names = NAME_SEPARATOR.split(name);
|
||||||
|
if (names != null && names.length > 0) {
|
||||||
|
for (String n : names) {
|
||||||
|
if (!cachedNames.containsKey(clazz)) {
|
||||||
|
cachedNames.put(clazz, n);
|
||||||
|
}
|
||||||
|
Class<?> c = extensionClasses.get(n);
|
||||||
|
if (c == null) {
|
||||||
|
extensionClasses.put(n, clazz);
|
||||||
|
} else if (c != clazz) {
|
||||||
|
cachedNames.remove(clazz);
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Duplicate extension " + type.getName() + " name "
|
||||||
|
+ n + " on "
|
||||||
|
+ c.getName() + " and "
|
||||||
|
+ clazz.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
IllegalStateException e = new IllegalStateException(
|
||||||
|
"Failed to load extension class(interface: " + type + ", class line: "
|
||||||
|
+ line + ") in " + url
|
||||||
|
+ ", cause: "
|
||||||
|
+ t.getMessage(),
|
||||||
|
t);
|
||||||
|
exceptions.put(line, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // end of while read lines
|
||||||
|
} finally {
|
||||||
|
if (reader != null) {
|
||||||
|
reader.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.error("Exception when load extension class(interface: " + type + ", class file: " + url
|
||||||
|
+ ") in " + url,
|
||||||
|
t);
|
||||||
|
}
|
||||||
|
} // end of while urls
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.error(
|
||||||
|
"Exception when load extension class(interface: " + type + ", description file: " + fileName + ").",
|
||||||
|
t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClassLoader findClassLoader() {
|
||||||
|
return ExtensionLoader.class.getClassLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.getClass().getName() + "[" + type.getName() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Holder<T> {
|
||||||
|
|
||||||
|
private volatile T value;
|
||||||
|
|
||||||
|
private void set(T value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private T get() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.sql.Date;
|
||||||
|
import java.sql.Time;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.sql.Types;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型转换工具类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午06:14:23
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class JdbcTypeUtil {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(JdbcTypeUtil.class);
|
||||||
|
|
||||||
|
public static Object typeConvert(String tableName, String columnName, String value, int sqlType, String mysqlType) {
|
||||||
|
if (value == null || value.equals("")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object res;
|
||||||
|
switch (sqlType) {
|
||||||
|
case Types.INTEGER:
|
||||||
|
res = Integer.parseInt(value);
|
||||||
|
break;
|
||||||
|
case Types.SMALLINT:
|
||||||
|
res = Short.parseShort(value);
|
||||||
|
break;
|
||||||
|
case Types.BIT:
|
||||||
|
case Types.TINYINT:
|
||||||
|
res = Byte.parseByte(value);
|
||||||
|
break;
|
||||||
|
case Types.BIGINT:
|
||||||
|
if (mysqlType.startsWith("bigint") && mysqlType.endsWith("unsigned")) {
|
||||||
|
res = new BigInteger(value);
|
||||||
|
} else {
|
||||||
|
res = Long.parseLong(value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// case Types.BIT:
|
||||||
|
case Types.BOOLEAN:
|
||||||
|
res = !"0".equals(value);
|
||||||
|
break;
|
||||||
|
case Types.DOUBLE:
|
||||||
|
case Types.FLOAT:
|
||||||
|
res = Double.parseDouble(value);
|
||||||
|
break;
|
||||||
|
case Types.REAL:
|
||||||
|
res = Float.parseFloat(value);
|
||||||
|
break;
|
||||||
|
case Types.DECIMAL:
|
||||||
|
case Types.NUMERIC:
|
||||||
|
res = new BigDecimal(value);
|
||||||
|
break;
|
||||||
|
case Types.BINARY:
|
||||||
|
case Types.VARBINARY:
|
||||||
|
case Types.LONGVARBINARY:
|
||||||
|
case Types.BLOB:
|
||||||
|
res = value.getBytes("ISO-8859-1");
|
||||||
|
break;
|
||||||
|
case Types.DATE:
|
||||||
|
if (!value.startsWith("0000-00-00")) {
|
||||||
|
value = value.trim().replace(" ", "T");
|
||||||
|
DateTime dt = new DateTime(value);
|
||||||
|
res = new Date(dt.toDate().getTime());
|
||||||
|
} else {
|
||||||
|
res = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.TIME:
|
||||||
|
value = "T" + value;
|
||||||
|
DateTime dt = new DateTime(value);
|
||||||
|
res = new Time(dt.toDate().getTime());
|
||||||
|
break;
|
||||||
|
case Types.TIMESTAMP:
|
||||||
|
if (!value.startsWith("0000-00-00")) {
|
||||||
|
value = value.trim().replace(" ", "T");
|
||||||
|
dt = new DateTime(value);
|
||||||
|
res = new Timestamp(dt.toDate().getTime());
|
||||||
|
} else {
|
||||||
|
res = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.CLOB:
|
||||||
|
default:
|
||||||
|
res = value;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("table: {} column: {}, failed convert type {} to {}", tableName, columnName, value, sqlType);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message对象解析工具类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午06:14:23
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class MessageUtil {
|
||||||
|
|
||||||
|
public static void parse4Dml(Message message, Consumer<Dml> consumer) {
|
||||||
|
if (message == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<CanalEntry.Entry> entries = message.getEntries();
|
||||||
|
|
||||||
|
for (CanalEntry.Entry entry : entries) {
|
||||||
|
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
|
||||||
|
|| entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CanalEntry.RowChange rowChange;
|
||||||
|
try {
|
||||||
|
rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
|
||||||
|
CanalEntry.EventType eventType = rowChange.getEventType();
|
||||||
|
|
||||||
|
final Dml dml = new Dml();
|
||||||
|
dml.setDatabase(entry.getHeader().getSchemaName());
|
||||||
|
dml.setTable(entry.getHeader().getTableName());
|
||||||
|
dml.setType(eventType.toString());
|
||||||
|
dml.setTs(System.currentTimeMillis());
|
||||||
|
dml.setSql(rowChange.getSql());
|
||||||
|
List<Map<String, Object>> data = new ArrayList<>();
|
||||||
|
List<Map<String, Object>> old = new ArrayList<>();
|
||||||
|
|
||||||
|
if (!rowChange.getIsDdl()) {
|
||||||
|
Set<String> updateSet = new HashSet<>();
|
||||||
|
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
|
||||||
|
if (eventType != CanalEntry.EventType.INSERT && eventType != CanalEntry.EventType.UPDATE
|
||||||
|
&& eventType != CanalEntry.EventType.DELETE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> row = new LinkedHashMap<>();
|
||||||
|
List<CanalEntry.Column> columns;
|
||||||
|
|
||||||
|
if (eventType == CanalEntry.EventType.DELETE) {
|
||||||
|
columns = rowData.getBeforeColumnsList();
|
||||||
|
} else {
|
||||||
|
columns = rowData.getAfterColumnsList();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (CanalEntry.Column column : columns) {
|
||||||
|
row.put(column.getName(),
|
||||||
|
JdbcTypeUtil.typeConvert(dml.getTable(),
|
||||||
|
column.getName(),
|
||||||
|
column.getValue(),
|
||||||
|
column.getSqlType(),
|
||||||
|
column.getMysqlType()));
|
||||||
|
// 获取update为true的字段
|
||||||
|
if (column.getUpdated()) {
|
||||||
|
updateSet.add(column.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!row.isEmpty()) {
|
||||||
|
data.add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventType == CanalEntry.EventType.UPDATE) {
|
||||||
|
Map<String, Object> rowOld = new LinkedHashMap<>();
|
||||||
|
for (CanalEntry.Column column : rowData.getBeforeColumnsList()) {
|
||||||
|
if (updateSet.contains(column.getName())) {
|
||||||
|
rowOld.put(column.getName(),
|
||||||
|
JdbcTypeUtil.typeConvert(dml.getTable(),
|
||||||
|
column.getName(),
|
||||||
|
column.getValue(),
|
||||||
|
column.getSqlType(),
|
||||||
|
column.getMysqlType()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update操作将记录修改前的值
|
||||||
|
if (!rowOld.isEmpty()) {
|
||||||
|
old.add(rowOld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!data.isEmpty()) {
|
||||||
|
dml.setData(data);
|
||||||
|
}
|
||||||
|
if (!old.isEmpty()) {
|
||||||
|
dml.setOld(old);
|
||||||
|
}
|
||||||
|
consumer.accept(dml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Consumer<T> {
|
||||||
|
|
||||||
|
void accept(T t);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.support;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ ElementType.TYPE })
|
||||||
|
public @interface SPI {
|
||||||
|
|
||||||
|
// Default SPI name
|
||||||
|
String value() default "";
|
||||||
|
}
|
||||||
101
client-adapter/hbase/pom.xml
Normal file
101
client-adapter/hbase/pom.xml
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<?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.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.hbase</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>canal client adapter hbase 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.17</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.hbase</groupId>
|
||||||
|
<artifactId>hbase-client</artifactId>
|
||||||
|
<version>1.1.2</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-log4j12</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>12.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<addMavenDescriptor>true</addMavenDescriptor>
|
||||||
|
</archive>
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/hbase-mapping/**</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-antrun-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>run</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<tasks>
|
||||||
|
<copy todir="${project.basedir}/../../client-launcher/target/canal_client/conf/hbase-mapping" overwrite="true" >
|
||||||
|
<fileset dir="${project.basedir}/src/main/resources/hbase-mapping" erroronmissingdir="true">
|
||||||
|
<include name="*.conf"/>
|
||||||
|
<include name="*.yml"/>
|
||||||
|
</fileset>
|
||||||
|
</copy>
|
||||||
|
</tasks>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<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>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.hbase.HBaseConfiguration;
|
||||||
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
|
||||||
|
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.service.HbaseSyncService;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration;
|
||||||
|
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.SPI;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HBase外部适配器
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午8:45:38
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@SPI("hbase")
|
||||||
|
public class HbaseAdapter implements CanalOuterAdapter {
|
||||||
|
|
||||||
|
private static volatile Map<String, MappingConfig> mappingConfigCache = null;
|
||||||
|
|
||||||
|
private Connection conn;
|
||||||
|
private HbaseSyncService hbaseSyncService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(CanalOuterAdapterConfiguration configuration) {
|
||||||
|
try {
|
||||||
|
if (mappingConfigCache == null) {
|
||||||
|
synchronized (MappingConfig.class) {
|
||||||
|
if (mappingConfigCache == null) {
|
||||||
|
Map<String, MappingConfig> hbaseMapping = MappingConfigLoader.load();
|
||||||
|
mappingConfigCache = new HashMap<>();
|
||||||
|
for (MappingConfig mappingConfig : hbaseMapping.values()) {
|
||||||
|
mappingConfigCache.put(mappingConfig.getHbaseOrm().getDatabase() + "-"
|
||||||
|
+ mappingConfig.getHbaseOrm().getTable(),
|
||||||
|
mappingConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String hosts = configuration.getZkHosts();
|
||||||
|
if (StringUtils.isEmpty(hosts)) {
|
||||||
|
hosts = configuration.getHosts();
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(hosts)) {
|
||||||
|
throw new RuntimeException("Empty zookeeper hosts");
|
||||||
|
}
|
||||||
|
String[] zkHosts = StringUtils.split(hosts, ",");
|
||||||
|
int zkPort = 0;
|
||||||
|
StringBuilder hostsWithoutPort = new StringBuilder();
|
||||||
|
for (String host : zkHosts) {
|
||||||
|
int i = host.indexOf(":");
|
||||||
|
hostsWithoutPort.append(host, 0, i);
|
||||||
|
hostsWithoutPort.append(",");
|
||||||
|
if (zkPort == 0) zkPort = Integer.parseInt(host.substring(i + 1));
|
||||||
|
}
|
||||||
|
hostsWithoutPort.deleteCharAt(hostsWithoutPort.length() - 1);
|
||||||
|
|
||||||
|
String znode = configuration.getProperties().getProperty("znodeParent");
|
||||||
|
if (StringUtils.isEmpty(znode)) {
|
||||||
|
znode = "/hbase";
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuration hbaseConfig = HBaseConfiguration.create();
|
||||||
|
hbaseConfig.set("hbase.zookeeper.quorum", hostsWithoutPort.toString());
|
||||||
|
hbaseConfig.set("hbase.zookeeper.property.clientPort", Integer.toString(zkPort));
|
||||||
|
hbaseConfig.set("zookeeper.znode.parent", znode);
|
||||||
|
conn = ConnectionFactory.createConnection(hbaseConfig);
|
||||||
|
hbaseSyncService = new HbaseSyncService(conn);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeOut(Message message) {
|
||||||
|
MessageUtil.parse4Dml(message, new MessageUtil.Consumer<Dml>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(Dml dml) {
|
||||||
|
if (dml == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String database = dml.getDatabase();
|
||||||
|
String table = dml.getTable();
|
||||||
|
MappingConfig config = mappingConfigCache.get(database + "-" + table);
|
||||||
|
hbaseSyncService.sync(config, dml);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
if (conn != null) {
|
||||||
|
try {
|
||||||
|
conn.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,337 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.config;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HBase表映射配置
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午06:45:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class MappingConfig {
|
||||||
|
|
||||||
|
private HbaseOrm hbaseOrm;
|
||||||
|
|
||||||
|
public HbaseOrm getHbaseOrm() {
|
||||||
|
return hbaseOrm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHbaseOrm(HbaseOrm hbaseOrm) {
|
||||||
|
this.hbaseOrm = hbaseOrm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate() {
|
||||||
|
if (hbaseOrm.database == null || hbaseOrm.database.isEmpty()) {
|
||||||
|
throw new NullPointerException("hbaseOrm.database");
|
||||||
|
}
|
||||||
|
if (hbaseOrm.table == null || hbaseOrm.table.isEmpty()) {
|
||||||
|
throw new NullPointerException("hbaseOrm.table");
|
||||||
|
}
|
||||||
|
if (hbaseOrm.hbaseTable == null || hbaseOrm.hbaseTable.isEmpty()) {
|
||||||
|
throw new NullPointerException("hbaseOrm.hbaseTable");
|
||||||
|
}
|
||||||
|
if (hbaseOrm.mode == null) {
|
||||||
|
throw new NullPointerException("hbaseOrm.mode");
|
||||||
|
}
|
||||||
|
if (hbaseOrm.rowKey != null && hbaseOrm.rowKeyColumn != null) {
|
||||||
|
throw new RuntimeException("已配置了复合主键作为RowKey,无需再指定RowKey列");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
MappingConfig config = (MappingConfig) o;
|
||||||
|
|
||||||
|
return hbaseOrm != null ? hbaseOrm.equals(config.hbaseOrm) : config.hbaseOrm == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return hbaseOrm != null ? hbaseOrm.hashCode() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ColumnItem {
|
||||||
|
|
||||||
|
private boolean isRowKey = false;
|
||||||
|
private String column;
|
||||||
|
private String family;
|
||||||
|
private String qualifier;
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
public boolean isRowKey() {
|
||||||
|
return isRowKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRowKey(boolean rowKey) {
|
||||||
|
isRowKey = rowKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColumn() {
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColumn(String column) {
|
||||||
|
this.column = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFamily() {
|
||||||
|
return family;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFamily(String family) {
|
||||||
|
this.family = family;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQualifier() {
|
||||||
|
return qualifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQualifier(String qualifier) {
|
||||||
|
this.qualifier = qualifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
ColumnItem that = (ColumnItem) o;
|
||||||
|
return Objects.equals(column, that.column);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
|
||||||
|
return Objects.hash(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Mode {
|
||||||
|
STRING("STRING"), NATIVE("NATIVE"), PHOENIX("PHOENIX");
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mode(String type){
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HbaseOrm {
|
||||||
|
|
||||||
|
private Mode mode = Mode.STRING;
|
||||||
|
private String database;
|
||||||
|
private String table;
|
||||||
|
private String hbaseTable;
|
||||||
|
private String family = "CF";
|
||||||
|
private boolean uppercaseQualifier = true;
|
||||||
|
private boolean autoCreateTable = false; // 同步时HBase中表不存在的情况下自动建表
|
||||||
|
private String rowKey; // 指定复合主键为rowKey
|
||||||
|
private Map<String, String> columns;
|
||||||
|
private ColumnItem rowKeyColumn;
|
||||||
|
private String etlCondition;
|
||||||
|
|
||||||
|
private Map<String, ColumnItem> columnItems = new LinkedHashMap<>();
|
||||||
|
private Set<String> families = new LinkedHashSet<>();
|
||||||
|
private int readBatch = 5000;
|
||||||
|
private int commitBatch = 5000;
|
||||||
|
|
||||||
|
public Mode getMode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMode(Mode mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 getHbaseTable() {
|
||||||
|
return hbaseTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHbaseTable(String hbaseTable) {
|
||||||
|
this.hbaseTable = hbaseTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getColumns() {
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAutoCreateTable() {
|
||||||
|
return autoCreateTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoCreateTable(boolean autoCreateTable) {
|
||||||
|
this.autoCreateTable = autoCreateTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 String getRowKey() {
|
||||||
|
return rowKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRowKey(String rowKey) {
|
||||||
|
this.rowKey = rowKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEtlCondition() {
|
||||||
|
return etlCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEtlCondition(String etlCondition) {
|
||||||
|
this.etlCondition = etlCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColumns(Map<String, String> columns) {
|
||||||
|
this.columns = columns;
|
||||||
|
|
||||||
|
if (columns != null) {
|
||||||
|
for (Map.Entry<String, String> columnField : columns.entrySet()) {
|
||||||
|
String field = columnField.getValue();
|
||||||
|
String type = null;
|
||||||
|
if (field != null) {
|
||||||
|
// 解析类型
|
||||||
|
int i = field.indexOf("$");
|
||||||
|
if (i > -1) {
|
||||||
|
type = field.substring(i + 1);
|
||||||
|
field = field.substring(0, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ColumnItem columnItem = new ColumnItem();
|
||||||
|
columnItem.setColumn(columnField.getKey());
|
||||||
|
columnItem.setType(type);
|
||||||
|
if ("rowKey".equalsIgnoreCase(field)) {
|
||||||
|
columnItem.setRowKey(true);
|
||||||
|
rowKeyColumn = columnItem;
|
||||||
|
} else {
|
||||||
|
if (field == null || field.equals("")) {
|
||||||
|
columnItem.setFamily(family);
|
||||||
|
columnItem.setQualifier(columnField.getKey());
|
||||||
|
} else {
|
||||||
|
int len = field.indexOf(":");
|
||||||
|
if (len > -1) {
|
||||||
|
columnItem.setFamily(field.substring(0, len));
|
||||||
|
columnItem.setQualifier(field.substring(len + 1));
|
||||||
|
} else {
|
||||||
|
columnItem.setFamily(family);
|
||||||
|
columnItem.setQualifier(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (uppercaseQualifier) {
|
||||||
|
columnItem.setQualifier(columnItem.getQualifier().toUpperCase());
|
||||||
|
}
|
||||||
|
families.add(columnItem.getFamily());
|
||||||
|
}
|
||||||
|
|
||||||
|
columnItems.put(columnField.getKey(), columnItem);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.columns = new LinkedHashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFamily() {
|
||||||
|
return family;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFamily(String family) {
|
||||||
|
this.family = family;
|
||||||
|
if (family == null) {
|
||||||
|
this.family = "CF";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUppercaseQualifier() {
|
||||||
|
return uppercaseQualifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUppercaseQualifier(boolean uppercaseQualifier) {
|
||||||
|
this.uppercaseQualifier = uppercaseQualifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColumnItem getRowKeyColumn() {
|
||||||
|
return rowKeyColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRowKeyColumn(ColumnItem rowKeyColumn) {
|
||||||
|
this.rowKeyColumn = rowKeyColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, ColumnItem> getColumnItems() {
|
||||||
|
return columnItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColumnItems(Map<String, ColumnItem> columnItems) {
|
||||||
|
this.columnItems = columnItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getFamilies() {
|
||||||
|
return families;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFamilies(Set<String> families) {
|
||||||
|
this.families = families;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
HbaseOrm hbaseOrm = (HbaseOrm) o;
|
||||||
|
|
||||||
|
if (table != null ? !table.equals(hbaseOrm.table) : hbaseOrm.table != null) return false;
|
||||||
|
return hbaseTable != null ? hbaseTable.equals(hbaseOrm.hbaseTable) : hbaseOrm.hbaseTable == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = table != null ? table.hashCode() : 0;
|
||||||
|
result = 31 * result + (hbaseTable != null ? hbaseTable.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
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.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HBase表映射配置加载器
|
||||||
|
* <p>
|
||||||
|
* 配置统一从hbase-mapping/configs.conf文件作为入口, 该文件包含所有表映射配置的名称或者文件名列表。
|
||||||
|
* 每个对应的表配置可以yml配置文件或者以database.table为配置名的简化形式
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午06:45:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class MappingConfigLoader {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class);
|
||||||
|
|
||||||
|
private static final String BASE_PATH = "hbase-mapping/";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载HBase表映射配置
|
||||||
|
*
|
||||||
|
* @return 配置名/配置文件名--对象
|
||||||
|
*/
|
||||||
|
public static Map<String, MappingConfig> load() {
|
||||||
|
logger.info("## Start loading mapping config ... ");
|
||||||
|
String mappingConfigContent = readConfigContent(BASE_PATH + "configs.conf");
|
||||||
|
|
||||||
|
Map<String, MappingConfig> result = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
String[] configLines = mappingConfigContent.split("\n");
|
||||||
|
for (String c : configLines) {
|
||||||
|
if (c == null) {
|
||||||
|
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.HbaseOrm hbaseOrm = new MappingConfig.HbaseOrm();
|
||||||
|
hbaseOrm.setHbaseTable(dbTable[0].toUpperCase() + "." + dbTable[1].toUpperCase());
|
||||||
|
hbaseOrm.setAutoCreateTable(true);
|
||||||
|
hbaseOrm.setDatabase(dbTable[0]);
|
||||||
|
hbaseOrm.setTable(dbTable[1]);
|
||||||
|
hbaseOrm.setMode(MappingConfig.Mode.STRING);
|
||||||
|
hbaseOrm.setRowKey(rowKey);
|
||||||
|
// 有定义rowKey
|
||||||
|
if (rowKey != null) {
|
||||||
|
MappingConfig.ColumnItem columnItem = new MappingConfig.ColumnItem();
|
||||||
|
columnItem.setRowKey(true);
|
||||||
|
columnItem.setColumn(rowKey);
|
||||||
|
hbaseOrm.setRowKeyColumn(columnItem);
|
||||||
|
}
|
||||||
|
config.setHbaseOrm(hbaseOrm);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException(String.format("配置项[%s]内容为空, 或格式不符合database.table", c));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // 配置文件配置
|
||||||
|
config = new Yaml().loadAs(configContent, MappingConfig.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
config.validate();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("ERROR Config: " + c + " " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
result.put(c, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("## Mapping config loaded");
|
||||||
|
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,418 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.service;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.hbase.support.*;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.Dml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HBase同步操作业务
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午06:45:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class HbaseSyncService {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
private HbaseTemplate hbaseTemplate; // HBase操作模板
|
||||||
|
|
||||||
|
public HbaseSyncService(Connection conn){
|
||||||
|
hbaseTemplate = new HbaseTemplate(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sync(MappingConfig config, Dml dml) {
|
||||||
|
try {
|
||||||
|
if (config != null) {
|
||||||
|
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.isDebugEnabled()) {
|
||||||
|
String res = dml.toString();
|
||||||
|
logger.debug(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入操作
|
||||||
|
*
|
||||||
|
* @param config 配置项
|
||||||
|
* @param dml DML数据
|
||||||
|
*/
|
||||||
|
private void insert(MappingConfig config, Dml dml) {
|
||||||
|
List<Map<String, Object>> data = dml.getData();
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm();
|
||||||
|
|
||||||
|
// if (!validHTable(config)) {
|
||||||
|
// logger.error("HBase table '{}' not exists", hbaseOrm.getHbaseTable());
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
int i = 1;
|
||||||
|
boolean complete = false;
|
||||||
|
List<HRow> rows = new ArrayList<>();
|
||||||
|
for (Map<String, Object> r : data) {
|
||||||
|
HRow hRow = new HRow();
|
||||||
|
|
||||||
|
// 拼接复合rowKey
|
||||||
|
if (hbaseOrm.getRowKey() != null) {
|
||||||
|
String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(",");
|
||||||
|
String rowKeyVale = getRowKeys(rowKeyColumns, r);
|
||||||
|
// params.put("rowKey", Bytes.toBytes(rowKeyVale));
|
||||||
|
hRow.setRowKey(Bytes.toBytes(rowKeyVale));
|
||||||
|
}
|
||||||
|
|
||||||
|
convertData2Row(hbaseOrm, hRow, r);
|
||||||
|
if (hRow.getRowKey() == null) {
|
||||||
|
throw new RuntimeException("empty rowKey");
|
||||||
|
}
|
||||||
|
rows.add(hRow);
|
||||||
|
complete = false;
|
||||||
|
if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rows.isEmpty()) {
|
||||||
|
hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows);
|
||||||
|
rows.clear();
|
||||||
|
complete = true;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (!complete && !rows.isEmpty()) {
|
||||||
|
hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将Map数据转换为HRow行数据
|
||||||
|
*
|
||||||
|
* @param hbaseOrm hbase映射配置
|
||||||
|
* @param hRow 行对象
|
||||||
|
* @param data Map数据
|
||||||
|
*/
|
||||||
|
private static void convertData2Row(MappingConfig.HbaseOrm hbaseOrm, HRow hRow, Map<String, Object> data) {
|
||||||
|
Map<String, MappingConfig.ColumnItem> columnItems = hbaseOrm.getColumnItems();
|
||||||
|
int i = 0;
|
||||||
|
for (Map.Entry<String, Object> entry : data.entrySet()) {
|
||||||
|
if (entry.getValue() != null) {
|
||||||
|
MappingConfig.ColumnItem columnItem = columnItems.get(entry.getKey());
|
||||||
|
|
||||||
|
byte[] bytes = typeConvert(columnItem, hbaseOrm, entry.getValue());
|
||||||
|
|
||||||
|
if (columnItem == null) {
|
||||||
|
String familyName = hbaseOrm.getFamily();
|
||||||
|
String qualifier = entry.getKey();
|
||||||
|
if (hbaseOrm.isUppercaseQualifier()) {
|
||||||
|
qualifier = qualifier.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hbaseOrm.getRowKey() == null && i == 0) {
|
||||||
|
hRow.setRowKey(bytes);
|
||||||
|
} else {
|
||||||
|
hRow.addCell(familyName, qualifier, bytes);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (columnItem.isRowKey()) {
|
||||||
|
// row.put("rowKey", bytes);
|
||||||
|
hRow.setRowKey(bytes);
|
||||||
|
} else {
|
||||||
|
hRow.addCell(columnItem.getFamily(), columnItem.getQualifier(), bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新操作
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* @param dml
|
||||||
|
*/
|
||||||
|
private void update(MappingConfig config, Dml dml) {
|
||||||
|
List<Map<String, Object>> data = dml.getData();
|
||||||
|
List<Map<String, Object>> old = dml.getOld();
|
||||||
|
if (old == null || old.isEmpty() || data == null || data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm();
|
||||||
|
|
||||||
|
// if (!validHTable(config)) {
|
||||||
|
// logger.error("HBase table '{}' not exists", hbaseOrm.getHbaseTable());
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
MappingConfig.ColumnItem rowKeyColumn = hbaseOrm.getRowKeyColumn();
|
||||||
|
int index = 0;
|
||||||
|
int i = 1;
|
||||||
|
boolean complete = false;
|
||||||
|
List<HRow> rows = new ArrayList<>();
|
||||||
|
out: for (Map<String, Object> r : data) {
|
||||||
|
byte[] rowKeyBytes;
|
||||||
|
|
||||||
|
if (hbaseOrm.getRowKey() != null) {
|
||||||
|
String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(",");
|
||||||
|
|
||||||
|
// 判断是否有复合主键修改
|
||||||
|
for (String updateColumn : old.get(index).keySet()) {
|
||||||
|
for (String rowKeyColumnName : rowKeyColumns) {
|
||||||
|
if (rowKeyColumnName.equalsIgnoreCase(updateColumn)) {
|
||||||
|
// 调用删除插入操作
|
||||||
|
deleteAndInsert(config, dml);
|
||||||
|
continue out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String rowKeyVale = getRowKeys(rowKeyColumns, r);
|
||||||
|
rowKeyBytes = Bytes.toBytes(rowKeyVale);
|
||||||
|
} else if (rowKeyColumn == null) {
|
||||||
|
Map<String, Object> rowKey = data.get(0);
|
||||||
|
rowKeyBytes = typeConvert(null, hbaseOrm, rowKey.values().iterator().next());
|
||||||
|
} else {
|
||||||
|
rowKeyBytes = typeConvert(rowKeyColumn, hbaseOrm, r.get(rowKeyColumn.getColumn()));
|
||||||
|
}
|
||||||
|
if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空");
|
||||||
|
|
||||||
|
Map<String, MappingConfig.ColumnItem> columnItems = hbaseOrm.getColumnItems();
|
||||||
|
HRow hRow = new HRow(rowKeyBytes);
|
||||||
|
for (String updateColumn : old.get(index).keySet()) {
|
||||||
|
MappingConfig.ColumnItem columnItem = columnItems.get(updateColumn);
|
||||||
|
if (columnItem == null) {
|
||||||
|
String family = hbaseOrm.getFamily();
|
||||||
|
String qualifier = updateColumn;
|
||||||
|
if (hbaseOrm.isUppercaseQualifier()) {
|
||||||
|
qualifier = qualifier.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
Object newVal = r.get(updateColumn);
|
||||||
|
|
||||||
|
if (newVal == null) {
|
||||||
|
hRow.addCell(family, qualifier, null);
|
||||||
|
} else {
|
||||||
|
hRow.addCell(family, qualifier, typeConvert(null, hbaseOrm, newVal));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 排除修改id的情况
|
||||||
|
if (columnItem.isRowKey()) continue;
|
||||||
|
|
||||||
|
Object newVal = r.get(updateColumn);
|
||||||
|
if (newVal == null) {
|
||||||
|
hRow.addCell(columnItem.getFamily(), columnItem.getQualifier(), null);
|
||||||
|
} else {
|
||||||
|
hRow.addCell(columnItem.getFamily(),
|
||||||
|
columnItem.getQualifier(),
|
||||||
|
typeConvert(columnItem, hbaseOrm, newVal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rows.add(hRow);
|
||||||
|
complete = false;
|
||||||
|
if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rows.isEmpty()) {
|
||||||
|
hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows);
|
||||||
|
rows.clear();
|
||||||
|
complete = true;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
if (!complete && !rows.isEmpty()) {
|
||||||
|
hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void delete(MappingConfig config, Dml dml) {
|
||||||
|
List<Map<String, Object>> data = dml.getData();
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm();
|
||||||
|
|
||||||
|
// if (!validHTable(config)) {
|
||||||
|
// logger.error("HBase table '{}' not exists", hbaseOrm.getHbaseTable());
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
MappingConfig.ColumnItem rowKeyColumn = hbaseOrm.getRowKeyColumn();
|
||||||
|
boolean complete = false;
|
||||||
|
int i = 1;
|
||||||
|
Set<byte[]> rowKeys = new HashSet<>();
|
||||||
|
for (Map<String, Object> r : data) {
|
||||||
|
byte[] rowKeyBytes;
|
||||||
|
|
||||||
|
if (hbaseOrm.getRowKey() != null) {
|
||||||
|
String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(",");
|
||||||
|
String rowKeyVale = getRowKeys(rowKeyColumns, r);
|
||||||
|
rowKeyBytes = Bytes.toBytes(rowKeyVale);
|
||||||
|
} else if (rowKeyColumn == null) {
|
||||||
|
// 如果不需要类型转换
|
||||||
|
Map<String, Object> rowKey = data.get(0);
|
||||||
|
rowKeyBytes = typeConvert(null, hbaseOrm, rowKey.values().iterator().next());
|
||||||
|
} else {
|
||||||
|
Object val = r.get(rowKeyColumn.getColumn());
|
||||||
|
rowKeyBytes = typeConvert(rowKeyColumn, hbaseOrm, val);
|
||||||
|
}
|
||||||
|
if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空");
|
||||||
|
rowKeys.add(rowKeyBytes);
|
||||||
|
complete = false;
|
||||||
|
if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rowKeys.isEmpty()) {
|
||||||
|
hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys);
|
||||||
|
rowKeys.clear();
|
||||||
|
complete = true;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (!complete && !rowKeys.isEmpty()) {
|
||||||
|
hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteAndInsert(MappingConfig config, Dml dml) {
|
||||||
|
List<Map<String, Object>> data = dml.getData();
|
||||||
|
List<Map<String, Object>> old = dml.getOld();
|
||||||
|
if (old == null || old.isEmpty() || data == null || data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm();
|
||||||
|
|
||||||
|
String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(",");
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
int i = 1;
|
||||||
|
boolean complete = false;
|
||||||
|
Set<byte[]> rowKeys = new HashSet<>();
|
||||||
|
List<HRow> rows = new ArrayList<>();
|
||||||
|
for (Map<String, Object> r : data) {
|
||||||
|
// 拼接老的rowKey
|
||||||
|
List<String> updateSubRowKey = new ArrayList<>();
|
||||||
|
for (String rowKeyColumnName : rowKeyColumns) {
|
||||||
|
for (String updateColumn : old.get(index).keySet()) {
|
||||||
|
if (rowKeyColumnName.equalsIgnoreCase(updateColumn)) {
|
||||||
|
updateSubRowKey.add(rowKeyColumnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateSubRowKey.isEmpty()) {
|
||||||
|
throw new RuntimeException("没有更新复合主键的RowKey");
|
||||||
|
}
|
||||||
|
StringBuilder oldRowKey = new StringBuilder();
|
||||||
|
StringBuilder newRowKey = new StringBuilder();
|
||||||
|
for (String rowKeyColumnName : rowKeyColumns) {
|
||||||
|
newRowKey.append(r.get(rowKeyColumnName).toString()).append("|");
|
||||||
|
if (!updateSubRowKey.contains(rowKeyColumnName)) {
|
||||||
|
// 从data取
|
||||||
|
oldRowKey.append(r.get(rowKeyColumnName).toString()).append("|");
|
||||||
|
} else {
|
||||||
|
// 从old取
|
||||||
|
oldRowKey.append(old.get(index).get(rowKeyColumnName).toString()).append("|");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int len = newRowKey.length();
|
||||||
|
newRowKey.delete(len - 1, len);
|
||||||
|
len = oldRowKey.length();
|
||||||
|
oldRowKey.delete(len - 1, len);
|
||||||
|
byte[] newRowKeyBytes = Bytes.toBytes(newRowKey.toString());
|
||||||
|
byte[] oldRowKeyBytes = Bytes.toBytes(oldRowKey.toString());
|
||||||
|
|
||||||
|
rowKeys.add(oldRowKeyBytes);
|
||||||
|
HRow row = new HRow(newRowKeyBytes);
|
||||||
|
convertData2Row(hbaseOrm, row, r);
|
||||||
|
rows.add(row);
|
||||||
|
complete = false;
|
||||||
|
if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rows.isEmpty()) {
|
||||||
|
hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys);
|
||||||
|
|
||||||
|
hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows);
|
||||||
|
rowKeys.clear();
|
||||||
|
rows.clear();
|
||||||
|
complete = true;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
if (!complete && !rows.isEmpty()) {
|
||||||
|
hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys);
|
||||||
|
hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据对应的类型进行转换
|
||||||
|
*
|
||||||
|
* @param columnItem 列项配置
|
||||||
|
* @param hbaseOrm hbase映射配置
|
||||||
|
* @param value 值
|
||||||
|
* @return 复合字段rowKey
|
||||||
|
*/
|
||||||
|
private static byte[] typeConvert(MappingConfig.ColumnItem columnItem, MappingConfig.HbaseOrm hbaseOrm,
|
||||||
|
Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] bytes = null;
|
||||||
|
if (columnItem == null || columnItem.getType() == null || "".equals(columnItem.getType())) {
|
||||||
|
if (MappingConfig.Mode.STRING == hbaseOrm.getMode()) {
|
||||||
|
bytes = Bytes.toBytes(value.toString());
|
||||||
|
} else if (MappingConfig.Mode.NATIVE == hbaseOrm.getMode()) {
|
||||||
|
bytes = TypeUtil.toBytes(value);
|
||||||
|
} else if (MappingConfig.Mode.PHOENIX == hbaseOrm.getMode()) {
|
||||||
|
PhType phType = PhType.getType(value.getClass());
|
||||||
|
bytes = PhTypeUtil.toBytes(value, phType);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (hbaseOrm.getMode() == MappingConfig.Mode.STRING) {
|
||||||
|
bytes = Bytes.toBytes(value.toString());
|
||||||
|
} else if (hbaseOrm.getMode() == MappingConfig.Mode.NATIVE) {
|
||||||
|
Type type = Type.getType(columnItem.getType());
|
||||||
|
bytes = TypeUtil.toBytes(value, type);
|
||||||
|
} else if (hbaseOrm.getMode() == MappingConfig.Mode.PHOENIX) {
|
||||||
|
PhType phType = PhType.getType(columnItem.getType());
|
||||||
|
bytes = PhTypeUtil.toBytes(value, phType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取复合字段作为rowKey的拼接
|
||||||
|
*
|
||||||
|
* @param rowKeyColumns 复合rowK对应的字段
|
||||||
|
* @param data 数据
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String getRowKeys(String[] rowKeyColumns, Map<String, Object> data) {
|
||||||
|
StringBuilder rowKeyValue = new StringBuilder();
|
||||||
|
for (String rowKeyColumnName : rowKeyColumns) {
|
||||||
|
Object obj = data.get(rowKeyColumnName);
|
||||||
|
if (obj != null) {
|
||||||
|
rowKeyValue.append(obj.toString());
|
||||||
|
}
|
||||||
|
rowKeyValue.append("|");
|
||||||
|
}
|
||||||
|
int len = rowKeyValue.length();
|
||||||
|
if (len > 0) {
|
||||||
|
rowKeyValue.delete(len - 1, len);
|
||||||
|
}
|
||||||
|
return rowKeyValue.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.support;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HBase操作对象类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午10:12:34
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class HRow {
|
||||||
|
|
||||||
|
private byte[] rowKey;
|
||||||
|
private List<HCell> cells = new ArrayList<>();
|
||||||
|
|
||||||
|
public HRow(){
|
||||||
|
}
|
||||||
|
|
||||||
|
public HRow(byte[] rowKey){
|
||||||
|
this.rowKey = rowKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getRowKey() {
|
||||||
|
return rowKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRowKey(byte[] rowKey) {
|
||||||
|
this.rowKey = rowKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<HCell> getCells() {
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCells(List<HCell> cells) {
|
||||||
|
this.cells = cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCell(String family, String qualifier, byte[] value) {
|
||||||
|
HCell hCell = new HCell(family, qualifier, value);
|
||||||
|
cells.add(hCell);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HCell {
|
||||||
|
|
||||||
|
private String family;
|
||||||
|
private String qualifier;
|
||||||
|
private byte[] value;
|
||||||
|
|
||||||
|
public HCell(){
|
||||||
|
}
|
||||||
|
|
||||||
|
public HCell(String family, String qualifier, byte[] value){
|
||||||
|
this.family = family;
|
||||||
|
this.qualifier = qualifier;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFamily() {
|
||||||
|
return family;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFamily(String family) {
|
||||||
|
this.family = family;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQualifier() {
|
||||||
|
return qualifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQualifier(String qualifier) {
|
||||||
|
this.qualifier = qualifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(byte[] value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.support;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hbase.HColumnDescriptor;
|
||||||
|
import org.apache.hadoop.hbase.HTableDescriptor;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.client.*;
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HBase操作模板
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午10:12:34
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class HbaseTemplate {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
private Connection conn;
|
||||||
|
|
||||||
|
public HbaseTemplate(Connection conn){
|
||||||
|
this.conn = conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean tableExists(String tableName) {
|
||||||
|
try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) {
|
||||||
|
|
||||||
|
return admin.tableExists(TableName.valueOf(tableName));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createTable(String tableName, String... familyNames) {
|
||||||
|
try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) {
|
||||||
|
|
||||||
|
HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(tableName));
|
||||||
|
// 添加列簇
|
||||||
|
if (familyNames != null) {
|
||||||
|
for (String familyName : familyNames) {
|
||||||
|
HColumnDescriptor hcd = new HColumnDescriptor(familyName);
|
||||||
|
desc.addFamily(hcd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
admin.createTable(desc);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableTable(String tableName) {
|
||||||
|
try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) {
|
||||||
|
admin.disableTable(tableName);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteTable(String tableName) {
|
||||||
|
try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) {
|
||||||
|
if (admin.isTableEnabled(tableName)) {
|
||||||
|
disableTable(tableName);
|
||||||
|
}
|
||||||
|
admin.deleteTable(tableName);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入一行数据
|
||||||
|
*
|
||||||
|
* @param tableName 表名
|
||||||
|
* @param hRow 行数据对象
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
public Boolean put(String tableName, HRow hRow) {
|
||||||
|
boolean flag = false;
|
||||||
|
try {
|
||||||
|
HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));
|
||||||
|
Put put = new Put(hRow.getRowKey());
|
||||||
|
for (HRow.HCell hCell : hRow.getCells()) {
|
||||||
|
put.addColumn(Bytes.toBytes(hCell.getFamily()), Bytes.toBytes(hCell.getQualifier()), hCell.getValue());
|
||||||
|
}
|
||||||
|
table.put(put);
|
||||||
|
flag = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量插入
|
||||||
|
*
|
||||||
|
* @param tableName 表名
|
||||||
|
* @param rows 行数据对象集合
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
public Boolean puts(String tableName, List<HRow> rows) {
|
||||||
|
boolean flag = false;
|
||||||
|
try {
|
||||||
|
HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));
|
||||||
|
List<Put> puts = new ArrayList<>();
|
||||||
|
for (HRow hRow : rows) {
|
||||||
|
Put put = new Put(hRow.getRowKey());
|
||||||
|
for (HRow.HCell hCell : hRow.getCells()) {
|
||||||
|
put.addColumn(Bytes.toBytes(hCell.getFamily()),
|
||||||
|
Bytes.toBytes(hCell.getQualifier()),
|
||||||
|
hCell.getValue());
|
||||||
|
}
|
||||||
|
puts.add(put);
|
||||||
|
}
|
||||||
|
if (!puts.isEmpty()) {
|
||||||
|
table.put(puts);
|
||||||
|
}
|
||||||
|
flag = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除数据
|
||||||
|
*
|
||||||
|
* @param tableName 表名
|
||||||
|
* @param rowKeys rowKey集合
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
public Boolean deletes(String tableName, Set<byte[]> rowKeys) {
|
||||||
|
boolean flag = false;
|
||||||
|
try {
|
||||||
|
HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));
|
||||||
|
List<Delete> deletes = new ArrayList<>();
|
||||||
|
for (byte[] rowKey : rowKeys) {
|
||||||
|
Delete delete = new Delete(rowKey);
|
||||||
|
deletes.add(delete);
|
||||||
|
}
|
||||||
|
if (!deletes.isEmpty()) {
|
||||||
|
table.delete(deletes);
|
||||||
|
}
|
||||||
|
flag = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.support;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.sql.Time;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phoenix类型
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午06:12:34
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public enum PhType {
|
||||||
|
DEFAULT(-1, "VARCHAR"),
|
||||||
|
UNSIGNED_INT(4, "UNSIGNED_INT"),
|
||||||
|
UNSIGNED_LONG(8, "UNSIGNED_LONG"),
|
||||||
|
UNSIGNED_TINYINT(1, "UNSIGNED_TINYINT"),
|
||||||
|
UNSIGNED_SMALLINT(2, "UNSIGNED_SMALLINT"),
|
||||||
|
UNSIGNED_FLOAT(4, "UNSIGNED_FLOAT"),
|
||||||
|
UNSIGNED_DOUBLE(8, "UNSIGNED_DOUBLE"),
|
||||||
|
INTEGER(4, "INTEGER"),
|
||||||
|
BIGINT(8, "BIGINT"),
|
||||||
|
TINYINT(1, "TINYINT"),
|
||||||
|
SMALLINT(2, "SMALLINT"),
|
||||||
|
FLOAT(4, "FLOAT"),
|
||||||
|
DOUBLE(8, "DOUBLE"),
|
||||||
|
DECIMAL(-1, "DECIMAL"),
|
||||||
|
BOOLEAN(1, "BOOLEAN"),
|
||||||
|
UNSIGNED_TIME(8, "UNSIGNED_TIME"),
|
||||||
|
UNSIGNED_DATE(8, "UNSIGNED_DATE"),
|
||||||
|
UNSIGNED_TIMESTAMP(12, "UNSIGNED_TIMESTAMP"),
|
||||||
|
TIME(8, "TIME"),
|
||||||
|
DATE(8, "DATE"),
|
||||||
|
TIMESTAMP(12, "TIMESTAMP"),
|
||||||
|
VARCHAR(-1, "VARCHAR"),
|
||||||
|
VARBINARY(-1, "VARBINARY");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* -1:长度可变
|
||||||
|
*/
|
||||||
|
private int len;
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
PhType(int len, String type) {
|
||||||
|
this.len = len;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLen() {
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PhType getType(Class<?> javaType) {
|
||||||
|
if (javaType == null) return DEFAULT;
|
||||||
|
PhType phType;
|
||||||
|
if (Integer.class.isAssignableFrom(javaType) || int.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = INTEGER;
|
||||||
|
} else if (Long.class.isAssignableFrom(javaType) || long.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = BIGINT;
|
||||||
|
} else if (Byte.class.isAssignableFrom(javaType) || byte.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = TINYINT;
|
||||||
|
} else if (Short.class.isAssignableFrom(javaType) || short.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = SMALLINT;
|
||||||
|
} else if (Float.class.isAssignableFrom(javaType) || float.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = FLOAT;
|
||||||
|
} else if (Double.class.isAssignableFrom(javaType) || double.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = DOUBLE;
|
||||||
|
} else if (Boolean.class.isAssignableFrom(javaType) || boolean.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = BOOLEAN;
|
||||||
|
} else if (java.sql.Date.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = DATE;
|
||||||
|
} else if (Time.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = DATE;
|
||||||
|
} else if (Timestamp.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = TIMESTAMP;
|
||||||
|
} else if (Date.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = DATE;
|
||||||
|
} else if (byte[].class.isAssignableFrom(javaType)) {
|
||||||
|
phType = VARBINARY;
|
||||||
|
} else if (String.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = VARCHAR;
|
||||||
|
} else if (BigDecimal.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = DECIMAL;
|
||||||
|
} else if (BigInteger.class.isAssignableFrom(javaType)) {
|
||||||
|
phType = UNSIGNED_LONG;
|
||||||
|
} else {
|
||||||
|
phType = DEFAULT;
|
||||||
|
}
|
||||||
|
return phType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PhType getType(String type) {
|
||||||
|
if (type == null) return DEFAULT;
|
||||||
|
PhType phType;
|
||||||
|
if (type.equalsIgnoreCase(UNSIGNED_INT.type)) {
|
||||||
|
phType = UNSIGNED_INT;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_LONG.type)) {
|
||||||
|
phType = UNSIGNED_LONG;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_TINYINT.type)) {
|
||||||
|
phType = UNSIGNED_TINYINT;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_SMALLINT.type)) {
|
||||||
|
phType = UNSIGNED_SMALLINT;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_FLOAT.type)) {
|
||||||
|
phType = UNSIGNED_FLOAT;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_DOUBLE.type)) {
|
||||||
|
phType = UNSIGNED_DOUBLE;
|
||||||
|
} else if (type.equalsIgnoreCase(INTEGER.type)) {
|
||||||
|
phType = INTEGER;
|
||||||
|
} else if (type.equalsIgnoreCase(BIGINT.type)) {
|
||||||
|
phType = BIGINT;
|
||||||
|
} else if (type.equalsIgnoreCase(TINYINT.type)) {
|
||||||
|
phType = TINYINT;
|
||||||
|
} else if (type.equalsIgnoreCase(SMALLINT.type)) {
|
||||||
|
phType = SMALLINT;
|
||||||
|
} else if (type.equalsIgnoreCase(FLOAT.type)) {
|
||||||
|
phType = FLOAT;
|
||||||
|
} else if (type.equalsIgnoreCase(DOUBLE.type)) {
|
||||||
|
phType = DOUBLE;
|
||||||
|
} else if (type.equalsIgnoreCase(BOOLEAN.type)) {
|
||||||
|
phType = BOOLEAN;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_TIME.type)) {
|
||||||
|
phType = UNSIGNED_TIME;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_DATE.type)) {
|
||||||
|
phType = UNSIGNED_DATE;
|
||||||
|
} else if (type.equalsIgnoreCase(UNSIGNED_TIMESTAMP.type)) {
|
||||||
|
phType = UNSIGNED_TIMESTAMP;
|
||||||
|
} else if (type.equalsIgnoreCase(TIME.type)) {
|
||||||
|
phType = TIME;
|
||||||
|
} else if (type.equalsIgnoreCase(DATE.type)) {
|
||||||
|
phType = DATE;
|
||||||
|
} else if (type.equalsIgnoreCase(TIMESTAMP.type)) {
|
||||||
|
phType = TIMESTAMP;
|
||||||
|
} else if (type.equalsIgnoreCase(VARCHAR.type)) {
|
||||||
|
phType = VARCHAR;
|
||||||
|
} else if (type.equalsIgnoreCase(VARBINARY.type)) {
|
||||||
|
phType = VARBINARY;
|
||||||
|
} else if (type.equalsIgnoreCase(DECIMAL.type)) {
|
||||||
|
phType = DECIMAL;
|
||||||
|
} else {
|
||||||
|
phType = DEFAULT;
|
||||||
|
}
|
||||||
|
return phType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,614 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.support;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.math.MathContext;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
|
import com.google.common.math.LongMath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phoenix类型转换工具类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午06:14:26
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class PhTypeUtil {
|
||||||
|
|
||||||
|
public static byte[] toBytes(Object v, PhType phType) {
|
||||||
|
if (v == null) return null;
|
||||||
|
byte[] b = null;
|
||||||
|
if (phType == PhType.DEFAULT) {
|
||||||
|
PhType phType1 = PhType.getType(v.getClass());
|
||||||
|
if (phType1 != null && phType1 != PhType.DEFAULT) {
|
||||||
|
toBytes(v, phType1);
|
||||||
|
}
|
||||||
|
} else if (phType == PhType.INTEGER) {
|
||||||
|
b = new byte[Bytes.SIZEOF_INT];
|
||||||
|
encodeInt(((Number) v).intValue(), b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_INT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_INT];
|
||||||
|
encodeUnsignedInt(((Number) v).intValue(), b, 0);
|
||||||
|
} else if (phType == PhType.BIGINT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_LONG];
|
||||||
|
encodeLong(((Number) v).longValue(), b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_LONG) {
|
||||||
|
b = new byte[Bytes.SIZEOF_LONG];
|
||||||
|
encodeUnsignedLong(((Number) v).longValue(), b, 0);
|
||||||
|
} else if (phType == PhType.SMALLINT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_SHORT];
|
||||||
|
encodeShort(((Number) v).shortValue(), b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_SMALLINT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_SHORT];
|
||||||
|
encodeUnsignedShort(((Number) v).shortValue(), b, 0);
|
||||||
|
} else if (phType == PhType.TINYINT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_BYTE];
|
||||||
|
encodeByte(((Number) v).byteValue(), b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_TINYINT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_BYTE];
|
||||||
|
encodeUnsignedByte(((Number) v).byteValue(), b, 0);
|
||||||
|
} else if (phType == PhType.FLOAT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_FLOAT];
|
||||||
|
encodeFloat(((Number) v).floatValue(), b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_FLOAT) {
|
||||||
|
b = new byte[Bytes.SIZEOF_FLOAT];
|
||||||
|
encodeUnsignedFloat(((Number) v).floatValue(), b, 0);
|
||||||
|
} else if (phType == PhType.DOUBLE) {
|
||||||
|
b = new byte[Bytes.SIZEOF_DOUBLE];
|
||||||
|
encodeDouble(((Number) v).doubleValue(), b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_DOUBLE) {
|
||||||
|
b = new byte[Bytes.SIZEOF_DOUBLE];
|
||||||
|
encodeUnsignedDouble(((Number) v).doubleValue(), b, 0);
|
||||||
|
} else if (phType == PhType.BOOLEAN) {
|
||||||
|
if ((Boolean) v) {
|
||||||
|
b = new byte[] { 1 };
|
||||||
|
} else {
|
||||||
|
b = new byte[] { 0 };
|
||||||
|
}
|
||||||
|
} else if (phType == PhType.TIME || phType == PhType.DATE) {
|
||||||
|
b = new byte[Bytes.SIZEOF_LONG];
|
||||||
|
encodeDate(v, b, 0);
|
||||||
|
} else if (phType == PhType.TIMESTAMP) {
|
||||||
|
b = new byte[Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT];
|
||||||
|
encodeTimestamp(v, b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_TIME || phType == PhType.UNSIGNED_DATE) {
|
||||||
|
b = new byte[Bytes.SIZEOF_LONG];
|
||||||
|
encodeUnsignedDate(v, b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_TIMESTAMP) {
|
||||||
|
b = new byte[Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT];
|
||||||
|
encodeUnsignedTimestamp(v, b, 0);
|
||||||
|
} else if (phType == PhType.VARBINARY) {
|
||||||
|
b = (byte[]) v;
|
||||||
|
} else if (phType == PhType.VARCHAR) {
|
||||||
|
b = Bytes.toBytes(v.toString());
|
||||||
|
} else if (phType == PhType.DECIMAL) {
|
||||||
|
if (v instanceof BigDecimal) {
|
||||||
|
b = encodeDecimal(v);
|
||||||
|
} else if (v instanceof Number) {
|
||||||
|
b = encodeDecimal(new BigDecimal(v.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object toObject(byte[] b, PhType phType) {
|
||||||
|
if (b == null) return null;
|
||||||
|
Object v = null;
|
||||||
|
if (phType == PhType.INTEGER) {
|
||||||
|
v = decodeInt(b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_INT) {
|
||||||
|
v = decodeUnsignedInt(b, 0);
|
||||||
|
} else if (phType == PhType.BIGINT) {
|
||||||
|
v = decodeLong(b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_LONG) {
|
||||||
|
v = decodeUnsignedLong(b, 0);
|
||||||
|
} else if (phType == PhType.SMALLINT) {
|
||||||
|
v = decodeShort(b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_SMALLINT) {
|
||||||
|
v = decodeUnsignedShort(b, 0);
|
||||||
|
} else if (phType == PhType.TINYINT) {
|
||||||
|
v = decodeByte(b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_TINYINT) {
|
||||||
|
v = decodeUnsignedByte(b, 0);
|
||||||
|
} else if (phType == PhType.FLOAT) {
|
||||||
|
v = decodeFloat(b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_FLOAT) {
|
||||||
|
v = decodeUnsignedFloat(b, 0);
|
||||||
|
} else if (phType == PhType.DOUBLE) {
|
||||||
|
v = decodeDouble(b, 0);
|
||||||
|
} else if (phType == PhType.UNSIGNED_DOUBLE) {
|
||||||
|
v = decodeUnsignedDouble(b, 0);
|
||||||
|
} else if (phType == PhType.BOOLEAN) {
|
||||||
|
checkForSufficientLength(b, 0, Bytes.SIZEOF_BOOLEAN);
|
||||||
|
if (b[0] == 1) {
|
||||||
|
v = true;
|
||||||
|
} else if (b[0] == 0) {
|
||||||
|
v = false;
|
||||||
|
}
|
||||||
|
} else if (phType == PhType.TIME || phType == PhType.DATE) {
|
||||||
|
v = new Date(decodeLong(b, 0));
|
||||||
|
} else if (phType == PhType.TIMESTAMP) {
|
||||||
|
long millisDeserialized = decodeLong(b, 0);
|
||||||
|
Timestamp ts = new Timestamp(millisDeserialized);
|
||||||
|
int nanosDeserialized = decodeUnsignedInt(b, Bytes.SIZEOF_LONG);
|
||||||
|
ts.setNanos(nanosDeserialized < 1000000 ? ts.getNanos() + nanosDeserialized : nanosDeserialized);
|
||||||
|
v = ts;
|
||||||
|
} else if (phType == PhType.UNSIGNED_TIME || phType == PhType.UNSIGNED_DATE) {
|
||||||
|
v = new Date(decodeUnsignedLong(b, 0));
|
||||||
|
} else if (phType == PhType.UNSIGNED_TIMESTAMP) {
|
||||||
|
long millisDeserialized = decodeUnsignedLong(b, 0);
|
||||||
|
Timestamp ts = new Timestamp(millisDeserialized);
|
||||||
|
int nanosDeserialized = decodeUnsignedInt(b, Bytes.SIZEOF_LONG);
|
||||||
|
ts.setNanos(nanosDeserialized < 1000000 ? ts.getNanos() + nanosDeserialized : nanosDeserialized);
|
||||||
|
v = ts;
|
||||||
|
} else if (phType == PhType.VARBINARY) {
|
||||||
|
v = b;
|
||||||
|
} else if (phType == PhType.VARCHAR || phType == PhType.DEFAULT) {
|
||||||
|
v = Bytes.toString(b);
|
||||||
|
} else if (phType == PhType.DECIMAL) {
|
||||||
|
v = decodeDecimal(b, 0, b.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int decodeInt(byte[] bytes, int o) {
|
||||||
|
checkForSufficientLength(bytes, o, Bytes.SIZEOF_INT);
|
||||||
|
int v;
|
||||||
|
v = bytes[o] ^ 0x80; // Flip sign bit back
|
||||||
|
for (int i = 1; i < Bytes.SIZEOF_INT; i++) {
|
||||||
|
v = (v << 8) + (bytes[o + i] & 0xff);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeInt(int v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_INT);
|
||||||
|
b[o + 0] = (byte) ((v >> 24) ^ 0x80); // Flip sign bit so that INTEGER is binary comparable
|
||||||
|
b[o + 1] = (byte) (v >> 16);
|
||||||
|
b[o + 2] = (byte) (v >> 8);
|
||||||
|
b[o + 3] = (byte) v;
|
||||||
|
return Bytes.SIZEOF_INT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int decodeUnsignedInt(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_INT);
|
||||||
|
|
||||||
|
int v = Bytes.toInt(b, o);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedInt(int v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_INT);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
Bytes.putInt(b, o, v);
|
||||||
|
return Bytes.SIZEOF_INT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long decodeLong(byte[] bytes, int o) {
|
||||||
|
checkForSufficientLength(bytes, o, Bytes.SIZEOF_LONG);
|
||||||
|
long v;
|
||||||
|
byte b = bytes[o];
|
||||||
|
v = b ^ 0x80; // Flip sign bit back
|
||||||
|
for (int i = 1; i < Bytes.SIZEOF_LONG; i++) {
|
||||||
|
b = bytes[o + i];
|
||||||
|
v = (v << 8) + (b & 0xff);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeLong(long v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_LONG);
|
||||||
|
b[o + 0] = (byte) ((v >> 56) ^ 0x80); // Flip sign bit so that INTEGER is binary comparable
|
||||||
|
b[o + 1] = (byte) (v >> 48);
|
||||||
|
b[o + 2] = (byte) (v >> 40);
|
||||||
|
b[o + 3] = (byte) (v >> 32);
|
||||||
|
b[o + 4] = (byte) (v >> 24);
|
||||||
|
b[o + 5] = (byte) (v >> 16);
|
||||||
|
b[o + 6] = (byte) (v >> 8);
|
||||||
|
b[o + 7] = (byte) v;
|
||||||
|
return Bytes.SIZEOF_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long decodeUnsignedLong(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_LONG);
|
||||||
|
long v = 0;
|
||||||
|
for (int i = o; i < o + Bytes.SIZEOF_LONG; i++) {
|
||||||
|
v <<= 8;
|
||||||
|
v ^= b[i] & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedLong(long v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_LONG);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
Bytes.putLong(b, o, v);
|
||||||
|
return Bytes.SIZEOF_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static short decodeShort(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT);
|
||||||
|
int v;
|
||||||
|
v = b[o] ^ 0x80; // Flip sign bit back
|
||||||
|
for (int i = 1; i < Bytes.SIZEOF_SHORT; i++) {
|
||||||
|
v = (v << 8) + (b[o + i] & 0xff);
|
||||||
|
}
|
||||||
|
return (short) v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeShort(short v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT);
|
||||||
|
b[o + 0] = (byte) ((v >> 8) ^ 0x80); // Flip sign bit so that Short is binary comparable
|
||||||
|
b[o + 1] = (byte) v;
|
||||||
|
return Bytes.SIZEOF_SHORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static short decodeUnsignedShort(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT);
|
||||||
|
short v = Bytes.toShort(b, o);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedShort(short v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
Bytes.putShort(b, o, v);
|
||||||
|
return Bytes.SIZEOF_SHORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte decodeByte(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_BYTE);
|
||||||
|
int v;
|
||||||
|
v = b[o] ^ 0x80; // Flip sign bit back
|
||||||
|
return (byte) v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeByte(byte v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_BYTE);
|
||||||
|
b[o] = (byte) (v ^ 0x80); // Flip sign bit so that Short is binary comparable
|
||||||
|
return Bytes.SIZEOF_BYTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte decodeUnsignedByte(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_BYTE);
|
||||||
|
byte v = b[o];
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedByte(byte v, byte[] b, int o) {
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
Bytes.putByte(b, o, v);
|
||||||
|
return Bytes.SIZEOF_BYTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float decodeFloat(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_INT);
|
||||||
|
int value;
|
||||||
|
value = Bytes.toInt(b, o);
|
||||||
|
value--;
|
||||||
|
value ^= (~value >> Integer.SIZE - 1) | Integer.MIN_VALUE;
|
||||||
|
return Float.intBitsToFloat(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeFloat(float v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_FLOAT);
|
||||||
|
int i = Float.floatToIntBits(v);
|
||||||
|
i = (i ^ ((i >> Integer.SIZE - 1) | Integer.MIN_VALUE)) + 1;
|
||||||
|
Bytes.putInt(b, o, i);
|
||||||
|
return Bytes.SIZEOF_FLOAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float decodeUnsignedFloat(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_FLOAT);
|
||||||
|
float v = Bytes.toFloat(b, o);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedFloat(float v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_FLOAT);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
Bytes.putFloat(b, o, v);
|
||||||
|
return Bytes.SIZEOF_FLOAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double decodeDouble(byte[] bytes, int o) {
|
||||||
|
checkForSufficientLength(bytes, o, Bytes.SIZEOF_LONG);
|
||||||
|
long l;
|
||||||
|
l = Bytes.toLong(bytes, o);
|
||||||
|
l--;
|
||||||
|
l ^= (~l >> Long.SIZE - 1) | Long.MIN_VALUE;
|
||||||
|
return Double.longBitsToDouble(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeDouble(double v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_LONG);
|
||||||
|
long l = Double.doubleToLongBits(v);
|
||||||
|
l = (l ^ ((l >> Long.SIZE - 1) | Long.MIN_VALUE)) + 1;
|
||||||
|
Bytes.putLong(b, o, l);
|
||||||
|
return Bytes.SIZEOF_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double decodeUnsignedDouble(byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_DOUBLE);
|
||||||
|
double v = Bytes.toDouble(b, o);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedDouble(double v, byte[] b, int o) {
|
||||||
|
checkForSufficientLength(b, o, Bytes.SIZEOF_DOUBLE);
|
||||||
|
if (v < 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
Bytes.putDouble(b, o, v);
|
||||||
|
return Bytes.SIZEOF_DOUBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeDate(Object v, byte[] b, int o) {
|
||||||
|
if (v instanceof Date) {
|
||||||
|
encodeLong(((Date) v).getTime(), b, 0);
|
||||||
|
} else if (v instanceof String) {
|
||||||
|
String dateStr = (String) v;
|
||||||
|
Date date;
|
||||||
|
try {
|
||||||
|
date = parseDatetime(dateStr);
|
||||||
|
if (date != null) {
|
||||||
|
encodeLong(date.getTime(), b, 0);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Bytes.SIZEOF_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeTimestamp(Object v, byte[] b, int o) {
|
||||||
|
if (v instanceof Timestamp) {
|
||||||
|
Timestamp ts = (Timestamp) v;
|
||||||
|
encodeLong(ts.getTime(), b, o);
|
||||||
|
Bytes.putInt(b, Bytes.SIZEOF_LONG, ts.getNanos() % 1000000);
|
||||||
|
} else {
|
||||||
|
encodeDate(v, b, o);
|
||||||
|
}
|
||||||
|
return Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedDate(Object v, byte[] b, int o) {
|
||||||
|
if (v instanceof Date) {
|
||||||
|
encodeUnsignedLong(((Date) v).getTime(), b, 0);
|
||||||
|
} else if (v instanceof String) {
|
||||||
|
String dateStr = (String) v;
|
||||||
|
Date date;
|
||||||
|
try {
|
||||||
|
date = parseDatetime(dateStr);
|
||||||
|
if (date != null) {
|
||||||
|
encodeUnsignedLong(date.getTime(), b, 0);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Bytes.SIZEOF_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int encodeUnsignedTimestamp(Object v, byte[] b, int o) {
|
||||||
|
if (v instanceof Timestamp) {
|
||||||
|
Timestamp ts = (Timestamp) v;
|
||||||
|
encodeUnsignedLong(ts.getTime(), b, o);
|
||||||
|
Bytes.putInt(b, Bytes.SIZEOF_LONG, ts.getNanos() % 1000000);
|
||||||
|
} else {
|
||||||
|
encodeUnsignedDate(v, b, o);
|
||||||
|
}
|
||||||
|
return Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] encodeDecimal(Object object) {
|
||||||
|
if (object == null) {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
BigDecimal v = (BigDecimal) object;
|
||||||
|
v = v.round(DEFAULT_MATH_CONTEXT).stripTrailingZeros();
|
||||||
|
int len = getLength(v);
|
||||||
|
byte[] result = new byte[Math.min(len, 21)];
|
||||||
|
decimalToBytes(v, result, 0, len);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigDecimal decodeDecimal(byte[] bytes, int offset, int length) {
|
||||||
|
if (length == 1 && bytes[offset] == ZERO_BYTE) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
int signum = ((bytes[offset] & 0x80) == 0) ? -1 : 1;
|
||||||
|
int scale;
|
||||||
|
int index;
|
||||||
|
int digitOffset;
|
||||||
|
long multiplier = 100L;
|
||||||
|
int begIndex = offset + 1;
|
||||||
|
if (signum == 1) {
|
||||||
|
scale = (byte) (((bytes[offset] & 0x7F) - 65) * -2);
|
||||||
|
index = offset + length;
|
||||||
|
digitOffset = POS_DIGIT_OFFSET;
|
||||||
|
} else {
|
||||||
|
scale = (byte) ((~bytes[offset] - 65 - 128) * -2);
|
||||||
|
index = offset + length - (bytes[offset + length - 1] == NEG_TERMINAL_BYTE ? 1 : 0);
|
||||||
|
digitOffset = -NEG_DIGIT_OFFSET;
|
||||||
|
}
|
||||||
|
length = index - offset;
|
||||||
|
long l = signum * bytes[--index] - digitOffset;
|
||||||
|
if (l % 10 == 0) { // trailing zero
|
||||||
|
scale--; // drop trailing zero and compensate in the scale
|
||||||
|
l /= 10;
|
||||||
|
multiplier = 10;
|
||||||
|
}
|
||||||
|
// Use long arithmetic for as long as we can
|
||||||
|
while (index > begIndex) {
|
||||||
|
if (l >= MAX_LONG_FOR_DESERIALIZE || multiplier >= Long.MAX_VALUE / 100) {
|
||||||
|
multiplier = LongMath.divide(multiplier, 100L, RoundingMode.UNNECESSARY);
|
||||||
|
break; // Exit loop early so we don't overflow our multiplier
|
||||||
|
}
|
||||||
|
int digit100 = signum * bytes[--index] - digitOffset;
|
||||||
|
l += digit100 * multiplier;
|
||||||
|
multiplier = LongMath.checkedMultiply(multiplier, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
BigInteger bi;
|
||||||
|
// If still more digits, switch to BigInteger arithmetic
|
||||||
|
if (index > begIndex) {
|
||||||
|
bi = BigInteger.valueOf(l);
|
||||||
|
BigInteger biMultiplier = BigInteger.valueOf(multiplier).multiply(ONE_HUNDRED);
|
||||||
|
do {
|
||||||
|
int digit100 = signum * bytes[--index] - digitOffset;
|
||||||
|
bi = bi.add(biMultiplier.multiply(BigInteger.valueOf(digit100)));
|
||||||
|
biMultiplier = biMultiplier.multiply(ONE_HUNDRED);
|
||||||
|
} while (index > begIndex);
|
||||||
|
if (signum == -1) {
|
||||||
|
bi = bi.negate();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bi = BigInteger.valueOf(l * signum);
|
||||||
|
}
|
||||||
|
// Update the scale based on the precision
|
||||||
|
scale += (length - 2) * 2;
|
||||||
|
BigDecimal v = new BigDecimal(bi, scale);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getLength(BigDecimal v) {
|
||||||
|
int signum = v.signum();
|
||||||
|
if (signum == 0) { // Special case for zero
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return (signum < 0 ? 2 : 1) + (v.precision() + 1 + (v.scale() % 2 == 0 ? 0 : 1)) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int MAX_PRECISION = 38;
|
||||||
|
private static final MathContext DEFAULT_MATH_CONTEXT = new MathContext(MAX_PRECISION, RoundingMode.HALF_UP);
|
||||||
|
private static final Integer MAX_BIG_DECIMAL_BYTES = 21;
|
||||||
|
private static final byte ZERO_BYTE = (byte) 0x80;
|
||||||
|
private static final byte NEG_TERMINAL_BYTE = (byte) 102;
|
||||||
|
private static final int EXP_BYTE_OFFSET = 65;
|
||||||
|
private static final int POS_DIGIT_OFFSET = 1;
|
||||||
|
private static final int NEG_DIGIT_OFFSET = 101;
|
||||||
|
private static final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE);
|
||||||
|
private static final BigInteger MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE);
|
||||||
|
private static final BigInteger ONE_HUNDRED = BigInteger.valueOf(100);
|
||||||
|
private static final long MAX_LONG_FOR_DESERIALIZE = Long.MAX_VALUE / 1000;
|
||||||
|
|
||||||
|
private static int decimalToBytes(BigDecimal v, byte[] result, final int offset, int length) {
|
||||||
|
int signum = v.signum();
|
||||||
|
if (signum == 0) {
|
||||||
|
result[offset] = ZERO_BYTE;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int index = offset + length;
|
||||||
|
int scale = v.scale();
|
||||||
|
int expOffset = scale % 2 * (scale < 0 ? -1 : 1);
|
||||||
|
int multiplyBy;
|
||||||
|
BigInteger divideBy;
|
||||||
|
if (expOffset == 0) {
|
||||||
|
multiplyBy = 1;
|
||||||
|
divideBy = ONE_HUNDRED;
|
||||||
|
} else {
|
||||||
|
multiplyBy = 10;
|
||||||
|
divideBy = BigInteger.TEN;
|
||||||
|
}
|
||||||
|
// Normalize the scale based on what is necessary to end up with a base 100
|
||||||
|
// decimal (i.e. 10.123e3)
|
||||||
|
int digitOffset;
|
||||||
|
BigInteger compareAgainst;
|
||||||
|
if (signum == 1) {
|
||||||
|
digitOffset = POS_DIGIT_OFFSET;
|
||||||
|
compareAgainst = MAX_LONG;
|
||||||
|
scale -= (length - 2) * 2;
|
||||||
|
result[offset] = (byte) ((-(scale + expOffset) / 2 + EXP_BYTE_OFFSET) | 0x80);
|
||||||
|
} else {
|
||||||
|
digitOffset = NEG_DIGIT_OFFSET;
|
||||||
|
compareAgainst = MIN_LONG;
|
||||||
|
// Scale adjustment shouldn't include terminal byte in length
|
||||||
|
scale -= (length - 2 - 1) * 2;
|
||||||
|
result[offset] = (byte) (~(-(scale + expOffset) / 2 + EXP_BYTE_OFFSET + 128) & 0x7F);
|
||||||
|
if (length <= MAX_BIG_DECIMAL_BYTES) {
|
||||||
|
result[--index] = NEG_TERMINAL_BYTE;
|
||||||
|
} else {
|
||||||
|
// Adjust length and offset down because we don't have enough room
|
||||||
|
length = MAX_BIG_DECIMAL_BYTES;
|
||||||
|
index = offset + length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BigInteger bi = v.unscaledValue();
|
||||||
|
// Use BigDecimal arithmetic until we can fit into a long
|
||||||
|
while (bi.compareTo(compareAgainst) * signum > 0) {
|
||||||
|
BigInteger[] dandr = bi.divideAndRemainder(divideBy);
|
||||||
|
bi = dandr[0];
|
||||||
|
int digit = dandr[1].intValue();
|
||||||
|
result[--index] = (byte) (digit * multiplyBy + digitOffset);
|
||||||
|
multiplyBy = 1;
|
||||||
|
divideBy = ONE_HUNDRED;
|
||||||
|
}
|
||||||
|
long l = bi.longValue();
|
||||||
|
do {
|
||||||
|
long divBy = 100 / multiplyBy;
|
||||||
|
long digit = l % divBy;
|
||||||
|
l /= divBy;
|
||||||
|
result[--index] = (byte) (digit * multiplyBy + digitOffset);
|
||||||
|
multiplyBy = 1;
|
||||||
|
} while (l != 0);
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkForSufficientLength(byte[] b, int offset, int requiredLength) {
|
||||||
|
if (b.length < offset + requiredLength) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Expected length of at least " + requiredLength + " bytes, but had " + (b.length - offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date parseDatetime(String dateStr) {
|
||||||
|
Date date = null;
|
||||||
|
int len = dateStr.length();
|
||||||
|
if (len == 10 && dateStr.charAt(4) == '-' && dateStr.charAt(7) == '-') {
|
||||||
|
date = new DateTime(dateStr).toDate();
|
||||||
|
} else if (len == 8 && dateStr.charAt(2) == ':' && dateStr.charAt(5) == ':') {
|
||||||
|
date = new DateTime("T" + dateStr).toDate();
|
||||||
|
} else if (len >= 19 && dateStr.charAt(4) == '-' && dateStr.charAt(7) == '-' && dateStr.charAt(13) == ':'
|
||||||
|
&& dateStr.charAt(16) == ':') {
|
||||||
|
date = new DateTime(dateStr.replace(" ", "T")).toDate();
|
||||||
|
}
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.support;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java类型
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午06:11:36
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public enum Type {
|
||||||
|
DEFAULT("STRING"),
|
||||||
|
STRING("STRING"),
|
||||||
|
INTEGER("INTEGER"),
|
||||||
|
LONG("LONG"),
|
||||||
|
SHORT("SHORT"),
|
||||||
|
BOOLEAN("BOOLEAN"),
|
||||||
|
FLOAT("FLOAT"),
|
||||||
|
DOUBLE("DOUBLE"),
|
||||||
|
BIGDECIMAL("BIGDECIMAL"),
|
||||||
|
DATE("DATE"),
|
||||||
|
BYTE("BYTE"),
|
||||||
|
BYTES("BYTES");
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
Type(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type getType(String type) {
|
||||||
|
if (type == null) {
|
||||||
|
return DEFAULT;
|
||||||
|
}
|
||||||
|
Type res;
|
||||||
|
if (type.equalsIgnoreCase("STRING")) {
|
||||||
|
res = STRING;
|
||||||
|
} else if (type.equalsIgnoreCase("INTEGER")) {
|
||||||
|
res = INTEGER;
|
||||||
|
} else if (type.equalsIgnoreCase("LONG")) {
|
||||||
|
res = LONG;
|
||||||
|
} else if (type.equalsIgnoreCase("SHORT")) {
|
||||||
|
res = SHORT;
|
||||||
|
} else if (type.equalsIgnoreCase("BOOLEAN")) {
|
||||||
|
res = BOOLEAN;
|
||||||
|
} else if (type.equalsIgnoreCase("FLOAT")) {
|
||||||
|
res = FLOAT;
|
||||||
|
} else if (type.equalsIgnoreCase("DOUBLE")) {
|
||||||
|
res = DOUBLE;
|
||||||
|
} else if (type.equalsIgnoreCase("BIGDECIMAL")) {
|
||||||
|
res = BIGDECIMAL;
|
||||||
|
} else if (type.equalsIgnoreCase("DATE")) {
|
||||||
|
res = DATE;
|
||||||
|
} else if (type.equalsIgnoreCase("BYTE")) {
|
||||||
|
res = BYTE;
|
||||||
|
} else if (type.equalsIgnoreCase("BYTES")) {
|
||||||
|
res = BYTES;
|
||||||
|
} else {
|
||||||
|
res = DEFAULT;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type getType(Class<?> javaType) {
|
||||||
|
if (javaType == null) {
|
||||||
|
return DEFAULT;
|
||||||
|
}
|
||||||
|
Type type;
|
||||||
|
if (Integer.class.isAssignableFrom(javaType) || int.class.isAssignableFrom(javaType)) {
|
||||||
|
type = INTEGER;
|
||||||
|
} else if (Long.class.isAssignableFrom(javaType) || long.class.isAssignableFrom(javaType)) {
|
||||||
|
type = LONG;
|
||||||
|
} else if (Byte.class.isAssignableFrom(javaType) || byte.class.isAssignableFrom(javaType)) {
|
||||||
|
type = BYTE;
|
||||||
|
} else if (Short.class.isAssignableFrom(javaType) || short.class.isAssignableFrom(javaType)) {
|
||||||
|
type = SHORT;
|
||||||
|
} else if (Float.class.isAssignableFrom(javaType) || float.class.isAssignableFrom(javaType)) {
|
||||||
|
type = FLOAT;
|
||||||
|
} else if (Double.class.isAssignableFrom(javaType) || double.class.isAssignableFrom(javaType)) {
|
||||||
|
type = DOUBLE;
|
||||||
|
} else if (Boolean.class.isAssignableFrom(javaType) || boolean.class.isAssignableFrom(javaType)) {
|
||||||
|
type = BOOLEAN;
|
||||||
|
} else if (Date.class.isAssignableFrom(javaType)) {
|
||||||
|
type = DATE;
|
||||||
|
} else if (byte[].class.isAssignableFrom(javaType)) {
|
||||||
|
type = BYTES;
|
||||||
|
} else if (String.class.isAssignableFrom(javaType)) {
|
||||||
|
type = STRING;
|
||||||
|
} else if (BigDecimal.class.isAssignableFrom(javaType)) {
|
||||||
|
type = BIGDECIMAL;
|
||||||
|
} else if (BigInteger.class.isAssignableFrom(javaType)) {
|
||||||
|
type = LONG;
|
||||||
|
} else {
|
||||||
|
type = DEFAULT;
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.hbase.support;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.sql.Time;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java类型转换工具类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-21 下午06:12:34
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class TypeUtil {
|
||||||
|
|
||||||
|
public static byte[] toBytes(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] bytes;
|
||||||
|
if (obj instanceof String) {
|
||||||
|
bytes = Bytes.toBytes((String) obj);
|
||||||
|
} else if (obj instanceof Integer) {
|
||||||
|
bytes = Bytes.toBytes((Integer) obj);
|
||||||
|
} else if (obj instanceof Long) {
|
||||||
|
bytes = Bytes.toBytes((Long) obj);
|
||||||
|
} else if (obj instanceof Short) {
|
||||||
|
bytes = Bytes.toBytes((Short) obj);
|
||||||
|
} else if (obj instanceof Boolean) {
|
||||||
|
bytes = Bytes.toBytes((Boolean) obj);
|
||||||
|
} else if (obj instanceof Float) {
|
||||||
|
bytes = Bytes.toBytes((Float) obj);
|
||||||
|
} else if (obj instanceof Double) {
|
||||||
|
bytes = Bytes.toBytes((Double) obj);
|
||||||
|
} else if (obj instanceof BigDecimal) {
|
||||||
|
bytes = Bytes.toBytes((BigDecimal) obj);
|
||||||
|
} else if (obj instanceof BigInteger) {
|
||||||
|
bytes = Bytes.toBytes(((BigInteger) obj).longValue());
|
||||||
|
} else if (obj instanceof Date) {
|
||||||
|
bytes = Bytes.toBytes(((Date) obj).getTime());
|
||||||
|
} else if (obj instanceof Byte) {
|
||||||
|
bytes = new byte[] { (byte) obj };
|
||||||
|
} else if (obj instanceof byte[]) {
|
||||||
|
bytes = (byte[]) obj;
|
||||||
|
} else {
|
||||||
|
// 其余类型统一转换为string
|
||||||
|
bytes = Bytes.toBytes(obj.toString());
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] toBytes(Object v, Type type) {
|
||||||
|
if (v == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] b = null;
|
||||||
|
if (type == Type.DEFAULT) {
|
||||||
|
Type type1 = Type.getType(v.getClass());
|
||||||
|
if (type1 != null && type1 != Type.DEFAULT) {
|
||||||
|
b = toBytes(v, type1);
|
||||||
|
}
|
||||||
|
} else if (type == Type.STRING) {
|
||||||
|
b = Bytes.toBytes(v.toString());
|
||||||
|
} else if (type == Type.INTEGER) {
|
||||||
|
b = Bytes.toBytes(((Number) v).intValue());
|
||||||
|
} else if (type == Type.LONG) {
|
||||||
|
b = Bytes.toBytes(((Number) v).longValue());
|
||||||
|
} else if (type == Type.SHORT) {
|
||||||
|
b = Bytes.toBytes(((Number) v).shortValue());
|
||||||
|
} else if (type == Type.BYTE) {
|
||||||
|
b = Bytes.toBytes(((Number) v).byteValue());
|
||||||
|
} else if (type == Type.FLOAT) {
|
||||||
|
b = Bytes.toBytes(((Number) v).floatValue());
|
||||||
|
} else if (type == Type.DOUBLE) {
|
||||||
|
b = Bytes.toBytes(((Number) v).doubleValue());
|
||||||
|
} else if (type == Type.BOOLEAN) {
|
||||||
|
b = Bytes.toBytes(((Boolean) v));
|
||||||
|
} else if (type == Type.DATE) {
|
||||||
|
b = Bytes.toBytes(((Date) v).getTime());
|
||||||
|
} else if (type == Type.BYTES) {
|
||||||
|
b = (byte[]) v;
|
||||||
|
} else if (type == Type.BIGDECIMAL) {
|
||||||
|
if (v instanceof BigDecimal) {
|
||||||
|
b = Bytes.toBytes((BigDecimal) v);
|
||||||
|
} else {
|
||||||
|
b = Bytes.toBytes(new BigDecimal(v.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T toObject(byte[] bytes, Class<T> clazz) {
|
||||||
|
if (bytes == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Object res;
|
||||||
|
if (String.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toString(bytes);
|
||||||
|
} else if (Integer.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toInt(bytes);
|
||||||
|
} else if (Long.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toLong(bytes);
|
||||||
|
} else if (Short.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toShort(bytes);
|
||||||
|
} else if (Boolean.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toBoolean(bytes);
|
||||||
|
} else if (Float.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toFloat(bytes);
|
||||||
|
} else if (Double.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toDouble(bytes);
|
||||||
|
} else if (BigDecimal.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toBigDecimal(bytes);
|
||||||
|
} else if (BigInteger.class.isAssignableFrom(clazz)) {
|
||||||
|
res = Bytes.toLong(bytes);
|
||||||
|
} else if (java.sql.Date.class.isAssignableFrom(clazz)) {
|
||||||
|
long ts = Bytes.toLong(bytes);
|
||||||
|
res = new java.sql.Date(ts);
|
||||||
|
} else if (Time.class.isAssignableFrom(clazz)) {
|
||||||
|
long ts = Bytes.toLong(bytes);
|
||||||
|
res = new Time(ts);
|
||||||
|
} else if (Timestamp.class.isAssignableFrom(clazz)) {
|
||||||
|
long ts = Bytes.toLong(bytes);
|
||||||
|
res = new Timestamp(ts);
|
||||||
|
} else if (Date.class.isAssignableFrom(clazz)) {
|
||||||
|
long ts = Bytes.toLong(bytes);
|
||||||
|
res = new Date(ts);
|
||||||
|
} else if (Byte.class.isAssignableFrom(clazz)) {
|
||||||
|
res = bytes[0];
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("mismatch class type");
|
||||||
|
}
|
||||||
|
// noinspection unchecked
|
||||||
|
return (T) res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T toObject(byte[] bytes, Type type) {
|
||||||
|
if (bytes == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Object res = null;
|
||||||
|
if (type == Type.STRING || type == Type.DEFAULT) {
|
||||||
|
res = Bytes.toString(bytes);
|
||||||
|
} else if (type == Type.INTEGER) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_INT) {
|
||||||
|
res = Bytes.toInt(bytes);
|
||||||
|
}
|
||||||
|
} else if (type == Type.LONG) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_LONG) {
|
||||||
|
res = Bytes.toLong(bytes);
|
||||||
|
}
|
||||||
|
} else if (type == Type.SHORT) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_SHORT) {
|
||||||
|
res = Bytes.toShort(bytes);
|
||||||
|
}
|
||||||
|
} else if (type == Type.BYTE) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_BYTE) {
|
||||||
|
res = bytes[0];
|
||||||
|
}
|
||||||
|
} else if (type == Type.FLOAT) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_FLOAT) {
|
||||||
|
res = Bytes.toFloat(bytes);
|
||||||
|
}
|
||||||
|
} else if (type == Type.DOUBLE) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_DOUBLE) {
|
||||||
|
res = Bytes.toDouble(bytes);
|
||||||
|
}
|
||||||
|
} else if (type == Type.BOOLEAN) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_BOOLEAN) {
|
||||||
|
res = Bytes.toBoolean(bytes);
|
||||||
|
}
|
||||||
|
} else if (type == Type.DATE) {
|
||||||
|
if (bytes.length == Bytes.SIZEOF_LONG) {
|
||||||
|
res = new Date(Bytes.toLong(bytes));
|
||||||
|
}
|
||||||
|
} else if (type == Type.BYTES) {
|
||||||
|
res = bytes;
|
||||||
|
} else if (type == Type.BIGDECIMAL) {
|
||||||
|
res = Bytes.toBigDecimal(bytes);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("mismatch class type");
|
||||||
|
}
|
||||||
|
// noinspection unchecked
|
||||||
|
return (T) res;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
hbase=com.alibaba.otter.canal.client.adapter.hbase.HbaseAdapter
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# 详细映射配置
|
||||||
|
mytest_person2.yml
|
||||||
|
|
||||||
|
# 简易配置, 只用指定数据库名.表名, 详细配置全部使用默认
|
||||||
|
mytest.person
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
hbaseOrm:
|
||||||
|
mode: PHOENIX #NATIVE #STRING
|
||||||
|
database: mytest # 数据库名
|
||||||
|
table: person2 # 数据库表名
|
||||||
|
hbaseTable: MYTEST.PERSON2 # HBase表名
|
||||||
|
family: CF # 默认统一Family名称
|
||||||
|
uppercaseQualifier: true # 字段名转大写, 默认为true
|
||||||
|
commitBatch: 3000 # 批量提交的大小
|
||||||
|
#rowKey: id,type # 复合字段rowKey不能和columns中的rowKey重复
|
||||||
|
columns:
|
||||||
|
# 数据库字段:HBase对应字段
|
||||||
|
id: ROWKEY$UNSIGNED_LONG
|
||||||
|
name: NAME
|
||||||
|
email: EMAIL
|
||||||
|
type: $DECIMAL
|
||||||
|
c_time: C_TIME$UNSIGNED_TIMESTAMP
|
||||||
|
birthday: BIRTHDAY$DATE
|
||||||
|
|
||||||
|
# -- NATIVE类型
|
||||||
|
# $DEFAULT
|
||||||
|
# $STRING
|
||||||
|
# $INTEGER
|
||||||
|
# $LONG
|
||||||
|
# $SHORT
|
||||||
|
# $BOOLEAN
|
||||||
|
# $FLOAT
|
||||||
|
# $DOUBLE
|
||||||
|
# $BIGDECIMAL
|
||||||
|
# $DATE
|
||||||
|
# $BYTE
|
||||||
|
# $BYTES
|
||||||
|
|
||||||
|
# -- PHOENIX类型
|
||||||
|
# $DEFAULT 对应PHOENIX里的VARCHAR
|
||||||
|
# $UNSIGNED_INT 对应PHOENIX里的UNSIGNED_INT 4字节
|
||||||
|
# $UNSIGNED_LONG 对应PHOENIX里的UNSIGNED_LONG 8字节
|
||||||
|
# $UNSIGNED_TINYINT 对应PHOENIX里的UNSIGNED_TINYINT 1字节
|
||||||
|
# $UNSIGNED_SMALLINT 对应PHOENIX里的UNSIGNED_SMALLINT 2字节
|
||||||
|
# $UNSIGNED_FLOAT 对应PHOENIX里的UNSIGNED_FLOAT 4字节
|
||||||
|
# $UNSIGNED_DOUBLE 对应PHOENIX里的UNSIGNED_DOUBLE 8字节
|
||||||
|
# $INTEGER 对应PHOENIX里的INTEGER 4字节
|
||||||
|
# $BIGINT 对应PHOENIX里的BIGINT 8字节
|
||||||
|
# $TINYINT 对应PHOENIX里的TINYINT 1字节
|
||||||
|
# $SMALLINT 对应PHOENIX里的SMALLINT 2字节
|
||||||
|
# $FLOAT 对应PHOENIX里的FLOAT 4字节
|
||||||
|
# DOUBLE 对应PHOENIX里的DOUBLE 8字节
|
||||||
|
# $BOOLEAN 对应PHOENIX里的BOOLEAN 1字节
|
||||||
|
# $TIME 对应PHOENIX里的TIME 8字节
|
||||||
|
# $DATE 对应PHOENIX里的DATE 8字节
|
||||||
|
# $TIMESTAMP 对应PHOENIX里的TIMESTAMP 12字节
|
||||||
|
# $UNSIGNED_TIME 对应PHOENIX里的UNSIGNED_TIME 8字节
|
||||||
|
# $UNSIGNED_DATE 对应PHOENIX里的UNSIGNED_DATE 8字节
|
||||||
|
# $UNSIGNED_TIMESTAMP 对应PHOENIX里的UNSIGNED_TIMESTAMP 12字节
|
||||||
|
# $VARCHAR 对应PHOENIX里的VARCHAR 动态长度
|
||||||
|
# $VARBINARY 对应PHOENIX里的VARBINARY 动态长度
|
||||||
|
# $DECIMAL 对应PHOENIX里的DECIMAL 动态长度
|
||||||
47
client-adapter/logger/pom.xml
Normal file
47
client-adapter/logger/pom.xml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?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.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.logger</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>canal client adapter logger example 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>
|
||||||
|
</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>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.logger;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration;
|
||||||
|
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.SPI;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部适配器示例
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午11:45:38
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@SPI("logger") // logger参数对应CanalOuterAdapterConfiguration配置中的name
|
||||||
|
public class LoggerAdapterExample implements CanalOuterAdapter {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeOut(Message message) {
|
||||||
|
// 直接输出日志信息
|
||||||
|
MessageUtil.parse4Dml(message, new MessageUtil.Consumer<Dml>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(Dml dml) {
|
||||||
|
logger.info(dml.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(CanalOuterAdapterConfiguration configuration) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
logger=com.alibaba.otter.canal.client.adapter.logger.LoggerAdapterExample
|
||||||
21
client-adapter/pom.xml
Normal file
21
client-adapter/pom.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?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</artifactId>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.client-adapter</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<name>canal client adapter module for otter ${project.version}</name>
|
||||||
|
<modules>
|
||||||
|
<module>common</module>
|
||||||
|
<module>logger</module>
|
||||||
|
<module>hbase</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
</project>
|
||||||
149
client-launcher/pom.xml
Normal file
149
client-launcher/pom.xml
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
<?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</artifactId>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.client-launcher</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>canal client launcher module for otter ${project.version}</name>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.common</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.client</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- 单独引入kafka依赖 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.kafka</groupId>
|
||||||
|
<artifactId>kafka-clients</artifactId>
|
||||||
|
<version>1.1.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
<version>1.17</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- outer adapter -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.logger</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<classifier>jar-with-dependencies</classifier>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>client-adapter.hbase</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<classifier>jar-with-dependencies</classifier>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<!-- deploy模块的packaging通常是jar,如果项目中没有java 源代码或资源文件,加上这一段配置使项目能通过构建 -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<addMavenDescriptor>true</addMavenDescriptor>
|
||||||
|
</archive>
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/logback.xml</exclude>
|
||||||
|
<exclude>**/canal-client.yml</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</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>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<!-- maven assembly插件需要一个描述文件 来告诉插件包的结构以及打包所需的文件来自哪里 -->
|
||||||
|
<descriptors>
|
||||||
|
<descriptor>${basedir}/src/main/assembly/dev.xml</descriptor>
|
||||||
|
</descriptors>
|
||||||
|
<finalName>canal_client</finalName>
|
||||||
|
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
<id>release</id>
|
||||||
|
<activation>
|
||||||
|
<property>
|
||||||
|
<name>env</name>
|
||||||
|
<value>release</value>
|
||||||
|
</property>
|
||||||
|
</activation>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<!-- 发布模式使用的maven assembly插件描述文件 -->
|
||||||
|
<descriptors>
|
||||||
|
<descriptor>${basedir}/src/main/assembly/release.xml</descriptor>
|
||||||
|
</descriptors>
|
||||||
|
<!-- 如果一个应用的包含多个deploy模块,如果使用同样的包名, 如果把它们复制的一个目录中可能会失败,所以包名加了 artifactId以示区分 -->
|
||||||
|
<finalName>${project.artifactId}-${project.version}</finalName>
|
||||||
|
<!-- scm 要求 release 模式打出的包放到顶级目录下的target子目录中 -->
|
||||||
|
<outputDirectory>${project.parent.build.directory}</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
|
||||||
|
</project>
|
||||||
57
client-launcher/src/main/assembly/dev.xml
Normal file
57
client-launcher/src/main/assembly/dev.xml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<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/conf</directory>
|
||||||
|
<outputDirectory>/conf</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>./src/main/resources</directory>
|
||||||
|
<outputDirectory>/conf</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
<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>
|
||||||
57
client-launcher/src/main/assembly/release.xml
Normal file
57
client-launcher/src/main/assembly/release.xml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<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>tar.gz</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/conf</directory>
|
||||||
|
<outputDirectory>/conf</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>./src/main/resources</directory>
|
||||||
|
<outputDirectory>/conf</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
<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>
|
||||||
25
client-launcher/src/main/bin/startup.bat
Normal file
25
client-launcher/src/main/bin/startup.bat
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
@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 canal_conf=%conf_dir%\canal-client.yml
|
||||||
|
set logback_configurationFile=%conf_dir%\logback.xml
|
||||||
|
|
||||||
|
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 JAVA_DEBUG_OPT= -server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=9099,server=y,suspend=n
|
||||||
|
set CANAL_OPTS= -DappName=otter-canal -Dlogback.configurationFile="%logback_configurationFile%" -Dcanal.conf="%canal_conf%"
|
||||||
|
|
||||||
|
set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %JAVA_DEBUG_OPT% %CANAL_OPTS%
|
||||||
|
|
||||||
|
set CMD_STR= java %JAVA_OPTS% -classpath "%CLASSPATH%" java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.client.ClientLauncher
|
||||||
|
echo start cmd : %CMD_STR%
|
||||||
|
|
||||||
|
java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.client.ClientLauncher
|
||||||
100
client-launcher/src/main/bin/startup.sh
Normal file
100
client-launcher/src/main/bin/startup.sh
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
#!/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}/..
|
||||||
|
canal_conf=$base/conf/canal-client.yml
|
||||||
|
logback_configurationFile=$base/conf/logback.xml
|
||||||
|
export LANG=en_US.UTF-8
|
||||||
|
export BASE=$base
|
||||||
|
|
||||||
|
if [ -f $base/bin/canal_client.pid ] ; then
|
||||||
|
echo "found canal_client.pid , Please run stop.sh first ,then startup.sh" 2>&2
|
||||||
|
exit 1
|
||||||
|
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
|
||||||
|
|
||||||
|
case "$#"
|
||||||
|
in
|
||||||
|
0 )
|
||||||
|
;;
|
||||||
|
1 )
|
||||||
|
var=$*
|
||||||
|
if [ -f $var ] ; then
|
||||||
|
canal_conf=$var
|
||||||
|
else
|
||||||
|
echo "THE PARAMETER IS NOT CORRECT.PLEASE CHECK AGAIN."
|
||||||
|
exit
|
||||||
|
fi;;
|
||||||
|
2 )
|
||||||
|
var=$1
|
||||||
|
if [ -f $var ] ; then
|
||||||
|
canal_conf=$var
|
||||||
|
else
|
||||||
|
if [ "$1" = "debug" ]; then
|
||||||
|
DEBUG_PORT=$2
|
||||||
|
DEBUG_SUSPEND="n"
|
||||||
|
JAVA_DEBUG_OPT="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=$DEBUG_PORT,server=y,suspend=$DEBUG_SUSPEND"
|
||||||
|
fi
|
||||||
|
fi;;
|
||||||
|
* )
|
||||||
|
echo "THE PARAMETERS MUST BE TWO OR LESS.PLEASE CHECK AGAIN."
|
||||||
|
exit;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
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"
|
||||||
|
CANAL_OPTS="-DappName=otter-canal-client -Dlogback.configurationFile=$logback_configurationFile -Dcanal.conf=$canal_conf"
|
||||||
|
|
||||||
|
if [ -e $canal_conf -a -e $logback_configurationFile ]
|
||||||
|
then
|
||||||
|
|
||||||
|
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 LOG CONFIGURATION : $logback_configurationFile
|
||||||
|
echo canal conf : $canal_conf
|
||||||
|
echo CLASSPATH :$CLASSPATH
|
||||||
|
$JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.client.ClientLauncher 1>>$base/logs/canal_client.log 2>&1 &
|
||||||
|
echo $! > $base/bin/canal_client.pid
|
||||||
|
|
||||||
|
echo "cd to $current_path for continue"
|
||||||
|
cd $current_path
|
||||||
|
else
|
||||||
|
echo "canal client conf("$canal_conf") OR log configration file($logback_configurationFile) is not exist,please create then first!"
|
||||||
|
fi
|
||||||
65
client-launcher/src/main/bin/stop.sh
Normal file
65
client-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/canal_client.pid
|
||||||
|
if [ ! -f "$pidfile" ];then
|
||||||
|
echo "canal client is not running. exists"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
pid=`cat $pidfile`
|
||||||
|
if [ "$pid" == "" ] ; then
|
||||||
|
pid=`get_pid "appName=otter-canal-client"`
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "`hostname`: stopping canal $pid ... "
|
||||||
|
kill $pid
|
||||||
|
|
||||||
|
LOOPS=0
|
||||||
|
while (true);
|
||||||
|
do
|
||||||
|
gpid=`get_pid "appName=otter-canal-client" "$pid"`
|
||||||
|
if [ "$gpid" == "" ] ; then
|
||||||
|
echo "Oook! cost:$LOOPS"
|
||||||
|
`rm $pidfile`
|
||||||
|
break;
|
||||||
|
fi
|
||||||
|
let LOOPS=LOOPS+1
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.alibaba.otter.canal.client;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.loader.CanalAdapterLoader;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
|
||||||
|
public class ClientLauncher {
|
||||||
|
|
||||||
|
private static final String CLASSPATH_URL_PREFIX = "classpath:";
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ClientLauncher.class);
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
logger.info("## set default uncaught exception handler");
|
||||||
|
setGlobalUncaughtExceptionHandler();
|
||||||
|
|
||||||
|
logger.info("## load canal client configurations");
|
||||||
|
String conf = System.getProperty("client.conf", "classpath:canal-client.yml");
|
||||||
|
CanalClientConfig canalClientConfig;
|
||||||
|
if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
|
||||||
|
conf = StringUtils.substringAfter(conf, CLASSPATH_URL_PREFIX);
|
||||||
|
canalClientConfig = new Yaml().loadAs(ClientLauncher.class.getClassLoader().getResourceAsStream(conf),
|
||||||
|
CanalClientConfig.class);
|
||||||
|
} else {
|
||||||
|
canalClientConfig = new Yaml().loadAs(new FileInputStream(conf), CanalClientConfig.class);
|
||||||
|
}
|
||||||
|
logger.info("## start the canal client adapters.");
|
||||||
|
final CanalAdapterLoader adapterLoader = new CanalAdapterLoader(canalClientConfig);
|
||||||
|
adapterLoader.init();
|
||||||
|
logger.info("## the canal client adapters are running now ......");
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
logger.info("## stop the canal client adapters");
|
||||||
|
adapterLoader.destroy();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("## something goes wrong when stopping canal client adapters:", e);
|
||||||
|
} finally {
|
||||||
|
logger.info("## canal client adapters are down.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("## something goes wrong when starting up the canal client adapters:", e);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setGlobalUncaughtExceptionHandler() {
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
|
logger.error("UnCaughtException", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.loader;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 适配器工作线程抽象类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午11:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractCanalAdapterWorker {
|
||||||
|
|
||||||
|
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
protected String canalDestination; // canal实例
|
||||||
|
protected List<List<CanalOuterAdapter>> canalOuterAdapters; // 外部适配器
|
||||||
|
protected ExecutorService groupInnerExecutorService; // 组内工作线程池
|
||||||
|
protected volatile boolean running = false; // 是否运行中
|
||||||
|
protected Thread thread = null;
|
||||||
|
protected Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
|
logger.error("parse events has an error", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected void writeOut(final Message message) {
|
||||||
|
List<Future<Boolean>> futures = new ArrayList<>();
|
||||||
|
// 组间适配器并行运行
|
||||||
|
for (List<CanalOuterAdapter> outerAdapters : canalOuterAdapters) {
|
||||||
|
final List<CanalOuterAdapter> adapters = outerAdapters;
|
||||||
|
futures.add(groupInnerExecutorService.submit(new Callable<Boolean>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean call() {
|
||||||
|
try {
|
||||||
|
// 组内适配器穿行运行,尽量不要配置组内适配器
|
||||||
|
for (CanalOuterAdapter c : adapters) {
|
||||||
|
long begin = System.currentTimeMillis();
|
||||||
|
c.writeOut(message);
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("{} elapsed time: {}",
|
||||||
|
c.getClass().getName(),
|
||||||
|
(System.currentTimeMillis() - begin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 等待所有适配器写入完成
|
||||||
|
// 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成
|
||||||
|
for (Future<Boolean> f : futures) {
|
||||||
|
try {
|
||||||
|
if (!f.get()) {
|
||||||
|
logger.error("Outer adapter write failed");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void start();
|
||||||
|
|
||||||
|
public abstract void stop();
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.loader;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import org.apache.kafka.clients.consumer.CommitFailedException;
|
||||||
|
import org.apache.kafka.common.errors.WakeupException;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector;
|
||||||
|
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* kafka对应的client适配器工作线程
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午11:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker {
|
||||||
|
|
||||||
|
private KafkaCanalConnector connector;
|
||||||
|
|
||||||
|
private String topic;
|
||||||
|
|
||||||
|
public CanalAdapterKafkaWorker(String zkServers, String bootstrapServers, String topic, String groupId,
|
||||||
|
List<List<CanalOuterAdapter>> canalOuterAdapters){
|
||||||
|
this.canalOuterAdapters = canalOuterAdapters;
|
||||||
|
this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size());
|
||||||
|
this.topic = topic;
|
||||||
|
this.canalDestination = topic;
|
||||||
|
connector = KafkaCanalConnectors.newKafkaConnector(zkServers, bootstrapServers, topic, null, groupId);
|
||||||
|
// connector.setSessionTimeout(5L, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
// super.initSwitcher(topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
if (!running) {
|
||||||
|
thread = new Thread(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.setUncaughtExceptionHandler(handler);
|
||||||
|
running = true;
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
try {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connector.stopRunning();
|
||||||
|
running = false;
|
||||||
|
|
||||||
|
// if (switcher != null && !switcher.state()) {
|
||||||
|
// switcher.set(true);
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (thread != null) {
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groupInnerExecutorService.shutdown();
|
||||||
|
logger.info("topic {} connectors' worker thread dead!", this.topic);
|
||||||
|
for (List<CanalOuterAdapter> outerAdapters : canalOuterAdapters) {
|
||||||
|
for (CanalOuterAdapter adapter : outerAdapters) {
|
||||||
|
adapter.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info("topic {} all connectors destroyed!", this.topic);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process() {
|
||||||
|
while (!running)
|
||||||
|
;
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
final AtomicBoolean executing = new AtomicBoolean(true);
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
logger.info("=============> Start to connect topic: {} <=============", this.topic);
|
||||||
|
connector.connect();
|
||||||
|
logger.info("=============> Start to subscribe topic: {} <=============", this.topic);
|
||||||
|
connector.subscribe();
|
||||||
|
logger.info("=============> Subscribe topic: {} succeed <=============", this.topic);
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
// switcher.get(); //等待开关开启
|
||||||
|
|
||||||
|
final Message message = connector.getWithoutAck();
|
||||||
|
|
||||||
|
executing.set(true);
|
||||||
|
if (message != null) {
|
||||||
|
executor.submit(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("topic: {} batchId: {} batchSize: {} ",
|
||||||
|
topic,
|
||||||
|
message.getId(),
|
||||||
|
message.getEntries().size());
|
||||||
|
}
|
||||||
|
long begin = System.currentTimeMillis();
|
||||||
|
writeOut(message);
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if ((System.currentTimeMillis() - begin) > 5 * 60 * 1000) {
|
||||||
|
logger.error("topic: {} batchId {} elapsed time: {} ms",
|
||||||
|
topic,
|
||||||
|
message.getId(),
|
||||||
|
now - begin);
|
||||||
|
}
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("topic: {} batchId {} elapsed time: {} ms",
|
||||||
|
topic,
|
||||||
|
message.getId(),
|
||||||
|
now - begin);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
} finally {
|
||||||
|
executing.compareAndSet(true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 间隔一段时间ack一次, 防止因超时未响应切换到另外台客户端
|
||||||
|
long currentTS = System.currentTimeMillis();
|
||||||
|
while (executing.get()) {
|
||||||
|
// 大于1分钟未消费完ack一次keep alive
|
||||||
|
if (System.currentTimeMillis() - currentTS > 60000) {
|
||||||
|
connector.ack();
|
||||||
|
currentTS = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
connector.ack();
|
||||||
|
}
|
||||||
|
} catch (CommitFailedException e) {
|
||||||
|
logger.warn(e.getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
TimeUnit.SECONDS.sleep(1L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
|
|
||||||
|
try {
|
||||||
|
connector.unsubscribe();
|
||||||
|
} catch (WakeupException e) {
|
||||||
|
// No-op. Continue process
|
||||||
|
}
|
||||||
|
connector.disconnnect();
|
||||||
|
logger.info("=============> Disconnect topic: {} <=============", this.topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.loader;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部适配器的加载器
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午11:45:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalAdapterLoader {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(CanalAdapterLoader.class);
|
||||||
|
|
||||||
|
private CanalClientConfig canalClientConfig;
|
||||||
|
|
||||||
|
private Map<String, CanalAdapterWorker> canalWorkers = new HashMap<>();
|
||||||
|
|
||||||
|
private Map<String, CanalAdapterKafkaWorker> canalKafkaWorkers = new HashMap<>();
|
||||||
|
|
||||||
|
private ExtensionLoader<CanalOuterAdapter> loader;
|
||||||
|
|
||||||
|
public CanalAdapterLoader(CanalClientConfig canalClientConfig){
|
||||||
|
this.canalClientConfig = canalClientConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化canal-client、 canal-client-kafka的适配器
|
||||||
|
*/
|
||||||
|
public void init() {
|
||||||
|
// canal instances 和 kafka topics 配置不能同时为空
|
||||||
|
if (canalClientConfig.getCanalInstances() == null && canalClientConfig.getKafkaTopics() == null) {
|
||||||
|
throw new RuntimeException("Blank config property: canalInstances or canalKafkaTopics");
|
||||||
|
}
|
||||||
|
|
||||||
|
loader = ExtensionLoader.getExtensionLoader(CanalOuterAdapter.class,
|
||||||
|
"" /* TODO canalClientConfig.getClassloaderPolicy() */);
|
||||||
|
|
||||||
|
String canalServerHost = this.canalClientConfig.getCanalServerHost();
|
||||||
|
SocketAddress sa = null;
|
||||||
|
if (canalServerHost != null) {
|
||||||
|
String[] ipPort = canalServerHost.split(":");
|
||||||
|
sa = new InetSocketAddress(ipPort[0], Integer.parseInt(ipPort[1]));
|
||||||
|
}
|
||||||
|
String zkHosts = this.canalClientConfig.getZookeeperHosts();
|
||||||
|
|
||||||
|
// if (zkHosts == null && sa == null) {
|
||||||
|
// throw new RuntimeException("Blank config property: canalServerHost or
|
||||||
|
// zookeeperHosts");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 初始化canal-client的适配器
|
||||||
|
if (canalClientConfig.getCanalInstances() != null) {
|
||||||
|
for (CanalClientConfig.CanalInstance instance : canalClientConfig.getCanalInstances()) {
|
||||||
|
List<List<CanalOuterAdapter>> canalOuterAdapterGroups = new ArrayList<>();
|
||||||
|
|
||||||
|
for (CanalClientConfig.AdapterGroup connectorGroup : instance.getAdapterGroups()) {
|
||||||
|
List<CanalOuterAdapter> canalOutConnectors = new ArrayList<>();
|
||||||
|
for (CanalOuterAdapterConfiguration c : connectorGroup.getOutAdapters()) {
|
||||||
|
loadConnector(c, canalOutConnectors);
|
||||||
|
}
|
||||||
|
canalOuterAdapterGroups.add(canalOutConnectors);
|
||||||
|
}
|
||||||
|
CanalAdapterWorker worker;
|
||||||
|
if (zkHosts != null) {
|
||||||
|
worker = new CanalAdapterWorker(instance.getInstance(), zkHosts, canalOuterAdapterGroups);
|
||||||
|
} else {
|
||||||
|
worker = new CanalAdapterWorker(instance.getInstance(), sa, canalOuterAdapterGroups);
|
||||||
|
}
|
||||||
|
canalWorkers.put(instance.getInstance(), worker);
|
||||||
|
worker.start();
|
||||||
|
logger.info("Start adapter for canal instance: {} succeed", instance.getInstance());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化canal-client-kafka的适配器
|
||||||
|
if (canalClientConfig.getKafkaTopics() != null) {
|
||||||
|
for (CanalClientConfig.KafkaTopic kafkaTopic : canalClientConfig.getKafkaTopics()) {
|
||||||
|
for (CanalClientConfig.Group group : kafkaTopic.getGroups()) {
|
||||||
|
List<List<CanalOuterAdapter>> canalOuterAdapterGroups = new ArrayList<>();
|
||||||
|
|
||||||
|
List<CanalOuterAdapter> canalOuterAdapters = new ArrayList<>();
|
||||||
|
|
||||||
|
for (CanalOuterAdapterConfiguration config : group.getOutAdapters()) {
|
||||||
|
// for (CanalOuterAdapterConfiguration config : adaptor.getOutAdapters()) {
|
||||||
|
loadConnector(config, canalOuterAdapters);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
canalOuterAdapterGroups.add(canalOuterAdapters);
|
||||||
|
|
||||||
|
// String zkServers = canalClientConfig.getZookeeperHosts();
|
||||||
|
CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(zkHosts,
|
||||||
|
canalClientConfig.getBootstrapServers(),
|
||||||
|
kafkaTopic.getTopic(),
|
||||||
|
group.getGroupId(),
|
||||||
|
canalOuterAdapterGroups);
|
||||||
|
canalKafkaWorkers.put(kafkaTopic.getTopic() + "-" + group.getGroupId(), canalKafkaWorker);
|
||||||
|
canalKafkaWorker.start();
|
||||||
|
logger.info("Start adapter for canal-client kafka topic: {} succeed",
|
||||||
|
kafkaTopic.getTopic() + "-" + group.getGroupId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadConnector(CanalOuterAdapterConfiguration config, List<CanalOuterAdapter> canalOutConnectors) {
|
||||||
|
try {
|
||||||
|
CanalOuterAdapter adapter = loader.getExtension(config.getName());
|
||||||
|
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||||
|
// 替换ClassLoader
|
||||||
|
Thread.currentThread().setContextClassLoader(adapter.getClass().getClassLoader());
|
||||||
|
adapter.init(config);
|
||||||
|
Thread.currentThread().setContextClassLoader(cl);
|
||||||
|
canalOutConnectors.add(adapter);
|
||||||
|
logger.info("Load canal adapter: {} succeed", config.getName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Load canal adapter: {} failed", config.getName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁所有适配器 为防止canal实例太多造成销毁阻塞, 并行销毁
|
||||||
|
*/
|
||||||
|
public void destroy() {
|
||||||
|
if (canalWorkers.size() > 0) {
|
||||||
|
ExecutorService stopExecutorService = Executors.newFixedThreadPool(canalWorkers.size());
|
||||||
|
for (CanalAdapterWorker v : canalWorkers.values()) {
|
||||||
|
final CanalAdapterWorker caw = v;
|
||||||
|
stopExecutorService.submit(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
caw.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stopExecutorService.shutdown();
|
||||||
|
}
|
||||||
|
if (canalKafkaWorkers.size() > 0) {
|
||||||
|
ExecutorService stopKafkaExecutorService = Executors.newFixedThreadPool(canalKafkaWorkers.size());
|
||||||
|
for (CanalAdapterKafkaWorker v : canalKafkaWorkers.values()) {
|
||||||
|
final CanalAdapterKafkaWorker cakw = v;
|
||||||
|
stopKafkaExecutorService.submit(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
cakw.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stopKafkaExecutorService.shutdown();
|
||||||
|
}
|
||||||
|
logger.info("All canal adapters destroyed");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package com.alibaba.otter.canal.client.adapter.loader;
|
||||||
|
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.CanalConnector;
|
||||||
|
import com.alibaba.otter.canal.client.CanalConnectors;
|
||||||
|
import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
|
||||||
|
import com.alibaba.otter.canal.client.impl.ClusterCanalConnector;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原生canal-server对应的client适配器工作线程
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-8-19 下午11:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalAdapterWorker extends AbstractCanalAdapterWorker {
|
||||||
|
|
||||||
|
private static final int BATCH_SIZE = 50;
|
||||||
|
private static final int SO_TIMEOUT = 0;
|
||||||
|
|
||||||
|
private CanalConnector connector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单台client适配器worker的构造方法
|
||||||
|
*
|
||||||
|
* @param canalDestination canal实例名
|
||||||
|
* @param address canal-server地址
|
||||||
|
* @param canalOuterAdapters 外部适配器组
|
||||||
|
*/
|
||||||
|
public CanalAdapterWorker(String canalDestination, SocketAddress address,
|
||||||
|
List<List<CanalOuterAdapter>> canalOuterAdapters){
|
||||||
|
this.canalOuterAdapters = canalOuterAdapters;
|
||||||
|
this.canalDestination = canalDestination;
|
||||||
|
groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size());
|
||||||
|
connector = CanalConnectors.newSingleConnector(address, canalDestination, "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HA模式下client适配器worker的构造方法
|
||||||
|
*
|
||||||
|
* @param canalDestination canal实例名
|
||||||
|
* @param zookeeperHosts zookeeper地址
|
||||||
|
* @param canalOuterAdapters 外部适配器组
|
||||||
|
*/
|
||||||
|
public CanalAdapterWorker(String canalDestination, String zookeeperHosts,
|
||||||
|
List<List<CanalOuterAdapter>> canalOuterAdapters){
|
||||||
|
this.canalOuterAdapters = canalOuterAdapters;
|
||||||
|
this.canalDestination = canalDestination;
|
||||||
|
groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size());
|
||||||
|
connector = CanalConnectors.newClusterConnector(zookeeperHosts, canalDestination, "", "");
|
||||||
|
((ClusterCanalConnector) connector).setSoTimeout(SO_TIMEOUT);
|
||||||
|
|
||||||
|
// super.initSwitcher(canalDestination);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
if (!running) {
|
||||||
|
thread = new Thread(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.setUncaughtExceptionHandler(handler);
|
||||||
|
thread.start();
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
try {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (switcher != null && !switcher.state()) {
|
||||||
|
// switcher.set(true);
|
||||||
|
// }
|
||||||
|
|
||||||
|
connector.stopRunning();
|
||||||
|
running = false;
|
||||||
|
|
||||||
|
logger.info("destination {} is waiting for adapters' worker thread die!", canalDestination);
|
||||||
|
if (thread != null) {
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groupInnerExecutorService.shutdown();
|
||||||
|
logger.info("destination {} adapters' worker thread dead!", canalDestination);
|
||||||
|
for (List<CanalOuterAdapter> outerAdapters : canalOuterAdapters) {
|
||||||
|
for (CanalOuterAdapter adapter : outerAdapters) {
|
||||||
|
adapter.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info("destination {} all adapters destroyed!", canalDestination);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process() {
|
||||||
|
while (!running)
|
||||||
|
; // waiting until running == true
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
// if (switcher != null) {
|
||||||
|
// switcher.get();
|
||||||
|
// }
|
||||||
|
logger.info("=============> Start to connect destination: {} <=============", this.canalDestination);
|
||||||
|
connector.connect();
|
||||||
|
logger.info("=============> Start to subscribe destination: {} <=============", this.canalDestination);
|
||||||
|
connector.subscribe();
|
||||||
|
logger.info("=============> Subscribe destination: {} succeed <=============", this.canalDestination);
|
||||||
|
while (running) {
|
||||||
|
// try {
|
||||||
|
// if (switcher != null) {
|
||||||
|
// switcher.get();
|
||||||
|
// }
|
||||||
|
// } catch (TimeoutException e) {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// server配置canal.instance.network.soTimeout(默认: 30s)
|
||||||
|
// 范围内未与server交互,server将关闭本次socket连接
|
||||||
|
Message message = connector.getWithoutAck(BATCH_SIZE); // 获取指定数量的数据
|
||||||
|
long batchId = message.getId();
|
||||||
|
try {
|
||||||
|
int size = message.getEntries().size();
|
||||||
|
|
||||||
|
if (batchId == -1 || size == 0) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("destination: {} batchId: {} batchSize: {} ",
|
||||||
|
this.canalDestination,
|
||||||
|
batchId,
|
||||||
|
size);
|
||||||
|
}
|
||||||
|
long begin = System.currentTimeMillis();
|
||||||
|
writeOut(message);
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("destination: {} batchId: {} elapsed time: {} ms",
|
||||||
|
this.canalDestination,
|
||||||
|
batchId,
|
||||||
|
now - begin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connector.ack(batchId); // 提交确认
|
||||||
|
} catch (Exception e) {
|
||||||
|
connector.rollback(batchId); // 处理失败, 回滚数据
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("process error!", e);
|
||||||
|
} finally {
|
||||||
|
connector.disconnect();
|
||||||
|
logger.info("=============> Disconnect destination: {} <=============", this.canalDestination);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (running) { // is reconnect
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
client-launcher/src/main/resources/canal-client.yml
Normal file
21
client-launcher/src/main/resources/canal-client.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
canalServerHost: 127.0.0.1:11111
|
||||||
|
#zookeeperHosts: slave1:2181
|
||||||
|
#bootstrapServers: slave1:6667,slave2:6667
|
||||||
|
|
||||||
|
canalInstances:
|
||||||
|
- instance: example
|
||||||
|
adapterGroups:
|
||||||
|
- outAdapters:
|
||||||
|
- name: logger
|
||||||
|
- name: hbase
|
||||||
|
hosts: slave1:2181
|
||||||
|
properties: {znodeParent: "/hbase-unsecure"}
|
||||||
|
#kafkaTopics:
|
||||||
|
#- topic: example
|
||||||
|
# groups:
|
||||||
|
# - groupId: example_g1
|
||||||
|
# outAdapters:
|
||||||
|
# - name: logger
|
||||||
|
# - name: hbase
|
||||||
|
# hosts: slave1:2181
|
||||||
|
# properties: {znodeParent: "/hbase-unsecure"}
|
||||||
47
client-launcher/src/main/resources/logback.xml
Normal file
47
client-launcher/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
|
<appender name="CANAL-ROOT" class="ch.qos.logback.classic.sift.SiftingAppender">
|
||||||
|
<discriminator>
|
||||||
|
<Key>destination</Key>
|
||||||
|
<DefaultValue>canal_client</DefaultValue>
|
||||||
|
</discriminator>
|
||||||
|
<sift>
|
||||||
|
<appender name="FILE-${destination}" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<File>../logs/${destination}.log</File>
|
||||||
|
<rollingPolicy
|
||||||
|
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<!-- rollover daily -->
|
||||||
|
<fileNamePattern>../logs/%d{yyyy-MM-dd}/${destination}-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
|
||||||
|
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||||
|
<!-- or whenever the file size reaches 100MB -->
|
||||||
|
<maxFileSize>512MB</maxFileSize>
|
||||||
|
</timeBasedFileNamingAndTriggeringPolicy>
|
||||||
|
<maxHistory>60</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>
|
||||||
|
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
|
||||||
|
</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
</sift>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="com.alibaba.otter.canal.client" additivity="false">
|
||||||
|
<level value="INFO" />
|
||||||
|
<!--<appender-ref ref="STDOUT"/>-->
|
||||||
|
<appender-ref ref="CANAL-ROOT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<root level="WARN">
|
||||||
|
<!--<appender-ref ref="STDOUT"/>-->
|
||||||
|
<appender-ref ref="CANAL-ROOT" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.26-SNAPSHOT</version>
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -16,6 +16,13 @@
|
|||||||
<artifactId>canal.protocol</artifactId>
|
<artifactId>canal.protocol</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 客户端要使用请单独引入kafka依赖 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.kafka</groupId>
|
||||||
|
<artifactId>kafka-clients</artifactId>
|
||||||
|
<version>1.1.1</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- junit -->
|
<!-- junit -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -69,7 +76,7 @@
|
|||||||
<links>
|
<links>
|
||||||
<link>https://github.com/alibaba/canal</link>
|
<link>https://github.com/alibaba/canal</link>
|
||||||
</links>
|
</links>
|
||||||
<outputDirectory>${project.build.directory}/apidocs/apidocs/${pom.version}</outputDirectory>
|
<outputDirectory>${project.build.directory}/apidocs/apidocs/${project.version}</outputDirectory>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|||||||
@@ -150,4 +150,10 @@ public interface CanalConnector {
|
|||||||
*/
|
*/
|
||||||
void rollback() throws CanalClientException;
|
void rollback() throws CanalClientException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中断的阻塞,用于优雅停止client
|
||||||
|
*
|
||||||
|
* @throws CanalClientException
|
||||||
|
*/
|
||||||
|
void stopRunning() throws CanalClientException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ public class CanalConnectors {
|
|||||||
public static CanalConnector newSingleConnector(SocketAddress address, String destination, String username,
|
public static CanalConnector newSingleConnector(SocketAddress address, String destination, String username,
|
||||||
String password) {
|
String password) {
|
||||||
SimpleCanalConnector canalConnector = new SimpleCanalConnector(address, username, password, destination);
|
SimpleCanalConnector canalConnector = new SimpleCanalConnector(address, username, password, destination);
|
||||||
canalConnector.setSoTimeout(30 * 1000);
|
canalConnector.setSoTimeout(60 * 1000);
|
||||||
|
canalConnector.setIdleTimeout(60 * 60 * 1000);
|
||||||
return canalConnector;
|
return canalConnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +49,8 @@ public class CanalConnectors {
|
|||||||
password,
|
password,
|
||||||
destination,
|
destination,
|
||||||
new SimpleNodeAccessStrategy(addresses));
|
new SimpleNodeAccessStrategy(addresses));
|
||||||
canalConnector.setSoTimeout(30 * 1000);
|
canalConnector.setSoTimeout(60 * 1000);
|
||||||
|
canalConnector.setIdleTimeout(60 * 60 * 1000);
|
||||||
return canalConnector;
|
return canalConnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +69,8 @@ public class CanalConnectors {
|
|||||||
password,
|
password,
|
||||||
destination,
|
destination,
|
||||||
new ClusterNodeAccessStrategy(destination, ZkClientx.getZkClient(zkServers)));
|
new ClusterNodeAccessStrategy(destination, ZkClientx.getZkClient(zkServers)));
|
||||||
canalConnector.setSoTimeout(30 * 1000);
|
canalConnector.setSoTimeout(60 * 1000);
|
||||||
|
canalConnector.setIdleTimeout(60 * 60 * 1000);
|
||||||
return canalConnector;
|
return canalConnector;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
private int soTimeout = 10000;
|
private int soTimeout = 60000;
|
||||||
private int retryTimes = 3;
|
private int idleTimeout = 60 * 60 * 1000;
|
||||||
|
private int retryTimes = 3; // 设置-1时可以subscribe阻塞等待时优雅停机
|
||||||
private int retryInterval = 5000; // 重试的时间间隔,默认5秒
|
private int retryInterval = 5000; // 重试的时间间隔,默认5秒
|
||||||
private CanalNodeAccessStrategy accessStrategy;
|
private CanalNodeAccessStrategy accessStrategy;
|
||||||
private SimpleCanalConnector currentConnector;
|
private SimpleCanalConnector currentConnector;
|
||||||
@@ -52,6 +53,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
};
|
};
|
||||||
currentConnector.setSoTimeout(soTimeout);
|
currentConnector.setSoTimeout(soTimeout);
|
||||||
|
currentConnector.setIdleTimeout(idleTimeout);
|
||||||
if (filter != null) {
|
if (filter != null) {
|
||||||
currentConnector.setFilter(filter);
|
currentConnector.setFilter(filter);
|
||||||
}
|
}
|
||||||
@@ -106,13 +108,17 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn(String.format(
|
if (retryTimes == -1 && t.getCause() instanceof InterruptedException) {
|
||||||
"something goes wrong when subscribing from server: %s",
|
logger.info("block waiting interrupted by other thread.");
|
||||||
currentConnector != null ? currentConnector.getAddress() : "null"),
|
return;
|
||||||
t);
|
} else {
|
||||||
times++;
|
logger.warn(String.format("something goes wrong when subscribing from server: %s",
|
||||||
restart();
|
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||||
logger.info("restart the connector for next round retry.");
|
times++;
|
||||||
|
restart();
|
||||||
|
logger.info("restart the connector for next round retry.");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +184,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
return msg;
|
return msg;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
||||||
currentConnector.getAddress()), t);
|
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -195,7 +201,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
return msg;
|
return msg;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
||||||
currentConnector.getAddress()), t);
|
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -212,7 +218,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
||||||
currentConnector.getAddress()), t);
|
currentConnector != null ? currentConnector.getAddress() : "null"),
|
||||||
|
t);
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -229,7 +236,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
||||||
currentConnector.getAddress()), t);
|
currentConnector != null ? currentConnector.getAddress() : "null"),
|
||||||
|
t);
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -247,7 +255,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn(String.format("something goes wrong when acking data from server:%s",
|
logger.warn(String.format("something goes wrong when acking data from server:%s",
|
||||||
currentConnector.getAddress()), t);
|
currentConnector != null ? currentConnector.getAddress() : "null"),
|
||||||
|
t);
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -294,6 +303,14 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
this.soTimeout = soTimeout;
|
this.soTimeout = soTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getIdleTimeout() {
|
||||||
|
return idleTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIdleTimeout(int idleTimeout) {
|
||||||
|
this.idleTimeout = idleTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
public int getRetryTimes() {
|
public int getRetryTimes() {
|
||||||
return retryTimes;
|
return retryTimes;
|
||||||
}
|
}
|
||||||
@@ -322,4 +339,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
return currentConnector;
|
return currentConnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void stopRunning() {
|
||||||
|
currentConnector.stopRunning();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import com.google.protobuf.ByteString;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于{@linkplain CanalServerWithNetty}定义的网络协议接口,对于canal数据进行get/rollback/ack等操作
|
* 基于{@linkplain CanalServerWithNetty}定义的网络协议接口,对于canal数据进行get/rollback/ack等操作
|
||||||
*
|
*
|
||||||
* @author jianghang 2012-10-24 下午05:37:20
|
* @author jianghang 2012-10-24 下午05:37:20
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
@@ -55,6 +55,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
private int soTimeout = 60000; // milliseconds
|
private int soTimeout = 60000; // milliseconds
|
||||||
|
private int idleTimeout = 60 * 60 * 1000; // client和server之间的空闲链接超时的时间,默认为1小时
|
||||||
private String filter; // 记录上一次的filter提交值,便于自动重试时提交
|
private String filter; // 记录上一次的filter提交值,便于自动重试时提交
|
||||||
|
|
||||||
private final ByteBuffer readHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
private final ByteBuffer readHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
||||||
@@ -70,21 +71,29 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
private volatile boolean connected = false; // 代表connected是否已正常执行,因为有HA,不代表在工作中
|
private volatile boolean connected = false; // 代表connected是否已正常执行,因为有HA,不代表在工作中
|
||||||
private boolean rollbackOnConnect = true; // 是否在connect链接成功后,自动执行rollback操作
|
private boolean rollbackOnConnect = true; // 是否在connect链接成功后,自动执行rollback操作
|
||||||
private boolean rollbackOnDisConnect = false; // 是否在connect链接成功后,自动执行rollback操作
|
private boolean rollbackOnDisConnect = false; // 是否在connect链接成功后,自动执行rollback操作
|
||||||
|
private boolean lazyParseEntry = false; // 是否自动化解析Entry对象,如果考虑最大化性能可以延后解析
|
||||||
// 读写数据分别使用不同的锁进行控制,减小锁粒度,读也需要排他锁,并发度容易造成数据包混乱,反序列化失败
|
// 读写数据分别使用不同的锁进行控制,减小锁粒度,读也需要排他锁,并发度容易造成数据包混乱,反序列化失败
|
||||||
private Object readDataLock = new Object();
|
private Object readDataLock = new Object();
|
||||||
private Object writeDataLock = new Object();
|
private Object writeDataLock = new Object();
|
||||||
|
|
||||||
|
private volatile boolean running = false;
|
||||||
|
|
||||||
public SimpleCanalConnector(SocketAddress address, String username, String password, String destination){
|
public SimpleCanalConnector(SocketAddress address, String username, String password, String destination){
|
||||||
this(address, username, password, destination, 60000);
|
this(address, username, password, destination, 60000, 60 * 60 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SimpleCanalConnector(SocketAddress address, String username, String password, String destination,
|
public SimpleCanalConnector(SocketAddress address, String username, String password, String destination,
|
||||||
int soTimeout){
|
int soTimeout){
|
||||||
|
this(address, username, password, destination, soTimeout, 60 * 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleCanalConnector(SocketAddress address, String username, String password, String destination,
|
||||||
|
int soTimeout, int idleTimeout){
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.soTimeout = soTimeout;
|
this.soTimeout = soTimeout;
|
||||||
|
this.idleTimeout = idleTimeout;
|
||||||
this.clientIdentity = new ClientIdentity(destination, (short) 1001);
|
this.clientIdentity = new ClientIdentity(destination, (short) 1001);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +108,9 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
doConnect();
|
doConnect();
|
||||||
if (filter != null) { // 如果存在条件,说明是自动切换,基于上一次的条件订阅一次
|
if (filter != null) { // 如果存在条件,说明是自动切换,基于上一次的条件订阅一次
|
||||||
subscribe(filter);
|
subscribe(filter);
|
||||||
@@ -152,8 +164,8 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
ClientAuth ca = ClientAuth.newBuilder()
|
ClientAuth ca = ClientAuth.newBuilder()
|
||||||
.setUsername(username != null ? username : "")
|
.setUsername(username != null ? username : "")
|
||||||
.setPassword(ByteString.copyFromUtf8(password != null ? password : ""))
|
.setPassword(ByteString.copyFromUtf8(password != null ? password : ""))
|
||||||
.setNetReadTimeout(soTimeout)
|
.setNetReadTimeout(idleTimeout)
|
||||||
.setNetWriteTimeout(soTimeout)
|
.setNetWriteTimeout(idleTimeout)
|
||||||
.build();
|
.build();
|
||||||
writeWithHeader(Packet.newBuilder()
|
writeWithHeader(Packet.newBuilder()
|
||||||
.setType(PacketType.CLIENTAUTHENTICATION)
|
.setType(PacketType.CLIENTAUTHENTICATION)
|
||||||
@@ -208,6 +220,9 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
public void subscribe(String filter) throws CanalClientException {
|
public void subscribe(String filter) throws CanalClientException {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
writeWithHeader(Packet.newBuilder()
|
writeWithHeader(Packet.newBuilder()
|
||||||
.setType(PacketType.SUBSCRIPTION)
|
.setType(PacketType.SUBSCRIPTION)
|
||||||
@@ -234,6 +249,9 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
public void unsubscribe() throws CanalClientException {
|
public void unsubscribe() throws CanalClientException {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
writeWithHeader(Packet.newBuilder()
|
writeWithHeader(Packet.newBuilder()
|
||||||
.setType(PacketType.UNSUBSCRIPTION)
|
.setType(PacketType.UNSUBSCRIPTION)
|
||||||
@@ -271,6 +289,9 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
|
public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
int size = (batchSize <= 0) ? 1000 : batchSize;
|
int size = (batchSize <= 0) ? 1000 : batchSize;
|
||||||
long time = (timeout == null || timeout < 0) ? -1 : timeout; // -1代表不做timeout控制
|
long time = (timeout == null || timeout < 0) ? -1 : timeout; // -1代表不做timeout控制
|
||||||
@@ -298,7 +319,8 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Message receiveMessages() throws IOException {
|
private Message receiveMessages() throws IOException {
|
||||||
Packet p = Packet.parseFrom(readNextPacket());
|
byte[] data = readNextPacket();
|
||||||
|
Packet p = Packet.parseFrom(data);
|
||||||
switch (p.getType()) {
|
switch (p.getType()) {
|
||||||
case MESSAGES: {
|
case MESSAGES: {
|
||||||
if (!p.getCompression().equals(Compression.NONE)) {
|
if (!p.getCompression().equals(Compression.NONE)) {
|
||||||
@@ -307,8 +329,13 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
Messages messages = Messages.parseFrom(p.getBody());
|
Messages messages = Messages.parseFrom(p.getBody());
|
||||||
Message result = new Message(messages.getBatchId());
|
Message result = new Message(messages.getBatchId());
|
||||||
for (ByteString byteString : messages.getMessagesList()) {
|
if (lazyParseEntry) {
|
||||||
result.addEntry(Entry.parseFrom(byteString));
|
// byteString
|
||||||
|
result.setRawEntries(messages.getMessagesList());
|
||||||
|
} else {
|
||||||
|
for (ByteString byteString : messages.getMessagesList()) {
|
||||||
|
result.addEntry(Entry.parseFrom(byteString));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -324,6 +351,9 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
public void ack(long batchId) throws CanalClientException {
|
public void ack(long batchId) throws CanalClientException {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ClientAck ca = ClientAck.newBuilder()
|
ClientAck ca = ClientAck.newBuilder()
|
||||||
.setDestination(clientIdentity.getDestination())
|
.setDestination(clientIdentity.getDestination())
|
||||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||||
@@ -445,7 +475,11 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
throw new CanalClientException("should connect first");
|
throw new CanalClientException("should connect first");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
running = true;
|
||||||
mutex.get();// 阻塞等待
|
mutex.get();// 阻塞等待
|
||||||
|
} else {
|
||||||
|
// 单机模式直接设置为running
|
||||||
|
running = true;
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
@@ -485,6 +519,14 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
this.soTimeout = soTimeout;
|
this.soTimeout = soTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getIdleTimeout() {
|
||||||
|
return idleTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIdleTimeout(int idleTimeout) {
|
||||||
|
this.idleTimeout = idleTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
public void setZkClientx(ZkClientx zkClientx) {
|
public void setZkClientx(ZkClientx zkClientx) {
|
||||||
this.zkClientx = zkClientx;
|
this.zkClientx = zkClientx;
|
||||||
initClientRunningMonitor(this.clientIdentity);
|
initClientRunningMonitor(this.clientIdentity);
|
||||||
@@ -502,4 +544,21 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isLazyParseEntry() {
|
||||||
|
return lazyParseEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLazyParseEntry(boolean lazyParseEntry) {
|
||||||
|
this.lazyParseEntry = lazyParseEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopRunning() {
|
||||||
|
if (running) {
|
||||||
|
running = false; // 设置为非running状态
|
||||||
|
if (!mutex.state()) {
|
||||||
|
mutex.set(true); // 中断阻塞
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,10 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
String path = ZookeeperPathUtils.getDestinationClientRunning(this.destination, clientData.getClientId());
|
String path = ZookeeperPathUtils.getDestinationClientRunning(this.destination, clientData.getClientId());
|
||||||
zkClient.unsubscribeDataChanges(path, dataListener);
|
zkClient.unsubscribeDataChanges(path, dataListener);
|
||||||
releaseRunning(); // 尝试一下release
|
releaseRunning(); // 尝试一下release
|
||||||
|
//Fix issue #697
|
||||||
|
if (delayExector != null) {
|
||||||
|
delayExector.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 改动记录:
|
// 改动记录:
|
||||||
|
|||||||
@@ -0,0 +1,241 @@
|
|||||||
|
package com.alibaba.otter.canal.client.kafka;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.kafka.clients.consumer.ConsumerRecords;
|
||||||
|
import org.apache.kafka.clients.consumer.KafkaConsumer;
|
||||||
|
import org.apache.kafka.common.TopicPartition;
|
||||||
|
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.kafka.running.ClientRunningData;
|
||||||
|
import com.alibaba.otter.canal.common.utils.AddressUtils;
|
||||||
|
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* canal kafka 数据操作客户端
|
||||||
|
*
|
||||||
|
* @author machengyuan @ 2018-6-12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class KafkaCanalConnector {
|
||||||
|
|
||||||
|
private KafkaConsumer<String, Message> kafkaConsumer;
|
||||||
|
private String topic;
|
||||||
|
private Integer partition;
|
||||||
|
private Properties properties;
|
||||||
|
// private ClientRunningMonitor runningMonitor; // 运行控制
|
||||||
|
// private BooleanMutex mutex = new BooleanMutex(false);
|
||||||
|
private ZkClientx zkClientx;
|
||||||
|
private volatile boolean connected = false;
|
||||||
|
private volatile boolean running = false;
|
||||||
|
|
||||||
|
public KafkaCanalConnector(String zkServers, String servers, String topic, Integer partition, String groupId){
|
||||||
|
this.topic = topic;
|
||||||
|
this.partition = partition;
|
||||||
|
|
||||||
|
properties = new Properties();
|
||||||
|
properties.put("bootstrap.servers", servers);
|
||||||
|
properties.put("group.id", groupId);
|
||||||
|
properties.put("enable.auto.commit", false);
|
||||||
|
properties.put("auto.commit.interval.ms", "1000");
|
||||||
|
properties.put("auto.offset.reset", "latest"); // 如果没有offset则从最后的offset开始读
|
||||||
|
properties.put("request.timeout.ms", "40000"); // 必须大于session.timeout.ms的设置
|
||||||
|
properties.put("session.timeout.ms", "30000"); // 默认为30秒
|
||||||
|
properties.put("max.poll.records", "1"); // 一次只取一条message数据
|
||||||
|
properties.put("key.deserializer", StringDeserializer.class.getName());
|
||||||
|
properties.put("value.deserializer", MessageDeserializer.class.getName());
|
||||||
|
|
||||||
|
if (zkServers != null) {
|
||||||
|
zkClientx = new ZkClientx(zkServers);
|
||||||
|
|
||||||
|
ClientRunningData clientData = new ClientRunningData();
|
||||||
|
clientData.setGroupId(groupId);
|
||||||
|
clientData.setAddress(AddressUtils.getHostIp());
|
||||||
|
|
||||||
|
// runningMonitor = new ClientRunningMonitor();
|
||||||
|
// runningMonitor.setTopic(topic);
|
||||||
|
// runningMonitor.setZkClient(zkClientx);
|
||||||
|
// runningMonitor.setClientData(clientData);
|
||||||
|
// runningMonitor.setListener(new ClientRunningListener() {
|
||||||
|
// public void processActiveEnter() {
|
||||||
|
// mutex.set(true);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public void processActiveExit() {
|
||||||
|
// mutex.set(false);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新设置sessionTime
|
||||||
|
*
|
||||||
|
* @param timeout
|
||||||
|
* @param unit
|
||||||
|
*/
|
||||||
|
public void setSessionTimeout(Long timeout, TimeUnit unit) {
|
||||||
|
long t = unit.toMillis(timeout);
|
||||||
|
properties.put("request.timeout.ms", String.valueOf(t + 60000));
|
||||||
|
properties.put("session.timeout.ms", String.valueOf(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开连接
|
||||||
|
*/
|
||||||
|
public void connect() {
|
||||||
|
if (connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (runningMonitor != null) {
|
||||||
|
// if (!runningMonitor.isStart()) {
|
||||||
|
// runningMonitor.start();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
connected = true;
|
||||||
|
|
||||||
|
if (kafkaConsumer == null) {
|
||||||
|
kafkaConsumer = new KafkaConsumer<String, Message>(properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭链接
|
||||||
|
*/
|
||||||
|
public void disconnnect() {
|
||||||
|
kafkaConsumer.close();
|
||||||
|
|
||||||
|
connected = false;
|
||||||
|
// if (runningMonitor.isStart()) {
|
||||||
|
// runningMonitor.stop();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitClientRunning() {
|
||||||
|
try {
|
||||||
|
if (zkClientx != null) {
|
||||||
|
if (!connected) {// 未调用connect
|
||||||
|
throw new CanalClientException("should connect first");
|
||||||
|
}
|
||||||
|
|
||||||
|
running = true;
|
||||||
|
// mutex.get();// 阻塞等待
|
||||||
|
} else {
|
||||||
|
// 单机模式直接设置为running
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new CanalClientException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean checkValid() {
|
||||||
|
if (zkClientx != null) {
|
||||||
|
// return mutex.state();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return true;// 默认都放过
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅topic
|
||||||
|
*/
|
||||||
|
public void subscribe() {
|
||||||
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partition == null) {
|
||||||
|
kafkaConsumer.subscribe(Collections.singletonList(topic));
|
||||||
|
} else {
|
||||||
|
TopicPartition topicPartition = new TopicPartition(topic, partition);
|
||||||
|
kafkaConsumer.assign(Collections.singletonList(topicPartition));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消订阅
|
||||||
|
*/
|
||||||
|
public void unsubscribe() {
|
||||||
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
kafkaConsumer.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据,自动进行确认
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Message get() {
|
||||||
|
return get(100L, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message get(Long timeout, TimeUnit unit) {
|
||||||
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Message message = getWithoutAck(timeout, unit);
|
||||||
|
this.ack();
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message getWithoutAck() {
|
||||||
|
return getWithoutAck(100L, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据,不进行确认,等待处理完成手工确认
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Message getWithoutAck(Long timeout, TimeUnit unit) {
|
||||||
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsumerRecords<String, Message> records = kafkaConsumer.poll(unit.toMillis(timeout)); // 基于配置,最多只能poll到一条数据
|
||||||
|
|
||||||
|
if (!records.isEmpty()) {
|
||||||
|
return records.iterator().next().value();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交offset,如果超过 session.timeout.ms 设置的时间没有ack则会抛出异常,ack失败
|
||||||
|
*/
|
||||||
|
public void ack() {
|
||||||
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
kafkaConsumer.commitSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopRunning() {
|
||||||
|
if (running) {
|
||||||
|
running = false; // 设置为非running状态
|
||||||
|
// if (!mutex.state()) {
|
||||||
|
// mutex.set(true); // 中断阻塞
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
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(null, servers, topic, partition, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建kafka客户端链接,独立运行不注册zk信息
|
||||||
|
*
|
||||||
|
* @param servers
|
||||||
|
* @param topic
|
||||||
|
* @param groupId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static KafkaCanalConnector newKafkaConnector(String servers, String topic, String groupId) {
|
||||||
|
return new KafkaCanalConnector(null, servers, topic, null, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建kafka客户端链接
|
||||||
|
*
|
||||||
|
* @param zkServers
|
||||||
|
* @param servers
|
||||||
|
* @param topic
|
||||||
|
* @param partition
|
||||||
|
* @param groupId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static KafkaCanalConnector newKafkaConnector(String zkServers, String servers, String topic, Integer partition, String groupId) {
|
||||||
|
return new KafkaCanalConnector(zkServers, servers, topic, partition, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建kafka客户端链接
|
||||||
|
*
|
||||||
|
* @param zkServers
|
||||||
|
* @param servers
|
||||||
|
* @param topic
|
||||||
|
* @param groupId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static KafkaCanalConnector newKafkaConnector(String zkServers, String servers, String topic, String groupId) {
|
||||||
|
return new KafkaCanalConnector(zkServers, servers, topic, null, groupId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.alibaba.otter.canal.client.kafka;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.kafka.common.serialization.Deserializer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalPacket;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kafka Message类的反序列化
|
||||||
|
*
|
||||||
|
* @author machengyuan @ 2018-6-12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class MessageDeserializer implements Deserializer<Message> {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(MessageDeserializer.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(Map<String, ?> configs, boolean isKey) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message deserialize(String topic, byte[] data) {
|
||||||
|
try {
|
||||||
|
if (data == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
CanalPacket.Packet p = CanalPacket.Packet.parseFrom(data);
|
||||||
|
switch (p.getType()) {
|
||||||
|
case MESSAGES: {
|
||||||
|
if (!p.getCompression().equals(CanalPacket.Compression.NONE)) {
|
||||||
|
throw new CanalClientException("compression is not supported in this connector");
|
||||||
|
}
|
||||||
|
|
||||||
|
CanalPacket.Messages messages = CanalPacket.Messages.parseFrom(p.getBody());
|
||||||
|
Message result = new Message(messages.getBatchId());
|
||||||
|
for (ByteString byteString : messages.getMessagesList()) {
|
||||||
|
result.addEntry(CanalEntry.Entry.parseFrom(byteString));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error when deserializing byte[] to message ");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.alibaba.otter.canal.client.kafka.running;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* client running状态信息
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-06-20 下午04:10:12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class ClientRunningData {
|
||||||
|
|
||||||
|
private String groupId;
|
||||||
|
private String address;
|
||||||
|
private boolean active = true;
|
||||||
|
|
||||||
|
public String getGroupId() {
|
||||||
|
return groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupId(String groupId) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddress(String address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isActive() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActive(boolean active) {
|
||||||
|
this.active = active;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.alibaba.otter.canal.client.kafka.running;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* client running状态信息
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-06-20 下午04:10:12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public interface ClientRunningListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发现在轮到自己做为active
|
||||||
|
*/
|
||||||
|
public void processActiveEnter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发一下当前active模式失败
|
||||||
|
*/
|
||||||
|
public void processActiveExit();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
package com.alibaba.otter.canal.client.kafka.running;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.I0Itec.zkclient.IZkDataListener;
|
||||||
|
import org.I0Itec.zkclient.exception.ZkException;
|
||||||
|
import org.I0Itec.zkclient.exception.ZkInterruptedException;
|
||||||
|
import org.I0Itec.zkclient.exception.ZkNoNodeException;
|
||||||
|
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
|
||||||
|
import org.apache.zookeeper.CreateMode;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
||||||
|
import com.alibaba.otter.canal.common.utils.AddressUtils;
|
||||||
|
import com.alibaba.otter.canal.common.utils.BooleanMutex;
|
||||||
|
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
||||||
|
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
||||||
|
import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils;
|
||||||
|
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* kafka client running状态信息
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-06-20 下午04:10:12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class ClientRunningMonitor extends AbstractCanalLifeCycle {
|
||||||
|
|
||||||
|
private static final String TOPIC_ROOT_NODE = ZookeeperPathUtils.CANAL_ROOT_NODE
|
||||||
|
+ ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + "topics";
|
||||||
|
|
||||||
|
private static final String TOPIC_NODE = TOPIC_ROOT_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR
|
||||||
|
+ "{0}";
|
||||||
|
|
||||||
|
private static final String TOPIC_CLIENTID_NODE = TOPIC_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR
|
||||||
|
+ "{1}";
|
||||||
|
|
||||||
|
private static final String TOPIC_CLIENTID_RUNNING_NODE = TOPIC_CLIENTID_NODE
|
||||||
|
+ ZookeeperPathUtils.ZOOKEEPER_SEPARATOR
|
||||||
|
+ ZookeeperPathUtils.RUNNING_NODE;
|
||||||
|
|
||||||
|
private static String getTopicClientRunning(String topic, String groupId) {
|
||||||
|
return MessageFormat.format(TOPIC_CLIENTID_RUNNING_NODE, topic, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getClientIdNodePath(String topic, String groupId) {
|
||||||
|
return MessageFormat.format(TOPIC_CLIENTID_NODE, topic, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ClientRunningMonitor.class);
|
||||||
|
private ZkClientx zkClient;
|
||||||
|
private String topic;
|
||||||
|
private ClientRunningData clientData;
|
||||||
|
private IZkDataListener dataListener;
|
||||||
|
private BooleanMutex mutex = new BooleanMutex(false);
|
||||||
|
private volatile boolean release = false;
|
||||||
|
private volatile ClientRunningData activeData;
|
||||||
|
private ScheduledExecutorService delayExector = Executors.newScheduledThreadPool(1);
|
||||||
|
private ClientRunningListener listener;
|
||||||
|
private int delayTime = 5;
|
||||||
|
|
||||||
|
private static Integer virtualPort;
|
||||||
|
|
||||||
|
public ClientRunningMonitor(){
|
||||||
|
if (virtualPort == null) {
|
||||||
|
Random rand = new Random();
|
||||||
|
virtualPort = rand.nextInt(9000) + 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataListener = new IZkDataListener() {
|
||||||
|
|
||||||
|
public void handleDataChange(String dataPath, Object data) throws Exception {
|
||||||
|
MDC.put("kafkaTopic", topic);
|
||||||
|
ClientRunningData runningData = JsonUtils.unmarshalFromByte((byte[]) data, ClientRunningData.class);
|
||||||
|
if (!isMine(runningData.getAddress())) {
|
||||||
|
mutex.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!runningData.isActive() && isMine(runningData.getAddress())) { // 说明出现了主动释放的操作,并且本机之前是active
|
||||||
|
release = true;
|
||||||
|
releaseRunning();// 彻底释放mainstem
|
||||||
|
}
|
||||||
|
|
||||||
|
activeData = (ClientRunningData) runningData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleDataDeleted(String dataPath) throws Exception {
|
||||||
|
MDC.put("kafkaTopic", topic);
|
||||||
|
|
||||||
|
mutex.set(false);
|
||||||
|
// 触发一下退出,可能是人为干预的释放操作或者网络闪断引起的session expired timeout
|
||||||
|
processActiveExit();
|
||||||
|
if (!release && activeData != null && isMine(activeData.getAddress())) {
|
||||||
|
// 如果上一次active的状态就是本机,则即时触发一下active抢占
|
||||||
|
initRunning();
|
||||||
|
} else {
|
||||||
|
// 否则就是等待delayTime,避免因网络瞬端或者zk异常,导致出现频繁的切换操作
|
||||||
|
delayExector.schedule(new Runnable() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
initRunning();
|
||||||
|
}
|
||||||
|
}, delayTime, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
super.start();
|
||||||
|
|
||||||
|
String path = getTopicClientRunning(this.topic, clientData.getGroupId());
|
||||||
|
|
||||||
|
zkClient.subscribeDataChanges(path, dataListener);
|
||||||
|
initRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
super.stop();
|
||||||
|
String path = getTopicClientRunning(this.topic, clientData.getGroupId());
|
||||||
|
zkClient.unsubscribeDataChanges(path, dataListener);
|
||||||
|
releaseRunning(); // 尝试一下release
|
||||||
|
// Fix issue #697
|
||||||
|
if (delayExector != null) {
|
||||||
|
delayExector.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 改动记录:
|
||||||
|
// 1,在方法上加synchronized关键字,保证同步顺序执行;
|
||||||
|
// 2,判断Zk上已经存在的activeData是否是本机,是的话把mutex重置为true,否则会导致死锁
|
||||||
|
// 3,增加异常处理,保证出现异常时,running节点能被删除,否则会导致死锁
|
||||||
|
public synchronized void initRunning() {
|
||||||
|
if (!isStart()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String path = getTopicClientRunning(this.topic, clientData.getGroupId());
|
||||||
|
// 序列化
|
||||||
|
byte[] bytes = JsonUtils.marshalToByte(clientData);
|
||||||
|
try {
|
||||||
|
mutex.set(false);
|
||||||
|
zkClient.create(path, bytes, CreateMode.EPHEMERAL);
|
||||||
|
processActiveEnter();// 触发一下事件
|
||||||
|
activeData = clientData;
|
||||||
|
mutex.set(true);
|
||||||
|
} catch (ZkNodeExistsException e) {
|
||||||
|
bytes = zkClient.readData(path, true);
|
||||||
|
if (bytes == null) {// 如果不存在节点,立即尝试一次
|
||||||
|
initRunning();
|
||||||
|
} else {
|
||||||
|
activeData = JsonUtils.unmarshalFromByte(bytes, ClientRunningData.class);
|
||||||
|
// 如果发现已经存在,判断一下是否自己,避免活锁
|
||||||
|
if (activeData.getAddress().contains(":") && isMine(activeData.getAddress())) {
|
||||||
|
mutex.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ZkNoNodeException e) {
|
||||||
|
zkClient.createPersistent(getClientIdNodePath(this.topic, clientData.getGroupId()), true); // 尝试创建父节点
|
||||||
|
initRunning();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.error(MessageFormat.format("There is an error when execute initRunning method, with destination [{0}].",
|
||||||
|
topic),
|
||||||
|
t);
|
||||||
|
// 出现任何异常尝试release
|
||||||
|
releaseRunning();
|
||||||
|
throw new CanalClientException("something goes wrong in initRunning method. ", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阻塞等待自己成为active,如果自己成为active,立马返回
|
||||||
|
*
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public void waitForActive() throws InterruptedException {
|
||||||
|
initRunning();
|
||||||
|
mutex.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查当前的状态
|
||||||
|
*/
|
||||||
|
public boolean check() {
|
||||||
|
String path = getTopicClientRunning(this.topic, clientData.getGroupId());
|
||||||
|
// ZookeeperPathUtils.getDestinationClientRunning(this.destination,
|
||||||
|
// clientData.getClientId());
|
||||||
|
try {
|
||||||
|
byte[] bytes = zkClient.readData(path);
|
||||||
|
ClientRunningData eventData = JsonUtils.unmarshalFromByte(bytes, ClientRunningData.class);
|
||||||
|
activeData = eventData;// 更新下为最新值
|
||||||
|
// 检查下nid是否为自己
|
||||||
|
boolean result = isMine(activeData.getAddress());
|
||||||
|
if (!result) {
|
||||||
|
logger.warn("canal is running in [{}] , but not in [{}]",
|
||||||
|
activeData.getAddress(),
|
||||||
|
clientData.getAddress());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (ZkNoNodeException e) {
|
||||||
|
logger.warn("canal is not run any in node");
|
||||||
|
return false;
|
||||||
|
} catch (ZkInterruptedException e) {
|
||||||
|
logger.warn("canal check is interrupt");
|
||||||
|
Thread.interrupted();// 清除interrupt标记
|
||||||
|
return check();
|
||||||
|
} catch (ZkException e) {
|
||||||
|
logger.warn("canal check is failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean releaseRunning() {
|
||||||
|
if (check()) {
|
||||||
|
String path = getTopicClientRunning(this.topic, clientData.getGroupId());
|
||||||
|
zkClient.delete(path);
|
||||||
|
mutex.set(false);
|
||||||
|
processActiveExit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== helper method ======================
|
||||||
|
|
||||||
|
private boolean isMine(String address) {
|
||||||
|
return address.equals(clientData.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processActiveEnter() {
|
||||||
|
if (listener != null) {
|
||||||
|
// 触发回调
|
||||||
|
listener.processActiveEnter();
|
||||||
|
this.clientData.setAddress(/* address */AddressUtils.getHostIp() + ":" + virtualPort);
|
||||||
|
|
||||||
|
String path = getTopicClientRunning(this.topic, clientData.getGroupId());
|
||||||
|
// 序列化
|
||||||
|
byte[] bytes = JsonUtils.marshalToByte(clientData);
|
||||||
|
zkClient.writeData(path, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processActiveExit() {
|
||||||
|
if (listener != null) {
|
||||||
|
listener.processActiveExit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setListener(ClientRunningListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== setter / getter =======================
|
||||||
|
|
||||||
|
public void setTopic(String topic) {
|
||||||
|
this.topic = topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientData(ClientRunningData clientData) {
|
||||||
|
this.clientData = clientData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDelayTime(int delayTime) {
|
||||||
|
this.delayTime = delayTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZkClient(ZkClientx zkClient) {
|
||||||
|
this.zkClient = zkClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.alibaba.otter.canal.client.running.kafka;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kafka 测试基类
|
||||||
|
*
|
||||||
|
* @author machengyuan @ 2018-6-12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractKafkaTest {
|
||||||
|
|
||||||
|
public static String topic = "example";
|
||||||
|
public static Integer partition = null;
|
||||||
|
public static String groupId = "g4";
|
||||||
|
public static String servers = "slave1:6667,slave2:6667,slave3:6667";
|
||||||
|
public static String zkServers = "slave1:2181,slave2:2181,slave3:2181";
|
||||||
|
|
||||||
|
public void sleep(long time) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(time);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Assert.fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package com.alibaba.otter.canal.client.running.kafka;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.kafka.common.errors.WakeupException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector;
|
||||||
|
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kafka client example
|
||||||
|
*
|
||||||
|
* @author machengyuan @ 2018-6-12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalKafkaClientExample {
|
||||||
|
|
||||||
|
protected final static Logger logger = LoggerFactory.getLogger(CanalKafkaClientExample.class);
|
||||||
|
|
||||||
|
private KafkaCanalConnector connector;
|
||||||
|
|
||||||
|
private static volatile boolean running = false;
|
||||||
|
|
||||||
|
private Thread thread = null;
|
||||||
|
|
||||||
|
private Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() {
|
||||||
|
|
||||||
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
|
logger.error("parse events has an error", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public CanalKafkaClientExample(String zkServers, String servers, String topic, Integer partition, String groupId) {
|
||||||
|
connector = KafkaCanalConnectors.newKafkaConnector(zkServers, servers, topic, partition, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
final CanalKafkaClientExample kafkaCanalClientExample = new CanalKafkaClientExample(AbstractKafkaTest.zkServers,
|
||||||
|
AbstractKafkaTest.servers,
|
||||||
|
AbstractKafkaTest.topic,
|
||||||
|
AbstractKafkaTest.partition,
|
||||||
|
AbstractKafkaTest.groupId);
|
||||||
|
logger.info("## start the kafka consumer: {}-{}", AbstractKafkaTest.topic, AbstractKafkaTest.groupId);
|
||||||
|
kafkaCanalClientExample.start();
|
||||||
|
logger.info("## the canal kafka consumer is running now ......");
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
logger.info("## stop the kafka consumer");
|
||||||
|
kafkaCanalClientExample.stop();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("##something goes wrong when stopping kafka consumer:", e);
|
||||||
|
} finally {
|
||||||
|
logger.info("## kafka consumer is down.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
while (running)
|
||||||
|
;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("## Something goes wrong when starting up the kafka consumer:", e);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
Assert.notNull(connector, "connector is null");
|
||||||
|
thread = new Thread(new Runnable() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.setUncaughtExceptionHandler(handler);
|
||||||
|
thread.start();
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
connector.stopRunning();
|
||||||
|
running = false;
|
||||||
|
if (thread != null) {
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process() {
|
||||||
|
while (!running)
|
||||||
|
;
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
connector.connect();
|
||||||
|
connector.subscribe();
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
Message message = connector.getWithoutAck(1L, TimeUnit.SECONDS); // 获取message
|
||||||
|
if (message == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
long batchId = message.getId();
|
||||||
|
int size = message.getEntries().size();
|
||||||
|
if (batchId == -1 || size == 0) {
|
||||||
|
// try {
|
||||||
|
// Thread.sleep(1000);
|
||||||
|
// } catch (InterruptedException e) {
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
// printSummary(message, batchId, size);
|
||||||
|
// printEntry(message.getEntries());
|
||||||
|
logger.info(message.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
connector.ack(); // 提交确认
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
connector.unsubscribe();
|
||||||
|
} catch (WakeupException e) {
|
||||||
|
// No-op. Continue process
|
||||||
|
}
|
||||||
|
connector.disconnnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.alibaba.otter.canal.client.running.kafka;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.kafka.common.errors.WakeupException;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector;
|
||||||
|
import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kafka consumer获取Message的测试例子
|
||||||
|
*
|
||||||
|
* @author machengyuan @ 2018-6-12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class KafkaClientRunningTest extends AbstractKafkaTest {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(KafkaClientRunningTest.class);
|
||||||
|
|
||||||
|
private boolean running = true;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKafkaConsumer() {
|
||||||
|
final ExecutorService executor = Executors.newFixedThreadPool(1);
|
||||||
|
|
||||||
|
final KafkaCanalConnector connector = KafkaCanalConnectors.newKafkaConnector(servers, topic, partition, groupId);
|
||||||
|
|
||||||
|
executor.submit(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
connector.connect();
|
||||||
|
connector.subscribe();
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
Message message = connector.getWithoutAck(3L, TimeUnit.SECONDS);
|
||||||
|
if (message != null) {
|
||||||
|
System.out.println(message);
|
||||||
|
}
|
||||||
|
connector.ack();
|
||||||
|
} catch (WakeupException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connector.unsubscribe();
|
||||||
|
connector.disconnnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(60000);
|
||||||
|
running = false;
|
||||||
|
executor.shutdown();
|
||||||
|
logger.info("shutdown completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.26-SNAPSHOT</version>
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.common</artifactId>
|
<artifactId>canal.common</artifactId>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.alibaba.fastjson.TypeReference;
|
import com.alibaba.fastjson.TypeReference;
|
||||||
|
import com.alibaba.fastjson.parser.ParserConfig;
|
||||||
import com.alibaba.fastjson.serializer.JSONSerializer;
|
import com.alibaba.fastjson.serializer.JSONSerializer;
|
||||||
import com.alibaba.fastjson.serializer.ObjectSerializer;
|
import com.alibaba.fastjson.serializer.ObjectSerializer;
|
||||||
import com.alibaba.fastjson.serializer.PropertyFilter;
|
import com.alibaba.fastjson.serializer.PropertyFilter;
|
||||||
@@ -28,6 +29,7 @@ public class JsonUtils {
|
|||||||
SerializeConfig.getGlobalInstance().put(InetAddress.class, InetAddressSerializer.instance);
|
SerializeConfig.getGlobalInstance().put(InetAddress.class, InetAddressSerializer.instance);
|
||||||
SerializeConfig.getGlobalInstance().put(Inet4Address.class, InetAddressSerializer.instance);
|
SerializeConfig.getGlobalInstance().put(Inet4Address.class, InetAddressSerializer.instance);
|
||||||
SerializeConfig.getGlobalInstance().put(Inet6Address.class, InetAddressSerializer.instance);
|
SerializeConfig.getGlobalInstance().put(Inet6Address.class, InetAddressSerializer.instance);
|
||||||
|
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) {
|
public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) {
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ public class NamedThreadFactory implements ThreadFactory {
|
|||||||
|
|
||||||
public void uncaughtException(Thread t,
|
public void uncaughtException(Thread t,
|
||||||
Throwable e) {
|
Throwable e) {
|
||||||
|
if (e instanceof InterruptedException
|
||||||
|
|| (e.getCause() != null && e.getCause() instanceof InterruptedException)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
logger.error("from " + t.getName(), e);
|
logger.error("from " + t.getName(), e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -91,18 +91,25 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
processStart();
|
processStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public synchronized void start() {
|
||||||
super.start();
|
super.start();
|
||||||
processStart();
|
try {
|
||||||
if (zkClient != null) {
|
processStart();
|
||||||
// 如果需要尽可能释放instance资源,不需要监听running节点,不然即使stop了这台机器,另一台机器立马会start
|
if (zkClient != null) {
|
||||||
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
|
// 如果需要尽可能释放instance资源,不需要监听running节点,不然即使stop了这台机器,另一台机器立马会start
|
||||||
zkClient.subscribeDataChanges(path, dataListener);
|
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
|
||||||
|
zkClient.subscribeDataChanges(path, dataListener);
|
||||||
|
|
||||||
initRunning();
|
initRunning();
|
||||||
} else {
|
} else {
|
||||||
processActiveEnter();// 没有zk,直接启动
|
processActiveEnter();// 没有zk,直接启动
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("start failed", e);
|
||||||
|
// 没有正常启动,重置一下状态,避免干扰下一次start
|
||||||
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void release() {
|
public void release() {
|
||||||
@@ -113,7 +120,7 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public synchronized void stop() {
|
||||||
super.stop();
|
super.stop();
|
||||||
|
|
||||||
if (zkClient != null) {
|
if (zkClient != null) {
|
||||||
@@ -234,11 +241,7 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
|
|
||||||
private void processActiveEnter() {
|
private void processActiveEnter() {
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
try {
|
listener.processActiveEnter();
|
||||||
listener.processActiveEnter();
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("processActiveEnter failed", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.26-SNAPSHOT</version>
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -11,6 +11,11 @@
|
|||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<name>canal dbsync module for otter ${project.version}</name>
|
<name>canal dbsync module for otter ${project.version}</name>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.parse.driver</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<!-- log -->
|
<!-- log -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ public final class CharsetConversion {
|
|||||||
putEntry(42, "latin7", "latin7_general_cs", "ISO8859_7");
|
putEntry(42, "latin7", "latin7_general_cs", "ISO8859_7");
|
||||||
putEntry(43, "macce", "macce_bin", "MacCentralEurope");
|
putEntry(43, "macce", "macce_bin", "MacCentralEurope");
|
||||||
putEntry(44, "cp1250", "cp1250_croatian_ci", "Cp1250");
|
putEntry(44, "cp1250", "cp1250_croatian_ci", "Cp1250");
|
||||||
putEntry(45, "utf8mb4", "utf8mb4_general_ci", "MacCentralEurope");
|
putEntry(45, "utf8mb4", "utf8mb4_general_ci", "UTF-8");
|
||||||
putEntry(46, "utf8mb4", "utf8mb4_bin", "MacCentralEurope");
|
putEntry(46, "utf8mb4", "utf8mb4_bin", "UTF-8");
|
||||||
putEntry(47, "latin1", "latin1_bin", "ISO8859_1");
|
putEntry(47, "latin1", "latin1_bin", "ISO8859_1");
|
||||||
putEntry(48, "latin1", "latin1_general_ci", "ISO8859_1");
|
putEntry(48, "latin1", "latin1_general_ci", "ISO8859_1");
|
||||||
putEntry(49, "latin1", "latin1_general_cs", "ISO8859_1");
|
putEntry(49, "latin1", "latin1_general_cs", "ISO8859_1");
|
||||||
|
|||||||
@@ -75,7 +75,9 @@ public final class FileLogFetcher extends LogFetcher {
|
|||||||
fin = new FileInputStream(file);
|
fin = new FileInputStream(file);
|
||||||
|
|
||||||
ensureCapacity(BIN_LOG_HEADER_SIZE);
|
ensureCapacity(BIN_LOG_HEADER_SIZE);
|
||||||
if (BIN_LOG_HEADER_SIZE != fin.read(buffer, 0, BIN_LOG_HEADER_SIZE)) throw new IOException("No binlog file header");
|
if (BIN_LOG_HEADER_SIZE != fin.read(buffer, 0, BIN_LOG_HEADER_SIZE)) {
|
||||||
|
throw new IOException("No binlog file header");
|
||||||
|
}
|
||||||
|
|
||||||
if (buffer[0] != BINLOG_MAGIC[0] || buffer[1] != BINLOG_MAGIC[1] || buffer[2] != BINLOG_MAGIC[2]
|
if (buffer[0] != BINLOG_MAGIC[0] || buffer[1] != BINLOG_MAGIC[1] || buffer[2] != BINLOG_MAGIC[2]
|
||||||
|| buffer[3] != BINLOG_MAGIC[3]) {
|
|| buffer[3] != BINLOG_MAGIC[3]) {
|
||||||
@@ -128,6 +130,19 @@ public final class FileLogFetcher extends LogFetcher {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (limit > 0) {
|
} else if (limit > 0) {
|
||||||
|
if (limit >= FormatDescriptionLogEvent.LOG_EVENT_HEADER_LEN) {
|
||||||
|
int lenPosition = position + 4 + 1 + 4;
|
||||||
|
long eventLen = ((long) (0xff & buffer[lenPosition++])) | ((long) (0xff & buffer[lenPosition++]) << 8)
|
||||||
|
| ((long) (0xff & buffer[lenPosition++]) << 16)
|
||||||
|
| ((long) (0xff & buffer[lenPosition++]) << 24);
|
||||||
|
|
||||||
|
if (limit >= eventLen) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
ensureCapacity((int) eventLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
System.arraycopy(buffer, origin, buffer, 0, limit);
|
System.arraycopy(buffer, origin, buffer, 0, limit);
|
||||||
position -= origin;
|
position -= origin;
|
||||||
origin = 0;
|
origin = 0;
|
||||||
@@ -153,7 +168,9 @@ public final class FileLogFetcher extends LogFetcher {
|
|||||||
* @see com.taobao.tddl.dbsync.binlog.LogFetcher#close()
|
* @see com.taobao.tddl.dbsync.binlog.LogFetcher#close()
|
||||||
*/
|
*/
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
if (fin != null) fin.close();
|
if (fin != null) {
|
||||||
|
fin.close();
|
||||||
|
}
|
||||||
|
|
||||||
fin = null;
|
fin = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public class LogBuffer {
|
|||||||
|
|
||||||
protected int origin, limit;
|
protected int origin, limit;
|
||||||
protected int position;
|
protected int position;
|
||||||
|
protected int semival;
|
||||||
|
|
||||||
protected LogBuffer(){
|
protected LogBuffer(){
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.taobao.tddl.dbsync.binlog;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
||||||
|
|
||||||
@@ -20,6 +21,8 @@ public final class LogContext {
|
|||||||
|
|
||||||
private LogPosition logPosition;
|
private LogPosition logPosition;
|
||||||
|
|
||||||
|
private GTIDSet gtidSet;
|
||||||
|
|
||||||
public LogContext(){
|
public LogContext(){
|
||||||
this.formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
this.formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
||||||
}
|
}
|
||||||
@@ -58,7 +61,14 @@ public final class LogContext {
|
|||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
||||||
|
|
||||||
mapOfTable.clear();
|
mapOfTable.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GTIDSet getGtidSet() {
|
||||||
|
return gtidSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGtidSet(GTIDSet gtidSet) {
|
||||||
|
this.gtidSet = gtidSet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.util.BitSet;
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.AppendBlockLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.AppendBlockLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.BeginLoadQueryLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.BeginLoadQueryLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.CreateFileLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.CreateFileLogEvent;
|
||||||
@@ -30,19 +31,23 @@ import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent;
|
|||||||
import com.taobao.tddl.dbsync.binlog.event.StartLogEventV3;
|
import com.taobao.tddl.dbsync.binlog.event.StartLogEventV3;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.StopLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.StopLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.TransactionContextLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.UnknownLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.UnknownLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.UserVarLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.UserVarLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.ViewChangeEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.XaPrepareLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.XidLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.XidLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent;
|
import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.mariadb.BinlogCheckPointLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.mariadb.BinlogCheckPointLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.mariadb.MariaGtidListLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.mariadb.MariaGtidListLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.mariadb.MariaGtidLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.mariadb.MariaGtidLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.mariadb.StartEncryptionLogEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a binary-log decoder.
|
* Implements a binary-log decoder.
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* LogDecoder decoder = new LogDecoder();
|
* LogDecoder decoder = new LogDecoder();
|
||||||
* decoder.handle(...);
|
* decoder.handle(...);
|
||||||
@@ -57,7 +62,7 @@ import com.taobao.tddl.dbsync.binlog.event.mariadb.MariaGtidLogEvent;
|
|||||||
* while (event != null);
|
* while (event != null);
|
||||||
* // no more events in buffer.
|
* // no more events in buffer.
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:changyuan.lh@taobao.com">Changyuan.lh</a>
|
* @author <a href="mailto:changyuan.lh@taobao.com">Changyuan.lh</a>
|
||||||
* @version 1.0
|
* @version 1.0
|
||||||
*/
|
*/
|
||||||
@@ -84,7 +89,7 @@ public final class LogDecoder {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Decoding an event from binary-log buffer.
|
* Decoding an event from binary-log buffer.
|
||||||
*
|
*
|
||||||
* @return <code>UknownLogEvent</code> if event type is unknown or skipped,
|
* @return <code>UknownLogEvent</code> if event type is unknown or skipped,
|
||||||
* <code>null</code> if buffer is not including a full event.
|
* <code>null</code> if buffer is not including a full event.
|
||||||
*/
|
*/
|
||||||
@@ -104,9 +109,16 @@ public final class LogDecoder {
|
|||||||
try {
|
try {
|
||||||
/* Decoding binary-log to event */
|
/* Decoding binary-log to event */
|
||||||
event = decode(buffer, header, context);
|
event = decode(buffer, header, context);
|
||||||
|
if (event != null) {
|
||||||
|
// set logFileName
|
||||||
|
event.getHeader().setLogFileName(context.getLogPosition().getFileName());
|
||||||
|
event.setSemival(buffer.semival);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (logger.isWarnEnabled()) logger.warn("Decoding " + LogEvent.getTypeName(header.getType())
|
if (logger.isWarnEnabled()) {
|
||||||
+ " failed from: " + context.getLogPosition(), e);
|
logger.warn("Decoding " + LogEvent.getTypeName(header.getType()) + " failed from: "
|
||||||
|
+ context.getLogPosition(), e);
|
||||||
|
}
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
buffer.limit(limit); /* Restore limit */
|
buffer.limit(limit); /* Restore limit */
|
||||||
@@ -129,7 +141,7 @@ public final class LogDecoder {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Deserialize an event from buffer.
|
* Deserialize an event from buffer.
|
||||||
*
|
*
|
||||||
* @return <code>UknownLogEvent</code> if event type is unknown or skipped.
|
* @return <code>UknownLogEvent</code> if event type is unknown or skipped.
|
||||||
*/
|
*/
|
||||||
public static LogEvent decode(LogBuffer buffer, LogHeader header, LogContext context) throws IOException {
|
public static LogEvent decode(LogBuffer buffer, LogHeader header, LogContext context) throws IOException {
|
||||||
@@ -148,18 +160,20 @@ public final class LogDecoder {
|
|||||||
// remove checksum bytes
|
// remove checksum bytes
|
||||||
buffer.limit(header.getEventLen() - LogEvent.BINLOG_CHECKSUM_LEN);
|
buffer.limit(header.getEventLen() - LogEvent.BINLOG_CHECKSUM_LEN);
|
||||||
}
|
}
|
||||||
|
GTIDSet gtidSet = context.getGtidSet();
|
||||||
switch (header.getType()) {
|
switch (header.getType()) {
|
||||||
case LogEvent.QUERY_EVENT: {
|
case LogEvent.QUERY_EVENT: {
|
||||||
QueryLogEvent event = new QueryLogEvent(header, buffer, descriptionEvent);
|
QueryLogEvent event = new QueryLogEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.XID_EVENT: {
|
case LogEvent.XID_EVENT: {
|
||||||
XidLogEvent event = new XidLogEvent(header, buffer, descriptionEvent);
|
XidLogEvent event = new XidLogEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.TABLE_MAP_EVENT: {
|
case LogEvent.TABLE_MAP_EVENT: {
|
||||||
@@ -257,12 +271,14 @@ public final class LogDecoder {
|
|||||||
RandLogEvent event = new RandLogEvent(header, buffer, descriptionEvent);
|
RandLogEvent event = new RandLogEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.USER_VAR_EVENT: {
|
case LogEvent.USER_VAR_EVENT: {
|
||||||
UserVarLogEvent event = new UserVarLogEvent(header, buffer, descriptionEvent);
|
UserVarLogEvent event = new UserVarLogEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.FORMAT_DESCRIPTION_EVENT: {
|
case LogEvent.FORMAT_DESCRIPTION_EVENT: {
|
||||||
@@ -313,6 +329,7 @@ public final class LogDecoder {
|
|||||||
HeartbeatLogEvent event = new HeartbeatLogEvent(header, buffer, descriptionEvent);
|
HeartbeatLogEvent event = new HeartbeatLogEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.IGNORABLE_LOG_EVENT: {
|
case LogEvent.IGNORABLE_LOG_EVENT: {
|
||||||
@@ -325,6 +342,7 @@ public final class LogDecoder {
|
|||||||
RowsQueryLogEvent event = new RowsQueryLogEvent(header, buffer, descriptionEvent);
|
RowsQueryLogEvent event = new RowsQueryLogEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.WRITE_ROWS_EVENT: {
|
case LogEvent.WRITE_ROWS_EVENT: {
|
||||||
@@ -332,6 +350,7 @@ public final class LogDecoder {
|
|||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
event.fillTable(context);
|
event.fillTable(context);
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.UPDATE_ROWS_EVENT: {
|
case LogEvent.UPDATE_ROWS_EVENT: {
|
||||||
@@ -339,6 +358,7 @@ public final class LogDecoder {
|
|||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
event.fillTable(context);
|
event.fillTable(context);
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.DELETE_ROWS_EVENT: {
|
case LogEvent.DELETE_ROWS_EVENT: {
|
||||||
@@ -346,6 +366,7 @@ public final class LogDecoder {
|
|||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
event.fillTable(context);
|
event.fillTable(context);
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.GTID_LOG_EVENT:
|
case LogEvent.GTID_LOG_EVENT:
|
||||||
@@ -353,6 +374,11 @@ public final class LogDecoder {
|
|||||||
GtidLogEvent event = new GtidLogEvent(header, buffer, descriptionEvent);
|
GtidLogEvent event = new GtidLogEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
if (gtidSet != null) {
|
||||||
|
gtidSet.update(event.getGtidStr());
|
||||||
|
// update latest gtid
|
||||||
|
header.putGtidStr(gtidSet);
|
||||||
|
}
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.PREVIOUS_GTIDS_LOG_EVENT: {
|
case LogEvent.PREVIOUS_GTIDS_LOG_EVENT: {
|
||||||
@@ -361,10 +387,29 @@ public final class LogDecoder {
|
|||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
case LogEvent.TRANSACTION_CONTEXT_EVENT: {
|
||||||
|
TransactionContextLogEvent event = new TransactionContextLogEvent(header, buffer, descriptionEvent);
|
||||||
|
/* updating position in context */
|
||||||
|
logPosition.position = header.getLogPos();
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
case LogEvent.VIEW_CHANGE_EVENT: {
|
||||||
|
ViewChangeEvent event = new ViewChangeEvent(header, buffer, descriptionEvent);
|
||||||
|
/* updating position in context */
|
||||||
|
logPosition.position = header.getLogPos();
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
case LogEvent.XA_PREPARE_LOG_EVENT: {
|
||||||
|
XaPrepareLogEvent event = new XaPrepareLogEvent(header, buffer, descriptionEvent);
|
||||||
|
/* updating position in context */
|
||||||
|
logPosition.position = header.getLogPos();
|
||||||
|
return event;
|
||||||
|
}
|
||||||
case LogEvent.ANNOTATE_ROWS_EVENT: {
|
case LogEvent.ANNOTATE_ROWS_EVENT: {
|
||||||
AnnotateRowsEvent event = new AnnotateRowsEvent(header, buffer, descriptionEvent);
|
AnnotateRowsEvent event = new AnnotateRowsEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
|
header.putGtidStr(context.getGtidSet());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
case LogEvent.BINLOG_CHECKPOINT_EVENT: {
|
case LogEvent.BINLOG_CHECKPOINT_EVENT: {
|
||||||
@@ -385,6 +430,12 @@ public final class LogDecoder {
|
|||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
case LogEvent.START_ENCRYPTION_EVENT: {
|
||||||
|
StartEncryptionLogEvent event = new StartEncryptionLogEvent(header, buffer, descriptionEvent);
|
||||||
|
/* updating position in context */
|
||||||
|
logPosition.position = header.getLogPos();
|
||||||
|
return event;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
/*
|
/*
|
||||||
* Create an object of Ignorable_log_event for unrecognized
|
* Create an object of Ignorable_log_event for unrecognized
|
||||||
@@ -397,9 +448,10 @@ public final class LogDecoder {
|
|||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
return event;
|
return event;
|
||||||
} else {
|
} else {
|
||||||
if (logger.isWarnEnabled()) logger.warn("Skipping unrecognized binlog event "
|
if (logger.isWarnEnabled()) {
|
||||||
+ LogEvent.getTypeName(header.getType()) + " from: "
|
logger.warn("Skipping unrecognized binlog event " + LogEvent.getTypeName(header.getType())
|
||||||
+ context.getLogPosition());
|
+ " from: " + context.getLogPosition());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,9 +161,17 @@ public abstract class LogEvent {
|
|||||||
|
|
||||||
public static final int PREVIOUS_GTIDS_LOG_EVENT = 35;
|
public static final int PREVIOUS_GTIDS_LOG_EVENT = 35;
|
||||||
|
|
||||||
|
/* MySQL 5.7 events */
|
||||||
|
public static final int TRANSACTION_CONTEXT_EVENT = 36;
|
||||||
|
|
||||||
|
public static final int VIEW_CHANGE_EVENT = 37;
|
||||||
|
|
||||||
|
/* Prepared XA transaction terminal event similar to Xid */
|
||||||
|
public static final int XA_PREPARE_LOG_EVENT = 38;
|
||||||
|
|
||||||
// mariaDb 5.5.34
|
// mariaDb 5.5.34
|
||||||
/* New MySQL/Sun events are to be added right above this comment */
|
/* New MySQL/Sun events are to be added right above this comment */
|
||||||
public static final int MYSQL_EVENTS_END = 36;
|
public static final int MYSQL_EVENTS_END = 39;
|
||||||
|
|
||||||
public static final int MARIA_EVENTS_BEGIN = 160;
|
public static final int MARIA_EVENTS_BEGIN = 160;
|
||||||
/* New Maria event numbers start from here */
|
/* New Maria event numbers start from here */
|
||||||
@@ -189,8 +197,10 @@ public abstract class LogEvent {
|
|||||||
*/
|
*/
|
||||||
public static final int GTID_LIST_EVENT = 163;
|
public static final int GTID_LIST_EVENT = 163;
|
||||||
|
|
||||||
|
public static final int START_ENCRYPTION_EVENT = 164;
|
||||||
|
|
||||||
/** end marker */
|
/** end marker */
|
||||||
public static final int ENUM_END_EVENT = 164;
|
public static final int ENUM_END_EVENT = 165;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1 byte length, 1 byte format Length is total length in bytes, including 2
|
* 1 byte length, 1 byte format Length is total length in bytes, including 2
|
||||||
@@ -360,6 +370,24 @@ public abstract class LogEvent {
|
|||||||
|
|
||||||
protected final LogHeader header;
|
protected final LogHeader header;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mysql半同步semi标识
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 0不需要semi ack 给mysql
|
||||||
|
* 1需要semi ack给mysql
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
protected int semival;
|
||||||
|
|
||||||
|
public int getSemival() {
|
||||||
|
return semival;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSemival(int semival) {
|
||||||
|
this.semival = semival;
|
||||||
|
}
|
||||||
|
|
||||||
protected LogEvent(LogHeader header){
|
protected LogEvent(LogHeader header){
|
||||||
this.header = header;
|
this.header = header;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,10 +59,15 @@ public final class FormatDescriptionLogEvent extends StartLogEventV3 {
|
|||||||
public static final int HEARTBEAT_HEADER_LEN = 0;
|
public static final int HEARTBEAT_HEADER_LEN = 0;
|
||||||
public static final int IGNORABLE_HEADER_LEN = 0;
|
public static final int IGNORABLE_HEADER_LEN = 0;
|
||||||
public static final int ROWS_HEADER_LEN_V2 = 10;
|
public static final int ROWS_HEADER_LEN_V2 = 10;
|
||||||
|
public static final int TRANSACTION_CONTEXT_HEADER_LEN = 18;
|
||||||
|
public static final int VIEW_CHANGE_HEADER_LEN = 52;
|
||||||
|
public static final int XA_PREPARE_HEADER_LEN = 0;
|
||||||
|
|
||||||
public static final int ANNOTATE_ROWS_HEADER_LEN = 0;
|
public static final int ANNOTATE_ROWS_HEADER_LEN = 0;
|
||||||
public static final int BINLOG_CHECKPOINT_HEADER_LEN = 4;
|
public static final int BINLOG_CHECKPOINT_HEADER_LEN = 4;
|
||||||
public static final int GTID_HEADER_LEN = 19;
|
public static final int GTID_HEADER_LEN = 19;
|
||||||
public static final int GTID_LIST_HEADER_LEN = 4;
|
public static final int GTID_LIST_HEADER_LEN = 4;
|
||||||
|
public static final int START_ENCRYPTION_HEADER_LEN = 0;
|
||||||
|
|
||||||
public static final int POST_HEADER_LENGTH = 11;
|
public static final int POST_HEADER_LENGTH = 11;
|
||||||
|
|
||||||
@@ -202,11 +207,17 @@ public final class FormatDescriptionLogEvent extends StartLogEventV3 {
|
|||||||
postHeaderLen[GTID_LOG_EVENT - 1] = POST_HEADER_LENGTH;
|
postHeaderLen[GTID_LOG_EVENT - 1] = POST_HEADER_LENGTH;
|
||||||
postHeaderLen[ANONYMOUS_GTID_LOG_EVENT - 1] = POST_HEADER_LENGTH;
|
postHeaderLen[ANONYMOUS_GTID_LOG_EVENT - 1] = POST_HEADER_LENGTH;
|
||||||
postHeaderLen[PREVIOUS_GTIDS_LOG_EVENT - 1] = IGNORABLE_HEADER_LEN;
|
postHeaderLen[PREVIOUS_GTIDS_LOG_EVENT - 1] = IGNORABLE_HEADER_LEN;
|
||||||
|
|
||||||
|
postHeaderLen[TRANSACTION_CONTEXT_EVENT - 1] = TRANSACTION_CONTEXT_HEADER_LEN;
|
||||||
|
postHeaderLen[VIEW_CHANGE_EVENT - 1] = VIEW_CHANGE_HEADER_LEN;
|
||||||
|
postHeaderLen[XA_PREPARE_LOG_EVENT - 1] = XA_PREPARE_HEADER_LEN;
|
||||||
|
|
||||||
// mariadb 10
|
// mariadb 10
|
||||||
postHeaderLen[ANNOTATE_ROWS_EVENT - 1] = ANNOTATE_ROWS_HEADER_LEN;
|
postHeaderLen[ANNOTATE_ROWS_EVENT - 1] = ANNOTATE_ROWS_HEADER_LEN;
|
||||||
postHeaderLen[BINLOG_CHECKPOINT_EVENT - 1] = BINLOG_CHECKPOINT_HEADER_LEN;
|
postHeaderLen[BINLOG_CHECKPOINT_EVENT - 1] = BINLOG_CHECKPOINT_HEADER_LEN;
|
||||||
postHeaderLen[GTID_EVENT - 1] = GTID_HEADER_LEN;
|
postHeaderLen[GTID_EVENT - 1] = GTID_HEADER_LEN;
|
||||||
postHeaderLen[GTID_LIST_EVENT - 1] = GTID_LIST_HEADER_LEN;
|
postHeaderLen[GTID_LIST_EVENT - 1] = GTID_LIST_HEADER_LEN;
|
||||||
|
postHeaderLen[START_ENCRYPTION_EVENT - 1] = START_ENCRYPTION_HEADER_LEN;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3: /* 4.0.x x>=2 */
|
case 3: /* 4.0.x x>=2 */
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.taobao.tddl.dbsync.binlog.event;
|
package com.taobao.tddl.dbsync.binlog.event;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
|
||||||
@@ -11,11 +14,16 @@ import com.taobao.tddl.dbsync.binlog.LogEvent;
|
|||||||
public class GtidLogEvent extends LogEvent {
|
public class GtidLogEvent extends LogEvent {
|
||||||
|
|
||||||
// / Length of the commit_flag in event encoding
|
// / Length of the commit_flag in event encoding
|
||||||
public static final int ENCODED_FLAG_LENGTH = 1;
|
public static final int ENCODED_FLAG_LENGTH = 1;
|
||||||
// / Length of SID in event encoding
|
// / Length of SID in event encoding
|
||||||
public static final int ENCODED_SID_LENGTH = 16;
|
public static final int ENCODED_SID_LENGTH = 16;
|
||||||
|
public static final int LOGICAL_TIMESTAMP_TYPE_CODE = 2;
|
||||||
|
|
||||||
private boolean commitFlag;
|
private boolean commitFlag;
|
||||||
|
private UUID sid;
|
||||||
|
private long gno;
|
||||||
|
private Long lastCommitted;
|
||||||
|
private Long sequenceNumber;
|
||||||
|
|
||||||
public GtidLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
public GtidLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
||||||
super(header);
|
super(header);
|
||||||
@@ -27,6 +35,21 @@ public class GtidLogEvent extends LogEvent {
|
|||||||
buffer.position(commonHeaderLen);
|
buffer.position(commonHeaderLen);
|
||||||
commitFlag = (buffer.getUint8() != 0); // ENCODED_FLAG_LENGTH
|
commitFlag = (buffer.getUint8() != 0); // ENCODED_FLAG_LENGTH
|
||||||
|
|
||||||
|
byte[] bs = buffer.getData(ENCODED_SID_LENGTH);
|
||||||
|
ByteBuffer bb = ByteBuffer.wrap(bs);
|
||||||
|
long high = bb.getLong();
|
||||||
|
long low = bb.getLong();
|
||||||
|
sid = new UUID(high, low);
|
||||||
|
|
||||||
|
gno = buffer.getLong64();
|
||||||
|
|
||||||
|
// support gtid lastCommitted and sequenceNumber
|
||||||
|
// fix bug #776
|
||||||
|
if (buffer.hasRemaining() && buffer.remaining() > 16 && buffer.getUint8() == LOGICAL_TIMESTAMP_TYPE_CODE) {
|
||||||
|
lastCommitted = buffer.getLong64();
|
||||||
|
sequenceNumber = buffer.getLong64();
|
||||||
|
}
|
||||||
|
|
||||||
// ignore gtid info read
|
// ignore gtid info read
|
||||||
// sid.copy_from((uchar *)ptr_buffer);
|
// sid.copy_from((uchar *)ptr_buffer);
|
||||||
// ptr_buffer+= ENCODED_SID_LENGTH;
|
// ptr_buffer+= ENCODED_SID_LENGTH;
|
||||||
@@ -42,4 +65,26 @@ public class GtidLogEvent extends LogEvent {
|
|||||||
return commitFlag;
|
return commitFlag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UUID getSid() {
|
||||||
|
return sid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getGno() {
|
||||||
|
return gno;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getLastCommitted() {
|
||||||
|
return lastCommitted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getSequenceNumber() {
|
||||||
|
return sequenceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGtidStr() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(sid.toString()).append(":");
|
||||||
|
sb.append(gno);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.taobao.tddl.dbsync.binlog.event;
|
package com.taobao.tddl.dbsync.binlog.event;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
|
||||||
@@ -115,6 +116,13 @@ public final class LogHeader {
|
|||||||
*/
|
*/
|
||||||
protected long crc; // ha_checksum
|
protected long crc; // ha_checksum
|
||||||
|
|
||||||
|
/**
|
||||||
|
* binlog fileName
|
||||||
|
*/
|
||||||
|
protected String logFileName;
|
||||||
|
|
||||||
|
protected String gtidStr;
|
||||||
|
|
||||||
/* for Start_event_v3 */
|
/* for Start_event_v3 */
|
||||||
public LogHeader(final int type){
|
public LogHeader(final int type){
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@@ -270,9 +278,27 @@ public final class LogHeader {
|
|||||||
return checksumAlg;
|
return checksumAlg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getLogFileName() {
|
||||||
|
return logFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogFileName(String logFileName) {
|
||||||
|
this.logFileName = logFileName;
|
||||||
|
}
|
||||||
|
|
||||||
private void processCheckSum(LogBuffer buffer) {
|
private void processCheckSum(LogBuffer buffer) {
|
||||||
if (checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_OFF && checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_UNDEF) {
|
if (checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_OFF && checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_UNDEF) {
|
||||||
crc = buffer.getUint32(eventLen - LogEvent.BINLOG_CHECKSUM_LEN);
|
crc = buffer.getUint32(eventLen - LogEvent.BINLOG_CHECKSUM_LEN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getGtidStr() {
|
||||||
|
return gtidStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putGtidStr(GTIDSet gtidSet) {
|
||||||
|
if (gtidSet != null) {
|
||||||
|
this.gtidStr = gtidSet.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ public final class RowsLogBuffer {
|
|||||||
public static final long DATETIMEF_INT_OFS = 0x8000000000L;
|
public static final long DATETIMEF_INT_OFS = 0x8000000000L;
|
||||||
public static final long TIMEF_INT_OFS = 0x800000L;
|
public static final long TIMEF_INT_OFS = 0x800000L;
|
||||||
public static final long TIMEF_OFS = 0x800000000000L;
|
public static final long TIMEF_OFS = 0x800000000000L;
|
||||||
|
private static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
||||||
|
|
||||||
private final LogBuffer buffer;
|
private final LogBuffer buffer;
|
||||||
private final int columnLen;
|
private final int columnLen;
|
||||||
@@ -480,13 +481,27 @@ public final class RowsLogBuffer {
|
|||||||
// cal.set(d / 10000, (d % 10000) / 100 - 1, d % 100, t /
|
// cal.set(d / 10000, (d % 10000) / 100 - 1, d % 100, t /
|
||||||
// 10000, (t % 10000) / 100, t % 100);
|
// 10000, (t % 10000) / 100, t % 100);
|
||||||
// value = new Timestamp(cal.getTimeInMillis());
|
// value = new Timestamp(cal.getTimeInMillis());
|
||||||
value = String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
// value = String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
||||||
d / 10000,
|
// d / 10000,
|
||||||
(d % 10000) / 100,
|
// (d % 10000) / 100,
|
||||||
d % 100,
|
// d % 100,
|
||||||
t / 10000,
|
// t / 10000,
|
||||||
(t % 10000) / 100,
|
// (t % 10000) / 100,
|
||||||
t % 100);
|
// t % 100);
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
appendNumber4(builder, d / 10000);
|
||||||
|
builder.append('-');
|
||||||
|
appendNumber2(builder, (d % 10000) / 100);
|
||||||
|
builder.append('-');
|
||||||
|
appendNumber2(builder, d % 100);
|
||||||
|
builder.append(' ');
|
||||||
|
appendNumber2(builder, t / 10000);
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, (t % 10000) / 100);
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, t % 100);
|
||||||
|
value = builder.toString();
|
||||||
}
|
}
|
||||||
javaType = Types.TIMESTAMP;
|
javaType = Types.TIMESTAMP;
|
||||||
length = 8;
|
length = 8;
|
||||||
@@ -540,13 +555,27 @@ public final class RowsLogBuffer {
|
|||||||
// % (1 << 5)), (int) (hms >> 12),
|
// % (1 << 5)), (int) (hms >> 12),
|
||||||
// (int) ((hms >> 6) % (1 << 6)), (int) (hms % (1 << 6)));
|
// (int) ((hms >> 6) % (1 << 6)), (int) (hms % (1 << 6)));
|
||||||
// value = new Timestamp(cal.getTimeInMillis());
|
// value = new Timestamp(cal.getTimeInMillis());
|
||||||
second = String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
// second = String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
||||||
(int) (ym / 13),
|
// (int) (ym / 13),
|
||||||
(int) (ym % 13),
|
// (int) (ym % 13),
|
||||||
(int) (ymd % (1 << 5)),
|
// (int) (ymd % (1 << 5)),
|
||||||
(int) (hms >> 12),
|
// (int) (hms >> 12),
|
||||||
(int) ((hms >> 6) % (1 << 6)),
|
// (int) ((hms >> 6) % (1 << 6)),
|
||||||
(int) (hms % (1 << 6)));
|
// (int) (hms % (1 << 6)));
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder(26);
|
||||||
|
appendNumber4(builder, (int) (ym / 13));
|
||||||
|
builder.append('-');
|
||||||
|
appendNumber2(builder, (int) (ym % 13));
|
||||||
|
builder.append('-');
|
||||||
|
appendNumber2(builder, (int) (ymd % (1 << 5)));
|
||||||
|
builder.append(' ');
|
||||||
|
appendNumber2(builder, (int) (hms >> 12));
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, (int) ((hms >> 6) % (1 << 6)));
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, (int) (hms % (1 << 6)));
|
||||||
|
second = builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta >= 1) {
|
if (meta >= 1) {
|
||||||
@@ -575,11 +604,28 @@ public final class RowsLogBuffer {
|
|||||||
// cal.set(70, 0, 1, i32 / 10000, (i32 % 10000) / 100, i32 %
|
// cal.set(70, 0, 1, i32 / 10000, (i32 % 10000) / 100, i32 %
|
||||||
// 100);
|
// 100);
|
||||||
// value = new Time(cal.getTimeInMillis());
|
// value = new Time(cal.getTimeInMillis());
|
||||||
value = String.format("%s%02d:%02d:%02d",
|
// value = String.format("%s%02d:%02d:%02d",
|
||||||
(i32 >= 0) ? "" : "-",
|
// (i32 >= 0) ? "" : "-",
|
||||||
u32 / 10000,
|
// u32 / 10000,
|
||||||
(u32 % 10000) / 100,
|
// (u32 % 10000) / 100,
|
||||||
u32 % 100);
|
// u32 % 100);
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder(12);
|
||||||
|
if (i32 < 0) {
|
||||||
|
builder.append('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
int d = u32 / 10000;
|
||||||
|
if (d > 100) {
|
||||||
|
builder.append(String.valueOf(d));
|
||||||
|
} else {
|
||||||
|
appendNumber2(builder, d);
|
||||||
|
}
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, (u32 % 10000) / 100);
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, u32 % 100);
|
||||||
|
value = builder.toString();
|
||||||
}
|
}
|
||||||
javaType = Types.TIME;
|
javaType = Types.TIME;
|
||||||
length = 3;
|
length = 3;
|
||||||
@@ -673,11 +719,28 @@ public final class RowsLogBuffer {
|
|||||||
// value = new Time(cal.getTimeInMillis());
|
// value = new Time(cal.getTimeInMillis());
|
||||||
long ultime = Math.abs(ltime);
|
long ultime = Math.abs(ltime);
|
||||||
intpart = ultime >> 24;
|
intpart = ultime >> 24;
|
||||||
second = String.format("%s%02d:%02d:%02d",
|
// second = String.format("%s%02d:%02d:%02d",
|
||||||
ltime >= 0 ? "" : "-",
|
// ltime >= 0 ? "" : "-",
|
||||||
(int) ((intpart >> 12) % (1 << 10)),
|
// (int) ((intpart >> 12) % (1 << 10)),
|
||||||
(int) ((intpart >> 6) % (1 << 6)),
|
// (int) ((intpart >> 6) % (1 << 6)),
|
||||||
(int) (intpart % (1 << 6)));
|
// (int) (intpart % (1 << 6)));
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder(12);
|
||||||
|
if (ltime < 0) {
|
||||||
|
builder.append('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
int d = (int) ((intpart >> 12) % (1 << 10));
|
||||||
|
if (d > 100) {
|
||||||
|
builder.append(String.valueOf(d));
|
||||||
|
} else {
|
||||||
|
appendNumber2(builder, d);
|
||||||
|
}
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, (int) ((intpart >> 6) % (1 << 6)));
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, (int) (intpart % (1 << 6)));
|
||||||
|
second = builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta >= 1) {
|
if (meta >= 1) {
|
||||||
@@ -717,7 +780,16 @@ public final class RowsLogBuffer {
|
|||||||
// cal.set((i32 / (16 * 32)), (i32 / 32 % 16) - 1, (i32 %
|
// cal.set((i32 / (16 * 32)), (i32 / 32 % 16) - 1, (i32 %
|
||||||
// 32));
|
// 32));
|
||||||
// value = new java.sql.Date(cal.getTimeInMillis());
|
// value = new java.sql.Date(cal.getTimeInMillis());
|
||||||
value = String.format("%04d-%02d-%02d", i32 / (16 * 32), i32 / 32 % 16, i32 % 32);
|
// value = String.format("%04d-%02d-%02d", i32 / (16 * 32),
|
||||||
|
// i32 / 32 % 16, i32 % 32);
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder(12);
|
||||||
|
appendNumber4(builder, i32 / (16 * 32));
|
||||||
|
builder.append('-');
|
||||||
|
appendNumber2(builder, i32 / 32 % 16);
|
||||||
|
builder.append('-');
|
||||||
|
appendNumber2(builder, i32 % 32);
|
||||||
|
value = builder.toString();
|
||||||
}
|
}
|
||||||
javaType = Types.DATE;
|
javaType = Types.DATE;
|
||||||
length = 3;
|
length = 3;
|
||||||
@@ -969,17 +1041,18 @@ public final class RowsLogBuffer {
|
|||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("!! Unknown JSON packlen = " + meta);
|
throw new IllegalArgumentException("!! Unknown JSON packlen = " + meta);
|
||||||
}
|
}
|
||||||
// len = buffer.getUint16();
|
if (0 == len) {
|
||||||
// buffer.forward(meta - 4);
|
// fixed issue #1 by lava, json column of zero length has no
|
||||||
int position = buffer.position();
|
// value, value parsing should be skipped
|
||||||
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(), buffer, len - 1, charsetName);
|
value = "";
|
||||||
StringBuilder builder = new StringBuilder();
|
} else {
|
||||||
jsonValue.toJsonString(builder, charsetName);
|
int position = buffer.position();
|
||||||
value = builder.toString();
|
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(), buffer, len - 1, charsetName);
|
||||||
buffer.position(position + len);
|
StringBuilder builder = new StringBuilder();
|
||||||
// byte[] binary = new byte[len];
|
jsonValue.toJsonString(builder, charsetName);
|
||||||
// buffer.fillBytes(binary, 0, len);
|
value = builder.toString();
|
||||||
// value = binary;
|
buffer.position(position + len);
|
||||||
|
}
|
||||||
javaType = Types.VARCHAR;
|
javaType = Types.VARCHAR;
|
||||||
length = len;
|
length = len;
|
||||||
break;
|
break;
|
||||||
@@ -1066,4 +1139,32 @@ public final class RowsLogBuffer {
|
|||||||
return sec.substring(0, meta);
|
return sec.substring(0, meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void appendNumber4(StringBuilder builder, int d) {
|
||||||
|
if (d >= 1000) {
|
||||||
|
builder.append(digits[d / 1000])
|
||||||
|
.append(digits[(d / 100) % 10])
|
||||||
|
.append(digits[(d / 10) % 10])
|
||||||
|
.append(digits[d % 10]);
|
||||||
|
} else {
|
||||||
|
builder.append('0');
|
||||||
|
appendNumber3(builder, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendNumber3(StringBuilder builder, int d) {
|
||||||
|
if (d >= 100) {
|
||||||
|
builder.append(digits[d / 100]).append(digits[(d / 10) % 10]).append(digits[d % 10]);
|
||||||
|
} else {
|
||||||
|
builder.append('0');
|
||||||
|
appendNumber2(builder, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendNumber2(StringBuilder builder, int d) {
|
||||||
|
if (d >= 10) {
|
||||||
|
builder.append(digits[(d / 10) % 10]).append(digits[d % 10]);
|
||||||
|
} else {
|
||||||
|
builder.append('0').append(digits[d]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ public abstract class RowsLogEvent extends LogEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final RowsLogBuffer getRowsBuf(String charsetName) {
|
public final RowsLogBuffer getRowsBuf(String charsetName) {
|
||||||
return new RowsLogBuffer(rowsBuf.duplicate(), columnLen, charsetName);
|
return new RowsLogBuffer(rowsBuf, columnLen, charsetName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final int getFlags(final int flags) {
|
public final int getFlags(final int flags) {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.taobao.tddl.dbsync.binlog.event;
|
||||||
|
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author agapple 2018年5月7日 下午7:05:39
|
||||||
|
* @version 1.0.26
|
||||||
|
* @since mysql 5.7
|
||||||
|
*/
|
||||||
|
public class TransactionContextLogEvent extends LogEvent {
|
||||||
|
|
||||||
|
public TransactionContextLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
||||||
|
super(header);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.taobao.tddl.dbsync.binlog.event;
|
||||||
|
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author agapple 2018年5月7日 下午7:05:39
|
||||||
|
* @version 1.0.26
|
||||||
|
* @since mysql 5.7
|
||||||
|
*/
|
||||||
|
public class ViewChangeEvent extends LogEvent {
|
||||||
|
|
||||||
|
public ViewChangeEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
||||||
|
super(header);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.taobao.tddl.dbsync.binlog.event;
|
||||||
|
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author agapple 2018年5月7日 下午7:05:39
|
||||||
|
* @version 1.0.26
|
||||||
|
* @since mysql 5.7
|
||||||
|
*/
|
||||||
|
public class XaPrepareLogEvent extends LogEvent {
|
||||||
|
|
||||||
|
private boolean onePhase;
|
||||||
|
private int formatId;
|
||||||
|
private int gtridLength;
|
||||||
|
private int bqualLength;
|
||||||
|
private byte[] data;
|
||||||
|
|
||||||
|
public XaPrepareLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
||||||
|
super(header);
|
||||||
|
|
||||||
|
final int commonHeaderLen = descriptionEvent.getCommonHeaderLen();
|
||||||
|
final int postHeaderLen = descriptionEvent.getPostHeaderLen()[header.getType() - 1];
|
||||||
|
|
||||||
|
int offset = commonHeaderLen + postHeaderLen;
|
||||||
|
buffer.position(offset);
|
||||||
|
|
||||||
|
onePhase = (buffer.getInt8() == 0x00 ? false : true);
|
||||||
|
|
||||||
|
formatId = buffer.getInt32();
|
||||||
|
gtridLength = buffer.getInt32();
|
||||||
|
bqualLength = buffer.getInt32();
|
||||||
|
|
||||||
|
int MY_XIDDATASIZE = 128;
|
||||||
|
if (MY_XIDDATASIZE >= gtridLength + bqualLength && gtridLength >= 0 && gtridLength <= 64 && bqualLength >= 0
|
||||||
|
&& bqualLength <= 64) {
|
||||||
|
data = buffer.getData(gtridLength + bqualLength);
|
||||||
|
} else {
|
||||||
|
formatId = -1;
|
||||||
|
gtridLength = 0;
|
||||||
|
bqualLength = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOnePhase() {
|
||||||
|
return onePhase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFormatId() {
|
||||||
|
return formatId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getGtridLength() {
|
||||||
|
return gtridLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBqualLength() {
|
||||||
|
return bqualLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,9 +13,30 @@ import com.taobao.tddl.dbsync.binlog.event.LogHeader;
|
|||||||
*/
|
*/
|
||||||
public class MariaGtidLogEvent extends IgnorableLogEvent {
|
public class MariaGtidLogEvent extends IgnorableLogEvent {
|
||||||
|
|
||||||
|
private long gtid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* mariadb gtidlog event format
|
||||||
|
* uint<8> GTID sequence
|
||||||
|
* uint<4> Replication Domain ID
|
||||||
|
* uint<1> Flags
|
||||||
|
*
|
||||||
|
* if flag & FL_GROUP_COMMIT_ID
|
||||||
|
* uint<8> commit_id
|
||||||
|
* else
|
||||||
|
* uint<6> 0
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
|
||||||
public MariaGtidLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
public MariaGtidLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
||||||
super(header, buffer, descriptionEvent);
|
super(header, buffer, descriptionEvent);
|
||||||
|
gtid = buffer.getUlong64().longValue();
|
||||||
// do nothing , just ignore log event
|
// do nothing , just ignore log event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getGtid() {
|
||||||
|
return gtid;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.taobao.tddl.dbsync.binlog.event.mariadb;
|
||||||
|
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.LogHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mariadb的Start_encryption_log_event
|
||||||
|
*
|
||||||
|
* @author agapple 2018年5月7日 下午7:23:02
|
||||||
|
* @version 1.0.26
|
||||||
|
*/
|
||||||
|
public class StartEncryptionLogEvent extends LogEvent {
|
||||||
|
|
||||||
|
public StartEncryptionLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
||||||
|
super(header);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.taobao.tddl.dbsync;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import com.taobao.tddl.dbsync.binlog.DirectLogFetcher;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
|
||||||
|
public class FetcherPerformanceTest {
|
||||||
|
|
||||||
|
public static void main(String args[]) {
|
||||||
|
DirectLogFetcher fetcher = new DirectLogFetcher();
|
||||||
|
try {
|
||||||
|
Class.forName("com.mysql.jdbc.Driver");
|
||||||
|
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306", "root", "hello");
|
||||||
|
Statement statement = connection.createStatement();
|
||||||
|
statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'");
|
||||||
|
statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
|
||||||
|
|
||||||
|
fetcher.open(connection, "mysql-bin.000006", 120L, 2);
|
||||||
|
|
||||||
|
AtomicLong sum = new AtomicLong(0);
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
long last = 0;
|
||||||
|
long end = 0;
|
||||||
|
|
||||||
|
while (fetcher.fetch()) {
|
||||||
|
sum.incrementAndGet();
|
||||||
|
long current = sum.get();
|
||||||
|
if (current - last >= 100000) {
|
||||||
|
end = System.currentTimeMillis();
|
||||||
|
long tps = ((current - last) * 1000) / (end - start);
|
||||||
|
System.out.println(" total : " + sum + " , cost : " + (end - start) + " , tps : " + tps);
|
||||||
|
last = current;
|
||||||
|
start = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
fetcher.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,15 +8,6 @@ import java.sql.Statement;
|
|||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.XidLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent;
|
|
||||||
|
|
||||||
public class DirectLogFetcherTest extends BaseLogFetcherTest {
|
public class DirectLogFetcherTest extends BaseLogFetcherTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -24,56 +15,54 @@ public class DirectLogFetcherTest extends BaseLogFetcherTest {
|
|||||||
DirectLogFetcher fecther = new DirectLogFetcher();
|
DirectLogFetcher fecther = new DirectLogFetcher();
|
||||||
try {
|
try {
|
||||||
Class.forName("com.mysql.jdbc.Driver");
|
Class.forName("com.mysql.jdbc.Driver");
|
||||||
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306", "root", "hello");
|
Connection connection = DriverManager.getConnection("jdbc:mysql://100.81.154.142:3306", "root", "hello");
|
||||||
Statement statement = connection.createStatement();
|
Statement statement = connection.createStatement();
|
||||||
statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'");
|
statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'");
|
||||||
statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
|
statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
|
||||||
|
|
||||||
fecther.open(connection, "mysql-bin.000001", 4L, 2);
|
fecther.open(connection, "mysql-bin.000006", 120L, 2);
|
||||||
|
|
||||||
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.UNKNOWN_EVENT);
|
||||||
LogContext context = new LogContext();
|
LogContext context = new LogContext();
|
||||||
while (fecther.fetch()) {
|
while (fecther.fetch()) {
|
||||||
LogEvent event = null;
|
decoder.decode(fecther, context);
|
||||||
event = decoder.decode(fecther, context);
|
continue;
|
||||||
|
// if (event == null) {
|
||||||
if (event == null) {
|
// continue;
|
||||||
continue;
|
// }
|
||||||
// throw new RuntimeException("parse failed");
|
//
|
||||||
}
|
// int eventType = event.getHeader().getType();
|
||||||
|
// switch (eventType) {
|
||||||
int eventType = event.getHeader().getType();
|
// case LogEvent.ROTATE_EVENT:
|
||||||
switch (eventType) {
|
// binlogFileName = ((RotateLogEvent) event).getFilename();
|
||||||
case LogEvent.ROTATE_EVENT:
|
// break;
|
||||||
binlogFileName = ((RotateLogEvent) event).getFilename();
|
// case LogEvent.WRITE_ROWS_EVENT_V1:
|
||||||
break;
|
// case LogEvent.WRITE_ROWS_EVENT:
|
||||||
case LogEvent.WRITE_ROWS_EVENT_V1:
|
// parseRowsEvent((WriteRowsLogEvent) event);
|
||||||
case LogEvent.WRITE_ROWS_EVENT:
|
// break;
|
||||||
parseRowsEvent((WriteRowsLogEvent) event);
|
// case LogEvent.UPDATE_ROWS_EVENT_V1:
|
||||||
break;
|
// case LogEvent.UPDATE_ROWS_EVENT:
|
||||||
case LogEvent.UPDATE_ROWS_EVENT_V1:
|
// parseRowsEvent((UpdateRowsLogEvent) event);
|
||||||
case LogEvent.UPDATE_ROWS_EVENT:
|
// break;
|
||||||
parseRowsEvent((UpdateRowsLogEvent) event);
|
// case LogEvent.DELETE_ROWS_EVENT_V1:
|
||||||
break;
|
// case LogEvent.DELETE_ROWS_EVENT:
|
||||||
case LogEvent.DELETE_ROWS_EVENT_V1:
|
// parseRowsEvent((DeleteRowsLogEvent) event);
|
||||||
case LogEvent.DELETE_ROWS_EVENT:
|
// break;
|
||||||
parseRowsEvent((DeleteRowsLogEvent) event);
|
// case LogEvent.QUERY_EVENT:
|
||||||
break;
|
// parseQueryEvent((QueryLogEvent) event);
|
||||||
case LogEvent.QUERY_EVENT:
|
// break;
|
||||||
parseQueryEvent((QueryLogEvent) event);
|
// case LogEvent.ROWS_QUERY_LOG_EVENT:
|
||||||
break;
|
// parseRowsQueryEvent((RowsQueryLogEvent) event);
|
||||||
case LogEvent.ROWS_QUERY_LOG_EVENT:
|
// break;
|
||||||
parseRowsQueryEvent((RowsQueryLogEvent) event);
|
// case LogEvent.ANNOTATE_ROWS_EVENT:
|
||||||
break;
|
// parseAnnotateRowsEvent((AnnotateRowsEvent) event);
|
||||||
case LogEvent.ANNOTATE_ROWS_EVENT:
|
// break;
|
||||||
parseAnnotateRowsEvent((AnnotateRowsEvent) event);
|
// case LogEvent.XID_EVENT:
|
||||||
break;
|
// parseXidEvent((XidLogEvent) event);
|
||||||
case LogEvent.XID_EVENT:
|
// break;
|
||||||
parseXidEvent((XidLogEvent) event);
|
// default:
|
||||||
break;
|
// break;
|
||||||
default:
|
// }
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.26-SNAPSHOT</version>
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -16,6 +16,14 @@
|
|||||||
<artifactId>canal.server</artifactId>
|
<artifactId>canal.server</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 这里指定runtime的metrics provider-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.prometheus</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -32,6 +40,7 @@
|
|||||||
<exclude>**/canal.properties</exclude>
|
<exclude>**/canal.properties</exclude>
|
||||||
<exclude>**/spring/**</exclude>
|
<exclude>**/spring/**</exclude>
|
||||||
<exclude>**/example/**</exclude>
|
<exclude>**/example/**</exclude>
|
||||||
|
<exclude>**/kafka.yml</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|||||||
5
deployer/src/main/bin/restart.sh
Normal file
5
deployer/src/main/bin/restart.sh
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
sh stop.sh
|
||||||
|
|
||||||
|
sh startup.sh
|
||||||
@@ -15,12 +15,15 @@ public class CanalConstants {
|
|||||||
public static final String CANAL_ID = ROOT + "." + "id";
|
public static final String CANAL_ID = ROOT + "." + "id";
|
||||||
public static final String CANAL_IP = ROOT + "." + "ip";
|
public static final String CANAL_IP = ROOT + "." + "ip";
|
||||||
public static final String CANAL_PORT = ROOT + "." + "port";
|
public static final String CANAL_PORT = ROOT + "." + "port";
|
||||||
|
public static final String CANAL_METRICS_PULL_PORT = ROOT + "." + "metrics.pull.port";
|
||||||
public static final String CANAL_ZKSERVERS = ROOT + "." + "zkServers";
|
public static final String CANAL_ZKSERVERS = ROOT + "." + "zkServers";
|
||||||
|
public static final String CANAL_WITHOUT_NETTY = ROOT + "." + "withoutNetty";
|
||||||
|
|
||||||
public static final String CANAL_DESTINATIONS = ROOT + "." + "destinations";
|
public static final String CANAL_DESTINATIONS = ROOT + "." + "destinations";
|
||||||
public static final String CANAL_AUTO_SCAN = ROOT + "." + "auto.scan";
|
public static final String CANAL_AUTO_SCAN = ROOT + "." + "auto.scan";
|
||||||
public static final String CANAL_AUTO_SCAN_INTERVAL = ROOT + "." + "auto.scan.interval";
|
public static final String CANAL_AUTO_SCAN_INTERVAL = ROOT + "." + "auto.scan.interval";
|
||||||
public static final String CANAL_CONF_DIR = ROOT + "." + "conf.dir";
|
public static final String CANAL_CONF_DIR = ROOT + "." + "conf.dir";
|
||||||
|
public static final String CANAL_SERVER_MODE = ROOT + "." + "serverMode";
|
||||||
|
|
||||||
public static final String CANAL_DESTINATION_SPLIT = ",";
|
public static final String CANAL_DESTINATION_SPLIT = ",";
|
||||||
public static final String GLOBAL_NAME = "global";
|
public static final String GLOBAL_NAME = "global";
|
||||||
@@ -32,6 +35,8 @@ public class CanalConstants {
|
|||||||
|
|
||||||
public static final String CANAL_DESTINATION_PROPERTY = ROOT + ".instance.destination";
|
public static final String CANAL_DESTINATION_PROPERTY = ROOT + ".instance.destination";
|
||||||
|
|
||||||
|
public static final String CANAL_SOCKETCHANNEL = ROOT + "." + "socketChannel";
|
||||||
|
|
||||||
public static String getInstanceModeKey(String destination) {
|
public static String getInstanceModeKey(String destination) {
|
||||||
return MessageFormat.format(INSTANCE_MODE_TEMPLATE, destination);
|
return MessageFormat.format(INSTANCE_MODE_TEMPLATE, destination);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import com.alibaba.otter.canal.instance.core.CanalInstanceGenerator;
|
|||||||
import com.alibaba.otter.canal.instance.manager.CanalConfigClient;
|
import com.alibaba.otter.canal.instance.manager.CanalConfigClient;
|
||||||
import com.alibaba.otter.canal.instance.manager.ManagerCanalInstanceGenerator;
|
import com.alibaba.otter.canal.instance.manager.ManagerCanalInstanceGenerator;
|
||||||
import com.alibaba.otter.canal.instance.spring.SpringCanalInstanceGenerator;
|
import com.alibaba.otter.canal.instance.spring.SpringCanalInstanceGenerator;
|
||||||
|
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||||
import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded;
|
import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded;
|
||||||
import com.alibaba.otter.canal.server.exception.CanalServerException;
|
import com.alibaba.otter.canal.server.exception.CanalServerException;
|
||||||
import com.alibaba.otter.canal.server.netty.CanalServerWithNetty;
|
import com.alibaba.otter.canal.server.netty.CanalServerWithNetty;
|
||||||
@@ -43,7 +44,7 @@ import com.google.common.collect.MigrateMap;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* canal调度控制器
|
* canal调度控制器
|
||||||
*
|
*
|
||||||
* @author jianghang 2012-11-8 下午12:03:11
|
* @author jianghang 2012-11-8 下午12:03:11
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
@@ -85,15 +86,32 @@ public class CanalController {
|
|||||||
// 初始化instance config
|
// 初始化instance config
|
||||||
initInstanceConfig(properties);
|
initInstanceConfig(properties);
|
||||||
|
|
||||||
|
// init socketChannel
|
||||||
|
String socketChannel = getProperty(properties, CanalConstants.CANAL_SOCKETCHANNEL);
|
||||||
|
if (StringUtils.isNotEmpty(socketChannel)) {
|
||||||
|
System.setProperty(CanalConstants.CANAL_SOCKETCHANNEL, socketChannel);
|
||||||
|
}
|
||||||
|
|
||||||
// 准备canal server
|
// 准备canal server
|
||||||
cid = Long.valueOf(getProperty(properties, CanalConstants.CANAL_ID));
|
cid = Long.valueOf(getProperty(properties, CanalConstants.CANAL_ID));
|
||||||
ip = getProperty(properties, CanalConstants.CANAL_IP);
|
ip = getProperty(properties, CanalConstants.CANAL_IP);
|
||||||
port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT));
|
port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT));
|
||||||
embededCanalServer = CanalServerWithEmbedded.instance();
|
embededCanalServer = CanalServerWithEmbedded.instance();
|
||||||
embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator
|
embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator
|
||||||
canalServer = CanalServerWithNetty.instance();
|
try {
|
||||||
canalServer.setIp(ip);
|
int metricsPort = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_METRICS_PULL_PORT));
|
||||||
canalServer.setPort(port);
|
embededCanalServer.setMetricsPort(metricsPort);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logger.info("No valid metrics server port found, use default 11112.");
|
||||||
|
embededCanalServer.setMetricsPort(11112);
|
||||||
|
}
|
||||||
|
|
||||||
|
String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY);
|
||||||
|
if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) {
|
||||||
|
canalServer = CanalServerWithNetty.instance();
|
||||||
|
canalServer.setIp(ip);
|
||||||
|
canalServer.setPort(port);
|
||||||
|
}
|
||||||
|
|
||||||
// 处理下ip为空,默认使用hostIp暴露到zk中
|
// 处理下ip为空,默认使用hostIp暴露到zk中
|
||||||
if (StringUtils.isEmpty(ip)) {
|
if (StringUtils.isEmpty(ip)) {
|
||||||
@@ -294,7 +312,7 @@ public class CanalController {
|
|||||||
return instanceGenerator.generate(destination);
|
return instanceGenerator.generate(destination);
|
||||||
} else if (config.getMode().isSpring()) {
|
} else if (config.getMode().isSpring()) {
|
||||||
SpringCanalInstanceGenerator instanceGenerator = new SpringCanalInstanceGenerator();
|
SpringCanalInstanceGenerator instanceGenerator = new SpringCanalInstanceGenerator();
|
||||||
synchronized (this) {
|
synchronized (CanalEventParser.class) {
|
||||||
try {
|
try {
|
||||||
// 设置当前正在加载的通道,加载spring查找文件时会用到该变量
|
// 设置当前正在加载的通道,加载spring查找文件时会用到该变量
|
||||||
System.setProperty(CanalConstants.CANAL_DESTINATION_PROPERTY, destination);
|
System.setProperty(CanalConstants.CANAL_DESTINATION_PROPERTY, destination);
|
||||||
@@ -369,8 +387,19 @@ public class CanalController {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getProperty(Properties properties, String key) {
|
public String getProperty(Properties properties, String key) {
|
||||||
return StringUtils.trim(properties.getProperty(StringUtils.trim(key)));
|
key = StringUtils.trim(key);
|
||||||
|
String value = System.getProperty(key);
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
value = System.getenv(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
value = properties.getProperty(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringUtils.trim(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() throws Throwable {
|
public void start() throws Throwable {
|
||||||
@@ -425,11 +454,15 @@ public class CanalController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 启动网络接口
|
// 启动网络接口
|
||||||
canalServer.start();
|
if (canalServer != null) {
|
||||||
|
canalServer.start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() throws Throwable {
|
public void stop() throws Throwable {
|
||||||
canalServer.stop();
|
if (canalServer != null) {
|
||||||
|
canalServer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
if (autoScan) {
|
if (autoScan) {
|
||||||
for (InstanceConfigMonitor monitor : instanceConfigMonitors.values()) {
|
for (InstanceConfigMonitor monitor : instanceConfigMonitors.values()) {
|
||||||
@@ -448,6 +481,10 @@ public class CanalController {
|
|||||||
// 释放canal的工作节点
|
// 释放canal的工作节点
|
||||||
releaseCid(ZookeeperPathUtils.getCanalClusterNode(ip + ":" + port));
|
releaseCid(ZookeeperPathUtils.getCanalClusterNode(ip + ":" + port));
|
||||||
logger.info("## stop the canal server[{}:{}]", ip, port);
|
logger.info("## stop the canal server[{}:{}]", ip, port);
|
||||||
|
|
||||||
|
if (zkclientx != null) {
|
||||||
|
zkclientx.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initCid(String path) {
|
private void initCid(String path) {
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import org.apache.commons.lang.StringUtils;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.kafka.CanalKafkaStarter;
|
||||||
|
import com.alibaba.otter.canal.server.CanalServerStarter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* canal独立版本启动的入口类
|
* canal独立版本启动的入口类
|
||||||
*
|
*
|
||||||
@@ -51,6 +54,18 @@ public class CanalLauncher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CanalServerStarter canalServerStarter = null;
|
||||||
|
String serverMode = controller.getProperty(properties, CanalConstants.CANAL_SERVER_MODE);
|
||||||
|
if (serverMode.equalsIgnoreCase("kafka")) {
|
||||||
|
canalServerStarter = new CanalKafkaStarter();
|
||||||
|
} else if (serverMode.equalsIgnoreCase("rocketMQ")) {
|
||||||
|
// 预留rocketMQ启动
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canalServerStarter != null) {
|
||||||
|
canalServerStarter.init();
|
||||||
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.error("## Something goes wrong when starting up the canal Server:", e);
|
logger.error("## Something goes wrong when starting up the canal Server:", e);
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
@@ -59,6 +74,7 @@ public class CanalLauncher {
|
|||||||
|
|
||||||
private static void setGlobalUncaughtExceptionHandler() {
|
private static void setGlobalUncaughtExceptionHandler() {
|
||||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void uncaughtException(Thread t, Throwable e) {
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
logger.error("UnCaughtException", e);
|
logger.error("UnCaughtException", e);
|
||||||
|
|||||||
@@ -3,10 +3,14 @@
|
|||||||
#################################################
|
#################################################
|
||||||
canal.id= 1
|
canal.id= 1
|
||||||
canal.ip=
|
canal.ip=
|
||||||
canal.port= 11111
|
canal.port=11111
|
||||||
|
canal.metrics.pull.port=11112
|
||||||
canal.zkServers=
|
canal.zkServers=
|
||||||
# flush data to zk
|
# flush data to zk
|
||||||
canal.zookeeper.flush.period = 1000
|
canal.zookeeper.flush.period = 1000
|
||||||
|
canal.withoutNetty = false
|
||||||
|
# tcp, kafka, rocketMQ
|
||||||
|
canal.serverMode = tcp
|
||||||
# flush meta cursor/parse position to file
|
# flush meta cursor/parse position to file
|
||||||
canal.file.data.dir = ${canal.conf.dir}
|
canal.file.data.dir = ${canal.conf.dir}
|
||||||
canal.file.flush.period = 1000
|
canal.file.flush.period = 1000
|
||||||
@@ -42,6 +46,7 @@ canal.instance.filter.query.dml = false
|
|||||||
canal.instance.filter.query.ddl = false
|
canal.instance.filter.query.ddl = false
|
||||||
canal.instance.filter.table.error = false
|
canal.instance.filter.table.error = false
|
||||||
canal.instance.filter.rows = false
|
canal.instance.filter.rows = false
|
||||||
|
canal.instance.filter.transaction.entry = false
|
||||||
|
|
||||||
# binlog format/image check
|
# binlog format/image check
|
||||||
canal.instance.binlog.format = ROW,STATEMENT,MIXED
|
canal.instance.binlog.format = ROW,STATEMENT,MIXED
|
||||||
@@ -50,6 +55,24 @@ canal.instance.binlog.image = FULL,MINIMAL,NOBLOB
|
|||||||
# binlog ddl isolation
|
# binlog ddl isolation
|
||||||
canal.instance.get.ddl.isolation = false
|
canal.instance.get.ddl.isolation = false
|
||||||
|
|
||||||
|
# parallel parser config
|
||||||
|
canal.instance.parser.parallel = true
|
||||||
|
## concurrent thread number, default 60% available processors, suggest not to exceed Runtime.getRuntime().availableProcessors()
|
||||||
|
#canal.instance.parser.parallelThreadSize = 16
|
||||||
|
## disruptor ringbuffer size, must be power of 2
|
||||||
|
canal.instance.parser.parallelBufferSize = 256
|
||||||
|
|
||||||
|
# table meta tsdb info
|
||||||
|
canal.instance.tsdb.enable=true
|
||||||
|
canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:}
|
||||||
|
canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
|
||||||
|
canal.instance.tsdb.dbUsername=canal
|
||||||
|
canal.instance.tsdb.dbPassword=canal
|
||||||
|
|
||||||
|
# rds oss binlog account
|
||||||
|
canal.instance.rds.accesskey =
|
||||||
|
canal.instance.rds.secretkey =
|
||||||
|
|
||||||
#################################################
|
#################################################
|
||||||
######### destinations #############
|
######### destinations #############
|
||||||
#################################################
|
#################################################
|
||||||
@@ -66,7 +89,6 @@ canal.instance.tsdb.spring.xml=classpath:spring/tsdb/h2-tsdb.xml
|
|||||||
canal.instance.global.mode = spring
|
canal.instance.global.mode = spring
|
||||||
canal.instance.global.lazy = false
|
canal.instance.global.lazy = false
|
||||||
#canal.instance.global.manager.address = 127.0.0.1:1099
|
#canal.instance.global.manager.address = 127.0.0.1:1099
|
||||||
#canal.instance.global.spring.xml = classpath:spring/local-instance.xml
|
|
||||||
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
|
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
|
||||||
canal.instance.global.spring.xml = classpath:spring/file-instance.xml
|
canal.instance.global.spring.xml = classpath:spring/file-instance.xml
|
||||||
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
|
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
|
||||||
|
|||||||
@@ -1,31 +1,39 @@
|
|||||||
#################################################
|
#################################################
|
||||||
## mysql serverId
|
## mysql serverId , v1.0.26+ will autoGen
|
||||||
canal.instance.mysql.slaveId=0
|
# canal.instance.mysql.slaveId=0
|
||||||
|
|
||||||
|
# enable gtid use true/false
|
||||||
|
canal.instance.gtidon=false
|
||||||
|
|
||||||
# position info
|
# position info
|
||||||
canal.instance.master.address=127.0.0.1:3306
|
canal.instance.master.address=127.0.0.1:3306
|
||||||
canal.instance.master.journal.name=
|
canal.instance.master.journal.name=
|
||||||
canal.instance.master.position=
|
canal.instance.master.position=
|
||||||
canal.instance.master.timestamp=
|
canal.instance.master.timestamp=
|
||||||
|
canal.instance.master.gtid=
|
||||||
|
|
||||||
|
# rds oss binlog
|
||||||
|
canal.instance.rds.accesskey=
|
||||||
|
canal.instance.rds.secretkey=
|
||||||
|
canal.instance.rds.instanceId=
|
||||||
|
|
||||||
# table meta tsdb info
|
# table meta tsdb info
|
||||||
canal.instance.tsdb.enable=true
|
canal.instance.tsdb.enable=true
|
||||||
canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:}
|
|
||||||
canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
|
|
||||||
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
|
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
|
||||||
canal.instance.tsdb.dbUsername=canal
|
#canal.instance.tsdb.dbUsername=canal
|
||||||
canal.instance.tsdb.dbPassword=canal
|
#canal.instance.tsdb.dbPassword=canal
|
||||||
|
|
||||||
|
|
||||||
#canal.instance.standby.address =
|
#canal.instance.standby.address =
|
||||||
#canal.instance.standby.journal.name =
|
#canal.instance.standby.journal.name =
|
||||||
#canal.instance.standby.position =
|
#canal.instance.standby.position =
|
||||||
#canal.instance.standby.timestamp =
|
#canal.instance.standby.timestamp =
|
||||||
|
#canal.instance.standby.gtid=
|
||||||
|
|
||||||
# username/password
|
# username/password
|
||||||
canal.instance.dbUsername=canal
|
canal.instance.dbUsername=canal
|
||||||
canal.instance.dbPassword=canal
|
canal.instance.dbPassword=canal
|
||||||
canal.instance.defaultDatabaseName=test
|
|
||||||
canal.instance.connectionCharset=UTF-8
|
canal.instance.connectionCharset=UTF-8
|
||||||
|
|
||||||
# table regex
|
# table regex
|
||||||
canal.instance.filter.regex=.*\\..*
|
canal.instance.filter.regex=.*\\..*
|
||||||
# table black regex
|
# table black regex
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
#################################################
|
|
||||||
# rds openapi binlog
|
|
||||||
canal.instance.rds.open.url=https://rds.aliyuncs.com/
|
|
||||||
canal.instance.rds.open.accesskey=
|
|
||||||
canal.instance.rds.open.secretkey=
|
|
||||||
canal.instance.rds.instanceId=
|
|
||||||
canal.instance.rds.startTime=
|
|
||||||
canal.instance.rds.endTime=
|
|
||||||
|
|
||||||
# local binlog dir
|
|
||||||
canal.instance.parser.directory=${canal.file.data.dir:../conf}/${canal.instance.destination:}/binlog
|
|
||||||
# position info
|
|
||||||
canal.instance.master.address=127.0.0.1:3306
|
|
||||||
|
|
||||||
# table meta tsdb info
|
|
||||||
canal.instance.tsdb.enable=true
|
|
||||||
canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:}
|
|
||||||
canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
|
|
||||||
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
|
|
||||||
#canal.instance.tsdb.dbUsername=canal
|
|
||||||
#canal.instance.tsdb.dbPassword=canal
|
|
||||||
|
|
||||||
# username/password
|
|
||||||
canal.instance.dbUsername=canal
|
|
||||||
canal.instance.dbPassword=canal
|
|
||||||
canal.instance.defaultDatabaseName=test
|
|
||||||
canal.instance.connectionCharset=UTF-8
|
|
||||||
# table regex
|
|
||||||
canal.instance.filter.regex=test\\..*
|
|
||||||
# table black regex
|
|
||||||
canal.instance.filter.black.regex=
|
|
||||||
#################################################
|
|
||||||
15
deployer/src/main/resources/kafka.yml
Normal file
15
deployer/src/main/resources/kafka.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
servers: slave1:6667,slave2:6667,slave3:6667
|
||||||
|
retries: 0
|
||||||
|
batchSize: 16384
|
||||||
|
lingerMs: 1
|
||||||
|
bufferMemory: 33554432
|
||||||
|
|
||||||
|
# Canal的batch size, 默认50K, 由于kafka最大消息体限制请勿超过1M(900K以下)
|
||||||
|
canalBatchSize: 50
|
||||||
|
# Canal get数据的超时时间, 单位: 毫秒, 空为不限超时
|
||||||
|
canalGetTimeout: 100
|
||||||
|
|
||||||
|
canalDestinations:
|
||||||
|
- canalDestination: example
|
||||||
|
topic: example
|
||||||
|
partition:
|
||||||
@@ -70,12 +70,16 @@
|
|||||||
<appender-ref ref="CANAL-ROOT" />
|
<appender-ref ref="CANAL-ROOT" />
|
||||||
</logger>
|
</logger>
|
||||||
<logger name="com.alibaba.otter.canal.meta.FileMixedMetaManager" additivity="false">
|
<logger name="com.alibaba.otter.canal.meta.FileMixedMetaManager" additivity="false">
|
||||||
<level value="INFO" />
|
<level value="INFO" />
|
||||||
<appender-ref ref="CANAL-META" />
|
<appender-ref ref="CANAL-META" />
|
||||||
</logger>
|
</logger>
|
||||||
|
<logger name="com.alibaba.otter.canal.kafka" additivity="false">
|
||||||
|
<level value="INFO" />
|
||||||
|
<appender-ref ref="CANAL-ROOT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
<root level="WARN">
|
<root level="WARN">
|
||||||
<appender-ref ref="STDOUT"/>
|
<!-- <appender-ref ref="STDOUT"/> -->
|
||||||
<appender-ref ref="CANAL-ROOT" />
|
<appender-ref ref="CANAL-ROOT" />
|
||||||
</root>
|
</root>
|
||||||
</configuration>
|
</configuration>
|
||||||
1304
deployer/src/main/resources/metrics/Canal_instances_tmpl.json
Normal file
1304
deployer/src/main/resources/metrics/Canal_instances_tmpl.json
Normal file
File diff suppressed because it is too large
Load Diff
39
deployer/src/main/resources/spring/base-instance.xml
Normal file
39
deployer/src/main/resources/spring/base-instance.xml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
|
||||||
|
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
|
||||||
|
xmlns:context="http://www.springframework.org/schema/context"
|
||||||
|
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||||
|
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
|
||||||
|
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
|
||||||
|
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
|
||||||
|
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
||||||
|
default-autowire="byName">
|
||||||
|
|
||||||
|
<!-- properties -->
|
||||||
|
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
||||||
|
<property name="ignoreResourceNotFound" value="true" />
|
||||||
|
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
||||||
|
<property name="locationNames">
|
||||||
|
<list>
|
||||||
|
<value>classpath:canal.properties</value>
|
||||||
|
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
||||||
|
</list>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
|
||||||
|
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
||||||
|
<property name="propertyEditorRegistrars">
|
||||||
|
<list>
|
||||||
|
<ref bean="socketAddressEditor" />
|
||||||
|
</list>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="baseEventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogEventParserProxy" abstract="true">
|
||||||
|
<property name="accesskey" value="${canal.instance.rds.accesskey:}" />
|
||||||
|
<property name="secretkey" value="${canal.instance.rds.secretkey:}" />
|
||||||
|
<property name="instanceId" value="${canal.instance.rds.instanceId:}" />
|
||||||
|
</bean>
|
||||||
|
</beans>
|
||||||
@@ -10,26 +10,7 @@
|
|||||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
||||||
default-autowire="byName">
|
default-autowire="byName">
|
||||||
|
|
||||||
<!-- properties -->
|
<import resource="classpath:spring/base-instance.xml" />
|
||||||
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
|
||||||
<property name="ignoreResourceNotFound" value="true" />
|
|
||||||
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
|
||||||
<property name="locationNames">
|
|
||||||
<list>
|
|
||||||
<value>classpath:canal.properties</value>
|
|
||||||
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
|
||||||
</list>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
|
|
||||||
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
|
||||||
<property name="propertyEditorRegistrars">
|
|
||||||
<list>
|
|
||||||
<ref bean="socketAddressEditor" />
|
|
||||||
</list>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<property name="destination" value="${canal.instance.destination}" />
|
||||||
@@ -81,9 +62,10 @@
|
|||||||
|
|
||||||
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
||||||
<property name="eventStore" ref="eventStore" />
|
<property name="eventStore" ref="eventStore" />
|
||||||
|
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser" parent="baseEventParser" >
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<property name="destination" value="${canal.instance.destination}" />
|
||||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||||
<!-- 心跳配置 -->
|
<!-- 心跳配置 -->
|
||||||
@@ -148,7 +130,7 @@
|
|||||||
<property name="address" value="${canal.instance.master.address}" />
|
<property name="address" value="${canal.instance.master.address}" />
|
||||||
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
||||||
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
||||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
|
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="standbyInfo">
|
<property name="standbyInfo">
|
||||||
@@ -156,7 +138,7 @@
|
|||||||
<property name="address" value="${canal.instance.standby.address}" />
|
<property name="address" value="${canal.instance.standby.address}" />
|
||||||
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
||||||
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
||||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
|
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
@@ -166,6 +148,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.master.position}" />
|
<property name="position" value="${canal.instance.master.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.master.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="standbyPosition">
|
<property name="standbyPosition">
|
||||||
@@ -173,6 +156,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.standby.journal.name}" />
|
<property name="journalName" value="${canal.instance.standby.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.standby.position}" />
|
<property name="position" value="${canal.instance.standby.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
|
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.standby.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
@@ -187,5 +171,13 @@
|
|||||||
<!--表结构相关-->
|
<!--表结构相关-->
|
||||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||||
|
|
||||||
|
<!--是否启用GTID模式-->
|
||||||
|
<property name="isGTIDMode" value="${canal.instance.gtidon:false}"/>
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@@ -10,26 +10,7 @@
|
|||||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
||||||
default-autowire="byName">
|
default-autowire="byName">
|
||||||
|
|
||||||
<!-- properties -->
|
<import resource="classpath:spring/base-instance.xml" />
|
||||||
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
|
||||||
<property name="ignoreResourceNotFound" value="true" />
|
|
||||||
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
|
||||||
<property name="locationNames">
|
|
||||||
<list>
|
|
||||||
<value>classpath:canal.properties</value>
|
|
||||||
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
|
||||||
</list>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
|
|
||||||
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
|
||||||
<property name="propertyEditorRegistrars">
|
|
||||||
<list>
|
|
||||||
<ref bean="socketAddressEditor" />
|
|
||||||
</list>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<property name="destination" value="${canal.instance.destination}" />
|
||||||
@@ -67,9 +48,10 @@
|
|||||||
|
|
||||||
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
||||||
<property name="eventStore" ref="eventStore" />
|
<property name="eventStore" ref="eventStore" />
|
||||||
|
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser" parent="baseEventParser">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<property name="destination" value="${canal.instance.destination}" />
|
||||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||||
<!-- 心跳配置 -->
|
<!-- 心跳配置 -->
|
||||||
@@ -133,7 +115,7 @@
|
|||||||
<property name="address" value="${canal.instance.master.address}" />
|
<property name="address" value="${canal.instance.master.address}" />
|
||||||
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
||||||
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
||||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
|
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="standbyInfo">
|
<property name="standbyInfo">
|
||||||
@@ -141,7 +123,7 @@
|
|||||||
<property name="address" value="${canal.instance.standby.address}" />
|
<property name="address" value="${canal.instance.standby.address}" />
|
||||||
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
||||||
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
||||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
|
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
@@ -151,6 +133,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.master.position}" />
|
<property name="position" value="${canal.instance.master.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.master.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="standbyPosition">
|
<property name="standbyPosition">
|
||||||
@@ -158,6 +141,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.standby.journal.name}" />
|
<property name="journalName" value="${canal.instance.standby.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.standby.position}" />
|
<property name="position" value="${canal.instance.standby.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
|
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.standby.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
@@ -172,5 +156,13 @@
|
|||||||
<!--表结构相关-->
|
<!--表结构相关-->
|
||||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||||
|
|
||||||
|
<!--是否启用GTID模式-->
|
||||||
|
<property name="isGTIDMode" value="${canal.instance.gtidon:false}"/>
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@@ -10,26 +10,7 @@
|
|||||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
||||||
default-autowire="byName">
|
default-autowire="byName">
|
||||||
|
|
||||||
<!-- properties -->
|
<import resource="classpath:spring/base-instance.xml" />
|
||||||
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
|
||||||
<property name="ignoreResourceNotFound" value="true" />
|
|
||||||
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
|
||||||
<property name="locationNames">
|
|
||||||
<list>
|
|
||||||
<value>classpath:canal.properties</value>
|
|
||||||
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
|
||||||
</list>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
|
|
||||||
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
|
||||||
<property name="propertyEditorRegistrars">
|
|
||||||
<list>
|
|
||||||
<ref bean="socketAddressEditor" />
|
|
||||||
</list>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<property name="destination" value="${canal.instance.destination}" />
|
||||||
@@ -64,6 +45,7 @@
|
|||||||
|
|
||||||
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
||||||
<property name="eventStore" ref="eventStore" />
|
<property name="eventStore" ref="eventStore" />
|
||||||
|
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.group.GroupEventParser">
|
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.group.GroupEventParser">
|
||||||
@@ -75,7 +57,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="eventParser1" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser1" parent="baseEventParser">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<property name="destination" value="${canal.instance.destination}" />
|
||||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||||
<!-- 心跳配置 -->
|
<!-- 心跳配置 -->
|
||||||
@@ -130,7 +112,7 @@
|
|||||||
<property name="address" value="${canal.instance.master1.address}" />
|
<property name="address" value="${canal.instance.master1.address}" />
|
||||||
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
||||||
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
||||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
|
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="standbyInfo">
|
<property name="standbyInfo">
|
||||||
@@ -138,7 +120,7 @@
|
|||||||
<property name="address" value="${canal.instance.standby1.address}" />
|
<property name="address" value="${canal.instance.standby1.address}" />
|
||||||
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
||||||
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
||||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
|
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
@@ -148,6 +130,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.master1.journal.name}" />
|
<property name="journalName" value="${canal.instance.master1.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.master1.position}" />
|
<property name="position" value="${canal.instance.master1.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.master1.timestamp}" />
|
<property name="timestamp" value="${canal.instance.master1.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.master1.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="standbyPosition">
|
<property name="standbyPosition">
|
||||||
@@ -155,6 +138,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.standby1.journal.name}" />
|
<property name="journalName" value="${canal.instance.standby1.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.standby1.position}" />
|
<property name="position" value="${canal.instance.standby1.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.standby1.timestamp}" />
|
<property name="timestamp" value="${canal.instance.standby1.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.standby1.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
@@ -164,9 +148,14 @@
|
|||||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser2" parent="baseEventParser">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<property name="destination" value="${canal.instance.destination}" />
|
||||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||||
<!-- 心跳配置 -->
|
<!-- 心跳配置 -->
|
||||||
@@ -221,7 +210,7 @@
|
|||||||
<property name="address" value="${canal.instance.master2.address}" />
|
<property name="address" value="${canal.instance.master2.address}" />
|
||||||
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
||||||
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
||||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
|
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="standbyInfo">
|
<property name="standbyInfo">
|
||||||
@@ -229,7 +218,7 @@
|
|||||||
<property name="address" value="${canal.instance.standby2.address}" />
|
<property name="address" value="${canal.instance.standby2.address}" />
|
||||||
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
||||||
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
||||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
|
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
@@ -239,6 +228,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.master2.journal.name}" />
|
<property name="journalName" value="${canal.instance.master2.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.master2.position}" />
|
<property name="position" value="${canal.instance.master2.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.master2.timestamp}" />
|
<property name="timestamp" value="${canal.instance.master2.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.master2.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="standbyPosition">
|
<property name="standbyPosition">
|
||||||
@@ -246,6 +236,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.standby2.journal.name}" />
|
<property name="journalName" value="${canal.instance.standby2.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.standby2.position}" />
|
<property name="position" value="${canal.instance.standby2.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.standby2.timestamp}" />
|
<property name="timestamp" value="${canal.instance.standby2.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.standby2.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
@@ -256,5 +247,10 @@
|
|||||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
|
|
||||||
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
|
|
||||||
xmlns:context="http://www.springframework.org/schema/context"
|
|
||||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
|
||||||
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
|
|
||||||
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
|
|
||||||
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
|
|
||||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
|
||||||
default-autowire="byName">
|
|
||||||
|
|
||||||
<!-- properties -->
|
|
||||||
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
|
||||||
<property name="ignoreResourceNotFound" value="true" />
|
|
||||||
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
|
||||||
<property name="locationNames">
|
|
||||||
<list>
|
|
||||||
<value>classpath:canal.properties</value>
|
|
||||||
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
|
||||||
</list>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
|
|
||||||
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
|
||||||
<property name="propertyEditorRegistrars">
|
|
||||||
<list>
|
|
||||||
<ref bean="socketAddressEditor" />
|
|
||||||
</list>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
|
||||||
<property name="eventParser">
|
|
||||||
<ref local="eventParser" />
|
|
||||||
</property>
|
|
||||||
<property name="eventSink">
|
|
||||||
<ref local="eventSink" />
|
|
||||||
</property>
|
|
||||||
<property name="eventStore">
|
|
||||||
<ref local="eventStore" />
|
|
||||||
</property>
|
|
||||||
<property name="metaManager">
|
|
||||||
<ref local="metaManager" />
|
|
||||||
</property>
|
|
||||||
<property name="alarmHandler">
|
|
||||||
<ref local="alarmHandler" />
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<!-- 报警处理类 -->
|
|
||||||
<bean id="alarmHandler" class="com.alibaba.otter.canal.common.alarm.LogAlarmHandler" />
|
|
||||||
|
|
||||||
<bean id="metaManager" class="com.alibaba.otter.canal.meta.FileMixedMetaManager">
|
|
||||||
<property name="dataDir" value="${canal.file.data.dir:../conf}" />
|
|
||||||
<property name="period" value="${canal.file.flush.period:1000}" />
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="eventStore" class="com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer">
|
|
||||||
<property name="bufferSize" value="${canal.instance.memory.buffer.size:16384}" />
|
|
||||||
<property name="bufferMemUnit" value="${canal.instance.memory.buffer.memunit:1024}" />
|
|
||||||
<property name="batchMode" value="${canal.instance.memory.batch.mode:MEMSIZE}" />
|
|
||||||
<property name="ddlIsolation" value="${canal.instance.get.ddl.isolation:false}" />
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
|
||||||
<property name="eventStore" ref="eventStore" />
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsLocalBinlogEventParser">
|
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
|
||||||
<property name="alarmHandler" ref="alarmHandler" />
|
|
||||||
|
|
||||||
<!-- 解析过滤处理 -->
|
|
||||||
<property name="eventFilter">
|
|
||||||
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
|
|
||||||
<constructor-arg index="0" value="${canal.instance.filter.regex:.*\..*}" />
|
|
||||||
</bean>
|
|
||||||
</property>
|
|
||||||
|
|
||||||
<property name="eventBlackFilter">
|
|
||||||
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
|
|
||||||
<constructor-arg index="0" value="${canal.instance.filter.black.regex:}" />
|
|
||||||
<constructor-arg index="1" value="false" />
|
|
||||||
</bean>
|
|
||||||
</property>
|
|
||||||
|
|
||||||
<!-- 最大事务解析大小,超过该大小后事务将被切分为多个事务投递 -->
|
|
||||||
<property name="transactionSize" value="${canal.instance.transaction.size:1024}" />
|
|
||||||
|
|
||||||
<!-- 解析编码 -->
|
|
||||||
<property name="connectionCharset" value="${canal.instance.connectionCharset:UTF-8}" />
|
|
||||||
|
|
||||||
<!-- 解析位点记录 -->
|
|
||||||
<property name="logPositionManager">
|
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
|
||||||
<constructor-arg>
|
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
|
||||||
</constructor-arg>
|
|
||||||
<constructor-arg>
|
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
|
||||||
<constructor-arg ref="metaManager"/>
|
|
||||||
</bean>
|
|
||||||
</constructor-arg>
|
|
||||||
</bean>
|
|
||||||
</property>
|
|
||||||
|
|
||||||
<!-- 解析数据库信息 -->
|
|
||||||
<property name="masterInfo">
|
|
||||||
<bean class="com.alibaba.otter.canal.parse.support.AuthenticationInfo">
|
|
||||||
<property name="address" value="${canal.instance.master.address}" />
|
|
||||||
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
|
||||||
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
|
||||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
|
|
||||||
</bean>
|
|
||||||
</property>
|
|
||||||
|
|
||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
|
||||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
|
||||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
|
|
||||||
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
|
|
||||||
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
|
|
||||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
|
||||||
<property name="needWait" value="${canal.instance.parser.needWait:false}"/>
|
|
||||||
<property name="directory" value="${canal.instance.parser.directory:}"/>
|
|
||||||
|
|
||||||
<!-- rds相关 -->
|
|
||||||
<property name="url" value="${canal.instance.rds.open.url:}"/>
|
|
||||||
<property name="accesskey" value="${canal.instance.rds.open.accesskey:}"/>
|
|
||||||
<property name="secretkey" value="${canal.instance.rds.open.secretkey:}"/>
|
|
||||||
<property name="instanceId" value="${canal.instance.rds.instanceId:}"/>
|
|
||||||
<property name="startTime" value="${canal.instance.rds.startTime:}"/>
|
|
||||||
<property name="endTime" value="${canal.instance.rds.endTime:}"/>
|
|
||||||
|
|
||||||
<!--表结构相关-->
|
|
||||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
|
||||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
|
||||||
</bean>
|
|
||||||
</beans>
|
|
||||||
@@ -10,26 +10,7 @@
|
|||||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
||||||
default-autowire="byName">
|
default-autowire="byName">
|
||||||
|
|
||||||
<!-- properties -->
|
<import resource="classpath:spring/base-instance.xml" />
|
||||||
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
|
||||||
<property name="ignoreResourceNotFound" value="true" />
|
|
||||||
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
|
||||||
<property name="locationNames">
|
|
||||||
<list>
|
|
||||||
<value>classpath:canal.properties</value>
|
|
||||||
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
|
||||||
</list>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
|
|
||||||
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
|
||||||
<property name="propertyEditorRegistrars">
|
|
||||||
<list>
|
|
||||||
<ref bean="socketAddressEditor" />
|
|
||||||
</list>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<property name="destination" value="${canal.instance.destination}" />
|
||||||
@@ -64,9 +45,10 @@
|
|||||||
|
|
||||||
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
||||||
<property name="eventStore" ref="eventStore" />
|
<property name="eventStore" ref="eventStore" />
|
||||||
|
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser" parent="baseEventParser">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<property name="destination" value="${canal.instance.destination}" />
|
||||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||||
<!-- 心跳配置 -->
|
<!-- 心跳配置 -->
|
||||||
@@ -121,7 +103,7 @@
|
|||||||
<property name="address" value="${canal.instance.master.address}" />
|
<property name="address" value="${canal.instance.master.address}" />
|
||||||
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
||||||
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
||||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
|
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="standbyInfo">
|
<property name="standbyInfo">
|
||||||
@@ -129,7 +111,7 @@
|
|||||||
<property name="address" value="${canal.instance.standby.address}" />
|
<property name="address" value="${canal.instance.standby.address}" />
|
||||||
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
||||||
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
||||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
|
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
@@ -139,6 +121,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.master.position}" />
|
<property name="position" value="${canal.instance.master.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.master.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="standbyPosition">
|
<property name="standbyPosition">
|
||||||
@@ -146,6 +129,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.standby.journal.name}" />
|
<property name="journalName" value="${canal.instance.standby.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.standby.position}" />
|
<property name="position" value="${canal.instance.standby.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
|
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.standby.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
@@ -160,5 +144,13 @@
|
|||||||
<!--表结构相关-->
|
<!--表结构相关-->
|
||||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:false}"/>
|
<property name="enableTsdb" value="${canal.instance.tsdb.enable:false}"/>
|
||||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||||
|
|
||||||
|
<!--是否启用GTID模式-->
|
||||||
|
<property name="isGTIDMode" value="${canal.instance.gtidon:false}"/>
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@@ -31,8 +31,8 @@
|
|||||||
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
|
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
|
||||||
<property name="driverClassName" value="org.h2.Driver" />
|
<property name="driverClassName" value="org.h2.Driver" />
|
||||||
<property name="url" value="${canal.instance.tsdb.url:}" />
|
<property name="url" value="${canal.instance.tsdb.url:}" />
|
||||||
<property name="username" value="${canal.instance.tsdb.dbUsername:canal}" />
|
<property name="username" value="${canal.instance.tsdb.dbUsername:}" />
|
||||||
<property name="password" value="${canal.instance.tsdb.dbPassword:canal}" />
|
<property name="password" value="${canal.instance.tsdb.dbPassword:}" />
|
||||||
<property name="maxActive" value="30" />
|
<property name="maxActive" value="30" />
|
||||||
<property name="initialSize" value="0" />
|
<property name="initialSize" value="0" />
|
||||||
<property name="minIdle" value="1" />
|
<property name="minIdle" value="1" />
|
||||||
|
|||||||
@@ -31,8 +31,8 @@
|
|||||||
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
|
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
|
||||||
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
|
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
|
||||||
<property name="url" value="${canal.instance.tsdb.url:}" />
|
<property name="url" value="${canal.instance.tsdb.url:}" />
|
||||||
<property name="username" value="${canal.instance.tsdb.dbUsername:canal}" />
|
<property name="username" value="${canal.instance.tsdb.dbUsername:}" />
|
||||||
<property name="password" value="${canal.instance.tsdb.dbPassword:canal}" />
|
<property name="password" value="${canal.instance.tsdb.dbPassword:}" />
|
||||||
<property name="maxActive" value="30" />
|
<property name="maxActive" value="30" />
|
||||||
<property name="initialSize" value="0" />
|
<property name="initialSize" value="0" />
|
||||||
<property name="minIdle" value="1" />
|
<property name="minIdle" value="1" />
|
||||||
|
|||||||
33
docker/Dockerfile
Normal file
33
docker/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
FROM canal/osbase:v1
|
||||||
|
|
||||||
|
MAINTAINER agapple (jianghang115@gmail.com)
|
||||||
|
|
||||||
|
# install canal
|
||||||
|
COPY image/ /tmp/docker/
|
||||||
|
COPY canal.deployer-*.tar.gz /home/admin/
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
cp -R /tmp/docker/alidata /alidata && \
|
||||||
|
chmod +x /alidata/bin/* && \
|
||||||
|
mkdir -p /home/admin && \
|
||||||
|
cp -R /tmp/docker/admin/* /home/admin/ && \
|
||||||
|
/bin/cp -f alidata/bin/lark-wait /usr/bin/lark-wait && \
|
||||||
|
|
||||||
|
mkdir -p /home/admin/canal-server && \
|
||||||
|
tar -xzvf /home/admin/canal.deployer-*.tar.gz -C /home/admin/canal-server && \
|
||||||
|
/bin/rm -f /home/admin/canal.deployer-*.tar.gz && \
|
||||||
|
|
||||||
|
mkdir -p home/admin/canal-server/logs && \
|
||||||
|
chmod +x /home/admin/*.sh && \
|
||||||
|
chmod +x /home/admin/bin/*.sh && \
|
||||||
|
chown admin: -R /home/admin && \
|
||||||
|
yum clean all && \
|
||||||
|
true
|
||||||
|
|
||||||
|
# 2222 sys , 8000 debug , 11111 canal , 11112 metrics
|
||||||
|
EXPOSE 2222 11111 8000 11112
|
||||||
|
|
||||||
|
WORKDIR /home/admin
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/alidata/bin/main.sh" ]
|
||||||
|
CMD [ "/home/admin/app.sh" ]
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user