mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
Compare commits
286 Commits
table_meta
...
canal-1.1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
20942533e6 | ||
|
|
6b2d80a145 | ||
|
|
01ef15223c | ||
|
|
da3290c381 | ||
|
|
a9284b1b39 | ||
|
|
3409472eb9 | ||
|
|
5348994b4a | ||
|
|
a3b9f6f1eb | ||
|
|
2dc019709b | ||
|
|
c56f58deae | ||
|
|
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>
|
||||||
|
|||||||
@@ -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.25-SNAPSHOT</version>
|
<version>1.1.0</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -69,7 +69,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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 改动记录:
|
// 改动记录:
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<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">
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.25-SNAPSHOT</version>
|
<version>1.1.0</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.25-SNAPSHOT</version>
|
<version>1.1.0</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</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(){
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,15 +30,19 @@ 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.
|
||||||
@@ -104,9 +108,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 */
|
||||||
@@ -361,6 +372,24 @@ 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 */
|
||||||
@@ -385,6 +414,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 +432,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;
|
||||||
|
|
||||||
@@ -14,8 +17,13 @@ public class GtidLogEvent extends LogEvent {
|
|||||||
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,23 @@ 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 +67,19 @@ 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,11 @@ public final class LogHeader {
|
|||||||
*/
|
*/
|
||||||
protected long crc; // ha_checksum
|
protected long crc; // ha_checksum
|
||||||
|
|
||||||
|
/**
|
||||||
|
* binlog fileName
|
||||||
|
*/
|
||||||
|
protected String logFileName;
|
||||||
|
|
||||||
/* 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,6 +275,14 @@ 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);
|
||||||
|
|||||||
@@ -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,52 @@
|
|||||||
|
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.25-SNAPSHOT</version>
|
<version>1.1.0</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>
|
||||||
|
|||||||
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,7 +15,9 @@ 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";
|
||||||
@@ -32,6 +34,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);
|
||||||
@@ -370,7 +388,18 @@ public class CanalController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getProperty(Properties properties, String key) {
|
private 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) {
|
||||||
|
|||||||
@@ -59,6 +59,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,12 @@
|
|||||||
#################################################
|
#################################################
|
||||||
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
|
||||||
# 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 +44,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 +53,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 +87,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=
|
|
||||||
#################################################
|
|
||||||
@@ -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 , 8080 web , 8000 debug , 11111 canal
|
||||||
|
EXPOSE 2222 11111 8000 8080
|
||||||
|
|
||||||
|
WORKDIR /home/admin
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/alidata/bin/main.sh" ]
|
||||||
|
CMD [ "/home/admin/app.sh" ]
|
||||||
41
docker/base/Dockerfile
Normal file
41
docker/base/Dockerfile
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
FROM centos:centos6.10
|
||||||
|
|
||||||
|
MAINTAINER agapple (jianghang115@gmail.com)
|
||||||
|
|
||||||
|
env DOWNLOAD_LINK="http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.rpm"
|
||||||
|
# install system
|
||||||
|
RUN \
|
||||||
|
/bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
|
||||||
|
echo 'root:Hello1234' | chpasswd && \
|
||||||
|
groupadd -r admin && useradd -g admin admin && \
|
||||||
|
yum install -y man && \
|
||||||
|
yum install -y dstat && \
|
||||||
|
yum install -y unzip && \
|
||||||
|
yum install -y nc && \
|
||||||
|
yum install -y openssh-server && \
|
||||||
|
yum install -y tar && \
|
||||||
|
yum install -y which && \
|
||||||
|
yum install -y wget && \
|
||||||
|
yum install -y perl && \
|
||||||
|
yum install -y file && \
|
||||||
|
ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key && \
|
||||||
|
ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key && \
|
||||||
|
sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd && \
|
||||||
|
sed -i -e 's/^#Port 22$/Port 2222/' /etc/ssh/sshd_config && \
|
||||||
|
mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh && \
|
||||||
|
yum install -y cronie && \
|
||||||
|
sed -i '/session required pam_loginuid.so/d' /etc/pam.d/crond && \
|
||||||
|
true
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
touch /var/lib/rpm/* && \
|
||||||
|
wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=xxx; oraclelicense=accept-securebackup-cookie" "$DOWNLOAD_LINK" -O /tmp/jdk-8-linux-x64.rpm && \
|
||||||
|
yum -y install /tmp/jdk-8-linux-x64.rpm && \
|
||||||
|
/bin/rm -f /tmp/jdk-8-linux-x64.rpm && \
|
||||||
|
|
||||||
|
echo "export JAVA_HOME=/usr/java/latest" >> /etc/profile && \
|
||||||
|
echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile && \
|
||||||
|
yum clean all && \
|
||||||
|
true
|
||||||
|
|
||||||
|
CMD ["/bin/bash"]
|
||||||
29
docker/build.sh
Normal file
29
docker/build.sh
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
current_path=`pwd`
|
||||||
|
case "`uname`" in
|
||||||
|
Darwin)
|
||||||
|
bin_abs_path=`cd $(dirname $0); pwd`
|
||||||
|
;;
|
||||||
|
Linux)
|
||||||
|
bin_abs_path=$(readlink -f $(dirname $0))
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
bin_abs_path=`cd $(dirname $0); pwd`
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
BASE=${bin_abs_path}
|
||||||
|
|
||||||
|
if [ "$1" == "base" ] ; then
|
||||||
|
docker build --no-cache -t canal/osbase $BASE/base
|
||||||
|
else
|
||||||
|
rm -rf $BASE/canal.*.tar.gz ;
|
||||||
|
cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ;
|
||||||
|
if [ "$1" == "kafka" ] ; then
|
||||||
|
cp $BASE/../target/canal-kafka-*.tar.gz $BASE/
|
||||||
|
docker build --no-cache -t canal/canal-server $BASE/
|
||||||
|
else
|
||||||
|
cp $BASE/../target/canal.deployer-*.tar.gz $BASE/
|
||||||
|
docker build --no-cache -t canal/canal-server $BASE/
|
||||||
|
fi
|
||||||
|
fi
|
||||||
119
docker/image/admin/app.sh
Executable file
119
docker/image/admin/app.sh
Executable file
@@ -0,0 +1,119 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
source /etc/profile
|
||||||
|
export JAVA_HOME=/usr/java/latest
|
||||||
|
export PATH=$JAVA_HOME/bin:$PATH
|
||||||
|
touch /tmp/start.log
|
||||||
|
chown admin: /tmp/start.log
|
||||||
|
chown -R admin: /home/admin/canal-server
|
||||||
|
host=`hostname -i`
|
||||||
|
|
||||||
|
# waitterm
|
||||||
|
# wait TERM/INT signal.
|
||||||
|
# see: http://veithen.github.io/2014/11/16/sigterm-propagation.html
|
||||||
|
waitterm() {
|
||||||
|
local PID
|
||||||
|
# any process to block
|
||||||
|
tail -f /dev/null &
|
||||||
|
PID="$!"
|
||||||
|
# setup trap, could do nothing, or just kill the blocker
|
||||||
|
trap "kill -TERM ${PID}" TERM INT
|
||||||
|
# wait for signal, ignore wait exit code
|
||||||
|
wait "${PID}" || true
|
||||||
|
# clear trap
|
||||||
|
trap - TERM INT
|
||||||
|
# wait blocker, ignore blocker exit code
|
||||||
|
wait "${PID}" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# waittermpid "${PIDFILE}".
|
||||||
|
# monitor process by pidfile && wait TERM/INT signal.
|
||||||
|
# if the process disappeared, return 1, means exit with ERROR.
|
||||||
|
# if TERM or INT signal received, return 0, means OK to exit.
|
||||||
|
waittermpid() {
|
||||||
|
local PIDFILE PID do_run error
|
||||||
|
PIDFILE="${1?}"
|
||||||
|
do_run=true
|
||||||
|
error=0
|
||||||
|
trap "do_run=false" TERM INT
|
||||||
|
while "${do_run}" ; do
|
||||||
|
PID="$(cat "${PIDFILE}")"
|
||||||
|
if ! ps -p "${PID}" >/dev/null 2>&1 ; then
|
||||||
|
do_run=false
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
sleep 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
trap - TERM INT
|
||||||
|
return "${error}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function checkStart() {
|
||||||
|
local name=$1
|
||||||
|
local cmd=$2
|
||||||
|
local timeout=$3
|
||||||
|
cost=5
|
||||||
|
while [ $timeout -gt 0 ]; do
|
||||||
|
ST=`eval $cmd`
|
||||||
|
if [ "$ST" == "0" ]; then
|
||||||
|
sleep 1
|
||||||
|
let timeout=timeout-1
|
||||||
|
let cost=cost+1
|
||||||
|
elif [ "$ST" == "" ]; then
|
||||||
|
sleep 1
|
||||||
|
let timeout=timeout-1
|
||||||
|
let cost=cost+1
|
||||||
|
else
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "start $name successful"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function start_canal() {
|
||||||
|
echo "start canal ..."
|
||||||
|
serverPort=`perl -le 'print $ENV{"canal.port"}'`
|
||||||
|
if [ -z "$serverPort" ] ; then
|
||||||
|
serverPort=11111
|
||||||
|
fi
|
||||||
|
|
||||||
|
destination=`perl -le 'print $ENV{"canal.destinations"}'`
|
||||||
|
if [[ "$destination" =~ ',' ]]; then
|
||||||
|
echo "multi destination:$destination is not support"
|
||||||
|
exit 1;
|
||||||
|
else
|
||||||
|
if [ "$destination" != "" ] && [ "$destination" != "example" ] ; then
|
||||||
|
mv /home/admin/canal-server/conf/example /home/admin/canal-server/conf/$destination
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
su admin -c 'cd /home/admin/canal-server/bin/ && sh restart.sh 1>>/tmp/start.log 2>&1'
|
||||||
|
sleep 5
|
||||||
|
#check start
|
||||||
|
checkStart "canal" "nc 127.0.0.1 $serverPort -w 1 -z | wc -l" 30
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop_canal() {
|
||||||
|
echo "stop canal"
|
||||||
|
su admin -c 'cd /home/admin/canal-server/bin/ && sh stop.sh 1>>/tmp/start.log 2>&1'
|
||||||
|
echo "stop canal successful ..."
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "==> START ..."
|
||||||
|
|
||||||
|
start_canal
|
||||||
|
|
||||||
|
echo "==> START SUCCESSFUL ..."
|
||||||
|
|
||||||
|
tail -f /dev/null &
|
||||||
|
# wait TERM signal
|
||||||
|
waitterm
|
||||||
|
|
||||||
|
echo "==> STOP"
|
||||||
|
|
||||||
|
stop_canal
|
||||||
|
|
||||||
|
echo "==> STOP SUCCESSFUL ..."
|
||||||
2
docker/image/admin/bin/clean_log
Normal file
2
docker/image/admin/bin/clean_log
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# cron clean log once per minute
|
||||||
|
*/2 * * * * admin /home/admin/bin/clean_log.sh >>/tmp/clean_log.log 2>&1
|
||||||
45
docker/image/admin/bin/clean_log.sh
Executable file
45
docker/image/admin/bin/clean_log.sh
Executable file
@@ -0,0 +1,45 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Global Settings
|
||||||
|
PATH="$HOME/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:/root/bin"
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
CUTOFF="85"
|
||||||
|
#获取磁盘使用率最高的分区
|
||||||
|
USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1)
|
||||||
|
before=$USAGE
|
||||||
|
|
||||||
|
baseClean(){
|
||||||
|
#删除tmp目录15天前的文件。
|
||||||
|
#更新文档时间戳
|
||||||
|
if [ -d /tmp/hsperfdata_admin ]
|
||||||
|
then
|
||||||
|
touch /tmp/hsperfdata_admin
|
||||||
|
touch /tmp/hsperfdata_admin/*
|
||||||
|
fi
|
||||||
|
|
||||||
|
find /tmp/ -type f -mtime +15 | xargs -t rm -rf >/dev/null 2>&1
|
||||||
|
|
||||||
|
|
||||||
|
now=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1)
|
||||||
|
echo "before:$before; now:$now"
|
||||||
|
}
|
||||||
|
|
||||||
|
CANAL_DIR="/home/admin/canal-server/logs"
|
||||||
|
if [[ -d $CANAL_DIR ]]; then
|
||||||
|
USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1)
|
||||||
|
if [[ $USAGE -ge 90 ]]; then
|
||||||
|
find $CANAL_DIR -type f -mtime +7 | xargs rm -rf {}
|
||||||
|
fi
|
||||||
|
USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1)
|
||||||
|
if [[ $USAGE -ge 80 ]]; then
|
||||||
|
find $CANAL_DIR -type f -mtime +3 | xargs rm -rf {}
|
||||||
|
fi
|
||||||
|
USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1)
|
||||||
|
if [[ $USAGE -ge 80 ]]; then
|
||||||
|
find $CANAL_DIR -type d -empty -mtime +3 | grep -v canal | xargs rm -rf {}
|
||||||
|
find $CANAL_DIR -type f -iname '*.tmp' | xargs rm -rf {}
|
||||||
|
fi
|
||||||
|
baseClean
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
18
docker/image/admin/health.sh
Executable file
18
docker/image/admin/health.sh
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
metrics_port=`perl -le 'print $ENV{"canal.metrics.pull.port"}'`
|
||||||
|
if [ "$metrics_port" == "" ]; then
|
||||||
|
metrics_port="11112"
|
||||||
|
fi
|
||||||
|
|
||||||
|
CHECK_URL="http://127.0.0.1:$metrics_port/metrics"
|
||||||
|
CHECK_POINT="canal"
|
||||||
|
CHECK_COUNT=`curl -s --connect-timeout 7 --max-time 7 $CHECK_URL | grep -c $CHECK_POINT`
|
||||||
|
if [ $CHECK_COUNT -eq 0 ]; then
|
||||||
|
echo "[FAILED]"
|
||||||
|
status=0
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "[ OK ]"
|
||||||
|
status=1
|
||||||
|
error=0
|
||||||
|
fi
|
||||||
11
docker/image/alidata/bin/exec_rc_local.sh
Executable file
11
docker/image/alidata/bin/exec_rc_local.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ "${SKIP_EXEC_RC_LOCAL}" = "YES" ] ; then
|
||||||
|
echo "skip /etc/rc.local: SKIP_EXEC_RC_LOCAL=${SKIP_EXEC_RC_LOCAL}"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${DOCKER_DEPLOY_TYPE}" = "HOST" ] ; then
|
||||||
|
echo "skip /etc/rc.local: DOCKER_DEPLOY_TYPE=${DOCKER_DEPLOY_TYPE}"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
6
docker/image/alidata/bin/lark-wait
Executable file
6
docker/image/alidata/bin/lark-wait
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
chown admin: -R /home/admin/
|
||||||
|
source /alidata/lib/proc.sh
|
||||||
|
waitterm
|
||||||
27
docker/image/alidata/bin/main.sh
Executable file
27
docker/image/alidata/bin/main.sh
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
[ -n "${DOCKER_DEPLOY_TYPE}" ] || DOCKER_DEPLOY_TYPE="VM"
|
||||||
|
echo "DOCKER_DEPLOY_TYPE=${DOCKER_DEPLOY_TYPE}"
|
||||||
|
|
||||||
|
# run init scripts
|
||||||
|
for e in $(ls /alidata/init/*) ; do
|
||||||
|
[ -x "${e}" ] || continue
|
||||||
|
echo "==> INIT $e"
|
||||||
|
$e
|
||||||
|
echo "==> EXIT CODE: $?"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "==> INIT DEFAULT"
|
||||||
|
service sshd start
|
||||||
|
service crond start
|
||||||
|
|
||||||
|
#echo "check hostname -i: `hostname -i`"
|
||||||
|
#hti_num=`hostname -i|awk '{print NF}'`
|
||||||
|
#if [ $hti_num -gt 1 ];then
|
||||||
|
# echo "hostname -i result error:`hostname -i`"
|
||||||
|
# exit 120
|
||||||
|
#fi
|
||||||
|
|
||||||
|
echo "==> INIT DONE"
|
||||||
|
echo "==> RUN ${*}"
|
||||||
|
exec "${@}"
|
||||||
19
docker/image/alidata/init/02init-sshd.sh
Executable file
19
docker/image/alidata/init/02init-sshd.sh
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# set port
|
||||||
|
if [ -z "${SSHD_PORT}" ] ; then
|
||||||
|
SSHD_PORT=22
|
||||||
|
[ "${DOCKER_DEPLOY_TYPE}" = "HOST" ] && SSHD_PORT=2222
|
||||||
|
fi
|
||||||
|
|
||||||
|
sed -r -i '/^OPTIONS=/ d' /etc/sysconfig/sshd
|
||||||
|
echo 'OPTIONS="-p '"${SSHD_PORT}"'"' >> /etc/sysconfig/sshd
|
||||||
|
|
||||||
|
# set admin ssh pulic key
|
||||||
|
if [ "${USE_ADMIN_PASSAGE}" = "YES" ] ; then
|
||||||
|
echo "set admin passage"
|
||||||
|
mkdir -p /home/admin/.ssh
|
||||||
|
chown admin:admin /home/admin/.ssh
|
||||||
|
chown admin:admin /home/admin/.ssh/authorized_keys
|
||||||
|
chmod 644 /home/admin/.ssh/authorized_keys
|
||||||
|
fi
|
||||||
66
docker/image/alidata/init/fix-hosts.py
Executable file
66
docker/image/alidata/init/fix-hosts.py
Executable file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#****************************************************************#
|
||||||
|
# Create Date: 2017-01-06 17:58
|
||||||
|
#***************************************************************#
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import shutil
|
||||||
|
from time import gmtime, strftime
|
||||||
|
|
||||||
|
# get host_name
|
||||||
|
host_name = socket.gethostname()
|
||||||
|
tmp_file = "/tmp/.lark-fix-host.hosts"
|
||||||
|
host_file = "/etc/hosts"
|
||||||
|
bak_file_name = "/tmp/hosts-fix-bak.%s" % ( strftime("%Y-%m-%d_%H-%M-%S", gmtime()) )
|
||||||
|
|
||||||
|
# load /etc/hosts file context
|
||||||
|
FH = open(host_file,"r")
|
||||||
|
file_lines = [ i.rstrip() for i in FH.readlines()]
|
||||||
|
FH.close()
|
||||||
|
file_lines_reverse = file_lines[::-1]
|
||||||
|
new_lines = []
|
||||||
|
bad_lines = []
|
||||||
|
last_match_line = ""
|
||||||
|
|
||||||
|
for line in file_lines_reverse:
|
||||||
|
if line.find(host_name) < 0: # 不匹配的行直接跳过
|
||||||
|
new_lines.append(line + "\n")
|
||||||
|
continue
|
||||||
|
|
||||||
|
cols = line.split()
|
||||||
|
new_cols = []
|
||||||
|
if cols[0].startswith("#"): # 跳过已经注释掉的行
|
||||||
|
new_lines.append(line + "\n")
|
||||||
|
continue
|
||||||
|
for col in cols:
|
||||||
|
if not col == host_name: # 跳过不匹配的列
|
||||||
|
new_cols.append(col)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if cols[0] == "127.0.0.1": # 如果第一列是 127.0.0.1 就跳过匹配的列, 防止 hostname -i 返回 127.0.0.1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 如果已经发现过匹配的列, 就丢掉重复的列
|
||||||
|
if not len(last_match_line) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_cols.append(col)
|
||||||
|
last_match_line = line
|
||||||
|
|
||||||
|
# 跳过 xx.xx.xx.xx hostname 这样的重复列
|
||||||
|
if len(new_cols) == 1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_l = "%s\n" % " ".join(new_cols)
|
||||||
|
new_lines.append(new_l)
|
||||||
|
|
||||||
|
# save tmp hosts
|
||||||
|
|
||||||
|
FH2=file(tmp_file,"w+")
|
||||||
|
FH2.writelines( new_lines[::-1])
|
||||||
|
FH2.close()
|
||||||
|
|
||||||
|
# mv to /etc/hosts
|
||||||
|
shutil.copy(host_file, bak_file_name)
|
||||||
|
shutil.move(tmp_file, host_file)
|
||||||
40
docker/image/alidata/lib/proc.sh
Normal file
40
docker/image/alidata/lib/proc.sh
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# waitterm
|
||||||
|
# wait TERM/INT signal.
|
||||||
|
# see: http://veithen.github.io/2014/11/16/sigterm-propagation.html
|
||||||
|
waitterm() {
|
||||||
|
local PID
|
||||||
|
# any process to block
|
||||||
|
tail -f /dev/null &
|
||||||
|
PID="$!"
|
||||||
|
# setup trap, could do nothing, or just kill the blocker
|
||||||
|
trap "kill -TERM ${PID}" TERM INT
|
||||||
|
# wait for signal, ignore wait exit code
|
||||||
|
wait "${PID}" || true
|
||||||
|
# clear trap
|
||||||
|
trap - TERM INT
|
||||||
|
# wait blocker, ignore blocker exit code
|
||||||
|
wait "${PID}" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# waittermpid "${PIDFILE}".
|
||||||
|
# monitor process by pidfile && wait TERM/INT signal.
|
||||||
|
# if the process disappeared, return 1, means exit with ERROR.
|
||||||
|
# if TERM or INT signal received, return 0, means OK to exit.
|
||||||
|
waittermpid() {
|
||||||
|
local PIDFILE PID do_run error
|
||||||
|
PIDFILE="${1?}"
|
||||||
|
do_run=true
|
||||||
|
error=0
|
||||||
|
trap "do_run=false" TERM INT
|
||||||
|
while "${do_run}" ; do
|
||||||
|
PID="$(cat "${PIDFILE}")"
|
||||||
|
if ! ps -p "${PID}" >/dev/null 2>&1 ; then
|
||||||
|
do_run=false
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
sleep 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
trap - TERM INT
|
||||||
|
return "${error}"
|
||||||
|
}
|
||||||
92
docker/run.sh
Normal file
92
docker/run.sh
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
function usage() {
|
||||||
|
echo "Usage:"
|
||||||
|
echo " run.sh [CONFIG]"
|
||||||
|
echo "example:"
|
||||||
|
echo " run.sh -e canal.instance.master.address=127.0.0.1:3306 \\"
|
||||||
|
echo " -e canal.instance.dbUsername=canal \\"
|
||||||
|
echo " -e canal.instance.dbPassword=canal \\"
|
||||||
|
echo " -e canal.instance.connectionCharset=UTF-8 \\"
|
||||||
|
echo " -e canal.instance.tsdb.enable=true \\"
|
||||||
|
echo " -e canal.instance.gtidon=false \\"
|
||||||
|
echo " -e canal.instance.filter.regex=.*\\\\\\..* "
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_port() {
|
||||||
|
local port=$1
|
||||||
|
local TL=$(which telnet)
|
||||||
|
if [ -f $TL ]; then
|
||||||
|
data=`echo quit | telnet 127.0.0.1 $port| grep -ic connected`
|
||||||
|
echo $data
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local NC=$(which nc)
|
||||||
|
if [ -f $NC ]; then
|
||||||
|
data=`nc -z -w 1 127.0.0.1 $port | grep -ic succeeded`
|
||||||
|
echo $data
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
echo "0"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMyIp() {
|
||||||
|
case "`uname`" in
|
||||||
|
Darwin)
|
||||||
|
myip=`echo "show State:/Network/Global/IPv4" | scutil | grep PrimaryInterface | awk '{print $3}' | xargs ifconfig | grep inet | grep -v inet6 | awk '{print $2}'`
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
myip=`ip route get 1 | awk '{print $NF;exit}'`
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo $myip
|
||||||
|
}
|
||||||
|
|
||||||
|
NET_MODE=""
|
||||||
|
case "`uname`" in
|
||||||
|
Darwin)
|
||||||
|
bin_abs_path=`cd $(dirname $0); pwd`
|
||||||
|
;;
|
||||||
|
Linux)
|
||||||
|
bin_abs_path=$(readlink -f $(dirname $0))
|
||||||
|
NET_MODE="--net=host"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
NET_MODE="--net=host"
|
||||||
|
bin_abs_path=`cd $(dirname $0); pwd`
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
BASE=${bin_abs_path}
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
usage
|
||||||
|
elif [ "$1" == "-h" ] ; then
|
||||||
|
usage
|
||||||
|
elif [ "$1" == "help" ] ; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
DATA="$BASE/data"
|
||||||
|
mkdir -p $DATA
|
||||||
|
CONFIG=${@:1}
|
||||||
|
#VOLUMNS="-v $DATA:/home/admin/canal-server/logs"
|
||||||
|
PORTLIST="8000 8080 2222 11111"
|
||||||
|
PORTS=""
|
||||||
|
for PORT in $PORTLIST ; do
|
||||||
|
#exist=`check_port $PORT`
|
||||||
|
exist="0"
|
||||||
|
if [ "$exist" == "0" ]; then
|
||||||
|
PORTS="$PORTS -p $PORT:$PORT"
|
||||||
|
else
|
||||||
|
echo "port $PORT is used , pls check"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
MEMORY="-m 4096m"
|
||||||
|
LOCALHOST=`getMyIp`
|
||||||
|
cmd="docker run -it -h $LOCALHOST $CONFIG --name=canal-server $VOLUMNS $NET_MODE $PORTS $MEMORY canal/canal-server"
|
||||||
|
echo $cmd
|
||||||
|
eval $cmd
|
||||||
@@ -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.25-SNAPSHOT</version>
|
<version>1.1.0</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class MysqlConnector {
|
|||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
private byte charsetNumber = 33;
|
private byte charsetNumber = 33;
|
||||||
private String defaultSchema = "retl";
|
private String defaultSchema = "test";
|
||||||
private int soTimeout = 30 * 1000;
|
private int soTimeout = 30 * 1000;
|
||||||
private int connTimeout = 5 * 1000;
|
private int connTimeout = 5 * 1000;
|
||||||
private int receiveBufferSize = 16 * 1024;
|
private int receiveBufferSize = 16 * 1024;
|
||||||
@@ -44,11 +44,16 @@ public class MysqlConnector {
|
|||||||
private long connectionId = -1;
|
private long connectionId = -1;
|
||||||
private AtomicBoolean connected = new AtomicBoolean(false);
|
private AtomicBoolean connected = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
public static final int timeout = 5 * 1000; // 5s
|
||||||
|
|
||||||
public MysqlConnector(){
|
public MysqlConnector(){
|
||||||
}
|
}
|
||||||
|
|
||||||
public MysqlConnector(InetSocketAddress address, String username, String password){
|
public MysqlConnector(InetSocketAddress address, String username, String password){
|
||||||
this.address = address;
|
String addr = address.getHostString();
|
||||||
|
int port = address.getPort();
|
||||||
|
this.address = new InetSocketAddress(addr, port);
|
||||||
|
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
}
|
}
|
||||||
@@ -101,7 +106,8 @@ public class MysqlConnector {
|
|||||||
MysqlUpdateExecutor executor = new MysqlUpdateExecutor(connector);
|
MysqlUpdateExecutor executor = new MysqlUpdateExecutor(connector);
|
||||||
executor.update("KILL CONNECTION " + connectionId);
|
executor.update("KILL CONNECTION " + connectionId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IOException("KILL DUMP " + connectionId + " failure", e);
|
// 忽略具体异常
|
||||||
|
logger.info("KILL DUMP " + connectionId + " failure", e);
|
||||||
} finally {
|
} finally {
|
||||||
if (connector != null) {
|
if (connector != null) {
|
||||||
connector.disconnect();
|
connector.disconnect();
|
||||||
@@ -144,8 +150,8 @@ public class MysqlConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void negotiate(SocketChannel channel) throws IOException {
|
private void negotiate(SocketChannel channel) throws IOException {
|
||||||
HeaderPacket header = PacketManager.readHeader(channel, 4);
|
HeaderPacket header = PacketManager.readHeader(channel, 4, timeout);
|
||||||
byte[] body = PacketManager.readBytes(channel, header.getPacketBodyLength());
|
byte[] body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
||||||
if (body[0] < 0) {// check field_count
|
if (body[0] < 0) {// check field_count
|
||||||
if (body[0] == -1) {
|
if (body[0] == -1) {
|
||||||
ErrorPacket error = new ErrorPacket();
|
ErrorPacket error = new ErrorPacket();
|
||||||
@@ -184,7 +190,7 @@ public class MysqlConnector {
|
|||||||
header = null;
|
header = null;
|
||||||
header = PacketManager.readHeader(channel, 4);
|
header = PacketManager.readHeader(channel, 4);
|
||||||
body = null;
|
body = null;
|
||||||
body = PacketManager.readBytes(channel, header.getPacketBodyLength());
|
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
||||||
assert body != null;
|
assert body != null;
|
||||||
if (body[0] < 0) {
|
if (body[0] < 0) {
|
||||||
if (body[0] == -1) {
|
if (body[0] == -1) {
|
||||||
@@ -333,4 +339,9 @@ public class MysqlConnector {
|
|||||||
public void setConnTimeout(int connTimeout) {
|
public void setConnTimeout(int connTimeout) {
|
||||||
this.connTimeout = connTimeout;
|
this.connTimeout = connTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.packets;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by hiwjd on 2018/4/23. hiwjd0@gmail.com
|
||||||
|
*/
|
||||||
|
public interface GTIDSet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化成字节数组
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
byte[] encode() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新当前实例
|
||||||
|
*
|
||||||
|
* @param str
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
void update(String str);
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.packets;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by hiwjd on 2018/4/23. hiwjd0@gmail.com
|
||||||
|
*/
|
||||||
|
public class MysqlGTIDSet implements GTIDSet {
|
||||||
|
|
||||||
|
public Map<String, UUIDSet> sets;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encode() throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
ByteHelper.writeUnsignedInt64LittleEndian(sets.size(), out);
|
||||||
|
|
||||||
|
for (Map.Entry<String, UUIDSet> entry : sets.entrySet()) {
|
||||||
|
out.write(entry.getValue().encode());
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(String str) {
|
||||||
|
UUIDSet us = UUIDSet.parse(str);
|
||||||
|
String sid = us.SID.toString();
|
||||||
|
if (sets.containsKey(sid)) {
|
||||||
|
sets.get(sid).intervals.addAll(us.intervals);
|
||||||
|
sets.get(sid).intervals = UUIDSet.combine(sets.get(sid).intervals);
|
||||||
|
} else {
|
||||||
|
sets.put(sid, us);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null) return false;
|
||||||
|
if (this == o) return true;
|
||||||
|
|
||||||
|
MysqlGTIDSet gs = (MysqlGTIDSet) o;
|
||||||
|
if (gs.sets == null) return false;
|
||||||
|
|
||||||
|
for (Map.Entry<String, UUIDSet> entry : sets.entrySet()) {
|
||||||
|
if (!entry.getValue().equals(gs.sets.get(entry.getKey()))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析如下格式的字符串为MysqlGTIDSet: 726757ad-4455-11e8-ae04-0242ac110002:1 =>
|
||||||
|
* MysqlGTIDSet{ sets: { 726757ad-4455-11e8-ae04-0242ac110002: UUIDSet{ SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:2}] } }
|
||||||
|
* } 726757ad-4455-11e8-ae04-0242ac110002:1-3 => MysqlGTIDSet{ sets: {
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002: UUIDSet{ SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4}] } }
|
||||||
|
* } 726757ad-4455-11e8-ae04-0242ac110002:1-3:4 => MysqlGTIDSet{ sets: {
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002: UUIDSet{ SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:5}] } }
|
||||||
|
* } 726757ad-4455-11e8-ae04-0242ac110002:1-3:7-9 => MysqlGTIDSet{ sets: {
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002: UUIDSet{ SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4},
|
||||||
|
* {start:7, stop: 10}] } } }
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002:1-3,726757
|
||||||
|
* ad-4455-11e8-ae04-0242ac110003:4 => MysqlGTIDSet{ sets: {
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002: UUIDSet{ SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4}] },
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110003: UUIDSet{ SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:4, stop:5}] } }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param gtidData
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static MysqlGTIDSet parse(String gtidData) {
|
||||||
|
Map<String, UUIDSet> m;
|
||||||
|
|
||||||
|
if (gtidData == null || gtidData.length() < 1) {
|
||||||
|
m = new HashMap<String, UUIDSet>();
|
||||||
|
} else {
|
||||||
|
String[] uuidStrs = gtidData.split(",");
|
||||||
|
m = new HashMap<String, UUIDSet>(uuidStrs.length);
|
||||||
|
for (int i = 0; i < uuidStrs.length; i++) {
|
||||||
|
UUIDSet uuidSet = UUIDSet.parse(uuidStrs[i]);
|
||||||
|
m.put(uuidSet.SID.toString(), uuidSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MysqlGTIDSet gs = new MysqlGTIDSet();
|
||||||
|
gs.sets = m;
|
||||||
|
|
||||||
|
return gs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for (Map.Entry<String, UUIDSet> entry : sets.entrySet()) {
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
sb.append(entry.getValue().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.packets;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by hiwjd on 2018/4/23. hiwjd0@gmail.com
|
||||||
|
*/
|
||||||
|
public class UUIDSet {
|
||||||
|
|
||||||
|
public UUID SID;
|
||||||
|
public List<Interval> intervals;
|
||||||
|
|
||||||
|
public byte[] encode() throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
|
||||||
|
bb.putLong(SID.getMostSignificantBits());
|
||||||
|
bb.putLong(SID.getLeastSignificantBits());
|
||||||
|
|
||||||
|
out.write(bb.array());
|
||||||
|
|
||||||
|
ByteHelper.writeUnsignedInt64LittleEndian(intervals.size(), out);
|
||||||
|
|
||||||
|
for (Interval interval : intervals) {
|
||||||
|
ByteHelper.writeUnsignedInt64LittleEndian(interval.start, out);
|
||||||
|
ByteHelper.writeUnsignedInt64LittleEndian(interval.stop, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null) return false;
|
||||||
|
if (this == o) return true;
|
||||||
|
|
||||||
|
UUIDSet us = (UUIDSet) o;
|
||||||
|
Collections.sort(intervals);
|
||||||
|
Collections.sort(us.intervals);
|
||||||
|
if (SID.equals(us.SID) && intervals.equals(us.intervals)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Interval implements Comparable<Interval> {
|
||||||
|
|
||||||
|
public long start;
|
||||||
|
public long stop;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
Interval interval = (Interval) o;
|
||||||
|
|
||||||
|
if (start != interval.start) return false;
|
||||||
|
return stop == interval.stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = (int) (start ^ (start >>> 32));
|
||||||
|
result = 31 * result + (int) (stop ^ (stop >>> 32));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Interval o) {
|
||||||
|
if (equals(o)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return start > o.start ? 1 : (start == o.start ? 0 : -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析如下格式字符串为UUIDSet: 726757ad-4455-11e8-ae04-0242ac110002:1 => UUIDSet{SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:2}]}
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002:1-3 => UUIDSet{SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4}]}
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002:1-3:4 UUIDSet{SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:5}]}
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002:1-3:7-9 UUIDSet{SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4},
|
||||||
|
* {start:7, stop:10}]}
|
||||||
|
*
|
||||||
|
* @param str
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static UUIDSet parse(String str) {
|
||||||
|
String[] ss = str.split(":");
|
||||||
|
|
||||||
|
if (ss.length < 2) {
|
||||||
|
throw new RuntimeException(String.format("parseUUIDSet failed due to wrong format: %s", str));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Interval> intervals = new ArrayList<Interval>();
|
||||||
|
for (int i = 1; i < ss.length; i++) {
|
||||||
|
intervals.add(parseInterval(ss[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
UUIDSet uuidSet = new UUIDSet();
|
||||||
|
uuidSet.SID = UUID.fromString(ss[0]);
|
||||||
|
uuidSet.intervals = combine(intervals);
|
||||||
|
|
||||||
|
return uuidSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append(SID.toString());
|
||||||
|
for (Interval interval : intervals) {
|
||||||
|
if (interval.start == interval.stop - 1) {
|
||||||
|
sb.append(":");
|
||||||
|
sb.append(interval.start);
|
||||||
|
} else {
|
||||||
|
sb.append(":");
|
||||||
|
sb.append(interval.start);
|
||||||
|
sb.append("-");
|
||||||
|
sb.append(interval.stop - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析如下格式字符串为Interval: 1 => Interval{start:1, stop:2} 1-3 =>
|
||||||
|
* Interval{start:1, stop:4} 注意!字符串格式表达时[n,m]是两侧都包含的,Interval表达时[n,m)右侧开
|
||||||
|
*
|
||||||
|
* @param str
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Interval parseInterval(String str) {
|
||||||
|
String[] ss = str.split("-");
|
||||||
|
|
||||||
|
Interval interval = new Interval();
|
||||||
|
switch (ss.length) {
|
||||||
|
case 1:
|
||||||
|
interval.start = Long.parseLong(ss[0]);
|
||||||
|
interval.stop = interval.start + 1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
interval.start = Long.parseLong(ss[0]);
|
||||||
|
interval.stop = Long.parseLong(ss[1]) + 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new RuntimeException(String.format("parseInterval failed due to wrong format: %s", str));
|
||||||
|
}
|
||||||
|
|
||||||
|
return interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把{start,stop}连续的合并掉: [{start:1, stop:4},{start:4, stop:5}] => [{start:1,
|
||||||
|
* stop:5}]
|
||||||
|
*
|
||||||
|
* @param intervals
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static List<Interval> combine(List<Interval> intervals) {
|
||||||
|
List<Interval> combined = new ArrayList<Interval>();
|
||||||
|
Collections.sort(intervals);
|
||||||
|
int len = intervals.size();
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
combined.add(intervals.get(i));
|
||||||
|
|
||||||
|
int j;
|
||||||
|
for (j = i + 1; j < len; j++) {
|
||||||
|
if (intervals.get(i).stop >= intervals.get(j).start) {
|
||||||
|
intervals.get(i).stop = intervals.get(j).stop;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = j - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.packets.client;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.CommandPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by hiwjd on 2018/4/24. hiwjd0@gmail.com
|
||||||
|
* https://dev.mysql.com/doc/internals/en/com-binlog-dump-gtid.html
|
||||||
|
*/
|
||||||
|
public class BinlogDumpGTIDCommandPacket extends CommandPacket {
|
||||||
|
|
||||||
|
public static final int BINLOG_DUMP_NON_BLOCK = 0x01;
|
||||||
|
public static final int BINLOG_THROUGH_POSITION = 0x02;
|
||||||
|
public static final int BINLOG_THROUGH_GTID = 0x04;
|
||||||
|
|
||||||
|
public long slaveServerId;
|
||||||
|
public GTIDSet gtidSet;
|
||||||
|
|
||||||
|
public BinlogDumpGTIDCommandPacket(){
|
||||||
|
setCommand((byte) 0x1e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fromBytes(byte[] data) throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] toBytes() throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
// 0. [1] write command number
|
||||||
|
out.write(getCommand());
|
||||||
|
// 1. [2] flags
|
||||||
|
ByteHelper.writeUnsignedShortLittleEndian(BINLOG_THROUGH_GTID, out);
|
||||||
|
// 2. [4] server-id
|
||||||
|
ByteHelper.writeUnsignedIntLittleEndian(slaveServerId, out);
|
||||||
|
// 3. [4] binlog-filename-len
|
||||||
|
ByteHelper.writeUnsignedIntLittleEndian(0, out);
|
||||||
|
// 4. [] binlog-filename
|
||||||
|
// skip
|
||||||
|
// 5. [8] binlog-pos
|
||||||
|
ByteHelper.writeUnsignedInt64LittleEndian(4, out);
|
||||||
|
// if flags & BINLOG_THROUGH_GTID {
|
||||||
|
byte[] bs = gtidSet.encode();
|
||||||
|
// 6. [4] data-size
|
||||||
|
ByteHelper.writeUnsignedIntLittleEndian(bs.length, out);
|
||||||
|
// 7, [] data
|
||||||
|
// [8] n_sids // 文档写的是4个字节,其实是8个字节
|
||||||
|
// for n_sids {
|
||||||
|
// [16] SID
|
||||||
|
// [8] n_intervals
|
||||||
|
// for n_intervals {
|
||||||
|
// [8] start (signed)
|
||||||
|
// [8] end (signed)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
out.write(bs);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.packets.client;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.CommandPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* COM_REGISTER_SLAVE
|
||||||
|
*
|
||||||
|
* @author zhibinliu
|
||||||
|
* @since 1.0.24
|
||||||
|
*/
|
||||||
|
public class RegisterSlaveCommandPacket extends CommandPacket {
|
||||||
|
|
||||||
|
public String reportHost;
|
||||||
|
public int reportPort;
|
||||||
|
public String reportUser;
|
||||||
|
public String reportPasswd;
|
||||||
|
public long serverId;
|
||||||
|
|
||||||
|
public RegisterSlaveCommandPacket(){
|
||||||
|
setCommand((byte) 0x15);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fromBytes(byte[] data) {
|
||||||
|
// bypass
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] toLH(int n) {
|
||||||
|
byte[] b = new byte[4];
|
||||||
|
b[0] = (byte) (n & 0xff);
|
||||||
|
b[1] = (byte) (n >> 8 & 0xff);
|
||||||
|
b[2] = (byte) (n >> 16 & 0xff);
|
||||||
|
b[3] = (byte) (n >> 24 & 0xff);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] toBytes() throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
out.write(getCommand());
|
||||||
|
ByteHelper.writeUnsignedIntLittleEndian(serverId, out);
|
||||||
|
out.write((byte) reportHost.getBytes().length);
|
||||||
|
ByteHelper.writeFixedLengthBytesFromStart(reportHost.getBytes(), reportHost.getBytes().length, out);
|
||||||
|
out.write((byte) reportUser.getBytes().length);
|
||||||
|
ByteHelper.writeFixedLengthBytesFromStart(reportUser.getBytes(), reportUser.getBytes().length, out);
|
||||||
|
out.write((byte) reportPasswd.getBytes().length);
|
||||||
|
ByteHelper.writeFixedLengthBytesFromStart(reportPasswd.getBytes(), reportPasswd.getBytes().length, out);
|
||||||
|
ByteHelper.writeUnsignedShortLittleEndian(reportPort, out);
|
||||||
|
ByteHelper.writeUnsignedIntLittleEndian(0, out);// Fake
|
||||||
|
// rpl_recovery_rank
|
||||||
|
ByteHelper.writeUnsignedIntLittleEndian(0, out);// master id
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.packets.client;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.CommandPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* semi ack command
|
||||||
|
*
|
||||||
|
* @author amos_chen
|
||||||
|
*/
|
||||||
|
public class SemiAckCommandPacket extends CommandPacket {
|
||||||
|
|
||||||
|
public long binlogPosition;
|
||||||
|
public String binlogFileName;
|
||||||
|
|
||||||
|
public SemiAckCommandPacket(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fromBytes(byte[] data) throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Bytes Name
|
||||||
|
* --------------------------------------------------------
|
||||||
|
* Bytes Name
|
||||||
|
* ----- ----
|
||||||
|
* 1 semi mark
|
||||||
|
* 8 binlog position to start at (little endian)
|
||||||
|
* n binlog file name
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public byte[] toBytes() throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
// 0 write semi mark
|
||||||
|
out.write(0xef);
|
||||||
|
// 1 write 8 bytes for position
|
||||||
|
ByteHelper.write8ByteUnsignedIntLittleEndian(binlogPosition, out);
|
||||||
|
|
||||||
|
// 2 write binlog filename
|
||||||
|
if (StringUtils.isNotEmpty(binlogFileName)) {
|
||||||
|
out.write(binlogFileName.getBytes());
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.nio.channels.ClosedByInterruptException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用BIO进行dump
|
||||||
|
*
|
||||||
|
* @author chuanyi
|
||||||
|
*/
|
||||||
|
public class BioSocketChannel implements SocketChannel {
|
||||||
|
|
||||||
|
static final int DEFAULT_CONNECT_TIMEOUT = 10 * 1000;
|
||||||
|
static final int SO_TIMEOUT = 1000;
|
||||||
|
private Socket socket;
|
||||||
|
private InputStream input;
|
||||||
|
private OutputStream output;
|
||||||
|
|
||||||
|
BioSocketChannel(Socket socket) throws IOException{
|
||||||
|
this.socket = socket;
|
||||||
|
this.input = new BufferedInputStream(socket.getInputStream(), 16384);
|
||||||
|
this.output = socket.getOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(byte[]... buf) throws IOException {
|
||||||
|
OutputStream output = this.output;
|
||||||
|
if (output != null) {
|
||||||
|
for (byte[] bs : buf) {
|
||||||
|
output.write(bs);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new SocketException("Socket already closed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] read(int readSize) throws IOException {
|
||||||
|
InputStream input = this.input;
|
||||||
|
byte[] data = new byte[readSize];
|
||||||
|
int remain = readSize;
|
||||||
|
if (input == null) {
|
||||||
|
throw new SocketException("Socket already closed.");
|
||||||
|
}
|
||||||
|
while (remain > 0) {
|
||||||
|
try {
|
||||||
|
int read = input.read(data, readSize - remain, remain);
|
||||||
|
if (read > -1) {
|
||||||
|
remain -= read;
|
||||||
|
} else {
|
||||||
|
throw new IOException("EOF encountered.");
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException te) {
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new ClosedByInterruptException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] read(int readSize, int timeout) throws IOException {
|
||||||
|
InputStream input = this.input;
|
||||||
|
byte[] data = new byte[readSize];
|
||||||
|
int remain = readSize;
|
||||||
|
int accTimeout = 0;
|
||||||
|
if (input == null) {
|
||||||
|
throw new SocketException("Socket already closed.");
|
||||||
|
}
|
||||||
|
while (remain > 0 && accTimeout < timeout) {
|
||||||
|
try {
|
||||||
|
int read = input.read(data, readSize - remain, remain);
|
||||||
|
if (read > -1) {
|
||||||
|
remain -= read;
|
||||||
|
} else {
|
||||||
|
throw new IOException("EOF encountered.");
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException te) {
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new ClosedByInterruptException();
|
||||||
|
}
|
||||||
|
accTimeout += SO_TIMEOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (remain > 0 && accTimeout >= timeout) {
|
||||||
|
throw new SocketTimeoutException("Timeout occurred, failed to read " + readSize + " bytes in " + timeout
|
||||||
|
+ " milliseconds.");
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read(byte[] data, int off, int len, int timeout) throws IOException {
|
||||||
|
InputStream input = this.input;
|
||||||
|
int accTimeout = 0;
|
||||||
|
if (input == null) {
|
||||||
|
throw new SocketException("Socket already closed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int n = 0;
|
||||||
|
while (n < len && accTimeout < timeout) {
|
||||||
|
try {
|
||||||
|
int read = input.read(data, off + n, len - n);
|
||||||
|
if (read > -1) {
|
||||||
|
n += read;
|
||||||
|
} else {
|
||||||
|
throw new IOException("EOF encountered.");
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException te) {
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new ClosedByInterruptException();
|
||||||
|
}
|
||||||
|
accTimeout += SO_TIMEOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n < len && accTimeout >= timeout) {
|
||||||
|
throw new SocketTimeoutException("Timeout occurred, failed to read " + len + " bytes in " + timeout
|
||||||
|
+ " milliseconds.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
Socket socket = this.socket;
|
||||||
|
if (socket != null) {
|
||||||
|
return socket.isConnected();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAddress getRemoteSocketAddress() {
|
||||||
|
Socket socket = this.socket;
|
||||||
|
if (socket != null) {
|
||||||
|
return socket.getRemoteSocketAddress();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
Socket socket = this.socket;
|
||||||
|
if (socket != null) {
|
||||||
|
try {
|
||||||
|
socket.shutdownInput();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore, could not do anymore
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
socket.shutdownOutput();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore, could not do anymore
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore, could not do anymore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.input = null;
|
||||||
|
this.output = null;
|
||||||
|
this.socket = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author luoyaogui 实现channel的管理(监听连接、读数据、回收) 2016-12-28
|
||||||
|
* @author chuanyi 2018-3-3 保留<code>open</code>减少文件变更数量
|
||||||
|
*/
|
||||||
|
public abstract class BioSocketChannelPool {
|
||||||
|
|
||||||
|
public static BioSocketChannel open(SocketAddress address) throws Exception {
|
||||||
|
Socket socket = new Socket();
|
||||||
|
socket.setSoTimeout(BioSocketChannel.SO_TIMEOUT);
|
||||||
|
socket.setTcpNoDelay(true);
|
||||||
|
socket.setKeepAlive(true);
|
||||||
|
socket.setReuseAddress(true);
|
||||||
|
socket.connect(address, BioSocketChannel.DEFAULT_CONNECT_TIMEOUT);
|
||||||
|
return new BioSocketChannel(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.PooledByteBufAllocator;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.util.internal.OutOfDirectMemoryError;
|
||||||
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
import io.netty.util.internal.SystemPropertyUtil;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.NotImplementedException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 封装netty的通信channel和数据接收缓存,实现读、写、连接校验的功能。 2016-12-28
|
||||||
|
*
|
||||||
|
* @author luoyaogui
|
||||||
|
*/
|
||||||
|
public class NettySocketChannel implements SocketChannel {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(SocketChannel.class);
|
||||||
|
private static final int WAIT_PERIOD = 10; // milliseconds
|
||||||
|
private static final int DEFAULT_INIT_BUFFER_SIZE = 1024 * 1024; // 1MB,默认初始缓存大小
|
||||||
|
// 参考 mysql-connector-java-5.1.40.jar: com.mysql.jdbc.MysqlIO.maxThreeBytes
|
||||||
|
// < 256 * 256 * 256 = 16MB
|
||||||
|
private static final int DEFAULT_MAX_BUFFER_SIZE = 16 * DEFAULT_INIT_BUFFER_SIZE; // 16MB,默认最大缓存大小
|
||||||
|
private Channel channel = null;
|
||||||
|
private Object lock = new Object();
|
||||||
|
private ByteBuf cache = PooledByteBufAllocator.DEFAULT.directBuffer(DEFAULT_INIT_BUFFER_SIZE); // 缓存大小
|
||||||
|
private int maxDirectBuffer = cache.maxCapacity();
|
||||||
|
|
||||||
|
public Channel getChannel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChannel(Channel channel) {
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeCache(ByteBuf buf) throws InterruptedException, IOException {
|
||||||
|
synchronized (lock) {
|
||||||
|
while (true) {
|
||||||
|
if (null == cache) {
|
||||||
|
throw new IOException("socket is closed !");
|
||||||
|
}
|
||||||
|
|
||||||
|
// source buffer is empty.
|
||||||
|
if (!buf.isReadable()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认缓存大小不够用时需自动清理或扩充,否则将因缓存空间不足而造成I/O超时假象
|
||||||
|
int length = buf.readableBytes();
|
||||||
|
int deltaSize = length - cache.writableBytes();
|
||||||
|
if (deltaSize > 0) {
|
||||||
|
// 首先避免频繁分配内存(扩容/收缩),其次避免频繁移动内存(清理)
|
||||||
|
if (cache.readerIndex() >= deltaSize) { // 可以清理
|
||||||
|
// 回收已读空间,重置读写指针
|
||||||
|
cache.discardReadBytes();
|
||||||
|
// 恢复自动扩充的过大缓存到默认初始缓存大小,释放空间
|
||||||
|
int oldCapacity = cache.capacity();
|
||||||
|
if (oldCapacity > DEFAULT_MAX_BUFFER_SIZE) { // 尝试收缩
|
||||||
|
int newCapacity = cache.writerIndex();
|
||||||
|
newCapacity = ((newCapacity - 1) / DEFAULT_INIT_BUFFER_SIZE + 1) * DEFAULT_INIT_BUFFER_SIZE; // 对齐
|
||||||
|
int quarter = (newCapacity >> 2); // 至少留空四分之一
|
||||||
|
quarter = ((quarter - 1) / DEFAULT_INIT_BUFFER_SIZE + 1) * DEFAULT_INIT_BUFFER_SIZE; // 对齐
|
||||||
|
newCapacity += quarter; // 留空四分之一
|
||||||
|
if (newCapacity < (oldCapacity >> 1)) { // 至少收缩二分之一
|
||||||
|
try {
|
||||||
|
cache.capacity(newCapacity);
|
||||||
|
logger.info("shrink cache capacity: {} - {} = {} bytes",
|
||||||
|
oldCapacity,
|
||||||
|
oldCapacity - newCapacity,
|
||||||
|
newCapacity);
|
||||||
|
} catch (OutOfMemoryError ignore) {
|
||||||
|
maxDirectBuffer = oldCapacity; // 未来不再超过当前容量,记录日志后继续
|
||||||
|
logger.warn("cache OutOfMemoryError: {} bytes", newCapacity, ignore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // 尝试扩容
|
||||||
|
int oldCapacity = cache.capacity();
|
||||||
|
if (oldCapacity < maxDirectBuffer) {
|
||||||
|
int quarter = (oldCapacity >> 2); // 至少扩容四分之一
|
||||||
|
quarter = ((quarter - 1) / DEFAULT_INIT_BUFFER_SIZE + 1) * DEFAULT_INIT_BUFFER_SIZE; // 对齐
|
||||||
|
deltaSize = ((deltaSize - 1) / quarter + 1) * quarter; // 对齐
|
||||||
|
int newCapacity = oldCapacity + deltaSize;
|
||||||
|
if (newCapacity > maxDirectBuffer) {
|
||||||
|
newCapacity = maxDirectBuffer;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
cache.capacity(newCapacity);
|
||||||
|
logger.info("expand cache capacity: {} + {} = {} bytes",
|
||||||
|
oldCapacity,
|
||||||
|
newCapacity - oldCapacity,
|
||||||
|
newCapacity);
|
||||||
|
} catch (OutOfDirectMemoryError e) {
|
||||||
|
// failed to allocate 885571168 byte(s) of
|
||||||
|
// direct memory (used: 1002946176, max:
|
||||||
|
// 1888485376)
|
||||||
|
long maxDirectMemory = SystemPropertyUtil.getLong("io.netty.maxDirectMemory", -1);
|
||||||
|
if (maxDirectMemory < 0) {
|
||||||
|
maxDirectMemory = PlatformDependent.maxDirectMemory();
|
||||||
|
}
|
||||||
|
if (maxDirectBuffer > maxDirectMemory) {
|
||||||
|
maxDirectBuffer = (int) maxDirectMemory;
|
||||||
|
newCapacity = maxDirectBuffer;
|
||||||
|
logger.warn("resize maxDirectBuffer: {} bytes", maxDirectBuffer, e);
|
||||||
|
try {
|
||||||
|
cache.capacity(newCapacity);
|
||||||
|
logger.info("expand cache capacity: {} + {} = {} bytes",
|
||||||
|
oldCapacity,
|
||||||
|
newCapacity - oldCapacity,
|
||||||
|
newCapacity);
|
||||||
|
} catch (OutOfMemoryError ignore) {
|
||||||
|
maxDirectBuffer = oldCapacity; // 未来不再超过当前容量,记录日志后继续
|
||||||
|
logger.warn("cache OutOfMemoryError: {} bytes", newCapacity, ignore);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
maxDirectBuffer = oldCapacity; // 未来不再超过当前容量,记录日志后继续
|
||||||
|
logger.warn("cache OutOfDirectMemoryError: {} bytes", newCapacity, e);
|
||||||
|
}
|
||||||
|
} catch (OutOfMemoryError ignore) {
|
||||||
|
maxDirectBuffer = oldCapacity; // 未来不再超过当前容量,记录日志后继续
|
||||||
|
logger.warn("cache OutOfMemoryError: {} bytes", newCapacity, ignore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deltaSize = length - cache.writableBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deltaSize != length) {
|
||||||
|
// deltaSize <= 0 可全部写入,deltaSize > 0 只能部分写入
|
||||||
|
if (deltaSize <= 0) {
|
||||||
|
cache.writeBytes(buf, length);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
cache.writeBytes(buf, length - deltaSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// dest buffer is full.
|
||||||
|
lock.wait(WAIT_PERIOD);
|
||||||
|
// 回收已读空间,重置读写指针
|
||||||
|
cache.discardReadBytes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(byte[]... buf) throws IOException {
|
||||||
|
if (channel != null && channel.isWritable()) {
|
||||||
|
channel.writeAndFlush(Unpooled.copiedBuffer(buf));
|
||||||
|
} else {
|
||||||
|
throw new IOException("write failed ! please checking !");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] read(int readSize) throws IOException {
|
||||||
|
return read(readSize, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] read(int readSize, int timeout) throws IOException {
|
||||||
|
int accumulatedWaitTime = 0;
|
||||||
|
|
||||||
|
// 若读取内容较长,则自动扩充超时时间,以初始缓存大小为基准计算倍数
|
||||||
|
if (timeout > 0 && readSize > DEFAULT_INIT_BUFFER_SIZE) {
|
||||||
|
timeout *= (readSize / DEFAULT_INIT_BUFFER_SIZE + 1);
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
if (readSize > cache.readableBytes()) {
|
||||||
|
if (null == channel) {
|
||||||
|
throw new IOException("socket has Interrupted !");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeout > 0) {
|
||||||
|
accumulatedWaitTime += WAIT_PERIOD;
|
||||||
|
if (accumulatedWaitTime > timeout) {
|
||||||
|
StringBuilder sb = new StringBuilder("socket read timeout occured !");
|
||||||
|
sb.append(" readSize = ").append(readSize);
|
||||||
|
sb.append(", readableBytes = ").append(cache.readableBytes());
|
||||||
|
sb.append(", timeout = ").append(timeout);
|
||||||
|
throw new IOException(sb.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
try {
|
||||||
|
wait(WAIT_PERIOD);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new IOException("socket has Interrupted !");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
byte[] back = new byte[readSize];
|
||||||
|
synchronized (lock) {
|
||||||
|
cache.readBytes(back);
|
||||||
|
}
|
||||||
|
return back;
|
||||||
|
}
|
||||||
|
} while (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read(byte[] data, int off, int len, int timeout) throws IOException {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return channel != null ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAddress getRemoteSocketAddress() {
|
||||||
|
return channel != null ? channel.remoteAddress() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
if (channel != null) {
|
||||||
|
channel.close();
|
||||||
|
}
|
||||||
|
channel = null;
|
||||||
|
// A fatal error has been detected by the Java Runtime Environment:
|
||||||
|
// EXCEPTION_ACCESS_VIOLATION (0xc0000005)
|
||||||
|
synchronized (lock) {
|
||||||
|
cache.discardReadBytes();// 回收已占用的内存
|
||||||
|
cache.release();// 释放整个内存
|
||||||
|
cache = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||||
|
|
||||||
|
import io.netty.bootstrap.Bootstrap;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.PooledByteBufAllocator;
|
||||||
|
import io.netty.channel.AdaptiveRecvByteBufAllocator;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInitializer;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.channel.EventLoopGroup;
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author luoyaogui 实现channel的管理(监听连接、读数据、回收) 2016-12-28
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "rawtypes", "deprecation" })
|
||||||
|
public abstract class NettySocketChannelPool {
|
||||||
|
|
||||||
|
private static EventLoopGroup group = new NioEventLoopGroup(); // 非阻塞IO线程组
|
||||||
|
private static Bootstrap boot = new Bootstrap(); // 主
|
||||||
|
private static Map<Channel, SocketChannel> chManager = new ConcurrentHashMap<Channel, SocketChannel>();
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(NettySocketChannelPool.class);
|
||||||
|
|
||||||
|
static {
|
||||||
|
boot.group(group)
|
||||||
|
.channel(NioSocketChannel.class)
|
||||||
|
.option(ChannelOption.TCP_NODELAY, true)
|
||||||
|
// 如果是延时敏感型应用,建议关闭Nagle算法
|
||||||
|
.option(ChannelOption.SO_KEEPALIVE, true)
|
||||||
|
.option(ChannelOption.SO_REUSEADDR, true)
|
||||||
|
.option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)
|
||||||
|
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
|
||||||
|
//
|
||||||
|
.handler(new ChannelInitializer() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initChannel(Channel ch) throws Exception {
|
||||||
|
ch.pipeline().addLast(new BusinessHandler());// 命令过滤和handler添加管理
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SocketChannel open(SocketAddress address) throws Exception {
|
||||||
|
SocketChannel socket = null;
|
||||||
|
ChannelFuture future = boot.connect(address).sync();
|
||||||
|
|
||||||
|
if (future.isSuccess()) {
|
||||||
|
future.channel().pipeline().get(BusinessHandler.class).latch.await();
|
||||||
|
socket = chManager.get(future.channel());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null == socket) {
|
||||||
|
throw new IOException("can't create socket!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BusinessHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||||
|
|
||||||
|
private NettySocketChannel socket = null;
|
||||||
|
private final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
socket.setChannel(null);
|
||||||
|
chManager.remove(ctx.channel());// 移除
|
||||||
|
super.channelInactive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
socket = new NettySocketChannel();
|
||||||
|
socket.setChannel(ctx.channel());
|
||||||
|
chManager.put(ctx.channel(), socket);
|
||||||
|
latch.countDown();
|
||||||
|
super.channelActive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
|
||||||
|
if (socket != null) {
|
||||||
|
socket.writeCache(msg);
|
||||||
|
} else {
|
||||||
|
// TODO: need graceful error handler.
|
||||||
|
logger.error("no socket available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
|
// need output error for troubeshooting.
|
||||||
|
logger.error("business error.", cause);
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,85 +1,25 @@
|
|||||||
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.buffer.PooledByteBufAllocator;
|
|
||||||
import io.netty.buffer.Unpooled;
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 封装netty的通信channel和数据接收缓存,实现读、写、连接校验的功能。 2016-12-28
|
* @author agapple 2018年3月12日 下午10:36:44
|
||||||
*
|
* @since 1.0.26
|
||||||
* @author luoyaogui
|
|
||||||
*/
|
*/
|
||||||
public class SocketChannel {
|
public interface SocketChannel {
|
||||||
|
|
||||||
private Channel channel = null;
|
public void write(byte[]... buf) throws IOException;
|
||||||
private Object lock = new Object();
|
|
||||||
private ByteBuf cache = PooledByteBufAllocator.DEFAULT.directBuffer(1024 * 1024); // 缓存大小
|
|
||||||
|
|
||||||
public Channel getChannel() {
|
public byte[] read(int readSize) throws IOException;
|
||||||
return channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChannel(Channel channel) {
|
public byte[] read(int readSize, int timeout) throws IOException;
|
||||||
this.channel = channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeCache(ByteBuf buf) {
|
public void read(byte[] data, int off, int len, int timeout) throws IOException;
|
||||||
synchronized (lock) {
|
|
||||||
cache.discardReadBytes();// 回收内存
|
|
||||||
cache.writeBytes(buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeChannel(byte[]... buf) throws IOException {
|
public boolean isConnected();
|
||||||
if (channel != null && channel.isWritable()) {
|
|
||||||
channel.writeAndFlush(Unpooled.copiedBuffer(buf));
|
|
||||||
} else {
|
|
||||||
throw new IOException("write failed ! please checking !");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] read(int readSize) throws IOException {
|
public SocketAddress getRemoteSocketAddress();
|
||||||
do {
|
|
||||||
if (readSize > cache.readableBytes()) {
|
|
||||||
if (null == channel) {
|
|
||||||
throw new java.nio.channels.ClosedByInterruptException();
|
|
||||||
}
|
|
||||||
synchronized (this) {
|
|
||||||
try {
|
|
||||||
wait(100);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new java.nio.channels.ClosedByInterruptException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
byte[] back = new byte[readSize];
|
|
||||||
synchronized (lock) {
|
|
||||||
cache.readBytes(back);
|
|
||||||
}
|
|
||||||
return back;
|
|
||||||
}
|
|
||||||
} while (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isConnected() {
|
public void close();
|
||||||
return channel != null ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SocketAddress getRemoteSocketAddress() {
|
|
||||||
return channel != null ? channel.remoteAddress() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close() {
|
|
||||||
if (channel != null) {
|
|
||||||
channel.close();
|
|
||||||
}
|
|
||||||
channel = null;
|
|
||||||
cache.discardReadBytes();// 回收已占用的内存
|
|
||||||
cache.release();// 释放整个内存
|
|
||||||
cache = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,103 +1,35 @@
|
|||||||
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||||
|
|
||||||
import io.netty.bootstrap.Bootstrap;
|
import java.net.SocketAddress;
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.buffer.PooledByteBufAllocator;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import io.netty.channel.AdaptiveRecvByteBufAllocator;
|
|
||||||
import io.netty.channel.Channel;
|
/**
|
||||||
import io.netty.channel.ChannelFuture;
|
* @author agapple 2018年3月12日 下午10:46:22
|
||||||
import io.netty.channel.ChannelFutureListener;
|
* @since 1.0.26
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
*/
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
public abstract class SocketChannelPool {
|
||||||
import io.netty.channel.ChannelInitializer;
|
|
||||||
import io.netty.channel.ChannelOption;
|
public static SocketChannel open(SocketAddress address) throws Exception {
|
||||||
import io.netty.channel.EventLoopGroup;
|
String type = chooseSocketChannel();
|
||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
if ("netty".equalsIgnoreCase(type)) {
|
||||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
return NettySocketChannelPool.open(address);
|
||||||
import io.netty.util.ReferenceCountUtil;
|
} else {
|
||||||
|
return BioSocketChannelPool.open(address);
|
||||||
import java.io.IOException;
|
}
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.util.Map;
|
}
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
private static String chooseSocketChannel() {
|
||||||
import com.alibaba.otter.canal.common.utils.BooleanMutex;
|
String socketChannel = System.getenv("canal.socketChannel");
|
||||||
|
if (StringUtils.isEmpty(socketChannel)) {
|
||||||
/**
|
socketChannel = System.getProperty("canal.socketChannel");
|
||||||
* @author luoyaogui 实现channel的管理(监听连接、读数据、回收) 2016-12-28
|
}
|
||||||
*/
|
|
||||||
@SuppressWarnings({ "rawtypes", "deprecation" })
|
if (StringUtils.isEmpty(socketChannel)) {
|
||||||
public abstract class SocketChannelPool {
|
socketChannel = "bio"; // bio or netty
|
||||||
|
}
|
||||||
private static EventLoopGroup group = new NioEventLoopGroup(); // 非阻塞IO线程组
|
|
||||||
private static Bootstrap boot = new Bootstrap(); // 主
|
return socketChannel;
|
||||||
private static Map<Channel, SocketChannel> chManager = new ConcurrentHashMap<Channel, SocketChannel>();
|
}
|
||||||
|
}
|
||||||
static {
|
|
||||||
boot.group(group)
|
|
||||||
.channel(NioSocketChannel.class)
|
|
||||||
.option(ChannelOption.SO_RCVBUF, 32 * 1024)
|
|
||||||
.option(ChannelOption.SO_SNDBUF, 32 * 1024)
|
|
||||||
.option(ChannelOption.TCP_NODELAY, true)
|
|
||||||
// 如果是延时敏感型应用,建议关闭Nagle算法
|
|
||||||
.option(ChannelOption.SO_KEEPALIVE, true)
|
|
||||||
.option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)
|
|
||||||
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
|
|
||||||
//
|
|
||||||
.handler(new ChannelInitializer() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initChannel(Channel arg0) throws Exception {
|
|
||||||
arg0.pipeline().addLast(new BusinessHandler());// 命令过滤和handler添加管理
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SocketChannel open(SocketAddress address) throws Exception {
|
|
||||||
final SocketChannel socket = new SocketChannel();
|
|
||||||
final BooleanMutex mutex = new BooleanMutex(false);
|
|
||||||
boot.connect(address).addListener(new ChannelFutureListener() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void operationComplete(ChannelFuture arg0) throws Exception {
|
|
||||||
if (arg0.isSuccess()) {
|
|
||||||
socket.setChannel(arg0.channel());
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex.set(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// wait for complete
|
|
||||||
mutex.get();
|
|
||||||
if (null == socket.getChannel()) {
|
|
||||||
throw new IOException("can't create socket!");
|
|
||||||
}
|
|
||||||
chManager.put(socket.getChannel(), socket);
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class BusinessHandler extends ChannelInboundHandlerAdapter {
|
|
||||||
|
|
||||||
private SocketChannel socket = null;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
socket.setChannel(null);
|
|
||||||
chManager.remove(ctx.channel());// 移除
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
||||||
if (null == socket) socket = chManager.get(ctx.channel());
|
|
||||||
if (socket != null) {
|
|
||||||
socket.writeCache((ByteBuf) msg);
|
|
||||||
}
|
|
||||||
ReferenceCountUtil.release(msg);// 添加防止内存泄漏的
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
||||||
ctx.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -109,6 +109,17 @@ public abstract class ByteHelper {
|
|||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void write8ByteUnsignedIntLittleEndian(long data, ByteArrayOutputStream out) {
|
||||||
|
out.write((byte) (data & 0xFF));
|
||||||
|
out.write((byte) (data >>> 8));
|
||||||
|
out.write((byte) (data >>> 16));
|
||||||
|
out.write((byte) (data >>> 24));
|
||||||
|
out.write((byte) (data >>> 32));
|
||||||
|
out.write((byte) (data >>> 40));
|
||||||
|
out.write((byte) (data >>> 48));
|
||||||
|
out.write((byte) (data >>> 56));
|
||||||
|
}
|
||||||
|
|
||||||
public static void writeUnsignedIntLittleEndian(long data, ByteArrayOutputStream out) {
|
public static void writeUnsignedIntLittleEndian(long data, ByteArrayOutputStream out) {
|
||||||
out.write((byte) (data & 0xFF));
|
out.write((byte) (data & 0xFF));
|
||||||
out.write((byte) (data >>> 8));
|
out.write((byte) (data >>> 8));
|
||||||
@@ -116,6 +127,17 @@ public abstract class ByteHelper {
|
|||||||
out.write((byte) (data >>> 24));
|
out.write((byte) (data >>> 24));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void writeUnsignedInt64LittleEndian(long data, ByteArrayOutputStream out) {
|
||||||
|
out.write((byte) (data & 0xFF));
|
||||||
|
out.write((byte) (data >>> 8));
|
||||||
|
out.write((byte) (data >>> 16));
|
||||||
|
out.write((byte) (data >>> 24));
|
||||||
|
out.write((byte) (data >>> 32));
|
||||||
|
out.write((byte) (data >>> 40));
|
||||||
|
out.write((byte) (data >>> 48));
|
||||||
|
out.write((byte) (data >>> 56));
|
||||||
|
}
|
||||||
|
|
||||||
public static void writeUnsignedShortLittleEndian(int data, ByteArrayOutputStream out) {
|
public static void writeUnsignedShortLittleEndian(int data, ByteArrayOutputStream out) {
|
||||||
out.write((byte) (data & 0xFF));
|
out.write((byte) (data & 0xFF));
|
||||||
out.write((byte) ((data >>> 8) & 0xFF));
|
out.write((byte) ((data >>> 8) & 0xFF));
|
||||||
|
|||||||
@@ -13,12 +13,22 @@ public abstract class PacketManager {
|
|||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static HeaderPacket readHeader(SocketChannel ch, int len, int timeout) throws IOException {
|
||||||
|
HeaderPacket header = new HeaderPacket();
|
||||||
|
header.fromBytes(ch.read(len, timeout));
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
public static byte[] readBytes(SocketChannel ch, int len) throws IOException {
|
public static byte[] readBytes(SocketChannel ch, int len) throws IOException {
|
||||||
return ch.read(len);
|
return ch.read(len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] readBytes(SocketChannel ch, int len, int timeout) throws IOException {
|
||||||
|
return ch.read(len, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
public static void writePkg(SocketChannel ch, byte[]... srcs) throws IOException {
|
public static void writePkg(SocketChannel ch, byte[]... srcs) throws IOException {
|
||||||
ch.writeChannel(srcs);
|
ch.write(srcs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void writeBody(SocketChannel ch, byte[] body) throws IOException {
|
public static void writeBody(SocketChannel ch, byte[] body) throws IOException {
|
||||||
@@ -29,6 +39,6 @@ public abstract class PacketManager {
|
|||||||
HeaderPacket header = new HeaderPacket();
|
HeaderPacket header = new HeaderPacket();
|
||||||
header.setPacketBodyLength(body.length);
|
header.setPacketBodyLength(body.length);
|
||||||
header.setPacketSequenceNumber(packetSeqNumber);
|
header.setPacketSequenceNumber(packetSeqNumber);
|
||||||
ch.writeChannel(header.toBytes(), body);
|
ch.write(header.toBytes(), body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.UUIDSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by hiwjd on 2018/4/25. hiwjd0@gmail.com
|
||||||
|
*/
|
||||||
|
public class MysqlGTIDSetTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncode() throws IOException {
|
||||||
|
GTIDSet gtidSet = MysqlGTIDSet.parse("726757ad-4455-11e8-ae04-0242ac110002:1");
|
||||||
|
byte[] bytes = gtidSet.encode();
|
||||||
|
|
||||||
|
byte[] expected = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x67, 0x57, (byte) 0xad, 0x44, 0x55,
|
||||||
|
0x11, (byte) 0xe8, (byte) 0xae, 0x04, 0x02, 0x42, (byte) 0xac, 0x11, 0x00, 0x02, 0x01, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00 };
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length; i++) {
|
||||||
|
assertEquals(expected[i], bytes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParse() {
|
||||||
|
Map<String, MysqlGTIDSet> cases = new HashMap<String, MysqlGTIDSet>(5);
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1",
|
||||||
|
buildForTest(new Material("726757ad-4455-11e8-ae04-0242ac110002", 1, 2)));
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3",
|
||||||
|
buildForTest(new Material("726757ad-4455-11e8-ae04-0242ac110002", 1, 4)));
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3:4",
|
||||||
|
buildForTest(new Material("726757ad-4455-11e8-ae04-0242ac110002", 1, 5)));
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3:7-9",
|
||||||
|
buildForTest(new Material("726757ad-4455-11e8-ae04-0242ac110002", 1, 4, 7, 10)));
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3,726757ad-4455-11e8-ae04-0242ac110003:4",
|
||||||
|
buildForTest(Arrays.asList(new Material("726757ad-4455-11e8-ae04-0242ac110002", 1, 4),
|
||||||
|
new Material("726757ad-4455-11e8-ae04-0242ac110003", 4, 5))));
|
||||||
|
|
||||||
|
for (Map.Entry<String, MysqlGTIDSet> entry : cases.entrySet()) {
|
||||||
|
MysqlGTIDSet expected = entry.getValue();
|
||||||
|
MysqlGTIDSet actual = MysqlGTIDSet.parse(entry.getKey());
|
||||||
|
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Material {
|
||||||
|
|
||||||
|
public Material(String uuid, long start, long stop){
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.start = start;
|
||||||
|
this.stop = stop;
|
||||||
|
this.start1 = 0;
|
||||||
|
this.stop1 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Material(String uuid, long start, long stop, long start1, long stop1){
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.start = start;
|
||||||
|
this.stop = stop;
|
||||||
|
this.start1 = start1;
|
||||||
|
this.stop1 = stop1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String uuid;
|
||||||
|
public long start;
|
||||||
|
public long stop;
|
||||||
|
public long start1;
|
||||||
|
public long stop1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MysqlGTIDSet buildForTest(Material material) {
|
||||||
|
return buildForTest(Arrays.asList(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
private MysqlGTIDSet buildForTest(List<Material> materials) {
|
||||||
|
Map<String, UUIDSet> sets = new HashMap<String, UUIDSet>();
|
||||||
|
for (Material a : materials) {
|
||||||
|
UUIDSet.Interval interval = new UUIDSet.Interval();
|
||||||
|
interval.start = a.start;
|
||||||
|
interval.stop = a.stop;
|
||||||
|
List<UUIDSet.Interval> intervals = new ArrayList<UUIDSet.Interval>();
|
||||||
|
intervals.add(interval);
|
||||||
|
|
||||||
|
if (a.start1 > 0 && a.stop1 > 0) {
|
||||||
|
UUIDSet.Interval interval1 = new UUIDSet.Interval();
|
||||||
|
interval1.start = a.start1;
|
||||||
|
interval1.stop = a.stop1;
|
||||||
|
intervals.add(interval1);
|
||||||
|
}
|
||||||
|
|
||||||
|
UUIDSet us = new UUIDSet();
|
||||||
|
us.SID = UUID.fromString(a.uuid);
|
||||||
|
us.intervals = intervals;
|
||||||
|
|
||||||
|
sets.put(a.uuid, us);
|
||||||
|
}
|
||||||
|
|
||||||
|
MysqlGTIDSet gs = new MysqlGTIDSet();
|
||||||
|
gs.sets = sets;
|
||||||
|
|
||||||
|
return gs;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.UUIDSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by hiwjd on 2018/4/26. hiwjd0@gmail.com
|
||||||
|
*/
|
||||||
|
public class UUIDSetTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToString() {
|
||||||
|
Map<String, String> cases = new HashMap<String, String>(4);
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1", "726757ad-4455-11e8-ae04-0242ac110002:1");
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3", "726757ad-4455-11e8-ae04-0242ac110002:1-3");
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3:4-6", "726757ad-4455-11e8-ae04-0242ac110002:1-6");
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3:5-7", "726757ad-4455-11e8-ae04-0242ac110002:1-3:5-7");
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> entry : cases.entrySet()) {
|
||||||
|
String expected = entry.getValue();
|
||||||
|
assertEquals(expected, UUIDSet.parse(entry.getKey()).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.25-SNAPSHOT</version>
|
<version>1.1.0</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -21,6 +21,82 @@
|
|||||||
<artifactId>canal.protocol</artifactId>
|
<artifactId>canal.protocol</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>druid</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.ddlutils</groupId>
|
||||||
|
<artifactId>ddlutils</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-beanutils</groupId>
|
||||||
|
<artifactId>commons-beanutils-core</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-lang</groupId>
|
||||||
|
<artifactId>commons-lang</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-dbcp</groupId>
|
||||||
|
<artifactId>commons-dbcp</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-pool</groupId>
|
||||||
|
<artifactId>commons-pool</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-logging</groupId>
|
||||||
|
<artifactId>commons-logging-api</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>dom4j</groupId>
|
||||||
|
<artifactId>dom4j</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>stax</groupId>
|
||||||
|
<artifactId>stax-api</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-collections</groupId>
|
||||||
|
<artifactId>commons-collections</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-digester</groupId>
|
||||||
|
<artifactId>commons-digester</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-betwixt</groupId>
|
||||||
|
<artifactId>commons-betwixt</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-pool2</artifactId>
|
||||||
|
<version>2.5.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-beanutils</groupId>
|
||||||
|
<artifactId>commons-beanutils</artifactId>
|
||||||
|
<version>1.8.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>3.7</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-collections</groupId>
|
||||||
|
<artifactId>commons-collections</artifactId>
|
||||||
|
<version>3.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- test dependency -->
|
<!-- test dependency -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.alibaba.otter.canal.example;
|
package com.alibaba.otter.canal.example;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang.SystemUtils;
|
import org.apache.commons.lang.SystemUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -16,6 +18,7 @@ import com.alibaba.otter.canal.protocol.CanalEntry.Column;
|
|||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
|
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
|
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry.Pair;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
|
import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
|
import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin;
|
import com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin;
|
||||||
@@ -56,10 +59,12 @@ public class AbstractCanalClientTest {
|
|||||||
context_format += "****************************************************" + SEP;
|
context_format += "****************************************************" + SEP;
|
||||||
|
|
||||||
row_format = SEP
|
row_format = SEP
|
||||||
+ "----------------> binlog[{}:{}] , name[{},{}] , eventType : {} , executeTime : {} , delay : {}ms"
|
+ "----------------> binlog[{}:{}] , name[{},{}] , eventType : {} , executeTime : {}({}) , gtid : ({}) , delay : {} ms"
|
||||||
+ SEP;
|
+ SEP;
|
||||||
|
|
||||||
transaction_format = SEP + "================> binlog[{}:{}] , executeTime : {} , delay : {}ms" + SEP;
|
transaction_format = SEP
|
||||||
|
+ "================> binlog[{}:{}] , executeTime : {}({}) , gtid : ({}) , delay : {}ms"
|
||||||
|
+ SEP;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,14 +87,15 @@ public class AbstractCanalClientTest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
thread.setUncaughtExceptionHandler(handler);
|
thread.setUncaughtExceptionHandler(handler);
|
||||||
thread.start();
|
|
||||||
running = true;
|
running = true;
|
||||||
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void stop() {
|
protected void stop() {
|
||||||
if (!running) {
|
if (!running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
connector.stopRunning();
|
||||||
running = false;
|
running = false;
|
||||||
if (thread != null) {
|
if (thread != null) {
|
||||||
try {
|
try {
|
||||||
@@ -157,14 +163,20 @@ public class AbstractCanalClientTest {
|
|||||||
long time = entry.getHeader().getExecuteTime();
|
long time = entry.getHeader().getExecuteTime();
|
||||||
Date date = new Date(time);
|
Date date = new Date(time);
|
||||||
SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
|
SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
|
||||||
return entry.getHeader().getLogfileName() + ":" + entry.getHeader().getLogfileOffset() + ":"
|
String position = entry.getHeader().getLogfileName() + ":" + entry.getHeader().getLogfileOffset() + ":"
|
||||||
+ entry.getHeader().getExecuteTime() + "(" + format.format(date) + ")";
|
+ entry.getHeader().getExecuteTime() + "(" + format.format(date) + ")";
|
||||||
|
if (StringUtils.isNotEmpty(entry.getHeader().getGtid())) {
|
||||||
|
position += " gtid(" + entry.getHeader().getGtid() + ")";
|
||||||
|
}
|
||||||
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void printEntry(List<Entry> entrys) {
|
protected void printEntry(List<Entry> entrys) {
|
||||||
for (Entry entry : entrys) {
|
for (Entry entry : entrys) {
|
||||||
long executeTime = entry.getHeader().getExecuteTime();
|
long executeTime = entry.getHeader().getExecuteTime();
|
||||||
long delayTime = new Date().getTime() - executeTime;
|
long delayTime = new Date().getTime() - executeTime;
|
||||||
|
Date date = new Date(entry.getHeader().getExecuteTime());
|
||||||
|
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
|
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
|
||||||
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN) {
|
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN) {
|
||||||
@@ -178,8 +190,10 @@ public class AbstractCanalClientTest {
|
|||||||
logger.info(transaction_format,
|
logger.info(transaction_format,
|
||||||
new Object[] { entry.getHeader().getLogfileName(),
|
new Object[] { entry.getHeader().getLogfileName(),
|
||||||
String.valueOf(entry.getHeader().getLogfileOffset()),
|
String.valueOf(entry.getHeader().getLogfileOffset()),
|
||||||
String.valueOf(entry.getHeader().getExecuteTime()), String.valueOf(delayTime) });
|
String.valueOf(entry.getHeader().getExecuteTime()), simpleDateFormat.format(date),
|
||||||
|
entry.getHeader().getGtid(), String.valueOf(delayTime) });
|
||||||
logger.info(" BEGIN ----> Thread id: {}", begin.getThreadId());
|
logger.info(" BEGIN ----> Thread id: {}", begin.getThreadId());
|
||||||
|
printXAInfo(begin.getPropsList());
|
||||||
} else if (entry.getEntryType() == EntryType.TRANSACTIONEND) {
|
} else if (entry.getEntryType() == EntryType.TRANSACTIONEND) {
|
||||||
TransactionEnd end = null;
|
TransactionEnd end = null;
|
||||||
try {
|
try {
|
||||||
@@ -190,10 +204,12 @@ public class AbstractCanalClientTest {
|
|||||||
// 打印事务提交信息,事务id
|
// 打印事务提交信息,事务id
|
||||||
logger.info("----------------\n");
|
logger.info("----------------\n");
|
||||||
logger.info(" END ----> transaction id: {}", end.getTransactionId());
|
logger.info(" END ----> transaction id: {}", end.getTransactionId());
|
||||||
|
printXAInfo(end.getPropsList());
|
||||||
logger.info(transaction_format,
|
logger.info(transaction_format,
|
||||||
new Object[] { entry.getHeader().getLogfileName(),
|
new Object[] { entry.getHeader().getLogfileName(),
|
||||||
String.valueOf(entry.getHeader().getLogfileOffset()),
|
String.valueOf(entry.getHeader().getLogfileOffset()),
|
||||||
String.valueOf(entry.getHeader().getExecuteTime()), String.valueOf(delayTime) });
|
String.valueOf(entry.getHeader().getExecuteTime()), simpleDateFormat.format(date),
|
||||||
|
entry.getHeader().getGtid(), String.valueOf(delayTime) });
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -213,13 +229,15 @@ public class AbstractCanalClientTest {
|
|||||||
new Object[] { entry.getHeader().getLogfileName(),
|
new Object[] { entry.getHeader().getLogfileName(),
|
||||||
String.valueOf(entry.getHeader().getLogfileOffset()), entry.getHeader().getSchemaName(),
|
String.valueOf(entry.getHeader().getLogfileOffset()), entry.getHeader().getSchemaName(),
|
||||||
entry.getHeader().getTableName(), eventType,
|
entry.getHeader().getTableName(), eventType,
|
||||||
String.valueOf(entry.getHeader().getExecuteTime()), String.valueOf(delayTime) });
|
String.valueOf(entry.getHeader().getExecuteTime()), simpleDateFormat.format(date),
|
||||||
|
entry.getHeader().getGtid(), String.valueOf(delayTime) });
|
||||||
|
|
||||||
if (eventType == EventType.QUERY || rowChage.getIsDdl()) {
|
if (eventType == EventType.QUERY || rowChage.getIsDdl()) {
|
||||||
logger.info(" sql ----> " + rowChage.getSql() + SEP);
|
logger.info(" sql ----> " + rowChage.getSql() + SEP);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printXAInfo(rowChage.getPropsList());
|
||||||
for (RowData rowData : rowChage.getRowDatasList()) {
|
for (RowData rowData : rowChage.getRowDatasList()) {
|
||||||
if (eventType == EventType.DELETE) {
|
if (eventType == EventType.DELETE) {
|
||||||
printColumn(rowData.getBeforeColumnsList());
|
printColumn(rowData.getBeforeColumnsList());
|
||||||
@@ -236,7 +254,17 @@ public class AbstractCanalClientTest {
|
|||||||
protected void printColumn(List<Column> columns) {
|
protected void printColumn(List<Column> columns) {
|
||||||
for (Column column : columns) {
|
for (Column column : columns) {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
builder.append(column.getName() + " : " + column.getValue());
|
try {
|
||||||
|
if (StringUtils.containsIgnoreCase(column.getMysqlType(), "BLOB")
|
||||||
|
|| StringUtils.containsIgnoreCase(column.getMysqlType(), "BINARY")) {
|
||||||
|
// get value bytes
|
||||||
|
builder.append(column.getName() + " : "
|
||||||
|
+ new String(column.getValue().getBytes("ISO-8859-1"), "UTF-8"));
|
||||||
|
} else {
|
||||||
|
builder.append(column.getName() + " : " + column.getValue());
|
||||||
|
}
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
}
|
||||||
builder.append(" type=" + column.getMysqlType());
|
builder.append(" type=" + column.getMysqlType());
|
||||||
if (column.getUpdated()) {
|
if (column.getUpdated()) {
|
||||||
builder.append(" update=" + column.getUpdated());
|
builder.append(" update=" + column.getUpdated());
|
||||||
@@ -246,6 +274,27 @@ public class AbstractCanalClientTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void printXAInfo(List<Pair> pairs) {
|
||||||
|
if (pairs == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String xaType = null;
|
||||||
|
String xaXid = null;
|
||||||
|
for (Pair pair : pairs) {
|
||||||
|
String key = pair.getKey();
|
||||||
|
if (StringUtils.endsWithIgnoreCase(key, "XA_TYPE")) {
|
||||||
|
xaType = pair.getValue();
|
||||||
|
} else if (StringUtils.endsWithIgnoreCase(key, "XA_XID")) {
|
||||||
|
xaXid = pair.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xaType != null && xaXid != null) {
|
||||||
|
logger.info(" ------> " + xaType + " " + xaXid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setConnector(CanalConnector connector) {
|
public void setConnector(CanalConnector connector) {
|
||||||
this.connector = connector;
|
this.connector = connector;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.alibaba.otter.canal.example;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.CanalConnector;
|
||||||
|
import com.alibaba.otter.canal.client.CanalConnectors;
|
||||||
|
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
public class SimpleCanalClientPermanceTest {
|
||||||
|
|
||||||
|
public static void main(String args[]) {
|
||||||
|
String destination = "example";
|
||||||
|
String ip = "127.0.0.1";
|
||||||
|
int batchSize = 1024;
|
||||||
|
int count = 0;
|
||||||
|
int sum = 0;
|
||||||
|
int perSum = 0;
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
long end = 0;
|
||||||
|
final ArrayBlockingQueue<Long> queue = new ArrayBlockingQueue<Long>(100);
|
||||||
|
try {
|
||||||
|
final CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(ip, 11111),
|
||||||
|
destination,
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
Thread ackThread = new Thread(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
long batchId = queue.take();
|
||||||
|
connector.ack(batchId);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ackThread.start();
|
||||||
|
|
||||||
|
((SimpleCanalConnector) connector).setLazyParseEntry(true);
|
||||||
|
connector.connect();
|
||||||
|
connector.subscribe();
|
||||||
|
while (true) {
|
||||||
|
Message message = connector.getWithoutAck(batchSize);
|
||||||
|
long batchId = message.getId();
|
||||||
|
int size = message.getRawEntries().size();
|
||||||
|
sum += size;
|
||||||
|
perSum += size;
|
||||||
|
count++;
|
||||||
|
queue.add(batchId);
|
||||||
|
if (count % 10 == 0) {
|
||||||
|
end = System.currentTimeMillis();
|
||||||
|
long tps = (perSum * 1000) / (end - start);
|
||||||
|
System.out.println(" total : " + sum + " , current : " + perSum + " , cost : " + (end - start)
|
||||||
|
+ " , tps : " + tps);
|
||||||
|
start = end;
|
||||||
|
perSum = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package com.alibaba.otter.canal.example.db;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class AbstractDbClient extends CanalConnectorClient {
|
||||||
|
|
||||||
|
|
||||||
|
public abstract void insert(CanalEntry.Header header, List<CanalEntry.Column> afterColumns);
|
||||||
|
|
||||||
|
public abstract void update(CanalEntry.Header header, List<CanalEntry.Column> afterColumns);
|
||||||
|
|
||||||
|
public abstract void delete(CanalEntry.Header header, List<CanalEntry.Column> beforeColumns);
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void start() {
|
||||||
|
if (running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void stop() {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.stop();
|
||||||
|
MDC.remove("destination");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processMessage(Message message) {
|
||||||
|
long batchId = message.getId();
|
||||||
|
//遍历每条消息
|
||||||
|
for (CanalEntry.Entry entry : message.getEntries()) {
|
||||||
|
session(entry);//no exception
|
||||||
|
}
|
||||||
|
//ack all the time。
|
||||||
|
connector.ack(batchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void session(CanalEntry.Entry entry) {
|
||||||
|
CanalEntry.EntryType entryType = entry.getEntryType();
|
||||||
|
int times = 0;
|
||||||
|
boolean success = false;
|
||||||
|
while (!success) {
|
||||||
|
if (times > 0) {
|
||||||
|
/**
|
||||||
|
* 1:retry,重试,重试默认为3次,由retryTimes参数决定,如果重试次数达到阈值,则跳过,并且记录日志。
|
||||||
|
* 2:ignore,直接忽略,不重试,记录日志。
|
||||||
|
*/
|
||||||
|
if (exceptionStrategy == ExceptionStrategy.RETRY.code) {
|
||||||
|
if (times >= retryTimes) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
switch (entryType) {
|
||||||
|
case TRANSACTIONBEGIN:
|
||||||
|
transactionBegin(entry);
|
||||||
|
break;
|
||||||
|
case TRANSACTIONEND:
|
||||||
|
transactionEnd(entry);
|
||||||
|
break;
|
||||||
|
case ROWDATA:
|
||||||
|
rowData(entry);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
success = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
times++;
|
||||||
|
logger.error("parse event has an error ,times: + " + times + ", data:" + entry.toString(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rowData(CanalEntry.Entry entry) throws Exception {
|
||||||
|
CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
|
||||||
|
CanalEntry.EventType eventType = rowChange.getEventType();
|
||||||
|
CanalEntry.Header header = entry.getHeader();
|
||||||
|
long executeTime = header.getExecuteTime();
|
||||||
|
long delayTime = new Date().getTime() - executeTime;
|
||||||
|
String sql = rowChange.getSql();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isDML(eventType) || rowChange.getIsDdl()) {
|
||||||
|
processDDL(header, eventType, sql);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//处理DML数据
|
||||||
|
processDML(header, eventType, rowChange, sql);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("process event error ,", e);
|
||||||
|
logger.error(rowFormat,
|
||||||
|
new Object[]{header.getLogfileName(), String.valueOf(header.getLogfileOffset()),
|
||||||
|
header.getSchemaName(), header.getTableName(), eventType,
|
||||||
|
String.valueOf(executeTime), String.valueOf(delayTime)});
|
||||||
|
throw e;//重新抛出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理 dml 数据
|
||||||
|
*
|
||||||
|
* @param header
|
||||||
|
* @param eventType
|
||||||
|
* @param rowChange
|
||||||
|
* @param sql
|
||||||
|
*/
|
||||||
|
protected void processDML(CanalEntry.Header header, CanalEntry.EventType eventType, CanalEntry.RowChange rowChange, String sql) {
|
||||||
|
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
|
||||||
|
switch (eventType) {
|
||||||
|
case DELETE:
|
||||||
|
delete(header, rowData.getBeforeColumnsList());
|
||||||
|
break;
|
||||||
|
case INSERT:
|
||||||
|
insert(header, rowData.getAfterColumnsList());
|
||||||
|
break;
|
||||||
|
case UPDATE:
|
||||||
|
update(header, rowData.getAfterColumnsList());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
whenOthers(header, sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,488 @@
|
|||||||
|
package com.alibaba.otter.canal.example.db;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.CanalConnector;
|
||||||
|
import com.alibaba.otter.canal.client.CanalConnectors;
|
||||||
|
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
import org.apache.commons.lang.SystemUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class CanalConnectorClient extends AbstractCanalLifeCycle implements InitializingBean {
|
||||||
|
|
||||||
|
protected static final Logger logger = LoggerFactory.getLogger(CanalConnectorClient.class);
|
||||||
|
protected static final String SEP = SystemUtils.LINE_SEPARATOR;
|
||||||
|
protected static String contextFormat;
|
||||||
|
protected static String rowFormat;
|
||||||
|
protected static String transactionFormat;
|
||||||
|
protected static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
|
||||||
|
static {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(SEP)
|
||||||
|
.append("-------------Batch-------------")
|
||||||
|
.append(SEP)
|
||||||
|
.append("* Batch Id: [{}] ,count : [{}] , Mem size : [{}] , Time : {}")
|
||||||
|
.append(SEP)
|
||||||
|
.append("* Start : [{}] ")
|
||||||
|
.append(SEP)
|
||||||
|
.append("* End : [{}] ")
|
||||||
|
.append(SEP)
|
||||||
|
.append("-------------------------------")
|
||||||
|
.append(SEP);
|
||||||
|
contextFormat = sb.toString();
|
||||||
|
|
||||||
|
sb = new StringBuilder();
|
||||||
|
sb.append(SEP)
|
||||||
|
.append("+++++++++++++Row+++++++++++++>>>")
|
||||||
|
.append("binlog[{}:{}] , name[{},{}] , eventType : {} , executeTime : {} , delay : {}ms")
|
||||||
|
.append(SEP);
|
||||||
|
rowFormat = sb.toString();
|
||||||
|
|
||||||
|
sb = new StringBuilder();
|
||||||
|
sb.append(SEP)
|
||||||
|
.append("===========Transaction {} : {}=======>>>")
|
||||||
|
.append("binlog[{}:{}] , executeTime : {} , delay : {}ms")
|
||||||
|
.append(SEP);
|
||||||
|
transactionFormat = sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String zkServers;//cluster
|
||||||
|
private String address;//single,ip:port
|
||||||
|
private String destination;
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
private int batchSize = 5 * 1024;
|
||||||
|
private String filter = "";//同canal filter,用于过滤database或者table的相关数据。
|
||||||
|
protected boolean debug = false;//开启debug,会把每条消息的详情打印
|
||||||
|
|
||||||
|
//1:retry,重试,重试默认为3次,由retryTimes参数决定,如果重试次数达到阈值,则跳过,并且记录日志。
|
||||||
|
//2:ignore,直接忽略,不重试,记录日志。
|
||||||
|
protected int exceptionStrategy = 1;
|
||||||
|
protected int retryTimes = 3;
|
||||||
|
protected int waitingTime = 100;//当binlog没有数据时,主线程等待的时间,单位ms,大于0
|
||||||
|
|
||||||
|
|
||||||
|
protected CanalConnector connector;
|
||||||
|
protected Thread thread;
|
||||||
|
|
||||||
|
protected Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() {
|
||||||
|
|
||||||
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
|
logger.error("process message has an error", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() {
|
||||||
|
if (waitingTime <= 0) {
|
||||||
|
throw new IllegalArgumentException("waitingTime must be greater than 0");
|
||||||
|
}
|
||||||
|
if (ExceptionStrategy.codeOf(exceptionStrategy) == null) {
|
||||||
|
throw new IllegalArgumentException("exceptionStrategy is not valid,1 or 2");
|
||||||
|
}
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
if (running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.start();
|
||||||
|
initConnector();
|
||||||
|
|
||||||
|
thread = new Thread(new Runnable() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
thread.setUncaughtExceptionHandler(handler);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.stop();
|
||||||
|
quietlyStop(thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void quietlyStop(Thread task) {
|
||||||
|
if (task != null) {
|
||||||
|
task.interrupt();
|
||||||
|
try {
|
||||||
|
task.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void process() {
|
||||||
|
int times = 0;
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
sleepWhenFailed(times);
|
||||||
|
//after block, should check the status of thread.
|
||||||
|
if (!running) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
MDC.put("destination", destination);
|
||||||
|
connector.connect();
|
||||||
|
connector.subscribe(filter);
|
||||||
|
connector.rollback();
|
||||||
|
times = 0;//reset;
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
// 获取指定数量的数据,不确认
|
||||||
|
Message message = connector.getWithoutAck(batchSize);
|
||||||
|
|
||||||
|
long batchId = message.getId();
|
||||||
|
int size = message.getEntries().size();
|
||||||
|
|
||||||
|
if (batchId == -1 || size == 0) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(waitingTime);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//logger
|
||||||
|
printBatch(message, batchId);
|
||||||
|
|
||||||
|
processMessage(message);
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("process error!", e);
|
||||||
|
if (times > 20) {
|
||||||
|
times = 0;
|
||||||
|
}
|
||||||
|
times++;
|
||||||
|
} finally {
|
||||||
|
connector.disconnect();
|
||||||
|
MDC.remove("destination");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void processMessage(Message message);
|
||||||
|
|
||||||
|
|
||||||
|
private void initConnector() {
|
||||||
|
if (zkServers != null && zkServers.length() > 0) {
|
||||||
|
connector = CanalConnectors.newClusterConnector(zkServers, destination, username, password);
|
||||||
|
} else if (address != null) {
|
||||||
|
String[] segments = address.split(":");
|
||||||
|
SocketAddress socketAddress = new InetSocketAddress(segments[0], Integer.valueOf(segments[1]));
|
||||||
|
connector = CanalConnectors.newSingleConnector(socketAddress, destination, username, password);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("zkServers or address cant be null at same time,you should specify one of them!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于控制当连接异常时,重试的策略,我们不应该每次都是立即重试,否则将可能导致大量的错误,在空转时导致CPU过高的问题
|
||||||
|
* sleep策略基于简单的累加
|
||||||
|
*
|
||||||
|
* @param times
|
||||||
|
*/
|
||||||
|
private void sleepWhenFailed(int times) {
|
||||||
|
if (times <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int sleepTime = 1000 + times * 100;//最大sleep 3s。
|
||||||
|
Thread.sleep(sleepTime);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印当前batch的摘要信息
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* @param batchId
|
||||||
|
*/
|
||||||
|
protected void printBatch(Message message, long batchId) {
|
||||||
|
if (!debug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<CanalEntry.Entry> entries = message.getEntries();
|
||||||
|
if (CollectionUtils.isEmpty(entries)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long memSize = 0;
|
||||||
|
for (CanalEntry.Entry entry : entries) {
|
||||||
|
memSize += entry.getHeader().getEventLength();
|
||||||
|
}
|
||||||
|
int size = entries.size();
|
||||||
|
String startPosition = buildPosition(entries.get(0));
|
||||||
|
String endPosition = buildPosition(message.getEntries().get(size - 1));
|
||||||
|
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
|
||||||
|
logger.info(contextFormat, new Object[]{batchId, size, memSize, format.format(new Date()), startPosition, endPosition});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String buildPosition(CanalEntry.Entry entry) {
|
||||||
|
CanalEntry.Header header = entry.getHeader();
|
||||||
|
long time = header.getExecuteTime();
|
||||||
|
Date date = new Date(time);
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(header.getLogfileName())
|
||||||
|
.append(":")
|
||||||
|
.append(header.getLogfileOffset())
|
||||||
|
.append(":")
|
||||||
|
.append(header.getExecuteTime())
|
||||||
|
.append("(")
|
||||||
|
.append(format.format(date))
|
||||||
|
.append(")");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default,only logging information
|
||||||
|
*
|
||||||
|
* @param entry
|
||||||
|
*/
|
||||||
|
protected void transactionBegin(CanalEntry.Entry entry) {
|
||||||
|
if (!debug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
CanalEntry.TransactionBegin begin = CanalEntry.TransactionBegin.parseFrom(entry.getStoreValue());
|
||||||
|
// 打印事务头信息,执行的线程id,事务耗时
|
||||||
|
CanalEntry.Header header = entry.getHeader();
|
||||||
|
long executeTime = header.getExecuteTime();
|
||||||
|
long delayTime = new Date().getTime() - executeTime;
|
||||||
|
logger.info(transactionFormat,
|
||||||
|
new Object[]{"begin", begin.getTransactionId(), header.getLogfileName(),
|
||||||
|
String.valueOf(header.getLogfileOffset()),
|
||||||
|
String.valueOf(header.getExecuteTime()), String.valueOf(delayTime)});
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("parse event has an error , data:" + entry.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void transactionEnd(CanalEntry.Entry entry) {
|
||||||
|
if (!debug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
CanalEntry.TransactionEnd end = CanalEntry.TransactionEnd.parseFrom(entry.getStoreValue());
|
||||||
|
// 打印事务提交信息,事务id
|
||||||
|
CanalEntry.Header header = entry.getHeader();
|
||||||
|
long executeTime = header.getExecuteTime();
|
||||||
|
long delayTime = new Date().getTime() - executeTime;
|
||||||
|
|
||||||
|
logger.info(transactionFormat,
|
||||||
|
new Object[]{"end", end.getTransactionId(), header.getLogfileName(),
|
||||||
|
String.valueOf(header.getLogfileOffset()),
|
||||||
|
String.valueOf(header.getExecuteTime()), String.valueOf(delayTime)});
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("parse event has an error , data:" + entry.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断事件类型为DML 数据
|
||||||
|
*
|
||||||
|
* @param eventType
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected boolean isDML(CanalEntry.EventType eventType) {
|
||||||
|
switch (eventType) {
|
||||||
|
case INSERT:
|
||||||
|
case UPDATE:
|
||||||
|
case DELETE:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理 DDL数据
|
||||||
|
*
|
||||||
|
* @param header
|
||||||
|
* @param eventType
|
||||||
|
* @param sql
|
||||||
|
*/
|
||||||
|
|
||||||
|
protected void processDDL(CanalEntry.Header header, CanalEntry.EventType eventType, String sql) {
|
||||||
|
if (!debug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String table = header.getSchemaName() + "." + header.getTableName();
|
||||||
|
//对于DDL,直接执行,因为没有行变更数据
|
||||||
|
switch (eventType) {
|
||||||
|
case CREATE:
|
||||||
|
logger.warn("parse create table event, table: {}, sql: {}", table, sql);
|
||||||
|
return;
|
||||||
|
case ALTER:
|
||||||
|
logger.warn("parse alter table event, table: {}, sql: {}", table, sql);
|
||||||
|
return;
|
||||||
|
case TRUNCATE:
|
||||||
|
logger.warn("parse truncate table event, table: {}, sql: {}", table, sql);
|
||||||
|
return;
|
||||||
|
case ERASE:
|
||||||
|
case QUERY:
|
||||||
|
logger.warn("parse event : {}, sql: {} . ignored!", eventType.name(), sql);
|
||||||
|
return;
|
||||||
|
case RENAME:
|
||||||
|
logger.warn("parse rename table event, table: {}, sql: {}", table, sql);
|
||||||
|
return;
|
||||||
|
case CINDEX:
|
||||||
|
logger.warn("parse create index event, table: {}, sql: {}", table, sql);
|
||||||
|
return;
|
||||||
|
case DINDEX:
|
||||||
|
logger.warn("parse delete index event, table: {}, sql: {}", table, sql);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
logger.warn("parse unknown event: {}, table: {}, sql: {}", new String[]{eventType.name(), table, sql});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强烈建议捕获异常,非上述已列出的其他操作,非核心
|
||||||
|
* 除了“insert”、“update”、“delete”操作之外的,其他类型的操作.
|
||||||
|
* 默认实现为“无操作”
|
||||||
|
*
|
||||||
|
* @param header 可以从header中获得schema、table的名称
|
||||||
|
* @param sql
|
||||||
|
*/
|
||||||
|
public void whenOthers(CanalEntry.Header header, String sql) {
|
||||||
|
String schema = header.getSchemaName();
|
||||||
|
String table = header.getTableName();
|
||||||
|
logger.error("ignore event,schema: {},table: {},SQL: {}", new String[]{schema, table, sql});
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ExceptionStrategy {
|
||||||
|
RETRY(1), IGNORE(2);
|
||||||
|
public int code;
|
||||||
|
|
||||||
|
ExceptionStrategy(int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ExceptionStrategy codeOf(Integer code) {
|
||||||
|
if (code != null) {
|
||||||
|
for (ExceptionStrategy e : ExceptionStrategy.values()) {
|
||||||
|
if (e.code == code) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getZkServers() {
|
||||||
|
return zkServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZkServers(String zkServers) {
|
||||||
|
this.zkServers = zkServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddress(String address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDestination() {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDestination(String destination) {
|
||||||
|
this.destination = destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBatchSize() {
|
||||||
|
return batchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBatchSize(int batchSize) {
|
||||||
|
this.batchSize = batchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFilter() {
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilter(String filter) {
|
||||||
|
this.filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDebug() {
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDebug(boolean debug) {
|
||||||
|
this.debug = debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getExceptionStrategy() {
|
||||||
|
return exceptionStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExceptionStrategy(int exceptionStrategy) {
|
||||||
|
this.exceptionStrategy = exceptionStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRetryTimes() {
|
||||||
|
return retryTimes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRetryTimes(int retryTimes) {
|
||||||
|
this.retryTimes = retryTimes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWaitingTime() {
|
||||||
|
return waitingTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWaitingTime(int waitingTime) {
|
||||||
|
this.waitingTime = waitingTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.alibaba.otter.canal.example.db;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.example.db.mysql.MysqlClient;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class MysqlLoadLauncher {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(MysqlLoadLauncher.class);
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
logger.info("## start the canal mysql client.");
|
||||||
|
final MysqlClient client = ServiceLocator.getMysqlClient();
|
||||||
|
logger.info("## the canal consumer is running now ......");
|
||||||
|
client.start();
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
logger.info("## stop the canal consumer");
|
||||||
|
client.stop();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("##something goes wrong when stopping canal consumer:\n{}", e);
|
||||||
|
} finally {
|
||||||
|
logger.info("## canal consumer is down.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("## Something goes wrong when starting up the canal consumer:\n{}", e);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package com.alibaba.otter.canal.example.db;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.context.ResourceLoaderAware;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扩展Spring的
|
||||||
|
* {@linkplain org.springframework.beans.factory.config.PropertyPlaceholderConfigurer}
|
||||||
|
* ,增加默认值的功能。 例如:${placeholder:defaultValue},假如placeholder的值不存在,则默认取得
|
||||||
|
* defaultValue。
|
||||||
|
*
|
||||||
|
* @author jianghang 2013-1-24 下午03:37:56
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class PropertyPlaceholderConfigurer extends org.springframework.beans.factory.config.PropertyPlaceholderConfigurer implements ResourceLoaderAware, InitializingBean {
|
||||||
|
|
||||||
|
private static final String PLACEHOLDER_PREFIX = "${";
|
||||||
|
private static final String PLACEHOLDER_SUFFIX = "}";
|
||||||
|
private ResourceLoader loader;
|
||||||
|
private String[] locationNames;
|
||||||
|
|
||||||
|
public PropertyPlaceholderConfigurer(){
|
||||||
|
setIgnoreUnresolvablePlaceholders(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceLoader(ResourceLoader loader) {
|
||||||
|
this.loader = loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocationNames(String[] locations) {
|
||||||
|
this.locationNames = locations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
Assert.notNull(loader, "no resourceLoader");
|
||||||
|
|
||||||
|
if (locationNames != null) {
|
||||||
|
for (int i = 0; i < locationNames.length; i++) {
|
||||||
|
locationNames[i] = resolveSystemPropertyPlaceholders(locationNames[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locationNames != null) {
|
||||||
|
List<Resource> resources = new ArrayList<Resource>(locationNames.length);
|
||||||
|
|
||||||
|
for (String location : locationNames) {
|
||||||
|
location = trimToNull(location);
|
||||||
|
|
||||||
|
if (location != null) {
|
||||||
|
resources.add(loader.getResource(location));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.setLocations(resources.toArray(new Resource[resources.size()]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveSystemPropertyPlaceholders(String text) {
|
||||||
|
StringBuilder buf = new StringBuilder(text);
|
||||||
|
|
||||||
|
for (int startIndex = buf.indexOf(PLACEHOLDER_PREFIX); startIndex >= 0;) {
|
||||||
|
int endIndex = buf.indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PREFIX.length());
|
||||||
|
|
||||||
|
if (endIndex != -1) {
|
||||||
|
String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
|
||||||
|
int nextIndex = endIndex + PLACEHOLDER_SUFFIX.length();
|
||||||
|
|
||||||
|
try {
|
||||||
|
String value = resolveSystemPropertyPlaceholder(placeholder);
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), value);
|
||||||
|
nextIndex = startIndex + value.length();
|
||||||
|
} else {
|
||||||
|
System.err.println("Could not resolve placeholder '"
|
||||||
|
+ placeholder
|
||||||
|
+ "' in ["
|
||||||
|
+ text
|
||||||
|
+ "] as system property: neither system property nor environment variable found");
|
||||||
|
}
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
System.err.println("Could not resolve placeholder '" + placeholder + "' in [" + text
|
||||||
|
+ "] as system property: " + ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
startIndex = buf.indexOf(PLACEHOLDER_PREFIX, nextIndex);
|
||||||
|
} else {
|
||||||
|
startIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveSystemPropertyPlaceholder(String placeholder) {
|
||||||
|
DefaultablePlaceholder dp = new DefaultablePlaceholder(placeholder);
|
||||||
|
String value = System.getProperty(dp.placeholder);
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
value = System.getenv(dp.placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
value = dp.defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) {
|
||||||
|
DefaultablePlaceholder dp = new DefaultablePlaceholder(placeholder);
|
||||||
|
String value = super.resolvePlaceholder(dp.placeholder, props, systemPropertiesMode);
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
value = dp.defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimToEmpty(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DefaultablePlaceholder {
|
||||||
|
|
||||||
|
private final String defaultValue;
|
||||||
|
private final String placeholder;
|
||||||
|
|
||||||
|
public DefaultablePlaceholder(String placeholder){
|
||||||
|
int commaIndex = placeholder.indexOf(":");
|
||||||
|
String defaultValue = null;
|
||||||
|
|
||||||
|
if (commaIndex >= 0) {
|
||||||
|
defaultValue = trimToEmpty(placeholder.substring(commaIndex + 1));
|
||||||
|
placeholder = trimToEmpty(placeholder.substring(0, commaIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.placeholder = placeholder;
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trimToNull(String str) {
|
||||||
|
if (str == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = str.trim();
|
||||||
|
|
||||||
|
if (result == null || result.length() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String trimToEmpty(String str) {
|
||||||
|
if (str == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.alibaba.otter.canal.example.db;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.example.db.mysql.MysqlClient;
|
||||||
|
import org.springframework.beans.factory.DisposableBean;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
public class ServiceLocator implements DisposableBean {
|
||||||
|
|
||||||
|
private static ApplicationContext applicationContext = null;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
applicationContext = new ClassPathXmlApplicationContext("classpath:client-spring.xml");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> T getBean(String name) {
|
||||||
|
assertContextInjected();
|
||||||
|
return (T) applicationContext.getBean(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void clearHolder() {
|
||||||
|
ServiceLocator.applicationContext = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() throws Exception {
|
||||||
|
ServiceLocator.clearHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertContextInjected() {
|
||||||
|
Assert.state(applicationContext != null, "ApplicationContext not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static MysqlClient getMysqlClient() {
|
||||||
|
return getBean("mysqlClient");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010-2101 Alibaba Group Holding Limited.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.alibaba.otter.canal.example.db.dialect;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.example.db.utils.DdlUtils;
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.collect.MigrateMap;
|
||||||
|
import org.apache.commons.lang.exception.NestableRuntimeException;
|
||||||
|
import org.apache.ddlutils.model.Table;
|
||||||
|
import org.springframework.dao.DataAccessException;
|
||||||
|
import org.springframework.jdbc.core.ConnectionCallback;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||||
|
import org.springframework.jdbc.support.lob.LobHandler;
|
||||||
|
import org.springframework.transaction.TransactionDefinition;
|
||||||
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DatabaseMetaData;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public abstract class AbstractDbDialect implements DbDialect {
|
||||||
|
|
||||||
|
protected int databaseMajorVersion;
|
||||||
|
protected int databaseMinorVersion;
|
||||||
|
protected String databaseName;
|
||||||
|
protected JdbcTemplate jdbcTemplate;
|
||||||
|
protected TransactionTemplate transactionTemplate;
|
||||||
|
protected LobHandler lobHandler;
|
||||||
|
protected Map<List<String>, Table> tables;
|
||||||
|
|
||||||
|
public AbstractDbDialect(final JdbcTemplate jdbcTemplate, LobHandler lobHandler) {
|
||||||
|
this.jdbcTemplate = jdbcTemplate;
|
||||||
|
this.lobHandler = lobHandler;
|
||||||
|
// 初始化transction
|
||||||
|
this.transactionTemplate = new TransactionTemplate();
|
||||||
|
transactionTemplate.setTransactionManager(new DataSourceTransactionManager(jdbcTemplate.getDataSource()));
|
||||||
|
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||||
|
|
||||||
|
// 初始化一些数据
|
||||||
|
jdbcTemplate.execute(new ConnectionCallback() {
|
||||||
|
|
||||||
|
public Object doInConnection(Connection c) throws SQLException, DataAccessException {
|
||||||
|
DatabaseMetaData meta = c.getMetaData();
|
||||||
|
databaseName = meta.getDatabaseProductName();
|
||||||
|
databaseMajorVersion = meta.getDatabaseMajorVersion();
|
||||||
|
databaseMinorVersion = meta.getDatabaseMinorVersion();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
initTables(jdbcTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Table findTable(String schema, String table, boolean useCache) {
|
||||||
|
List<String> key = Arrays.asList(schema, table);
|
||||||
|
if (useCache == false) {
|
||||||
|
tables.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tables.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Table findTable(String schema, String table) {
|
||||||
|
return findTable(schema, table, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LobHandler getLobHandler() {
|
||||||
|
return lobHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JdbcTemplate getJdbcTemplate() {
|
||||||
|
return jdbcTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransactionTemplate getTransactionTemplate() {
|
||||||
|
return transactionTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initTables(final JdbcTemplate jdbcTemplate) {
|
||||||
|
this.tables = MigrateMap.makeComputingMap(new Function<List<String>, Table>() {
|
||||||
|
|
||||||
|
public Table apply(List<String> names) {
|
||||||
|
Assert.isTrue(names.size() == 2);
|
||||||
|
try {
|
||||||
|
Table table = DdlUtils.findTable(jdbcTemplate, names.get(0), names.get(0), names.get(1));
|
||||||
|
if (table == null) {
|
||||||
|
throw new NestableRuntimeException("no found table [" + names.get(0) + "." + names.get(1)
|
||||||
|
+ "] , pls check");
|
||||||
|
} else {
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new NestableRuntimeException("find table [" + names.get(0) + "." + names.get(1) + "] error",
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010-2101 Alibaba Group Holding Limited.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.alibaba.otter.canal.example.db.dialect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认的基于标准SQL实现的CRUD sql封装
|
||||||
|
*
|
||||||
|
* @author jianghang 2011-10-27 下午01:37:00
|
||||||
|
* @version 4.0.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractSqlTemplate implements SqlTemplate {
|
||||||
|
|
||||||
|
private static final String DOT = ".";
|
||||||
|
|
||||||
|
public String getSelectSql(String schemaName, String tableName, String[] pkNames, String[] columnNames) {
|
||||||
|
StringBuilder sql = new StringBuilder("select ");
|
||||||
|
int size = columnNames.length;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
sql.append(appendEscape(columnNames[i])).append((i + 1 < size) ? " , " : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
sql.append(" from ").append(getFullName(schemaName, tableName)).append(" where ( ");
|
||||||
|
appendColumnEquals(sql, pkNames, "and");
|
||||||
|
sql.append(" ) ");
|
||||||
|
return sql.toString().intern();// 不使用intern,避免方法区内存消耗过多
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUpdateSql(String schemaName, String tableName, String[] pkNames, String[] columnNames) {
|
||||||
|
StringBuilder sql = new StringBuilder("update " + getFullName(schemaName, tableName) + " set ");
|
||||||
|
appendColumnEquals(sql, columnNames, ",");
|
||||||
|
sql.append(" where (");
|
||||||
|
appendColumnEquals(sql, pkNames, "and");
|
||||||
|
sql.append(")");
|
||||||
|
return sql.toString().intern(); // 不使用intern,避免方法区内存消耗过多
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInsertSql(String schemaName, String tableName, String[] pkNames, String[] columnNames) {
|
||||||
|
StringBuilder sql = new StringBuilder("insert into " + getFullName(schemaName, tableName) + "(");
|
||||||
|
String[] allColumns = new String[pkNames.length + columnNames.length];
|
||||||
|
System.arraycopy(columnNames, 0, allColumns, 0, columnNames.length);
|
||||||
|
System.arraycopy(pkNames, 0, allColumns, columnNames.length, pkNames.length);
|
||||||
|
|
||||||
|
int size = allColumns.length;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
sql.append(appendEscape(allColumns[i])).append((i + 1 < size) ? "," : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
sql.append(") values (");
|
||||||
|
appendColumnQuestions(sql, allColumns);
|
||||||
|
sql.append(")");
|
||||||
|
return sql.toString().intern();// intern优化,避免出现大量相同的字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeleteSql(String schemaName, String tableName, String[] pkNames) {
|
||||||
|
StringBuilder sql = new StringBuilder("delete from " + getFullName(schemaName, tableName) + " where ");
|
||||||
|
appendColumnEquals(sql, pkNames, "and");
|
||||||
|
return sql.toString().intern();// intern优化,避免出现大量相同的字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getFullName(String schemaName, String tableName) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (schemaName != null) {
|
||||||
|
sb.append(appendEscape(schemaName)).append(DOT);
|
||||||
|
}
|
||||||
|
sb.append(appendEscape(tableName));
|
||||||
|
return sb.toString().intern();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================ helper method ============
|
||||||
|
|
||||||
|
protected String appendEscape(String columnName) {
|
||||||
|
return columnName;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void appendColumnQuestions(StringBuilder sql, String[] columns) {
|
||||||
|
int size = columns.length;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
sql.append("?").append((i + 1 < size) ? " , " : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void appendColumnEquals(StringBuilder sql, String[] columns, String separator) {
|
||||||
|
int size = columns.length;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
sql.append(" ").append(appendEscape(columns[i])).append(" = ").append("? ");
|
||||||
|
if (i != size - 1) {
|
||||||
|
sql.append(separator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.alibaba.otter.canal.example.db.dialect;
|
||||||
|
|
||||||
|
import org.apache.ddlutils.model.Table;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.support.lob.LobHandler;
|
||||||
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
|
public interface DbDialect {
|
||||||
|
|
||||||
|
LobHandler getLobHandler();
|
||||||
|
|
||||||
|
JdbcTemplate getJdbcTemplate();
|
||||||
|
|
||||||
|
TransactionTemplate getTransactionTemplate();
|
||||||
|
|
||||||
|
Table findTable(String schema, String table);
|
||||||
|
|
||||||
|
Table findTable(String schema, String table, boolean useCache);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010-2101 Alibaba Group Holding Limited.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.alibaba.otter.canal.example.db.dialect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sql构造模板操作
|
||||||
|
*
|
||||||
|
* @author jianghang 2011-10-27 下午01:31:15
|
||||||
|
* @version 4.0.0
|
||||||
|
*/
|
||||||
|
public interface SqlTemplate {
|
||||||
|
|
||||||
|
public String getSelectSql(String schemaName, String tableName, String[] pkNames, String[] columnNames);
|
||||||
|
|
||||||
|
public String getUpdateSql(String schemaName, String tableName, String[] pkNames, String[] columnNames);
|
||||||
|
|
||||||
|
public String getDeleteSql(String schemaName, String tableName, String[] pkNames);
|
||||||
|
|
||||||
|
public String getInsertSql(String schemaName, String tableName, String[] pkNames, String[] columnNames);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取对应的mergeSql
|
||||||
|
*/
|
||||||
|
public String getMergeSql(String schemaName, String tableName, String[] pkNames, String[] columnNames,
|
||||||
|
String[] viewColumnNames, boolean updatePks);
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package com.alibaba.otter.canal.example.db.dialect;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enumeration wrapper around JDBC table types.
|
||||||
|
*/
|
||||||
|
public enum TableType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unknown
|
||||||
|
*/
|
||||||
|
unknown,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System table
|
||||||
|
*/
|
||||||
|
system_table,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global temporary
|
||||||
|
*/
|
||||||
|
global_temporary,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local temporary
|
||||||
|
*/
|
||||||
|
local_temporary,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table
|
||||||
|
*/
|
||||||
|
table,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View
|
||||||
|
*/
|
||||||
|
view,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias
|
||||||
|
*/
|
||||||
|
alias,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synonym
|
||||||
|
*/
|
||||||
|
synonym,;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an array of table types to an array of their corresponding string values.
|
||||||
|
*
|
||||||
|
* @param tableTypes Array of table types
|
||||||
|
* @return Array of string table types
|
||||||
|
*/
|
||||||
|
public static String[] toStrings(final TableType[] tableTypes) {
|
||||||
|
if ((tableTypes == null) || (tableTypes.length == 0)) {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> tableTypeStrings = new ArrayList<String>(tableTypes.length);
|
||||||
|
|
||||||
|
for (final TableType tableType : tableTypes) {
|
||||||
|
if (tableType != null) {
|
||||||
|
tableTypeStrings.add(tableType.toString().toUpperCase(Locale.ENGLISH));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableTypeStrings.toArray(new String[tableTypeStrings.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an array of string table types to an array of their corresponding enumeration values.
|
||||||
|
*
|
||||||
|
* @param tableTypeStrings Array of string table types
|
||||||
|
* @return Array of table types
|
||||||
|
*/
|
||||||
|
public static TableType[] valueOf(final String[] tableTypeStrings) {
|
||||||
|
if ((tableTypeStrings == null) || (tableTypeStrings.length == 0)) {
|
||||||
|
return new TableType[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<TableType> tableTypes = new ArrayList<TableType>(tableTypeStrings.length);
|
||||||
|
|
||||||
|
for (final String tableTypeString : tableTypeStrings) {
|
||||||
|
tableTypes.add(valueOf(tableTypeString.toLowerCase(Locale.ENGLISH)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableTypes.toArray(new TableType[tableTypes.size()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010-2101 Alibaba Group Holding Limited.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.alibaba.otter.canal.example.db.dialect.mysql;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.example.db.dialect.AbstractDbDialect;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.support.lob.LobHandler;
|
||||||
|
|
||||||
|
public class MysqlDialect extends AbstractDbDialect {
|
||||||
|
|
||||||
|
public MysqlDialect(JdbcTemplate jdbcTemplate, LobHandler lobHandler) {
|
||||||
|
super(jdbcTemplate, lobHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmptyStringNulled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010-2101 Alibaba Group Holding Limited.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.alibaba.otter.canal.example.db.dialect.mysql;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.example.db.dialect.AbstractSqlTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mysql sql生成模板
|
||||||
|
*
|
||||||
|
* @author jianghang 2011-10-27 下午01:41:20
|
||||||
|
* @version 4.0.0
|
||||||
|
*/
|
||||||
|
public class MysqlSqlTemplate extends AbstractSqlTemplate {
|
||||||
|
|
||||||
|
private static final String ESCAPE = "`";
|
||||||
|
|
||||||
|
public String getMergeSql(String schemaName, String tableName, String[] pkNames, String[] columnNames,
|
||||||
|
String[] viewColumnNames, boolean includePks) {
|
||||||
|
StringBuilder sql = new StringBuilder("insert into " + getFullName(schemaName, tableName) + "(");
|
||||||
|
int size = columnNames.length;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
sql.append(appendEscape(columnNames[i])).append(" , ");
|
||||||
|
}
|
||||||
|
size = pkNames.length;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
sql.append(appendEscape(pkNames[i])).append((i + 1 < size) ? " , " : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
sql.append(") values (");
|
||||||
|
size = columnNames.length;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
sql.append("?").append(" , ");
|
||||||
|
}
|
||||||
|
size = pkNames.length;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
sql.append("?").append((i + 1 < size) ? " , " : "");
|
||||||
|
}
|
||||||
|
sql.append(")");
|
||||||
|
sql.append(" on duplicate key update ");
|
||||||
|
|
||||||
|
size = columnNames.length;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
sql.append(appendEscape(columnNames[i]))
|
||||||
|
.append("=values(")
|
||||||
|
.append(appendEscape(columnNames[i]))
|
||||||
|
.append(")");
|
||||||
|
if (includePks) {
|
||||||
|
sql.append(" , ");
|
||||||
|
} else {
|
||||||
|
sql.append((i + 1 < size) ? " , " : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includePks) {
|
||||||
|
// mysql merge sql匹配了uniqe / primary key时都会执行update,所以需要更新pk信息
|
||||||
|
size = pkNames.length;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
sql.append(appendEscape(pkNames[i])).append("=values(").append(appendEscape(pkNames[i])).append(")");
|
||||||
|
sql.append((i + 1 < size) ? " , " : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sql.toString().intern();// intern优化,避免出现大量相同的字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String appendEscape(String columnName) {
|
||||||
|
return ESCAPE + columnName + ESCAPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package com.alibaba.otter.canal.example.db.mysql;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.otter.canal.example.db.AbstractDbClient;
|
||||||
|
import com.alibaba.otter.canal.example.db.dialect.DbDialect;
|
||||||
|
import com.alibaba.otter.canal.example.db.dialect.mysql.MysqlDialect;
|
||||||
|
import com.alibaba.otter.canal.example.db.dialect.mysql.MysqlSqlTemplate;
|
||||||
|
import com.alibaba.otter.canal.example.db.dialect.SqlTemplate;
|
||||||
|
import com.alibaba.otter.canal.example.db.utils.SqlUtils;
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
|
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.ddlutils.model.Column;
|
||||||
|
import org.apache.ddlutils.model.Table;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.core.PreparedStatementSetter;
|
||||||
|
import org.springframework.jdbc.core.StatementCreatorUtils;
|
||||||
|
import org.springframework.jdbc.support.lob.DefaultLobHandler;
|
||||||
|
import org.springframework.jdbc.support.lob.LobCreator;
|
||||||
|
import org.springframework.transaction.TransactionStatus;
|
||||||
|
import org.springframework.transaction.support.TransactionCallback;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Types;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class AbstractMysqlClient extends AbstractDbClient {
|
||||||
|
|
||||||
|
private DataSource dataSource;
|
||||||
|
|
||||||
|
private DbDialect dbDialect;
|
||||||
|
private SqlTemplate sqlTemplate;
|
||||||
|
|
||||||
|
protected Integer execute(final CanalEntry.Header header, final List<CanalEntry.Column> columns) {
|
||||||
|
final String sql = getSql(header, columns);
|
||||||
|
final LobCreator lobCreator = dbDialect.getLobHandler().getLobCreator();
|
||||||
|
dbDialect.getTransactionTemplate().execute(new TransactionCallback() {
|
||||||
|
|
||||||
|
public Object doInTransaction(TransactionStatus status) {
|
||||||
|
try {
|
||||||
|
JdbcTemplate template = dbDialect.getJdbcTemplate();
|
||||||
|
int affect = template.update(sql, new PreparedStatementSetter() {
|
||||||
|
|
||||||
|
public void setValues(PreparedStatement ps) throws SQLException {
|
||||||
|
doPreparedStatement(ps, dbDialect, lobCreator, header, columns);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return affect;
|
||||||
|
} finally {
|
||||||
|
lobCreator.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSql(CanalEntry.Header header, List<CanalEntry.Column> columns) {
|
||||||
|
List<String> pkNames = new ArrayList<>();
|
||||||
|
List<String> colNames = new ArrayList<>();
|
||||||
|
for (CanalEntry.Column column : columns) {
|
||||||
|
if (column.getIsKey()) {
|
||||||
|
pkNames.add(column.getName());
|
||||||
|
} else {
|
||||||
|
colNames.add(column.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String sql = "";
|
||||||
|
CanalEntry.EventType eventType = header.getEventType();
|
||||||
|
switch (eventType) {
|
||||||
|
case INSERT:
|
||||||
|
sql = sqlTemplate.getInsertSql(header.getSchemaName(), header.getTableName(), pkNames.toArray(new String[]{}), colNames.toArray(new String[]{}));
|
||||||
|
break;
|
||||||
|
case UPDATE:
|
||||||
|
sql = sqlTemplate.getUpdateSql(header.getSchemaName(), header.getTableName(), pkNames.toArray(new String[]{}), colNames.toArray(new String[]{}));
|
||||||
|
break;
|
||||||
|
case DELETE:
|
||||||
|
sql = sqlTemplate.getDeleteSql(header.getSchemaName(), header.getTableName(), pkNames.toArray(new String[]{}));
|
||||||
|
}
|
||||||
|
logger.info("Execute sql: {}", sql);
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doPreparedStatement(PreparedStatement ps, DbDialect dbDialect, LobCreator lobCreator,
|
||||||
|
CanalEntry.Header header, List<CanalEntry.Column> columns) throws SQLException {
|
||||||
|
|
||||||
|
List<CanalEntry.Column> rebuildColumns = new ArrayList<>(columns.size());
|
||||||
|
|
||||||
|
List<CanalEntry.Column> keyColumns = new ArrayList<>(columns.size());
|
||||||
|
List<CanalEntry.Column> notKeyColumns = new ArrayList<>(columns.size());
|
||||||
|
for (CanalEntry.Column column : columns) {
|
||||||
|
if (column.getIsKey()) {
|
||||||
|
keyColumns.add(column);
|
||||||
|
} else {
|
||||||
|
notKeyColumns.add(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CanalEntry.EventType eventType = header.getEventType();
|
||||||
|
switch (eventType) {
|
||||||
|
case INSERT:
|
||||||
|
case UPDATE:
|
||||||
|
// insert/update语句对应的字段数序都是将主键排在后面
|
||||||
|
rebuildColumns.addAll(notKeyColumns);
|
||||||
|
rebuildColumns.addAll(keyColumns);
|
||||||
|
break;
|
||||||
|
case DELETE:
|
||||||
|
rebuildColumns.addAll(keyColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取一下当前字段名的数据是否必填
|
||||||
|
Table table = dbDialect.findTable(header.getSchemaName(), header.getTableName());
|
||||||
|
Map<String, Boolean> isRequiredMap = new HashMap();
|
||||||
|
for (Column tableColumn : table.getColumns()) {
|
||||||
|
isRequiredMap.put(StringUtils.lowerCase(tableColumn.getName()), tableColumn.isRequired());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Object> values = new ArrayList<>(rebuildColumns.size());
|
||||||
|
for (int i = 0; i < rebuildColumns.size(); i++) {
|
||||||
|
int paramIndex = i + 1;
|
||||||
|
CanalEntry.Column column = rebuildColumns.get(i);
|
||||||
|
int sqlType = column.getSqlType();
|
||||||
|
|
||||||
|
Boolean isRequired = isRequiredMap.get(StringUtils.lowerCase(column.getName()));
|
||||||
|
if (isRequired == null) {
|
||||||
|
// 清理一下目标库的表结构,二次检查一下
|
||||||
|
table = dbDialect.findTable(header.getSchemaName(), header.getTableName());
|
||||||
|
|
||||||
|
isRequiredMap = new HashMap<>();
|
||||||
|
for (Column tableColumn : table.getColumns()) {
|
||||||
|
isRequiredMap.put(StringUtils.lowerCase(tableColumn.getName()), tableColumn.isRequired());
|
||||||
|
}
|
||||||
|
|
||||||
|
isRequired = isRequiredMap.get(StringUtils.lowerCase(column.getName()));
|
||||||
|
if (isRequired == null) {
|
||||||
|
throw new CanalClientException(String.format("column name %s is not found in Table[%s]",
|
||||||
|
column.getName(),
|
||||||
|
table.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object param;
|
||||||
|
if (sqlType == Types.TIME || sqlType == Types.TIMESTAMP || sqlType == Types.DATE) {
|
||||||
|
// 解决mysql的0000-00-00 00:00:00问题,直接依赖mysql
|
||||||
|
// driver进行处理,如果转化为Timestamp会出错
|
||||||
|
param = column.getValue();
|
||||||
|
if (param instanceof String && StringUtils.isEmpty(String.valueOf(param))) {
|
||||||
|
param = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
param = SqlUtils.stringToSqlValue(column.getValue(),
|
||||||
|
sqlType,
|
||||||
|
isRequired,
|
||||||
|
column.getIsNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (sqlType) {
|
||||||
|
case Types.CLOB:
|
||||||
|
lobCreator.setClobAsString(ps, paramIndex, (String) param);
|
||||||
|
break;
|
||||||
|
case Types.BLOB:
|
||||||
|
lobCreator.setBlobAsBytes(ps, paramIndex, (byte[]) param);
|
||||||
|
break;
|
||||||
|
case Types.TIME:
|
||||||
|
case Types.TIMESTAMP:
|
||||||
|
case Types.DATE:
|
||||||
|
ps.setObject(paramIndex, param);
|
||||||
|
break;
|
||||||
|
case Types.BIT:
|
||||||
|
StatementCreatorUtils.setParameterValue(ps, paramIndex, Types.DECIMAL, null, param);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
StatementCreatorUtils.setParameterValue(ps, paramIndex, sqlType, null, param);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
values.add(param);
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
logger.error("## SetParam error , [sqltype={}, value={}]",
|
||||||
|
new Object[]{sqlType, param});
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info("## sql values: {}", JSON.toJSONString(values));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() {
|
||||||
|
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||||
|
DefaultLobHandler lobHandler = new DefaultLobHandler();
|
||||||
|
lobHandler.setStreamAsLob(true);
|
||||||
|
dbDialect = new MysqlDialect(jdbcTemplate, lobHandler);
|
||||||
|
sqlTemplate = new MysqlSqlTemplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataSource getDataSource() {
|
||||||
|
return dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDataSource(DataSource dataSource) {
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.alibaba.otter.canal.example.db.mysql;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MysqlClient extends AbstractMysqlClient {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insert(CanalEntry.Header header, List<CanalEntry.Column> afterColumns) {
|
||||||
|
execute(header, afterColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(CanalEntry.Header header, List<CanalEntry.Column> afterColumns) {
|
||||||
|
execute(header, afterColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(CanalEntry.Header header, List<CanalEntry.Column> beforeColumns) {
|
||||||
|
execute(header, beforeColumns);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.alibaba.otter.canal.example.db.utils;
|
||||||
|
|
||||||
|
import org.apache.commons.beanutils.ConversionException;
|
||||||
|
import org.apache.commons.beanutils.Converter;
|
||||||
|
import org.apache.commons.beanutils.converters.ArrayConverter;
|
||||||
|
import org.apache.commons.beanutils.converters.ByteConverter;
|
||||||
|
|
||||||
|
public class ByteArrayConverter implements Converter {
|
||||||
|
|
||||||
|
public static final Converter SQL_BYTES = new ByteArrayConverter(null);
|
||||||
|
private static final Converter converter = new ArrayConverter(byte[].class, new ByteConverter());
|
||||||
|
|
||||||
|
protected final Object defaultValue;
|
||||||
|
protected final boolean useDefault;
|
||||||
|
|
||||||
|
public ByteArrayConverter() {
|
||||||
|
this.defaultValue = null;
|
||||||
|
this.useDefault = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteArrayConverter(Object defaultValue) {
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
this.useDefault = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object convert(Class type, Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
if (useDefault) {
|
||||||
|
return (defaultValue);
|
||||||
|
} else {
|
||||||
|
throw new ConversionException("No value specified");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof byte[]) {
|
||||||
|
return (value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BLOB类型,canal直接存储为String("ISO-8859-1")
|
||||||
|
if (value instanceof String) {
|
||||||
|
try {
|
||||||
|
return ((String) value).getBytes("ISO-8859-1");
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ConversionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return converter.convert(type, value); // byteConvertor进行转化
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
package com.alibaba.otter.canal.example.db.utils;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.example.db.dialect.TableType;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||||
|
import org.apache.commons.lang.builder.ToStringStyle;
|
||||||
|
import org.apache.commons.lang.math.NumberUtils;
|
||||||
|
import org.apache.ddlutils.model.Column;
|
||||||
|
import org.apache.ddlutils.model.Table;
|
||||||
|
import org.apache.ddlutils.platform.DatabaseMetaDataWrapper;
|
||||||
|
import org.apache.ddlutils.platform.MetaDataColumnDescriptor;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.dao.DataAccessException;
|
||||||
|
import org.springframework.jdbc.core.ConnectionCallback;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.support.JdbcUtils;
|
||||||
|
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
|
public class DdlUtils {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(DdlUtils.class);
|
||||||
|
private static TableType[] SUPPORTED_TABLE_TYPES = new TableType[]{TableType.view, TableType.table};
|
||||||
|
private final static Map<Integer, String> _defaultSizes = new HashMap<Integer, String>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
_defaultSizes.put(new Integer(1), "254");
|
||||||
|
_defaultSizes.put(new Integer(12), "254");
|
||||||
|
_defaultSizes.put(new Integer(-1), "254");
|
||||||
|
_defaultSizes.put(new Integer(-2), "254");
|
||||||
|
_defaultSizes.put(new Integer(-3), "254");
|
||||||
|
_defaultSizes.put(new Integer(-4), "254");
|
||||||
|
_defaultSizes.put(new Integer(4), "32");
|
||||||
|
_defaultSizes.put(new Integer(-5), "64");
|
||||||
|
_defaultSizes.put(new Integer(7), "7,0");
|
||||||
|
_defaultSizes.put(new Integer(6), "15,0");
|
||||||
|
_defaultSizes.put(new Integer(8), "15,0");
|
||||||
|
_defaultSizes.put(new Integer(3), "15,15");
|
||||||
|
_defaultSizes.put(new Integer(2), "15,15");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Table findTable(final JdbcTemplate jdbcTemplate, final String catalogName, final String schemaName,
|
||||||
|
final String tableName) {
|
||||||
|
return (Table) jdbcTemplate.execute(new ConnectionCallback() {
|
||||||
|
|
||||||
|
public Object doInConnection(Connection con) throws SQLException, DataAccessException {
|
||||||
|
Table table = null;
|
||||||
|
DatabaseMetaDataWrapper metaData = new DatabaseMetaDataWrapper();
|
||||||
|
try {
|
||||||
|
|
||||||
|
DatabaseMetaData databaseMetaData = con.getMetaData();
|
||||||
|
|
||||||
|
metaData.setMetaData(databaseMetaData);
|
||||||
|
metaData.setTableTypes(TableType.toStrings(SUPPORTED_TABLE_TYPES));
|
||||||
|
metaData.setCatalog(catalogName);
|
||||||
|
metaData.setSchemaPattern(schemaName);
|
||||||
|
|
||||||
|
String convertTableName = tableName;
|
||||||
|
if (databaseMetaData.storesUpperCaseIdentifiers()) {
|
||||||
|
metaData.setCatalog(catalogName.toUpperCase());
|
||||||
|
metaData.setSchemaPattern(schemaName.toUpperCase());
|
||||||
|
convertTableName = tableName.toUpperCase();
|
||||||
|
}
|
||||||
|
if (databaseMetaData.storesLowerCaseIdentifiers()) {
|
||||||
|
metaData.setCatalog(catalogName.toLowerCase());
|
||||||
|
metaData.setSchemaPattern(schemaName.toLowerCase());
|
||||||
|
convertTableName = tableName.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultSet tableData = null;
|
||||||
|
try {
|
||||||
|
tableData = metaData.getTables(convertTableName);
|
||||||
|
|
||||||
|
while ((tableData != null) && tableData.next()) {
|
||||||
|
Map<String, Object> values = readColumns(tableData, initColumnsForTable());
|
||||||
|
|
||||||
|
table = readTable(metaData, values);
|
||||||
|
if (table.getName().equalsIgnoreCase(tableName)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
JdbcUtils.closeResultSet(tableData);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
makeAllColumnsPrimaryKeysIfNoPrimaryKeysFound(table);
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Treat tables with no primary keys as a table with all primary keys.
|
||||||
|
*/
|
||||||
|
private static void makeAllColumnsPrimaryKeysIfNoPrimaryKeysFound(Table table) {
|
||||||
|
if ((table != null) && (table.getPrimaryKeyColumns() != null) && (table.getPrimaryKeyColumns().length == 0)) {
|
||||||
|
Column[] allCoumns = table.getColumns();
|
||||||
|
|
||||||
|
for (Column column : allCoumns) {
|
||||||
|
column.setPrimaryKey(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Table readTable(DatabaseMetaDataWrapper metaData, Map<String, Object> values) throws SQLException {
|
||||||
|
String tableName = (String) values.get("TABLE_NAME");
|
||||||
|
Table table = null;
|
||||||
|
|
||||||
|
if ((tableName != null) && (tableName.length() > 0)) {
|
||||||
|
table = new Table();
|
||||||
|
table.setName(tableName);
|
||||||
|
table.setType((String) values.get("TABLE_TYPE"));
|
||||||
|
table.setCatalog((String) values.get("TABLE_CAT"));
|
||||||
|
table.setSchema((String) values.get("TABLE_SCHEM"));
|
||||||
|
table.setDescription((String) values.get("REMARKS"));
|
||||||
|
table.addColumns(readColumns(metaData, tableName));
|
||||||
|
|
||||||
|
Collection<String> primaryKeys = readPrimaryKeyNames(metaData, tableName);
|
||||||
|
|
||||||
|
for (Object key : primaryKeys) {
|
||||||
|
Column col = table.findColumn((String) key, true);
|
||||||
|
|
||||||
|
if (col != null) {
|
||||||
|
col.setPrimaryKey(true);
|
||||||
|
} else {
|
||||||
|
throw new NullPointerException(String.format("%s pk %s is null - %s %s",
|
||||||
|
tableName,
|
||||||
|
key,
|
||||||
|
ToStringBuilder.reflectionToString(metaData, ToStringStyle.SIMPLE_STYLE),
|
||||||
|
ToStringBuilder.reflectionToString(values, ToStringStyle.SIMPLE_STYLE)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<MetaDataColumnDescriptor> initColumnsForTable() {
|
||||||
|
List<MetaDataColumnDescriptor> result = new ArrayList<MetaDataColumnDescriptor>();
|
||||||
|
|
||||||
|
result.add(new MetaDataColumnDescriptor("TABLE_NAME", Types.VARCHAR));
|
||||||
|
result.add(new MetaDataColumnDescriptor("TABLE_TYPE", Types.VARCHAR, "UNKNOWN"));
|
||||||
|
result.add(new MetaDataColumnDescriptor("TABLE_CAT", Types.VARCHAR));
|
||||||
|
result.add(new MetaDataColumnDescriptor("TABLE_SCHEM", Types.VARCHAR));
|
||||||
|
result.add(new MetaDataColumnDescriptor("REMARKS", Types.VARCHAR));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<MetaDataColumnDescriptor> initColumnsForColumn() {
|
||||||
|
List<MetaDataColumnDescriptor> result = new ArrayList<MetaDataColumnDescriptor>();
|
||||||
|
|
||||||
|
// As suggested by Alexandre Borgoltz, we're reading the COLUMN_DEF
|
||||||
|
// first because Oracle
|
||||||
|
// has problems otherwise (it seemingly requires a LONG column to be the
|
||||||
|
// first to be read)
|
||||||
|
// See also DDLUTILS-29
|
||||||
|
result.add(new MetaDataColumnDescriptor("COLUMN_DEF", Types.VARCHAR));
|
||||||
|
|
||||||
|
// we're also reading the table name so that a model reader impl can
|
||||||
|
// filter manually
|
||||||
|
result.add(new MetaDataColumnDescriptor("TABLE_NAME", Types.VARCHAR));
|
||||||
|
result.add(new MetaDataColumnDescriptor("COLUMN_NAME", Types.VARCHAR));
|
||||||
|
result.add(new MetaDataColumnDescriptor("TYPE_NAME", Types.VARCHAR));
|
||||||
|
result.add(new MetaDataColumnDescriptor("DATA_TYPE", Types.INTEGER, new Integer(Types.OTHER)));
|
||||||
|
result.add(new MetaDataColumnDescriptor("NUM_PREC_RADIX", Types.INTEGER, new Integer(10)));
|
||||||
|
result.add(new MetaDataColumnDescriptor("DECIMAL_DIGITS", Types.INTEGER, new Integer(0)));
|
||||||
|
result.add(new MetaDataColumnDescriptor("COLUMN_SIZE", Types.VARCHAR));
|
||||||
|
result.add(new MetaDataColumnDescriptor("IS_NULLABLE", Types.VARCHAR, "YES"));
|
||||||
|
result.add(new MetaDataColumnDescriptor("REMARKS", Types.VARCHAR));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<MetaDataColumnDescriptor> initColumnsForPK() {
|
||||||
|
List<MetaDataColumnDescriptor> result = new ArrayList<MetaDataColumnDescriptor>();
|
||||||
|
|
||||||
|
result.add(new MetaDataColumnDescriptor("COLUMN_NAME", Types.VARCHAR));
|
||||||
|
|
||||||
|
// we're also reading the table name so that a model reader impl can
|
||||||
|
// filter manually
|
||||||
|
result.add(new MetaDataColumnDescriptor("TABLE_NAME", Types.VARCHAR));
|
||||||
|
|
||||||
|
// the name of the primary key is currently only interesting to the pk
|
||||||
|
// index name resolution
|
||||||
|
result.add(new MetaDataColumnDescriptor("PK_NAME", Types.VARCHAR));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Column> readColumns(DatabaseMetaDataWrapper metaData, String tableName) throws SQLException {
|
||||||
|
ResultSet columnData = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
columnData = metaData.getColumns(tableName, null);
|
||||||
|
|
||||||
|
List<Column> columns = new ArrayList<Column>();
|
||||||
|
Map<String, Object> values;
|
||||||
|
|
||||||
|
for (; columnData.next(); columns.add(readColumn(metaData, values))) {
|
||||||
|
Map<String, Object> tmp = readColumns(columnData, initColumnsForColumn());
|
||||||
|
if (tableName.equalsIgnoreCase((String) tmp.get("TABLE_NAME"))) {
|
||||||
|
values = tmp;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
} finally {
|
||||||
|
JdbcUtils.closeResultSet(columnData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Column readColumn(DatabaseMetaDataWrapper metaData, Map<String, Object> values) throws SQLException {
|
||||||
|
Column column = new Column();
|
||||||
|
|
||||||
|
column.setName((String) values.get("COLUMN_NAME"));
|
||||||
|
column.setDefaultValue((String) values.get("COLUMN_DEF"));
|
||||||
|
column.setTypeCode(((Integer) values.get("DATA_TYPE")).intValue());
|
||||||
|
|
||||||
|
String typeName = (String) values.get("TYPE_NAME");
|
||||||
|
// column.setType(typeName);
|
||||||
|
|
||||||
|
if ((typeName != null) && typeName.startsWith("TIMESTAMP")) {
|
||||||
|
column.setTypeCode(Types.TIMESTAMP);
|
||||||
|
}
|
||||||
|
// modify 2013-09-25,处理下unsigned
|
||||||
|
if ((typeName != null) && StringUtils.containsIgnoreCase(typeName, "UNSIGNED")) {
|
||||||
|
// 如果为unsigned,往上调大一个量级,避免数据溢出
|
||||||
|
switch (column.getTypeCode()) {
|
||||||
|
case Types.TINYINT:
|
||||||
|
column.setTypeCode(Types.SMALLINT);
|
||||||
|
break;
|
||||||
|
case Types.SMALLINT:
|
||||||
|
column.setTypeCode(Types.INTEGER);
|
||||||
|
break;
|
||||||
|
case Types.INTEGER:
|
||||||
|
column.setTypeCode(Types.BIGINT);
|
||||||
|
break;
|
||||||
|
case Types.BIGINT:
|
||||||
|
column.setTypeCode(Types.DECIMAL);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer precision = (Integer) values.get("NUM_PREC_RADIX");
|
||||||
|
|
||||||
|
if (precision != null) {
|
||||||
|
column.setPrecisionRadix(precision.intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
String size = (String) values.get("COLUMN_SIZE");
|
||||||
|
|
||||||
|
if (size == null) {
|
||||||
|
size = (String) _defaultSizes.get(new Integer(column.getTypeCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're setting the size after the precision and radix in case
|
||||||
|
// the database prefers to return them in the size value
|
||||||
|
column.setSize(size);
|
||||||
|
|
||||||
|
int scale = 0;
|
||||||
|
Object dec_digits = values.get("DECIMAL_DIGITS");
|
||||||
|
|
||||||
|
if (dec_digits instanceof String) {
|
||||||
|
scale = (dec_digits == null) ? 0 : NumberUtils.toInt(dec_digits.toString());
|
||||||
|
} else if (dec_digits instanceof Integer) {
|
||||||
|
scale = (dec_digits == null) ? 0 : (Integer) dec_digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scale != 0) {
|
||||||
|
column.setScale(scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
column.setRequired("NO".equalsIgnoreCase(((String) values.get("IS_NULLABLE")).trim()));
|
||||||
|
column.setDescription((String) values.get("REMARKS"));
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> readColumns(ResultSet resultSet, List<MetaDataColumnDescriptor> columnDescriptors)
|
||||||
|
throws SQLException {
|
||||||
|
Map<String, Object> values = new HashMap<String, Object>();
|
||||||
|
MetaDataColumnDescriptor descriptor;
|
||||||
|
|
||||||
|
for (Iterator<MetaDataColumnDescriptor> it = columnDescriptors.iterator(); it.hasNext(); values.put(descriptor.getName(),
|
||||||
|
descriptor.readColumn(resultSet))) {
|
||||||
|
descriptor = (MetaDataColumnDescriptor) it.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Collection<String> readPrimaryKeyNames(DatabaseMetaDataWrapper metaData, String tableName)
|
||||||
|
throws SQLException {
|
||||||
|
ResultSet pkData = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<String> pks = new ArrayList<String>();
|
||||||
|
Map<String, Object> values;
|
||||||
|
|
||||||
|
for (pkData = metaData.getPrimaryKeys(tableName); pkData.next(); pks.add(readPrimaryKeyName(metaData,
|
||||||
|
values))) {
|
||||||
|
values = readColumns(pkData, initColumnsForPK());
|
||||||
|
}
|
||||||
|
|
||||||
|
return pks;
|
||||||
|
} finally {
|
||||||
|
JdbcUtils.closeResultSet(pkData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readPrimaryKeyName(DatabaseMetaDataWrapper metaData, Map<String, Object> values)
|
||||||
|
throws SQLException {
|
||||||
|
return (String) values.get("COLUMN_NAME");
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user