mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
Compare commits
431 Commits
canal-1.0.
...
metrics_su
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67a3a1d955 | ||
|
|
a857b87388 | ||
|
|
b57ac0bcdf | ||
|
|
ea6391d1c3 | ||
|
|
89a32aaabd | ||
|
|
b53131553e | ||
|
|
68d8afae3e | ||
|
|
a2eb128698 | ||
|
|
760f2dda80 | ||
|
|
bf9dc0073d | ||
|
|
92f24bd02e | ||
|
|
d4797a8fb9 | ||
|
|
060bd8e31d | ||
|
|
61c7f7738d | ||
|
|
9e816bc48f | ||
|
|
377a72e1c0 | ||
|
|
88de747ed5 | ||
|
|
725d36aa98 | ||
|
|
d24e10fb3e | ||
|
|
fdae646e26 | ||
|
|
b1dc5bd59f | ||
|
|
23f7b3b5d8 | ||
|
|
09339333bb | ||
|
|
f36ce647e6 | ||
|
|
40c3e65341 | ||
|
|
6d3584b6fb | ||
|
|
4006cedaad | ||
|
|
cd0bf60824 | ||
|
|
c4c45d9e0b | ||
|
|
02868d8fec | ||
|
|
7d9e2dc69b | ||
|
|
b5a1898cec | ||
|
|
680faea2bb | ||
|
|
1cd77bf551 | ||
|
|
9786faba9a | ||
|
|
325fe89aed | ||
|
|
ffeec8fc86 | ||
|
|
7662c9241e | ||
|
|
d01195ac82 | ||
|
|
7c7ce43455 | ||
|
|
119c5a6971 | ||
|
|
8a952d3a5f | ||
|
|
764e1b6dc0 | ||
|
|
8b186d1330 | ||
|
|
4e5e2f84e0 | ||
|
|
3c716e2a43 | ||
|
|
872dad8ce8 | ||
|
|
bd6ab1cc80 | ||
|
|
7fd958a490 | ||
|
|
b7431eb7d0 | ||
|
|
c7fc149e7e | ||
|
|
da1ce51a0f | ||
|
|
368554166c | ||
|
|
c4ee350717 | ||
|
|
26edbeaa28 | ||
|
|
93ebc7b973 | ||
|
|
ae0f43d1c7 | ||
|
|
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 | ||
|
|
8d6f19cc33 | ||
|
|
1463463f1e | ||
|
|
77342663e3 | ||
|
|
e01e48ff65 | ||
|
|
4f00f5ea8d | ||
|
|
19707f92d4 | ||
|
|
3930ea0f9b | ||
|
|
676cf47987 | ||
|
|
788c1d9b84 | ||
|
|
84a9aeb46c | ||
|
|
e8da1c884b | ||
|
|
041cbf04c7 | ||
|
|
a1cf292baa | ||
|
|
67436b2bc4 | ||
|
|
ca350a79bd | ||
|
|
214e404e1b | ||
|
|
34bcfbc3cb | ||
|
|
402245dbbf | ||
|
|
2cebfa04dd | ||
|
|
62e8ff6ec8 | ||
|
|
b265bf4669 | ||
|
|
989a69e7be | ||
|
|
780b4f715b | ||
|
|
8e88dc87f7 | ||
|
|
867d6607d4 | ||
|
|
22e3d2f3f1 | ||
|
|
ecdd619da7 | ||
|
|
9ba93bae5f | ||
|
|
0b2f37a272 | ||
|
|
0bd4a1146c | ||
|
|
3dba400624 | ||
|
|
523130cf8b | ||
|
|
525a00ea17 | ||
|
|
b6090da0e5 | ||
|
|
9ea88c7863 | ||
|
|
48c108806a | ||
|
|
8600e9825b | ||
|
|
29815c41b0 | ||
|
|
717e2cb7f3 | ||
|
|
3e5b3e1467 | ||
|
|
a46ac3b814 | ||
|
|
f6c2eb8963 | ||
|
|
07b979f242 | ||
|
|
6b20959a2d | ||
|
|
fdd40224d5 | ||
|
|
a6604978ed | ||
|
|
4d624cf184 | ||
|
|
7e026f2495 | ||
|
|
2e3796cb07 | ||
|
|
b2a5f482ee | ||
|
|
a78622ae02 | ||
|
|
e5444ae658 | ||
|
|
73627df082 | ||
|
|
c0f2d274d7 | ||
|
|
2eb05b5934 | ||
|
|
83689ee519 | ||
|
|
d78b389737 | ||
|
|
20d8ac67dc | ||
|
|
19a56fb77e | ||
|
|
c629e8c899 | ||
|
|
75a5a4d7df | ||
|
|
045a2e5a50 | ||
|
|
11d0712291 | ||
|
|
03ff9512dd | ||
|
|
0ee2e767d2 | ||
|
|
ff97dcbbc9 | ||
|
|
92c1a51726 | ||
|
|
36216e63ea | ||
|
|
db4fc68a55 | ||
|
|
eef58005d9 | ||
|
|
8141ddc4e8 | ||
|
|
004f26465f | ||
|
|
5dc62bbda8 | ||
|
|
f45f1ac74f | ||
|
|
5c11c71783 | ||
|
|
f745c698e9 | ||
|
|
5f4bbab722 | ||
|
|
a483f03711 | ||
|
|
a1207060bf | ||
|
|
5055fdb556 | ||
|
|
a69b69c724 | ||
|
|
90e6d34fb4 | ||
|
|
907ecd43e4 | ||
|
|
c57a921a1a | ||
|
|
83fee4ffbb | ||
|
|
bc7927fe75 | ||
|
|
e67f27018c | ||
|
|
3f38ad6f49 | ||
|
|
96dca0b9e6 | ||
|
|
ad2a96b3f1 | ||
|
|
6284a429a0 | ||
|
|
83d8a39d16 | ||
|
|
4898829384 | ||
|
|
69a7e6d083 | ||
|
|
a5d6c3b747 | ||
|
|
86378cad32 | ||
|
|
8626c6ee24 | ||
|
|
350f8637c3 | ||
|
|
96454d4387 | ||
|
|
8a293e4b0c | ||
|
|
006dd63646 | ||
|
|
b17c11868c | ||
|
|
0c9eecd758 | ||
|
|
5d9d5b1e44 | ||
|
|
b23e814c48 | ||
|
|
b349b400cf | ||
|
|
c86fba6092 | ||
|
|
9742fdc9d9 | ||
|
|
c99b4b8cd1 | ||
|
|
aa8ea96572 | ||
|
|
f6cf88a6bd | ||
|
|
14a5762fbb | ||
|
|
eda24df3fe | ||
|
|
3a5a4b06a9 | ||
|
|
ba173fcd48 | ||
|
|
69170f58df | ||
|
|
c3e38e50e2 | ||
|
|
ae05a3f39f | ||
|
|
6eafc73a65 | ||
|
|
63b3c4ab54 | ||
|
|
ecba855bf9 | ||
|
|
b599846470 | ||
|
|
8aefd7e79b | ||
|
|
4e76a4bd6e | ||
|
|
f1a1cb26cc | ||
|
|
4f1e3bafe7 | ||
|
|
6a03ffaf7b | ||
|
|
d7d969915e | ||
|
|
4898ed7b83 | ||
|
|
11e1d1d600 | ||
|
|
e614c6141d | ||
|
|
8ded94a09e | ||
|
|
38cc81b7da | ||
|
|
533c5cb8f6 | ||
|
|
5932e1e31f | ||
|
|
70dda221f1 | ||
|
|
c32a5d0882 | ||
|
|
fbd99acac2 | ||
|
|
51cfa7fa96 | ||
|
|
652101035e | ||
|
|
1813c1eff9 | ||
|
|
31ea9fa8f0 | ||
|
|
369acd8eed | ||
|
|
891eb960ca | ||
|
|
2e5a165b88 | ||
|
|
6b9c1305ff | ||
|
|
4f504aab31 | ||
|
|
dc28c68bf0 | ||
|
|
38f2f98da3 | ||
|
|
efebbe301a | ||
|
|
bf10b16138 | ||
|
|
37478f3409 | ||
|
|
d2f98cda87 | ||
|
|
ed74d934d3 | ||
|
|
7b29828f60 | ||
|
|
9164fe4ca7 | ||
|
|
51ac311aa1 | ||
|
|
643bd5cb71 | ||
|
|
8375ea4b7d | ||
|
|
72ceb58fa3 | ||
|
|
44f2b74862 | ||
|
|
5a34195201 | ||
|
|
d976932177 | ||
|
|
d887c8f763 | ||
|
|
197a1446b7 | ||
|
|
155c00b256 | ||
|
|
b16f4844a1 | ||
|
|
f2f2bfb538 | ||
|
|
5f6580399c | ||
|
|
10930b9e3d | ||
|
|
d1afeb7540 | ||
|
|
119af9fca1 | ||
|
|
f0ce4b569e | ||
|
|
4459b8acdf | ||
|
|
bb5d5749bd | ||
|
|
169baafb8b | ||
|
|
199f30de87 | ||
|
|
87c14f0b51 | ||
|
|
f35d965f63 | ||
|
|
9956b3557f |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -13,3 +13,6 @@ otter.properties
|
||||
jtester.properties
|
||||
.idea/
|
||||
*.iml
|
||||
.DS_Store
|
||||
*.tar.gz
|
||||
*.rpm
|
||||
|
||||
26
README.md
26
README.md
@@ -5,11 +5,13 @@
|
||||
<ol>
|
||||
<li>canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。</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>
|
||||
|
||||
<h1>背景</h1>
|
||||
<p style="font-size: 14px;"> 早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&消费的业务,从此开启了一段新纪元。</p>
|
||||
<p style="font-size: 14px;"> ps. 目前内部版本已经支持mysql和oracle部分版本的日志解析,当前的canal开源版本支持5.6及以下的版本(阿里内部mysql 5.6.10, mysql 5.5.18和5.1.40/48)</p>
|
||||
<p style="font-size: 14px;"> ps. 目前内部版本已经支持mysql和oracle部分版本的日志解析,当前的canal开源版本支持5.7及以下的版本(阿里内部mysql 5.7.13, 5.6.10, mysql 5.5.18和5.1.40/48)</p>
|
||||
<p style="font-size: 14px;"> </p>
|
||||
<p style="font-size: 14px;">基于日志增量订阅&消费支持的业务:</p>
|
||||
<ol style="font-size: 14px;">
|
||||
@@ -62,11 +64,24 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
|
||||
<li><a href="https://github.com/alibaba/canal/releases">Download</a></li>
|
||||
</ul>
|
||||
|
||||
<h1>相关资料: </h1>
|
||||
<h1>相关资料</h1>
|
||||
|
||||
* ADC阿里技术嘉年华分享ppt (放在google docs上,可能需要翻墙): <a href="https://docs.google.com/presentation/d/1MkszUPYRDkfVPz9IqOT1LLT5d9tuwde_WC8GZvjaDRg/edit?usp=sharing">ppt下载</href>
|
||||
* [与阿里巴巴的RocketMQ配合使用](https://github.com/alibaba/RocketMQ)
|
||||
|
||||
<h1>相关开源</h1>
|
||||
<ol>
|
||||
<li>阿里巴巴分布式数据库同步系统(解决中美异地机房):<a href="http://github.com/alibaba/otter">http://github.com/alibaba/otter</a></li>
|
||||
<li>阿里巴巴去Oracle数据迁移同步工具(目标支持MySQL/DRDS):<a href="http://github.com/alibaba/yugong">http://github.com/alibaba/yugong</a></li>
|
||||
</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>
|
||||
<ol>
|
||||
@@ -75,3 +90,10 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
|
||||
<li>新浪微博: agapple0002 </li>
|
||||
<li>报告issue:<a href="https://github.com/alibaba/canal/issues">issues</a></li>
|
||||
</ol>
|
||||
|
||||
<pre>
|
||||
【招聘】阿里巴巴中间件团队招聘JAVA高级工程师
|
||||
岗位主要为技术型内容(非业务部门),阿里中间件整个体系对于未来想在技术上有所沉淀的同学还是非常有帮助的
|
||||
工作地点:杭州、北京均可. ps. 阿里待遇向来都是不错的,有意者可以QQ、微博私聊.
|
||||
具体招聘内容:https://job.alibaba.com/zhaopin/position_detail.htm?positionId=32666
|
||||
</pre>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
@@ -69,7 +69,7 @@
|
||||
<links>
|
||||
<link>https://github.com/alibaba/canal</link>
|
||||
</links>
|
||||
<outputDirectory>${project.build.directory}/apidocs/apidocs/${pom.version}</outputDirectory>
|
||||
<outputDirectory>${project.build.directory}/apidocs/apidocs/${project.version}</outputDirectory>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
||||
@@ -150,4 +150,10 @@ public interface CanalConnector {
|
||||
*/
|
||||
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,
|
||||
String password) {
|
||||
SimpleCanalConnector canalConnector = new SimpleCanalConnector(address, username, password, destination);
|
||||
canalConnector.setSoTimeout(30 * 1000);
|
||||
canalConnector.setSoTimeout(60 * 1000);
|
||||
canalConnector.setIdleTimeout(60 * 60 * 1000);
|
||||
return canalConnector;
|
||||
}
|
||||
|
||||
@@ -48,7 +49,8 @@ public class CanalConnectors {
|
||||
password,
|
||||
destination,
|
||||
new SimpleNodeAccessStrategy(addresses));
|
||||
canalConnector.setSoTimeout(30 * 1000);
|
||||
canalConnector.setSoTimeout(60 * 1000);
|
||||
canalConnector.setIdleTimeout(60 * 60 * 1000);
|
||||
return canalConnector;
|
||||
}
|
||||
|
||||
@@ -67,7 +69,8 @@ public class CanalConnectors {
|
||||
password,
|
||||
destination,
|
||||
new ClusterNodeAccessStrategy(destination, ZkClientx.getZkClient(zkServers)));
|
||||
canalConnector.setSoTimeout(30 * 1000);
|
||||
canalConnector.setSoTimeout(60 * 1000);
|
||||
canalConnector.setIdleTimeout(60 * 60 * 1000);
|
||||
return canalConnector;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,7 @@ import java.net.SocketAddress;
|
||||
*/
|
||||
public interface CanalNodeAccessStrategy {
|
||||
|
||||
SocketAddress currentNode();
|
||||
|
||||
SocketAddress nextNode();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.alibaba.otter.canal.client.impl;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -23,8 +22,9 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
private String username;
|
||||
private String password;
|
||||
private int soTimeout = 10000;
|
||||
private int retryTimes = 3;
|
||||
private int soTimeout = 60000;
|
||||
private int idleTimeout = 60 * 60 * 1000;
|
||||
private int retryTimes = 3; // 设置-1时可以subscribe阻塞等待时优雅停机
|
||||
private int retryInterval = 5000; // 重试的时间间隔,默认5秒
|
||||
private CanalNodeAccessStrategy accessStrategy;
|
||||
private SimpleCanalConnector currentConnector;
|
||||
@@ -41,12 +41,19 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
|
||||
public void connect() throws CanalClientException {
|
||||
while (currentConnector == null) {
|
||||
SocketAddress nextAddress = this.accessStrategy.nextNode();
|
||||
int times = 0;
|
||||
while (true) {
|
||||
try {
|
||||
currentConnector = new SimpleCanalConnector(nextAddress, username, password, destination);
|
||||
currentConnector = new SimpleCanalConnector(null, username, password, destination) {
|
||||
|
||||
@Override
|
||||
public SocketAddress getNextAddress() {
|
||||
return accessStrategy.nextNode();
|
||||
}
|
||||
|
||||
};
|
||||
currentConnector.setSoTimeout(soTimeout);
|
||||
currentConnector.setIdleTimeout(idleTimeout);
|
||||
if (filter != null) {
|
||||
currentConnector.setFilter(filter);
|
||||
}
|
||||
@@ -57,7 +64,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
currentConnector.connect();
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to connect to:{} after retry {} times", nextAddress, times);
|
||||
logger.warn("failed to connect to:{} after retry {} times", accessStrategy.currentNode(), times);
|
||||
currentConnector.disconnect();
|
||||
currentConnector = null;
|
||||
// retry for #retryTimes for each node when trying to
|
||||
@@ -101,12 +108,17 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
this.filter = filter;
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when subscribing from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
if (retryTimes == -1 && t.getCause() instanceof InterruptedException) {
|
||||
logger.info("block waiting interrupted by other thread.");
|
||||
return;
|
||||
} else {
|
||||
logger.warn(String.format("something goes wrong when subscribing from server: %s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,9 +132,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
currentConnector.unsubscribe();
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when unsubscribing from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when unsubscribing from server:%s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -138,9 +149,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
Message msg = currentConnector.get(batchSize);
|
||||
return msg;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when getting data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when getting data from server:%s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -156,9 +166,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
Message msg = currentConnector.get(batchSize, timeout, unit);
|
||||
return msg;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when getting data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when getting data from server:%s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -174,9 +183,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
Message msg = currentConnector.getWithoutAck(batchSize);
|
||||
return msg;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when getWithoutAck data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -192,9 +200,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
Message msg = currentConnector.getWithoutAck(batchSize, timeout, unit);
|
||||
return msg;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when getWithoutAck data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -210,9 +217,9 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
currentConnector.rollback(batchId);
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when rollbacking data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"),
|
||||
t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -228,9 +235,9 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
currentConnector.rollback();
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when rollbacking data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"),
|
||||
t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -247,9 +254,9 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
currentConnector.ack(batchId);
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when acking data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when acking data from server:%s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"),
|
||||
t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -296,6 +303,14 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
this.soTimeout = soTimeout;
|
||||
}
|
||||
|
||||
public int getIdleTimeout() {
|
||||
return idleTimeout;
|
||||
}
|
||||
|
||||
public void setIdleTimeout(int idleTimeout) {
|
||||
this.idleTimeout = idleTimeout;
|
||||
}
|
||||
|
||||
public int getRetryTimes() {
|
||||
return retryTimes;
|
||||
}
|
||||
@@ -324,4 +339,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
return currentConnector;
|
||||
}
|
||||
|
||||
public void stopRunning() {
|
||||
currentConnector.stopRunning();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -62,6 +62,11 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
|
||||
initRunning(this.zkClient.readData(runningPath, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress currentNode() {
|
||||
return nextNode();
|
||||
}
|
||||
|
||||
public SocketAddress nextNode() {
|
||||
if (runningAddress != null) {// 如果服务已经启动,直接选择当前正在工作的节点
|
||||
return runningAddress;
|
||||
|
||||
@@ -5,7 +5,11 @@ import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.Channel;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -37,11 +41,10 @@ import com.alibaba.otter.canal.protocol.ClientIdentity;
|
||||
import com.alibaba.otter.canal.protocol.Message;
|
||||
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
/**
|
||||
* 基于{@linkplain CanalServerWithNetty}定义的网络协议接口,对于canal数据进行get/rollback/ack等操作
|
||||
*
|
||||
*
|
||||
* @author jianghang 2012-10-24 下午05:37:20
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@@ -52,11 +55,14 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
private String username;
|
||||
private String password;
|
||||
private int soTimeout = 60000; // milliseconds
|
||||
private int idleTimeout = 60 * 60 * 1000; // client和server之间的空闲链接超时的时间,默认为1小时
|
||||
private String filter; // 记录上一次的filter提交值,便于自动重试时提交
|
||||
|
||||
private final ByteBuffer readHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
||||
private final ByteBuffer writeHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
||||
private SocketChannel channel;
|
||||
private ReadableByteChannel readableChannel;
|
||||
private WritableByteChannel writableChannel;
|
||||
private List<Compression> supportedCompressions = new ArrayList<Compression>();
|
||||
private ClientIdentity clientIdentity;
|
||||
private ClientRunningMonitor runningMonitor; // 运行控制
|
||||
@@ -65,21 +71,29 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
private volatile boolean connected = false; // 代表connected是否已正常执行,因为有HA,不代表在工作中
|
||||
private boolean rollbackOnConnect = true; // 是否在connect链接成功后,自动执行rollback操作
|
||||
private boolean rollbackOnDisConnect = false; // 是否在connect链接成功后,自动执行rollback操作
|
||||
|
||||
private boolean lazyParseEntry = false; // 是否自动化解析Entry对象,如果考虑最大化性能可以延后解析
|
||||
// 读写数据分别使用不同的锁进行控制,减小锁粒度,读也需要排他锁,并发度容易造成数据包混乱,反序列化失败
|
||||
private Object readDataLock = new Object();
|
||||
private Object writeDataLock = new Object();
|
||||
|
||||
private volatile boolean running = false;
|
||||
|
||||
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,
|
||||
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.username = username;
|
||||
this.password = password;
|
||||
this.soTimeout = soTimeout;
|
||||
this.idleTimeout = idleTimeout;
|
||||
this.clientIdentity = new ClientIdentity(destination, (short) 1001);
|
||||
}
|
||||
|
||||
@@ -94,6 +108,9 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
} else {
|
||||
waitClientRunning();
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
doConnect();
|
||||
if (filter != null) { // 如果存在条件,说明是自动切换,基于上一次的条件订阅一次
|
||||
subscribe(filter);
|
||||
@@ -125,8 +142,14 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
try {
|
||||
channel = SocketChannel.open();
|
||||
channel.socket().setSoTimeout(soTimeout);
|
||||
SocketAddress address = getAddress();
|
||||
if (address == null) {
|
||||
address = getNextAddress();
|
||||
}
|
||||
channel.connect(address);
|
||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
||||
readableChannel = Channels.newChannel(channel.socket().getInputStream());
|
||||
writableChannel = Channels.newChannel(channel.socket().getOutputStream());
|
||||
Packet p = Packet.parseFrom(readNextPacket());
|
||||
if (p.getVersion() != 1) {
|
||||
throw new CanalClientException("unsupported version at this client.");
|
||||
}
|
||||
@@ -140,17 +163,17 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
//
|
||||
ClientAuth ca = ClientAuth.newBuilder()
|
||||
.setUsername(username != null ? username : "")
|
||||
.setNetReadTimeout(soTimeout)
|
||||
.setNetWriteTimeout(soTimeout)
|
||||
.setPassword(ByteString.copyFromUtf8(password != null ? password : ""))
|
||||
.setNetReadTimeout(idleTimeout)
|
||||
.setNetWriteTimeout(idleTimeout)
|
||||
.build();
|
||||
writeWithHeader(channel,
|
||||
Packet.newBuilder()
|
||||
.setType(PacketType.CLIENTAUTHENTICATION)
|
||||
.setBody(ca.toByteString())
|
||||
.build()
|
||||
.toByteArray());
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.CLIENTAUTHENTICATION)
|
||||
.setBody(ca.toByteString())
|
||||
.build()
|
||||
.toByteArray());
|
||||
//
|
||||
Packet ack = Packet.parseFrom(readNextPacket(channel));
|
||||
Packet ack = Packet.parseFrom(readNextPacket());
|
||||
if (ack.getType() != PacketType.ACK) {
|
||||
throw new CanalClientException("unexpected packet type when ack is expected");
|
||||
}
|
||||
@@ -169,36 +192,50 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
|
||||
private void doDisconnnect() throws CanalClientException {
|
||||
if (readableChannel != null) {
|
||||
quietlyClose(readableChannel);
|
||||
readableChannel = null;
|
||||
}
|
||||
if (writableChannel != null) {
|
||||
quietlyClose(writableChannel);
|
||||
writableChannel = null;
|
||||
}
|
||||
if (channel != null) {
|
||||
try {
|
||||
channel.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("exception on closing channel:{} \n {}", channel, e);
|
||||
}
|
||||
quietlyClose(channel);
|
||||
channel = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void quietlyClose(Channel channel) {
|
||||
try {
|
||||
channel.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("exception on closing channel:{} \n {}", channel, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void subscribe() throws CanalClientException {
|
||||
subscribe(""); // 传递空字符即可
|
||||
}
|
||||
|
||||
public void subscribe(String filter) throws CanalClientException {
|
||||
waitClientRunning();
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
writeWithHeader(channel,
|
||||
Packet.newBuilder()
|
||||
.setType(PacketType.SUBSCRIPTION)
|
||||
.setBody(Sub.newBuilder()
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.setFilter(filter != null ? filter : "")
|
||||
.build()
|
||||
.toByteString())
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.SUBSCRIPTION)
|
||||
.setBody(Sub.newBuilder()
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.setFilter(filter != null ? filter : "")
|
||||
.build()
|
||||
.toByteArray());
|
||||
.toByteString())
|
||||
.build()
|
||||
.toByteArray());
|
||||
//
|
||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
||||
Packet p = Packet.parseFrom(readNextPacket());
|
||||
Ack ack = Ack.parseFrom(p.getBody());
|
||||
if (ack.getErrorCode() > 0) {
|
||||
throw new CanalClientException("failed to subscribe with reason: " + ack.getErrorMessage());
|
||||
@@ -212,19 +249,21 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
|
||||
public void unsubscribe() throws CanalClientException {
|
||||
waitClientRunning();
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
writeWithHeader(channel,
|
||||
Packet.newBuilder()
|
||||
.setType(PacketType.UNSUBSCRIPTION)
|
||||
.setBody(Unsub.newBuilder()
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.build()
|
||||
.toByteString())
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.UNSUBSCRIPTION)
|
||||
.setBody(Unsub.newBuilder()
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.build()
|
||||
.toByteArray());
|
||||
.toByteString())
|
||||
.build()
|
||||
.toByteArray());
|
||||
//
|
||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
||||
Packet p = Packet.parseFrom(readNextPacket());
|
||||
Ack ack = Ack.parseFrom(p.getBody());
|
||||
if (ack.getErrorCode() > 0) {
|
||||
throw new CanalClientException("failed to unSubscribe with reason: " + ack.getErrorMessage());
|
||||
@@ -250,6 +289,9 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
|
||||
public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
|
||||
waitClientRunning();
|
||||
if (!running) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
int size = (batchSize <= 0) ? 1000 : batchSize;
|
||||
long time = (timeout == null || timeout < 0) ? -1 : timeout; // -1代表不做timeout控制
|
||||
@@ -257,29 +299,28 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
unit = TimeUnit.MILLISECONDS;
|
||||
}
|
||||
|
||||
writeWithHeader(channel,
|
||||
Packet.newBuilder()
|
||||
.setType(PacketType.GET)
|
||||
.setBody(Get.newBuilder()
|
||||
.setAutoAck(false)
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.setFetchSize(size)
|
||||
.setTimeout(time)
|
||||
.setUnit(unit.ordinal())
|
||||
.build()
|
||||
.toByteString())
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.GET)
|
||||
.setBody(Get.newBuilder()
|
||||
.setAutoAck(false)
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.setFetchSize(size)
|
||||
.setTimeout(time)
|
||||
.setUnit(unit.ordinal())
|
||||
.build()
|
||||
.toByteArray());
|
||||
|
||||
.toByteString())
|
||||
.build()
|
||||
.toByteArray());
|
||||
return receiveMessages();
|
||||
} catch (IOException e) {
|
||||
throw new CanalClientException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Message receiveMessages() throws InvalidProtocolBufferException, IOException {
|
||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
||||
private Message receiveMessages() throws IOException {
|
||||
byte[] data = readNextPacket();
|
||||
Packet p = Packet.parseFrom(data);
|
||||
switch (p.getType()) {
|
||||
case MESSAGES: {
|
||||
if (!p.getCompression().equals(Compression.NONE)) {
|
||||
@@ -288,8 +329,13 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
|
||||
Messages messages = Messages.parseFrom(p.getBody());
|
||||
Message result = new Message(messages.getBatchId());
|
||||
for (ByteString byteString : messages.getMessagesList()) {
|
||||
result.addEntry(Entry.parseFrom(byteString));
|
||||
if (lazyParseEntry) {
|
||||
// byteString
|
||||
result.setRawEntries(messages.getMessagesList());
|
||||
} else {
|
||||
for (ByteString byteString : messages.getMessagesList()) {
|
||||
result.addEntry(Entry.parseFrom(byteString));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -305,13 +351,16 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
|
||||
public void ack(long batchId) throws CanalClientException {
|
||||
waitClientRunning();
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
ClientAck ca = ClientAck.newBuilder()
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.setBatchId(batchId)
|
||||
.build();
|
||||
try {
|
||||
writeWithHeader(channel, Packet.newBuilder()
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.CLIENTACK)
|
||||
.setBody(ca.toByteString())
|
||||
.build()
|
||||
@@ -329,7 +378,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
.setBatchId(batchId)
|
||||
.build();
|
||||
try {
|
||||
writeWithHeader(channel, Packet.newBuilder()
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.CLIENTROLLBACK)
|
||||
.setBody(ca.toByteString())
|
||||
.build()
|
||||
@@ -346,7 +395,15 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
|
||||
// ==================== helper method ====================
|
||||
|
||||
private void writeWithHeader(SocketChannel channel, byte[] body) throws IOException {
|
||||
private void writeWithHeader(byte[] body) throws IOException {
|
||||
writeWithHeader(writableChannel, body);
|
||||
}
|
||||
|
||||
private byte[] readNextPacket() throws IOException {
|
||||
return readNextPacket(readableChannel);
|
||||
}
|
||||
|
||||
private void writeWithHeader(WritableByteChannel channel, byte[] body) throws IOException {
|
||||
synchronized (writeDataLock) {
|
||||
writeHeader.clear();
|
||||
writeHeader.putInt(body.length);
|
||||
@@ -356,7 +413,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readNextPacket(SocketChannel channel) throws IOException {
|
||||
private byte[] readNextPacket(ReadableByteChannel channel) throws IOException {
|
||||
synchronized (readDataLock) {
|
||||
readHeader.clear();
|
||||
read(channel, readHeader);
|
||||
@@ -367,7 +424,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
}
|
||||
|
||||
private void read(SocketChannel channel, ByteBuffer buffer) throws IOException {
|
||||
private void read(ReadableByteChannel channel, ByteBuffer buffer) throws IOException {
|
||||
while (buffer.hasRemaining()) {
|
||||
int r = channel.read(buffer);
|
||||
if (r == -1) {
|
||||
@@ -418,7 +475,11 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
throw new CanalClientException("should connect first");
|
||||
}
|
||||
|
||||
running = true;
|
||||
mutex.get();// 阻塞等待
|
||||
} else {
|
||||
// 单机模式直接设置为running
|
||||
running = true;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
@@ -434,6 +495,10 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
}
|
||||
|
||||
public SocketAddress getNextAddress() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public SocketAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
@@ -454,6 +519,14 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
this.soTimeout = soTimeout;
|
||||
}
|
||||
|
||||
public int getIdleTimeout() {
|
||||
return idleTimeout;
|
||||
}
|
||||
|
||||
public void setIdleTimeout(int idleTimeout) {
|
||||
this.idleTimeout = idleTimeout;
|
||||
}
|
||||
|
||||
public void setZkClientx(ZkClientx zkClientx) {
|
||||
this.zkClientx = zkClientx;
|
||||
initClientRunningMonitor(this.clientIdentity);
|
||||
@@ -471,4 +544,21 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
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); // 中断阻塞
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,4 +32,9 @@ public class SimpleNodeAccessStrategy implements CanalNodeAccessStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress currentNode() {
|
||||
return nodes.get(index);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.alibaba.otter.canal.client.impl.running;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -20,6 +21,7 @@ import com.alibaba.otter.canal.common.utils.BooleanMutex;
|
||||
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
||||
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
||||
import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils;
|
||||
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||
|
||||
/**
|
||||
* clinet running控制
|
||||
@@ -62,6 +64,8 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
|
||||
public void handleDataDeleted(String dataPath) throws Exception {
|
||||
MDC.put("destination", destination);
|
||||
mutex.set(false);
|
||||
// 触发一下退出,可能是人为干预的释放操作或者网络闪断引起的session expired timeout
|
||||
processActiveExit();
|
||||
if (!release && activeData != null && isMine(activeData.getAddress())) {
|
||||
// 如果上一次active的状态就是本机,则即时触发一下active抢占
|
||||
initRunning();
|
||||
@@ -94,9 +98,17 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
|
||||
String path = ZookeeperPathUtils.getDestinationClientRunning(this.destination, clientData.getClientId());
|
||||
zkClient.unsubscribeDataChanges(path, dataListener);
|
||||
releaseRunning(); // 尝试一下release
|
||||
//Fix issue #697
|
||||
if (delayExector != null) {
|
||||
delayExector.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public void initRunning() {
|
||||
// 改动记录:
|
||||
// 1,在方法上加synchronized关键字,保证同步顺序执行;
|
||||
// 2,判断Zk上已经存在的activeData是否是本机,是的话把mutex重置为true,否则会导致死锁
|
||||
// 3,增加异常处理,保证出现异常时,running节点能被删除,否则会导致死锁
|
||||
public synchronized void initRunning() {
|
||||
if (!isStart()) {
|
||||
return;
|
||||
}
|
||||
@@ -116,11 +128,22 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
|
||||
initRunning();
|
||||
} else {
|
||||
activeData = JsonUtils.unmarshalFromByte(bytes, ClientRunningData.class);
|
||||
// 如果发现已经存在,判断一下是否自己,避免活锁
|
||||
if (activeData.getAddress().contains(":") && isMine(activeData.getAddress())) {
|
||||
mutex.set(true);
|
||||
}
|
||||
}
|
||||
} catch (ZkNoNodeException e) {
|
||||
zkClient.createPersistent(ZookeeperPathUtils.getClientIdNodePath(this.destination, clientData.getClientId()),
|
||||
true); // 尝试创建父节点
|
||||
initRunning();
|
||||
} catch (Throwable t) {
|
||||
logger.error(MessageFormat.format("There is an error when execute initRunning method, with destination [{0}].",
|
||||
destination),
|
||||
t);
|
||||
// 出现任何异常尝试release
|
||||
releaseRunning();
|
||||
throw new CanalClientException("something goes wrong in initRunning method. ", t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="WARN">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>canal.common</artifactId>
|
||||
@@ -11,13 +11,18 @@
|
||||
<name>canal common module for otter ${project.version}</name>
|
||||
<url>http://b2b-doc.alibaba-inc.com/display/opentech/Otter</url>
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
</dependency>
|
||||
<!-- zk -->
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.sgroschupf</groupId>
|
||||
<groupId>com.101tec</groupId>
|
||||
<artifactId>zkclient</artifactId>
|
||||
</dependency>
|
||||
<!-- external -->
|
||||
@@ -28,11 +33,10 @@
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring</artifactId>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
@@ -59,10 +63,37 @@
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-orm</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<version>${spring_version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- junit -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
package com.alibaba.otter.canal.common.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
import com.alibaba.fastjson.parser.ParserConfig;
|
||||
import com.alibaba.fastjson.serializer.JSONSerializer;
|
||||
import com.alibaba.fastjson.serializer.ObjectSerializer;
|
||||
import com.alibaba.fastjson.serializer.PropertyFilter;
|
||||
import com.alibaba.fastjson.serializer.SerializeConfig;
|
||||
import com.alibaba.fastjson.serializer.SerializeWriter;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
|
||||
@@ -17,6 +25,13 @@ import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
*/
|
||||
public class JsonUtils {
|
||||
|
||||
static {
|
||||
SerializeConfig.getGlobalInstance().put(InetAddress.class, InetAddressSerializer.instance);
|
||||
SerializeConfig.getGlobalInstance().put(Inet4Address.class, InetAddressSerializer.instance);
|
||||
SerializeConfig.getGlobalInstance().put(Inet6Address.class, InetAddressSerializer.instance);
|
||||
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
|
||||
}
|
||||
|
||||
public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) {
|
||||
return (T) JSON.parseObject(bytes, targetClass);// 默认为UTF-8
|
||||
}
|
||||
@@ -70,4 +85,22 @@ public class JsonUtils {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static class InetAddressSerializer implements ObjectSerializer {
|
||||
|
||||
public static InetAddressSerializer instance = new InetAddressSerializer();
|
||||
|
||||
@Override
|
||||
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features)
|
||||
throws IOException {
|
||||
if (object == null) {
|
||||
serializer.writeNull();
|
||||
return;
|
||||
}
|
||||
|
||||
InetAddress address = (InetAddress) object;
|
||||
// 优先使用name
|
||||
serializer.write(address.getHostName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ public class NamedThreadFactory implements ThreadFactory {
|
||||
|
||||
public void uncaughtException(Thread t,
|
||||
Throwable e) {
|
||||
if (e instanceof InterruptedException
|
||||
|| (e.getCause() != null && e.getCause() instanceof InterruptedException)) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.error("from " + t.getName(), e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,20 +8,15 @@ import java.util.List;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.I0Itec.zkclient.IZkConnection;
|
||||
import org.I0Itec.zkclient.ZkConnection;
|
||||
import org.I0Itec.zkclient.exception.ZkException;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.zookeeper.ClientCnxn;
|
||||
import org.apache.zookeeper.CreateMode;
|
||||
import org.apache.zookeeper.KeeperException;
|
||||
import org.apache.zookeeper.Watcher;
|
||||
import org.apache.zookeeper.ZooDefs.Ids;
|
||||
import org.apache.zookeeper.ZooKeeper;
|
||||
import org.apache.zookeeper.ZooKeeper.States;
|
||||
import org.apache.zookeeper.client.ConnectStringParser;
|
||||
import org.apache.zookeeper.client.HostProvider;
|
||||
import org.apache.zookeeper.client.StaticHostProvider;
|
||||
import org.apache.zookeeper.data.Stat;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -32,7 +27,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
* @author jianghang 2012-7-10 下午02:31:42
|
||||
* @version 1.0.0
|
||||
*/
|
||||
public class ZooKeeperx implements IZkConnection {
|
||||
public class ZooKeeperx extends ZkConnection {
|
||||
|
||||
private static final String SERVER_COMMA = ";";
|
||||
private static final Logger logger = LoggerFactory.getLogger(ZooKeeperx.class);
|
||||
@@ -40,12 +35,12 @@ public class ZooKeeperx implements IZkConnection {
|
||||
private static final Field hostProviderField = ReflectionUtils.findField(ClientCnxn.class, "hostProvider");
|
||||
private static final Field serverAddressesField = ReflectionUtils.findField(StaticHostProvider.class,
|
||||
"serverAddresses");
|
||||
private static final Field zookeeperLockField = ReflectionUtils.findField(ZkConnection.class,
|
||||
"_zookeeperLock");
|
||||
private static final Field zookeeperFiled = ReflectionUtils.findField(ZkConnection.class, "_zk");
|
||||
private static final int DEFAULT_SESSION_TIMEOUT = 90000;
|
||||
|
||||
private ZooKeeper _zk = null;
|
||||
private Lock _zookeeperLock = new ReentrantLock();
|
||||
|
||||
private final List<String> _servers;
|
||||
private final List<String> _serversList;
|
||||
private final int _sessionTimeOut;
|
||||
|
||||
public ZooKeeperx(String zkServers){
|
||||
@@ -53,109 +48,55 @@ public class ZooKeeperx implements IZkConnection {
|
||||
}
|
||||
|
||||
public ZooKeeperx(String zkServers, int sessionTimeOut){
|
||||
_servers = Arrays.asList(StringUtils.split(zkServers, SERVER_COMMA));
|
||||
super(zkServers, sessionTimeOut);
|
||||
_serversList = Arrays.asList(StringUtils.split(this.getServers(), SERVER_COMMA));
|
||||
_sessionTimeOut = sessionTimeOut;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(Watcher watcher) {
|
||||
ReflectionUtils.makeAccessible(zookeeperLockField);
|
||||
ReflectionUtils.makeAccessible(zookeeperFiled);
|
||||
Lock _zookeeperLock = (ReentrantLock) ReflectionUtils.getField(zookeeperLockField, this);
|
||||
ZooKeeper _zk = (ZooKeeper) ReflectionUtils.getField(zookeeperFiled, this);
|
||||
|
||||
_zookeeperLock.lock();
|
||||
try {
|
||||
if (_zk != null) {
|
||||
throw new IllegalStateException("zk client has already been started");
|
||||
}
|
||||
String zkServers = _serversList.get(0);
|
||||
|
||||
try {
|
||||
logger.debug("Creating new ZookKeeper instance to connect to " + _servers + ".");
|
||||
_zk = new ZooKeeper(_servers.get(0), _sessionTimeOut, watcher);
|
||||
logger.debug("Creating new ZookKeeper instance to connect to " + zkServers + ".");
|
||||
_zk = new ZooKeeper(zkServers, _sessionTimeOut, watcher);
|
||||
configMutliCluster(_zk);
|
||||
ReflectionUtils.setField(zookeeperFiled, this, _zk);
|
||||
} catch (IOException e) {
|
||||
throw new ZkException("Unable to connect to " + _servers, e);
|
||||
throw new ZkException("Unable to connect to " + zkServers, e);
|
||||
}
|
||||
} finally {
|
||||
_zookeeperLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws InterruptedException {
|
||||
_zookeeperLock.lock();
|
||||
try {
|
||||
if (_zk != null) {
|
||||
logger.debug("Closing ZooKeeper connected to " + _servers);
|
||||
_zk.close();
|
||||
_zk = null;
|
||||
}
|
||||
} finally {
|
||||
_zookeeperLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public String create(String path, byte[] data, CreateMode mode) throws KeeperException, InterruptedException {
|
||||
return _zk.create(path, data, Ids.OPEN_ACL_UNSAFE, mode);
|
||||
}
|
||||
|
||||
public void delete(String path) throws InterruptedException, KeeperException {
|
||||
_zk.delete(path, -1);
|
||||
}
|
||||
|
||||
public boolean exists(String path, boolean watch) throws KeeperException, InterruptedException {
|
||||
return _zk.exists(path, watch) != null;
|
||||
}
|
||||
|
||||
public List<String> getChildren(final String path, final boolean watch) throws KeeperException,
|
||||
InterruptedException {
|
||||
return _zk.getChildren(path, watch);
|
||||
}
|
||||
|
||||
public byte[] readData(String path, Stat stat, boolean watch) throws KeeperException, InterruptedException {
|
||||
return _zk.getData(path, watch, stat);
|
||||
}
|
||||
|
||||
public void writeData(String path, byte[] data) throws KeeperException, InterruptedException {
|
||||
writeData(path, data, -1);
|
||||
}
|
||||
|
||||
public void writeData(String path, byte[] data, int version) throws KeeperException, InterruptedException {
|
||||
_zk.setData(path, data, version);
|
||||
}
|
||||
|
||||
public States getZookeeperState() {
|
||||
return _zk != null ? _zk.getState() : null;
|
||||
}
|
||||
|
||||
public ZooKeeper getZookeeper() {
|
||||
return _zk;
|
||||
}
|
||||
|
||||
public long getCreateTime(String path) throws KeeperException, InterruptedException {
|
||||
Stat stat = _zk.exists(path, false);
|
||||
if (stat != null) {
|
||||
return stat.getCtime();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public String getServers() {
|
||||
return StringUtils.join(_servers, SERVER_COMMA);
|
||||
}
|
||||
|
||||
// ===============================
|
||||
|
||||
public void configMutliCluster(ZooKeeper zk) {
|
||||
if (_servers.size() == 1) {
|
||||
if (_serversList.size() == 1) {
|
||||
return;
|
||||
}
|
||||
String cluster1 = _servers.get(0);
|
||||
String cluster1 = _serversList.get(0);
|
||||
try {
|
||||
if (_servers.size() > 1) {
|
||||
if (_serversList.size() > 1) {
|
||||
// 强制的声明accessible
|
||||
ReflectionUtils.makeAccessible(clientCnxnField);
|
||||
ReflectionUtils.makeAccessible(hostProviderField);
|
||||
ReflectionUtils.makeAccessible(serverAddressesField);
|
||||
|
||||
// 添加第二组集群列表
|
||||
for (int i = 1; i < _servers.size(); i++) {
|
||||
String cluster = _servers.get(i);
|
||||
for (int i = 1; i < _serversList.size(); i++) {
|
||||
String cluster = _serversList.get(i);
|
||||
// 强制获取zk中的地址信息
|
||||
ClientCnxn cnxn = (ClientCnxn) ReflectionUtils.getField(clientCnxnField, zk);
|
||||
HostProvider hostProvider = (HostProvider) ReflectionUtils.getField(hostProviderField, cnxn);
|
||||
|
||||
@@ -87,18 +87,29 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
||||
|
||||
}
|
||||
|
||||
public void start() {
|
||||
super.start();
|
||||
public void init() {
|
||||
processStart();
|
||||
if (zkClient != null) {
|
||||
// 如果需要尽可能释放instance资源,不需要监听running节点,不然即使stop了这台机器,另一台机器立马会start
|
||||
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
|
||||
zkClient.subscribeDataChanges(path, dataListener);
|
||||
}
|
||||
|
||||
initRunning();
|
||||
} else {
|
||||
processActiveEnter();// 没有zk,直接启动
|
||||
public synchronized void start() {
|
||||
super.start();
|
||||
try {
|
||||
processStart();
|
||||
if (zkClient != null) {
|
||||
// 如果需要尽可能释放instance资源,不需要监听running节点,不然即使stop了这台机器,另一台机器立马会start
|
||||
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
|
||||
zkClient.subscribeDataChanges(path, dataListener);
|
||||
|
||||
initRunning();
|
||||
} else {
|
||||
processActiveEnter();// 没有zk,直接启动
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("start failed", e);
|
||||
// 没有正常启动,重置一下状态,避免干扰下一次start
|
||||
stop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void release() {
|
||||
@@ -109,7 +120,7 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
public synchronized void stop() {
|
||||
super.stop();
|
||||
|
||||
if (zkClient != null) {
|
||||
@@ -230,11 +241,7 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
||||
|
||||
private void processActiveEnter() {
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.processActiveEnter();
|
||||
} catch (Exception e) {
|
||||
logger.error("processActiveEnter failed", e);
|
||||
}
|
||||
listener.processActiveEnter();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -103,8 +103,8 @@ public final class CharsetConversion {
|
||||
putEntry(42, "latin7", "latin7_general_cs", "ISO8859_7");
|
||||
putEntry(43, "macce", "macce_bin", "MacCentralEurope");
|
||||
putEntry(44, "cp1250", "cp1250_croatian_ci", "Cp1250");
|
||||
putEntry(45, "utf8mb4", "utf8mb4_general_ci", "MacCentralEurope");
|
||||
putEntry(46, "utf8mb4", "utf8mb4_bin", "MacCentralEurope");
|
||||
putEntry(45, "utf8mb4", "utf8mb4_general_ci", "UTF-8");
|
||||
putEntry(46, "utf8mb4", "utf8mb4_bin", "UTF-8");
|
||||
putEntry(47, "latin1", "latin1_bin", "ISO8859_1");
|
||||
putEntry(48, "latin1", "latin1_general_ci", "ISO8859_1");
|
||||
putEntry(49, "latin1", "latin1_general_cs", "ISO8859_1");
|
||||
|
||||
@@ -75,7 +75,9 @@ public final class FileLogFetcher extends LogFetcher {
|
||||
fin = new FileInputStream(file);
|
||||
|
||||
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]
|
||||
|| buffer[3] != BINLOG_MAGIC[3]) {
|
||||
@@ -128,6 +130,19 @@ public final class FileLogFetcher extends LogFetcher {
|
||||
return true;
|
||||
}
|
||||
} 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);
|
||||
position -= origin;
|
||||
origin = 0;
|
||||
|
||||
@@ -0,0 +1,418 @@
|
||||
package com.taobao.tddl.dbsync.binlog;
|
||||
|
||||
/**
|
||||
* 处理下MySQL json二进制转化为可读的字符串
|
||||
*
|
||||
* @author agapple 2016年6月30日 上午11:26:17
|
||||
* @since 1.0.22
|
||||
*/
|
||||
public class JsonConversion {
|
||||
|
||||
// JSON TYPE
|
||||
public static final int JSONB_TYPE_SMALL_OBJECT = 0x0;
|
||||
public static final int JSONB_TYPE_LARGE_OBJECT = 0x1;
|
||||
public static final int JSONB_TYPE_SMALL_ARRAY = 0x2;
|
||||
public static final int JSONB_TYPE_LARGE_ARRAY = 0x3;
|
||||
public static final int JSONB_TYPE_LITERAL = 0x4;
|
||||
public static final int JSONB_TYPE_INT16 = 0x5;
|
||||
public static final int JSONB_TYPE_UINT16 = 0x6;
|
||||
public static final int JSONB_TYPE_INT32 = 0x7;
|
||||
public static final int JSONB_TYPE_UINT32 = 0x8;
|
||||
public static final int JSONB_TYPE_INT64 = 0x9;
|
||||
public static final int JSONB_TYPE_UINT64 = 0xA;
|
||||
public static final int JSONB_TYPE_DOUBLE = 0xB;
|
||||
public static final int JSONB_TYPE_STRING = 0xC;
|
||||
public static final int JSONB_TYPE_OPAQUE = 0xF;
|
||||
public static final char JSONB_NULL_LITERAL = '\0';
|
||||
public static final char JSONB_TRUE_LITERAL = '\1';
|
||||
public static final char JSONB_FALSE_LITERAL = '\2';
|
||||
|
||||
/*
|
||||
* The size of offset or size fields in the small and the large storage
|
||||
* format for JSON objects and JSON arrays.
|
||||
*/
|
||||
public static final int SMALL_OFFSET_SIZE = 2;
|
||||
public static final int LARGE_OFFSET_SIZE = 4;
|
||||
|
||||
/*
|
||||
* The size of key entries for objects when using the small storage format
|
||||
* or the large storage format. In the small format it is 4 bytes (2 bytes
|
||||
* for key length and 2 bytes for key offset). In the large format it is 6
|
||||
* (2 bytes for length, 4 bytes for offset).
|
||||
*/
|
||||
public static final int KEY_ENTRY_SIZE_SMALL = (2 + SMALL_OFFSET_SIZE);
|
||||
public static final int KEY_ENTRY_SIZE_LARGE = (2 + LARGE_OFFSET_SIZE);
|
||||
|
||||
/*
|
||||
* The size of value entries for objects or arrays. When using the small
|
||||
* storage format, the entry size is 3 (1 byte for type, 2 bytes for
|
||||
* offset). When using the large storage format, it is 5 (1 byte for type, 4
|
||||
* bytes for offset).
|
||||
*/
|
||||
public static final int VALUE_ENTRY_SIZE_SMALL = (1 + SMALL_OFFSET_SIZE);
|
||||
public static final int VALUE_ENTRY_SIZE_LARGE = (1 + LARGE_OFFSET_SIZE);
|
||||
|
||||
public static Json_Value parse_value(int type, LogBuffer buffer, long len, String charsetName) {
|
||||
buffer = buffer.duplicate(buffer.position(), (int) len);
|
||||
switch (type) {
|
||||
case JSONB_TYPE_SMALL_OBJECT:
|
||||
return parse_array_or_object(Json_enum_type.OBJECT, buffer, len, false, charsetName);
|
||||
case JSONB_TYPE_LARGE_OBJECT:
|
||||
return parse_array_or_object(Json_enum_type.OBJECT, buffer, len, true, charsetName);
|
||||
case JSONB_TYPE_SMALL_ARRAY:
|
||||
return parse_array_or_object(Json_enum_type.ARRAY, buffer, len, false, charsetName);
|
||||
case JSONB_TYPE_LARGE_ARRAY:
|
||||
return parse_array_or_object(Json_enum_type.ARRAY, buffer, len, true, charsetName);
|
||||
default:
|
||||
return parse_scalar(type, buffer, len, charsetName);
|
||||
}
|
||||
}
|
||||
|
||||
private static Json_Value parse_array_or_object(Json_enum_type type, LogBuffer buffer, long len, boolean large,
|
||||
String charsetName) {
|
||||
long offset_size = large ? LARGE_OFFSET_SIZE : SMALL_OFFSET_SIZE;
|
||||
if (len < 2 * offset_size) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
long element_count = read_offset_or_size(buffer, large);
|
||||
long bytes = read_offset_or_size(buffer, large);
|
||||
|
||||
if (bytes > len) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
long header_size = 2 * offset_size;
|
||||
if (type == Json_enum_type.OBJECT) {
|
||||
header_size += element_count * (large ? KEY_ENTRY_SIZE_LARGE : KEY_ENTRY_SIZE_SMALL);
|
||||
}
|
||||
|
||||
header_size += element_count * (large ? VALUE_ENTRY_SIZE_LARGE : VALUE_ENTRY_SIZE_SMALL);
|
||||
if (header_size > bytes) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
return new Json_Value(type, buffer.rewind(), element_count, bytes, large);
|
||||
}
|
||||
|
||||
private static long read_offset_or_size(LogBuffer buffer, boolean large) {
|
||||
return large ? buffer.getUint32() : buffer.getUint16();
|
||||
}
|
||||
|
||||
private static Json_Value parse_scalar(int type, LogBuffer buffer, long len, String charsetName) {
|
||||
switch (type) {
|
||||
case JSONB_TYPE_LITERAL:
|
||||
/* purecov: inspected */
|
||||
int data = buffer.getUint8();
|
||||
switch (data) {
|
||||
case JSONB_NULL_LITERAL:
|
||||
return new Json_Value(Json_enum_type.LITERAL_NULL);
|
||||
case JSONB_TRUE_LITERAL:
|
||||
return new Json_Value(Json_enum_type.LITERAL_TRUE);
|
||||
case JSONB_FALSE_LITERAL:
|
||||
return new Json_Value(Json_enum_type.LITERAL_FALSE);
|
||||
default:
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
case JSONB_TYPE_INT16:
|
||||
return new Json_Value(Json_enum_type.INT, buffer.getInt16());
|
||||
case JSONB_TYPE_INT32:
|
||||
return new Json_Value(Json_enum_type.INT, buffer.getInt32());
|
||||
case JSONB_TYPE_INT64:
|
||||
return new Json_Value(Json_enum_type.INT, buffer.getLong64());
|
||||
case JSONB_TYPE_UINT16:
|
||||
return new Json_Value(Json_enum_type.UINT, buffer.getUint16());
|
||||
case JSONB_TYPE_UINT32:
|
||||
return new Json_Value(Json_enum_type.UINT, buffer.getUint32());
|
||||
case JSONB_TYPE_UINT64:
|
||||
return new Json_Value(Json_enum_type.UINT, buffer.getUlong64());
|
||||
case JSONB_TYPE_DOUBLE:
|
||||
return new Json_Value(Json_enum_type.DOUBLE, Double.valueOf(buffer.getDouble64()));
|
||||
case JSONB_TYPE_STRING:
|
||||
int max_bytes = (int) Math.min(len, 5);
|
||||
long tlen = 0;
|
||||
long str_len = 0;
|
||||
long n = 0;
|
||||
byte[] datas = buffer.getData(max_bytes);
|
||||
for (int i = 0; i < max_bytes; i++) {
|
||||
// Get the next 7 bits of the length.
|
||||
tlen |= (datas[i] & 0x7f) << (7 * i);
|
||||
if ((datas[i] & 0x80) == 0) {
|
||||
// The length shouldn't exceed 32 bits.
|
||||
if (tlen > 4294967296L) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
|
||||
// This was the last byte. Return successfully.
|
||||
n = i + 1;
|
||||
str_len = tlen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (len < n + str_len) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
return new Json_Value(Json_enum_type.STRING, buffer.rewind()
|
||||
.forward((int) n)
|
||||
.getFixString((int) str_len, charsetName));
|
||||
case JSONB_TYPE_OPAQUE:
|
||||
/*
|
||||
* There should always be at least one byte, which tells the
|
||||
* field type of the opaque value.
|
||||
*/
|
||||
// The type is encoded as a uint8 that maps to an
|
||||
// enum_field_types.
|
||||
int type_byte = buffer.getUint8();
|
||||
int position = buffer.position();
|
||||
// Then there's the length of the value.
|
||||
int q_max_bytes = (int) Math.min(len, 5);
|
||||
long q_tlen = 0;
|
||||
long q_str_len = 0;
|
||||
long q_n = 0;
|
||||
byte[] q_datas = buffer.getData(q_max_bytes);
|
||||
for (int i = 0; i < q_max_bytes; i++) {
|
||||
// Get the next 7 bits of the length.
|
||||
q_tlen |= (q_datas[i] & 0x7f) << (7 * i);
|
||||
if ((q_datas[i] & 0x80) == 0) {
|
||||
// The length shouldn't exceed 32 bits.
|
||||
if (q_tlen > 4294967296L) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
|
||||
// This was the last byte. Return successfully.
|
||||
q_n = i + 1;
|
||||
q_str_len = q_tlen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (q_str_len == 0 || len < q_n + q_str_len) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
return new Json_Value(type_byte, buffer.position(position).forward((int) q_n), q_str_len);
|
||||
default:
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
}
|
||||
|
||||
public static class Json_Value {
|
||||
|
||||
Json_enum_type m_type;
|
||||
int m_field_type;
|
||||
LogBuffer m_data;
|
||||
long m_element_count;
|
||||
long m_length;
|
||||
String m_string_value;
|
||||
Number m_int_value;
|
||||
double m_double_value;
|
||||
boolean m_large;
|
||||
|
||||
public Json_Value(Json_enum_type t){
|
||||
this.m_type = t;
|
||||
}
|
||||
|
||||
public Json_Value(Json_enum_type t, Number val){
|
||||
this.m_type = t;
|
||||
if (t == Json_enum_type.DOUBLE) {
|
||||
this.m_double_value = val.doubleValue();
|
||||
} else {
|
||||
this.m_int_value = val;
|
||||
}
|
||||
}
|
||||
|
||||
public Json_Value(Json_enum_type t, String value){
|
||||
this.m_type = t;
|
||||
this.m_string_value = value;
|
||||
}
|
||||
|
||||
public Json_Value(int field_type, LogBuffer data, long bytes){
|
||||
this.m_type = Json_enum_type.OPAQUE; // 不确定类型
|
||||
this.m_field_type = field_type;
|
||||
this.m_data = data;
|
||||
this.m_length = bytes;
|
||||
}
|
||||
|
||||
public Json_Value(Json_enum_type t, LogBuffer data, long element_count, long bytes, boolean large){
|
||||
this.m_type = t;
|
||||
this.m_data = data;
|
||||
this.m_element_count = element_count;
|
||||
this.m_length = bytes;
|
||||
this.m_large = large;
|
||||
}
|
||||
|
||||
public String key(int i, String charsetName) {
|
||||
m_data.rewind();
|
||||
int offset_size = m_large ? LARGE_OFFSET_SIZE : SMALL_OFFSET_SIZE;
|
||||
int key_entry_size = m_large ? KEY_ENTRY_SIZE_LARGE : KEY_ENTRY_SIZE_SMALL;
|
||||
int entry_offset = 2 * offset_size + key_entry_size * i;
|
||||
// The offset of the key is the first part of the key
|
||||
// entry.
|
||||
m_data.forward(entry_offset);
|
||||
long key_offset = read_offset_or_size(m_data, m_large);
|
||||
// The length of the key is the second part of the
|
||||
// entry, always two
|
||||
// bytes.
|
||||
long key_length = m_data.getUint16();
|
||||
return m_data.rewind().forward((int) key_offset).getFixString((int) key_length, charsetName);
|
||||
}
|
||||
|
||||
public Json_Value element(int i, String charsetName) {
|
||||
m_data.rewind();
|
||||
int offset_size = m_large ? LARGE_OFFSET_SIZE : SMALL_OFFSET_SIZE;
|
||||
int key_entry_size = m_large ? KEY_ENTRY_SIZE_LARGE : KEY_ENTRY_SIZE_SMALL;
|
||||
int value_entry_size = m_large ? VALUE_ENTRY_SIZE_LARGE : VALUE_ENTRY_SIZE_SMALL;
|
||||
int first_entry_offset = 2 * offset_size;
|
||||
if (m_type == Json_enum_type.OBJECT) {
|
||||
first_entry_offset += m_element_count * key_entry_size;
|
||||
}
|
||||
int entry_offset = first_entry_offset + value_entry_size * i;
|
||||
int type = m_data.forward(entry_offset).getUint8();
|
||||
if (type == JSONB_TYPE_INT16 || type == JSONB_TYPE_UINT16 || type == JSONB_TYPE_LITERAL
|
||||
|| (m_large && (type == JSONB_TYPE_INT32 || type == JSONB_TYPE_UINT32))) {
|
||||
return parse_scalar(type, m_data, value_entry_size - 1, charsetName);
|
||||
}
|
||||
int value_offset = (int) read_offset_or_size(m_data, m_large);
|
||||
return parse_value(type, m_data.rewind().forward(value_offset), (int) m_length - value_offset, charsetName);
|
||||
}
|
||||
|
||||
public StringBuilder toJsonString(StringBuilder buf, String charsetName) {
|
||||
switch (m_type) {
|
||||
case OBJECT:
|
||||
buf.append("{");
|
||||
for (int i = 0; i < m_element_count; ++i) {
|
||||
if (i > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append('"').append(key(i, charsetName)).append('"');
|
||||
buf.append(": ");
|
||||
element(i, charsetName).toJsonString(buf, charsetName);
|
||||
}
|
||||
buf.append("}");
|
||||
break;
|
||||
case ARRAY:
|
||||
buf.append("[");
|
||||
for (int i = 0; i < m_element_count; ++i) {
|
||||
if (i > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
element(i, charsetName).toJsonString(buf, charsetName);
|
||||
}
|
||||
buf.append("]");
|
||||
break;
|
||||
case DOUBLE:
|
||||
buf.append(Double.valueOf(m_double_value).toString());
|
||||
break;
|
||||
case INT:
|
||||
buf.append(m_int_value.toString());
|
||||
break;
|
||||
case UINT:
|
||||
buf.append(m_int_value.toString());
|
||||
break;
|
||||
case LITERAL_FALSE:
|
||||
buf.append("false");
|
||||
break;
|
||||
case LITERAL_TRUE:
|
||||
buf.append("true");
|
||||
break;
|
||||
case LITERAL_NULL:
|
||||
buf.append("NULL");
|
||||
break;
|
||||
case OPAQUE:
|
||||
String text = null;
|
||||
if (m_field_type == LogEvent.MYSQL_TYPE_NEWDECIMAL) {
|
||||
int precision = m_data.getInt8();
|
||||
int scale = m_data.getInt8();
|
||||
text = m_data.getDecimal(precision, scale).toPlainString();
|
||||
buf.append(text);
|
||||
} else if (m_field_type == LogEvent.MYSQL_TYPE_TIME) {
|
||||
long packed_value = m_data.getLong64();
|
||||
if (packed_value == 0) {
|
||||
text = "00:00:00";
|
||||
} else {
|
||||
long ultime = Math.abs(packed_value);
|
||||
long intpart = ultime >> 24;
|
||||
int frac = (int) (ultime % (1L << 24));
|
||||
text = String.format("%s%02d:%02d:%02d",
|
||||
packed_value >= 0 ? "" : "-",
|
||||
(int) ((intpart >> 12) % (1 << 10)),
|
||||
(int) ((intpart >> 6) % (1 << 6)),
|
||||
(int) (intpart % (1 << 6)));
|
||||
text = text + "." + usecondsToStr(frac, 6);
|
||||
}
|
||||
buf.append('"').append(text).append('"');
|
||||
} else if (m_field_type == LogEvent.MYSQL_TYPE_DATE || m_field_type == LogEvent.MYSQL_TYPE_DATETIME
|
||||
|| m_field_type == LogEvent.MYSQL_TYPE_TIMESTAMP) {
|
||||
long packed_value = m_data.getLong64();
|
||||
if (packed_value == 0) {
|
||||
text = "0000-00-00 00:00:00";
|
||||
} else {
|
||||
// 构造TimeStamp只处理到秒
|
||||
long ultime = Math.abs(packed_value);
|
||||
long intpart = ultime >> 24;
|
||||
int frac = (int) (ultime % (1L << 24));
|
||||
long ymd = intpart >> 17;
|
||||
long ym = ymd >> 5;
|
||||
long hms = intpart % (1 << 17);
|
||||
text = String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
||||
(int) (ym / 13),
|
||||
(int) (ym % 13),
|
||||
(int) (ymd % (1 << 5)),
|
||||
(int) (hms >> 12),
|
||||
(int) ((hms >> 6) % (1 << 6)),
|
||||
(int) (hms % (1 << 6)));
|
||||
text = text + "." + usecondsToStr(frac, 6);
|
||||
}
|
||||
buf.append('"').append(text).append('"');
|
||||
} else {
|
||||
text = m_data.getFixString((int) m_length, charsetName);
|
||||
buf.append('"').append(escapse(text)).append('"');
|
||||
}
|
||||
|
||||
break;
|
||||
case STRING:
|
||||
buf.append('"').append(escapse(m_string_value)).append('"');
|
||||
break;
|
||||
case ERROR:
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
private static StringBuilder escapse(String data) {
|
||||
StringBuilder sb = new StringBuilder(data.length());
|
||||
int endIndex = data.length();
|
||||
for (int i = 0; i < endIndex; ++i) {
|
||||
char c = data.charAt(i);
|
||||
if (c == '"') {
|
||||
sb.append('\\');
|
||||
} else if (c == '\\') {
|
||||
sb.append("\\");
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
public static enum Json_enum_type {
|
||||
OBJECT, ARRAY, STRING, INT, UINT, DOUBLE, LITERAL_NULL, LITERAL_TRUE, LITERAL_FALSE, OPAQUE, ERROR
|
||||
}
|
||||
|
||||
private static String usecondsToStr(int frac, int meta) {
|
||||
String sec = String.valueOf(frac);
|
||||
if (meta > 6) {
|
||||
throw new IllegalArgumentException("unknow useconds meta : " + meta);
|
||||
}
|
||||
|
||||
if (sec.length() < 6) {
|
||||
StringBuilder result = new StringBuilder(6);
|
||||
int len = 6 - sec.length();
|
||||
for (; len > 0; len--) {
|
||||
result.append('0');
|
||||
}
|
||||
result.append(sec);
|
||||
sec = result.toString();
|
||||
}
|
||||
|
||||
return sec.substring(0, meta);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ public class LogBuffer {
|
||||
|
||||
protected int origin, limit;
|
||||
protected int position;
|
||||
protected int semival;
|
||||
|
||||
protected LogBuffer(){
|
||||
}
|
||||
@@ -168,8 +169,8 @@ public class LogBuffer {
|
||||
* @param newLimit The new limit value; must be non-negative and no larger
|
||||
* than this buffer's capacity
|
||||
* @return This buffer
|
||||
* @throws IllegalArgumentException If the preconditions on <tt>newLimit</tt>
|
||||
* do not hold
|
||||
* @throws IllegalArgumentException If the preconditions on
|
||||
* <tt>newLimit</tt> do not hold
|
||||
*/
|
||||
public final LogBuffer limit(int newLimit) {
|
||||
if (origin + newLimit > buffer.length || newLimit < 0) throw new IllegalArgumentException("capacity excceed: "
|
||||
|
||||
@@ -58,7 +58,6 @@ public final class LogContext {
|
||||
|
||||
public void reset() {
|
||||
formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
||||
|
||||
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.StopLogEvent;
|
||||
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.UpdateRowsLogEvent;
|
||||
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.XaPrepareLogEvent;
|
||||
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.BinlogCheckPointLogEvent;
|
||||
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.StartEncryptionLogEvent;
|
||||
|
||||
/**
|
||||
* Implements a binary-log decoder.
|
||||
@@ -104,9 +108,16 @@ public final class LogDecoder {
|
||||
try {
|
||||
/* Decoding binary-log to event */
|
||||
event = decode(buffer, header, context);
|
||||
if (event != null) {
|
||||
// set logFileName
|
||||
event.getHeader().setLogFileName(context.getLogPosition().getFileName());
|
||||
event.setSemival(buffer.semival);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (logger.isWarnEnabled()) logger.warn("Decoding " + LogEvent.getTypeName(header.getType())
|
||||
+ " failed from: " + context.getLogPosition(), e);
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Decoding " + LogEvent.getTypeName(header.getType()) + " failed from: "
|
||||
+ context.getLogPosition(), e);
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
buffer.limit(limit); /* Restore limit */
|
||||
@@ -361,6 +372,24 @@ public final class LogDecoder {
|
||||
logPosition.position = header.getLogPos();
|
||||
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: {
|
||||
AnnotateRowsEvent event = new AnnotateRowsEvent(header, buffer, descriptionEvent);
|
||||
/* updating position in context */
|
||||
@@ -385,6 +414,12 @@ public final class LogDecoder {
|
||||
logPosition.position = header.getLogPos();
|
||||
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:
|
||||
/*
|
||||
* Create an object of Ignorable_log_event for unrecognized
|
||||
@@ -397,9 +432,10 @@ public final class LogDecoder {
|
||||
logPosition.position = header.getLogPos();
|
||||
return event;
|
||||
} else {
|
||||
if (logger.isWarnEnabled()) logger.warn("Skipping unrecognized binlog event "
|
||||
+ LogEvent.getTypeName(header.getType()) + " from: "
|
||||
+ context.getLogPosition());
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Skipping unrecognized binlog event " + LogEvent.getTypeName(header.getType())
|
||||
+ " from: " + context.getLogPosition());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -161,9 +161,17 @@ public abstract class LogEvent {
|
||||
|
||||
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
|
||||
/* 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;
|
||||
/* 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 START_ENCRYPTION_EVENT = 164;
|
||||
|
||||
/** 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
|
||||
@@ -267,6 +277,7 @@ public abstract class LogEvent {
|
||||
public static final int MYSQL_TYPE_TIMESTAMP2 = 17;
|
||||
public static final int MYSQL_TYPE_DATETIME2 = 18;
|
||||
public static final int MYSQL_TYPE_TIME2 = 19;
|
||||
public static final int MYSQL_TYPE_JSON = 245;
|
||||
public static final int MYSQL_TYPE_NEWDECIMAL = 246;
|
||||
public static final int MYSQL_TYPE_ENUM = 247;
|
||||
public static final int MYSQL_TYPE_SET = 248;
|
||||
@@ -359,6 +370,24 @@ public abstract class LogEvent {
|
||||
|
||||
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){
|
||||
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 IGNORABLE_HEADER_LEN = 0;
|
||||
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 BINLOG_CHECKPOINT_HEADER_LEN = 4;
|
||||
public static final int GTID_HEADER_LEN = 19;
|
||||
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;
|
||||
|
||||
@@ -141,6 +146,11 @@ public final class FormatDescriptionLogEvent extends StartLogEventV3 {
|
||||
}
|
||||
}
|
||||
|
||||
public FormatDescriptionLogEvent(final int binlogVersion, int binlogChecksum){
|
||||
this(binlogVersion);
|
||||
this.header.checksumAlg = binlogChecksum;
|
||||
}
|
||||
|
||||
public FormatDescriptionLogEvent(final int binlogVersion){
|
||||
this.binlogVersion = binlogVersion;
|
||||
|
||||
@@ -197,11 +207,17 @@ public final class FormatDescriptionLogEvent extends StartLogEventV3 {
|
||||
postHeaderLen[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[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
|
||||
postHeaderLen[ANNOTATE_ROWS_EVENT - 1] = ANNOTATE_ROWS_HEADER_LEN;
|
||||
postHeaderLen[BINLOG_CHECKPOINT_EVENT - 1] = BINLOG_CHECKPOINT_HEADER_LEN;
|
||||
postHeaderLen[GTID_EVENT - 1] = GTID_HEADER_LEN;
|
||||
postHeaderLen[GTID_LIST_EVENT - 1] = GTID_LIST_HEADER_LEN;
|
||||
postHeaderLen[START_ENCRYPTION_EVENT - 1] = START_ENCRYPTION_HEADER_LEN;
|
||||
break;
|
||||
|
||||
case 3: /* 4.0.x x>=2 */
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
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.LogEvent;
|
||||
|
||||
@@ -14,8 +17,13 @@ public class GtidLogEvent extends LogEvent {
|
||||
public static final int ENCODED_FLAG_LENGTH = 1;
|
||||
// / Length of SID in event encoding
|
||||
public static final int ENCODED_SID_LENGTH = 16;
|
||||
public static final int LOGICAL_TIMESTAMP_TYPE_CODE = 2;
|
||||
|
||||
private boolean commitFlag;
|
||||
private UUID sid;
|
||||
private long gno;
|
||||
private Long lastCommitted;
|
||||
private Long sequenceNumber;
|
||||
|
||||
public GtidLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
||||
super(header);
|
||||
@@ -27,6 +35,23 @@ public class GtidLogEvent extends LogEvent {
|
||||
buffer.position(commonHeaderLen);
|
||||
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
|
||||
// sid.copy_from((uchar *)ptr_buffer);
|
||||
// ptr_buffer+= ENCODED_SID_LENGTH;
|
||||
@@ -42,4 +67,19 @@ public class GtidLogEvent extends LogEvent {
|
||||
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
|
||||
|
||||
/**
|
||||
* binlog fileName
|
||||
*/
|
||||
protected String logFileName;
|
||||
|
||||
/* for Start_event_v3 */
|
||||
public LogHeader(final int type){
|
||||
this.type = type;
|
||||
@@ -270,6 +275,14 @@ public final class LogHeader {
|
||||
return checksumAlg;
|
||||
}
|
||||
|
||||
public String getLogFileName() {
|
||||
return logFileName;
|
||||
}
|
||||
|
||||
public void setLogFileName(String logFileName) {
|
||||
this.logFileName = logFileName;
|
||||
}
|
||||
|
||||
private void processCheckSum(LogBuffer buffer) {
|
||||
if (checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_OFF && checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_UNDEF) {
|
||||
crc = buffer.getUint32(eventLen - LogEvent.BINLOG_CHECKSUM_LEN);
|
||||
|
||||
@@ -330,17 +330,14 @@ public class QueryLogEvent extends LogEvent {
|
||||
* MariaDb type,
|
||||
* sec_part of NOW()
|
||||
*/
|
||||
+ 1 + (MAX_DBS_IN_EVENT_MTS * (1 + NAME_LEN)) + 3 + 1 + 16 + 1 + 60/*
|
||||
* type
|
||||
* ,
|
||||
* user_len
|
||||
* ,
|
||||
* user
|
||||
* ,
|
||||
* host_len
|
||||
* ,
|
||||
* host
|
||||
*/);
|
||||
+ 1 + (MAX_DBS_IN_EVENT_MTS * (1 + NAME_LEN)) + 3 + 1 + 32 * 3
|
||||
+ 1 + 60/*
|
||||
* type ,
|
||||
* user_len ,
|
||||
* user ,
|
||||
* host_len ,
|
||||
* host
|
||||
*/);
|
||||
/**
|
||||
* Fixed data part:
|
||||
* <ul>
|
||||
@@ -397,7 +394,7 @@ public class QueryLogEvent extends LogEvent {
|
||||
// inspection by the DBA
|
||||
private final long execTime;
|
||||
private final int errorCode;
|
||||
private final long sessionId; /* thread_id */
|
||||
private final long sessionId; /* thread_id */
|
||||
|
||||
/**
|
||||
* 'flags2' is a second set of flags (on top of those in Log_event), for
|
||||
@@ -499,26 +496,26 @@ public class QueryLogEvent extends LogEvent {
|
||||
}
|
||||
|
||||
/* query event post-header */
|
||||
public static final int Q_THREAD_ID_OFFSET = 0;
|
||||
public static final int Q_EXEC_TIME_OFFSET = 4;
|
||||
public static final int Q_DB_LEN_OFFSET = 8;
|
||||
public static final int Q_ERR_CODE_OFFSET = 9;
|
||||
public static final int Q_STATUS_VARS_LEN_OFFSET = 11;
|
||||
public static final int Q_DATA_OFFSET = QUERY_HEADER_LEN;
|
||||
public static final int Q_THREAD_ID_OFFSET = 0;
|
||||
public static final int Q_EXEC_TIME_OFFSET = 4;
|
||||
public static final int Q_DB_LEN_OFFSET = 8;
|
||||
public static final int Q_ERR_CODE_OFFSET = 9;
|
||||
public static final int Q_STATUS_VARS_LEN_OFFSET = 11;
|
||||
public static final int Q_DATA_OFFSET = QUERY_HEADER_LEN;
|
||||
|
||||
/* these are codes, not offsets; not more than 256 values (1 byte). */
|
||||
public static final int Q_FLAGS2_CODE = 0;
|
||||
public static final int Q_SQL_MODE_CODE = 1;
|
||||
public static final int Q_FLAGS2_CODE = 0;
|
||||
public static final int Q_SQL_MODE_CODE = 1;
|
||||
|
||||
/**
|
||||
* Q_CATALOG_CODE is catalog with end zero stored; it is used only by MySQL
|
||||
* 5.0.x where 0<=x<=3. We have to keep it to be able to replicate these old
|
||||
* masters.
|
||||
*/
|
||||
public static final int Q_CATALOG_CODE = 2;
|
||||
public static final int Q_AUTO_INCREMENT = 3;
|
||||
public static final int Q_CHARSET_CODE = 4;
|
||||
public static final int Q_TIME_ZONE_CODE = 5;
|
||||
public static final int Q_CATALOG_CODE = 2;
|
||||
public static final int Q_AUTO_INCREMENT = 3;
|
||||
public static final int Q_CHARSET_CODE = 4;
|
||||
public static final int Q_TIME_ZONE_CODE = 5;
|
||||
|
||||
/**
|
||||
* Q_CATALOG_NZ_CODE is catalog withOUT end zero stored; it is used by MySQL
|
||||
@@ -528,31 +525,47 @@ public class QueryLogEvent extends LogEvent {
|
||||
* would crash (segfault etc) because it would expect a 0 when there is
|
||||
* none.
|
||||
*/
|
||||
public static final int Q_CATALOG_NZ_CODE = 6;
|
||||
public static final int Q_CATALOG_NZ_CODE = 6;
|
||||
|
||||
public static final int Q_LC_TIME_NAMES_CODE = 7;
|
||||
public static final int Q_LC_TIME_NAMES_CODE = 7;
|
||||
|
||||
public static final int Q_CHARSET_DATABASE_CODE = 8;
|
||||
public static final int Q_CHARSET_DATABASE_CODE = 8;
|
||||
|
||||
public static final int Q_TABLE_MAP_FOR_UPDATE_CODE = 9;
|
||||
public static final int Q_TABLE_MAP_FOR_UPDATE_CODE = 9;
|
||||
|
||||
public static final int Q_MASTER_DATA_WRITTEN_CODE = 10;
|
||||
public static final int Q_MASTER_DATA_WRITTEN_CODE = 10;
|
||||
|
||||
public static final int Q_INVOKER = 11;
|
||||
public static final int Q_INVOKER = 11;
|
||||
|
||||
/**
|
||||
* Q_UPDATED_DB_NAMES status variable collects of the updated databases
|
||||
* total number and their names to be propagated to the slave in order to
|
||||
* facilitate the parallel applying of the Query events.
|
||||
*/
|
||||
public static final int Q_UPDATED_DB_NAMES = 12;
|
||||
public static final int Q_UPDATED_DB_NAMES = 12;
|
||||
|
||||
public static final int Q_MICROSECONDS = 13;
|
||||
public static final int Q_MICROSECONDS = 13;
|
||||
/**
|
||||
* A old (unused now) code for Query_log_event status similar to
|
||||
* G_COMMIT_TS.
|
||||
*/
|
||||
public static final int Q_COMMIT_TS = 14;
|
||||
/**
|
||||
* A code for Query_log_event status, similar to G_COMMIT_TS2.
|
||||
*/
|
||||
public static final int Q_COMMIT_TS2 = 15;
|
||||
/**
|
||||
* The master connection @@session.explicit_defaults_for_timestamp which is
|
||||
* recorded for queries, CREATE and ALTER table that is defined with a
|
||||
* TIMESTAMP column, that are dependent on that feature. For pre-WL6292
|
||||
* master's the associated with this code value is zero.
|
||||
*/
|
||||
public static final int Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP = 16;
|
||||
|
||||
/**
|
||||
* FROM MariaDB 5.5.34
|
||||
*/
|
||||
public static final int Q_HRNOW = 128;
|
||||
public static final int Q_HRNOW = 128;
|
||||
|
||||
private final void unpackVariables(LogBuffer buffer, final int end) throws IOException {
|
||||
int code = -1;
|
||||
@@ -632,6 +645,9 @@ public class QueryLogEvent extends LogEvent {
|
||||
mtsAccessedDbNames[i] = buffer.getFixString(length < NAME_LEN ? length : NAME_LEN);
|
||||
}
|
||||
break;
|
||||
case Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP:
|
||||
buffer.forward(1);
|
||||
break;
|
||||
case Q_HRNOW:
|
||||
// int when_sec_part = buffer.getUint24();
|
||||
buffer.forward(3);
|
||||
|
||||
@@ -90,6 +90,7 @@ public final class RotateLogEvent extends LogEvent {
|
||||
int filenameLen = buffer.limit() - filenameOffset;
|
||||
if (filenameLen > FN_REFLEN - 1) filenameLen = FN_REFLEN - 1;
|
||||
buffer.position(filenameOffset);
|
||||
|
||||
filename = buffer.getFixString(filenameLen);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import java.util.BitSet;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import com.taobao.tddl.dbsync.binlog.JsonConversion;
|
||||
import com.taobao.tddl.dbsync.binlog.JsonConversion.Json_Value;
|
||||
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||
|
||||
@@ -25,6 +27,8 @@ public final class RowsLogBuffer {
|
||||
public static final long DATETIMEF_INT_OFS = 0x8000000000L;
|
||||
public static final long TIMEF_INT_OFS = 0x800000L;
|
||||
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 int columnLen;
|
||||
private final String charsetName;
|
||||
@@ -163,10 +167,13 @@ public final class RowsLogBuffer {
|
||||
|
||||
case LogEvent.MYSQL_TYPE_TIMESTAMP:
|
||||
case LogEvent.MYSQL_TYPE_DATETIME:
|
||||
case LogEvent.MYSQL_TYPE_TIMESTAMP2:
|
||||
case LogEvent.MYSQL_TYPE_DATETIME2:
|
||||
javaType = Types.TIMESTAMP;
|
||||
break;
|
||||
|
||||
case LogEvent.MYSQL_TYPE_TIME:
|
||||
case LogEvent.MYSQL_TYPE_TIME2:
|
||||
javaType = Types.TIME;
|
||||
break;
|
||||
|
||||
@@ -474,13 +481,27 @@ public final class RowsLogBuffer {
|
||||
// cal.set(d / 10000, (d % 10000) / 100 - 1, d % 100, t /
|
||||
// 10000, (t % 10000) / 100, t % 100);
|
||||
// value = new Timestamp(cal.getTimeInMillis());
|
||||
value = String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
||||
d / 10000,
|
||||
(d % 10000) / 100,
|
||||
d % 100,
|
||||
t / 10000,
|
||||
(t % 10000) / 100,
|
||||
t % 100);
|
||||
// value = String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
||||
// d / 10000,
|
||||
// (d % 10000) / 100,
|
||||
// d % 100,
|
||||
// t / 10000,
|
||||
// (t % 10000) / 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;
|
||||
length = 8;
|
||||
@@ -534,13 +555,27 @@ public final class RowsLogBuffer {
|
||||
// % (1 << 5)), (int) (hms >> 12),
|
||||
// (int) ((hms >> 6) % (1 << 6)), (int) (hms % (1 << 6)));
|
||||
// value = new Timestamp(cal.getTimeInMillis());
|
||||
second = String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
||||
(int) (ym / 13),
|
||||
(int) (ym % 13),
|
||||
(int) (ymd % (1 << 5)),
|
||||
(int) (hms >> 12),
|
||||
(int) ((hms >> 6) % (1 << 6)),
|
||||
(int) (hms % (1 << 6)));
|
||||
// second = String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
||||
// (int) (ym / 13),
|
||||
// (int) (ym % 13),
|
||||
// (int) (ymd % (1 << 5)),
|
||||
// (int) (hms >> 12),
|
||||
// (int) ((hms >> 6) % (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) {
|
||||
@@ -569,11 +604,28 @@ public final class RowsLogBuffer {
|
||||
// cal.set(70, 0, 1, i32 / 10000, (i32 % 10000) / 100, i32 %
|
||||
// 100);
|
||||
// value = new Time(cal.getTimeInMillis());
|
||||
value = String.format("%s%02d:%02d:%02d",
|
||||
(i32 >= 0) ? "" : "-",
|
||||
u32 / 10000,
|
||||
(u32 % 10000) / 100,
|
||||
u32 % 100);
|
||||
// value = String.format("%s%02d:%02d:%02d",
|
||||
// (i32 >= 0) ? "" : "-",
|
||||
// u32 / 10000,
|
||||
// (u32 % 10000) / 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;
|
||||
length = 3;
|
||||
@@ -667,11 +719,28 @@ public final class RowsLogBuffer {
|
||||
// value = new Time(cal.getTimeInMillis());
|
||||
long ultime = Math.abs(ltime);
|
||||
intpart = ultime >> 24;
|
||||
second = String.format("%s%02d:%02d:%02d",
|
||||
ltime >= 0 ? "" : "-",
|
||||
(int) ((intpart >> 12) % (1 << 10)),
|
||||
(int) ((intpart >> 6) % (1 << 6)),
|
||||
(int) (intpart % (1 << 6)));
|
||||
// second = String.format("%s%02d:%02d:%02d",
|
||||
// ltime >= 0 ? "" : "-",
|
||||
// (int) ((intpart >> 12) % (1 << 10)),
|
||||
// (int) ((intpart >> 6) % (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) {
|
||||
@@ -711,7 +780,16 @@ public final class RowsLogBuffer {
|
||||
// cal.set((i32 / (16 * 32)), (i32 / 32 % 16) - 1, (i32 %
|
||||
// 32));
|
||||
// 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;
|
||||
length = 3;
|
||||
@@ -942,6 +1020,43 @@ public final class RowsLogBuffer {
|
||||
length = len;
|
||||
break;
|
||||
}
|
||||
case LogEvent.MYSQL_TYPE_JSON: {
|
||||
switch (meta) {
|
||||
case 1: {
|
||||
len = buffer.getUint8();
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
len = buffer.getUint16();
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
len = buffer.getUint24();
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
len = (int) buffer.getUint32();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException("!! Unknown JSON packlen = " + meta);
|
||||
}
|
||||
if (0 == len) {
|
||||
// fixed issue #1 by lava, json column of zero length has no
|
||||
// value, value parsing should be skipped
|
||||
value = "";
|
||||
} else {
|
||||
int position = buffer.position();
|
||||
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(), buffer, len - 1, charsetName);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
jsonValue.toJsonString(builder, charsetName);
|
||||
value = builder.toString();
|
||||
buffer.position(position + len);
|
||||
}
|
||||
javaType = Types.VARCHAR;
|
||||
length = len;
|
||||
break;
|
||||
}
|
||||
case LogEvent.MYSQL_TYPE_GEOMETRY: {
|
||||
/*
|
||||
* MYSQL_TYPE_GEOMETRY: copy from BLOB or TEXT
|
||||
@@ -1023,4 +1138,33 @@ public final class RowsLogBuffer {
|
||||
|
||||
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) {
|
||||
return new RowsLogBuffer(rowsBuf.duplicate(), columnLen, charsetName);
|
||||
return new RowsLogBuffer(rowsBuf, columnLen, charsetName);
|
||||
}
|
||||
|
||||
public final int getFlags(final int flags) {
|
||||
|
||||
@@ -402,6 +402,7 @@ public final class TableMapLogEvent extends LogEvent {
|
||||
case MYSQL_TYPE_DOUBLE:
|
||||
case MYSQL_TYPE_FLOAT:
|
||||
case MYSQL_TYPE_GEOMETRY:
|
||||
case MYSQL_TYPE_JSON:
|
||||
/*
|
||||
* These types store a single byte.
|
||||
*/
|
||||
|
||||
@@ -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 {
|
||||
|
||||
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){
|
||||
super(header, buffer, descriptionEvent);
|
||||
gtid = buffer.getUlong64().longValue();
|
||||
// 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.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 {
|
||||
|
||||
@Test
|
||||
@@ -24,56 +15,54 @@ public class DirectLogFetcherTest extends BaseLogFetcherTest {
|
||||
DirectLogFetcher fecther = new DirectLogFetcher();
|
||||
try {
|
||||
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.execute("SET @master_binlog_checksum='@@global.binlog_checksum'");
|
||||
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();
|
||||
while (fecther.fetch()) {
|
||||
LogEvent event = null;
|
||||
event = decoder.decode(fecther, context);
|
||||
|
||||
if (event == null) {
|
||||
continue;
|
||||
// throw new RuntimeException("parse failed");
|
||||
}
|
||||
|
||||
int eventType = event.getHeader().getType();
|
||||
switch (eventType) {
|
||||
case LogEvent.ROTATE_EVENT:
|
||||
binlogFileName = ((RotateLogEvent) event).getFilename();
|
||||
break;
|
||||
case LogEvent.WRITE_ROWS_EVENT_V1:
|
||||
case LogEvent.WRITE_ROWS_EVENT:
|
||||
parseRowsEvent((WriteRowsLogEvent) event);
|
||||
break;
|
||||
case LogEvent.UPDATE_ROWS_EVENT_V1:
|
||||
case LogEvent.UPDATE_ROWS_EVENT:
|
||||
parseRowsEvent((UpdateRowsLogEvent) event);
|
||||
break;
|
||||
case LogEvent.DELETE_ROWS_EVENT_V1:
|
||||
case LogEvent.DELETE_ROWS_EVENT:
|
||||
parseRowsEvent((DeleteRowsLogEvent) event);
|
||||
break;
|
||||
case LogEvent.QUERY_EVENT:
|
||||
parseQueryEvent((QueryLogEvent) event);
|
||||
break;
|
||||
case LogEvent.ROWS_QUERY_LOG_EVENT:
|
||||
parseRowsQueryEvent((RowsQueryLogEvent) event);
|
||||
break;
|
||||
case LogEvent.ANNOTATE_ROWS_EVENT:
|
||||
parseAnnotateRowsEvent((AnnotateRowsEvent) event);
|
||||
break;
|
||||
case LogEvent.XID_EVENT:
|
||||
parseXidEvent((XidLogEvent) event);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
decoder.decode(fecther, context);
|
||||
continue;
|
||||
// if (event == null) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// int eventType = event.getHeader().getType();
|
||||
// switch (eventType) {
|
||||
// case LogEvent.ROTATE_EVENT:
|
||||
// binlogFileName = ((RotateLogEvent) event).getFilename();
|
||||
// break;
|
||||
// case LogEvent.WRITE_ROWS_EVENT_V1:
|
||||
// case LogEvent.WRITE_ROWS_EVENT:
|
||||
// parseRowsEvent((WriteRowsLogEvent) event);
|
||||
// break;
|
||||
// case LogEvent.UPDATE_ROWS_EVENT_V1:
|
||||
// case LogEvent.UPDATE_ROWS_EVENT:
|
||||
// parseRowsEvent((UpdateRowsLogEvent) event);
|
||||
// break;
|
||||
// case LogEvent.DELETE_ROWS_EVENT_V1:
|
||||
// case LogEvent.DELETE_ROWS_EVENT:
|
||||
// parseRowsEvent((DeleteRowsLogEvent) event);
|
||||
// break;
|
||||
// case LogEvent.QUERY_EVENT:
|
||||
// parseQueryEvent((QueryLogEvent) event);
|
||||
// break;
|
||||
// case LogEvent.ROWS_QUERY_LOG_EVENT:
|
||||
// parseRowsQueryEvent((RowsQueryLogEvent) event);
|
||||
// break;
|
||||
// case LogEvent.ANNOTATE_ROWS_EVENT:
|
||||
// parseAnnotateRowsEvent((AnnotateRowsEvent) event);
|
||||
// break;
|
||||
// case LogEvent.XID_EVENT:
|
||||
// parseXidEvent((XidLogEvent) event);
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
@@ -16,6 +16,14 @@
|
||||
<artifactId>canal.server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 这里指定runtime的metrics provider-->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal.prometheus</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<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
|
||||
@@ -16,6 +16,7 @@ public class CanalConstants {
|
||||
public static final String CANAL_IP = ROOT + "." + "ip";
|
||||
public static final String CANAL_PORT = ROOT + "." + "port";
|
||||
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_AUTO_SCAN = ROOT + "." + "auto.scan";
|
||||
@@ -32,6 +33,8 @@ public class CanalConstants {
|
||||
|
||||
public static final String CANAL_DESTINATION_PROPERTY = ROOT + ".instance.destination";
|
||||
|
||||
public static final String CANAL_SOCKETCHANNEL = ROOT + "." + "socketChannel";
|
||||
|
||||
public static String getInstanceModeKey(String destination) {
|
||||
return MessageFormat.format(INSTANCE_MODE_TEMPLATE, destination);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Properties;
|
||||
|
||||
import org.I0Itec.zkclient.IZkStateListener;
|
||||
import org.I0Itec.zkclient.exception.ZkNoNodeException;
|
||||
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.zookeeper.Watcher.Event.KeeperState;
|
||||
@@ -33,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.ManagerCanalInstanceGenerator;
|
||||
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.exception.CanalServerException;
|
||||
import com.alibaba.otter.canal.server.netty.CanalServerWithNetty;
|
||||
@@ -42,7 +44,7 @@ import com.google.common.collect.MigrateMap;
|
||||
|
||||
/**
|
||||
* canal调度控制器
|
||||
*
|
||||
*
|
||||
* @author jianghang 2012-11-8 下午12:03:11
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@@ -84,15 +86,24 @@ public class CanalController {
|
||||
// 初始化instance config
|
||||
initInstanceConfig(properties);
|
||||
|
||||
// init socketChannel
|
||||
String socketChannel = getProperty(properties, CanalConstants.CANAL_SOCKETCHANNEL);
|
||||
if (StringUtils.isNotEmpty(socketChannel)) {
|
||||
System.setProperty(CanalConstants.CANAL_SOCKETCHANNEL, socketChannel);
|
||||
}
|
||||
|
||||
// 准备canal server
|
||||
cid = Long.valueOf(getProperty(properties, CanalConstants.CANAL_ID));
|
||||
ip = getProperty(properties, CanalConstants.CANAL_IP);
|
||||
port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT));
|
||||
embededCanalServer = new CanalServerWithEmbedded();
|
||||
embededCanalServer = CanalServerWithEmbedded.instance();
|
||||
embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator
|
||||
canalServer = new CanalServerWithNetty(embededCanalServer);
|
||||
canalServer.setIp(ip);
|
||||
canalServer.setPort(port);
|
||||
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中
|
||||
if (StringUtils.isEmpty(ip)) {
|
||||
@@ -148,6 +159,11 @@ public class CanalController {
|
||||
public void handleNewSession() throws Exception {
|
||||
initCid(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSessionEstablishmentError(Throwable error) throws Exception {
|
||||
logger.error("failed to connect to zookeeper", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
@@ -172,6 +188,8 @@ public class CanalController {
|
||||
if (zkclientx != null) {
|
||||
runningMonitor.setZkClient(zkclientx);
|
||||
}
|
||||
// 触发创建一下cid节点
|
||||
runningMonitor.init();
|
||||
return runningMonitor;
|
||||
}
|
||||
}));
|
||||
@@ -189,10 +207,10 @@ public class CanalController {
|
||||
instanceConfigs.put(destination, config);
|
||||
}
|
||||
|
||||
if (!config.getLazy() && !embededCanalServer.isStart(destination)) {
|
||||
if (!embededCanalServer.isStart(destination)) {
|
||||
// HA机制启动
|
||||
ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination);
|
||||
if (!runningMonitor.isStart()) {
|
||||
if (!config.getLazy() && !runningMonitor.isStart()) {
|
||||
runningMonitor.start();
|
||||
}
|
||||
}
|
||||
@@ -231,7 +249,13 @@ public class CanalController {
|
||||
if (StringUtils.isEmpty(rootDir)) {
|
||||
rootDir = "../conf";
|
||||
}
|
||||
monitor.setRootConf(rootDir);
|
||||
|
||||
if (StringUtils.equals("otter-canal", System.getProperty("appName"))) {
|
||||
monitor.setRootConf(rootDir);
|
||||
} else {
|
||||
// eclipse debug模式
|
||||
monitor.setRootConf("src/main/resources/");
|
||||
}
|
||||
return monitor;
|
||||
} else if (mode.isManager()) {
|
||||
return new ManagerInstanceConfigMonitor();
|
||||
@@ -280,7 +304,7 @@ public class CanalController {
|
||||
return instanceGenerator.generate(destination);
|
||||
} else if (config.getMode().isSpring()) {
|
||||
SpringCanalInstanceGenerator instanceGenerator = new SpringCanalInstanceGenerator();
|
||||
synchronized (this) {
|
||||
synchronized (CanalEventParser.class) {
|
||||
try {
|
||||
// 设置当前正在加载的通道,加载spring查找文件时会用到该变量
|
||||
System.setProperty(CanalConstants.CANAL_DESTINATION_PROPERTY, destination);
|
||||
@@ -356,7 +380,18 @@ public class CanalController {
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -374,6 +409,11 @@ public class CanalController {
|
||||
public void handleNewSession() throws Exception {
|
||||
initCid(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSessionEstablishmentError(Throwable error) throws Exception {
|
||||
logger.error("failed to connect to zookeeper", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 优先启动embeded服务
|
||||
@@ -383,10 +423,10 @@ public class CanalController {
|
||||
final String destination = entry.getKey();
|
||||
InstanceConfig config = entry.getValue();
|
||||
// 创建destination的工作节点
|
||||
if (!config.getLazy() && !embededCanalServer.isStart(destination)) {
|
||||
if (!embededCanalServer.isStart(destination)) {
|
||||
// HA机制启动
|
||||
ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination);
|
||||
if (!runningMonitor.isStart()) {
|
||||
if (!config.getLazy() && !runningMonitor.isStart()) {
|
||||
runningMonitor.start();
|
||||
}
|
||||
}
|
||||
@@ -406,11 +446,15 @@ public class CanalController {
|
||||
}
|
||||
|
||||
// 启动网络接口
|
||||
canalServer.start();
|
||||
if (canalServer != null) {
|
||||
canalServer.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() throws Throwable {
|
||||
canalServer.stop();
|
||||
if (canalServer != null) {
|
||||
canalServer.stop();
|
||||
}
|
||||
|
||||
if (autoScan) {
|
||||
for (InstanceConfigMonitor monitor : instanceConfigMonitors.values()) {
|
||||
@@ -429,6 +473,10 @@ public class CanalController {
|
||||
// 释放canal的工作节点
|
||||
releaseCid(ZookeeperPathUtils.getCanalClusterNode(ip + ":" + port));
|
||||
logger.info("## stop the canal server[{}:{}]", ip, port);
|
||||
|
||||
if (zkclientx != null) {
|
||||
zkclientx.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void initCid(String path) {
|
||||
@@ -442,6 +490,9 @@ public class CanalController {
|
||||
String parentDir = path.substring(0, path.lastIndexOf('/'));
|
||||
zkclientx.createPersistent(parentDir, true);
|
||||
zkclientx.createEphemeral(path);
|
||||
} catch (ZkNodeExistsException e) {
|
||||
// ignore
|
||||
// 因为第一次启动时创建了cid,但在stop/start的时可能会关闭和新建,允许出现NodeExists问题s
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.io.FileInputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -21,6 +20,10 @@ public class CanalLauncher {
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
try {
|
||||
logger.info("## set default uncaught exception handler");
|
||||
setGlobalUncaughtExceptionHandler();
|
||||
|
||||
logger.info("## load canal configurations");
|
||||
String conf = System.getProperty("canal.conf", "classpath:canal.properties");
|
||||
Properties properties = new Properties();
|
||||
if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
|
||||
@@ -41,8 +44,7 @@ public class CanalLauncher {
|
||||
logger.info("## stop the canal server");
|
||||
controller.stop();
|
||||
} catch (Throwable e) {
|
||||
logger.warn("##something goes wrong when stopping canal Server:\n{}",
|
||||
ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn("##something goes wrong when stopping canal Server:", e);
|
||||
} finally {
|
||||
logger.info("## canal server is down.");
|
||||
}
|
||||
@@ -50,9 +52,18 @@ public class CanalLauncher {
|
||||
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
logger.error("## Something goes wrong when starting up the canal Server:\n{}",
|
||||
ExceptionUtils.getFullStackTrace(e));
|
||||
logger.error("## Something goes wrong when starting up the canal Server:", e);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setGlobalUncaughtExceptionHandler() {
|
||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
logger.error("UnCaughtException", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -49,6 +48,8 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1,
|
||||
new NamedThreadFactory("canal-instance-scan"));
|
||||
|
||||
private volatile boolean isFirst = true;
|
||||
|
||||
public void start() {
|
||||
super.start();
|
||||
Assert.notNull(rootConf, "root conf dir is null!");
|
||||
@@ -58,6 +59,9 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
public void run() {
|
||||
try {
|
||||
scan();
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
logger.error("scan failed", e);
|
||||
}
|
||||
@@ -122,13 +126,18 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
|
||||
if (!actions.containsKey(destination) && instanceConfigs.length > 0) {
|
||||
// 存在合法的instance.properties,并且第一次添加时,进行启动操作
|
||||
notifyStart(instanceDir, destination);
|
||||
notifyStart(instanceDir, destination, instanceConfigs);
|
||||
} else if (actions.containsKey(destination)) {
|
||||
// 历史已经启动过
|
||||
if (instanceConfigs.length == 0) { // 如果不存在合法的instance.properties
|
||||
notifyStop(destination);
|
||||
} else {
|
||||
InstanceConfigFiles lastFile = lastFiles.get(destination);
|
||||
// 历史启动过 所以配置文件信息必然存在
|
||||
if (!isFirst && CollectionUtils.isEmpty(lastFile.getInstanceFiles())) {
|
||||
logger.error("[{}] is started, but not found instance file info.", destination);
|
||||
}
|
||||
|
||||
boolean hasChanged = judgeFileChanged(instanceConfigs, lastFile.getInstanceFiles());
|
||||
// 通知变化
|
||||
if (hasChanged) {
|
||||
@@ -161,13 +170,22 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyStart(File instanceDir, String destination) {
|
||||
private void notifyStart(File instanceDir, String destination, File[] instanceConfigs) {
|
||||
try {
|
||||
defaultAction.start(destination);
|
||||
actions.put(destination, defaultAction);
|
||||
|
||||
// 启动成功后记录配置文件信息
|
||||
InstanceConfigFiles lastFile = lastFiles.get(destination);
|
||||
List<FileInfo> newFileInfo = new ArrayList<FileInfo>();
|
||||
for (File instanceConfig : instanceConfigs) {
|
||||
newFileInfo.add(new FileInfo(instanceConfig.getName(), instanceConfig.lastModified()));
|
||||
}
|
||||
lastFile.setInstanceFiles(newFileInfo);
|
||||
|
||||
logger.info("auto notify start {} successful.", destination);
|
||||
} catch (Throwable e) {
|
||||
logger.error("scan add found[{}] but start failed", destination, ExceptionUtils.getFullStackTrace(e));
|
||||
logger.error(String.format("scan add found[%s] but start failed", destination), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,9 +193,10 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
InstanceAction action = actions.remove(destination);
|
||||
try {
|
||||
action.stop(destination);
|
||||
lastFiles.remove(destination);
|
||||
logger.info("auto notify stop {} successful.", destination);
|
||||
} catch (Throwable e) {
|
||||
logger.error("scan delete found[{}] but stop failed", destination, ExceptionUtils.getFullStackTrace(e));
|
||||
logger.error(String.format("scan delete found[%s] but stop failed", destination), e);
|
||||
actions.put(destination, action);// 再重新加回去,下一次scan时再执行删除
|
||||
}
|
||||
}
|
||||
@@ -189,9 +208,7 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
action.reload(destination);
|
||||
logger.info("auto notify reload {} successful.", destination);
|
||||
} catch (Throwable e) {
|
||||
logger.error("scan reload found[{}] but reload failed",
|
||||
destination,
|
||||
ExceptionUtils.getFullStackTrace(e));
|
||||
logger.error(String.format("scan reload found[%s] but reload failed", destination), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -294,5 +311,4 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
#################################################
|
||||
canal.id= 1
|
||||
canal.ip=
|
||||
canal.port= 11111
|
||||
canal.port=11111
|
||||
canal.zkServers=
|
||||
# flush data to zk
|
||||
canal.zookeeper.flush.period = 1000
|
||||
canal.withoutNetty = false
|
||||
# flush meta cursor/parse position to file
|
||||
canal.file.data.dir = ${canal.conf.dir}
|
||||
canal.file.flush.period = 1000
|
||||
@@ -36,10 +37,13 @@ canal.instance.network.sendBufferSize = 16384
|
||||
canal.instance.network.soTimeout = 30
|
||||
|
||||
# binlog filter config
|
||||
canal.instance.filter.druid.ddl = true
|
||||
canal.instance.filter.query.dcl = false
|
||||
canal.instance.filter.query.dml = false
|
||||
canal.instance.filter.query.ddl = false
|
||||
canal.instance.filter.table.error = false
|
||||
canal.instance.filter.rows = false
|
||||
canal.instance.filter.transaction.entry = false
|
||||
|
||||
# binlog format/image check
|
||||
canal.instance.binlog.format = ROW,STATEMENT,MIXED
|
||||
@@ -48,6 +52,20 @@ canal.instance.binlog.image = FULL,MINIMAL,NOBLOB
|
||||
# binlog ddl isolation
|
||||
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
|
||||
|
||||
#################################################
|
||||
######### destinations #############
|
||||
#################################################
|
||||
@@ -58,9 +76,13 @@ canal.conf.dir = ../conf
|
||||
canal.auto.scan = true
|
||||
canal.auto.scan.interval = 5
|
||||
|
||||
canal.instance.tsdb.spring.xml=classpath:spring/tsdb/h2-tsdb.xml
|
||||
#canal.instance.tsdb.spring.xml=classpath:spring/tsdb/mysql-tsdb.xml
|
||||
|
||||
canal.instance.global.mode = spring
|
||||
canal.instance.global.lazy = false
|
||||
#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/file-instance.xml
|
||||
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
|
||||
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
|
||||
|
||||
@@ -1,27 +1,36 @@
|
||||
#################################################
|
||||
## mysql serverId
|
||||
canal.instance.mysql.slaveId = 1234
|
||||
## mysql serverId , v1.0.26+ will autoGen
|
||||
# canal.instance.mysql.slaveId=0
|
||||
|
||||
# enable gtid use true/false
|
||||
canal.instance.gtidon=false
|
||||
|
||||
# position info
|
||||
canal.instance.master.address = 127.0.0.1:3306
|
||||
canal.instance.master.journal.name =
|
||||
canal.instance.master.position =
|
||||
canal.instance.master.timestamp =
|
||||
canal.instance.master.address=127.0.0.1:3306
|
||||
canal.instance.master.journal.name=
|
||||
canal.instance.master.position=
|
||||
canal.instance.master.timestamp=
|
||||
canal.instance.master.gtid=
|
||||
|
||||
#canal.instance.standby.address =
|
||||
# table meta tsdb info
|
||||
canal.instance.tsdb.enable=true
|
||||
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
|
||||
#canal.instance.tsdb.dbUsername=canal
|
||||
#canal.instance.tsdb.dbPassword=canal
|
||||
|
||||
#canal.instance.standby.address =
|
||||
#canal.instance.standby.journal.name =
|
||||
#canal.instance.standby.position =
|
||||
#canal.instance.standby.timestamp =
|
||||
#canal.instance.standby.timestamp =
|
||||
#canal.instance.standby.gtid=
|
||||
|
||||
# username/password
|
||||
canal.instance.dbUsername = canal
|
||||
canal.instance.dbPassword = canal
|
||||
canal.instance.defaultDatabaseName =
|
||||
canal.instance.connectionCharset = UTF-8
|
||||
canal.instance.dbUsername=canal
|
||||
canal.instance.dbPassword=canal
|
||||
canal.instance.connectionCharset=UTF-8
|
||||
|
||||
# table regex
|
||||
canal.instance.filter.regex = .*\\..*
|
||||
canal.instance.filter.regex=.*\\..*
|
||||
# table black regex
|
||||
canal.instance.filter.black.regex =
|
||||
|
||||
canal.instance.filter.black.regex=
|
||||
#################################################
|
||||
32
deployer/src/main/resources/example/rds_instance.properties
Normal file
32
deployer/src/main/resources/example/rds_instance.properties
Normal file
@@ -0,0 +1,32 @@
|
||||
#################################################
|
||||
# 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,14 +70,16 @@
|
||||
<appender-ref ref="CANAL-ROOT" />
|
||||
</logger>
|
||||
<logger name="com.alibaba.otter.canal.meta.FileMixedMetaManager" additivity="false">
|
||||
<level value="INFO" />
|
||||
<level value="INFO" />
|
||||
<appender-ref ref="CANAL-META" />
|
||||
</logger>
|
||||
<logger name="com.alibaba.otter.canal.kafka" additivity="false">
|
||||
<level value="INFO" />
|
||||
<appender-ref ref="CANAL-ROOT" />
|
||||
</logger>
|
||||
|
||||
<root level="WARN">
|
||||
<!--
|
||||
<appender-ref ref="STDOUT"/>
|
||||
-->
|
||||
<appender-ref ref="CANAL-ROOT" />
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -81,11 +81,12 @@
|
||||
|
||||
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
||||
<property name="eventStore" ref="eventStore" />
|
||||
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||
@@ -128,14 +129,14 @@
|
||||
<!-- 解析位点记录 -->
|
||||
<property name="logPositionManager">
|
||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||
<property name="primary">
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||
</property>
|
||||
<property name="failback">
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||
<property name="metaManager" ref="metaManager" />
|
||||
<constructor-arg ref="metaManager"/>
|
||||
</bean>
|
||||
</property>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
@@ -148,7 +149,7 @@
|
||||
<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}" />
|
||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="standbyInfo">
|
||||
@@ -156,7 +157,7 @@
|
||||
<property name="address" value="${canal.instance.standby.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}" />
|
||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
@@ -166,6 +167,7 @@
|
||||
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
||||
<property name="position" value="${canal.instance.master.position}" />
|
||||
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
||||
<property name="gtid" value="${canal.instance.master.gtid}" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="standbyPosition">
|
||||
@@ -173,13 +175,28 @@
|
||||
<property name="journalName" value="${canal.instance.standby.journal.name}" />
|
||||
<property name="position" value="${canal.instance.standby.position}" />
|
||||
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
|
||||
<property name="gtid" value="${canal.instance.standby.gtid}" />
|
||||
</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="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||
|
||||
<!--表结构相关-->
|
||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||
<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>
|
||||
</beans>
|
||||
</beans>
|
||||
|
||||
@@ -67,11 +67,12 @@
|
||||
|
||||
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
||||
<property name="eventStore" ref="eventStore" />
|
||||
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||
@@ -113,14 +114,14 @@
|
||||
<!-- 解析位点记录 -->
|
||||
<property name="logPositionManager">
|
||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||
<property name="primary">
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||
</property>
|
||||
<property name="failback">
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||
<property name="metaManager" ref="metaManager" />
|
||||
<constructor-arg ref="metaManager"/>
|
||||
</bean>
|
||||
</property>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
@@ -133,7 +134,7 @@
|
||||
<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}" />
|
||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="standbyInfo">
|
||||
@@ -141,7 +142,7 @@
|
||||
<property name="address" value="${canal.instance.standby.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}" />
|
||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
@@ -151,6 +152,7 @@
|
||||
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
||||
<property name="position" value="${canal.instance.master.position}" />
|
||||
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
||||
<property name="gtid" value="${canal.instance.master.gtid}" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="standbyPosition">
|
||||
@@ -158,13 +160,28 @@
|
||||
<property name="journalName" value="${canal.instance.standby.journal.name}" />
|
||||
<property name="position" value="${canal.instance.standby.position}" />
|
||||
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
|
||||
<property name="gtid" value="${canal.instance.standby.gtid}" />
|
||||
</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="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||
|
||||
<!--表结构相关-->
|
||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||
<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>
|
||||
</beans>
|
||||
</beans>
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
|
||||
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
||||
<property name="eventStore" ref="eventStore" />
|
||||
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.group.GroupEventParser">
|
||||
@@ -77,7 +78,7 @@
|
||||
|
||||
<bean id="eventParser1" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||
@@ -130,7 +131,7 @@
|
||||
<property name="address" value="${canal.instance.master1.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}" />
|
||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="standbyInfo">
|
||||
@@ -138,7 +139,7 @@
|
||||
<property name="address" value="${canal.instance.standby1.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}" />
|
||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
@@ -148,6 +149,7 @@
|
||||
<property name="journalName" value="${canal.instance.master1.journal.name}" />
|
||||
<property name="position" value="${canal.instance.master1.position}" />
|
||||
<property name="timestamp" value="${canal.instance.master1.timestamp}" />
|
||||
<property name="gtid" value="${canal.instance.master1.gtid}" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="standbyPosition">
|
||||
@@ -155,19 +157,26 @@
|
||||
<property name="journalName" value="${canal.instance.standby1.journal.name}" />
|
||||
<property name="position" value="${canal.instance.standby1.position}" />
|
||||
<property name="timestamp" value="${canal.instance.standby1.timestamp}" />
|
||||
<property name="gtid" value="${canal.instance.standby1.gtid}" />
|
||||
</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="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||
<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 id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||
@@ -220,7 +229,7 @@
|
||||
<property name="address" value="${canal.instance.master2.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}" />
|
||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="standbyInfo">
|
||||
@@ -228,7 +237,7 @@
|
||||
<property name="address" value="${canal.instance.standby2.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}" />
|
||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
@@ -238,6 +247,7 @@
|
||||
<property name="journalName" value="${canal.instance.master2.journal.name}" />
|
||||
<property name="position" value="${canal.instance.master2.position}" />
|
||||
<property name="timestamp" value="${canal.instance.master2.timestamp}" />
|
||||
<property name="gtid" value="${canal.instance.master2.gtid}" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="standbyPosition">
|
||||
@@ -245,13 +255,21 @@
|
||||
<property name="journalName" value="${canal.instance.standby2.journal.name}" />
|
||||
<property name="position" value="${canal.instance.standby2.position}" />
|
||||
<property name="timestamp" value="${canal.instance.standby2.timestamp}" />
|
||||
<property name="gtid" value="${canal.instance.standby2.gtid}" />
|
||||
</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="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||
<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>
|
||||
</beans>
|
||||
</beans>
|
||||
|
||||
147
deployer/src/main/resources/spring/local-instance.xml
Normal file
147
deployer/src/main/resources/spring/local-instance.xml
Normal file
@@ -0,0 +1,147 @@
|
||||
<?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" />
|
||||
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
|
||||
</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:test}" />
|
||||
</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:}"/>
|
||||
|
||||
<!-- 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>
|
||||
</beans>
|
||||
@@ -64,11 +64,12 @@
|
||||
|
||||
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
||||
<property name="eventStore" ref="eventStore" />
|
||||
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||
@@ -121,7 +122,7 @@
|
||||
<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}" />
|
||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="standbyInfo">
|
||||
@@ -129,7 +130,7 @@
|
||||
<property name="address" value="${canal.instance.standby.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}" />
|
||||
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
@@ -139,6 +140,7 @@
|
||||
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
||||
<property name="position" value="${canal.instance.master.position}" />
|
||||
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
||||
<property name="gtid" value="${canal.instance.master.gtid}" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="standbyPosition">
|
||||
@@ -146,13 +148,28 @@
|
||||
<property name="journalName" value="${canal.instance.standby.journal.name}" />
|
||||
<property name="position" value="${canal.instance.standby.position}" />
|
||||
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
|
||||
<property name="gtid" value="${canal.instance.standby.gtid}" />
|
||||
</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="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||
|
||||
<!--表结构相关-->
|
||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:false}"/>
|
||||
<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>
|
||||
</beans>
|
||||
</beans>
|
||||
|
||||
60
deployer/src/main/resources/spring/tsdb/h2-tsdb.xml
Normal file
60
deployer/src/main/resources/spring/tsdb/h2-tsdb.xml
Normal file
@@ -0,0 +1,60 @@
|
||||
<?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>
|
||||
|
||||
<!-- 基于db的实现 -->
|
||||
<bean id="tableMetaTSDB" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta">
|
||||
<property name="metaHistoryDAO" ref="metaHistoryDAO"/>
|
||||
<property name="metaSnapshotDAO" ref="metaSnapshotDAO"/>
|
||||
</bean>
|
||||
|
||||
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="org.h2.Driver" />
|
||||
<property name="url" value="${canal.instance.tsdb.url:}" />
|
||||
<property name="username" value="${canal.instance.tsdb.dbUsername:}" />
|
||||
<property name="password" value="${canal.instance.tsdb.dbPassword:}" />
|
||||
<property name="maxActive" value="30" />
|
||||
<property name="initialSize" value="0" />
|
||||
<property name="minIdle" value="1" />
|
||||
<property name="maxWait" value="10000" />
|
||||
<property name="timeBetweenEvictionRunsMillis" value="60000" />
|
||||
<property name="minEvictableIdleTimeMillis" value="300000" />
|
||||
<property name="testWhileIdle" value="true" />
|
||||
<property name="testOnBorrow" value="false" />
|
||||
<property name="testOnReturn" value="false" />
|
||||
<property name="useUnfairLock" value="true" />
|
||||
</bean>
|
||||
|
||||
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="configLocation" value="classpath:spring/tsdb/sql-map/sqlmap-config.xml"/>
|
||||
</bean>
|
||||
|
||||
<bean id="metaHistoryDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDAO">
|
||||
<property name="sqlMapClient" ref="sqlMapClient"/>
|
||||
</bean>
|
||||
|
||||
<bean id="metaSnapshotDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDAO">
|
||||
<property name="sqlMapClient" ref="sqlMapClient"/>
|
||||
</bean>
|
||||
</beans>
|
||||
63
deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml
Normal file
63
deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<?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>
|
||||
|
||||
<!-- 基于db的实现 -->
|
||||
<bean id="tableMetaTSDB" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta">
|
||||
<property name="metaHistoryDAO" ref="metaHistoryDAO"/>
|
||||
<property name="metaSnapshotDAO" ref="metaSnapshotDAO"/>
|
||||
</bean>
|
||||
|
||||
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
|
||||
<property name="url" value="${canal.instance.tsdb.url:}" />
|
||||
<property name="username" value="${canal.instance.tsdb.dbUsername:}" />
|
||||
<property name="password" value="${canal.instance.tsdb.dbPassword:}" />
|
||||
<property name="maxActive" value="30" />
|
||||
<property name="initialSize" value="0" />
|
||||
<property name="minIdle" value="1" />
|
||||
<property name="maxWait" value="10000" />
|
||||
<property name="timeBetweenEvictionRunsMillis" value="60000" />
|
||||
<property name="minEvictableIdleTimeMillis" value="300000" />
|
||||
<property name="validationQuery" value="SELECT 1" />
|
||||
<property name="exceptionSorterClassName" value="com.alibaba.druid.pool.vendor.MySqlExceptionSorter" />
|
||||
<property name="validConnectionCheckerClassName" value="com.alibaba.druid.pool.vendor.MySqlValidConnectionChecker" />
|
||||
<property name="testWhileIdle" value="true" />
|
||||
<property name="testOnBorrow" value="false" />
|
||||
<property name="testOnReturn" value="false" />
|
||||
<property name="useUnfairLock" value="true" />
|
||||
</bean>
|
||||
|
||||
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="configLocation" value="classpath:spring/tsdb/sql-map/sqlmap-config.xml"/>
|
||||
</bean>
|
||||
|
||||
<bean id="metaHistoryDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDAO">
|
||||
<property name="sqlMapClient" ref="sqlMapClient"/>
|
||||
</bean>
|
||||
|
||||
<bean id="metaSnapshotDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDAO">
|
||||
<property name="sqlMapClient" ref="sqlMapClient"/>
|
||||
</bean>
|
||||
</beans>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
|
||||
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
|
||||
<sqlMapConfig>
|
||||
<settings useStatementNamespaces="true"/>
|
||||
<sqlMap resource="spring/tsdb/sql-map/sqlmap_history.xml"/>
|
||||
<sqlMap resource="spring/tsdb/sql-map/sqlmap_snapshot.xml"/>
|
||||
</sqlMapConfig>
|
||||
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd" >
|
||||
<sqlMap namespace="meta_history">
|
||||
<typeAlias alias="metaHistoryDO" type="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDO"/>
|
||||
<sql id="allColumns">
|
||||
<![CDATA[
|
||||
gmt_create,gmt_modified,destination,binlog_file,binlog_offest,binlog_master_id,binlog_timestamp,use_schema,sql_schema,sql_table,sql_text,sql_type,extra
|
||||
]]>
|
||||
</sql>
|
||||
<sql id="allVOColumns">
|
||||
<![CDATA[
|
||||
a.id as id,a.gmt_create as gmtCreate,a.gmt_modified as gmtModified,
|
||||
a.destination as destination,a.binlog_file as binlogFile,a.binlog_offest as binlogOffest,a.binlog_master_id as binlogMasterId,a.binlog_timestamp as binlogTimestamp,
|
||||
a.use_schema as useSchema,a.sql_schema as sqlSchema,a.sql_table as sqlTable,a.sql_text as sqlText,a.sql_type as sqlType,a.extra as extra
|
||||
]]>
|
||||
</sql>
|
||||
|
||||
<select id="findByTimestamp" parameterClass="java.util.Map" resultClass="metaHistoryDO">
|
||||
select
|
||||
<include refid="allVOColumns"/>
|
||||
from meta_history a
|
||||
<![CDATA[
|
||||
where destination = #destination# and binlog_timestamp >= #snapshotTimestamp# and binlog_timestamp <= #timestamp#
|
||||
order by binlog_timestamp asc,id asc
|
||||
]]>
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterClass="metaHistoryDO">
|
||||
insert into meta_history (<include refid="allColumns"/>)
|
||||
values(CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,#destination#,#binlogFile#,#binlogOffest#,#binlogMasterId#,#binlogTimestamp#,#useSchema#,#sqlSchema#,#sqlTable#,#sqlText#,#sqlType#,#extra#)
|
||||
</insert>
|
||||
|
||||
<delete id="deleteByName" parameterClass="java.util.Map">
|
||||
delete from meta_history
|
||||
where destination=#destination#
|
||||
</delete>
|
||||
|
||||
|
||||
<delete id="deleteByGmtModified" parameterClass="java.util.Map">
|
||||
<![CDATA[
|
||||
delete from meta_history
|
||||
where gmt_modified < timestamp(#timestamp#)
|
||||
]]>
|
||||
</delete>
|
||||
</sqlMap>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd" >
|
||||
<sqlMap namespace="meta_snapshot">
|
||||
<typeAlias alias="metaSnapshotDO" type="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDO"/>
|
||||
<typeAlias alias="tableMetaSnapshotDO"
|
||||
type="com.alibaba.middleware.jingwei.biz.dataobject.CanalTableMetaSnapshotDO"/>
|
||||
<sql id="allColumns">
|
||||
<![CDATA[
|
||||
gmt_create,gmt_modified,destination,binlog_file,binlog_offest,binlog_master_id,binlog_timestamp,data,extra
|
||||
]]>
|
||||
</sql>
|
||||
<sql id="allVOColumns">
|
||||
<![CDATA[
|
||||
a.id as id,a.gmt_create as gmtCreate,a.gmt_modified as gmtModified,
|
||||
a.destination as destination,a.binlog_file as binlogFile,a.binlog_offest as binlogOffest,a.binlog_master_id as binlogMasterId,a.binlog_timestamp as binlogTimestamp,a.data as data,a.extra as extra
|
||||
]]>
|
||||
</sql>
|
||||
|
||||
<select id="findByTimestamp" parameterClass="java.util.Map" resultClass="metaSnapshotDO">
|
||||
select <include refid="allVOColumns"/>
|
||||
<![CDATA[
|
||||
from meta_snapshot a
|
||||
where destination = #destination# and binlog_timestamp < #timestamp#
|
||||
order by binlog_timestamp desc,id desc
|
||||
limit 1
|
||||
]]>
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterClass="metaSnapshotDO">
|
||||
insert into meta_snapshot (<include refid="allColumns"/>)
|
||||
values(CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,#destination#,#binlogFile#,#binlogOffest#,#binlogMasterId#,#binlogTimestamp#,#data#,#extra#)
|
||||
</insert>
|
||||
|
||||
<update id="update" parameterClass="metaSnapshotDO">
|
||||
update meta_snapshot set gmt_modified=now(),
|
||||
binlog_file=#binlogFile#,binlog_offest=#binlogOffest#,binlog_master_id=#binlogMasterId#,binlog_timestamp=#binlogTimestamp#,data=#data#,extra=#extra#
|
||||
where destination=#destination# and binlog_timestamp=0
|
||||
</update>
|
||||
|
||||
<delete id="deleteByName" parameterClass="java.util.Map">
|
||||
delete from meta_snapshot
|
||||
where destination=#destination#
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByGmtModified" parameterClass="java.util.Map">
|
||||
<![CDATA[
|
||||
delete from meta_snapshot
|
||||
where gmt_modified < timestamp(#timestamp#)
|
||||
]]>
|
||||
</delete>
|
||||
</sqlMap>
|
||||
39
deployer/src/main/resources/spring/tsdb/sql/create_table.sql
Normal file
39
deployer/src/main/resources/spring/tsdb/sql/create_table.sql
Normal file
@@ -0,0 +1,39 @@
|
||||
CREATE TABLE IF NOT EXISTS `meta_snapshot` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`gmt_create` datetime NOT NULL COMMENT '创建时间',
|
||||
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
|
||||
`destination` varchar(128) DEFAULT NULL COMMENT '通道名称',
|
||||
`binlog_file` varchar(64) DEFAULT NULL COMMENT 'binlog文件名',
|
||||
`binlog_offest` bigint(20) DEFAULT NULL COMMENT 'binlog偏移量',
|
||||
`binlog_master_id` varchar(64) DEFAULT NULL COMMENT 'binlog节点id',
|
||||
`binlog_timestamp` bigint(20) DEFAULT NULL COMMENT 'binlog应用的时间戳',
|
||||
`data` longtext DEFAULT NULL COMMENT '表结构数据',
|
||||
`extra` text DEFAULT NULL COMMENT '额外的扩展信息',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY binlog_file_offest(`destination`,`binlog_master_id`,`binlog_file`,`binlog_offest`),
|
||||
KEY `destination` (`destination`),
|
||||
KEY `destination_timestamp` (`destination`,`binlog_timestamp`),
|
||||
KEY `gmt_modified` (`gmt_modified`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='表结构记录表快照表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `meta_history` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`gmt_create` datetime NOT NULL COMMENT '创建时间',
|
||||
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
|
||||
`destination` varchar(128) DEFAULT NULL COMMENT '通道名称',
|
||||
`binlog_file` varchar(64) DEFAULT NULL COMMENT 'binlog文件名',
|
||||
`binlog_offest` bigint(20) DEFAULT NULL COMMENT 'binlog偏移量',
|
||||
`binlog_master_id` varchar(64) DEFAULT NULL COMMENT 'binlog节点id',
|
||||
`binlog_timestamp` bigint(20) DEFAULT NULL COMMENT 'binlog应用的时间戳',
|
||||
`use_schema` varchar(1024) DEFAULT NULL COMMENT '执行sql时对应的schema',
|
||||
`schema` varchar(1024) DEFAULT NULL COMMENT '对应的schema',
|
||||
`table` varchar(1024) DEFAULT NULL COMMENT '对应的table',
|
||||
`sql` longtext DEFAULT NULL COMMENT '执行的sql',
|
||||
`type` varchar(256) DEFAULT NULL COMMENT 'sql类型',
|
||||
`extra` text DEFAULT NULL COMMENT '额外的扩展信息',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY binlog_file_offest(`destination`,`binlog_master_id`,`binlog_file`,`binlog_offest`),
|
||||
KEY `destination` (`destination`),
|
||||
KEY `destination_timestamp` (`destination`,`binlog_timestamp`),
|
||||
KEY `gmt_modified` (`gmt_modified`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='表结构变化明细表';
|
||||
67
docker/Dockerfile
Normal file
67
docker/Dockerfile
Normal file
@@ -0,0 +1,67 @@
|
||||
FROM centos:centos6.7
|
||||
|
||||
MAINTAINER agapple (jianghang115@gmail.com)
|
||||
|
||||
# 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 && \
|
||||
yum clean all && \
|
||||
true
|
||||
|
||||
# install canal
|
||||
COPY image/ /tmp/docker/
|
||||
COPY canal.deployer-*.tar.gz /home/admin/
|
||||
COPY jdk-8-linux-x64.rpm /tmp/
|
||||
|
||||
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 && \
|
||||
|
||||
touch /var/lib/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 && \
|
||||
/bin/mv /home/admin/bin/clean_log /etc/cron.d && \
|
||||
|
||||
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" ]
|
||||
30
docker/build.sh
Normal file
30
docker/build.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/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 [ ! -f $BASE/jdk*.rpm ] ; then
|
||||
DOWNLOAD_LINK="http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.tar.gz"
|
||||
wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=xxx; oraclelicense=accept-securebackup-cookie" "$DOWNLOAD_LINK" -O $BASE/jdk-8-linux-x64.rpm
|
||||
fi
|
||||
|
||||
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
|
||||
117
docker/image/admin/app.sh
Executable file
117
docker/image/admin/app.sh
Executable file
@@ -0,0 +1,117 @@
|
||||
#!/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
|
||||
mv /home/admin/canal-server/conf/example /home/admin/canal-server/conf/$destination
|
||||
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
|
||||
13
docker/image/admin/health.sh
Executable file
13
docker/image/admin/health.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
CHECK_URL="http://127.0.0.1:8080/metrics"
|
||||
CHECK_POINT="success"
|
||||
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>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
@@ -33,10 +33,6 @@
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.netty</groupId>
|
||||
<artifactId>netty</artifactId>
|
||||
</dependency>
|
||||
<!-- test dependency -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
||||
@@ -2,19 +2,19 @@ package com.alibaba.otter.canal.parse.driver.mysql;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.ClientAuthenticationPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.QuitCommandPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ErrorPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.HandshakeInitializationPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.Reply323Packet;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannelPool;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.MySQLPasswordEncrypter;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
||||
|
||||
@@ -32,20 +32,28 @@ public class MysqlConnector {
|
||||
private String password;
|
||||
|
||||
private byte charsetNumber = 33;
|
||||
private String defaultSchema = "retl";
|
||||
private String defaultSchema = "test";
|
||||
private int soTimeout = 30 * 1000;
|
||||
private int connTimeout = 5 * 1000;
|
||||
private int receiveBufferSize = 16 * 1024;
|
||||
private int sendBufferSize = 16 * 1024;
|
||||
|
||||
private SocketChannel channel;
|
||||
private volatile boolean dumping = false;
|
||||
// mysql connectinnId
|
||||
private long connectionId = -1;
|
||||
private AtomicBoolean connected = new AtomicBoolean(false);
|
||||
|
||||
public static final int timeout = 5 * 1000; // 5s
|
||||
|
||||
public MysqlConnector(){
|
||||
}
|
||||
|
||||
public MysqlConnector(InetSocketAddress address, String username, String password){
|
||||
String addr = address.getHostString();
|
||||
int port = address.getPort();
|
||||
this.address = new InetSocketAddress(addr, port);
|
||||
|
||||
this.address = address;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
@@ -61,14 +69,12 @@ public class MysqlConnector {
|
||||
public void connect() throws IOException {
|
||||
if (connected.compareAndSet(false, true)) {
|
||||
try {
|
||||
channel = SocketChannel.open();
|
||||
configChannel(channel);
|
||||
channel = SocketChannelPool.open(address);
|
||||
logger.info("connect MysqlConnection to {}...", address);
|
||||
channel.connect(address);
|
||||
negotiate(channel);
|
||||
} catch (Exception e) {
|
||||
disconnect();
|
||||
throw new IOException("connect " + this.address + " failure:" + ExceptionUtils.getStackTrace(e));
|
||||
throw new IOException("connect " + this.address + " failure", e);
|
||||
}
|
||||
} else {
|
||||
logger.error("the channel can't be connected twice.");
|
||||
@@ -86,10 +92,29 @@ public class MysqlConnector {
|
||||
if (channel != null) {
|
||||
channel.close();
|
||||
}
|
||||
|
||||
logger.info("disConnect MysqlConnection to {}...", address);
|
||||
} catch (Exception e) {
|
||||
throw new IOException("disconnect " + this.address + " failure:" + ExceptionUtils.getStackTrace(e));
|
||||
throw new IOException("disconnect " + this.address + " failure", e);
|
||||
}
|
||||
|
||||
// 执行一次quit
|
||||
if (dumping && connectionId >= 0) {
|
||||
MysqlConnector connector = null;
|
||||
try {
|
||||
connector = this.fork();
|
||||
connector.connect();
|
||||
MysqlUpdateExecutor executor = new MysqlUpdateExecutor(connector);
|
||||
executor.update("KILL CONNECTION " + connectionId);
|
||||
} catch (Exception e) {
|
||||
// 忽略具体异常
|
||||
logger.info("KILL DUMP " + connectionId + " failure", e);
|
||||
} finally {
|
||||
if (connector != null) {
|
||||
connector.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
dumping = false;
|
||||
}
|
||||
} else {
|
||||
logger.info("the channel {} is not connected", this.address);
|
||||
@@ -110,23 +135,23 @@ public class MysqlConnector {
|
||||
connector.setReceiveBufferSize(getReceiveBufferSize());
|
||||
connector.setSendBufferSize(getSendBufferSize());
|
||||
connector.setSoTimeout(getSoTimeout());
|
||||
connector.setConnTimeout(connTimeout);
|
||||
return connector;
|
||||
}
|
||||
|
||||
// ====================== help method ====================
|
||||
public void quit() throws IOException {
|
||||
QuitCommandPacket quit = new QuitCommandPacket();
|
||||
byte[] cmdBody = quit.toBytes();
|
||||
|
||||
private void configChannel(SocketChannel channel) throws IOException {
|
||||
channel.socket().setKeepAlive(true);
|
||||
channel.socket().setReuseAddress(true);
|
||||
channel.socket().setSoTimeout(soTimeout);
|
||||
channel.socket().setTcpNoDelay(true);
|
||||
channel.socket().setReceiveBufferSize(receiveBufferSize);
|
||||
channel.socket().setSendBufferSize(sendBufferSize);
|
||||
HeaderPacket quitHeader = new HeaderPacket();
|
||||
quitHeader.setPacketBodyLength(cmdBody.length);
|
||||
quitHeader.setPacketSequenceNumber((byte) 0x00);
|
||||
PacketManager.writePkg(channel, quitHeader.toBytes(), cmdBody);
|
||||
}
|
||||
|
||||
private void negotiate(SocketChannel channel) throws IOException {
|
||||
HeaderPacket header = PacketManager.readHeader(channel, 4);
|
||||
byte[] body = PacketManager.readBytes(channel, header.getPacketBodyLength());
|
||||
HeaderPacket header = PacketManager.readHeader(channel, 4, timeout);
|
||||
byte[] body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
||||
if (body[0] < 0) {// check field_count
|
||||
if (body[0] == -1) {
|
||||
ErrorPacket error = new ErrorPacket();
|
||||
@@ -140,6 +165,7 @@ public class MysqlConnector {
|
||||
}
|
||||
HandshakeInitializationPacket handshakePacket = new HandshakeInitializationPacket();
|
||||
handshakePacket.fromBytes(body);
|
||||
connectionId = handshakePacket.threadId; // 记录一下connection
|
||||
|
||||
logger.info("handshake initialization packet received, prepare the client authentication packet to send");
|
||||
|
||||
@@ -157,15 +183,14 @@ public class MysqlConnector {
|
||||
h.setPacketBodyLength(clientAuthPkgBody.length);
|
||||
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
|
||||
|
||||
PacketManager.write(channel,
|
||||
new ByteBuffer[] { ByteBuffer.wrap(h.toBytes()), ByteBuffer.wrap(clientAuthPkgBody) });
|
||||
PacketManager.writePkg(channel, h.toBytes(), clientAuthPkgBody);
|
||||
logger.info("client authentication packet is sent out.");
|
||||
|
||||
// check auth result
|
||||
header = null;
|
||||
header = PacketManager.readHeader(channel, 4);
|
||||
body = null;
|
||||
body = PacketManager.readBytes(channel, header.getPacketBodyLength());
|
||||
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
||||
assert body != null;
|
||||
if (body[0] < 0) {
|
||||
if (body[0] == -1) {
|
||||
@@ -194,7 +219,7 @@ public class MysqlConnector {
|
||||
h323.setPacketBodyLength(b323Body.length);
|
||||
h323.setPacketSequenceNumber((byte) (packetSequenceNumber + 1));
|
||||
|
||||
PacketManager.write(channel, new ByteBuffer[] { ByteBuffer.wrap(h323.toBytes()), ByteBuffer.wrap(b323Body) });
|
||||
PacketManager.writePkg(channel, h323.toBytes(), b323Body);
|
||||
logger.info("client 323 authentication packet is sent out.");
|
||||
// check auth result
|
||||
HeaderPacket header = PacketManager.readHeader(channel, 4);
|
||||
@@ -291,4 +316,32 @@ public class MysqlConnector {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public long getConnectionId() {
|
||||
return connectionId;
|
||||
}
|
||||
|
||||
public void setConnectionId(long connectionId) {
|
||||
this.connectionId = connectionId;
|
||||
}
|
||||
|
||||
public boolean isDumping() {
|
||||
return dumping;
|
||||
}
|
||||
|
||||
public void setDumping(boolean dumping) {
|
||||
this.dumping = dumping;
|
||||
}
|
||||
|
||||
public int getConnTimeout() {
|
||||
return connTimeout;
|
||||
}
|
||||
|
||||
public void setConnTimeout(int connTimeout) {
|
||||
this.connTimeout = connTimeout;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.QueryCommandPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.EOFPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ErrorPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetHeaderPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.RowDataPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
||||
|
||||
/**
|
||||
@@ -24,9 +25,9 @@ public class MysqlQueryExecutor {
|
||||
|
||||
private SocketChannel channel;
|
||||
|
||||
public MysqlQueryExecutor(MysqlConnector connector){
|
||||
public MysqlQueryExecutor(MysqlConnector connector) throws IOException{
|
||||
if (!connector.isConnected()) {
|
||||
throw new RuntimeException("should execute connector.connect() first");
|
||||
throw new IOException("should execute connector.connect() first");
|
||||
}
|
||||
|
||||
this.channel = connector.getChannel();
|
||||
@@ -51,7 +52,7 @@ public class MysqlQueryExecutor {
|
||||
QueryCommandPacket cmd = new QueryCommandPacket();
|
||||
cmd.setQueryString(queryString);
|
||||
byte[] bodyBytes = cmd.toBytes();
|
||||
PacketManager.write(channel, bodyBytes);
|
||||
PacketManager.writeBody(channel, bodyBytes);
|
||||
byte[] body = readNextPacket();
|
||||
|
||||
if (body[0] < 0) {
|
||||
@@ -88,16 +89,70 @@ public class MysqlQueryExecutor {
|
||||
for (RowDataPacket r : rowData) {
|
||||
resultSet.getFieldValues().addAll(r.getColumns());
|
||||
}
|
||||
resultSet.setSourceAddress(channel.socket().getRemoteSocketAddress());
|
||||
resultSet.setSourceAddress(channel.getRemoteSocketAddress());
|
||||
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
private void readEofPacket() throws IOException {
|
||||
public List<ResultSetPacket> queryMulti(String queryString) throws IOException {
|
||||
QueryCommandPacket cmd = new QueryCommandPacket();
|
||||
cmd.setQueryString(queryString);
|
||||
byte[] bodyBytes = cmd.toBytes();
|
||||
PacketManager.writeBody(channel, bodyBytes);
|
||||
List<ResultSetPacket> resultSets = new ArrayList<ResultSetPacket>();
|
||||
boolean moreResult = true;
|
||||
while (moreResult) {
|
||||
byte[] body = readNextPacket();
|
||||
if (body[0] < 0) {
|
||||
ErrorPacket packet = new ErrorPacket();
|
||||
packet.fromBytes(body);
|
||||
throw new IOException(packet + "\n with command: " + queryString);
|
||||
}
|
||||
|
||||
ResultSetHeaderPacket rsHeader = new ResultSetHeaderPacket();
|
||||
rsHeader.fromBytes(body);
|
||||
|
||||
List<FieldPacket> fields = new ArrayList<FieldPacket>();
|
||||
for (int i = 0; i < rsHeader.getColumnCount(); i++) {
|
||||
FieldPacket fp = new FieldPacket();
|
||||
fp.fromBytes(readNextPacket());
|
||||
fields.add(fp);
|
||||
}
|
||||
|
||||
moreResult = readEofPacket();
|
||||
|
||||
List<RowDataPacket> rowData = new ArrayList<RowDataPacket>();
|
||||
while (true) {
|
||||
body = readNextPacket();
|
||||
if (body[0] == -2) {
|
||||
break;
|
||||
}
|
||||
RowDataPacket rowDataPacket = new RowDataPacket();
|
||||
rowDataPacket.fromBytes(body);
|
||||
rowData.add(rowDataPacket);
|
||||
}
|
||||
|
||||
ResultSetPacket resultSet = new ResultSetPacket();
|
||||
resultSet.getFieldDescriptors().addAll(fields);
|
||||
for (RowDataPacket r : rowData) {
|
||||
resultSet.getFieldValues().addAll(r.getColumns());
|
||||
}
|
||||
resultSet.setSourceAddress(channel.getRemoteSocketAddress());
|
||||
resultSets.add(resultSet);
|
||||
}
|
||||
|
||||
return resultSets;
|
||||
}
|
||||
|
||||
private boolean readEofPacket() throws IOException {
|
||||
byte[] eofBody = readNextPacket();
|
||||
EOFPacket packet = new EOFPacket();
|
||||
packet.fromBytes(eofBody);
|
||||
if (eofBody[0] != -2) {
|
||||
throw new IOException("EOF Packet is expected, but packet with field_count=" + eofBody[0] + " is found.");
|
||||
}
|
||||
|
||||
return (packet.statusFlag & 0x0008) != 0;
|
||||
}
|
||||
|
||||
protected byte[] readNextPacket() throws IOException {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -21,28 +20,29 @@ public class MysqlUpdateExecutor {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MysqlUpdateExecutor.class);
|
||||
|
||||
private SocketChannel channel;
|
||||
private MysqlConnector connector;
|
||||
|
||||
public MysqlUpdateExecutor(MysqlConnector connector){
|
||||
public MysqlUpdateExecutor(MysqlConnector connector) throws IOException{
|
||||
if (!connector.isConnected()) {
|
||||
throw new RuntimeException("should execute connector.connect() first");
|
||||
throw new IOException("should execute connector.connect() first");
|
||||
}
|
||||
|
||||
this.channel = connector.getChannel();
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
public MysqlUpdateExecutor(SocketChannel ch){
|
||||
this.channel = ch;
|
||||
}
|
||||
/*
|
||||
* public MysqlUpdateExecutor(SocketChannel ch){ this.channel = ch; }
|
||||
*/
|
||||
|
||||
public OKPacket update(String updateString) throws IOException {
|
||||
QueryCommandPacket cmd = new QueryCommandPacket();
|
||||
cmd.setQueryString(updateString);
|
||||
byte[] bodyBytes = cmd.toBytes();
|
||||
PacketManager.write(channel, bodyBytes);
|
||||
PacketManager.writeBody(connector.getChannel(), bodyBytes);
|
||||
|
||||
logger.debug("read update result...");
|
||||
byte[] body = PacketManager.readBytes(channel, PacketManager.readHeader(channel, 4).getPacketBodyLength());
|
||||
byte[] body = PacketManager.readBytes(connector.getChannel(),
|
||||
PacketManager.readHeader(connector.getChannel(), 4).getPacketBodyLength());
|
||||
if (body[0] < 0) {
|
||||
ErrorPacket packet = new ErrorPacket();
|
||||
packet.fromBytes(body);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -47,10 +47,11 @@ public class ClientAuthenticationPacket extends PacketWithHeaderPacket {
|
||||
/**
|
||||
* CLIENT_LONG_PASSWORD CLIENT_LONG_FLAG CLIENT_PROTOCOL_41
|
||||
* CLIENT_INTERACTIVE CLIENT_TRANSACTIONS CLIENT_SECURE_CONNECTION
|
||||
* CLIENT_MULTI_STATEMENTS;
|
||||
*/
|
||||
ByteHelper.writeUnsignedIntLittleEndian(1 | 4 | 512 | 8192 | 32768, out); // remove
|
||||
// client_interactive
|
||||
// feature
|
||||
ByteHelper.writeUnsignedIntLittleEndian(1 | 4 | 512 | 8192 | 32768 | 0x00010000, out); // remove
|
||||
// client_interactive
|
||||
// feature
|
||||
|
||||
// 2. write max_packet_size
|
||||
ByteHelper.writeUnsignedIntLittleEndian(MSC.MAX_PACKET_LENGTH, out);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* quit cmd
|
||||
*
|
||||
* @author agapple 2016年3月1日 下午8:33:02
|
||||
* @since 1.0.22
|
||||
*/
|
||||
public class QuitCommandPacket extends CommandPacket {
|
||||
|
||||
public static final byte[] QUIT = new byte[] { 1, 0, 0, 0, 1 };
|
||||
|
||||
public QuitCommandPacket(){
|
||||
setCommand((byte) 0x01);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromBytes(byte[] data) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(getCommand());
|
||||
out.write(QUIT);
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -74,6 +74,8 @@ public class FieldPacket extends PacketWithHeaderPacket {
|
||||
this.decimals = data[index];
|
||||
index++;
|
||||
//
|
||||
index += 2;// skip filter
|
||||
//
|
||||
if (index < data.length) {
|
||||
reader.setIndex(index);
|
||||
this.definition = reader.readLengthCodedString(data);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
/**
|
||||
* @author agapple 2018年3月12日 下午10:36:44
|
||||
* @since 1.0.26
|
||||
*/
|
||||
public interface SocketChannel {
|
||||
|
||||
public void write(byte[]... buf) throws IOException;
|
||||
|
||||
public byte[] read(int readSize) throws IOException;
|
||||
|
||||
public byte[] read(int readSize, int timeout) throws IOException;
|
||||
|
||||
public void read(byte[] data, int off, int len, int timeout) throws IOException;
|
||||
|
||||
public boolean isConnected();
|
||||
|
||||
public SocketAddress getRemoteSocketAddress();
|
||||
|
||||
public void close();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
/**
|
||||
* @author agapple 2018年3月12日 下午10:46:22
|
||||
* @since 1.0.26
|
||||
*/
|
||||
public abstract class SocketChannelPool {
|
||||
|
||||
public static SocketChannel open(SocketAddress address) throws Exception {
|
||||
String type = chooseSocketChannel();
|
||||
if ("netty".equalsIgnoreCase(type)) {
|
||||
return NettySocketChannelPool.open(address);
|
||||
} else {
|
||||
return BioSocketChannelPool.open(address);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String chooseSocketChannel() {
|
||||
String socketChannel = System.getenv("canal.socketChannel");
|
||||
if (StringUtils.isEmpty(socketChannel)) {
|
||||
socketChannel = System.getProperty("canal.socketChannel");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(socketChannel)) {
|
||||
socketChannel = "bio"; // bio or netty
|
||||
}
|
||||
|
||||
return socketChannel;
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,17 @@ public abstract class ByteHelper {
|
||||
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) {
|
||||
out.write((byte) (data & 0xFF));
|
||||
out.write((byte) (data >>> 8));
|
||||
@@ -116,6 +127,17 @@ public abstract class ByteHelper {
|
||||
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) {
|
||||
out.write((byte) (data & 0xFF));
|
||||
out.write((byte) ((data >>> 8) & 0xFF));
|
||||
|
||||
@@ -1,69 +1,44 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||
|
||||
public abstract class PacketManager {
|
||||
|
||||
public static HeaderPacket readHeader(SocketChannel ch, int len) throws IOException {
|
||||
HeaderPacket header = new HeaderPacket();
|
||||
header.fromBytes(readBytesAsBuffer(ch, len).array());
|
||||
header.fromBytes(ch.read(len));
|
||||
return header;
|
||||
}
|
||||
|
||||
public static ByteBuffer readBytesAsBuffer(SocketChannel ch, int len) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(len);
|
||||
while (buffer.hasRemaining()) {
|
||||
int readNum = ch.read(buffer);
|
||||
if (readNum == -1) {
|
||||
throw new IOException("Unexpected End Stream");
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
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 {
|
||||
return readBytesAsBuffer(ch, len).array();
|
||||
return ch.read(len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since We r using blocking IO, so we will just write once and assert the
|
||||
* length to simplify the read operation.<br>
|
||||
* If the block write doesn't work as we expected, we will change this
|
||||
* implementation as per the result.
|
||||
*
|
||||
* @param ch
|
||||
* @param len
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void write(SocketChannel ch, ByteBuffer[] srcs) throws IOException {
|
||||
@SuppressWarnings("unused")
|
||||
long total = 0;
|
||||
for (ByteBuffer buffer : srcs) {
|
||||
total += buffer.remaining();
|
||||
}
|
||||
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 {
|
||||
ch.write(srcs);
|
||||
// https://github.com/alibaba/canal/issues/24
|
||||
// 部分windows用户会出现size != total的情况,jdk为java7/openjdk,估计和java版本有关,暂时不做检查
|
||||
// long size = ch.write(srcs);
|
||||
// if (size != total) {
|
||||
// throw new IOException("unexpected blocking io behavior");
|
||||
// }
|
||||
}
|
||||
|
||||
public static void write(SocketChannel ch, byte[] body) throws IOException {
|
||||
write(ch, body, (byte) 0);
|
||||
public static void writeBody(SocketChannel ch, byte[] body) throws IOException {
|
||||
writeBody0(ch, body, (byte) 0);
|
||||
}
|
||||
|
||||
public static void write(SocketChannel ch, byte[] body, byte packetSeqNumber) throws IOException {
|
||||
public static void writeBody0(SocketChannel ch, byte[] body, byte packetSeqNumber) throws IOException {
|
||||
HeaderPacket header = new HeaderPacket();
|
||||
header.setPacketBodyLength(body.length);
|
||||
header.setPacketSequenceNumber(packetSeqNumber);
|
||||
write(ch, new ByteBuffer[] { ByteBuffer.wrap(header.toBytes()), ByteBuffer.wrap(body) });
|
||||
ch.write(header.toBytes(), body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
||||
|
||||
@@ -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>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
@@ -21,6 +21,82 @@
|
||||
<artifactId>canal.protocol</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</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 -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
||||
90
example/src/main/bin/init.sh
Executable file
90
example/src/main/bin/init.sh
Executable file
@@ -0,0 +1,90 @@
|
||||
#! /bin/bash
|
||||
current_path=`pwd`
|
||||
case "`uname`" in
|
||||
Linux)
|
||||
bin_abs_path=$(readlink -f $(dirname $0))
|
||||
;;
|
||||
*)
|
||||
bin_abs_path=`cd $(dirname $0); pwd`
|
||||
;;
|
||||
esac
|
||||
base=${bin_abs_path}/..
|
||||
|
||||
# default values
|
||||
PORT=3306
|
||||
MYSQL_IP="0"
|
||||
|
||||
# parse arguments
|
||||
while getopts "h:p:" opt; do
|
||||
case $opt in
|
||||
p)
|
||||
if [[ $OPTARG -gt 0 ]]; then
|
||||
PORT=$OPTARG
|
||||
else
|
||||
echo "Invalid -p option: -$OPTARG, should be positive integer >&2"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
h)
|
||||
MYSQL_IP=$OPTARG
|
||||
;;
|
||||
\?)
|
||||
printUsage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
function printUsage
|
||||
{
|
||||
cat << __Usage
|
||||
|
||||
Initialise the instance config property file automatically. This tool will connect to Mysql master server and get the binlog file and position then update the instance config property file for you.
|
||||
|
||||
Usage: $0 -p [port] -h [hostname]
|
||||
Options:
|
||||
-p: mysql port, default to 3306
|
||||
-h: mysql host ip.
|
||||
For example:
|
||||
$0 -p 3306 -h 172.17.0.2
|
||||
|
||||
Enjoy!!!
|
||||
|
||||
__Usage
|
||||
}
|
||||
|
||||
if [[ $PORT -lt 0 ]]; then
|
||||
echo "invalid port argument $PORT"
|
||||
printUsage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if mysql ip is specified
|
||||
if [[ $MYSQL_IP = "0" ]]; then
|
||||
echo '-h mysql host ip argument is required.'
|
||||
printUsage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MYSQL_ADDR="$MYSQL_IP:$PORT"
|
||||
|
||||
MASTER_STATUS=`mysql -ucanal -pcanal -h$MYSQL_IP -P$PORT -e 'show master status' 2>/dev/null | head -n 2 | tail -n 1`
|
||||
|
||||
LOG_FILE=`echo "$MASTER_STATUS" | cut -f 1`
|
||||
LOG_POS=`echo "$MASTER_STATUS" | cut -f 2`
|
||||
|
||||
INSTANCE_CONF_FILE=$base/conf/example/instance.properties
|
||||
|
||||
if [ ! -f $INSTANCE_CONF_FILE ]; then
|
||||
echo "Can not find instance config file: $INSTANCE_CONF_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# replace the binlog file and position
|
||||
sed -i.bak -e "s/canal\.instance\.master\.address = .*/canal\.instance\.master\.address = $MYSQL_ADDR/" -e "s/canal\.instance\.master\.journal\.name = .*/canal\.instance\.master\.journal\.name = $LOG_FILE/" -e "s/canal\.instance\.master\.position = .*/canal\.instance\.master\.position = $LOG_POS/" $INSTANCE_CONF_FILE
|
||||
|
||||
cat $INSTANCE_CONF_FILE
|
||||
|
||||
echo
|
||||
echo 'Above is your instance config file, check if the settings are correct'
|
||||
echo
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user