mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
Compare commits
228 Commits
canal-1.0.
...
canal-1.0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@ otter.properties
|
|||||||
jtester.properties
|
jtester.properties
|
||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
|
.DS_Store
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<h1>背景</h1>
|
<h1>背景</h1>
|
||||||
<p style="font-size: 14px;"> 早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&消费的业务,从此开启了一段新纪元。</p>
|
<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>
|
||||||
<p style="font-size: 14px;">基于日志增量订阅&消费支持的业务:</p>
|
<p style="font-size: 14px;">基于日志增量订阅&消费支持的业务:</p>
|
||||||
<ol style="font-size: 14px;">
|
<ol style="font-size: 14px;">
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<li>slave重做中继日志中的事件,将改变反映它自己的数据。</li>
|
<li>slave重做中继日志中的事件,将改变反映它自己的数据。</li>
|
||||||
</ol>
|
</ol>
|
||||||
<h3>canal的工作原理:</h3>
|
<h3>canal的工作原理:</h3>
|
||||||
<p><img width="590" src="https://camo.githubusercontent.com/46c626b4cde399db43b2634a7911a04aecf273a0/687474703a2f2f646c2e69746579652e636f6d2f75706c6f61642f6174746163686d656e742f303038302f333130372f63383762363762612d333934632d333038362d393537372d3964623035626530346339352e6a7067" alt="" height="273">
|
<p><img width="590" src="http://dl.iteye.com/upload/attachment/0080/3107/c87b67ba-394c-3086-9577-9db05be04c95.jpg" alt="" height="273">
|
||||||
<p>原理相对比较简单:</p>
|
<p>原理相对比较简单:</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li>canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议</li>
|
<li>canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议</li>
|
||||||
@@ -62,11 +62,16 @@ 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>
|
<li><a href="https://github.com/alibaba/canal/releases">Download</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h1>相关资料: </h1>
|
<h1>相关资料</h1>
|
||||||
|
|
||||||
* ADC阿里技术嘉年华分享ppt (放在google docs上,可能需要翻墙): <a href="https://docs.google.com/presentation/d/1MkszUPYRDkfVPz9IqOT1LLT5d9tuwde_WC8GZvjaDRg/edit?usp=sharing">ppt下载</href>
|
* 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)
|
* [与阿里巴巴的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>
|
<h1>问题反馈</h1>
|
||||||
<ol>
|
<ol>
|
||||||
@@ -75,3 +80,10 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
|
|||||||
<li>新浪微博: agapple0002 </li>
|
<li>新浪微博: agapple0002 </li>
|
||||||
<li>报告issue:<a href="https://github.com/alibaba/canal/issues">issues</a></li>
|
<li>报告issue:<a href="https://github.com/alibaba/canal/issues">issues</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
【招聘】阿里巴巴中间件团队招聘JAVA高级工程师
|
||||||
|
岗位主要为技术型内容(非业务部门),阿里中间件整个体系对于未来想在技术上有所沉淀的同学还是非常有帮助的
|
||||||
|
工作地点:杭州、北京均可. ps. 阿里待遇向来都是不错的,有意者可以QQ、微博私聊.
|
||||||
|
具体招聘内容:https://job.alibaba.com/zhaopin/position_detail.htm?positionId=32666
|
||||||
|
</pre>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.21</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
<links>
|
<links>
|
||||||
<link>https://github.com/alibaba/canal</link>
|
<link>https://github.com/alibaba/canal</link>
|
||||||
</links>
|
</links>
|
||||||
<outputDirectory>${project.build.directory}/apidocs/apidocs/${pom.version}</outputDirectory>
|
<outputDirectory>${project.build.directory}/apidocs/apidocs/${project.version}</outputDirectory>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|||||||
@@ -10,5 +10,7 @@ import java.net.SocketAddress;
|
|||||||
*/
|
*/
|
||||||
public interface CanalNodeAccessStrategy {
|
public interface CanalNodeAccessStrategy {
|
||||||
|
|
||||||
|
SocketAddress currentNode();
|
||||||
|
|
||||||
SocketAddress nextNode();
|
SocketAddress nextNode();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.alibaba.otter.canal.client.impl;
|
|||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -41,11 +40,17 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
public void connect() throws CanalClientException {
|
public void connect() throws CanalClientException {
|
||||||
while (currentConnector == null) {
|
while (currentConnector == null) {
|
||||||
SocketAddress nextAddress = this.accessStrategy.nextNode();
|
|
||||||
int times = 0;
|
int times = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
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.setSoTimeout(soTimeout);
|
||||||
if (filter != null) {
|
if (filter != null) {
|
||||||
currentConnector.setFilter(filter);
|
currentConnector.setFilter(filter);
|
||||||
@@ -57,7 +62,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
currentConnector.connect();
|
currentConnector.connect();
|
||||||
break;
|
break;
|
||||||
} catch (Exception e) {
|
} 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.disconnect();
|
||||||
currentConnector = null;
|
currentConnector = null;
|
||||||
// retry for #retryTimes for each node when trying to
|
// retry for #retryTimes for each node when trying to
|
||||||
@@ -101,9 +106,10 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when subscribing from server:{}\n{}",
|
logger.warn(String.format(
|
||||||
currentConnector.getAddress(),
|
"something goes wrong when subscribing from server: %s",
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
currentConnector != null ? currentConnector.getAddress() : "null"),
|
||||||
|
t);
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -120,9 +126,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
currentConnector.unsubscribe();
|
currentConnector.unsubscribe();
|
||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when unsubscribing from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when unsubscribing from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -138,9 +143,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
Message msg = currentConnector.get(batchSize);
|
Message msg = currentConnector.get(batchSize);
|
||||||
return msg;
|
return msg;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when getting data from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when getting data from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -156,9 +160,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
Message msg = currentConnector.get(batchSize, timeout, unit);
|
Message msg = currentConnector.get(batchSize, timeout, unit);
|
||||||
return msg;
|
return msg;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when getting data from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when getting data from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -174,9 +177,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
Message msg = currentConnector.getWithoutAck(batchSize);
|
Message msg = currentConnector.getWithoutAck(batchSize);
|
||||||
return msg;
|
return msg;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when getWithoutAck data from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector.getAddress()), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -192,9 +194,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
Message msg = currentConnector.getWithoutAck(batchSize, timeout, unit);
|
Message msg = currentConnector.getWithoutAck(batchSize, timeout, unit);
|
||||||
return msg;
|
return msg;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when getWithoutAck data from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector.getAddress()), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -210,9 +211,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
currentConnector.rollback(batchId);
|
currentConnector.rollback(batchId);
|
||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when rollbacking data from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector.getAddress()), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -228,9 +228,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
currentConnector.rollback();
|
currentConnector.rollback();
|
||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when rollbacking data from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector.getAddress()), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -247,9 +246,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
currentConnector.ack(batchId);
|
currentConnector.ack(batchId);
|
||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn("something goes wrong when acking data from server:{}\n{}",
|
logger.warn(String.format("something goes wrong when acking data from server:%s",
|
||||||
currentConnector.getAddress(),
|
currentConnector.getAddress()), t);
|
||||||
ExceptionUtils.getFullStackTrace(t));
|
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
|
|||||||
initRunning(this.zkClient.readData(runningPath, true));
|
initRunning(this.zkClient.readData(runningPath, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketAddress currentNode() {
|
||||||
|
return nextNode();
|
||||||
|
}
|
||||||
|
|
||||||
public SocketAddress nextNode() {
|
public SocketAddress nextNode() {
|
||||||
if (runningAddress != null) {// 如果服务已经启动,直接选择当前正在工作的节点
|
if (runningAddress != null) {// 如果服务已经启动,直接选择当前正在工作的节点
|
||||||
return runningAddress;
|
return runningAddress;
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import java.net.InetSocketAddress;
|
|||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
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.SocketChannel;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -37,7 +41,6 @@ import com.alibaba.otter.canal.protocol.ClientIdentity;
|
|||||||
import com.alibaba.otter.canal.protocol.Message;
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于{@linkplain CanalServerWithNetty}定义的网络协议接口,对于canal数据进行get/rollback/ack等操作
|
* 基于{@linkplain CanalServerWithNetty}定义的网络协议接口,对于canal数据进行get/rollback/ack等操作
|
||||||
@@ -57,6 +60,8 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
private final ByteBuffer readHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
private final ByteBuffer readHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
||||||
private final ByteBuffer writeHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
private final ByteBuffer writeHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
||||||
private SocketChannel channel;
|
private SocketChannel channel;
|
||||||
|
private ReadableByteChannel readableChannel;
|
||||||
|
private WritableByteChannel writableChannel;
|
||||||
private List<Compression> supportedCompressions = new ArrayList<Compression>();
|
private List<Compression> supportedCompressions = new ArrayList<Compression>();
|
||||||
private ClientIdentity clientIdentity;
|
private ClientIdentity clientIdentity;
|
||||||
private ClientRunningMonitor runningMonitor; // 运行控制
|
private ClientRunningMonitor runningMonitor; // 运行控制
|
||||||
@@ -125,8 +130,14 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
try {
|
try {
|
||||||
channel = SocketChannel.open();
|
channel = SocketChannel.open();
|
||||||
channel.socket().setSoTimeout(soTimeout);
|
channel.socket().setSoTimeout(soTimeout);
|
||||||
|
SocketAddress address = getAddress();
|
||||||
|
if (address == null) {
|
||||||
|
address = getNextAddress();
|
||||||
|
}
|
||||||
channel.connect(address);
|
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) {
|
if (p.getVersion() != 1) {
|
||||||
throw new CanalClientException("unsupported version at this client.");
|
throw new CanalClientException("unsupported version at this client.");
|
||||||
}
|
}
|
||||||
@@ -140,17 +151,17 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
//
|
//
|
||||||
ClientAuth ca = ClientAuth.newBuilder()
|
ClientAuth ca = ClientAuth.newBuilder()
|
||||||
.setUsername(username != null ? username : "")
|
.setUsername(username != null ? username : "")
|
||||||
|
.setPassword(ByteString.copyFromUtf8(password != null ? password : ""))
|
||||||
.setNetReadTimeout(soTimeout)
|
.setNetReadTimeout(soTimeout)
|
||||||
.setNetWriteTimeout(soTimeout)
|
.setNetWriteTimeout(soTimeout)
|
||||||
.build();
|
.build();
|
||||||
writeWithHeader(channel,
|
writeWithHeader(Packet.newBuilder()
|
||||||
Packet.newBuilder()
|
|
||||||
.setType(PacketType.CLIENTAUTHENTICATION)
|
.setType(PacketType.CLIENTAUTHENTICATION)
|
||||||
.setBody(ca.toByteString())
|
.setBody(ca.toByteString())
|
||||||
.build()
|
.build()
|
||||||
.toByteArray());
|
.toByteArray());
|
||||||
//
|
//
|
||||||
Packet ack = Packet.parseFrom(readNextPacket(channel));
|
Packet ack = Packet.parseFrom(readNextPacket());
|
||||||
if (ack.getType() != PacketType.ACK) {
|
if (ack.getType() != PacketType.ACK) {
|
||||||
throw new CanalClientException("unexpected packet type when ack is expected");
|
throw new CanalClientException("unexpected packet type when ack is expected");
|
||||||
}
|
}
|
||||||
@@ -169,14 +180,26 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void doDisconnnect() throws CanalClientException {
|
private void doDisconnnect() throws CanalClientException {
|
||||||
|
if (readableChannel != null) {
|
||||||
|
quietlyClose(readableChannel);
|
||||||
|
readableChannel = null;
|
||||||
|
}
|
||||||
|
if (writableChannel != null) {
|
||||||
|
quietlyClose(writableChannel);
|
||||||
|
writableChannel = null;
|
||||||
|
}
|
||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
|
quietlyClose(channel);
|
||||||
|
channel = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void quietlyClose(Channel channel) {
|
||||||
try {
|
try {
|
||||||
channel.close();
|
channel.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.warn("exception on closing channel:{} \n {}", channel, e);
|
logger.warn("exception on closing channel:{} \n {}", channel, e);
|
||||||
}
|
}
|
||||||
channel = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void subscribe() throws CanalClientException {
|
public void subscribe() throws CanalClientException {
|
||||||
@@ -186,8 +209,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
public void subscribe(String filter) throws CanalClientException {
|
public void subscribe(String filter) throws CanalClientException {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
try {
|
try {
|
||||||
writeWithHeader(channel,
|
writeWithHeader(Packet.newBuilder()
|
||||||
Packet.newBuilder()
|
|
||||||
.setType(PacketType.SUBSCRIPTION)
|
.setType(PacketType.SUBSCRIPTION)
|
||||||
.setBody(Sub.newBuilder()
|
.setBody(Sub.newBuilder()
|
||||||
.setDestination(clientIdentity.getDestination())
|
.setDestination(clientIdentity.getDestination())
|
||||||
@@ -198,7 +220,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
.build()
|
.build()
|
||||||
.toByteArray());
|
.toByteArray());
|
||||||
//
|
//
|
||||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
Packet p = Packet.parseFrom(readNextPacket());
|
||||||
Ack ack = Ack.parseFrom(p.getBody());
|
Ack ack = Ack.parseFrom(p.getBody());
|
||||||
if (ack.getErrorCode() > 0) {
|
if (ack.getErrorCode() > 0) {
|
||||||
throw new CanalClientException("failed to subscribe with reason: " + ack.getErrorMessage());
|
throw new CanalClientException("failed to subscribe with reason: " + ack.getErrorMessage());
|
||||||
@@ -213,8 +235,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
public void unsubscribe() throws CanalClientException {
|
public void unsubscribe() throws CanalClientException {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
try {
|
try {
|
||||||
writeWithHeader(channel,
|
writeWithHeader(Packet.newBuilder()
|
||||||
Packet.newBuilder()
|
|
||||||
.setType(PacketType.UNSUBSCRIPTION)
|
.setType(PacketType.UNSUBSCRIPTION)
|
||||||
.setBody(Unsub.newBuilder()
|
.setBody(Unsub.newBuilder()
|
||||||
.setDestination(clientIdentity.getDestination())
|
.setDestination(clientIdentity.getDestination())
|
||||||
@@ -224,7 +245,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
.build()
|
.build()
|
||||||
.toByteArray());
|
.toByteArray());
|
||||||
//
|
//
|
||||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
Packet p = Packet.parseFrom(readNextPacket());
|
||||||
Ack ack = Ack.parseFrom(p.getBody());
|
Ack ack = Ack.parseFrom(p.getBody());
|
||||||
if (ack.getErrorCode() > 0) {
|
if (ack.getErrorCode() > 0) {
|
||||||
throw new CanalClientException("failed to unSubscribe with reason: " + ack.getErrorMessage());
|
throw new CanalClientException("failed to unSubscribe with reason: " + ack.getErrorMessage());
|
||||||
@@ -257,8 +278,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
unit = TimeUnit.MILLISECONDS;
|
unit = TimeUnit.MILLISECONDS;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeWithHeader(channel,
|
writeWithHeader(Packet.newBuilder()
|
||||||
Packet.newBuilder()
|
|
||||||
.setType(PacketType.GET)
|
.setType(PacketType.GET)
|
||||||
.setBody(Get.newBuilder()
|
.setBody(Get.newBuilder()
|
||||||
.setAutoAck(false)
|
.setAutoAck(false)
|
||||||
@@ -271,15 +291,14 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
.toByteString())
|
.toByteString())
|
||||||
.build()
|
.build()
|
||||||
.toByteArray());
|
.toByteArray());
|
||||||
|
|
||||||
return receiveMessages();
|
return receiveMessages();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new CanalClientException(e);
|
throw new CanalClientException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Message receiveMessages() throws InvalidProtocolBufferException, IOException {
|
private Message receiveMessages() throws IOException {
|
||||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
Packet p = Packet.parseFrom(readNextPacket());
|
||||||
switch (p.getType()) {
|
switch (p.getType()) {
|
||||||
case MESSAGES: {
|
case MESSAGES: {
|
||||||
if (!p.getCompression().equals(Compression.NONE)) {
|
if (!p.getCompression().equals(Compression.NONE)) {
|
||||||
@@ -311,7 +330,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
.setBatchId(batchId)
|
.setBatchId(batchId)
|
||||||
.build();
|
.build();
|
||||||
try {
|
try {
|
||||||
writeWithHeader(channel, Packet.newBuilder()
|
writeWithHeader(Packet.newBuilder()
|
||||||
.setType(PacketType.CLIENTACK)
|
.setType(PacketType.CLIENTACK)
|
||||||
.setBody(ca.toByteString())
|
.setBody(ca.toByteString())
|
||||||
.build()
|
.build()
|
||||||
@@ -329,7 +348,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
.setBatchId(batchId)
|
.setBatchId(batchId)
|
||||||
.build();
|
.build();
|
||||||
try {
|
try {
|
||||||
writeWithHeader(channel, Packet.newBuilder()
|
writeWithHeader(Packet.newBuilder()
|
||||||
.setType(PacketType.CLIENTROLLBACK)
|
.setType(PacketType.CLIENTROLLBACK)
|
||||||
.setBody(ca.toByteString())
|
.setBody(ca.toByteString())
|
||||||
.build()
|
.build()
|
||||||
@@ -346,7 +365,15 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
// ==================== helper method ====================
|
// ==================== 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) {
|
synchronized (writeDataLock) {
|
||||||
writeHeader.clear();
|
writeHeader.clear();
|
||||||
writeHeader.putInt(body.length);
|
writeHeader.putInt(body.length);
|
||||||
@@ -356,7 +383,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] readNextPacket(SocketChannel channel) throws IOException {
|
private byte[] readNextPacket(ReadableByteChannel channel) throws IOException {
|
||||||
synchronized (readDataLock) {
|
synchronized (readDataLock) {
|
||||||
readHeader.clear();
|
readHeader.clear();
|
||||||
read(channel, readHeader);
|
read(channel, readHeader);
|
||||||
@@ -367,7 +394,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()) {
|
while (buffer.hasRemaining()) {
|
||||||
int r = channel.read(buffer);
|
int r = channel.read(buffer);
|
||||||
if (r == -1) {
|
if (r == -1) {
|
||||||
@@ -434,6 +461,10 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SocketAddress getNextAddress() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public SocketAddress getAddress() {
|
public SocketAddress getAddress() {
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
package com.alibaba.otter.canal.client.impl.running;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.text.MessageFormat;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
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.utils.JsonUtils;
|
||||||
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
||||||
import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils;
|
import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils;
|
||||||
|
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* clinet running控制
|
* clinet running控制
|
||||||
@@ -62,6 +64,8 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
public void handleDataDeleted(String dataPath) throws Exception {
|
public void handleDataDeleted(String dataPath) throws Exception {
|
||||||
MDC.put("destination", destination);
|
MDC.put("destination", destination);
|
||||||
mutex.set(false);
|
mutex.set(false);
|
||||||
|
// 触发一下退出,可能是人为干预的释放操作或者网络闪断引起的session expired timeout
|
||||||
|
processActiveExit();
|
||||||
if (!release && activeData != null && isMine(activeData.getAddress())) {
|
if (!release && activeData != null && isMine(activeData.getAddress())) {
|
||||||
// 如果上一次active的状态就是本机,则即时触发一下active抢占
|
// 如果上一次active的状态就是本机,则即时触发一下active抢占
|
||||||
initRunning();
|
initRunning();
|
||||||
@@ -96,7 +100,11 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
releaseRunning(); // 尝试一下release
|
releaseRunning(); // 尝试一下release
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initRunning() {
|
// 改动记录:
|
||||||
|
// 1,在方法上加synchronized关键字,保证同步顺序执行;
|
||||||
|
// 2,判断Zk上已经存在的activeData是否是本机,是的话把mutex重置为true,否则会导致死锁
|
||||||
|
// 3,增加异常处理,保证出现异常时,running节点能被删除,否则会导致死锁
|
||||||
|
public synchronized void initRunning() {
|
||||||
if (!isStart()) {
|
if (!isStart()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -116,11 +124,22 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
initRunning();
|
initRunning();
|
||||||
} else {
|
} else {
|
||||||
activeData = JsonUtils.unmarshalFromByte(bytes, ClientRunningData.class);
|
activeData = JsonUtils.unmarshalFromByte(bytes, ClientRunningData.class);
|
||||||
|
// 如果发现已经存在,判断一下是否自己,避免活锁
|
||||||
|
if (activeData.getAddress().contains(":") && isMine(activeData.getAddress())) {
|
||||||
|
mutex.set(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (ZkNoNodeException e) {
|
} catch (ZkNoNodeException e) {
|
||||||
zkClient.createPersistent(ZookeeperPathUtils.getClientIdNodePath(this.destination, clientData.getClientId()),
|
zkClient.createPersistent(ZookeeperPathUtils.getClientIdNodePath(this.destination, clientData.getClientId()),
|
||||||
true); // 尝试创建父节点
|
true); // 尝试创建父节点
|
||||||
initRunning();
|
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>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<root level="WARN">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
</root>
|
</root>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.21</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.common</artifactId>
|
<artifactId>canal.common</artifactId>
|
||||||
@@ -11,13 +11,18 @@
|
|||||||
<name>canal common module for otter ${project.version}</name>
|
<name>canal common module for otter ${project.version}</name>
|
||||||
<url>http://b2b-doc.alibaba-inc.com/display/opentech/Otter</url>
|
<url>http://b2b-doc.alibaba-inc.com/display/opentech/Otter</url>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-all</artifactId>
|
||||||
|
</dependency>
|
||||||
<!-- zk -->
|
<!-- zk -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.zookeeper</groupId>
|
<groupId>org.apache.zookeeper</groupId>
|
||||||
<artifactId>zookeeper</artifactId>
|
<artifactId>zookeeper</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.sgroschupf</groupId>
|
<groupId>com.101tec</groupId>
|
||||||
<artifactId>zkclient</artifactId>
|
<artifactId>zkclient</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- external -->
|
<!-- external -->
|
||||||
@@ -28,11 +33,10 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-lang</groupId>
|
<groupId>commons-lang</groupId>
|
||||||
<artifactId>commons-lang</artifactId>
|
<artifactId>commons-lang</artifactId>
|
||||||
<version>2.6</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework</groupId>
|
<groupId>commons-codec</groupId>
|
||||||
<artifactId>spring</artifactId>
|
<artifactId>commons-codec</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba</groupId>
|
<groupId>com.alibaba</groupId>
|
||||||
@@ -59,10 +63,37 @@
|
|||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
</dependency>
|
</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 -->
|
<!-- junit -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
package com.alibaba.otter.canal.common.utils;
|
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.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.alibaba.fastjson.TypeReference;
|
import com.alibaba.fastjson.TypeReference;
|
||||||
|
import com.alibaba.fastjson.parser.ParserConfig;
|
||||||
import com.alibaba.fastjson.serializer.JSONSerializer;
|
import com.alibaba.fastjson.serializer.JSONSerializer;
|
||||||
|
import com.alibaba.fastjson.serializer.ObjectSerializer;
|
||||||
import com.alibaba.fastjson.serializer.PropertyFilter;
|
import com.alibaba.fastjson.serializer.PropertyFilter;
|
||||||
|
import com.alibaba.fastjson.serializer.SerializeConfig;
|
||||||
import com.alibaba.fastjson.serializer.SerializeWriter;
|
import com.alibaba.fastjson.serializer.SerializeWriter;
|
||||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||||
|
|
||||||
@@ -17,6 +25,13 @@ import com.alibaba.fastjson.serializer.SerializerFeature;
|
|||||||
*/
|
*/
|
||||||
public class JsonUtils {
|
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) {
|
public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) {
|
||||||
return (T) JSON.parseObject(bytes, targetClass);// 默认为UTF-8
|
return (T) JSON.parseObject(bytes, targetClass);// 默认为UTF-8
|
||||||
}
|
}
|
||||||
@@ -70,4 +85,22 @@ public class JsonUtils {
|
|||||||
out.close();
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,15 @@ import java.util.List;
|
|||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import org.I0Itec.zkclient.IZkConnection;
|
import org.I0Itec.zkclient.ZkConnection;
|
||||||
import org.I0Itec.zkclient.exception.ZkException;
|
import org.I0Itec.zkclient.exception.ZkException;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.zookeeper.ClientCnxn;
|
import org.apache.zookeeper.ClientCnxn;
|
||||||
import org.apache.zookeeper.CreateMode;
|
|
||||||
import org.apache.zookeeper.KeeperException;
|
|
||||||
import org.apache.zookeeper.Watcher;
|
import org.apache.zookeeper.Watcher;
|
||||||
import org.apache.zookeeper.ZooDefs.Ids;
|
|
||||||
import org.apache.zookeeper.ZooKeeper;
|
import org.apache.zookeeper.ZooKeeper;
|
||||||
import org.apache.zookeeper.ZooKeeper.States;
|
|
||||||
import org.apache.zookeeper.client.ConnectStringParser;
|
import org.apache.zookeeper.client.ConnectStringParser;
|
||||||
import org.apache.zookeeper.client.HostProvider;
|
import org.apache.zookeeper.client.HostProvider;
|
||||||
import org.apache.zookeeper.client.StaticHostProvider;
|
import org.apache.zookeeper.client.StaticHostProvider;
|
||||||
import org.apache.zookeeper.data.Stat;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.util.ReflectionUtils;
|
import org.springframework.util.ReflectionUtils;
|
||||||
@@ -32,7 +27,7 @@ import org.springframework.util.ReflectionUtils;
|
|||||||
* @author jianghang 2012-7-10 下午02:31:42
|
* @author jianghang 2012-7-10 下午02:31:42
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
public class ZooKeeperx implements IZkConnection {
|
public class ZooKeeperx extends ZkConnection {
|
||||||
|
|
||||||
private static final String SERVER_COMMA = ";";
|
private static final String SERVER_COMMA = ";";
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ZooKeeperx.class);
|
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 hostProviderField = ReflectionUtils.findField(ClientCnxn.class, "hostProvider");
|
||||||
private static final Field serverAddressesField = ReflectionUtils.findField(StaticHostProvider.class,
|
private static final Field serverAddressesField = ReflectionUtils.findField(StaticHostProvider.class,
|
||||||
"serverAddresses");
|
"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 static final int DEFAULT_SESSION_TIMEOUT = 90000;
|
||||||
|
|
||||||
private ZooKeeper _zk = null;
|
private final List<String> _serversList;
|
||||||
private Lock _zookeeperLock = new ReentrantLock();
|
|
||||||
|
|
||||||
private final List<String> _servers;
|
|
||||||
private final int _sessionTimeOut;
|
private final int _sessionTimeOut;
|
||||||
|
|
||||||
public ZooKeeperx(String zkServers){
|
public ZooKeeperx(String zkServers){
|
||||||
@@ -53,109 +48,55 @@ public class ZooKeeperx implements IZkConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ZooKeeperx(String zkServers, int sessionTimeOut){
|
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;
|
_sessionTimeOut = sessionTimeOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void connect(Watcher watcher) {
|
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();
|
_zookeeperLock.lock();
|
||||||
try {
|
try {
|
||||||
if (_zk != null) {
|
if (_zk != null) {
|
||||||
throw new IllegalStateException("zk client has already been started");
|
throw new IllegalStateException("zk client has already been started");
|
||||||
}
|
}
|
||||||
|
String zkServers = _serversList.get(0);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.debug("Creating new ZookKeeper instance to connect to " + _servers + ".");
|
logger.debug("Creating new ZookKeeper instance to connect to " + zkServers + ".");
|
||||||
_zk = new ZooKeeper(_servers.get(0), _sessionTimeOut, watcher);
|
_zk = new ZooKeeper(zkServers, _sessionTimeOut, watcher);
|
||||||
configMutliCluster(_zk);
|
configMutliCluster(_zk);
|
||||||
|
ReflectionUtils.setField(zookeeperFiled, this, _zk);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ZkException("Unable to connect to " + _servers, e);
|
throw new ZkException("Unable to connect to " + zkServers, e);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
_zookeeperLock.unlock();
|
_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) {
|
public void configMutliCluster(ZooKeeper zk) {
|
||||||
if (_servers.size() == 1) {
|
if (_serversList.size() == 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String cluster1 = _servers.get(0);
|
String cluster1 = _serversList.get(0);
|
||||||
try {
|
try {
|
||||||
if (_servers.size() > 1) {
|
if (_serversList.size() > 1) {
|
||||||
// 强制的声明accessible
|
// 强制的声明accessible
|
||||||
ReflectionUtils.makeAccessible(clientCnxnField);
|
ReflectionUtils.makeAccessible(clientCnxnField);
|
||||||
ReflectionUtils.makeAccessible(hostProviderField);
|
ReflectionUtils.makeAccessible(hostProviderField);
|
||||||
ReflectionUtils.makeAccessible(serverAddressesField);
|
ReflectionUtils.makeAccessible(serverAddressesField);
|
||||||
|
|
||||||
// 添加第二组集群列表
|
// 添加第二组集群列表
|
||||||
for (int i = 1; i < _servers.size(); i++) {
|
for (int i = 1; i < _serversList.size(); i++) {
|
||||||
String cluster = _servers.get(i);
|
String cluster = _serversList.get(i);
|
||||||
// 强制获取zk中的地址信息
|
// 强制获取zk中的地址信息
|
||||||
ClientCnxn cnxn = (ClientCnxn) ReflectionUtils.getField(clientCnxnField, zk);
|
ClientCnxn cnxn = (ClientCnxn) ReflectionUtils.getField(clientCnxnField, zk);
|
||||||
HostProvider hostProvider = (HostProvider) ReflectionUtils.getField(hostProviderField, cnxn);
|
HostProvider hostProvider = (HostProvider) ReflectionUtils.getField(hostProviderField, cnxn);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.concurrent.Executors;
|
|||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.common.CanalException;
|
||||||
import org.I0Itec.zkclient.IZkDataListener;
|
import org.I0Itec.zkclient.IZkDataListener;
|
||||||
import org.I0Itec.zkclient.exception.ZkException;
|
import org.I0Itec.zkclient.exception.ZkException;
|
||||||
import org.I0Itec.zkclient.exception.ZkInterruptedException;
|
import org.I0Itec.zkclient.exception.ZkInterruptedException;
|
||||||
@@ -87,8 +88,13 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void init() {
|
||||||
|
processStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
try {
|
||||||
processStart();
|
processStart();
|
||||||
if (zkClient != null) {
|
if (zkClient != null) {
|
||||||
// 如果需要尽可能释放instance资源,不需要监听running节点,不然即使stop了这台机器,另一台机器立马会start
|
// 如果需要尽可能释放instance资源,不需要监听running节点,不然即使stop了这台机器,另一台机器立马会start
|
||||||
@@ -99,6 +105,12 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
} else {
|
} else {
|
||||||
processActiveEnter();// 没有zk,直接启动
|
processActiveEnter();// 没有zk,直接启动
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("start failed", e);
|
||||||
|
// 没有正常启动,重置一下状态,避免干扰下一次start
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void release() {
|
public void release() {
|
||||||
@@ -109,7 +121,7 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public synchronized void stop() {
|
||||||
super.stop();
|
super.stop();
|
||||||
|
|
||||||
if (zkClient != null) {
|
if (zkClient != null) {
|
||||||
@@ -230,11 +242,7 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
|
|
||||||
private void processActiveEnter() {
|
private void processActiveEnter() {
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
try {
|
|
||||||
listener.processActiveEnter();
|
listener.processActiveEnter();
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("processActiveEnter failed", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.21</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -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 origin, limit;
|
||||||
protected int position;
|
protected int position;
|
||||||
|
protected int semival;
|
||||||
|
|
||||||
protected LogBuffer(){
|
protected LogBuffer(){
|
||||||
}
|
}
|
||||||
@@ -168,8 +169,8 @@ public class LogBuffer {
|
|||||||
* @param newLimit The new limit value; must be non-negative and no larger
|
* @param newLimit The new limit value; must be non-negative and no larger
|
||||||
* than this buffer's capacity
|
* than this buffer's capacity
|
||||||
* @return This buffer
|
* @return This buffer
|
||||||
* @throws IllegalArgumentException If the preconditions on <tt>newLimit</tt>
|
* @throws IllegalArgumentException If the preconditions on
|
||||||
* do not hold
|
* <tt>newLimit</tt> do not hold
|
||||||
*/
|
*/
|
||||||
public final LogBuffer limit(int newLimit) {
|
public final LogBuffer limit(int newLimit) {
|
||||||
if (origin + newLimit > buffer.length || newLimit < 0) throw new IllegalArgumentException("capacity excceed: "
|
if (origin + newLimit > buffer.length || newLimit < 0) throw new IllegalArgumentException("capacity excceed: "
|
||||||
|
|||||||
@@ -104,9 +104,14 @@ public final class LogDecoder {
|
|||||||
try {
|
try {
|
||||||
/* Decoding binary-log to event */
|
/* Decoding binary-log to event */
|
||||||
event = decode(buffer, header, context);
|
event = decode(buffer, header, context);
|
||||||
|
if (event != null) {
|
||||||
|
event.setSemival(buffer.semival);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (logger.isWarnEnabled()) logger.warn("Decoding " + LogEvent.getTypeName(header.getType())
|
if (logger.isWarnEnabled()) {
|
||||||
+ " failed from: " + context.getLogPosition(), e);
|
logger.warn("Decoding " + LogEvent.getTypeName(header.getType()) + " failed from: "
|
||||||
|
+ context.getLogPosition(), e);
|
||||||
|
}
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
buffer.limit(limit); /* Restore limit */
|
buffer.limit(limit); /* Restore limit */
|
||||||
|
|||||||
@@ -267,6 +267,7 @@ public abstract class LogEvent {
|
|||||||
public static final int MYSQL_TYPE_TIMESTAMP2 = 17;
|
public static final int MYSQL_TYPE_TIMESTAMP2 = 17;
|
||||||
public static final int MYSQL_TYPE_DATETIME2 = 18;
|
public static final int MYSQL_TYPE_DATETIME2 = 18;
|
||||||
public static final int MYSQL_TYPE_TIME2 = 19;
|
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_NEWDECIMAL = 246;
|
||||||
public static final int MYSQL_TYPE_ENUM = 247;
|
public static final int MYSQL_TYPE_ENUM = 247;
|
||||||
public static final int MYSQL_TYPE_SET = 248;
|
public static final int MYSQL_TYPE_SET = 248;
|
||||||
@@ -359,6 +360,26 @@ public abstract class LogEvent {
|
|||||||
|
|
||||||
protected final LogHeader header;
|
protected final LogHeader header;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mysql半同步semi标识
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 0不需要semi ack 给mysql
|
||||||
|
* 1需要semi ack给mysql
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
protected int semival;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public int getSemival() {
|
||||||
|
return semival;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSemival(int semival) {
|
||||||
|
this.semival = semival;
|
||||||
|
}
|
||||||
|
|
||||||
protected LogEvent(LogHeader header){
|
protected LogEvent(LogHeader header){
|
||||||
this.header = header;
|
this.header = header;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,6 +141,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){
|
public FormatDescriptionLogEvent(final int binlogVersion){
|
||||||
this.binlogVersion = binlogVersion;
|
this.binlogVersion = binlogVersion;
|
||||||
|
|
||||||
|
|||||||
@@ -330,15 +330,12 @@ public class QueryLogEvent extends LogEvent {
|
|||||||
* MariaDb type,
|
* MariaDb type,
|
||||||
* sec_part of NOW()
|
* sec_part of NOW()
|
||||||
*/
|
*/
|
||||||
+ 1 + (MAX_DBS_IN_EVENT_MTS * (1 + NAME_LEN)) + 3 + 1 + 16 + 1 + 60/*
|
+ 1 + (MAX_DBS_IN_EVENT_MTS * (1 + NAME_LEN)) + 3 + 1 + 32 * 3
|
||||||
* type
|
+ 1 + 60/*
|
||||||
* ,
|
* type ,
|
||||||
* user_len
|
* user_len ,
|
||||||
* ,
|
* user ,
|
||||||
* user
|
* host_len ,
|
||||||
* ,
|
|
||||||
* host_len
|
|
||||||
* ,
|
|
||||||
* host
|
* host
|
||||||
*/);
|
*/);
|
||||||
/**
|
/**
|
||||||
@@ -548,6 +545,22 @@ public class QueryLogEvent extends LogEvent {
|
|||||||
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
|
* FROM MariaDB 5.5.34
|
||||||
@@ -632,6 +645,9 @@ public class QueryLogEvent extends LogEvent {
|
|||||||
mtsAccessedDbNames[i] = buffer.getFixString(length < NAME_LEN ? length : NAME_LEN);
|
mtsAccessedDbNames[i] = buffer.getFixString(length < NAME_LEN ? length : NAME_LEN);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP:
|
||||||
|
buffer.forward(1);
|
||||||
|
break;
|
||||||
case Q_HRNOW:
|
case Q_HRNOW:
|
||||||
// int when_sec_part = buffer.getUint24();
|
// int when_sec_part = buffer.getUint24();
|
||||||
buffer.forward(3);
|
buffer.forward(3);
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ public final class RotateLogEvent extends LogEvent {
|
|||||||
int filenameLen = buffer.limit() - filenameOffset;
|
int filenameLen = buffer.limit() - filenameOffset;
|
||||||
if (filenameLen > FN_REFLEN - 1) filenameLen = FN_REFLEN - 1;
|
if (filenameLen > FN_REFLEN - 1) filenameLen = FN_REFLEN - 1;
|
||||||
buffer.position(filenameOffset);
|
buffer.position(filenameOffset);
|
||||||
|
|
||||||
filename = buffer.getFixString(filenameLen);
|
filename = buffer.getFixString(filenameLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import java.util.BitSet;
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import com.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.LogBuffer;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
|
||||||
@@ -25,6 +27,7 @@ public final class RowsLogBuffer {
|
|||||||
public static final long DATETIMEF_INT_OFS = 0x8000000000L;
|
public static final long DATETIMEF_INT_OFS = 0x8000000000L;
|
||||||
public static final long TIMEF_INT_OFS = 0x800000L;
|
public static final long TIMEF_INT_OFS = 0x800000L;
|
||||||
public static final long TIMEF_OFS = 0x800000000000L;
|
public static final long TIMEF_OFS = 0x800000000000L;
|
||||||
|
|
||||||
private final LogBuffer buffer;
|
private final LogBuffer buffer;
|
||||||
private final int columnLen;
|
private final int columnLen;
|
||||||
private final String charsetName;
|
private final String charsetName;
|
||||||
@@ -945,6 +948,42 @@ public final class RowsLogBuffer {
|
|||||||
length = len;
|
length = len;
|
||||||
break;
|
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: {
|
case LogEvent.MYSQL_TYPE_GEOMETRY: {
|
||||||
/*
|
/*
|
||||||
* MYSQL_TYPE_GEOMETRY: copy from BLOB or TEXT
|
* MYSQL_TYPE_GEOMETRY: copy from BLOB or TEXT
|
||||||
@@ -1026,4 +1065,5 @@ public final class RowsLogBuffer {
|
|||||||
|
|
||||||
return sec.substring(0, meta);
|
return sec.substring(0, meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -402,6 +402,7 @@ public final class TableMapLogEvent extends LogEvent {
|
|||||||
case MYSQL_TYPE_DOUBLE:
|
case MYSQL_TYPE_DOUBLE:
|
||||||
case MYSQL_TYPE_FLOAT:
|
case MYSQL_TYPE_FLOAT:
|
||||||
case MYSQL_TYPE_GEOMETRY:
|
case MYSQL_TYPE_GEOMETRY:
|
||||||
|
case MYSQL_TYPE_JSON:
|
||||||
/*
|
/*
|
||||||
* These types store a single byte.
|
* These types store a single byte.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,9 +13,30 @@ import com.taobao.tddl.dbsync.binlog.event.LogHeader;
|
|||||||
*/
|
*/
|
||||||
public class MariaGtidLogEvent extends IgnorableLogEvent {
|
public class MariaGtidLogEvent extends IgnorableLogEvent {
|
||||||
|
|
||||||
|
private long gtid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* mariadb gtidlog event format
|
||||||
|
* uint<8> GTID sequence
|
||||||
|
* uint<4> Replication Domain ID
|
||||||
|
* uint<1> Flags
|
||||||
|
*
|
||||||
|
* if flag & FL_GROUP_COMMIT_ID
|
||||||
|
* uint<8> commit_id
|
||||||
|
* else
|
||||||
|
* uint<6> 0
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
|
||||||
public MariaGtidLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
public MariaGtidLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
||||||
super(header, buffer, descriptionEvent);
|
super(header, buffer, descriptionEvent);
|
||||||
|
gtid = buffer.getUlong64().longValue();
|
||||||
// do nothing , just ignore log event
|
// do nothing , just ignore log event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getGtid() {
|
||||||
|
return gtid;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.21</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ public class CanalConstants {
|
|||||||
|
|
||||||
public static final String CANAL_DESTINATION_PROPERTY = ROOT + ".instance.destination";
|
public static final String CANAL_DESTINATION_PROPERTY = ROOT + ".instance.destination";
|
||||||
|
|
||||||
|
public static final String CANAL_SOCKETCHANNEL = ROOT + "." + "socketChannel";
|
||||||
|
|
||||||
public static String getInstanceModeKey(String destination) {
|
public static String getInstanceModeKey(String destination) {
|
||||||
return MessageFormat.format(INSTANCE_MODE_TEMPLATE, destination);
|
return MessageFormat.format(INSTANCE_MODE_TEMPLATE, destination);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.util.Properties;
|
|||||||
|
|
||||||
import org.I0Itec.zkclient.IZkStateListener;
|
import org.I0Itec.zkclient.IZkStateListener;
|
||||||
import org.I0Itec.zkclient.exception.ZkNoNodeException;
|
import org.I0Itec.zkclient.exception.ZkNoNodeException;
|
||||||
|
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
|
||||||
import org.apache.commons.lang.BooleanUtils;
|
import org.apache.commons.lang.BooleanUtils;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.zookeeper.Watcher.Event.KeeperState;
|
import org.apache.zookeeper.Watcher.Event.KeeperState;
|
||||||
@@ -84,13 +85,19 @@ public class CanalController {
|
|||||||
// 初始化instance config
|
// 初始化instance config
|
||||||
initInstanceConfig(properties);
|
initInstanceConfig(properties);
|
||||||
|
|
||||||
|
// init socketChannel
|
||||||
|
String socketChannel = getProperty(properties, CanalConstants.CANAL_SOCKETCHANNEL);
|
||||||
|
if (StringUtils.isNotEmpty(socketChannel)) {
|
||||||
|
System.setProperty(CanalConstants.CANAL_SOCKETCHANNEL, socketChannel);
|
||||||
|
}
|
||||||
|
|
||||||
// 准备canal server
|
// 准备canal server
|
||||||
cid = Long.valueOf(getProperty(properties, CanalConstants.CANAL_ID));
|
cid = Long.valueOf(getProperty(properties, CanalConstants.CANAL_ID));
|
||||||
ip = getProperty(properties, CanalConstants.CANAL_IP);
|
ip = getProperty(properties, CanalConstants.CANAL_IP);
|
||||||
port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT));
|
port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT));
|
||||||
embededCanalServer = new CanalServerWithEmbedded();
|
embededCanalServer = CanalServerWithEmbedded.instance();
|
||||||
embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator
|
embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator
|
||||||
canalServer = new CanalServerWithNetty(embededCanalServer);
|
canalServer = CanalServerWithNetty.instance();
|
||||||
canalServer.setIp(ip);
|
canalServer.setIp(ip);
|
||||||
canalServer.setPort(port);
|
canalServer.setPort(port);
|
||||||
|
|
||||||
@@ -148,6 +155,11 @@ public class CanalController {
|
|||||||
public void handleNewSession() throws Exception {
|
public void handleNewSession() throws Exception {
|
||||||
initCid(path);
|
initCid(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleSessionEstablishmentError(Throwable error) throws Exception {
|
||||||
|
logger.error("failed to connect to zookeeper", error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -172,6 +184,8 @@ public class CanalController {
|
|||||||
if (zkclientx != null) {
|
if (zkclientx != null) {
|
||||||
runningMonitor.setZkClient(zkclientx);
|
runningMonitor.setZkClient(zkclientx);
|
||||||
}
|
}
|
||||||
|
// 触发创建一下cid节点
|
||||||
|
runningMonitor.init();
|
||||||
return runningMonitor;
|
return runningMonitor;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -189,10 +203,10 @@ public class CanalController {
|
|||||||
instanceConfigs.put(destination, config);
|
instanceConfigs.put(destination, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.getLazy() && !embededCanalServer.isStart(destination)) {
|
if (!embededCanalServer.isStart(destination)) {
|
||||||
// HA机制启动
|
// HA机制启动
|
||||||
ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination);
|
ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination);
|
||||||
if (!runningMonitor.isStart()) {
|
if (!config.getLazy() && !runningMonitor.isStart()) {
|
||||||
runningMonitor.start();
|
runningMonitor.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +245,13 @@ public class CanalController {
|
|||||||
if (StringUtils.isEmpty(rootDir)) {
|
if (StringUtils.isEmpty(rootDir)) {
|
||||||
rootDir = "../conf";
|
rootDir = "../conf";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StringUtils.equals("otter-canal", System.getProperty("appName"))) {
|
||||||
monitor.setRootConf(rootDir);
|
monitor.setRootConf(rootDir);
|
||||||
|
} else {
|
||||||
|
// eclipse debug模式
|
||||||
|
monitor.setRootConf("src/main/resources/");
|
||||||
|
}
|
||||||
return monitor;
|
return monitor;
|
||||||
} else if (mode.isManager()) {
|
} else if (mode.isManager()) {
|
||||||
return new ManagerInstanceConfigMonitor();
|
return new ManagerInstanceConfigMonitor();
|
||||||
@@ -374,6 +394,11 @@ public class CanalController {
|
|||||||
public void handleNewSession() throws Exception {
|
public void handleNewSession() throws Exception {
|
||||||
initCid(path);
|
initCid(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleSessionEstablishmentError(Throwable error) throws Exception {
|
||||||
|
logger.error("failed to connect to zookeeper", error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 优先启动embeded服务
|
// 优先启动embeded服务
|
||||||
@@ -383,10 +408,10 @@ public class CanalController {
|
|||||||
final String destination = entry.getKey();
|
final String destination = entry.getKey();
|
||||||
InstanceConfig config = entry.getValue();
|
InstanceConfig config = entry.getValue();
|
||||||
// 创建destination的工作节点
|
// 创建destination的工作节点
|
||||||
if (!config.getLazy() && !embededCanalServer.isStart(destination)) {
|
if (!embededCanalServer.isStart(destination)) {
|
||||||
// HA机制启动
|
// HA机制启动
|
||||||
ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination);
|
ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination);
|
||||||
if (!runningMonitor.isStart()) {
|
if (!config.getLazy() && !runningMonitor.isStart()) {
|
||||||
runningMonitor.start();
|
runningMonitor.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -442,6 +467,9 @@ public class CanalController {
|
|||||||
String parentDir = path.substring(0, path.lastIndexOf('/'));
|
String parentDir = path.substring(0, path.lastIndexOf('/'));
|
||||||
zkclientx.createPersistent(parentDir, true);
|
zkclientx.createPersistent(parentDir, true);
|
||||||
zkclientx.createEphemeral(path);
|
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 java.util.Properties;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -21,6 +20,10 @@ public class CanalLauncher {
|
|||||||
|
|
||||||
public static void main(String[] args) throws Throwable {
|
public static void main(String[] args) throws Throwable {
|
||||||
try {
|
try {
|
||||||
|
logger.info("## set default uncaught exception handler");
|
||||||
|
setGlobalUncaughtExceptionHandler();
|
||||||
|
|
||||||
|
logger.info("## load canal configurations");
|
||||||
String conf = System.getProperty("canal.conf", "classpath:canal.properties");
|
String conf = System.getProperty("canal.conf", "classpath:canal.properties");
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
|
if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
|
||||||
@@ -41,8 +44,7 @@ public class CanalLauncher {
|
|||||||
logger.info("## stop the canal server");
|
logger.info("## stop the canal server");
|
||||||
controller.stop();
|
controller.stop();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.warn("##something goes wrong when stopping canal Server:\n{}",
|
logger.warn("##something goes wrong when stopping canal Server:", e);
|
||||||
ExceptionUtils.getFullStackTrace(e));
|
|
||||||
} finally {
|
} finally {
|
||||||
logger.info("## canal server is down.");
|
logger.info("## canal server is down.");
|
||||||
}
|
}
|
||||||
@@ -50,9 +52,17 @@ public class CanalLauncher {
|
|||||||
|
|
||||||
});
|
});
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.error("## Something goes wrong when starting up the canal Server:\n{}",
|
logger.error("## Something goes wrong when starting up the canal Server:", e);
|
||||||
ExceptionUtils.getFullStackTrace(e));
|
|
||||||
System.exit(0);
|
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 java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@@ -49,6 +48,8 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
|||||||
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1,
|
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1,
|
||||||
new NamedThreadFactory("canal-instance-scan"));
|
new NamedThreadFactory("canal-instance-scan"));
|
||||||
|
|
||||||
|
private volatile boolean isFirst = true;
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
super.start();
|
super.start();
|
||||||
Assert.notNull(rootConf, "root conf dir is null!");
|
Assert.notNull(rootConf, "root conf dir is null!");
|
||||||
@@ -58,6 +59,9 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
|||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
scan();
|
scan();
|
||||||
|
if (isFirst) {
|
||||||
|
isFirst = false;
|
||||||
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.error("scan failed", e);
|
logger.error("scan failed", e);
|
||||||
}
|
}
|
||||||
@@ -122,13 +126,18 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
|||||||
|
|
||||||
if (!actions.containsKey(destination) && instanceConfigs.length > 0) {
|
if (!actions.containsKey(destination) && instanceConfigs.length > 0) {
|
||||||
// 存在合法的instance.properties,并且第一次添加时,进行启动操作
|
// 存在合法的instance.properties,并且第一次添加时,进行启动操作
|
||||||
notifyStart(instanceDir, destination);
|
notifyStart(instanceDir, destination, instanceConfigs);
|
||||||
} else if (actions.containsKey(destination)) {
|
} else if (actions.containsKey(destination)) {
|
||||||
// 历史已经启动过
|
// 历史已经启动过
|
||||||
if (instanceConfigs.length == 0) { // 如果不存在合法的instance.properties
|
if (instanceConfigs.length == 0) { // 如果不存在合法的instance.properties
|
||||||
notifyStop(destination);
|
notifyStop(destination);
|
||||||
} else {
|
} else {
|
||||||
InstanceConfigFiles lastFile = lastFiles.get(destination);
|
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());
|
boolean hasChanged = judgeFileChanged(instanceConfigs, lastFile.getInstanceFiles());
|
||||||
// 通知变化
|
// 通知变化
|
||||||
if (hasChanged) {
|
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 {
|
try {
|
||||||
defaultAction.start(destination);
|
defaultAction.start(destination);
|
||||||
actions.put(destination, defaultAction);
|
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);
|
logger.info("auto notify start {} successful.", destination);
|
||||||
} catch (Throwable e) {
|
} 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);
|
InstanceAction action = actions.remove(destination);
|
||||||
try {
|
try {
|
||||||
action.stop(destination);
|
action.stop(destination);
|
||||||
|
lastFiles.remove(destination);
|
||||||
logger.info("auto notify stop {} successful.", destination);
|
logger.info("auto notify stop {} successful.", destination);
|
||||||
} catch (Throwable e) {
|
} 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时再执行删除
|
actions.put(destination, action);// 再重新加回去,下一次scan时再执行删除
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,9 +208,7 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
|||||||
action.reload(destination);
|
action.reload(destination);
|
||||||
logger.info("auto notify reload {} successful.", destination);
|
logger.info("auto notify reload {} successful.", destination);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.error("scan reload found[{}] but reload failed",
|
logger.error(String.format("scan reload found[%s] but reload failed", destination), e);
|
||||||
destination,
|
|
||||||
ExceptionUtils.getFullStackTrace(e));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,5 +311,4 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,10 +36,12 @@ canal.instance.network.sendBufferSize = 16384
|
|||||||
canal.instance.network.soTimeout = 30
|
canal.instance.network.soTimeout = 30
|
||||||
|
|
||||||
# binlog filter config
|
# binlog filter config
|
||||||
|
canal.instance.filter.druid.ddl = true
|
||||||
canal.instance.filter.query.dcl = false
|
canal.instance.filter.query.dcl = false
|
||||||
canal.instance.filter.query.dml = false
|
canal.instance.filter.query.dml = false
|
||||||
canal.instance.filter.query.ddl = false
|
canal.instance.filter.query.ddl = false
|
||||||
canal.instance.filter.table.error = false
|
canal.instance.filter.table.error = false
|
||||||
|
canal.instance.filter.rows = false
|
||||||
|
|
||||||
# binlog format/image check
|
# binlog format/image check
|
||||||
canal.instance.binlog.format = ROW,STATEMENT,MIXED
|
canal.instance.binlog.format = ROW,STATEMENT,MIXED
|
||||||
@@ -58,9 +60,13 @@ canal.conf.dir = ../conf
|
|||||||
canal.auto.scan = true
|
canal.auto.scan = true
|
||||||
canal.auto.scan.interval = 5
|
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.mode = spring
|
||||||
canal.instance.global.lazy = false
|
canal.instance.global.lazy = false
|
||||||
#canal.instance.global.manager.address = 127.0.0.1:1099
|
#canal.instance.global.manager.address = 127.0.0.1:1099
|
||||||
|
#canal.instance.global.spring.xml = classpath:spring/local-instance.xml
|
||||||
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
|
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
|
||||||
canal.instance.global.spring.xml = classpath:spring/file-instance.xml
|
canal.instance.global.spring.xml = classpath:spring/file-instance.xml
|
||||||
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
|
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
|
||||||
@@ -1,27 +1,33 @@
|
|||||||
#################################################
|
#################################################
|
||||||
## mysql serverId
|
## mysql serverId
|
||||||
canal.instance.mysql.slaveId = 1234
|
canal.instance.mysql.slaveId=0
|
||||||
|
|
||||||
# position info
|
# position info
|
||||||
canal.instance.master.address=127.0.0.1:3306
|
canal.instance.master.address=127.0.0.1:3306
|
||||||
canal.instance.master.journal.name=
|
canal.instance.master.journal.name=
|
||||||
canal.instance.master.position=
|
canal.instance.master.position=
|
||||||
canal.instance.master.timestamp=
|
canal.instance.master.timestamp=
|
||||||
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
#canal.instance.standby.address =
|
#canal.instance.standby.address =
|
||||||
#canal.instance.standby.journal.name =
|
#canal.instance.standby.journal.name =
|
||||||
#canal.instance.standby.position =
|
#canal.instance.standby.position =
|
||||||
#canal.instance.standby.timestamp =
|
#canal.instance.standby.timestamp =
|
||||||
|
|
||||||
# username/password
|
# username/password
|
||||||
canal.instance.dbUsername=canal
|
canal.instance.dbUsername=canal
|
||||||
canal.instance.dbPassword=canal
|
canal.instance.dbPassword=canal
|
||||||
canal.instance.defaultDatabaseName =
|
canal.instance.defaultDatabaseName=test
|
||||||
canal.instance.connectionCharset=UTF-8
|
canal.instance.connectionCharset=UTF-8
|
||||||
|
|
||||||
# table regex
|
# table regex
|
||||||
canal.instance.filter.regex=.*\\..*
|
canal.instance.filter.regex=.*\\..*
|
||||||
# table black regex
|
# table black regex
|
||||||
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=
|
||||||
|
#################################################
|
||||||
@@ -75,9 +75,7 @@
|
|||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
<root level="WARN">
|
<root level="WARN">
|
||||||
<!--
|
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
-->
|
|
||||||
<appender-ref ref="CANAL-ROOT" />
|
<appender-ref ref="CANAL-ROOT" />
|
||||||
</root>
|
</root>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
|
|
||||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<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="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||||
@@ -128,14 +128,14 @@
|
|||||||
<!-- 解析位点记录 -->
|
<!-- 解析位点记录 -->
|
||||||
<property name="logPositionManager">
|
<property name="logPositionManager">
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||||
<property name="primary">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||||
</property>
|
</constructor-arg>
|
||||||
<property name="failback">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||||
<property name="metaManager" ref="metaManager" />
|
<constructor-arg ref="metaManager"/>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</constructor-arg>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
@@ -178,8 +178,14 @@
|
|||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl: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="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||||
|
|
||||||
|
<!--表结构相关-->
|
||||||
|
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||||
|
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
|
|
||||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<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="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||||
@@ -113,14 +113,14 @@
|
|||||||
<!-- 解析位点记录 -->
|
<!-- 解析位点记录 -->
|
||||||
<property name="logPositionManager">
|
<property name="logPositionManager">
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||||
<property name="primary">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||||
</property>
|
</constructor-arg>
|
||||||
<property name="failback">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||||
<property name="metaManager" ref="metaManager" />
|
<constructor-arg ref="metaManager"/>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</constructor-arg>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
@@ -163,8 +163,14 @@
|
|||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl: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="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||||
|
|
||||||
|
<!--表结构相关-->
|
||||||
|
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||||
|
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
|
|
||||||
<bean id="eventParser1" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser1" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<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="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||||
@@ -160,6 +160,7 @@
|
|||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl: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="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||||
@@ -167,7 +168,7 @@
|
|||||||
|
|
||||||
<bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<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="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||||
@@ -250,6 +251,8 @@
|
|||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl: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="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||||
|
|||||||
141
deployer/src/main/resources/spring/local-instance.xml
Normal file
141
deployer/src/main/resources/spring/local-instance.xml
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
|
||||||
|
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
|
||||||
|
xmlns:context="http://www.springframework.org/schema/context"
|
||||||
|
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||||
|
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
|
||||||
|
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
|
||||||
|
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
|
||||||
|
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
|
||||||
|
default-autowire="byName">
|
||||||
|
|
||||||
|
<!-- properties -->
|
||||||
|
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
|
||||||
|
<property name="ignoreResourceNotFound" value="true" />
|
||||||
|
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
|
||||||
|
<property name="locationNames">
|
||||||
|
<list>
|
||||||
|
<value>classpath:canal.properties</value>
|
||||||
|
<value>classpath:${canal.instance.destination:}/instance.properties</value>
|
||||||
|
</list>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
|
||||||
|
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
||||||
|
<property name="propertyEditorRegistrars">
|
||||||
|
<list>
|
||||||
|
<ref bean="socketAddressEditor" />
|
||||||
|
</list>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
|
||||||
|
<property name="destination" value="${canal.instance.destination}" />
|
||||||
|
<property name="eventParser">
|
||||||
|
<ref local="eventParser" />
|
||||||
|
</property>
|
||||||
|
<property name="eventSink">
|
||||||
|
<ref local="eventSink" />
|
||||||
|
</property>
|
||||||
|
<property name="eventStore">
|
||||||
|
<ref local="eventStore" />
|
||||||
|
</property>
|
||||||
|
<property name="metaManager">
|
||||||
|
<ref local="metaManager" />
|
||||||
|
</property>
|
||||||
|
<property name="alarmHandler">
|
||||||
|
<ref local="alarmHandler" />
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<!-- 报警处理类 -->
|
||||||
|
<bean id="alarmHandler" class="com.alibaba.otter.canal.common.alarm.LogAlarmHandler" />
|
||||||
|
|
||||||
|
<bean id="metaManager" class="com.alibaba.otter.canal.meta.FileMixedMetaManager">
|
||||||
|
<property name="dataDir" value="${canal.file.data.dir:../conf}" />
|
||||||
|
<property name="period" value="${canal.file.flush.period:1000}" />
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="eventStore" class="com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer">
|
||||||
|
<property name="bufferSize" value="${canal.instance.memory.buffer.size:16384}" />
|
||||||
|
<property name="bufferMemUnit" value="${canal.instance.memory.buffer.memunit:1024}" />
|
||||||
|
<property name="batchMode" value="${canal.instance.memory.batch.mode:MEMSIZE}" />
|
||||||
|
<property name="ddlIsolation" value="${canal.instance.get.ddl.isolation:false}" />
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
|
||||||
|
<property name="eventStore" ref="eventStore" />
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsLocalBinlogEventParser">
|
||||||
|
<property name="destination" value="${canal.instance.destination}" />
|
||||||
|
<property name="alarmHandler" ref="alarmHandler" />
|
||||||
|
|
||||||
|
<!-- 解析过滤处理 -->
|
||||||
|
<property name="eventFilter">
|
||||||
|
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
|
||||||
|
<constructor-arg index="0" value="${canal.instance.filter.regex:.*\..*}" />
|
||||||
|
</bean>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<property name="eventBlackFilter">
|
||||||
|
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
|
||||||
|
<constructor-arg index="0" value="${canal.instance.filter.black.regex:}" />
|
||||||
|
<constructor-arg index="1" value="false" />
|
||||||
|
</bean>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<!-- 最大事务解析大小,超过该大小后事务将被切分为多个事务投递 -->
|
||||||
|
<property name="transactionSize" value="${canal.instance.transaction.size:1024}" />
|
||||||
|
|
||||||
|
<!-- 解析编码 -->
|
||||||
|
<property name="connectionCharset" value="${canal.instance.connectionCharset:UTF-8}" />
|
||||||
|
|
||||||
|
<!-- 解析位点记录 -->
|
||||||
|
<property name="logPositionManager">
|
||||||
|
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||||
|
<constructor-arg>
|
||||||
|
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||||
|
</constructor-arg>
|
||||||
|
<constructor-arg>
|
||||||
|
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||||
|
<constructor-arg ref="metaManager"/>
|
||||||
|
</bean>
|
||||||
|
</constructor-arg>
|
||||||
|
</bean>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<!-- 解析数据库信息 -->
|
||||||
|
<property name="masterInfo">
|
||||||
|
<bean class="com.alibaba.otter.canal.parse.support.AuthenticationInfo">
|
||||||
|
<property name="address" value="${canal.instance.master.address}" />
|
||||||
|
<property name="username" value="${canal.instance.dbUsername:retl}" />
|
||||||
|
<property name="password" value="${canal.instance.dbPassword:retl}" />
|
||||||
|
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
|
||||||
|
</bean>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
|
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||||
|
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
|
||||||
|
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
|
||||||
|
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||||
|
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
|
<property name="needWait" value="${canal.instance.parser.needWait:false}"/>
|
||||||
|
<property name="directory" value="${canal.instance.parser.directory:}"/>
|
||||||
|
|
||||||
|
<!-- rds相关 -->
|
||||||
|
<property name="url" value="${canal.instance.rds.open.url:}"/>
|
||||||
|
<property name="accesskey" value="${canal.instance.rds.open.accesskey:}"/>
|
||||||
|
<property name="secretkey" value="${canal.instance.rds.open.secretkey:}"/>
|
||||||
|
<property name="instanceId" value="${canal.instance.rds.instanceId:}"/>
|
||||||
|
<property name="startTime" value="${canal.instance.rds.startTime:}"/>
|
||||||
|
<property name="endTime" value="${canal.instance.rds.endTime:}"/>
|
||||||
|
|
||||||
|
<!--表结构相关-->
|
||||||
|
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||||
|
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||||
|
</bean>
|
||||||
|
</beans>
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
|
|
||||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||||
<property name="destination" value="${canal.instance.destination}" />
|
<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="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||||
@@ -151,8 +151,14 @@
|
|||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl: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="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||||
|
|
||||||
|
<!--表结构相关-->
|
||||||
|
<property name="enableTsdb" value="${canal.instance.tsdb.enable:false}"/>
|
||||||
|
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||||
</bean>
|
</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:canal}" />
|
||||||
|
<property name="password" value="${canal.instance.tsdb.dbPassword:canal}" />
|
||||||
|
<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:canal}" />
|
||||||
|
<property name="password" value="${canal.instance.tsdb.dbPassword:canal}" />
|
||||||
|
<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='表结构变化明细表';
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.21</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
@@ -33,10 +33,6 @@
|
|||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.netty</groupId>
|
|
||||||
<artifactId>netty</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<!-- test dependency -->
|
<!-- test dependency -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
|
|||||||
@@ -2,19 +2,19 @@ package com.alibaba.otter.canal.parse.driver.mysql;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
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.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.ErrorPacket;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.HandshakeInitializationPacket;
|
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.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.MySQLPasswordEncrypter;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
||||||
|
|
||||||
@@ -34,18 +34,26 @@ public class MysqlConnector {
|
|||||||
private byte charsetNumber = 33;
|
private byte charsetNumber = 33;
|
||||||
private String defaultSchema = "retl";
|
private String defaultSchema = "retl";
|
||||||
private int soTimeout = 30 * 1000;
|
private int soTimeout = 30 * 1000;
|
||||||
|
private int connTimeout = 5 * 1000;
|
||||||
private int receiveBufferSize = 16 * 1024;
|
private int receiveBufferSize = 16 * 1024;
|
||||||
private int sendBufferSize = 16 * 1024;
|
private int sendBufferSize = 16 * 1024;
|
||||||
|
|
||||||
private SocketChannel channel;
|
private SocketChannel channel;
|
||||||
|
private volatile boolean dumping = false;
|
||||||
|
// mysql connectinnId
|
||||||
|
private long connectionId = -1;
|
||||||
private AtomicBoolean connected = new AtomicBoolean(false);
|
private AtomicBoolean connected = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
public static final int timeout = 5 * 1000; // 5s
|
||||||
|
|
||||||
public MysqlConnector(){
|
public MysqlConnector(){
|
||||||
}
|
}
|
||||||
|
|
||||||
public MysqlConnector(InetSocketAddress address, String username, String password){
|
public MysqlConnector(InetSocketAddress address, String username, String password){
|
||||||
|
String addr = address.getHostString();
|
||||||
|
int port = address.getPort();
|
||||||
|
this.address = new InetSocketAddress(addr, port);
|
||||||
|
|
||||||
this.address = address;
|
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
}
|
}
|
||||||
@@ -61,14 +69,12 @@ public class MysqlConnector {
|
|||||||
public void connect() throws IOException {
|
public void connect() throws IOException {
|
||||||
if (connected.compareAndSet(false, true)) {
|
if (connected.compareAndSet(false, true)) {
|
||||||
try {
|
try {
|
||||||
channel = SocketChannel.open();
|
channel = SocketChannelPool.open(address);
|
||||||
configChannel(channel);
|
|
||||||
logger.info("connect MysqlConnection to {}...", address);
|
logger.info("connect MysqlConnection to {}...", address);
|
||||||
channel.connect(address);
|
|
||||||
negotiate(channel);
|
negotiate(channel);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
disconnect();
|
disconnect();
|
||||||
throw new IOException("connect " + this.address + " failure:" + ExceptionUtils.getStackTrace(e));
|
throw new IOException("connect " + this.address + " failure", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.error("the channel can't be connected twice.");
|
logger.error("the channel can't be connected twice.");
|
||||||
@@ -86,10 +92,29 @@ public class MysqlConnector {
|
|||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
channel.close();
|
channel.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("disConnect MysqlConnection to {}...", address);
|
logger.info("disConnect MysqlConnection to {}...", address);
|
||||||
} catch (Exception e) {
|
} 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 {
|
} else {
|
||||||
logger.info("the channel {} is not connected", this.address);
|
logger.info("the channel {} is not connected", this.address);
|
||||||
@@ -110,23 +135,23 @@ public class MysqlConnector {
|
|||||||
connector.setReceiveBufferSize(getReceiveBufferSize());
|
connector.setReceiveBufferSize(getReceiveBufferSize());
|
||||||
connector.setSendBufferSize(getSendBufferSize());
|
connector.setSendBufferSize(getSendBufferSize());
|
||||||
connector.setSoTimeout(getSoTimeout());
|
connector.setSoTimeout(getSoTimeout());
|
||||||
|
connector.setConnTimeout(connTimeout);
|
||||||
return connector;
|
return connector;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== help method ====================
|
public void quit() throws IOException {
|
||||||
|
QuitCommandPacket quit = new QuitCommandPacket();
|
||||||
|
byte[] cmdBody = quit.toBytes();
|
||||||
|
|
||||||
private void configChannel(SocketChannel channel) throws IOException {
|
HeaderPacket quitHeader = new HeaderPacket();
|
||||||
channel.socket().setKeepAlive(true);
|
quitHeader.setPacketBodyLength(cmdBody.length);
|
||||||
channel.socket().setReuseAddress(true);
|
quitHeader.setPacketSequenceNumber((byte) 0x00);
|
||||||
channel.socket().setSoTimeout(soTimeout);
|
PacketManager.writePkg(channel, quitHeader.toBytes(), cmdBody);
|
||||||
channel.socket().setTcpNoDelay(true);
|
|
||||||
channel.socket().setReceiveBufferSize(receiveBufferSize);
|
|
||||||
channel.socket().setSendBufferSize(sendBufferSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void negotiate(SocketChannel channel) throws IOException {
|
private void negotiate(SocketChannel channel) throws IOException {
|
||||||
HeaderPacket header = PacketManager.readHeader(channel, 4);
|
HeaderPacket header = PacketManager.readHeader(channel, 4, timeout);
|
||||||
byte[] body = PacketManager.readBytes(channel, header.getPacketBodyLength());
|
byte[] body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
||||||
if (body[0] < 0) {// check field_count
|
if (body[0] < 0) {// check field_count
|
||||||
if (body[0] == -1) {
|
if (body[0] == -1) {
|
||||||
ErrorPacket error = new ErrorPacket();
|
ErrorPacket error = new ErrorPacket();
|
||||||
@@ -140,6 +165,7 @@ public class MysqlConnector {
|
|||||||
}
|
}
|
||||||
HandshakeInitializationPacket handshakePacket = new HandshakeInitializationPacket();
|
HandshakeInitializationPacket handshakePacket = new HandshakeInitializationPacket();
|
||||||
handshakePacket.fromBytes(body);
|
handshakePacket.fromBytes(body);
|
||||||
|
connectionId = handshakePacket.threadId; // 记录一下connection
|
||||||
|
|
||||||
logger.info("handshake initialization packet received, prepare the client authentication packet to send");
|
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.setPacketBodyLength(clientAuthPkgBody.length);
|
||||||
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
|
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
|
||||||
|
|
||||||
PacketManager.write(channel,
|
PacketManager.writePkg(channel, h.toBytes(), clientAuthPkgBody);
|
||||||
new ByteBuffer[] { ByteBuffer.wrap(h.toBytes()), ByteBuffer.wrap(clientAuthPkgBody) });
|
|
||||||
logger.info("client authentication packet is sent out.");
|
logger.info("client authentication packet is sent out.");
|
||||||
|
|
||||||
// check auth result
|
// check auth result
|
||||||
header = null;
|
header = null;
|
||||||
header = PacketManager.readHeader(channel, 4);
|
header = PacketManager.readHeader(channel, 4);
|
||||||
body = null;
|
body = null;
|
||||||
body = PacketManager.readBytes(channel, header.getPacketBodyLength());
|
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
||||||
assert body != null;
|
assert body != null;
|
||||||
if (body[0] < 0) {
|
if (body[0] < 0) {
|
||||||
if (body[0] == -1) {
|
if (body[0] == -1) {
|
||||||
@@ -194,7 +219,7 @@ public class MysqlConnector {
|
|||||||
h323.setPacketBodyLength(b323Body.length);
|
h323.setPacketBodyLength(b323Body.length);
|
||||||
h323.setPacketSequenceNumber((byte) (packetSequenceNumber + 1));
|
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.");
|
logger.info("client 323 authentication packet is sent out.");
|
||||||
// check auth result
|
// check auth result
|
||||||
HeaderPacket header = PacketManager.readHeader(channel, 4);
|
HeaderPacket header = PacketManager.readHeader(channel, 4);
|
||||||
@@ -291,4 +316,32 @@ public class MysqlConnector {
|
|||||||
this.password = password;
|
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;
|
package com.alibaba.otter.canal.parse.driver.mysql;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
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.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.ErrorPacket;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket;
|
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.ResultSetHeaderPacket;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
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.packets.server.RowDataPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,9 +25,9 @@ public class MysqlQueryExecutor {
|
|||||||
|
|
||||||
private SocketChannel channel;
|
private SocketChannel channel;
|
||||||
|
|
||||||
public MysqlQueryExecutor(MysqlConnector connector){
|
public MysqlQueryExecutor(MysqlConnector connector) throws IOException{
|
||||||
if (!connector.isConnected()) {
|
if (!connector.isConnected()) {
|
||||||
throw new RuntimeException("should execute connector.connect() first");
|
throw new IOException("should execute connector.connect() first");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.channel = connector.getChannel();
|
this.channel = connector.getChannel();
|
||||||
@@ -51,7 +52,7 @@ public class MysqlQueryExecutor {
|
|||||||
QueryCommandPacket cmd = new QueryCommandPacket();
|
QueryCommandPacket cmd = new QueryCommandPacket();
|
||||||
cmd.setQueryString(queryString);
|
cmd.setQueryString(queryString);
|
||||||
byte[] bodyBytes = cmd.toBytes();
|
byte[] bodyBytes = cmd.toBytes();
|
||||||
PacketManager.write(channel, bodyBytes);
|
PacketManager.writeBody(channel, bodyBytes);
|
||||||
byte[] body = readNextPacket();
|
byte[] body = readNextPacket();
|
||||||
|
|
||||||
if (body[0] < 0) {
|
if (body[0] < 0) {
|
||||||
@@ -88,16 +89,70 @@ public class MysqlQueryExecutor {
|
|||||||
for (RowDataPacket r : rowData) {
|
for (RowDataPacket r : rowData) {
|
||||||
resultSet.getFieldValues().addAll(r.getColumns());
|
resultSet.getFieldValues().addAll(r.getColumns());
|
||||||
}
|
}
|
||||||
resultSet.setSourceAddress(channel.socket().getRemoteSocketAddress());
|
resultSet.setSourceAddress(channel.getRemoteSocketAddress());
|
||||||
|
|
||||||
return resultSet;
|
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();
|
byte[] eofBody = readNextPacket();
|
||||||
|
EOFPacket packet = new EOFPacket();
|
||||||
|
packet.fromBytes(eofBody);
|
||||||
if (eofBody[0] != -2) {
|
if (eofBody[0] != -2) {
|
||||||
throw new IOException("EOF Packet is expected, but packet with field_count=" + eofBody[0] + " is found.");
|
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 {
|
protected byte[] readNextPacket() throws IOException {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.alibaba.otter.canal.parse.driver.mysql;
|
package com.alibaba.otter.canal.parse.driver.mysql;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -21,28 +20,29 @@ public class MysqlUpdateExecutor {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MysqlUpdateExecutor.class);
|
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()) {
|
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 {
|
public OKPacket update(String updateString) throws IOException {
|
||||||
QueryCommandPacket cmd = new QueryCommandPacket();
|
QueryCommandPacket cmd = new QueryCommandPacket();
|
||||||
cmd.setQueryString(updateString);
|
cmd.setQueryString(updateString);
|
||||||
byte[] bodyBytes = cmd.toBytes();
|
byte[] bodyBytes = cmd.toBytes();
|
||||||
PacketManager.write(channel, bodyBytes);
|
PacketManager.writeBody(connector.getChannel(), bodyBytes);
|
||||||
|
|
||||||
logger.debug("read update result...");
|
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) {
|
if (body[0] < 0) {
|
||||||
ErrorPacket packet = new ErrorPacket();
|
ErrorPacket packet = new ErrorPacket();
|
||||||
packet.fromBytes(body);
|
packet.fromBytes(body);
|
||||||
|
|||||||
@@ -47,8 +47,9 @@ public class ClientAuthenticationPacket extends PacketWithHeaderPacket {
|
|||||||
/**
|
/**
|
||||||
* CLIENT_LONG_PASSWORD CLIENT_LONG_FLAG CLIENT_PROTOCOL_41
|
* CLIENT_LONG_PASSWORD CLIENT_LONG_FLAG CLIENT_PROTOCOL_41
|
||||||
* CLIENT_INTERACTIVE CLIENT_TRANSACTIONS CLIENT_SECURE_CONNECTION
|
* CLIENT_INTERACTIVE CLIENT_TRANSACTIONS CLIENT_SECURE_CONNECTION
|
||||||
|
* CLIENT_MULTI_STATEMENTS;
|
||||||
*/
|
*/
|
||||||
ByteHelper.writeUnsignedIntLittleEndian(1 | 4 | 512 | 8192 | 32768, out); // remove
|
ByteHelper.writeUnsignedIntLittleEndian(1 | 4 | 512 | 8192 | 32768 | 0x00010000, out); // remove
|
||||||
// client_interactive
|
// client_interactive
|
||||||
// feature
|
// feature
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||||
|
|
||||||
|
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 = socket.getInputStream();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,24 @@
|
|||||||
|
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.setReceiveBufferSize(32 * 1024);
|
||||||
|
socket.setSendBufferSize(32 * 1024);
|
||||||
|
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,227 @@
|
|||||||
|
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.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,111 @@
|
|||||||
|
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.SO_RCVBUF, 32 * 1024)
|
||||||
|
.option(ChannelOption.SO_SNDBUF, 32 * 1024)
|
||||||
|
.option(ChannelOption.TCP_NODELAY, true)
|
||||||
|
// 如果是延时敏感型应用,建议关闭Nagle算法
|
||||||
|
.option(ChannelOption.SO_KEEPALIVE, true)
|
||||||
|
.option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)
|
||||||
|
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
|
||||||
|
//
|
||||||
|
.handler(new ChannelInitializer() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initChannel(Channel 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,23 @@
|
|||||||
|
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 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();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void write8ByteUnsignedIntLittleEndian(long data, ByteArrayOutputStream out) {
|
||||||
|
out.write((byte) (data & 0xFF));
|
||||||
|
out.write((byte) (data >>> 8));
|
||||||
|
out.write((byte) (data >>> 16));
|
||||||
|
out.write((byte) (data >>> 24));
|
||||||
|
out.write((byte) (data >>> 32));
|
||||||
|
out.write((byte) (data >>> 40));
|
||||||
|
out.write((byte) (data >>> 48));
|
||||||
|
out.write((byte) (data >>> 56));
|
||||||
|
}
|
||||||
|
|
||||||
public static void writeUnsignedIntLittleEndian(long data, ByteArrayOutputStream out) {
|
public static void writeUnsignedIntLittleEndian(long data, ByteArrayOutputStream out) {
|
||||||
out.write((byte) (data & 0xFF));
|
out.write((byte) (data & 0xFF));
|
||||||
out.write((byte) (data >>> 8));
|
out.write((byte) (data >>> 8));
|
||||||
|
|||||||
@@ -1,69 +1,44 @@
|
|||||||
package com.alibaba.otter.canal.parse.driver.mysql.utils;
|
package com.alibaba.otter.canal.parse.driver.mysql.utils;
|
||||||
|
|
||||||
import java.io.IOException;
|
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.packets.HeaderPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||||
|
|
||||||
public abstract class PacketManager {
|
public abstract class PacketManager {
|
||||||
|
|
||||||
public static HeaderPacket readHeader(SocketChannel ch, int len) throws IOException {
|
public static HeaderPacket readHeader(SocketChannel ch, int len) throws IOException {
|
||||||
HeaderPacket header = new HeaderPacket();
|
HeaderPacket header = new HeaderPacket();
|
||||||
header.fromBytes(readBytesAsBuffer(ch, len).array());
|
header.fromBytes(ch.read(len));
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ByteBuffer readBytesAsBuffer(SocketChannel ch, int len) throws IOException {
|
public static HeaderPacket readHeader(SocketChannel ch, int len, int timeout) throws IOException {
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(len);
|
HeaderPacket header = new HeaderPacket();
|
||||||
while (buffer.hasRemaining()) {
|
header.fromBytes(ch.read(len, timeout));
|
||||||
int readNum = ch.read(buffer);
|
return header;
|
||||||
if (readNum == -1) {
|
|
||||||
throw new IOException("Unexpected End Stream");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] readBytes(SocketChannel ch, int len) throws IOException {
|
public static byte[] readBytes(SocketChannel ch, int len) throws IOException {
|
||||||
return readBytesAsBuffer(ch, len).array();
|
return ch.read(len);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static byte[] readBytes(SocketChannel ch, int len, int timeout) throws IOException {
|
||||||
* Since We r using blocking IO, so we will just write once and assert the
|
return ch.read(len, timeout);
|
||||||
* 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 void writePkg(SocketChannel ch, byte[]... srcs) throws IOException {
|
||||||
ch.write(srcs);
|
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 {
|
public static void writeBody(SocketChannel ch, byte[] body) throws IOException {
|
||||||
write(ch, body, (byte) 0);
|
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();
|
HeaderPacket header = new HeaderPacket();
|
||||||
header.setPacketBodyLength(body.length);
|
header.setPacketBodyLength(body.length);
|
||||||
header.setPacketSequenceNumber(packetSeqNumber);
|
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 java.net.InetSocketAddress;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.21</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -56,10 +56,10 @@ public class AbstractCanalClientTest {
|
|||||||
context_format += "****************************************************" + SEP;
|
context_format += "****************************************************" + SEP;
|
||||||
|
|
||||||
row_format = SEP
|
row_format = SEP
|
||||||
+ "----------------> binlog[{}:{}] , name[{},{}] , eventType : {} , executeTime : {} , delay : {}ms"
|
+ "----------------> binlog[{}:{}] , name[{},{}] , eventType : {} , executeTime : {}({}) , delay : {} ms"
|
||||||
+ SEP;
|
+ SEP;
|
||||||
|
|
||||||
transaction_format = SEP + "================> binlog[{}:{}] , executeTime : {} , delay : {}ms" + SEP;
|
transaction_format = SEP + "================> binlog[{}:{}] , executeTime : {}({}) , delay : {}ms" + SEP;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +165,8 @@ public class AbstractCanalClientTest {
|
|||||||
for (Entry entry : entrys) {
|
for (Entry entry : entrys) {
|
||||||
long executeTime = entry.getHeader().getExecuteTime();
|
long executeTime = entry.getHeader().getExecuteTime();
|
||||||
long delayTime = new Date().getTime() - executeTime;
|
long delayTime = new Date().getTime() - executeTime;
|
||||||
|
Date date = new Date(entry.getHeader().getExecuteTime());
|
||||||
|
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
|
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
|
||||||
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN) {
|
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN) {
|
||||||
@@ -178,7 +180,8 @@ public class AbstractCanalClientTest {
|
|||||||
logger.info(transaction_format,
|
logger.info(transaction_format,
|
||||||
new Object[] { entry.getHeader().getLogfileName(),
|
new Object[] { entry.getHeader().getLogfileName(),
|
||||||
String.valueOf(entry.getHeader().getLogfileOffset()),
|
String.valueOf(entry.getHeader().getLogfileOffset()),
|
||||||
String.valueOf(entry.getHeader().getExecuteTime()), String.valueOf(delayTime) });
|
String.valueOf(entry.getHeader().getExecuteTime()), simpleDateFormat.format(date),
|
||||||
|
String.valueOf(delayTime) });
|
||||||
logger.info(" BEGIN ----> Thread id: {}", begin.getThreadId());
|
logger.info(" BEGIN ----> Thread id: {}", begin.getThreadId());
|
||||||
} else if (entry.getEntryType() == EntryType.TRANSACTIONEND) {
|
} else if (entry.getEntryType() == EntryType.TRANSACTIONEND) {
|
||||||
TransactionEnd end = null;
|
TransactionEnd end = null;
|
||||||
@@ -193,7 +196,8 @@ public class AbstractCanalClientTest {
|
|||||||
logger.info(transaction_format,
|
logger.info(transaction_format,
|
||||||
new Object[] { entry.getHeader().getLogfileName(),
|
new Object[] { entry.getHeader().getLogfileName(),
|
||||||
String.valueOf(entry.getHeader().getLogfileOffset()),
|
String.valueOf(entry.getHeader().getLogfileOffset()),
|
||||||
String.valueOf(entry.getHeader().getExecuteTime()), String.valueOf(delayTime) });
|
String.valueOf(entry.getHeader().getExecuteTime()), simpleDateFormat.format(date),
|
||||||
|
String.valueOf(delayTime) });
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -213,7 +217,8 @@ public class AbstractCanalClientTest {
|
|||||||
new Object[] { entry.getHeader().getLogfileName(),
|
new Object[] { entry.getHeader().getLogfileName(),
|
||||||
String.valueOf(entry.getHeader().getLogfileOffset()), entry.getHeader().getSchemaName(),
|
String.valueOf(entry.getHeader().getLogfileOffset()), entry.getHeader().getSchemaName(),
|
||||||
entry.getHeader().getTableName(), eventType,
|
entry.getHeader().getTableName(), eventType,
|
||||||
String.valueOf(entry.getHeader().getExecuteTime()), String.valueOf(delayTime) });
|
String.valueOf(entry.getHeader().getExecuteTime()), simpleDateFormat.format(date),
|
||||||
|
String.valueOf(delayTime) });
|
||||||
|
|
||||||
if (eventType == EventType.QUERY || rowChage.getIsDdl()) {
|
if (eventType == EventType.QUERY || rowChage.getIsDdl()) {
|
||||||
logger.info(" sql ----> " + rowChage.getSql() + SEP);
|
logger.info(" sql ----> " + rowChage.getSql() + SEP);
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.alibaba.otter.canal.example;
|
package com.alibaba.otter.canal.example;
|
||||||
|
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
||||||
|
|
||||||
import com.alibaba.otter.canal.client.CanalConnector;
|
import com.alibaba.otter.canal.client.CanalConnector;
|
||||||
import com.alibaba.otter.canal.client.CanalConnectors;
|
import com.alibaba.otter.canal.client.CanalConnectors;
|
||||||
|
|
||||||
@@ -41,7 +39,7 @@ public class ClusterCanalClientTest extends AbstractCanalClientTest {
|
|||||||
logger.info("## stop the canal client");
|
logger.info("## stop the canal client");
|
||||||
clientTest.stop();
|
clientTest.stop();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.warn("##something goes wrong when stopping canal:\n{}", ExceptionUtils.getFullStackTrace(e));
|
logger.warn("##something goes wrong when stopping canal:", e);
|
||||||
} finally {
|
} finally {
|
||||||
logger.info("## canal client is down.");
|
logger.info("## canal client is down.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package com.alibaba.otter.canal.example;
|
|||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
||||||
|
|
||||||
import com.alibaba.otter.canal.client.CanalConnector;
|
import com.alibaba.otter.canal.client.CanalConnector;
|
||||||
import com.alibaba.otter.canal.client.CanalConnectors;
|
import com.alibaba.otter.canal.client.CanalConnectors;
|
||||||
import com.alibaba.otter.canal.common.utils.AddressUtils;
|
import com.alibaba.otter.canal.common.utils.AddressUtils;
|
||||||
@@ -23,8 +21,11 @@ public class SimpleCanalClientTest extends AbstractCanalClientTest {
|
|||||||
public static void main(String args[]) {
|
public static void main(String args[]) {
|
||||||
// 根据ip,直接创建链接,无HA的功能
|
// 根据ip,直接创建链接,无HA的功能
|
||||||
String destination = "example";
|
String destination = "example";
|
||||||
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(AddressUtils.getHostIp(),
|
String ip = AddressUtils.getHostIp();
|
||||||
11111), destination, "", "");
|
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(ip, 11111),
|
||||||
|
destination,
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
final SimpleCanalClientTest clientTest = new SimpleCanalClientTest(destination);
|
final SimpleCanalClientTest clientTest = new SimpleCanalClientTest(destination);
|
||||||
clientTest.setConnector(connector);
|
clientTest.setConnector(connector);
|
||||||
@@ -36,7 +37,7 @@ public class SimpleCanalClientTest extends AbstractCanalClientTest {
|
|||||||
logger.info("## stop the canal client");
|
logger.info("## stop the canal client");
|
||||||
clientTest.stop();
|
clientTest.stop();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.warn("##something goes wrong when stopping canal:\n{}", ExceptionUtils.getFullStackTrace(e));
|
logger.warn("##something goes wrong when stopping canal:", e);
|
||||||
} finally {
|
} finally {
|
||||||
logger.info("## canal client is down.");
|
logger.info("## canal client is down.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.21</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.alibaba.otter.canal.filter;
|
package com.alibaba.otter.canal.filter;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.filter.aviater.AviaterELFilter;
|
import com.alibaba.otter.canal.filter.aviater.AviaterELFilter;
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import java.util.concurrent.ExecutorService;
|
|||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
|
|
||||||
import org.apache.commons.lang.RandomStringUtils;
|
import org.apache.commons.lang.RandomStringUtils;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.21</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.instance.core</artifactId>
|
<artifactId>canal.instance.core</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,244 @@
|
|||||||
|
package com.alibaba.otter.canal.instance.core;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
||||||
|
import com.alibaba.otter.canal.common.alarm.CanalAlarmHandler;
|
||||||
|
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
||||||
|
import com.alibaba.otter.canal.meta.CanalMetaManager;
|
||||||
|
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||||
|
import com.alibaba.otter.canal.parse.ha.CanalHAController;
|
||||||
|
import com.alibaba.otter.canal.parse.ha.HeartBeatHAController;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.group.GroupEventParser;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
|
||||||
|
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
|
import com.alibaba.otter.canal.protocol.ClientIdentity;
|
||||||
|
import com.alibaba.otter.canal.sink.CanalEventSink;
|
||||||
|
import com.alibaba.otter.canal.store.CanalEventStore;
|
||||||
|
import com.alibaba.otter.canal.store.model.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created with Intellig IDEA. Author: yinxiu Date: 2016-01-07 Time: 22:26
|
||||||
|
*/
|
||||||
|
public class AbstractCanalInstance extends AbstractCanalLifeCycle implements CanalInstance {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(AbstractCanalInstance.class);
|
||||||
|
|
||||||
|
protected Long canalId; // 和manager交互唯一标示
|
||||||
|
protected String destination; // 队列名字
|
||||||
|
protected CanalEventStore<Event> eventStore; // 有序队列
|
||||||
|
|
||||||
|
protected CanalEventParser eventParser; // 解析对应的数据信息
|
||||||
|
protected CanalEventSink<List<CanalEntry.Entry>> eventSink; // 链接parse和store的桥接器
|
||||||
|
protected CanalMetaManager metaManager; // 消费信息管理器
|
||||||
|
protected CanalAlarmHandler alarmHandler; // alarm报警机制
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean subscribeChange(ClientIdentity identity) {
|
||||||
|
if (StringUtils.isNotEmpty(identity.getFilter())) {
|
||||||
|
logger.info("subscribe filter change to " + identity.getFilter());
|
||||||
|
AviaterRegexFilter aviaterFilter = new AviaterRegexFilter(identity.getFilter());
|
||||||
|
|
||||||
|
boolean isGroup = (eventParser instanceof GroupEventParser);
|
||||||
|
if (isGroup) {
|
||||||
|
// 处理group的模式
|
||||||
|
List<CanalEventParser> eventParsers = ((GroupEventParser) eventParser).getEventParsers();
|
||||||
|
for (CanalEventParser singleEventParser : eventParsers) {// 需要遍历启动
|
||||||
|
((AbstractEventParser) singleEventParser).setEventFilter(aviaterFilter);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
((AbstractEventParser) eventParser).setEventFilter(aviaterFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter的处理规则
|
||||||
|
// a. parser处理数据过滤处理
|
||||||
|
// b. sink处理数据的路由&分发,一份parse数据经过sink后可以分发为多份,每份的数据可以根据自己的过滤规则不同而有不同的数据
|
||||||
|
// 后续内存版的一对多分发,可以考虑
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
super.start();
|
||||||
|
if (!metaManager.isStart()) {
|
||||||
|
metaManager.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!alarmHandler.isStart()) {
|
||||||
|
alarmHandler.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eventStore.isStart()) {
|
||||||
|
eventStore.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eventSink.isStart()) {
|
||||||
|
eventSink.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eventParser.isStart()) {
|
||||||
|
beforeStartEventParser(eventParser);
|
||||||
|
eventParser.start();
|
||||||
|
afterStartEventParser(eventParser);
|
||||||
|
}
|
||||||
|
logger.info("start successful....");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
super.stop();
|
||||||
|
logger.info("stop CannalInstance for {}-{} ", new Object[] { canalId, destination });
|
||||||
|
|
||||||
|
if (eventParser.isStart()) {
|
||||||
|
beforeStopEventParser(eventParser);
|
||||||
|
eventParser.stop();
|
||||||
|
afterStopEventParser(eventParser);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventSink.isStart()) {
|
||||||
|
eventSink.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventStore.isStart()) {
|
||||||
|
eventStore.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metaManager.isStart()) {
|
||||||
|
metaManager.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alarmHandler.isStart()) {
|
||||||
|
alarmHandler.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("stop successful....");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void beforeStartEventParser(CanalEventParser eventParser) {
|
||||||
|
|
||||||
|
boolean isGroup = (eventParser instanceof GroupEventParser);
|
||||||
|
if (isGroup) {
|
||||||
|
// 处理group的模式
|
||||||
|
List<CanalEventParser> eventParsers = ((GroupEventParser) eventParser).getEventParsers();
|
||||||
|
for (CanalEventParser singleEventParser : eventParsers) {// 需要遍历启动
|
||||||
|
startEventParserInternal(singleEventParser, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
startEventParserInternal(eventParser, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// around event parser, default impl
|
||||||
|
protected void afterStartEventParser(CanalEventParser eventParser) {
|
||||||
|
// 读取一下历史订阅的filter信息
|
||||||
|
List<ClientIdentity> clientIdentitys = metaManager.listAllSubscribeInfo(destination);
|
||||||
|
for (ClientIdentity clientIdentity : clientIdentitys) {
|
||||||
|
subscribeChange(clientIdentity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// around event parser
|
||||||
|
protected void beforeStopEventParser(CanalEventParser eventParser) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void afterStopEventParser(CanalEventParser eventParser) {
|
||||||
|
|
||||||
|
boolean isGroup = (eventParser instanceof GroupEventParser);
|
||||||
|
if (isGroup) {
|
||||||
|
// 处理group的模式
|
||||||
|
List<CanalEventParser> eventParsers = ((GroupEventParser) eventParser).getEventParsers();
|
||||||
|
for (CanalEventParser singleEventParser : eventParsers) {// 需要遍历启动
|
||||||
|
stopEventParserInternal(singleEventParser);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stopEventParserInternal(eventParser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化单个eventParser,不需要考虑group
|
||||||
|
*/
|
||||||
|
protected void startEventParserInternal(CanalEventParser eventParser, boolean isGroup) {
|
||||||
|
if (eventParser instanceof AbstractEventParser) {
|
||||||
|
AbstractEventParser abstractEventParser = (AbstractEventParser) eventParser;
|
||||||
|
// 首先启动log position管理器
|
||||||
|
CanalLogPositionManager logPositionManager = abstractEventParser.getLogPositionManager();
|
||||||
|
if (!logPositionManager.isStart()) {
|
||||||
|
logPositionManager.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventParser instanceof MysqlEventParser) {
|
||||||
|
MysqlEventParser mysqlEventParser = (MysqlEventParser) eventParser;
|
||||||
|
CanalHAController haController = mysqlEventParser.getHaController();
|
||||||
|
|
||||||
|
if (haController instanceof HeartBeatHAController) {
|
||||||
|
((HeartBeatHAController) haController).setCanalHASwitchable(mysqlEventParser);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!haController.isStart()) {
|
||||||
|
haController.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void stopEventParserInternal(CanalEventParser eventParser) {
|
||||||
|
if (eventParser instanceof AbstractEventParser) {
|
||||||
|
AbstractEventParser abstractEventParser = (AbstractEventParser) eventParser;
|
||||||
|
// 首先启动log position管理器
|
||||||
|
CanalLogPositionManager logPositionManager = abstractEventParser.getLogPositionManager();
|
||||||
|
if (logPositionManager.isStart()) {
|
||||||
|
logPositionManager.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventParser instanceof MysqlEventParser) {
|
||||||
|
MysqlEventParser mysqlEventParser = (MysqlEventParser) eventParser;
|
||||||
|
CanalHAController haController = mysqlEventParser.getHaController();
|
||||||
|
if (haController.isStart()) {
|
||||||
|
haController.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================getter==================================
|
||||||
|
@Override
|
||||||
|
public String getDestination() {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CanalEventParser getEventParser() {
|
||||||
|
return eventParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CanalEventSink getEventSink() {
|
||||||
|
return eventSink;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CanalEventStore getEventStore() {
|
||||||
|
return eventStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CanalMetaManager getMetaManager() {
|
||||||
|
return metaManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CanalAlarmHandler getAlarmHandler() {
|
||||||
|
return alarmHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
package com.alibaba.otter.canal.instance.core;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
|
||||||
import com.alibaba.otter.canal.parse.CanalEventParser;
|
|
||||||
import com.alibaba.otter.canal.parse.ha.CanalHAController;
|
|
||||||
import com.alibaba.otter.canal.parse.ha.HeartBeatHAController;
|
|
||||||
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
|
|
||||||
import com.alibaba.otter.canal.parse.inbound.group.GroupEventParser;
|
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
|
|
||||||
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author zebin.xuzb 2012-10-17 下午3:12:34
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
|
||||||
public abstract class CanalInstanceSupport extends AbstractCanalLifeCycle {
|
|
||||||
|
|
||||||
protected void beforeStartEventParser(CanalEventParser eventParser) {
|
|
||||||
|
|
||||||
boolean isGroup = (eventParser instanceof GroupEventParser);
|
|
||||||
if (isGroup) {
|
|
||||||
// 处理group的模式
|
|
||||||
List<CanalEventParser> eventParsers = ((GroupEventParser) eventParser).getEventParsers();
|
|
||||||
for (CanalEventParser singleEventParser : eventParsers) {// 需要遍历启动
|
|
||||||
startEventParserInternal(singleEventParser, true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
startEventParserInternal(eventParser, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// around event parser
|
|
||||||
protected void afterStartEventParser(CanalEventParser eventParser) {
|
|
||||||
// noop
|
|
||||||
}
|
|
||||||
|
|
||||||
// around event parser
|
|
||||||
protected void beforeStopEventParser(CanalEventParser eventParser) {
|
|
||||||
// noop
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void afterStopEventParser(CanalEventParser eventParser) {
|
|
||||||
|
|
||||||
boolean isGroup = (eventParser instanceof GroupEventParser);
|
|
||||||
if (isGroup) {
|
|
||||||
// 处理group的模式
|
|
||||||
List<CanalEventParser> eventParsers = ((GroupEventParser) eventParser).getEventParsers();
|
|
||||||
for (CanalEventParser singleEventParser : eventParsers) {// 需要遍历启动
|
|
||||||
stopEventParserInternal(singleEventParser);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stopEventParserInternal(eventParser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化单个eventParser,不需要考虑group
|
|
||||||
*/
|
|
||||||
protected void startEventParserInternal(CanalEventParser eventParser, boolean isGroup) {
|
|
||||||
if (eventParser instanceof AbstractEventParser) {
|
|
||||||
AbstractEventParser abstractEventParser = (AbstractEventParser) eventParser;
|
|
||||||
// 首先启动log position管理器
|
|
||||||
CanalLogPositionManager logPositionManager = abstractEventParser.getLogPositionManager();
|
|
||||||
if (!logPositionManager.isStart()) {
|
|
||||||
logPositionManager.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventParser instanceof MysqlEventParser) {
|
|
||||||
MysqlEventParser mysqlEventParser = (MysqlEventParser) eventParser;
|
|
||||||
CanalHAController haController = mysqlEventParser.getHaController();
|
|
||||||
|
|
||||||
if (haController instanceof HeartBeatHAController) {
|
|
||||||
((HeartBeatHAController) haController).setCanalHASwitchable(mysqlEventParser);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!haController.isStart()) {
|
|
||||||
haController.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void stopEventParserInternal(CanalEventParser eventParser) {
|
|
||||||
if (eventParser instanceof AbstractEventParser) {
|
|
||||||
AbstractEventParser abstractEventParser = (AbstractEventParser) eventParser;
|
|
||||||
// 首先启动log position管理器
|
|
||||||
CanalLogPositionManager logPositionManager = abstractEventParser.getLogPositionManager();
|
|
||||||
if (logPositionManager.isStart()) {
|
|
||||||
logPositionManager.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventParser instanceof MysqlEventParser) {
|
|
||||||
MysqlEventParser mysqlEventParser = (MysqlEventParser) eventParser;
|
|
||||||
CanalHAController haController = mysqlEventParser.getHaController();
|
|
||||||
if (haController.isStart()) {
|
|
||||||
haController.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.21</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ import com.alibaba.otter.canal.common.alarm.LogAlarmHandler;
|
|||||||
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
||||||
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
||||||
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
||||||
import com.alibaba.otter.canal.instance.core.CanalInstance;
|
import com.alibaba.otter.canal.instance.core.AbstractCanalInstance;
|
||||||
import com.alibaba.otter.canal.instance.core.CanalInstanceSupport;
|
|
||||||
import com.alibaba.otter.canal.instance.manager.model.Canal;
|
import com.alibaba.otter.canal.instance.manager.model.Canal;
|
||||||
import com.alibaba.otter.canal.instance.manager.model.CanalParameter;
|
import com.alibaba.otter.canal.instance.manager.model.CanalParameter;
|
||||||
import com.alibaba.otter.canal.instance.manager.model.CanalParameter.DataSourcing;
|
import com.alibaba.otter.canal.instance.manager.model.CanalParameter.DataSourcing;
|
||||||
@@ -28,7 +27,6 @@ import com.alibaba.otter.canal.instance.manager.model.CanalParameter.MetaMode;
|
|||||||
import com.alibaba.otter.canal.instance.manager.model.CanalParameter.SourcingType;
|
import com.alibaba.otter.canal.instance.manager.model.CanalParameter.SourcingType;
|
||||||
import com.alibaba.otter.canal.instance.manager.model.CanalParameter.StorageMode;
|
import com.alibaba.otter.canal.instance.manager.model.CanalParameter.StorageMode;
|
||||||
import com.alibaba.otter.canal.instance.manager.model.CanalParameter.StorageScavengeMode;
|
import com.alibaba.otter.canal.instance.manager.model.CanalParameter.StorageScavengeMode;
|
||||||
import com.alibaba.otter.canal.meta.CanalMetaManager;
|
|
||||||
import com.alibaba.otter.canal.meta.MemoryMetaManager;
|
import com.alibaba.otter.canal.meta.MemoryMetaManager;
|
||||||
import com.alibaba.otter.canal.meta.PeriodMixedMetaManager;
|
import com.alibaba.otter.canal.meta.PeriodMixedMetaManager;
|
||||||
import com.alibaba.otter.canal.meta.ZooKeeperMetaManager;
|
import com.alibaba.otter.canal.meta.ZooKeeperMetaManager;
|
||||||
@@ -46,17 +44,12 @@ import com.alibaba.otter.canal.parse.index.MetaLogPositionManager;
|
|||||||
import com.alibaba.otter.canal.parse.index.PeriodMixedLogPositionManager;
|
import com.alibaba.otter.canal.parse.index.PeriodMixedLogPositionManager;
|
||||||
import com.alibaba.otter.canal.parse.index.ZooKeeperLogPositionManager;
|
import com.alibaba.otter.canal.parse.index.ZooKeeperLogPositionManager;
|
||||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
|
||||||
import com.alibaba.otter.canal.protocol.ClientIdentity;
|
|
||||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||||
import com.alibaba.otter.canal.sink.CanalEventSink;
|
|
||||||
import com.alibaba.otter.canal.sink.entry.EntryEventSink;
|
import com.alibaba.otter.canal.sink.entry.EntryEventSink;
|
||||||
import com.alibaba.otter.canal.sink.entry.group.GroupEventSink;
|
import com.alibaba.otter.canal.sink.entry.group.GroupEventSink;
|
||||||
import com.alibaba.otter.canal.store.AbstractCanalStoreScavenge;
|
import com.alibaba.otter.canal.store.AbstractCanalStoreScavenge;
|
||||||
import com.alibaba.otter.canal.store.CanalEventStore;
|
|
||||||
import com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer;
|
import com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer;
|
||||||
import com.alibaba.otter.canal.store.model.BatchMode;
|
import com.alibaba.otter.canal.store.model.BatchMode;
|
||||||
import com.alibaba.otter.canal.store.model.Event;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单个canal实例,比如一个destination会独立一个实例
|
* 单个canal实例,比如一个destination会独立一个实例
|
||||||
@@ -64,24 +57,11 @@ import com.alibaba.otter.canal.store.model.Event;
|
|||||||
* @author jianghang 2012-7-11 下午09:26:51
|
* @author jianghang 2012-7-11 下午09:26:51
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
public class CanalInstanceWithManager extends CanalInstanceSupport implements CanalInstance {
|
public class CanalInstanceWithManager extends AbstractCanalInstance {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CanalInstanceWithManager.class);
|
private static final Logger logger = LoggerFactory.getLogger(CanalInstanceWithManager.class);
|
||||||
protected Long canalId; // 和manager交互唯一标示
|
|
||||||
protected String destination; // 队列名字
|
|
||||||
protected String filter; // 过滤表达式
|
protected String filter; // 过滤表达式
|
||||||
protected CanalParameter parameters; // 对应参数
|
protected CanalParameter parameters; // 对应参数
|
||||||
protected CanalMetaManager metaManager; // 消费信息管理器
|
|
||||||
protected CanalEventStore<Event> eventStore; // 有序队列
|
|
||||||
|
|
||||||
protected CanalEventParser eventParser; // 解析对应的数据信息
|
|
||||||
protected CanalEventSink<List<Entry>> eventSink; // 链接parse和store的桥接器
|
|
||||||
protected CanalAlarmHandler alarmHandler; // alarm报警机制
|
|
||||||
protected ZkClientx zkClientx;
|
|
||||||
|
|
||||||
public CanalInstanceWithManager(Canal canal){
|
|
||||||
this(canal, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CanalInstanceWithManager(Canal canal, String filter){
|
public CanalInstanceWithManager(Canal canal, String filter){
|
||||||
this.parameters = canal.getCanalParameter();
|
this.parameters = canal.getCanalParameter();
|
||||||
@@ -89,7 +69,7 @@ public class CanalInstanceWithManager extends CanalInstanceSupport implements Ca
|
|||||||
this.destination = canal.getName();
|
this.destination = canal.getName();
|
||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
|
|
||||||
logger.info("init CannalInstance for {}-{} with parameters:{}", canalId, destination, parameters);
|
logger.info("init CanalInstance for {}-{} with parameters:{}", canalId, destination, parameters);
|
||||||
// 初始化报警机制
|
// 初始化报警机制
|
||||||
initAlarmHandler();
|
initAlarmHandler();
|
||||||
// 初始化metaManager
|
// 初始化metaManager
|
||||||
@@ -101,7 +81,7 @@ public class CanalInstanceWithManager extends CanalInstanceSupport implements Ca
|
|||||||
// 初始化eventParser;
|
// 初始化eventParser;
|
||||||
initEventParser();
|
initEventParser();
|
||||||
|
|
||||||
// 基础工具,需要提前start,会有先订阅再根据filter条件启动paser的需求
|
// 基础工具,需要提前start,会有先订阅再根据filter条件启动parse的需求
|
||||||
if (!alarmHandler.isStart()) {
|
if (!alarmHandler.isStart()) {
|
||||||
alarmHandler.start();
|
alarmHandler.start();
|
||||||
}
|
}
|
||||||
@@ -113,98 +93,9 @@ public class CanalInstanceWithManager extends CanalInstanceSupport implements Ca
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
super.start();
|
|
||||||
// 初始化metaManager
|
// 初始化metaManager
|
||||||
logger.info("start CannalInstance for {}-{} with parameters:{}", canalId, destination, parameters);
|
logger.info("start CannalInstance for {}-{} with parameters:{}", canalId, destination, parameters);
|
||||||
|
super.start();
|
||||||
if (!metaManager.isStart()) {
|
|
||||||
metaManager.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!alarmHandler.isStart()) {
|
|
||||||
alarmHandler.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!eventStore.isStart()) {
|
|
||||||
eventStore.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!eventSink.isStart()) {
|
|
||||||
eventSink.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!eventParser.isStart()) {
|
|
||||||
beforeStartEventParser(eventParser);
|
|
||||||
eventParser.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("start successful....");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
logger.info("stop CannalInstance for {}-{} ", new Object[] { canalId, destination });
|
|
||||||
|
|
||||||
if (eventParser.isStart()) {
|
|
||||||
eventParser.stop();
|
|
||||||
afterStopEventParser(eventParser);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventSink.isStart()) {
|
|
||||||
eventSink.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventStore.isStart()) {
|
|
||||||
eventStore.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metaManager.isStart()) {
|
|
||||||
metaManager.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alarmHandler.isStart()) {
|
|
||||||
alarmHandler.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (zkClientx != null) {
|
|
||||||
// zkClientx.close();
|
|
||||||
// }
|
|
||||||
|
|
||||||
super.stop();
|
|
||||||
logger.info("stop successful....");
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean subscribeChange(ClientIdentity identity) {
|
|
||||||
if (StringUtils.isNotEmpty(identity.getFilter())) {
|
|
||||||
AviaterRegexFilter aviaterFilter = new AviaterRegexFilter(identity.getFilter());
|
|
||||||
|
|
||||||
boolean isGroup = (eventParser instanceof GroupEventParser);
|
|
||||||
if (isGroup) {
|
|
||||||
// 处理group的模式
|
|
||||||
List<CanalEventParser> eventParsers = ((GroupEventParser) eventParser).getEventParsers();
|
|
||||||
for (CanalEventParser singleEventParser : eventParsers) {// 需要遍历启动
|
|
||||||
((AbstractEventParser) singleEventParser).setEventFilter(aviaterFilter);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
((AbstractEventParser) eventParser).setEventFilter(aviaterFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter的处理规则
|
|
||||||
// a. parser处理数据过滤处理
|
|
||||||
// b. sink处理数据的路由&分发,一份parse数据经过sink后可以分发为多份,每份的数据可以根据自己的过滤规则不同而有不同的数据
|
|
||||||
// 后续内存版的一对多分发,可以考虑
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void afterStartEventParser(CanalEventParser eventParser) {
|
|
||||||
super.afterStartEventParser(eventParser);
|
|
||||||
|
|
||||||
// 读取一下历史订阅的filter信息
|
|
||||||
List<ClientIdentity> clientIdentitys = metaManager.listAllSubscribeInfo(destination);
|
|
||||||
for (ClientIdentity clientIdentity : clientIdentitys) {
|
|
||||||
subscribeChange(clientIdentity);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void initAlarmHandler() {
|
protected void initAlarmHandler() {
|
||||||
@@ -455,29 +346,24 @@ public class CanalInstanceWithManager extends CanalInstanceSupport implements Ca
|
|||||||
protected CanalLogPositionManager initLogPositionManager() {
|
protected CanalLogPositionManager initLogPositionManager() {
|
||||||
logger.info("init logPositionPersistManager begin...");
|
logger.info("init logPositionPersistManager begin...");
|
||||||
IndexMode indexMode = parameters.getIndexMode();
|
IndexMode indexMode = parameters.getIndexMode();
|
||||||
CanalLogPositionManager logPositionManager = null;
|
CanalLogPositionManager logPositionManager;
|
||||||
if (indexMode.isMemory()) {
|
if (indexMode.isMemory()) {
|
||||||
logPositionManager = new MemoryLogPositionManager();
|
logPositionManager = new MemoryLogPositionManager();
|
||||||
} else if (indexMode.isZookeeper()) {
|
} else if (indexMode.isZookeeper()) {
|
||||||
logPositionManager = new ZooKeeperLogPositionManager();
|
logPositionManager = new ZooKeeperLogPositionManager(getZkclientx());
|
||||||
((ZooKeeperLogPositionManager) logPositionManager).setZkClientx(getZkclientx());
|
|
||||||
} else if (indexMode.isMixed()) {
|
} else if (indexMode.isMixed()) {
|
||||||
logPositionManager = new PeriodMixedLogPositionManager();
|
MemoryLogPositionManager memoryLogPositionManager = new MemoryLogPositionManager();
|
||||||
|
ZooKeeperLogPositionManager zooKeeperLogPositionManager = new ZooKeeperLogPositionManager(getZkclientx());
|
||||||
ZooKeeperLogPositionManager zooKeeperLogPositionManager = new ZooKeeperLogPositionManager();
|
logPositionManager = new PeriodMixedLogPositionManager(memoryLogPositionManager,
|
||||||
zooKeeperLogPositionManager.setZkClientx(getZkclientx());
|
zooKeeperLogPositionManager,
|
||||||
((PeriodMixedLogPositionManager) logPositionManager).setZooKeeperLogPositionManager(zooKeeperLogPositionManager);
|
1000L);
|
||||||
} else if (indexMode.isMeta()) {
|
} else if (indexMode.isMeta()) {
|
||||||
logPositionManager = new MetaLogPositionManager();
|
logPositionManager = new MetaLogPositionManager(metaManager);
|
||||||
((MetaLogPositionManager) logPositionManager).setMetaManager(metaManager);
|
|
||||||
} else if (indexMode.isMemoryMetaFailback()) {
|
} else if (indexMode.isMemoryMetaFailback()) {
|
||||||
MemoryLogPositionManager primaryLogPositionManager = new MemoryLogPositionManager();
|
MemoryLogPositionManager primary = new MemoryLogPositionManager();
|
||||||
MetaLogPositionManager failbackLogPositionManager = new MetaLogPositionManager();
|
MetaLogPositionManager secondary = new MetaLogPositionManager(metaManager);
|
||||||
failbackLogPositionManager.setMetaManager(metaManager);
|
|
||||||
|
|
||||||
logPositionManager = new FailbackLogPositionManager();
|
logPositionManager = new FailbackLogPositionManager(primary, secondary);
|
||||||
((FailbackLogPositionManager) logPositionManager).setPrimary(primaryLogPositionManager);
|
|
||||||
((FailbackLogPositionManager) logPositionManager).setFailback(failbackLogPositionManager);
|
|
||||||
} else {
|
} else {
|
||||||
throw new CanalException("unsupport indexMode for " + indexMode);
|
throw new CanalException("unsupport indexMode for " + indexMode);
|
||||||
}
|
}
|
||||||
@@ -515,32 +401,6 @@ public class CanalInstanceWithManager extends CanalInstanceSupport implements Ca
|
|||||||
return ZkClientx.getZkClient(StringUtils.join(zkClusters, ";"));
|
return ZkClientx.getZkClient(StringUtils.join(zkClusters, ";"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================
|
|
||||||
|
|
||||||
public String getDestination() {
|
|
||||||
return destination;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CanalMetaManager getMetaManager() {
|
|
||||||
return metaManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CanalEventStore<Event> getEventStore() {
|
|
||||||
return eventStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CanalEventParser getEventParser() {
|
|
||||||
return eventParser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CanalEventSink<List<Entry>> getEventSink() {
|
|
||||||
return eventSink;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CanalAlarmHandler getAlarmHandler() {
|
|
||||||
return alarmHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAlarmHandler(CanalAlarmHandler alarmHandler) {
|
public void setAlarmHandler(CanalAlarmHandler alarmHandler) {
|
||||||
this.alarmHandler = alarmHandler;
|
this.alarmHandler = alarmHandler;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.21</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.21</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -2,20 +2,14 @@ package com.alibaba.otter.canal.instance.spring;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.common.alarm.CanalAlarmHandler;
|
import com.alibaba.otter.canal.common.alarm.CanalAlarmHandler;
|
||||||
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
import com.alibaba.otter.canal.instance.core.AbstractCanalInstance;
|
||||||
import com.alibaba.otter.canal.instance.core.CanalInstance;
|
|
||||||
import com.alibaba.otter.canal.instance.core.CanalInstanceSupport;
|
|
||||||
import com.alibaba.otter.canal.meta.CanalMetaManager;
|
import com.alibaba.otter.canal.meta.CanalMetaManager;
|
||||||
import com.alibaba.otter.canal.parse.CanalEventParser;
|
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||||
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
|
|
||||||
import com.alibaba.otter.canal.parse.inbound.group.GroupEventParser;
|
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry;
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
import com.alibaba.otter.canal.protocol.ClientIdentity;
|
|
||||||
import com.alibaba.otter.canal.sink.CanalEventSink;
|
import com.alibaba.otter.canal.sink.CanalEventSink;
|
||||||
import com.alibaba.otter.canal.store.CanalEventStore;
|
import com.alibaba.otter.canal.store.CanalEventStore;
|
||||||
import com.alibaba.otter.canal.store.model.Event;
|
import com.alibaba.otter.canal.store.model.Event;
|
||||||
@@ -27,130 +21,13 @@ import com.alibaba.otter.canal.store.model.Event;
|
|||||||
* @author zebin.xuzb
|
* @author zebin.xuzb
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
public class CanalInstanceWithSpring extends CanalInstanceSupport implements CanalInstance {
|
public class CanalInstanceWithSpring extends AbstractCanalInstance {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CanalInstanceWithSpring.class);
|
private static final Logger logger = LoggerFactory.getLogger(CanalInstanceWithSpring.class);
|
||||||
private String destination;
|
|
||||||
private CanalEventParser eventParser;
|
|
||||||
private CanalEventSink<List<CanalEntry.Entry>> eventSink;
|
|
||||||
private CanalEventStore<Event> eventStore;
|
|
||||||
private CanalMetaManager metaManager;
|
|
||||||
private CanalAlarmHandler alarmHandler;
|
|
||||||
|
|
||||||
public String getDestination() {
|
|
||||||
return this.destination;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CanalEventParser getEventParser() {
|
|
||||||
return this.eventParser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CanalEventSink<List<CanalEntry.Entry>> getEventSink() {
|
|
||||||
return this.eventSink;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CanalEventStore<Event> getEventStore() {
|
|
||||||
return this.eventStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CanalMetaManager getMetaManager() {
|
|
||||||
return this.metaManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CanalAlarmHandler getAlarmHandler() {
|
|
||||||
return alarmHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean subscribeChange(ClientIdentity identity) {
|
|
||||||
if (StringUtils.isNotEmpty(identity.getFilter())) {
|
|
||||||
logger.info("subscribe filter change to " + identity.getFilter());
|
|
||||||
AviaterRegexFilter aviaterFilter = new AviaterRegexFilter(identity.getFilter());
|
|
||||||
|
|
||||||
boolean isGroup = (eventParser instanceof GroupEventParser);
|
|
||||||
if (isGroup) {
|
|
||||||
// 处理group的模式
|
|
||||||
List<CanalEventParser> eventParsers = ((GroupEventParser) eventParser).getEventParsers();
|
|
||||||
for (CanalEventParser singleEventParser : eventParsers) {// 需要遍历启动
|
|
||||||
((AbstractEventParser) singleEventParser).setEventFilter(aviaterFilter);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
((AbstractEventParser) eventParser).setEventFilter(aviaterFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter的处理规则
|
|
||||||
// a. parser处理数据过滤处理
|
|
||||||
// b. sink处理数据的路由&分发,一份parse数据经过sink后可以分发为多份,每份的数据可以根据自己的过滤规则不同而有不同的数据
|
|
||||||
// 后续内存版的一对多分发,可以考虑
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void afterStartEventParser(CanalEventParser eventParser) {
|
|
||||||
super.afterStartEventParser(eventParser);
|
|
||||||
|
|
||||||
// 读取一下历史订阅的filter信息
|
|
||||||
List<ClientIdentity> clientIdentitys = metaManager.listAllSubscribeInfo(destination);
|
|
||||||
for (ClientIdentity clientIdentity : clientIdentitys) {
|
|
||||||
subscribeChange(clientIdentity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
super.start();
|
|
||||||
|
|
||||||
logger.info("start CannalInstance for {}-{} ", new Object[] { 1, destination });
|
logger.info("start CannalInstance for {}-{} ", new Object[] { 1, destination });
|
||||||
if (!metaManager.isStart()) {
|
super.start();
|
||||||
metaManager.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!eventStore.isStart()) {
|
|
||||||
eventStore.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!eventSink.isStart()) {
|
|
||||||
eventSink.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!eventParser.isStart()) {
|
|
||||||
beforeStartEventParser(eventParser);
|
|
||||||
eventParser.start();
|
|
||||||
afterStartEventParser(eventParser);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("start successful....");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
logger.info("stop CannalInstance for {}-{} ", new Object[] { 1, destination });
|
|
||||||
if (eventParser.isStart()) {
|
|
||||||
beforeStopEventParser(eventParser);
|
|
||||||
eventParser.stop();
|
|
||||||
afterStopEventParser(eventParser);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventSink.isStart()) {
|
|
||||||
eventSink.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventStore.isStart()) {
|
|
||||||
eventStore.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metaManager.isStart()) {
|
|
||||||
metaManager.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alarmHandler.isStart()) {
|
|
||||||
alarmHandler.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (zkClientx != null) {
|
|
||||||
// zkClientx.close();
|
|
||||||
// }
|
|
||||||
|
|
||||||
super.stop();
|
|
||||||
logger.info("stop successful....");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======== setter ========
|
// ======== setter ========
|
||||||
|
|||||||
@@ -120,14 +120,14 @@
|
|||||||
<!-- 解析位点记录 -->
|
<!-- 解析位点记录 -->
|
||||||
<property name="logPositionManager">
|
<property name="logPositionManager">
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||||
<property name="primary">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||||
</property>
|
</constructor-arg>
|
||||||
<property name="failback">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||||
<property name="metaManager" ref="metaManager" />
|
<constructor-arg ref="metaManager"/>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</constructor-arg>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
|
|||||||
@@ -106,14 +106,16 @@
|
|||||||
<!-- 解析位点记录 -->
|
<!-- 解析位点记录 -->
|
||||||
<property name="logPositionManager">
|
<property name="logPositionManager">
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||||
<property name="primary">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||||
</property>
|
</constructor-arg>
|
||||||
<property name="failback">
|
<constructor-arg>
|
||||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||||
<property name="metaManager" ref="metaManager" />
|
<constructor-arg>
|
||||||
|
<ref bean="metaManager"/>
|
||||||
|
</constructor-arg>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</constructor-arg>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.21</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ public class FileMixedMetaManager extends MemoryMetaManager implements CanalMeta
|
|||||||
this.dataDir = new File(dataDir);
|
this.dataDir = new File(dataDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDataDir(File dataDir) {
|
public void setDataDirByFile(File dataDir) {
|
||||||
this.dataDir = dataDir;
|
this.dataDir = dataDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,8 +142,8 @@ public class PeriodMixedMetaManager extends MemoryMetaManager implements CanalMe
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void updateCursor(ClientIdentity clientIdentity, Position position) throws CanalMetaManagerException {
|
public void updateCursor(ClientIdentity clientIdentity, Position position) throws CanalMetaManagerException {
|
||||||
updateCursorTasks.add(clientIdentity);// 添加到任务队列中进行触发
|
|
||||||
super.updateCursor(clientIdentity, position);
|
super.updateCursor(clientIdentity, position);
|
||||||
|
updateCursorTasks.add(clientIdentity);// 添加到任务队列中进行触发
|
||||||
}
|
}
|
||||||
|
|
||||||
public Position getCursor(ClientIdentity clientIdentity) throws CanalMetaManagerException {
|
public Position getCursor(ClientIdentity clientIdentity) throws CanalMetaManagerException {
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import java.io.IOException;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -32,7 +31,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testSubscribeAll() {
|
public void testSubscribeAll() {
|
||||||
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
||||||
metaManager.setDataDir(dataDir);
|
metaManager.setDataDirByFile(dataDir);
|
||||||
metaManager.setPeriod(100);
|
metaManager.setPeriod(100);
|
||||||
|
|
||||||
metaManager.start();
|
metaManager.start();
|
||||||
@@ -41,7 +40,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
|||||||
sleep(2000L);
|
sleep(2000L);
|
||||||
// 重新构建一次,能获得上一次zk上的记录
|
// 重新构建一次,能获得上一次zk上的记录
|
||||||
FileMixedMetaManager metaManager2 = new FileMixedMetaManager();
|
FileMixedMetaManager metaManager2 = new FileMixedMetaManager();
|
||||||
metaManager2.setDataDir(dataDir);
|
metaManager2.setDataDirByFile(dataDir);
|
||||||
metaManager2.setPeriod(100);
|
metaManager2.setPeriod(100);
|
||||||
metaManager2.start();
|
metaManager2.start();
|
||||||
|
|
||||||
@@ -53,7 +52,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testBatchAll() {
|
public void testBatchAll() {
|
||||||
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
||||||
metaManager.setDataDir(dataDir);
|
metaManager.setDataDirByFile(dataDir);
|
||||||
metaManager.setPeriod(100);
|
metaManager.setPeriod(100);
|
||||||
|
|
||||||
metaManager.start();
|
metaManager.start();
|
||||||
@@ -68,7 +67,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testCursorAll() {
|
public void testCursorAll() {
|
||||||
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
||||||
metaManager.setDataDir(dataDir);
|
metaManager.setDataDirByFile(dataDir);
|
||||||
metaManager.setPeriod(100);
|
metaManager.setPeriod(100);
|
||||||
metaManager.start();
|
metaManager.start();
|
||||||
|
|
||||||
@@ -77,7 +76,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
|||||||
sleep(1000L);
|
sleep(1000L);
|
||||||
// 重新构建一次,能获得上一次zk上的记录
|
// 重新构建一次,能获得上一次zk上的记录
|
||||||
FileMixedMetaManager metaManager2 = new FileMixedMetaManager();
|
FileMixedMetaManager metaManager2 = new FileMixedMetaManager();
|
||||||
metaManager2.setDataDir(dataDir);
|
metaManager2.setDataDirByFile(dataDir);
|
||||||
metaManager2.setPeriod(100);
|
metaManager2.setPeriod(100);
|
||||||
metaManager2.start();
|
metaManager2.start();
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.alibaba.otter.canal.meta;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.protocol.position.PositionRange;
|
import com.alibaba.otter.canal.protocol.position.PositionRange;
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ package com.alibaba.otter.canal.meta;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ package com.alibaba.otter.canal.meta;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ package com.alibaba.otter.canal.meta;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.otter</groupId>
|
<groupId>com.alibaba.otter</groupId>
|
||||||
<artifactId>canal</artifactId>
|
<artifactId>canal</artifactId>
|
||||||
<version>1.0.21</version>
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>canal.parse</artifactId>
|
<artifactId>canal.parse</artifactId>
|
||||||
@@ -45,11 +45,45 @@
|
|||||||
<artifactId>canal.parse.driver</artifactId>
|
<artifactId>canal.parse.driver</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>druid</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.fastsql</groupId>
|
||||||
|
<artifactId>fastsql</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.ibatis</groupId>
|
||||||
|
<artifactId>ibatis-sqlmap</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-compress</artifactId>
|
||||||
|
</dependency>
|
||||||
<!-- test dependency -->
|
<!-- test dependency -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import com.alibaba.otter.canal.parse.CanalEventParser;
|
|||||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
|
import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
|
||||||
import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer.TransactionFlushCallback;
|
import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer.TransactionFlushCallback;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
|
|
||||||
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
|
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
|
||||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry;
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
@@ -97,6 +96,10 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
protected void preDump(ErosaConnection connection) {
|
protected void preDump(ErosaConnection connection) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean processTableMeta(EntryPosition position) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected void afterDump(ErosaConnection connection) {
|
protected void afterDump(ErosaConnection connection) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +123,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
throw new CanalParseException("consume failed!");
|
throw new CanalParseException("consume failed!");
|
||||||
}
|
}
|
||||||
|
|
||||||
LogPosition position = buildLastTranasctionPosition(transaction);
|
LogPosition position = buildLastTransactionPosition(transaction);
|
||||||
if (position != null) { // 可能position为空
|
if (position != null) { // 可能position为空
|
||||||
logPositionManager.persistLogPosition(AbstractEventParser.this.destination, position);
|
logPositionManager.persistLogPosition(AbstractEventParser.this.destination, position);
|
||||||
}
|
}
|
||||||
@@ -146,7 +149,6 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
ErosaConnection erosaConnection = null;
|
ErosaConnection erosaConnection = null;
|
||||||
while (running) {
|
while (running) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// 开始执行replication
|
// 开始执行replication
|
||||||
// 1. 构造Erosa连接
|
// 1. 构造Erosa连接
|
||||||
erosaConnection = buildErosaConnection();
|
erosaConnection = buildErosaConnection();
|
||||||
@@ -159,11 +161,17 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
|
|
||||||
erosaConnection.connect();// 链接
|
erosaConnection.connect();// 链接
|
||||||
// 4. 获取最后的位置信息
|
// 4. 获取最后的位置信息
|
||||||
final EntryPosition startPosition = findStartPosition(erosaConnection);
|
EntryPosition position = findStartPosition(erosaConnection);
|
||||||
|
final EntryPosition startPosition = position;
|
||||||
if (startPosition == null) {
|
if (startPosition == null) {
|
||||||
throw new CanalParseException("can't find start position for " + destination);
|
throw new CanalParseException("can't find start position for " + destination);
|
||||||
}
|
}
|
||||||
logger.info("find start position : {}", startPosition.toString());
|
|
||||||
|
if (!processTableMeta(startPosition)) {
|
||||||
|
throw new CanalParseException("can't find init table meta for " + destination
|
||||||
|
+ " with position : " + startPosition);
|
||||||
|
}
|
||||||
|
logger.warn("find start position : {}", startPosition.toString());
|
||||||
// 重新链接,因为在找position过程中可能有状态,需要断开后重建
|
// 重新链接,因为在找position过程中可能有状态,需要断开后重建
|
||||||
erosaConnection.reconnect();
|
erosaConnection.reconnect();
|
||||||
|
|
||||||
@@ -173,7 +181,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
|
|
||||||
public boolean sink(EVENT event) {
|
public boolean sink(EVENT event) {
|
||||||
try {
|
try {
|
||||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event);
|
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, false);
|
||||||
|
|
||||||
if (!running) {
|
if (!running) {
|
||||||
return false;
|
return false;
|
||||||
@@ -190,9 +198,12 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
return running;
|
return running;
|
||||||
} catch (TableIdNotFoundException e) {
|
} catch (TableIdNotFoundException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Throwable e) {
|
||||||
|
if (e.getCause() instanceof TableIdNotFoundException) {
|
||||||
|
throw (TableIdNotFoundException) e.getCause();
|
||||||
|
}
|
||||||
// 记录一下,出错的位点信息
|
// 记录一下,出错的位点信息
|
||||||
processError(e,
|
processSinkError(e,
|
||||||
this.lastPosition,
|
this.lastPosition,
|
||||||
startPosition.getJournalName(),
|
startPosition.getJournalName(),
|
||||||
startPosition.getPosition());
|
startPosition.getPosition());
|
||||||
@@ -219,6 +230,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
logger.error(String.format("dump address %s has an error, retrying. caused by ",
|
logger.error(String.format("dump address %s has an error, retrying. caused by ",
|
||||||
runningInfo.getAddress().toString()), e);
|
runningInfo.getAddress().toString()), e);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
processDumpError(e);
|
||||||
exception = e;
|
exception = e;
|
||||||
if (!running) {
|
if (!running) {
|
||||||
if (!(e instanceof java.nio.channels.ClosedByInterruptException || e.getCause() instanceof java.nio.channels.ClosedByInterruptException)) {
|
if (!(e instanceof java.nio.channels.ClosedByInterruptException || e.getCause() instanceof java.nio.channels.ClosedByInterruptException)) {
|
||||||
@@ -231,6 +243,8 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
sendAlarm(destination, ExceptionUtils.getFullStackTrace(e));
|
sendAlarm(destination, ExceptionUtils.getFullStackTrace(e));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
// 重新置为中断状态
|
||||||
|
Thread.interrupted();
|
||||||
// 关闭一下链接
|
// 关闭一下链接
|
||||||
afterDump(erosaConnection);
|
afterDump(erosaConnection);
|
||||||
try {
|
try {
|
||||||
@@ -269,7 +283,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
parseThread.setUncaughtExceptionHandler(handler);
|
parseThread.setUncaughtExceptionHandler(handler);
|
||||||
parseThread.setName(String.format("destination = %s , address = %s , EventParser",
|
parseThread.setName(String.format("destination = %s , address = %s , EventParser",
|
||||||
destination,
|
destination,
|
||||||
runningInfo == null ? null : runningInfo.getAddress().toString()));
|
runningInfo == null ? null : runningInfo.getAddress()));
|
||||||
parseThread.start();
|
parseThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,14 +328,13 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CanalEntry.Entry parseAndProfilingIfNecessary(EVENT bod) throws Exception {
|
protected CanalEntry.Entry parseAndProfilingIfNecessary(EVENT bod, boolean isSeek) throws Exception {
|
||||||
long startTs = -1;
|
long startTs = -1;
|
||||||
boolean enabled = getProfilingEnabled();
|
boolean enabled = getProfilingEnabled();
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
startTs = System.currentTimeMillis();
|
startTs = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
CanalEntry.Entry event = binlogParser.parse(bod);
|
CanalEntry.Entry event = binlogParser.parse(bod, isSeek);
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
this.parsingInterval = System.currentTimeMillis() - startTs;
|
this.parsingInterval = System.currentTimeMillis() - startTs;
|
||||||
}
|
}
|
||||||
@@ -336,20 +349,11 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
return profilingEnabled.get();
|
return profilingEnabled.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected LogPosition buildLastTranasctionPosition(List<CanalEntry.Entry> entries) { // 初始化一下
|
protected LogPosition buildLastTransactionPosition(List<CanalEntry.Entry> entries) { // 初始化一下
|
||||||
for (int i = entries.size() - 1; i > 0; i--) {
|
for (int i = entries.size() - 1; i > 0; i--) {
|
||||||
CanalEntry.Entry entry = entries.get(i);
|
CanalEntry.Entry entry = entries.get(i);
|
||||||
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {// 尽量记录一个事务做为position
|
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {// 尽量记录一个事务做为position
|
||||||
LogPosition logPosition = new LogPosition();
|
return buildLastPosition(entry);
|
||||||
EntryPosition position = new EntryPosition();
|
|
||||||
position.setJournalName(entry.getHeader().getLogfileName());
|
|
||||||
position.setPosition(entry.getHeader().getLogfileOffset());
|
|
||||||
position.setTimestamp(entry.getHeader().getExecuteTime());
|
|
||||||
logPosition.setPostion(position);
|
|
||||||
|
|
||||||
LogIdentity identity = new LogIdentity(runningInfo.getAddress(), -1L);
|
|
||||||
logPosition.setIdentity(identity);
|
|
||||||
return logPosition;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,6 +366,8 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
position.setJournalName(entry.getHeader().getLogfileName());
|
position.setJournalName(entry.getHeader().getLogfileName());
|
||||||
position.setPosition(entry.getHeader().getLogfileOffset());
|
position.setPosition(entry.getHeader().getLogfileOffset());
|
||||||
position.setTimestamp(entry.getHeader().getExecuteTime());
|
position.setTimestamp(entry.getHeader().getExecuteTime());
|
||||||
|
// add serverId at 2016-06-28
|
||||||
|
position.setServerId(entry.getHeader().getServerId());
|
||||||
logPosition.setPostion(position);
|
logPosition.setPostion(position);
|
||||||
|
|
||||||
LogIdentity identity = new LogIdentity(runningInfo.getAddress(), -1L);
|
LogIdentity identity = new LogIdentity(runningInfo.getAddress(), -1L);
|
||||||
@@ -369,7 +375,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
return logPosition;
|
return logPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void processError(Exception e, LogPosition lastPosition, String startBinlogFile, long startPosition) {
|
protected void processSinkError(Throwable e, LogPosition lastPosition, String startBinlogFile, long startPosition) {
|
||||||
if (lastPosition != null) {
|
if (lastPosition != null) {
|
||||||
logger.warn(String.format("ERROR ## parse this event has an error , last position : [%s]",
|
logger.warn(String.format("ERROR ## parse this event has an error , last position : [%s]",
|
||||||
lastPosition.getPostion()),
|
lastPosition.getPostion()),
|
||||||
@@ -381,13 +387,21 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void processDumpError(Throwable e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
protected void startHeartBeat(ErosaConnection connection) {
|
protected void startHeartBeat(ErosaConnection connection) {
|
||||||
lastEntryTime = 0L; // 初始化
|
lastEntryTime = 0L; // 初始化
|
||||||
if (timer == null) {// lazy初始化一下
|
if (timer == null) {// lazy初始化一下
|
||||||
String name = String.format("destination = %s , address = %s , HeartBeatTimeTask",
|
String name = String.format("destination = %s , address = %s , HeartBeatTimeTask",
|
||||||
destination,
|
destination,
|
||||||
runningInfo == null ? null : runningInfo.getAddress().toString());
|
runningInfo == null ? null : runningInfo.getAddress().toString());
|
||||||
synchronized (MysqlEventParser.class) {
|
synchronized (AbstractEventParser.class) {
|
||||||
|
// synchronized (MysqlEventParser.class) {
|
||||||
|
// why use MysqlEventParser.class, u know, MysqlEventParser is
|
||||||
|
// the child class 4 AbstractEventParser,
|
||||||
|
// do this is ...
|
||||||
if (timer == null) {
|
if (timer == null) {
|
||||||
timer = new Timer(name, true);
|
timer = new Timer(name, true);
|
||||||
}
|
}
|
||||||
@@ -424,7 +438,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.warn("heartBeat run failed " + ExceptionUtils.getStackTrace(e));
|
logger.warn("heartBeat run failed ", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import com.alibaba.otter.canal.protocol.CanalEntry;
|
|||||||
*/
|
*/
|
||||||
public interface BinlogParser<T> extends CanalLifeCycle {
|
public interface BinlogParser<T> extends CanalLifeCycle {
|
||||||
|
|
||||||
CanalEntry.Entry parse(T event) throws CanalParseException;
|
CanalEntry.Entry parse(T event, boolean isSeek) throws CanalParseException;
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ public interface ErosaConnection {
|
|||||||
|
|
||||||
public void disconnect() throws IOException;
|
public void disconnect() throws IOException;
|
||||||
|
|
||||||
public boolean isConnected();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用于快速数据查找,和dump的区别在于,seek会只给出部分的数据
|
* 用于快速数据查找,和dump的区别在于,seek会只给出部分的数据
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.alibaba.otter.canal.parse.inbound;
|
package com.alibaba.otter.canal.parse.inbound;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
@@ -20,38 +21,114 @@ import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
|||||||
*/
|
*/
|
||||||
public class TableMeta {
|
public class TableMeta {
|
||||||
|
|
||||||
private String fullName; // schema.table
|
private String schema;
|
||||||
private List<FieldMeta> fileds;
|
private String table;
|
||||||
|
private List<FieldMeta> fields = new ArrayList<TableMeta.FieldMeta>();
|
||||||
|
private String ddl; // 表结构的DDL语句
|
||||||
|
|
||||||
public TableMeta(String fullName, List<FieldMeta> fileds){
|
public TableMeta(){
|
||||||
this.fullName = fullName;
|
|
||||||
this.fileds = fileds;
|
}
|
||||||
|
|
||||||
|
public TableMeta(String schema, String table, List<FieldMeta> fields){
|
||||||
|
this.schema = schema;
|
||||||
|
this.table = table;
|
||||||
|
this.fields = fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFullName() {
|
public String getFullName() {
|
||||||
return fullName;
|
return schema + "." + table;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFullName(String fullName) {
|
public String getSchema() {
|
||||||
this.fullName = fullName;
|
return schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FieldMeta> getFileds() {
|
public void setSchema(String schema) {
|
||||||
return fileds;
|
this.schema = schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFileds(List<FieldMeta> fileds) {
|
public String getTable() {
|
||||||
this.fileds = fileds;
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTable(String table) {
|
||||||
|
this.table = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FieldMeta> getFields() {
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFields(List<FieldMeta> fileds) {
|
||||||
|
this.fields = fileds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldMeta getFieldMetaByName(String name) {
|
||||||
|
for (FieldMeta meta : fields) {
|
||||||
|
if (meta.getColumnName().equalsIgnoreCase(name)) {
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("unknow column : " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FieldMeta> getPrimaryFields() {
|
||||||
|
List<FieldMeta> primarys = new ArrayList<TableMeta.FieldMeta>();
|
||||||
|
for (FieldMeta meta : fields) {
|
||||||
|
if (meta.isKey()) {
|
||||||
|
primarys.add(meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return primarys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDdl() {
|
||||||
|
return ddl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDdl(String ddl) {
|
||||||
|
this.ddl = ddl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addFieldMeta(FieldMeta fieldMeta) {
|
||||||
|
this.fields.add(fieldMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder data = new StringBuilder();
|
||||||
|
data.append("TableMeta [schema=" + schema + ", table=" + table + ", fileds=");
|
||||||
|
for (FieldMeta field : fields) {
|
||||||
|
data.append("\n\t").append(field.toString());
|
||||||
|
}
|
||||||
|
data.append("\n]");
|
||||||
|
return data.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class FieldMeta {
|
public static class FieldMeta {
|
||||||
|
|
||||||
|
public FieldMeta(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldMeta(String columnName, String columnType, boolean nullable, boolean key, String defaultValue){
|
||||||
|
this.columnName = columnName;
|
||||||
|
this.columnType = columnType;
|
||||||
|
this.nullable = nullable;
|
||||||
|
this.key = key;
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
private String columnName;
|
private String columnName;
|
||||||
private String columnType;
|
private String columnType;
|
||||||
private String isNullable;
|
private boolean nullable;
|
||||||
private String iskey;
|
private boolean key;
|
||||||
private String defaultValue;
|
private String defaultValue;
|
||||||
private String extra;
|
private String extra;
|
||||||
|
private boolean unique;
|
||||||
|
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
return columnName;
|
return columnName;
|
||||||
@@ -69,20 +146,8 @@ public class TableMeta {
|
|||||||
this.columnType = columnType;
|
this.columnType = columnType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getIsNullable() {
|
public void setNullable(boolean nullable) {
|
||||||
return isNullable;
|
this.nullable = nullable;
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsNullable(String isNullable) {
|
|
||||||
this.isNullable = isNullable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIskey() {
|
|
||||||
return iskey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIskey(String iskey) {
|
|
||||||
this.iskey = iskey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDefaultValue() {
|
public String getDefaultValue() {
|
||||||
@@ -93,6 +158,22 @@ public class TableMeta {
|
|||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isUnsigned() {
|
||||||
|
return StringUtils.containsIgnoreCase(columnType, "unsigned");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNullable() {
|
||||||
|
return nullable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(boolean key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
public String getExtra() {
|
public String getExtra() {
|
||||||
return extra;
|
return extra;
|
||||||
}
|
}
|
||||||
@@ -101,22 +182,21 @@ public class TableMeta {
|
|||||||
this.extra = extra;
|
this.extra = extra;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUnsigned() {
|
public boolean isUnique() {
|
||||||
return StringUtils.containsIgnoreCase(columnType, "unsigned");
|
return unique;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isKey() {
|
public void setUnique(boolean unique) {
|
||||||
return StringUtils.equalsIgnoreCase(iskey, "PRI");
|
this.unique = unique;
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNullable() {
|
|
||||||
return StringUtils.equalsIgnoreCase(isNullable, "YES");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "FieldMeta [columnName=" + columnName + ", columnType=" + columnType + ", defaultValue="
|
return "FieldMeta [columnName=" + columnName + ", columnType=" + columnType + ", nullable=" + nullable
|
||||||
+ defaultValue + ", extra=" + extra + ", isNullable=" + isNullable + ", iskey=" + iskey + "]";
|
+ ", key=" + key + ", defaultValue=" + defaultValue + ", extra=" + extra + ", unique=" + unique
|
||||||
|
+ "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,22 +7,31 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
||||||
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
||||||
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
|
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
|
||||||
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDB;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDBBuilder;
|
||||||
|
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||||
|
|
||||||
public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
||||||
|
|
||||||
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
protected static final long BINLOG_START_OFFEST = 4L;
|
protected static final long BINLOG_START_OFFEST = 4L;
|
||||||
|
|
||||||
|
protected boolean enableTsdb = false;
|
||||||
|
protected String tsdbSpringXml;
|
||||||
|
protected TableMetaTSDB tableMetaTSDB;
|
||||||
// 编码信息
|
// 编码信息
|
||||||
protected byte connectionCharsetNumber = (byte) 33;
|
protected byte connectionCharsetNumber = (byte) 33;
|
||||||
protected Charset connectionCharset = Charset.forName("UTF-8");
|
protected Charset connectionCharset = Charset.forName("UTF-8");
|
||||||
protected boolean filterQueryDcl = false;
|
protected boolean filterQueryDcl = false;
|
||||||
protected boolean filterQueryDml = false;
|
protected boolean filterQueryDml = false;
|
||||||
protected boolean filterQueryDdl = false;
|
protected boolean filterQueryDdl = false;
|
||||||
|
protected boolean filterRows = false;
|
||||||
protected boolean filterTableError = false;
|
protected boolean filterTableError = false;
|
||||||
|
protected boolean useDruidDdlFilter = true;
|
||||||
|
|
||||||
protected BinlogParser buildParser() {
|
protected BinlogParser buildParser() {
|
||||||
LogEventConvert convert = new LogEventConvert();
|
LogEventConvert convert = new LogEventConvert();
|
||||||
@@ -38,6 +47,7 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
|||||||
convert.setFilterQueryDcl(filterQueryDcl);
|
convert.setFilterQueryDcl(filterQueryDcl);
|
||||||
convert.setFilterQueryDml(filterQueryDml);
|
convert.setFilterQueryDml(filterQueryDml);
|
||||||
convert.setFilterQueryDdl(filterQueryDdl);
|
convert.setFilterQueryDdl(filterQueryDdl);
|
||||||
|
convert.setFilterRows(filterRows);
|
||||||
convert.setFilterTableError(filterTableError);
|
convert.setFilterTableError(filterTableError);
|
||||||
return convert;
|
return convert;
|
||||||
}
|
}
|
||||||
@@ -51,6 +61,40 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回滚到指定位点
|
||||||
|
*
|
||||||
|
* @param position
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected boolean processTableMeta(EntryPosition position) {
|
||||||
|
if (tableMetaTSDB != null) {
|
||||||
|
return tableMetaTSDB.rollback(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() throws CanalParseException {
|
||||||
|
if (enableTsdb) {
|
||||||
|
if (tableMetaTSDB == null) {
|
||||||
|
// 初始化
|
||||||
|
tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() throws CanalParseException {
|
||||||
|
if (enableTsdb) {
|
||||||
|
TableMetaTSDBBuilder.destory(destination);
|
||||||
|
tableMetaTSDB = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.stop();
|
||||||
|
}
|
||||||
|
|
||||||
public void setEventBlackFilter(CanalEventFilter eventBlackFilter) {
|
public void setEventBlackFilter(CanalEventFilter eventBlackFilter) {
|
||||||
super.setEventBlackFilter(eventBlackFilter);
|
super.setEventBlackFilter(eventBlackFilter);
|
||||||
|
|
||||||
@@ -87,8 +131,40 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
|||||||
this.filterQueryDdl = filterQueryDdl;
|
this.filterQueryDdl = filterQueryDdl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFilterRows(boolean filterRows) {
|
||||||
|
this.filterRows = filterRows;
|
||||||
|
}
|
||||||
|
|
||||||
public void setFilterTableError(boolean filterTableError) {
|
public void setFilterTableError(boolean filterTableError) {
|
||||||
this.filterTableError = filterTableError;
|
this.filterTableError = filterTableError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isUseDruidDdlFilter() {
|
||||||
|
return useDruidDdlFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUseDruidDdlFilter(boolean useDruidDdlFilter) {
|
||||||
|
this.useDruidDdlFilter = useDruidDdlFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnableTsdb(boolean enableTsdb) {
|
||||||
|
this.enableTsdb = enableTsdb;
|
||||||
|
if (this.enableTsdb) {
|
||||||
|
if (tableMetaTSDB == null) {
|
||||||
|
// 初始化
|
||||||
|
tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTsdbSpringXml(String tsdbSpringXml) {
|
||||||
|
this.tsdbSpringXml = tsdbSpringXml;
|
||||||
|
if (this.enableTsdb) {
|
||||||
|
if (tableMetaTSDB == null) {
|
||||||
|
// 初始化
|
||||||
|
tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
|||||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogPosition;
|
import com.taobao.tddl.dbsync.binlog.LogPosition;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* local bin log connection (not real connection)
|
* local bin log connection (not real connection)
|
||||||
@@ -83,7 +84,10 @@ public class LocalBinLogConnection implements ErosaConnection {
|
|||||||
context.setLogPosition(new LogPosition(binlogfilename, binlogPosition));
|
context.setLogPosition(new LogPosition(binlogfilename, binlogPosition));
|
||||||
while (running) {
|
while (running) {
|
||||||
boolean needContinue = true;
|
boolean needContinue = true;
|
||||||
LogEvent event;
|
LogEvent event = null;
|
||||||
|
// 处理一下binlog文件名
|
||||||
|
event = new RotateLogEvent(context.getLogPosition().getFileName(), 4);
|
||||||
|
func.sink(event);
|
||||||
while (fetcher.fetch()) {
|
while (fetcher.fetch()) {
|
||||||
event = decoder.decode(fetcher, context);
|
event = decoder.decode(fetcher, context);
|
||||||
if (event == null) {
|
if (event == null) {
|
||||||
@@ -94,14 +98,6 @@ public class LocalBinLogConnection implements ErosaConnection {
|
|||||||
needContinue = false;
|
needContinue = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// do {
|
|
||||||
// event = decoder.decode(fetcher, context);
|
|
||||||
// if (event != null && !func.sink(event)) {
|
|
||||||
// needContinue = false;
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// } while (event != null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needContinue) {// 读取下一个
|
if (needContinue) {// 读取下一个
|
||||||
@@ -119,7 +115,6 @@ public class LocalBinLogConnection implements ErosaConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
current = nextFile;
|
current = nextFile;
|
||||||
|
|
||||||
fetcher.open(current);
|
fetcher.open(current);
|
||||||
context.setLogPosition(new LogPosition(nextFile.getName()));
|
context.setLogPosition(new LogPosition(nextFile.getName()));
|
||||||
} else {
|
} else {
|
||||||
@@ -145,6 +140,7 @@ public class LocalBinLogConnection implements ErosaConnection {
|
|||||||
|
|
||||||
FileLogFetcher fetcher = new FileLogFetcher(bufferSize);
|
FileLogFetcher fetcher = new FileLogFetcher(bufferSize);
|
||||||
LogDecoder decoder = new LogDecoder();
|
LogDecoder decoder = new LogDecoder();
|
||||||
|
decoder.handle(LogEvent.FORMAT_DESCRIPTION_EVENT);
|
||||||
decoder.handle(LogEvent.QUERY_EVENT);
|
decoder.handle(LogEvent.QUERY_EVENT);
|
||||||
decoder.handle(LogEvent.XID_EVENT);
|
decoder.handle(LogEvent.XID_EVENT);
|
||||||
LogContext context = new LogContext();
|
LogContext context = new LogContext();
|
||||||
@@ -158,13 +154,11 @@ public class LocalBinLogConnection implements ErosaConnection {
|
|||||||
|
|
||||||
binlogFilename = lastXidLogFilename;
|
binlogFilename = lastXidLogFilename;
|
||||||
binlogFileOffset = lastXidLogFileOffset;
|
binlogFileOffset = lastXidLogFileOffset;
|
||||||
L: while (fetcher.fetch()) {
|
while (fetcher.fetch()) {
|
||||||
LogEvent event;
|
LogEvent event = decoder.decode(fetcher, context);
|
||||||
do {
|
|
||||||
event = decoder.decode(fetcher, context);
|
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
if (event.getWhen() > timestampSeconds) {
|
if (event.getWhen() > timestampSeconds) {
|
||||||
break L;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
needContinue = false;
|
needContinue = false;
|
||||||
@@ -181,7 +175,6 @@ public class LocalBinLogConnection implements ErosaConnection {
|
|||||||
lastXidLogFileOffset = event.getLogPos();
|
lastXidLogFileOffset = event.getLogPos();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (event != null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needContinue) {// 读取下一个
|
if (needContinue) {// 读取下一个
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
package com.alibaba.otter.canal.parse.inbound.mysql;
|
package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.parse.CanalEventParser;
|
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta;
|
||||||
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
|
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
|
||||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||||
@@ -19,12 +24,14 @@ import com.alibaba.otter.canal.protocol.position.LogPosition;
|
|||||||
public class LocalBinlogEventParser extends AbstractMysqlEventParser implements CanalEventParser {
|
public class LocalBinlogEventParser extends AbstractMysqlEventParser implements CanalEventParser {
|
||||||
|
|
||||||
// 数据库信息
|
// 数据库信息
|
||||||
private AuthenticationInfo masterInfo;
|
protected AuthenticationInfo masterInfo;
|
||||||
private EntryPosition masterPosition; // binlog信息
|
protected EntryPosition masterPosition; // binlog信息
|
||||||
|
protected MysqlConnection metaConnection; // 查询meta信息的链接
|
||||||
|
protected TableMetaCache tableMetaCache; // 对应meta
|
||||||
|
|
||||||
private String directory;
|
protected String directory;
|
||||||
private boolean needWait = false;
|
protected boolean needWait = false;
|
||||||
private int bufferSize = 16 * 1024;
|
protected int bufferSize = 16 * 1024;
|
||||||
|
|
||||||
public LocalBinlogEventParser(){
|
public LocalBinlogEventParser(){
|
||||||
// this.runningInfo = new AuthenticationInfo();
|
// this.runningInfo = new AuthenticationInfo();
|
||||||
@@ -35,6 +42,37 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
|
|||||||
return buildLocalBinLogConnection();
|
return buildLocalBinLogConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void preDump(ErosaConnection connection) {
|
||||||
|
metaConnection = buildMysqlConnection();
|
||||||
|
try {
|
||||||
|
metaConnection.connect();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new CanalParseException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tableMetaTSDB != null && tableMetaTSDB instanceof DatabaseTableMeta) {
|
||||||
|
((DatabaseTableMeta) tableMetaTSDB).setConnection(metaConnection);
|
||||||
|
((DatabaseTableMeta) tableMetaTSDB).setFilter(eventFilter);
|
||||||
|
((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
tableMetaCache = new TableMetaCache(metaConnection, tableMetaTSDB);
|
||||||
|
((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void afterDump(ErosaConnection connection) {
|
||||||
|
if (metaConnection != null) {
|
||||||
|
try {
|
||||||
|
metaConnection.disconnect();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("ERROR # disconnect meta connection for address:{}", metaConnection.getConnector()
|
||||||
|
.getAddress(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void start() throws CanalParseException {
|
public void start() throws CanalParseException {
|
||||||
if (runningInfo == null) { // 第一次链接主库
|
if (runningInfo == null) { // 第一次链接主库
|
||||||
runningInfo = masterInfo;
|
runningInfo = masterInfo;
|
||||||
@@ -43,6 +81,24 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
|
|||||||
super.start();
|
super.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
if (metaConnection != null) {
|
||||||
|
try {
|
||||||
|
metaConnection.disconnect();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("ERROR # disconnect meta connection for address:{}", metaConnection.getConnector()
|
||||||
|
.getAddress(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tableMetaCache != null) {
|
||||||
|
tableMetaCache.clearTableMeta();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.stop();
|
||||||
|
}
|
||||||
|
|
||||||
private ErosaConnection buildLocalBinLogConnection() {
|
private ErosaConnection buildLocalBinLogConnection() {
|
||||||
LocalBinLogConnection connection = new LocalBinLogConnection();
|
LocalBinLogConnection connection = new LocalBinLogConnection();
|
||||||
|
|
||||||
@@ -53,6 +109,19 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
|
|||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MysqlConnection buildMysqlConnection() {
|
||||||
|
MysqlConnection connection = new MysqlConnection(runningInfo.getAddress(),
|
||||||
|
runningInfo.getUsername(),
|
||||||
|
runningInfo.getPassword(),
|
||||||
|
connectionCharsetNumber,
|
||||||
|
runningInfo.getDefaultDatabaseName());
|
||||||
|
connection.getConnector().setReceiveBufferSize(64 * 1024);
|
||||||
|
connection.getConnector().setSendBufferSize(64 * 1024);
|
||||||
|
connection.getConnector().setSoTimeout(30 * 1000);
|
||||||
|
connection.setCharset(connectionCharset);
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected EntryPosition findStartPosition(ErosaConnection connection) {
|
protected EntryPosition findStartPosition(ErosaConnection connection) {
|
||||||
// 处理逻辑
|
// 处理逻辑
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package com.alibaba.otter.canal.parse.inbound.mysql;
|
package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||||
|
|
||||||
|
import static com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher.MASTER_HEARTBEAT_PERIOD_SECONDS;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -15,12 +17,16 @@ import com.alibaba.otter.canal.parse.driver.mysql.MysqlQueryExecutor;
|
|||||||
import com.alibaba.otter.canal.parse.driver.mysql.MysqlUpdateExecutor;
|
import com.alibaba.otter.canal.parse.driver.mysql.MysqlUpdateExecutor;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.BinlogDumpCommandPacket;
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.BinlogDumpCommandPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.RegisterSlaveCommandPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.SemiAckCommandPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ErrorPacket;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
||||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||||
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher;
|
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher;
|
||||||
|
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogContext;
|
import com.taobao.tddl.dbsync.binlog.LogContext;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
@@ -35,17 +41,36 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
private BinlogFormat binlogFormat;
|
private BinlogFormat binlogFormat;
|
||||||
private BinlogImage binlogImage;
|
private BinlogImage binlogImage;
|
||||||
|
|
||||||
|
// tsdb releated
|
||||||
|
private AuthenticationInfo authInfo;
|
||||||
|
protected int connTimeout = 5 * 1000; // 5秒
|
||||||
|
protected int soTimeout = 60 * 60 * 1000; // 1小时
|
||||||
|
|
||||||
public MysqlConnection(){
|
public MysqlConnection(){
|
||||||
}
|
}
|
||||||
|
|
||||||
public MysqlConnection(InetSocketAddress address, String username, String password){
|
public MysqlConnection(InetSocketAddress address, String username, String password){
|
||||||
|
authInfo = new AuthenticationInfo();
|
||||||
|
authInfo.setAddress(address);
|
||||||
|
authInfo.setUsername(username);
|
||||||
|
authInfo.setPassword(password);
|
||||||
connector = new MysqlConnector(address, username, password);
|
connector = new MysqlConnector(address, username, password);
|
||||||
|
// 将connection里面的参数透传下
|
||||||
|
connector.setSoTimeout(soTimeout);
|
||||||
|
connector.setConnTimeout(connTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MysqlConnection(InetSocketAddress address, String username, String password, byte charsetNumber,
|
public MysqlConnection(InetSocketAddress address, String username, String password, byte charsetNumber,
|
||||||
String defaultSchema){
|
String defaultSchema){
|
||||||
|
authInfo = new AuthenticationInfo();
|
||||||
|
authInfo.setAddress(address);
|
||||||
|
authInfo.setUsername(username);
|
||||||
|
authInfo.setPassword(password);
|
||||||
|
authInfo.setDefaultDatabaseName(defaultSchema);
|
||||||
connector = new MysqlConnector(address, username, password, charsetNumber, defaultSchema);
|
connector = new MysqlConnector(address, username, password, charsetNumber, defaultSchema);
|
||||||
|
// 将connection里面的参数透传下
|
||||||
|
connector.setSoTimeout(soTimeout);
|
||||||
|
connector.setConnTimeout(connTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void connect() throws IOException {
|
public void connect() throws IOException {
|
||||||
@@ -69,6 +94,11 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
return exector.query(cmd);
|
return exector.query(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ResultSetPacket> queryMulti(String cmd) throws IOException {
|
||||||
|
MysqlQueryExecutor exector = new MysqlQueryExecutor(connector);
|
||||||
|
return exector.queryMulti(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
public void update(String cmd) throws IOException {
|
public void update(String cmd) throws IOException {
|
||||||
MysqlUpdateExecutor exector = new MysqlUpdateExecutor(connector);
|
MysqlUpdateExecutor exector = new MysqlUpdateExecutor(connector);
|
||||||
exector.update(cmd);
|
exector.update(cmd);
|
||||||
@@ -105,6 +135,7 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
|
|
||||||
public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
||||||
updateSettings();
|
updateSettings();
|
||||||
|
sendRegisterSlave();
|
||||||
sendBinlogDump(binlogfilename, binlogPosition);
|
sendBinlogDump(binlogfilename, binlogPosition);
|
||||||
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
||||||
fetcher.start(connector.getChannel());
|
fetcher.start(connector.getChannel());
|
||||||
@@ -121,6 +152,10 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
if (!func.sink(event)) {
|
if (!func.sink(event)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.getSemival() == 1) {
|
||||||
|
sendSemiAck(context.getLogPosition().getFileName(), binlogPosition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +163,36 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
throw new NullPointerException("Not implement yet");
|
throw new NullPointerException("Not implement yet");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendRegisterSlave() throws IOException {
|
||||||
|
RegisterSlaveCommandPacket cmd = new RegisterSlaveCommandPacket();
|
||||||
|
cmd.reportHost = authInfo.getAddress().getAddress().getHostAddress();
|
||||||
|
cmd.reportPasswd = authInfo.getPassword();
|
||||||
|
cmd.reportUser = authInfo.getUsername();
|
||||||
|
cmd.reportPort = authInfo.getAddress().getPort(); // 暂时先用master节点的port
|
||||||
|
cmd.serverId = this.slaveId;
|
||||||
|
byte[] cmdBody = cmd.toBytes();
|
||||||
|
|
||||||
|
logger.info("Register slave {}", cmd);
|
||||||
|
|
||||||
|
HeaderPacket header = new HeaderPacket();
|
||||||
|
header.setPacketBodyLength(cmdBody.length);
|
||||||
|
header.setPacketSequenceNumber((byte) 0x00);
|
||||||
|
PacketManager.writePkg(connector.getChannel(), header.toBytes(), cmdBody);
|
||||||
|
|
||||||
|
header = PacketManager.readHeader(connector.getChannel(), 4);
|
||||||
|
byte[] body = PacketManager.readBytes(connector.getChannel(), header.getPacketBodyLength());
|
||||||
|
assert body != null;
|
||||||
|
if (body[0] < 0) {
|
||||||
|
if (body[0] == -1) {
|
||||||
|
ErrorPacket err = new ErrorPacket();
|
||||||
|
err.fromBytes(body);
|
||||||
|
throw new IOException("Error When doing Register slave:" + err.toString());
|
||||||
|
} else {
|
||||||
|
throw new IOException("unpexpected packet with field_count=" + body[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void sendBinlogDump(String binlogfilename, Long binlogPosition) throws IOException {
|
private void sendBinlogDump(String binlogfilename, Long binlogPosition) throws IOException {
|
||||||
BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket();
|
BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket();
|
||||||
binlogDumpCmd.binlogFileName = binlogfilename;
|
binlogDumpCmd.binlogFileName = binlogfilename;
|
||||||
@@ -139,8 +204,22 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
HeaderPacket binlogDumpHeader = new HeaderPacket();
|
HeaderPacket binlogDumpHeader = new HeaderPacket();
|
||||||
binlogDumpHeader.setPacketBodyLength(cmdBody.length);
|
binlogDumpHeader.setPacketBodyLength(cmdBody.length);
|
||||||
binlogDumpHeader.setPacketSequenceNumber((byte) 0x00);
|
binlogDumpHeader.setPacketSequenceNumber((byte) 0x00);
|
||||||
PacketManager.write(connector.getChannel(), new ByteBuffer[] { ByteBuffer.wrap(binlogDumpHeader.toBytes()),
|
PacketManager.writePkg(connector.getChannel(), binlogDumpHeader.toBytes(), cmdBody);
|
||||||
ByteBuffer.wrap(cmdBody) });
|
connector.setDumping(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendSemiAck(String binlogfilename, Long binlogPosition) throws IOException {
|
||||||
|
SemiAckCommandPacket semiAckCmd = new SemiAckCommandPacket();
|
||||||
|
semiAckCmd.binlogFileName = binlogfilename;
|
||||||
|
semiAckCmd.binlogPosition = binlogPosition;
|
||||||
|
|
||||||
|
byte[] cmdBody = semiAckCmd.toBytes();
|
||||||
|
|
||||||
|
logger.info("SEMI ACK with position:{}", semiAckCmd);
|
||||||
|
HeaderPacket semiAckHeader = new HeaderPacket();
|
||||||
|
semiAckHeader.setPacketBodyLength(cmdBody.length);
|
||||||
|
semiAckHeader.setPacketSequenceNumber((byte) 0x00);
|
||||||
|
PacketManager.writePkg(connector.getChannel(), semiAckHeader.toBytes(), cmdBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MysqlConnection fork() {
|
public MysqlConnection fork() {
|
||||||
@@ -148,6 +227,8 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
connection.setCharset(getCharset());
|
connection.setCharset(getCharset());
|
||||||
connection.setSlaveId(getSlaveId());
|
connection.setSlaveId(getSlaveId());
|
||||||
connection.setConnector(connector.fork());
|
connection.setConnector(connector.fork());
|
||||||
|
// set authInfo
|
||||||
|
connection.setAuthInfo(authInfo);
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,25 +249,25 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
try {
|
try {
|
||||||
update("set wait_timeout=9999999");
|
update("set wait_timeout=9999999");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
logger.warn("update wait_timeout failed", e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
update("set net_write_timeout=1800");
|
update("set net_write_timeout=1800");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
logger.warn("update net_write_timeout failed", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
update("set net_read_timeout=1800");
|
update("set net_read_timeout=1800");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
logger.warn("update net_read_timeout failed", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 设置服务端返回结果时不做编码转化,直接按照数据库的二进制编码进行发送,由客户端自己根据需求进行编码转化
|
// 设置服务端返回结果时不做编码转化,直接按照数据库的二进制编码进行发送,由客户端自己根据需求进行编码转化
|
||||||
update("set names 'binary'");
|
update("set names 'binary'");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
logger.warn("update names failed", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -196,14 +277,40 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
// 但也不能乱设置,需要和mysql server的checksum配置一致,不然RotateLogEvent会出现乱码
|
// 但也不能乱设置,需要和mysql server的checksum配置一致,不然RotateLogEvent会出现乱码
|
||||||
update("set @master_binlog_checksum= '@@global.binlog_checksum'");
|
update("set @master_binlog_checksum= '@@global.binlog_checksum'");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
logger.warn("update master_binlog_checksum failed", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 参考:https://github.com/alibaba/canal/issues/284
|
||||||
|
// mysql5.6需要设置slave_uuid避免被server kill链接
|
||||||
|
update("set @slave_uuid=uuid()");
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (!StringUtils.contains(e.getMessage(), "Unknown system variable")) {
|
||||||
|
logger.warn("update slave_uuid failed", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// mariadb针对特殊的类型,需要设置session变量
|
// mariadb针对特殊的类型,需要设置session变量
|
||||||
update("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
|
update("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
logger.warn("update mariadb_slave_capability failed", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MASTER_HEARTBEAT_PERIOD sets the interval in seconds between
|
||||||
|
* replication heartbeats. Whenever the master's binary log is updated
|
||||||
|
* with an event, the waiting period for the next heartbeat is reset.
|
||||||
|
* interval is a decimal value having the range 0 to 4294967 seconds and
|
||||||
|
* a resolution in milliseconds; the smallest nonzero value is 0.001.
|
||||||
|
* Heartbeats are sent by the master only if there are no unsent events
|
||||||
|
* in the binary log file for a period longer than interval.
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
long periodNano = TimeUnit.SECONDS.toNanos(MASTER_HEARTBEAT_PERIOD_SECONDS);
|
||||||
|
update("SET @master_heartbeat_period=" + periodNano);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("update master_heartbeat_period failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,4 +480,23 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
return binlogImage;
|
return binlogImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InetSocketAddress getAddress() {
|
||||||
|
return authInfo.getAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConnTimeout(int connTimeout) {
|
||||||
|
this.connTimeout = connTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSoTimeout(int soTimeout) {
|
||||||
|
this.soTimeout = soTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationInfo getAuthInfo() {
|
||||||
|
return authInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthInfo(AuthenticationInfo authInfo) {
|
||||||
|
this.authInfo = authInfo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
package com.alibaba.otter.canal.parse.inbound.mysql;
|
package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
||||||
import com.alibaba.otter.canal.parse.CanalEventParser;
|
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||||
import com.alibaba.otter.canal.parse.CanalHASwitchable;
|
import com.alibaba.otter.canal.parse.CanalHASwitchable;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket;
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket;
|
||||||
@@ -26,9 +27,9 @@ import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogFormat;
|
|||||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogImage;
|
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogImage;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache;
|
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta;
|
||||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry;
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
|
||||||
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||||
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
import com.alibaba.otter.canal.protocol.position.LogPosition;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
@@ -67,7 +68,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验
|
private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验
|
||||||
private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验
|
private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验
|
||||||
|
|
||||||
// 心跳检查
|
// update by yishun.chen,特殊异常处理参数
|
||||||
|
private int dumpErrorCount = 0; // binlogDump失败异常计数
|
||||||
|
private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值
|
||||||
|
|
||||||
protected ErosaConnection buildErosaConnection() {
|
protected ErosaConnection buildErosaConnection() {
|
||||||
return buildMysqlConnection(this.runningInfo);
|
return buildMysqlConnection(this.runningInfo);
|
||||||
@@ -92,6 +95,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
for (BinlogFormat supportFormat : supportBinlogFormats) {
|
for (BinlogFormat supportFormat : supportBinlogFormats) {
|
||||||
if (supportFormat != null && format == supportFormat) {
|
if (supportFormat != null && format == supportFormat) {
|
||||||
found = true;
|
found = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +110,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
for (BinlogImage supportImage : supportBinlogImages) {
|
for (BinlogImage supportImage : supportBinlogImages) {
|
||||||
if (supportImage != null && image == supportImage) {
|
if (supportImage != null && image == supportImage) {
|
||||||
found = true;
|
found = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +119,13 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tableMetaCache = new TableMetaCache(metaConnection);
|
if (tableMetaTSDB != null && tableMetaTSDB instanceof DatabaseTableMeta) {
|
||||||
|
((DatabaseTableMeta) tableMetaTSDB).setConnection(metaConnection);
|
||||||
|
((DatabaseTableMeta) tableMetaTSDB).setFilter(eventFilter);
|
||||||
|
((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
tableMetaCache = new TableMetaCache(metaConnection, tableMetaTSDB);
|
||||||
((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache);
|
((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,10 +187,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void stopHeartBeat() {
|
protected void stopHeartBeat() {
|
||||||
|
TimerTask heartBeatTimerTask = this.heartBeatTimerTask;
|
||||||
super.stopHeartBeat();
|
super.stopHeartBeat();
|
||||||
|
if (heartBeatTimerTask != null && heartBeatTimerTask instanceof MysqlDetectingTimeTask) {
|
||||||
if (heartBeatTimerTask != null) {
|
|
||||||
|
|
||||||
MysqlConnection mysqlConnection = ((MysqlDetectingTimeTask) heartBeatTimerTask).getMysqlConnection();
|
MysqlConnection mysqlConnection = ((MysqlDetectingTimeTask) heartBeatTimerTask).getMysqlConnection();
|
||||||
try {
|
try {
|
||||||
mysqlConnection.disconnect();
|
mysqlConnection.disconnect();
|
||||||
@@ -234,19 +244,19 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
((HeartBeatCallback) haController).onFailed(e);
|
((HeartBeatCallback) haController).onFailed(e);
|
||||||
}
|
}
|
||||||
reconnect = true;
|
reconnect = true;
|
||||||
logger.warn("connect failed by " + ExceptionUtils.getStackTrace(e));
|
logger.warn("connect failed by ", e);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (haController != null && haController instanceof HeartBeatCallback) {
|
if (haController != null && haController instanceof HeartBeatCallback) {
|
||||||
((HeartBeatCallback) haController).onFailed(e);
|
((HeartBeatCallback) haController).onFailed(e);
|
||||||
}
|
}
|
||||||
reconnect = true;
|
reconnect = true;
|
||||||
logger.warn("connect failed by " + ExceptionUtils.getStackTrace(e));
|
logger.warn("connect failed by ", e);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
if (haController != null && haController instanceof HeartBeatCallback) {
|
if (haController != null && haController instanceof HeartBeatCallback) {
|
||||||
((HeartBeatCallback) haController).onFailed(e);
|
((HeartBeatCallback) haController).onFailed(e);
|
||||||
}
|
}
|
||||||
reconnect = true;
|
reconnect = true;
|
||||||
logger.warn("connect failed by " + ExceptionUtils.getStackTrace(e));
|
logger.warn("connect failed by ", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -304,10 +314,35 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
connection.getConnector().setSendBufferSize(sendBufferSize);
|
connection.getConnector().setSendBufferSize(sendBufferSize);
|
||||||
connection.getConnector().setSoTimeout(defaultConnectionTimeoutInSeconds * 1000);
|
connection.getConnector().setSoTimeout(defaultConnectionTimeoutInSeconds * 1000);
|
||||||
connection.setCharset(connectionCharset);
|
connection.setCharset(connectionCharset);
|
||||||
|
// 随机生成slaveId
|
||||||
|
if (this.slaveId <= 0) {
|
||||||
|
this.slaveId = generateUniqueServerId();
|
||||||
|
}
|
||||||
connection.setSlaveId(this.slaveId);
|
connection.setSlaveId(this.slaveId);
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final long generateUniqueServerId() {
|
||||||
|
try {
|
||||||
|
// a=`echo $masterip|cut -d\. -f1`
|
||||||
|
// b=`echo $masterip|cut -d\. -f2`
|
||||||
|
// c=`echo $masterip|cut -d\. -f3`
|
||||||
|
// d=`echo $masterip|cut -d\. -f4`
|
||||||
|
// #server_id=`expr $a \* 256 \* 256 \* 256 + $b \* 256 \* 256 + $c
|
||||||
|
// \* 256 + $d `
|
||||||
|
// #server_id=$b$c$d
|
||||||
|
// server_id=`expr $b \* 256 \* 256 + $c \* 256 + $d `
|
||||||
|
InetAddress localHost = InetAddress.getLocalHost();
|
||||||
|
byte[] addr = localHost.getAddress();
|
||||||
|
int salt = (destination != null) ? destination.hashCode() : 0;
|
||||||
|
return ((0x7f & salt) << 24) + ((0xff & (int) addr[1]) << 16) // NL
|
||||||
|
+ ((0xff & (int) addr[2]) << 8) // NL
|
||||||
|
+ (0xff & (int) addr[3]);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
throw new CanalParseException("Unknown host", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected EntryPosition findStartPosition(ErosaConnection connection) throws IOException {
|
protected EntryPosition findStartPosition(ErosaConnection connection) throws IOException {
|
||||||
EntryPosition startPosition = findStartPositionInternal(connection);
|
EntryPosition startPosition = findStartPositionInternal(connection);
|
||||||
if (needTransactionPosition.get()) {
|
if (needTransactionPosition.get()) {
|
||||||
@@ -324,6 +359,47 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
return startPosition;
|
return startPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected EntryPosition findEndPosition(ErosaConnection connection) throws IOException {
|
||||||
|
MysqlConnection mysqlConnection = (MysqlConnection) connection;
|
||||||
|
EntryPosition endPosition = findEndPosition(mysqlConnection);
|
||||||
|
return endPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected EntryPosition findEndPositionWithMasterIdAndTimestamp(MysqlConnection connection) {
|
||||||
|
MysqlConnection mysqlConnection = (MysqlConnection) connection;
|
||||||
|
final EntryPosition endPosition = findEndPosition(mysqlConnection);
|
||||||
|
if (tableMetaTSDB != null) {
|
||||||
|
long startTimestamp = System.currentTimeMillis();
|
||||||
|
return findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
||||||
|
startTimestamp,
|
||||||
|
endPosition,
|
||||||
|
endPosition.getJournalName(),
|
||||||
|
true);
|
||||||
|
} else {
|
||||||
|
return endPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected EntryPosition findPositionWithMasterIdAndTimestamp(MysqlConnection connection, EntryPosition fixedPosition) {
|
||||||
|
MysqlConnection mysqlConnection = (MysqlConnection) connection;
|
||||||
|
if (tableMetaTSDB != null && (fixedPosition.getTimestamp() == null || fixedPosition.getTimestamp() <= 0)) {
|
||||||
|
// 使用一个未来极大的时间,基于位点进行定位
|
||||||
|
long startTimestamp = System.currentTimeMillis() + 102L * 365 * 24 * 3600 * 1000; // 当前时间的未来102年
|
||||||
|
EntryPosition entryPosition = findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
||||||
|
startTimestamp,
|
||||||
|
fixedPosition,
|
||||||
|
fixedPosition.getJournalName(),
|
||||||
|
true);
|
||||||
|
if (entryPosition == null) {
|
||||||
|
throw new CanalParseException("[fixed timestamp] can't found begin/commit position before with fixed position"
|
||||||
|
+ fixedPosition.getJournalName() + ":" + fixedPosition.getPosition());
|
||||||
|
}
|
||||||
|
return entryPosition;
|
||||||
|
} else {
|
||||||
|
return fixedPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected EntryPosition findStartPositionInternal(ErosaConnection connection) {
|
protected EntryPosition findStartPositionInternal(ErosaConnection connection) {
|
||||||
MysqlConnection mysqlConnection = (MysqlConnection) connection;
|
MysqlConnection mysqlConnection = (MysqlConnection) connection;
|
||||||
LogPosition logPosition = logPositionManager.getLatestIndexBy(destination);
|
LogPosition logPosition = logPositionManager.getLatestIndexBy(destination);
|
||||||
@@ -337,7 +413,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (entryPosition == null) {
|
if (entryPosition == null) {
|
||||||
entryPosition = findEndPosition(mysqlConnection); // 默认从当前最后一个位置进行消费
|
entryPosition = findEndPositionWithMasterIdAndTimestamp(mysqlConnection); // 默认从当前最后一个位置进行消费
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断一下是否需要按时间订阅
|
// 判断一下是否需要按时间订阅
|
||||||
@@ -349,13 +425,15 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
return findByStartTimeStamp(mysqlConnection, entryPosition.getTimestamp());
|
return findByStartTimeStamp(mysqlConnection, entryPosition.getTimestamp());
|
||||||
} else {
|
} else {
|
||||||
logger.warn("prepare to find start position just show master status");
|
logger.warn("prepare to find start position just show master status");
|
||||||
return findEndPosition(mysqlConnection); // 默认从当前最后一个位置进行消费
|
return findEndPositionWithMasterIdAndTimestamp(mysqlConnection); // 默认从当前最后一个位置进行消费
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (entryPosition.getPosition() != null && entryPosition.getPosition() > 0L) {
|
if (entryPosition.getPosition() != null && entryPosition.getPosition() > 0L) {
|
||||||
// 如果指定binlogName + offest,直接返回
|
// 如果指定binlogName + offest,直接返回
|
||||||
|
entryPosition = findPositionWithMasterIdAndTimestamp(mysqlConnection, entryPosition);
|
||||||
logger.warn("prepare to find start position {}:{}:{}",
|
logger.warn("prepare to find start position {}:{}:{}",
|
||||||
new Object[] { entryPosition.getJournalName(), entryPosition.getPosition(), "" });
|
new Object[] { entryPosition.getJournalName(), entryPosition.getPosition(),
|
||||||
|
entryPosition.getTimestamp() });
|
||||||
return entryPosition;
|
return entryPosition;
|
||||||
} else {
|
} else {
|
||||||
EntryPosition specificLogFilePosition = null;
|
EntryPosition specificLogFilePosition = null;
|
||||||
@@ -369,7 +447,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
specificLogFilePosition = findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
specificLogFilePosition = findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
||||||
entryPosition.getTimestamp(),
|
entryPosition.getTimestamp(),
|
||||||
endPosition,
|
endPosition,
|
||||||
entryPosition.getJournalName());
|
entryPosition.getJournalName(),
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,7 +463,27 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (logPosition.getIdentity().getSourceAddress().equals(mysqlConnection.getConnector().getAddress())) {
|
if (logPosition.getIdentity().getSourceAddress().equals(mysqlConnection.getConnector().getAddress())) {
|
||||||
logger.warn("prepare to find start position just last position");
|
if (dumpErrorCountThreshold >= 0 && dumpErrorCount > dumpErrorCountThreshold) {
|
||||||
|
// binlog定位位点失败,可能有两个原因:
|
||||||
|
// 1. binlog位点被删除
|
||||||
|
// 2.vip模式的mysql,发生了主备切换,判断一下serverId是否变化,针对这种模式可以发起一次基于时间戳查找合适的binlog位点
|
||||||
|
boolean case2 = (standbyInfo == null || standbyInfo.getAddress() == null)
|
||||||
|
&& logPosition.getPostion().getServerId() != null
|
||||||
|
&& !logPosition.getPostion().getServerId().equals(findServerId(mysqlConnection));
|
||||||
|
if (case2) {
|
||||||
|
long timestamp = logPosition.getPostion().getTimestamp();
|
||||||
|
long newStartTimestamp = timestamp - fallbackIntervalInSeconds * 1000;
|
||||||
|
logger.warn("prepare to find start position by last position {}:{}:{}", new Object[] { "", "",
|
||||||
|
logPosition.getPostion().getTimestamp() });
|
||||||
|
EntryPosition findPosition = findByStartTimeStamp(mysqlConnection, newStartTimestamp);
|
||||||
|
// 重新置为一下
|
||||||
|
dumpErrorCount = 0;
|
||||||
|
return findPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 其余情况
|
||||||
|
logger.warn("prepare to find start position just last position\n {}",
|
||||||
|
JsonUtils.marshalToString(logPosition));
|
||||||
return logPosition.getPostion();
|
return logPosition.getPostion();
|
||||||
} else {
|
} else {
|
||||||
// 针对切换的情况,考虑回退时间
|
// 针对切换的情况,考虑回退时间
|
||||||
@@ -400,40 +499,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
// 主要考虑一个事务执行时间可能会几秒种,如果仅仅按照timestamp相同,则可能会丢失事务的前半部分数据
|
// 主要考虑一个事务执行时间可能会几秒种,如果仅仅按照timestamp相同,则可能会丢失事务的前半部分数据
|
||||||
private Long findTransactionBeginPosition(ErosaConnection mysqlConnection, final EntryPosition entryPosition)
|
private Long findTransactionBeginPosition(ErosaConnection mysqlConnection, final EntryPosition entryPosition)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
// 尝试找到一个合适的位置
|
|
||||||
final AtomicBoolean reDump = new AtomicBoolean(false);
|
|
||||||
mysqlConnection.reconnect();
|
|
||||||
mysqlConnection.seek(entryPosition.getJournalName(), entryPosition.getPosition(), new SinkFunction<LogEvent>() {
|
|
||||||
|
|
||||||
private LogPosition lastPosition;
|
|
||||||
|
|
||||||
public boolean sink(LogEvent event) {
|
|
||||||
try {
|
|
||||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event);
|
|
||||||
if (entry == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 直接查询第一条业务数据,确认是否为事务Begin/End
|
|
||||||
if (CanalEntry.EntryType.TRANSACTIONBEGIN == entry.getEntryType()
|
|
||||||
|| CanalEntry.EntryType.TRANSACTIONEND == entry.getEntryType()) {
|
|
||||||
lastPosition = buildLastPosition(entry);
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
reDump.set(true);
|
|
||||||
lastPosition = buildLastPosition(entry);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 上一次记录的poistion可能为一条update/insert/delete变更事件,直接进行dump的话,会缺少tableMap事件,导致tableId未进行解析
|
|
||||||
processError(e, lastPosition, entryPosition.getJournalName(), entryPosition.getPosition());
|
|
||||||
reDump.set(true);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// 针对开始的第一条为非Begin记录,需要从该binlog扫描
|
// 针对开始的第一条为非Begin记录,需要从该binlog扫描
|
||||||
if (reDump.get()) {
|
|
||||||
final AtomicLong preTransactionStartPosition = new AtomicLong(0L);
|
final AtomicLong preTransactionStartPosition = new AtomicLong(0L);
|
||||||
mysqlConnection.reconnect();
|
mysqlConnection.reconnect();
|
||||||
mysqlConnection.seek(entryPosition.getJournalName(), 4L, new SinkFunction<LogEvent>() {
|
mysqlConnection.seek(entryPosition.getJournalName(), 4L, new SinkFunction<LogEvent>() {
|
||||||
@@ -442,7 +508,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
|
|
||||||
public boolean sink(LogEvent event) {
|
public boolean sink(LogEvent event) {
|
||||||
try {
|
try {
|
||||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event);
|
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, true);
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -460,7 +526,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
|
|
||||||
lastPosition = buildLastPosition(entry);
|
lastPosition = buildLastPosition(entry);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
processError(e, lastPosition, entryPosition.getJournalName(), entryPosition.getPosition());
|
processSinkError(e, lastPosition, entryPosition.getJournalName(), entryPosition.getPosition());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,9 +540,6 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
throw new CanalParseException("preTransactionStartPosition greater than startPosition from zk or localconf, maybe lost data");
|
throw new CanalParseException("preTransactionStartPosition greater than startPosition from zk or localconf, maybe lost data");
|
||||||
}
|
}
|
||||||
return preTransactionStartPosition.get();
|
return preTransactionStartPosition.get();
|
||||||
} else {
|
|
||||||
return entryPosition.getPosition();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据时间查找binlog位置
|
// 根据时间查找binlog位置
|
||||||
@@ -493,7 +556,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
EntryPosition entryPosition = findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
EntryPosition entryPosition = findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
||||||
startTimestamp,
|
startTimestamp,
|
||||||
endPosition,
|
endPosition,
|
||||||
startSearchBinlogFile);
|
startSearchBinlogFile,
|
||||||
|
false);
|
||||||
if (entryPosition == null) {
|
if (entryPosition == null) {
|
||||||
if (StringUtils.equalsIgnoreCase(minBinlogFileName, startSearchBinlogFile)) {
|
if (StringUtils.equalsIgnoreCase(minBinlogFileName, startSearchBinlogFile)) {
|
||||||
// 已经找到最早的一个binlog,没必要往前找了
|
// 已经找到最早的一个binlog,没必要往前找了
|
||||||
@@ -520,9 +584,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
return entryPosition;
|
return entryPosition;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("the binlogfile:{} doesn't exist, to continue to search the next binlogfile , caused by {}",
|
logger.warn(String.format("the binlogfile:%s doesn't exist, to continue to search the next binlogfile , caused by",
|
||||||
startSearchBinlogFile,
|
startSearchBinlogFile),
|
||||||
ExceptionUtils.getFullStackTrace(e));
|
e);
|
||||||
int binlogSeqNum = Integer.parseInt(startSearchBinlogFile.substring(startSearchBinlogFile.indexOf(".") + 1));
|
int binlogSeqNum = Integer.parseInt(startSearchBinlogFile.substring(startSearchBinlogFile.indexOf(".") + 1));
|
||||||
if (binlogSeqNum <= 1) {
|
if (binlogSeqNum <= 1) {
|
||||||
logger.warn("Didn't find the corresponding binlog files");
|
logger.warn("Didn't find the corresponding binlog files");
|
||||||
@@ -540,6 +604,22 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前db的serverId信息
|
||||||
|
*/
|
||||||
|
private Long findServerId(MysqlConnection mysqlConnection) {
|
||||||
|
try {
|
||||||
|
ResultSetPacket packet = mysqlConnection.query("show variables like 'server_id'");
|
||||||
|
List<String> fields = packet.getFieldValues();
|
||||||
|
if (CollectionUtils.isEmpty(fields)) {
|
||||||
|
throw new CanalParseException("command : show variables like 'server_id' has an error! pls check. you need (at least one of) the SUPER,REPLICATION CLIENT privilege(s) for this operation");
|
||||||
|
}
|
||||||
|
return Long.valueOf(fields.get(1));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new CanalParseException("command : show variables like 'server_id' has an error!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询当前的binlog位置
|
* 查询当前的binlog位置
|
||||||
*/
|
*/
|
||||||
@@ -625,7 +705,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
private EntryPosition findAsPerTimestampInSpecificLogFile(MysqlConnection mysqlConnection,
|
private EntryPosition findAsPerTimestampInSpecificLogFile(MysqlConnection mysqlConnection,
|
||||||
final Long startTimestamp,
|
final Long startTimestamp,
|
||||||
final EntryPosition endPosition,
|
final EntryPosition endPosition,
|
||||||
final String searchBinlogFile) {
|
final String searchBinlogFile,
|
||||||
|
final Boolean justForPositionTimestamp) {
|
||||||
|
|
||||||
final LogPosition logPosition = new LogPosition();
|
final LogPosition logPosition = new LogPosition();
|
||||||
try {
|
try {
|
||||||
@@ -638,7 +719,16 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
public boolean sink(LogEvent event) {
|
public boolean sink(LogEvent event) {
|
||||||
EntryPosition entryPosition = null;
|
EntryPosition entryPosition = null;
|
||||||
try {
|
try {
|
||||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event);
|
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, true);
|
||||||
|
if (justForPositionTimestamp && logPosition.getPostion() == null && event.getWhen() > 0) {
|
||||||
|
// 初始位点
|
||||||
|
entryPosition = new EntryPosition(searchBinlogFile,
|
||||||
|
event.getLogPos(),
|
||||||
|
event.getWhen() * 1000,
|
||||||
|
event.getServerId());
|
||||||
|
logPosition.setPostion(entryPosition);
|
||||||
|
}
|
||||||
|
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -646,11 +736,14 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
String logfilename = entry.getHeader().getLogfileName();
|
String logfilename = entry.getHeader().getLogfileName();
|
||||||
Long logfileoffset = entry.getHeader().getLogfileOffset();
|
Long logfileoffset = entry.getHeader().getLogfileOffset();
|
||||||
Long logposTimestamp = entry.getHeader().getExecuteTime();
|
Long logposTimestamp = entry.getHeader().getExecuteTime();
|
||||||
|
Long serverId = entry.getHeader().getServerId();
|
||||||
|
|
||||||
if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())
|
if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())
|
||||||
|| CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
|| CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("compare exit condition:{},{},{}, startTimestamp={}...", new Object[] {
|
logger.debug("compare exit condition:{},{},{}, startTimestamp={}...", new Object[] {
|
||||||
logfilename, logfileoffset, logposTimestamp, startTimestamp });
|
logfilename, logfileoffset, logposTimestamp, startTimestamp });
|
||||||
|
}
|
||||||
// 事务头和尾寻找第一条记录时间戳,如果最小的一条记录都不满足条件,可直接退出
|
// 事务头和尾寻找第一条记录时间戳,如果最小的一条记录都不满足条件,可直接退出
|
||||||
if (logposTimestamp >= startTimestamp) {
|
if (logposTimestamp >= startTimestamp) {
|
||||||
return false;
|
return false;
|
||||||
@@ -658,7 +751,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.equals(endPosition.getJournalName(), logfilename)
|
if (StringUtils.equals(endPosition.getJournalName(), logfilename)
|
||||||
&& endPosition.getPosition() <= (logfileoffset + event.getEventLen())) {
|
&& endPosition.getPosition() < logfileoffset) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,23 +759,25 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
// position = current +
|
// position = current +
|
||||||
// data.length,代表该事务的下一条offest,避免多余的事务重复
|
// data.length,代表该事务的下一条offest,避免多余的事务重复
|
||||||
if (CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
if (CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
||||||
entryPosition = new EntryPosition(logfilename,
|
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp, serverId);
|
||||||
logfileoffset + event.getEventLen(),
|
if (logger.isDebugEnabled()) {
|
||||||
logposTimestamp);
|
|
||||||
logger.debug("set {} to be pending start position before finding another proper one...",
|
logger.debug("set {} to be pending start position before finding another proper one...",
|
||||||
entryPosition);
|
entryPosition);
|
||||||
|
}
|
||||||
logPosition.setPostion(entryPosition);
|
logPosition.setPostion(entryPosition);
|
||||||
} else if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())) {
|
} else if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())) {
|
||||||
// 当前事务开始位点
|
// 当前事务开始位点
|
||||||
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp);
|
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp, serverId);
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("set {} to be pending start position before finding another proper one...",
|
logger.debug("set {} to be pending start position before finding another proper one...",
|
||||||
entryPosition);
|
entryPosition);
|
||||||
|
}
|
||||||
logPosition.setPostion(entryPosition);
|
logPosition.setPostion(entryPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastPosition = buildLastPosition(entry);
|
lastPosition = buildLastPosition(entry);
|
||||||
} catch (Exception e) {
|
} catch (Throwable e) {
|
||||||
processError(e, lastPosition, searchBinlogFile, 4L);
|
processSinkError(e, lastPosition, searchBinlogFile, 4L);
|
||||||
}
|
}
|
||||||
|
|
||||||
return running;
|
return running;
|
||||||
@@ -700,21 +795,17 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Entry parseAndProfilingIfNecessary(LogEvent bod) throws Exception {
|
@Override
|
||||||
long startTs = -1;
|
protected void processDumpError(Throwable e) {
|
||||||
boolean enabled = getProfilingEnabled();
|
if (e instanceof IOException) {
|
||||||
if (enabled) {
|
String message = e.getMessage();
|
||||||
startTs = System.currentTimeMillis();
|
if (StringUtils.contains(message, "errno = 1236")) {
|
||||||
|
// 1236 errorCode代表ER_MASTER_FATAL_ERROR_READING_BINLOG
|
||||||
|
dumpErrorCount++;
|
||||||
}
|
}
|
||||||
CanalEntry.Entry event = binlogParser.parse(bod);
|
|
||||||
if (enabled) {
|
|
||||||
this.parsingInterval = System.currentTimeMillis() - startTs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedEventCount.incrementAndGet() < 0) {
|
super.processDumpError(e);
|
||||||
parsedEventCount.set(0);
|
|
||||||
}
|
|
||||||
return event;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSupportBinlogFormats(String formatStrs) {
|
public void setSupportBinlogFormats(String formatStrs) {
|
||||||
@@ -800,4 +891,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
public void setHaController(CanalHAController haController) {
|
public void setHaController(CanalHAController haController) {
|
||||||
this.haController = haController;
|
this.haController = haController;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDumpErrorCountThreshold(int dumpErrorCountThreshold) {
|
||||||
|
this.dumpErrorCountThreshold = dumpErrorCountThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ package com.alibaba.otter.canal.parse.inbound.mysql.dbsync;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.ClosedByInterruptException;
|
import java.nio.channels.ClosedByInterruptException;
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogFetcher;
|
import com.taobao.tddl.dbsync.binlog.LogFetcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,6 +21,11 @@ public class DirectLogFetcher extends LogFetcher {
|
|||||||
|
|
||||||
protected static final Logger logger = LoggerFactory.getLogger(DirectLogFetcher.class);
|
protected static final Logger logger = LoggerFactory.getLogger(DirectLogFetcher.class);
|
||||||
|
|
||||||
|
// Master heartbeat interval
|
||||||
|
public static final int MASTER_HEARTBEAT_PERIOD_SECONDS = 15;
|
||||||
|
// +10s 确保 timeout > heartbeat interval
|
||||||
|
private static final int READ_TIMEOUT_MILLISECONDS = (MASTER_HEARTBEAT_PERIOD_SECONDS + 10) * 1000;
|
||||||
|
|
||||||
/** Command to dump binlog */
|
/** Command to dump binlog */
|
||||||
public static final byte COM_BINLOG_DUMP = 18;
|
public static final byte COM_BINLOG_DUMP = 18;
|
||||||
|
|
||||||
@@ -38,6 +42,8 @@ public class DirectLogFetcher extends LogFetcher {
|
|||||||
|
|
||||||
private SocketChannel channel;
|
private SocketChannel channel;
|
||||||
|
|
||||||
|
private boolean issemi = false;
|
||||||
|
|
||||||
// private BufferedInputStream input;
|
// private BufferedInputStream input;
|
||||||
|
|
||||||
public DirectLogFetcher(){
|
public DirectLogFetcher(){
|
||||||
@@ -54,6 +60,10 @@ public class DirectLogFetcher extends LogFetcher {
|
|||||||
|
|
||||||
public void start(SocketChannel channel) throws IOException {
|
public void start(SocketChannel channel) throws IOException {
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
|
String dbsemi = System.getProperty("db.semi");
|
||||||
|
if ("1".equals(dbsemi)) {
|
||||||
|
issemi = true;
|
||||||
|
}
|
||||||
// 和mysql driver一样,提供buffer机制,提升读取binlog速度
|
// 和mysql driver一样,提供buffer机制,提升读取binlog速度
|
||||||
// this.input = new
|
// this.input = new
|
||||||
// BufferedInputStream(channel.socket().getInputStream(), 16384);
|
// BufferedInputStream(channel.socket().getInputStream(), 16384);
|
||||||
@@ -107,6 +117,14 @@ public class DirectLogFetcher extends LogFetcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if mysql is in semi mode
|
||||||
|
if (issemi) {
|
||||||
|
// parse semi mark
|
||||||
|
int semimark = getUint8(NET_HEADER_SIZE + 1);
|
||||||
|
int semival = getUint8(NET_HEADER_SIZE + 2);
|
||||||
|
this.semival = semival;
|
||||||
|
}
|
||||||
|
|
||||||
// The first packet is a multi-packet, concatenate the packets.
|
// The first packet is a multi-packet, concatenate the packets.
|
||||||
while (netlen == MAX_PACKET_LENGTH) {
|
while (netlen == MAX_PACKET_LENGTH) {
|
||||||
if (!fetch0(0, NET_HEADER_SIZE)) {
|
if (!fetch0(0, NET_HEADER_SIZE)) {
|
||||||
@@ -123,7 +141,11 @@ public class DirectLogFetcher extends LogFetcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Preparing buffer variables to decoding.
|
// Preparing buffer variables to decoding.
|
||||||
|
if (issemi) {
|
||||||
|
origin = NET_HEADER_SIZE + 3;
|
||||||
|
} else {
|
||||||
origin = NET_HEADER_SIZE + 1;
|
origin = NET_HEADER_SIZE + 1;
|
||||||
|
}
|
||||||
position = origin;
|
position = origin;
|
||||||
limit -= origin;
|
limit -= origin;
|
||||||
return true;
|
return true;
|
||||||
@@ -149,20 +171,8 @@ public class DirectLogFetcher extends LogFetcher {
|
|||||||
private final boolean fetch0(final int off, final int len) throws IOException {
|
private final boolean fetch0(final int off, final int len) throws IOException {
|
||||||
ensureCapacity(off + len);
|
ensureCapacity(off + len);
|
||||||
|
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(this.buffer, off, len);
|
byte[] read = channel.read(len, READ_TIMEOUT_MILLISECONDS);
|
||||||
while (buffer.hasRemaining()) {
|
System.arraycopy(read, 0, this.buffer, off, len);
|
||||||
int readNum = channel.read(buffer);
|
|
||||||
if (readNum == -1) {
|
|
||||||
throw new IOException("Unexpected End Stream");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for (int count, n = 0; n < len; n += count) {
|
|
||||||
// if (0 > (count = input.read(buffer, off + n, len - n))) {
|
|
||||||
// // Reached end of input stream
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (limit < off + len) limit = off + len;
|
if (limit < off + len) limit = off + len;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.math.BigDecimal;
|
|||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.sql.Types;
|
import java.sql.Types;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -21,7 +22,9 @@ import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
|
|||||||
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
||||||
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
||||||
import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta;
|
import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.SimpleDdlParser.DdlResult;
|
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.DdlResult;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.DruidDdlParser;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.SimpleDdlParser;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Column;
|
import com.alibaba.otter.canal.protocol.CanalEntry.Column;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
|
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
|
||||||
@@ -33,6 +36,7 @@ import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
|
|||||||
import com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin;
|
import com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd;
|
import com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Type;
|
import com.alibaba.otter.canal.protocol.CanalEntry.Type;
|
||||||
|
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent;
|
||||||
@@ -84,8 +88,12 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
private boolean filterQueryDdl = false;
|
private boolean filterQueryDdl = false;
|
||||||
// 是否跳过table相关的解析异常,比如表不存在或者列数量不匹配,issue 92
|
// 是否跳过table相关的解析异常,比如表不存在或者列数量不匹配,issue 92
|
||||||
private boolean filterTableError = false;
|
private boolean filterTableError = false;
|
||||||
|
// 新增rows过滤,用于仅订阅除rows以外的数据
|
||||||
|
private boolean filterRows = false;
|
||||||
|
private boolean useDruidDdlFilter = true;
|
||||||
|
|
||||||
public Entry parse(LogEvent logEvent) throws CanalParseException {
|
@Override
|
||||||
|
public Entry parse(LogEvent logEvent, boolean isSeek) throws CanalParseException {
|
||||||
if (logEvent == null || logEvent instanceof UnknownLogEvent) {
|
if (logEvent == null || logEvent instanceof UnknownLogEvent) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -96,7 +104,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
binlogFileName = ((RotateLogEvent) logEvent).getFilename();
|
binlogFileName = ((RotateLogEvent) logEvent).getFilename();
|
||||||
break;
|
break;
|
||||||
case LogEvent.QUERY_EVENT:
|
case LogEvent.QUERY_EVENT:
|
||||||
return parseQueryEvent((QueryLogEvent) logEvent);
|
return parseQueryEvent((QueryLogEvent) logEvent, isSeek);
|
||||||
case LogEvent.XID_EVENT:
|
case LogEvent.XID_EVENT:
|
||||||
return parseXidEvent((XidLogEvent) logEvent);
|
return parseXidEvent((XidLogEvent) logEvent);
|
||||||
case LogEvent.TABLE_MAP_EVENT:
|
case LogEvent.TABLE_MAP_EVENT:
|
||||||
@@ -135,7 +143,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Entry parseQueryEvent(QueryLogEvent event) {
|
private Entry parseQueryEvent(QueryLogEvent event, boolean isSeek) {
|
||||||
String queryString = event.getQuery();
|
String queryString = event.getQuery();
|
||||||
if (StringUtils.endsWithIgnoreCase(queryString, BEGIN)) {
|
if (StringUtils.endsWithIgnoreCase(queryString, BEGIN)) {
|
||||||
TransactionBegin transactionBegin = createTransactionBegin(event.getSessionId());
|
TransactionBegin transactionBegin = createTransactionBegin(event.getSessionId());
|
||||||
@@ -146,16 +154,81 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
Header header = createHeader(binlogFileName, event.getHeader(), "", "", null);
|
Header header = createHeader(binlogFileName, event.getHeader(), "", "", null);
|
||||||
return createEntry(header, EntryType.TRANSACTIONEND, transactionEnd.toByteString());
|
return createEntry(header, EntryType.TRANSACTIONEND, transactionEnd.toByteString());
|
||||||
} else {
|
} else {
|
||||||
// DDL语句处理
|
boolean notFilter = false;
|
||||||
|
EventType type = EventType.QUERY;
|
||||||
|
String tableName = null;
|
||||||
|
String schemaName = null;
|
||||||
|
if (useDruidDdlFilter) {
|
||||||
|
List<DdlResult> results = DruidDdlParser.parse(queryString, event.getDbName());
|
||||||
|
for (DdlResult result : results) {
|
||||||
|
if (!processFilter(queryString, result)) {
|
||||||
|
// 只要有一个数据不进行过滤
|
||||||
|
notFilter = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (results.size() > 0) {
|
||||||
|
// 如果针对多行的DDL,只能取第一条
|
||||||
|
type = results.get(0).getType();
|
||||||
|
schemaName = results.get(0).getSchemaName();
|
||||||
|
tableName = results.get(0).getTableName();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
DdlResult result = SimpleDdlParser.parse(queryString, event.getDbName());
|
DdlResult result = SimpleDdlParser.parse(queryString, event.getDbName());
|
||||||
|
if (!processFilter(queryString, result)) {
|
||||||
String schemaName = event.getDbName();
|
notFilter = true;
|
||||||
if (StringUtils.isNotEmpty(result.getSchemaName())) {
|
|
||||||
schemaName = result.getSchemaName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type = result.getType();
|
||||||
|
schemaName = result.getSchemaName();
|
||||||
|
tableName = result.getTableName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!notFilter) {
|
||||||
|
// 如果是过滤的数据就不处理了
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSeek) {
|
||||||
|
// 使用新的表结构元数据管理方式
|
||||||
|
EntryPosition position = createPosition(event.getHeader());
|
||||||
|
tableMetaCache.apply(position, event.getDbName(), queryString, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Header header = createHeader(binlogFileName, event.getHeader(), schemaName, tableName, type);
|
||||||
|
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||||
|
if (type != EventType.QUERY) {
|
||||||
|
rowChangeBuider.setIsDdl(true);
|
||||||
|
}
|
||||||
|
rowChangeBuider.setSql(queryString);
|
||||||
|
if (StringUtils.isNotEmpty(event.getDbName())) {// 可能为空
|
||||||
|
rowChangeBuider.setDdlSchemaName(event.getDbName());
|
||||||
|
}
|
||||||
|
rowChangeBuider.setEventType(type);
|
||||||
|
return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean processFilter(String queryString, DdlResult result) {
|
||||||
|
String schemaName = result.getSchemaName();
|
||||||
String tableName = result.getTableName();
|
String tableName = result.getTableName();
|
||||||
EventType type = EventType.QUERY;
|
// fixed issue https://github.com/alibaba/canal/issues/58
|
||||||
|
// 更新下table meta cache
|
||||||
|
if (tableMetaCache != null
|
||||||
|
&& (result.getType() == EventType.ALTER || result.getType() == EventType.ERASE || result.getType() == EventType.RENAME)) {
|
||||||
|
// 对外返回,保证兼容,还是返回QUERY类型,这里暂不解析tableName,所以无法支持过滤
|
||||||
|
for (DdlResult renameResult = result; renameResult != null; renameResult = renameResult.getRenameTableResult()) {
|
||||||
|
String schemaName0 = renameResult.getSchemaName();
|
||||||
|
String tableName0 = renameResult.getTableName();
|
||||||
|
if (StringUtils.isNotEmpty(tableName0)) {
|
||||||
|
// 如果解析到了正确的表信息,则根据全名进行清除
|
||||||
|
tableMetaCache.clearTableMeta(schemaName0, tableName0);
|
||||||
|
} else {
|
||||||
|
// 如果无法解析正确的表信息,则根据schema进行清除
|
||||||
|
tableMetaCache.clearTableMetaWithSchemaName(schemaName0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// fixed issue https://github.com/alibaba/canal/issues/58
|
// fixed issue https://github.com/alibaba/canal/issues/58
|
||||||
if (result.getType() == EventType.ALTER || result.getType() == EventType.ERASE
|
if (result.getType() == EventType.ALTER || result.getType() == EventType.ERASE
|
||||||
|| result.getType() == EventType.CREATE || result.getType() == EventType.TRUNCATE
|
|| result.getType() == EventType.CREATE || result.getType() == EventType.TRUNCATE
|
||||||
@@ -163,10 +236,9 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
|| result.getType() == EventType.DINDEX) { // 针对DDL类型
|
|| result.getType() == EventType.DINDEX) { // 针对DDL类型
|
||||||
|
|
||||||
if (filterQueryDdl) {
|
if (filterQueryDdl) {
|
||||||
return null;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
type = result.getType();
|
|
||||||
if (StringUtils.isEmpty(tableName)
|
if (StringUtils.isEmpty(tableName)
|
||||||
|| (result.getType() == EventType.RENAME && StringUtils.isEmpty(result.getOriTableName()))) {
|
|| (result.getType() == EventType.RENAME && StringUtils.isEmpty(result.getOriTableName()))) {
|
||||||
// 如果解析不出tableName,记录一下日志,方便bugfix,目前直接抛出异常,中断解析
|
// 如果解析不出tableName,记录一下日志,方便bugfix,目前直接抛出异常,中断解析
|
||||||
@@ -181,11 +253,11 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
// rename校验只要源和目标满足一个就进行操作
|
// rename校验只要源和目标满足一个就进行操作
|
||||||
if (nameFilter != null
|
if (nameFilter != null
|
||||||
&& !nameFilter.filter(result.getOriSchemaName() + "." + result.getOriTableName())) {
|
&& !nameFilter.filter(result.getOriSchemaName() + "." + result.getOriTableName())) {
|
||||||
return null;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 其他情况返回null
|
// 其他情况返回null
|
||||||
return null;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,11 +266,11 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
// rename校验只要源和目标满足一个就进行操作
|
// rename校验只要源和目标满足一个就进行操作
|
||||||
if (nameBlackFilter != null
|
if (nameBlackFilter != null
|
||||||
&& nameBlackFilter.filter(result.getOriSchemaName() + "." + result.getOriTableName())) {
|
&& nameBlackFilter.filter(result.getOriSchemaName() + "." + result.getOriTableName())) {
|
||||||
return null;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 其他情况返回null
|
// 其他情况返回null
|
||||||
return null;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,44 +278,13 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
|| result.getType() == EventType.DELETE) {
|
|| result.getType() == EventType.DELETE) {
|
||||||
// 对外返回,保证兼容,还是返回QUERY类型,这里暂不解析tableName,所以无法支持过滤
|
// 对外返回,保证兼容,还是返回QUERY类型,这里暂不解析tableName,所以无法支持过滤
|
||||||
if (filterQueryDml) {
|
if (filterQueryDml) {
|
||||||
return null;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (filterQueryDcl) {
|
} else if (filterQueryDcl) {
|
||||||
return null;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新下table meta cache
|
return false;
|
||||||
if (tableMetaCache != null
|
|
||||||
&& (result.getType() == EventType.ALTER || result.getType() == EventType.ERASE || result.getType() == EventType.RENAME)) {
|
|
||||||
for (DdlResult renameResult = result; renameResult != null; renameResult = renameResult.getRenameTableResult()) {
|
|
||||||
String schemaName0 = event.getDbName(); // 防止rename语句后产生schema变更带来影响
|
|
||||||
if (StringUtils.isNotEmpty(renameResult.getSchemaName())) {
|
|
||||||
schemaName0 = renameResult.getSchemaName();
|
|
||||||
}
|
|
||||||
|
|
||||||
tableName = renameResult.getTableName();
|
|
||||||
if (StringUtils.isNotEmpty(tableName)) {
|
|
||||||
// 如果解析到了正确的表信息,则根据全名进行清除
|
|
||||||
tableMetaCache.clearTableMeta(schemaName0, tableName);
|
|
||||||
} else {
|
|
||||||
// 如果无法解析正确的表信息,则根据schema进行清除
|
|
||||||
tableMetaCache.clearTableMetaWithSchemaName(schemaName0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Header header = createHeader(binlogFileName, event.getHeader(), schemaName, tableName, type);
|
|
||||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
|
||||||
if (result.getType() != EventType.QUERY) {
|
|
||||||
rowChangeBuider.setIsDdl(true);
|
|
||||||
}
|
|
||||||
rowChangeBuider.setSql(queryString);
|
|
||||||
if (StringUtils.isNotEmpty(event.getDbName())) {// 可能为空
|
|
||||||
rowChangeBuider.setDdlSchemaName(event.getDbName());
|
|
||||||
}
|
|
||||||
rowChangeBuider.setEventType(result.getType());
|
|
||||||
return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Entry parseRowsQueryEvent(RowsQueryLogEvent event) {
|
private Entry parseRowsQueryEvent(RowsQueryLogEvent event) {
|
||||||
@@ -254,7 +295,15 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
String queryString = null;
|
String queryString = null;
|
||||||
try {
|
try {
|
||||||
queryString = new String(event.getRowsQuery().getBytes(ISO_8859_1), charset.name());
|
queryString = new String(event.getRowsQuery().getBytes(ISO_8859_1), charset.name());
|
||||||
return buildQueryEntry(queryString, event.getHeader());
|
String tableName = null;
|
||||||
|
if (useDruidDdlFilter) {
|
||||||
|
List<DdlResult> results = DruidDdlParser.parse(queryString, null);
|
||||||
|
if (results.size() > 0) {
|
||||||
|
tableName = results.get(0).getTableName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildQueryEntry(queryString, event.getHeader(), tableName);
|
||||||
} catch (UnsupportedEncodingException e) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
throw new CanalParseException(e);
|
throw new CanalParseException(e);
|
||||||
}
|
}
|
||||||
@@ -305,6 +354,9 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Entry parseRowsEvent(RowsLogEvent event) {
|
private Entry parseRowsEvent(RowsLogEvent event) {
|
||||||
|
if (filterRows) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
TableMapLogEvent table = event.getTable();
|
TableMapLogEvent table = event.getTable();
|
||||||
if (table == null) {
|
if (table == null) {
|
||||||
@@ -312,6 +364,10 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
throw new TableIdNotFoundException("not found tableId:" + event.getTableId());
|
throw new TableIdNotFoundException("not found tableId:" + event.getTableId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isHeartBeat = isAliSQLHeartBeat(table.getDbName(), table.getTableName());
|
||||||
|
boolean isRDSHeartBeat = tableMetaCache.isOnRDS()
|
||||||
|
&& isRDSHeartBeat(table.getDbName(), table.getTableName());
|
||||||
|
|
||||||
String fullname = table.getDbName() + "." + table.getTableName();
|
String fullname = table.getDbName() + "." + table.getTableName();
|
||||||
// check name filter
|
// check name filter
|
||||||
if (nameFilter != null && !nameFilter.filter(fullname)) {
|
if (nameFilter != null && !nameFilter.filter(fullname)) {
|
||||||
@@ -321,6 +377,25 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if (isHeartBeat || isRDSHeartBeat) {
|
||||||
|
// // 忽略rds模式的mysql.ha_health_check心跳数据
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
TableMeta tableMeta = null;
|
||||||
|
if (isRDSHeartBeat) {
|
||||||
|
// 处理rds模式的mysql.ha_health_check心跳数据
|
||||||
|
// 主要RDS的心跳表基本无权限,需要mock一个tableMeta
|
||||||
|
FieldMeta idMeta = new FieldMeta("id", "bigint(20)", true, false, "0");
|
||||||
|
FieldMeta typeMeta = new FieldMeta("type", "char(1)", false, true, "0");
|
||||||
|
tableMeta = new TableMeta(table.getDbName(), table.getTableName(), Arrays.asList(idMeta, typeMeta));
|
||||||
|
} else if (isHeartBeat) {
|
||||||
|
// 处理alisql模式的test.heartbeat心跳数据
|
||||||
|
// 心跳表基本无权限,需要mock一个tableMeta
|
||||||
|
FieldMeta idMeta = new FieldMeta("id", "smallint(6)", false, true, null);
|
||||||
|
FieldMeta typeMeta = new FieldMeta("type", "int(11)", true, false, null);
|
||||||
|
tableMeta = new TableMeta(table.getDbName(), table.getTableName(), Arrays.asList(idMeta, typeMeta));
|
||||||
|
}
|
||||||
|
|
||||||
EventType eventType = null;
|
EventType eventType = null;
|
||||||
int type = event.getHeader().getType();
|
int type = event.getHeader().getType();
|
||||||
if (LogEvent.WRITE_ROWS_EVENT_V1 == type || LogEvent.WRITE_ROWS_EVENT == type) {
|
if (LogEvent.WRITE_ROWS_EVENT_V1 == type || LogEvent.WRITE_ROWS_EVENT == type) {
|
||||||
@@ -338,6 +413,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
table.getDbName(),
|
table.getDbName(),
|
||||||
table.getTableName(),
|
table.getTableName(),
|
||||||
eventType);
|
eventType);
|
||||||
|
EntryPosition position = createPosition(event.getHeader());
|
||||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||||
rowChangeBuider.setTableId(event.getTableId());
|
rowChangeBuider.setTableId(event.getTableId());
|
||||||
rowChangeBuider.setIsDdl(false);
|
rowChangeBuider.setIsDdl(false);
|
||||||
@@ -345,11 +421,11 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
rowChangeBuider.setEventType(eventType);
|
rowChangeBuider.setEventType(eventType);
|
||||||
RowsLogBuffer buffer = event.getRowsBuf(charset.name());
|
RowsLogBuffer buffer = event.getRowsBuf(charset.name());
|
||||||
BitSet columns = event.getColumns();
|
BitSet columns = event.getColumns();
|
||||||
BitSet changeColumns = event.getColumns();
|
BitSet changeColumns = event.getChangeColumns();
|
||||||
boolean tableError = false;
|
boolean tableError = false;
|
||||||
TableMeta tableMeta = null;
|
if (tableMetaCache != null && tableMeta == null) {// 入错存在table meta
|
||||||
if (tableMetaCache != null) {// 入错存在table meta cache
|
// cache
|
||||||
tableMeta = getTableMeta(table.getDbName(), table.getTableName(), true);
|
tableMeta = getTableMeta(table.getDbName(), table.getTableName(), true, position);
|
||||||
if (tableMeta == null) {
|
if (tableMeta == null) {
|
||||||
tableError = true;
|
tableError = true;
|
||||||
if (!filterTableError) {
|
if (!filterTableError) {
|
||||||
@@ -375,7 +451,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
tableError |= parseOneRow(rowDataBuilder, event, buffer, event.getChangeColumns(), true, tableMeta);
|
tableError |= parseOneRow(rowDataBuilder, event, buffer, changeColumns, true, tableMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
rowChangeBuider.addRowDatas(rowDataBuilder.build());
|
rowChangeBuider.addRowDatas(rowDataBuilder.build());
|
||||||
@@ -395,21 +471,42 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EntryPosition createPosition(LogHeader logHeader) {
|
||||||
|
return new EntryPosition(binlogFileName,
|
||||||
|
logHeader.getLogPos(),
|
||||||
|
logHeader.getWhen() * 1000L,
|
||||||
|
logHeader.getServerId()); // 记录到秒
|
||||||
|
}
|
||||||
|
|
||||||
private boolean parseOneRow(RowData.Builder rowDataBuilder, RowsLogEvent event, RowsLogBuffer buffer, BitSet cols,
|
private boolean parseOneRow(RowData.Builder rowDataBuilder, RowsLogEvent event, RowsLogBuffer buffer, BitSet cols,
|
||||||
boolean isAfter, TableMeta tableMeta) throws UnsupportedEncodingException {
|
boolean isAfter, TableMeta tableMeta) throws UnsupportedEncodingException {
|
||||||
final int columnCnt = event.getTable().getColumnCnt();
|
int columnCnt = event.getTable().getColumnCnt();
|
||||||
final ColumnInfo[] columnInfo = event.getTable().getColumnInfo();
|
ColumnInfo[] columnInfo = event.getTable().getColumnInfo();
|
||||||
|
|
||||||
boolean tableError = false;
|
boolean tableError = false;
|
||||||
// check table fileds count,只能处理加字段
|
// check table fileds count,只能处理加字段
|
||||||
if (tableMeta != null && columnInfo.length > tableMeta.getFileds().size()) {
|
boolean existRDSNoPrimaryKey = false;
|
||||||
|
if (tableMeta != null && columnInfo.length > tableMeta.getFields().size()) {
|
||||||
|
if (tableMetaCache.isOnRDS()) {
|
||||||
|
// 特殊处理下RDS的场景
|
||||||
|
List<FieldMeta> primaryKeys = tableMeta.getPrimaryFields();
|
||||||
|
if (primaryKeys == null || primaryKeys.isEmpty()) {
|
||||||
|
if (columnInfo.length == tableMeta.getFields().size() + 1
|
||||||
|
&& columnInfo[columnInfo.length - 1].type == LogEvent.MYSQL_TYPE_LONGLONG) {
|
||||||
|
existRDSNoPrimaryKey = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EntryPosition position = createPosition(event.getHeader());
|
||||||
|
if (!existRDSNoPrimaryKey) {
|
||||||
// online ddl增加字段操作步骤:
|
// online ddl增加字段操作步骤:
|
||||||
// 1. 新增一张临时表,将需要做ddl表的数据全量导入
|
// 1. 新增一张临时表,将需要做ddl表的数据全量导入
|
||||||
// 2. 在老表上建立I/U/D的trigger,增量的将数据插入到临时表
|
// 2. 在老表上建立I/U/D的trigger,增量的将数据插入到临时表
|
||||||
// 3. 锁住应用请求,将临时表rename为老表的名字,完成增加字段的操作
|
// 3. 锁住应用请求,将临时表rename为老表的名字,完成增加字段的操作
|
||||||
// 尝试做一次reload,可能因为ddl没有正确解析,或者使用了类似online ddl的操作
|
// 尝试做一次reload,可能因为ddl没有正确解析,或者使用了类似online ddl的操作
|
||||||
// 因为online ddl没有对应表名的alter语法,所以不会有clear cache的操作
|
// 因为online ddl没有对应表名的alter语法,所以不会有clear cache的操作
|
||||||
tableMeta = getTableMeta(event.getTable().getDbName(), event.getTable().getTableName(), false);// 强制重新获取一次
|
tableMeta = getTableMeta(event.getTable().getDbName(), event.getTable().getTableName(), false, position);// 强制重新获取一次
|
||||||
if (tableMeta == null) {
|
if (tableMeta == null) {
|
||||||
tableError = true;
|
tableError = true;
|
||||||
if (!filterTableError) {
|
if (!filterTableError) {
|
||||||
@@ -419,13 +516,17 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 在做一次判断
|
// 在做一次判断
|
||||||
if (tableMeta != null && columnInfo.length > tableMeta.getFileds().size()) {
|
if (tableMeta != null && columnInfo.length > tableMeta.getFields().size()) {
|
||||||
tableError = true;
|
tableError = true;
|
||||||
if (!filterTableError) {
|
if (!filterTableError) {
|
||||||
throw new CanalParseException("column size is not match for table:" + tableMeta.getFullName() + ","
|
throw new CanalParseException("column size is not match for table:" + tableMeta.getFullName()
|
||||||
+ columnInfo.length + " vs " + tableMeta.getFileds().size());
|
+ "," + columnInfo.length + " vs " + tableMeta.getFields().size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn("[" + event.getTable().getDbName() + "." + event.getTable().getTableName()
|
||||||
|
+ "] is no primary key , skip alibaba_rds_row_id column");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < columnCnt; i++) {
|
for (int i = 0; i < columnCnt; i++) {
|
||||||
@@ -435,12 +536,18 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (existRDSNoPrimaryKey && i == columnCnt - 1 && info.type == LogEvent.MYSQL_TYPE_LONGLONG) {
|
||||||
|
// 不解析最后一列
|
||||||
|
buffer.nextValue(info.type, info.meta, false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
Column.Builder columnBuilder = Column.newBuilder();
|
Column.Builder columnBuilder = Column.newBuilder();
|
||||||
|
|
||||||
FieldMeta fieldMeta = null;
|
FieldMeta fieldMeta = null;
|
||||||
if (tableMeta != null && !tableError) {
|
if (tableMeta != null && !tableError) {
|
||||||
// 处理file meta
|
// 处理file meta
|
||||||
fieldMeta = tableMeta.getFileds().get(i);
|
fieldMeta = tableMeta.getFields().get(i);
|
||||||
columnBuilder.setName(fieldMeta.getColumnName());
|
columnBuilder.setName(fieldMeta.getColumnName());
|
||||||
columnBuilder.setIsKey(fieldMeta.isKey());
|
columnBuilder.setIsKey(fieldMeta.isKey());
|
||||||
// 增加mysql type类型,issue 73
|
// 增加mysql type类型,issue 73
|
||||||
@@ -460,6 +567,10 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
buffer.nextValue(info.type, info.meta, isBinary);
|
buffer.nextValue(info.type, info.meta, isBinary);
|
||||||
|
if (existRDSNoPrimaryKey && i == columnCnt - 1 && info.type == LogEvent.MYSQL_TYPE_LONGLONG) {
|
||||||
|
// 不解析最后一列
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
int javaType = buffer.getJavaType();
|
int javaType = buffer.getJavaType();
|
||||||
if (buffer.isNull()) {
|
if (buffer.isNull()) {
|
||||||
@@ -577,6 +688,14 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Entry buildQueryEntry(String queryString, LogHeader logHeader, String tableName) {
|
||||||
|
Header header = createHeader(binlogFileName, logHeader, "", tableName, EventType.QUERY);
|
||||||
|
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||||
|
rowChangeBuider.setSql(queryString);
|
||||||
|
rowChangeBuider.setEventType(EventType.QUERY);
|
||||||
|
return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
|
||||||
|
}
|
||||||
|
|
||||||
private Entry buildQueryEntry(String queryString, LogHeader logHeader) {
|
private Entry buildQueryEntry(String queryString, LogHeader logHeader) {
|
||||||
Header header = createHeader(binlogFileName, logHeader, "", "", EventType.QUERY);
|
Header header = createHeader(binlogFileName, logHeader, "", "", EventType.QUERY);
|
||||||
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
|
||||||
@@ -635,9 +754,9 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TableMeta getTableMeta(String dbName, String tbName, boolean useCache) {
|
private TableMeta getTableMeta(String dbName, String tbName, boolean useCache, EntryPosition position) {
|
||||||
try {
|
try {
|
||||||
return tableMetaCache.getTableMeta(dbName, tbName, useCache);
|
return tableMetaCache.getTableMeta(dbName, tbName, useCache, position);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String message = ExceptionUtils.getRootCauseMessage(e);
|
String message = ExceptionUtils.getRootCauseMessage(e);
|
||||||
if (filterTableError) {
|
if (filterTableError) {
|
||||||
@@ -655,6 +774,14 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
|| "TEXT".equalsIgnoreCase(columnType) || "TINYTEXT".equalsIgnoreCase(columnType);
|
|| "TEXT".equalsIgnoreCase(columnType) || "TINYTEXT".equalsIgnoreCase(columnType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isAliSQLHeartBeat(String schema, String table) {
|
||||||
|
return "test".equalsIgnoreCase(schema) && "heartbeat".equalsIgnoreCase(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRDSHeartBeat(String schema, String table) {
|
||||||
|
return "mysql".equalsIgnoreCase(schema) && "ha_health_check".equalsIgnoreCase(table);
|
||||||
|
}
|
||||||
|
|
||||||
public static TransactionBegin createTransactionBegin(long threadId) {
|
public static TransactionBegin createTransactionBegin(long threadId) {
|
||||||
TransactionBegin.Builder beginBuilder = TransactionBegin.newBuilder();
|
TransactionBegin.Builder beginBuilder = TransactionBegin.newBuilder();
|
||||||
beginBuilder.setThreadId(threadId);
|
beginBuilder.setThreadId(threadId);
|
||||||
@@ -714,4 +841,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|
|||||||
this.filterTableError = filterTableError;
|
this.filterTableError = filterTableError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFilterRows(boolean filterRows) {
|
||||||
|
this.filterRows = filterRows;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,14 @@ import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
|||||||
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
||||||
import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta;
|
import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection;
|
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection;
|
||||||
import com.google.common.base.Function;
|
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.DruidDdlParser;
|
||||||
import com.google.common.collect.MigrateMap;
|
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.MemoryTableMeta;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDB;
|
||||||
|
import com.alibaba.otter.canal.protocol.position.EntryPosition;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理table meta解析和缓存
|
* 处理table meta解析和缓存
|
||||||
@@ -32,22 +38,28 @@ public class TableMetaCache {
|
|||||||
public static final String COLUMN_DEFAULT = "COLUMN_DEFAULT";
|
public static final String COLUMN_DEFAULT = "COLUMN_DEFAULT";
|
||||||
public static final String EXTRA = "EXTRA";
|
public static final String EXTRA = "EXTRA";
|
||||||
private MysqlConnection connection;
|
private MysqlConnection connection;
|
||||||
|
private boolean isOnRDS = false;
|
||||||
|
|
||||||
|
private TableMetaTSDB tableMetaTSDB;
|
||||||
// 第一层tableId,第二层schema.table,解决tableId重复,对应多张表
|
// 第一层tableId,第二层schema.table,解决tableId重复,对应多张表
|
||||||
private Map<String, TableMeta> tableMetaCache;
|
private LoadingCache<String, TableMeta> tableMetaDB;
|
||||||
|
|
||||||
public TableMetaCache(MysqlConnection con){
|
public TableMetaCache(MysqlConnection con, TableMetaTSDB tableMetaTSDB){
|
||||||
this.connection = con;
|
this.connection = con;
|
||||||
tableMetaCache = MigrateMap.makeComputingMap(new Function<String, TableMeta>() {
|
this.tableMetaTSDB = tableMetaTSDB;
|
||||||
|
// 如果持久存储的表结构为空,从db里面获取下
|
||||||
|
if (tableMetaTSDB == null) {
|
||||||
|
this.tableMetaDB = CacheBuilder.newBuilder().build(new CacheLoader<String, TableMeta>() {
|
||||||
|
|
||||||
public TableMeta apply(String name) {
|
@Override
|
||||||
|
public TableMeta load(String name) throws Exception {
|
||||||
try {
|
try {
|
||||||
return getTableMeta0(name);
|
return getTableMetaByDB(name);
|
||||||
} catch (IOException e) {
|
} catch (Throwable e) {
|
||||||
// 尝试做一次retry操作
|
// 尝试做一次retry操作
|
||||||
try {
|
try {
|
||||||
connection.reconnect();
|
connection.reconnect();
|
||||||
return getTableMeta0(name);
|
return getTableMetaByDB(name);
|
||||||
} catch (IOException e1) {
|
} catch (IOException e1) {
|
||||||
throw new CanalParseException("fetch failed by table meta:" + name, e1);
|
throw new CanalParseException("fetch failed by table meta:" + name, e1);
|
||||||
}
|
}
|
||||||
@@ -55,52 +67,50 @@ public class TableMetaCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TableMeta getTableMeta(String schema, String table) {
|
try {
|
||||||
return getTableMeta(schema, table, true);
|
ResultSetPacket packet = connection.query("show global variables like 'rds\\_%'");
|
||||||
|
if (packet.getFieldValues().size() > 0) {
|
||||||
|
isOnRDS = true;
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
public TableMeta getTableMeta(String schema, String table, boolean useCache) {
|
|
||||||
if (!useCache) {
|
|
||||||
tableMetaCache.remove(getFullName(schema, table));
|
|
||||||
}
|
|
||||||
|
|
||||||
return tableMetaCache.get(getFullName(schema, table));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearTableMeta(String schema, String table) {
|
|
||||||
tableMetaCache.remove(getFullName(schema, table));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearTableMetaWithSchemaName(String schema) {
|
|
||||||
// Set<String> removeNames = new HashSet<String>(); //
|
|
||||||
// 存一份临时变量,避免在遍历的时候进行删除
|
|
||||||
for (String name : tableMetaCache.keySet()) {
|
|
||||||
if (StringUtils.startsWithIgnoreCase(name, schema + ".")) {
|
|
||||||
// removeNames.add(name);
|
|
||||||
tableMetaCache.remove(name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// for (String name : removeNames) {
|
private TableMeta getTableMetaByDB(String fullname) throws IOException {
|
||||||
// tables.remove(name);
|
try {
|
||||||
// }
|
ResultSetPacket packet = connection.query("show create table " + fullname);
|
||||||
}
|
String[] names = StringUtils.split(fullname, "`.`");
|
||||||
|
String schema = names[0];
|
||||||
public void clearTableMeta() {
|
String table = names[1].substring(0, names[1].length());
|
||||||
tableMetaCache.clear();
|
return new TableMeta(schema, table, parseTableMeta(schema, table, packet));
|
||||||
}
|
} catch (Throwable e) { // fallback to desc table
|
||||||
|
|
||||||
private TableMeta getTableMeta0(String fullname) throws IOException {
|
|
||||||
ResultSetPacket packet = connection.query("desc " + fullname);
|
ResultSetPacket packet = connection.query("desc " + fullname);
|
||||||
return new TableMeta(fullname, parserTableMeta(packet));
|
String[] names = StringUtils.split(fullname, "`.`");
|
||||||
|
String schema = names[0];
|
||||||
|
String table = names[1].substring(0, names[1].length());
|
||||||
|
return new TableMeta(schema, table, parseTableMetaByDesc(packet));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<FieldMeta> parserTableMeta(ResultSetPacket packet) {
|
public static List<FieldMeta> parseTableMeta(String schema, String table, ResultSetPacket packet) {
|
||||||
Map<String, Integer> nameMaps = new HashMap<String, Integer>(6, 1f);
|
if (packet.getFieldValues().size() > 1) {
|
||||||
|
String createDDL = packet.getFieldValues().get(1);
|
||||||
|
MemoryTableMeta memoryTableMeta = new MemoryTableMeta();
|
||||||
|
memoryTableMeta.apply(DatabaseTableMeta.INIT_POSITION, schema, createDDL, null);
|
||||||
|
TableMeta tableMeta = memoryTableMeta.find(schema, table);
|
||||||
|
return tableMeta.getFields();
|
||||||
|
} else {
|
||||||
|
return new ArrayList<FieldMeta>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理desc table的结果
|
||||||
|
*/
|
||||||
|
public static List<FieldMeta> parseTableMetaByDesc(ResultSetPacket packet) {
|
||||||
|
Map<String, Integer> nameMaps = new HashMap<String, Integer>(6, 1f);
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (FieldPacket fieldPacket : packet.getFieldDescriptors()) {
|
for (FieldPacket fieldPacket : packet.getFieldDescriptors()) {
|
||||||
nameMaps.put(fieldPacket.getOriginalName(), index++);
|
nameMaps.put(fieldPacket.getOriginalName(), index++);
|
||||||
@@ -114,9 +124,14 @@ public class TableMetaCache {
|
|||||||
// 做一个优化,使用String.intern(),共享String对象,减少内存使用
|
// 做一个优化,使用String.intern(),共享String对象,减少内存使用
|
||||||
meta.setColumnName(packet.getFieldValues().get(nameMaps.get(COLUMN_NAME) + i * size).intern());
|
meta.setColumnName(packet.getFieldValues().get(nameMaps.get(COLUMN_NAME) + i * size).intern());
|
||||||
meta.setColumnType(packet.getFieldValues().get(nameMaps.get(COLUMN_TYPE) + i * size));
|
meta.setColumnType(packet.getFieldValues().get(nameMaps.get(COLUMN_TYPE) + i * size));
|
||||||
meta.setIsNullable(packet.getFieldValues().get(nameMaps.get(IS_NULLABLE) + i * size));
|
meta.setNullable(StringUtils.equalsIgnoreCase(packet.getFieldValues().get(nameMaps.get(IS_NULLABLE) + i
|
||||||
meta.setIskey(packet.getFieldValues().get(nameMaps.get(COLUMN_KEY) + i * size));
|
* size),
|
||||||
meta.setDefaultValue(packet.getFieldValues().get(nameMaps.get(COLUMN_DEFAULT) + i * size));
|
"YES"));
|
||||||
|
meta.setKey("PRI".equalsIgnoreCase(packet.getFieldValues().get(nameMaps.get(COLUMN_KEY) + i * size)));
|
||||||
|
meta.setUnique("UNI".equalsIgnoreCase(packet.getFieldValues().get(nameMaps.get(COLUMN_KEY) + i * size)));
|
||||||
|
// 特殊处理引号
|
||||||
|
meta.setDefaultValue(DruidDdlParser.unescapeQuotaName(packet.getFieldValues()
|
||||||
|
.get(nameMaps.get(COLUMN_DEFAULT) + i * size)));
|
||||||
meta.setExtra(packet.getFieldValues().get(nameMaps.get(EXTRA) + i * size));
|
meta.setExtra(packet.getFieldValues().get(nameMaps.get(EXTRA) + i * size));
|
||||||
|
|
||||||
result.add(meta);
|
result.add(meta);
|
||||||
@@ -125,6 +140,98 @@ public class TableMetaCache {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TableMeta getTableMeta(String schema, String table) {
|
||||||
|
return getTableMeta(schema, table, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableMeta getTableMeta(String schema, String table, boolean useCache) {
|
||||||
|
if (!useCache) {
|
||||||
|
tableMetaDB.invalidate(getFullName(schema, table));
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableMetaDB.getUnchecked(getFullName(schema, table));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableMeta getTableMeta(String schema, String table, EntryPosition position) {
|
||||||
|
return getTableMeta(schema, table, true, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableMeta getTableMeta(String schema, String table, boolean useCache, EntryPosition position) {
|
||||||
|
TableMeta tableMeta = null;
|
||||||
|
if (tableMetaTSDB != null) {
|
||||||
|
tableMeta = tableMetaTSDB.find(schema, table);
|
||||||
|
if (tableMeta == null) {
|
||||||
|
// 因为条件变化,可能第一次的tableMeta没取到,需要从db获取一次,并记录到snapshot中
|
||||||
|
String fullName = getFullName(schema, table);
|
||||||
|
try {
|
||||||
|
ResultSetPacket packet = connection.query("show create table " + fullName);
|
||||||
|
String createDDL = null;
|
||||||
|
if (packet.getFieldValues().size() > 0) {
|
||||||
|
createDDL = packet.getFieldValues().get(1);
|
||||||
|
}
|
||||||
|
// 强制覆盖掉内存值
|
||||||
|
tableMetaTSDB.apply(position, schema, createDDL, "first");
|
||||||
|
tableMeta = tableMetaTSDB.find(schema, table);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new CanalParseException("fetch failed by table meta:" + fullName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tableMeta;
|
||||||
|
} else {
|
||||||
|
if (!useCache) {
|
||||||
|
tableMetaDB.invalidate(getFullName(schema, table));
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableMetaDB.getUnchecked(getFullName(schema, table));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearTableMeta(String schema, String table) {
|
||||||
|
if (tableMetaTSDB != null) {
|
||||||
|
// tsdb不需要做,会基于ddl sql自动清理
|
||||||
|
} else {
|
||||||
|
tableMetaDB.invalidate(getFullName(schema, table));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearTableMetaWithSchemaName(String schema) {
|
||||||
|
if (tableMetaTSDB != null) {
|
||||||
|
// tsdb不需要做,会基于ddl sql自动清理
|
||||||
|
} else {
|
||||||
|
for (String name : tableMetaDB.asMap().keySet()) {
|
||||||
|
if (StringUtils.startsWithIgnoreCase(name, schema + ".")) {
|
||||||
|
// removeNames.add(name);
|
||||||
|
tableMetaDB.invalidate(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearTableMeta() {
|
||||||
|
if (tableMetaTSDB != null) {
|
||||||
|
// tsdb不需要做,会基于ddl sql自动清理
|
||||||
|
} else {
|
||||||
|
tableMetaDB.invalidateAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新一下本地的表结构内存
|
||||||
|
*
|
||||||
|
* @param position
|
||||||
|
* @param schema
|
||||||
|
* @param ddl
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean apply(EntryPosition position, String schema, String ddl, String extra) {
|
||||||
|
if (tableMetaTSDB != null) {
|
||||||
|
return tableMetaTSDB.apply(position, schema, ddl, extra);
|
||||||
|
} else {
|
||||||
|
// ignore
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String getFullName(String schema, String table) {
|
private String getFullName(String schema, String table) {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
return builder.append('`')
|
return builder.append('`')
|
||||||
@@ -136,4 +243,13 @@ public class TableMetaCache {
|
|||||||
.append('`')
|
.append('`')
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isOnRDS() {
|
||||||
|
return isOnRDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnRDS(boolean isOnRDS) {
|
||||||
|
this.isOnRDS = isOnRDS;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user