mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
Compare commits
250 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 | ||
|
|
8375ea4b7d | ||
|
|
72ceb58fa3 | ||
|
|
44f2b74862 | ||
|
|
5a34195201 | ||
|
|
d976932177 | ||
|
|
d887c8f763 | ||
|
|
197a1446b7 | ||
|
|
155c00b256 | ||
|
|
b16f4844a1 | ||
|
|
f2f2bfb538 | ||
|
|
5f6580399c | ||
|
|
10930b9e3d | ||
|
|
d1afeb7540 | ||
|
|
119af9fca1 | ||
|
|
f0ce4b569e | ||
|
|
4459b8acdf | ||
|
|
bb5d5749bd | ||
|
|
169baafb8b | ||
|
|
199f30de87 | ||
|
|
87c14f0b51 | ||
|
|
f35d965f63 | ||
|
|
9956b3557f |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@ otter.properties
|
||||
jtester.properties
|
||||
.idea/
|
||||
*.iml
|
||||
.DS_Store
|
||||
|
||||
16
README.md
16
README.md
@@ -9,7 +9,7 @@
|
||||
|
||||
<h1>背景</h1>
|
||||
<p style="font-size: 14px;"> 早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&消费的业务,从此开启了一段新纪元。</p>
|
||||
<p style="font-size: 14px;"> ps. 目前内部版本已经支持mysql和oracle部分版本的日志解析,当前的canal开源版本支持5.6及以下的版本(阿里内部mysql 5.6.10, mysql 5.5.18和5.1.40/48)</p>
|
||||
<p style="font-size: 14px;"> ps. 目前内部版本已经支持mysql和oracle部分版本的日志解析,当前的canal开源版本支持5.7及以下的版本(阿里内部mysql 5.7.13, 5.6.10, mysql 5.5.18和5.1.40/48)</p>
|
||||
<p style="font-size: 14px;"> </p>
|
||||
<p style="font-size: 14px;">基于日志增量订阅&消费支持的业务:</p>
|
||||
<ol style="font-size: 14px;">
|
||||
@@ -62,11 +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>
|
||||
</ul>
|
||||
|
||||
<h1>相关资料: </h1>
|
||||
<h1>相关资料</h1>
|
||||
|
||||
* ADC阿里技术嘉年华分享ppt (放在google docs上,可能需要翻墙): <a href="https://docs.google.com/presentation/d/1MkszUPYRDkfVPz9IqOT1LLT5d9tuwde_WC8GZvjaDRg/edit?usp=sharing">ppt下载</href>
|
||||
* [与阿里巴巴的RocketMQ配合使用](https://github.com/alibaba/RocketMQ)
|
||||
|
||||
<h1>相关开源</h1>
|
||||
<ol>
|
||||
<li>阿里巴巴分布式数据库同步系统(解决中美异地机房):<a href="http://github.com/alibaba/otter">http://github.com/alibaba/otter</a></li>
|
||||
<li>阿里巴巴去Oracle数据迁移同步工具(目标支持MySQL/DRDS):<a href="http://github.com/alibaba/yugong">http://github.com/alibaba/yugong</a></li>
|
||||
</ol>
|
||||
|
||||
<h1>问题反馈</h1>
|
||||
<ol>
|
||||
@@ -75,3 +80,10 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
|
||||
<li>新浪微博: agapple0002 </li>
|
||||
<li>报告issue:<a href="https://github.com/alibaba/canal/issues">issues</a></li>
|
||||
</ol>
|
||||
|
||||
<pre>
|
||||
【招聘】阿里巴巴中间件团队招聘JAVA高级工程师
|
||||
岗位主要为技术型内容(非业务部门),阿里中间件整个体系对于未来想在技术上有所沉淀的同学还是非常有帮助的
|
||||
工作地点:杭州、北京均可. ps. 阿里待遇向来都是不错的,有意者可以QQ、微博私聊.
|
||||
具体招聘内容:https://job.alibaba.com/zhaopin/position_detail.htm?positionId=32666
|
||||
</pre>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
@@ -69,7 +69,7 @@
|
||||
<links>
|
||||
<link>https://github.com/alibaba/canal</link>
|
||||
</links>
|
||||
<outputDirectory>${project.build.directory}/apidocs/apidocs/${pom.version}</outputDirectory>
|
||||
<outputDirectory>${project.build.directory}/apidocs/apidocs/${project.version}</outputDirectory>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
||||
@@ -10,5 +10,7 @@ import java.net.SocketAddress;
|
||||
*/
|
||||
public interface CanalNodeAccessStrategy {
|
||||
|
||||
SocketAddress currentNode();
|
||||
|
||||
SocketAddress nextNode();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.alibaba.otter.canal.client.impl;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -41,11 +40,17 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
|
||||
public void connect() throws CanalClientException {
|
||||
while (currentConnector == null) {
|
||||
SocketAddress nextAddress = this.accessStrategy.nextNode();
|
||||
int times = 0;
|
||||
while (true) {
|
||||
try {
|
||||
currentConnector = new SimpleCanalConnector(nextAddress, username, password, destination);
|
||||
currentConnector = new SimpleCanalConnector(null, username, password, destination) {
|
||||
|
||||
@Override
|
||||
public SocketAddress getNextAddress() {
|
||||
return accessStrategy.nextNode();
|
||||
}
|
||||
|
||||
};
|
||||
currentConnector.setSoTimeout(soTimeout);
|
||||
if (filter != null) {
|
||||
currentConnector.setFilter(filter);
|
||||
@@ -57,7 +62,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
currentConnector.connect();
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to connect to:{} after retry {} times", nextAddress, times);
|
||||
logger.warn("failed to connect to:{} after retry {} times", accessStrategy.currentNode(), times);
|
||||
currentConnector.disconnect();
|
||||
currentConnector = null;
|
||||
// retry for #retryTimes for each node when trying to
|
||||
@@ -101,9 +106,10 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
this.filter = filter;
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when subscribing from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format(
|
||||
"something goes wrong when subscribing from server: %s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"),
|
||||
t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -120,9 +126,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
currentConnector.unsubscribe();
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when unsubscribing from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when unsubscribing from server:%s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -138,9 +143,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
Message msg = currentConnector.get(batchSize);
|
||||
return msg;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when getting data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when getting data from server:%s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -156,9 +160,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
Message msg = currentConnector.get(batchSize, timeout, unit);
|
||||
return msg;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when getting data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when getting data from server:%s",
|
||||
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -174,9 +177,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
Message msg = currentConnector.getWithoutAck(batchSize);
|
||||
return msg;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when getWithoutAck data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
||||
currentConnector.getAddress()), t);
|
||||
times++;
|
||||
restart();
|
||||
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);
|
||||
return msg;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when getWithoutAck data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
||||
currentConnector.getAddress()), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -210,9 +211,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
currentConnector.rollback(batchId);
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when rollbacking data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
||||
currentConnector.getAddress()), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -228,9 +228,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
currentConnector.rollback();
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when rollbacking data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
||||
currentConnector.getAddress()), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
@@ -247,9 +246,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
||||
currentConnector.ack(batchId);
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("something goes wrong when acking data from server:{}\n{}",
|
||||
currentConnector.getAddress(),
|
||||
ExceptionUtils.getFullStackTrace(t));
|
||||
logger.warn(String.format("something goes wrong when acking data from server:%s",
|
||||
currentConnector.getAddress()), t);
|
||||
times++;
|
||||
restart();
|
||||
logger.info("restart the connector for next round retry.");
|
||||
|
||||
@@ -62,6 +62,11 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
|
||||
initRunning(this.zkClient.readData(runningPath, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress currentNode() {
|
||||
return nextNode();
|
||||
}
|
||||
|
||||
public SocketAddress nextNode() {
|
||||
if (runningAddress != null) {// 如果服务已经启动,直接选择当前正在工作的节点
|
||||
return runningAddress;
|
||||
|
||||
@@ -5,7 +5,11 @@ import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.Channel;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -37,7 +41,6 @@ import com.alibaba.otter.canal.protocol.ClientIdentity;
|
||||
import com.alibaba.otter.canal.protocol.Message;
|
||||
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
/**
|
||||
* 基于{@linkplain CanalServerWithNetty}定义的网络协议接口,对于canal数据进行get/rollback/ack等操作
|
||||
@@ -57,6 +60,8 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
private final ByteBuffer readHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
||||
private final ByteBuffer writeHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
||||
private SocketChannel channel;
|
||||
private ReadableByteChannel readableChannel;
|
||||
private WritableByteChannel writableChannel;
|
||||
private List<Compression> supportedCompressions = new ArrayList<Compression>();
|
||||
private ClientIdentity clientIdentity;
|
||||
private ClientRunningMonitor runningMonitor; // 运行控制
|
||||
@@ -125,8 +130,14 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
try {
|
||||
channel = SocketChannel.open();
|
||||
channel.socket().setSoTimeout(soTimeout);
|
||||
SocketAddress address = getAddress();
|
||||
if (address == null) {
|
||||
address = getNextAddress();
|
||||
}
|
||||
channel.connect(address);
|
||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
||||
readableChannel = Channels.newChannel(channel.socket().getInputStream());
|
||||
writableChannel = Channels.newChannel(channel.socket().getOutputStream());
|
||||
Packet p = Packet.parseFrom(readNextPacket());
|
||||
if (p.getVersion() != 1) {
|
||||
throw new CanalClientException("unsupported version at this client.");
|
||||
}
|
||||
@@ -140,17 +151,17 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
//
|
||||
ClientAuth ca = ClientAuth.newBuilder()
|
||||
.setUsername(username != null ? username : "")
|
||||
.setPassword(ByteString.copyFromUtf8(password != null ? password : ""))
|
||||
.setNetReadTimeout(soTimeout)
|
||||
.setNetWriteTimeout(soTimeout)
|
||||
.build();
|
||||
writeWithHeader(channel,
|
||||
Packet.newBuilder()
|
||||
.setType(PacketType.CLIENTAUTHENTICATION)
|
||||
.setBody(ca.toByteString())
|
||||
.build()
|
||||
.toByteArray());
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.CLIENTAUTHENTICATION)
|
||||
.setBody(ca.toByteString())
|
||||
.build()
|
||||
.toByteArray());
|
||||
//
|
||||
Packet ack = Packet.parseFrom(readNextPacket(channel));
|
||||
Packet ack = Packet.parseFrom(readNextPacket());
|
||||
if (ack.getType() != PacketType.ACK) {
|
||||
throw new CanalClientException("unexpected packet type when ack is expected");
|
||||
}
|
||||
@@ -169,16 +180,28 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
|
||||
private void doDisconnnect() throws CanalClientException {
|
||||
if (readableChannel != null) {
|
||||
quietlyClose(readableChannel);
|
||||
readableChannel = null;
|
||||
}
|
||||
if (writableChannel != null) {
|
||||
quietlyClose(writableChannel);
|
||||
writableChannel = null;
|
||||
}
|
||||
if (channel != null) {
|
||||
try {
|
||||
channel.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("exception on closing channel:{} \n {}", channel, e);
|
||||
}
|
||||
quietlyClose(channel);
|
||||
channel = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void quietlyClose(Channel channel) {
|
||||
try {
|
||||
channel.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("exception on closing channel:{} \n {}", channel, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void subscribe() throws CanalClientException {
|
||||
subscribe(""); // 传递空字符即可
|
||||
}
|
||||
@@ -186,19 +209,18 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
public void subscribe(String filter) throws CanalClientException {
|
||||
waitClientRunning();
|
||||
try {
|
||||
writeWithHeader(channel,
|
||||
Packet.newBuilder()
|
||||
.setType(PacketType.SUBSCRIPTION)
|
||||
.setBody(Sub.newBuilder()
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.setFilter(filter != null ? filter : "")
|
||||
.build()
|
||||
.toByteString())
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.SUBSCRIPTION)
|
||||
.setBody(Sub.newBuilder()
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.setFilter(filter != null ? filter : "")
|
||||
.build()
|
||||
.toByteArray());
|
||||
.toByteString())
|
||||
.build()
|
||||
.toByteArray());
|
||||
//
|
||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
||||
Packet p = Packet.parseFrom(readNextPacket());
|
||||
Ack ack = Ack.parseFrom(p.getBody());
|
||||
if (ack.getErrorCode() > 0) {
|
||||
throw new CanalClientException("failed to subscribe with reason: " + ack.getErrorMessage());
|
||||
@@ -213,18 +235,17 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
public void unsubscribe() throws CanalClientException {
|
||||
waitClientRunning();
|
||||
try {
|
||||
writeWithHeader(channel,
|
||||
Packet.newBuilder()
|
||||
.setType(PacketType.UNSUBSCRIPTION)
|
||||
.setBody(Unsub.newBuilder()
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.build()
|
||||
.toByteString())
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.UNSUBSCRIPTION)
|
||||
.setBody(Unsub.newBuilder()
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.build()
|
||||
.toByteArray());
|
||||
.toByteString())
|
||||
.build()
|
||||
.toByteArray());
|
||||
//
|
||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
||||
Packet p = Packet.parseFrom(readNextPacket());
|
||||
Ack ack = Ack.parseFrom(p.getBody());
|
||||
if (ack.getErrorCode() > 0) {
|
||||
throw new CanalClientException("failed to unSubscribe with reason: " + ack.getErrorMessage());
|
||||
@@ -257,29 +278,27 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
unit = TimeUnit.MILLISECONDS;
|
||||
}
|
||||
|
||||
writeWithHeader(channel,
|
||||
Packet.newBuilder()
|
||||
.setType(PacketType.GET)
|
||||
.setBody(Get.newBuilder()
|
||||
.setAutoAck(false)
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.setFetchSize(size)
|
||||
.setTimeout(time)
|
||||
.setUnit(unit.ordinal())
|
||||
.build()
|
||||
.toByteString())
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.GET)
|
||||
.setBody(Get.newBuilder()
|
||||
.setAutoAck(false)
|
||||
.setDestination(clientIdentity.getDestination())
|
||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||
.setFetchSize(size)
|
||||
.setTimeout(time)
|
||||
.setUnit(unit.ordinal())
|
||||
.build()
|
||||
.toByteArray());
|
||||
|
||||
.toByteString())
|
||||
.build()
|
||||
.toByteArray());
|
||||
return receiveMessages();
|
||||
} catch (IOException e) {
|
||||
throw new CanalClientException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Message receiveMessages() throws InvalidProtocolBufferException, IOException {
|
||||
Packet p = Packet.parseFrom(readNextPacket(channel));
|
||||
private Message receiveMessages() throws IOException {
|
||||
Packet p = Packet.parseFrom(readNextPacket());
|
||||
switch (p.getType()) {
|
||||
case MESSAGES: {
|
||||
if (!p.getCompression().equals(Compression.NONE)) {
|
||||
@@ -311,7 +330,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
.setBatchId(batchId)
|
||||
.build();
|
||||
try {
|
||||
writeWithHeader(channel, Packet.newBuilder()
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.CLIENTACK)
|
||||
.setBody(ca.toByteString())
|
||||
.build()
|
||||
@@ -329,7 +348,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
.setBatchId(batchId)
|
||||
.build();
|
||||
try {
|
||||
writeWithHeader(channel, Packet.newBuilder()
|
||||
writeWithHeader(Packet.newBuilder()
|
||||
.setType(PacketType.CLIENTROLLBACK)
|
||||
.setBody(ca.toByteString())
|
||||
.build()
|
||||
@@ -346,7 +365,15 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
|
||||
// ==================== helper method ====================
|
||||
|
||||
private void writeWithHeader(SocketChannel channel, byte[] body) throws IOException {
|
||||
private void writeWithHeader(byte[] body) throws IOException {
|
||||
writeWithHeader(writableChannel, body);
|
||||
}
|
||||
|
||||
private byte[] readNextPacket() throws IOException {
|
||||
return readNextPacket(readableChannel);
|
||||
}
|
||||
|
||||
private void writeWithHeader(WritableByteChannel channel, byte[] body) throws IOException {
|
||||
synchronized (writeDataLock) {
|
||||
writeHeader.clear();
|
||||
writeHeader.putInt(body.length);
|
||||
@@ -356,7 +383,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readNextPacket(SocketChannel channel) throws IOException {
|
||||
private byte[] readNextPacket(ReadableByteChannel channel) throws IOException {
|
||||
synchronized (readDataLock) {
|
||||
readHeader.clear();
|
||||
read(channel, readHeader);
|
||||
@@ -367,7 +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()) {
|
||||
int r = channel.read(buffer);
|
||||
if (r == -1) {
|
||||
@@ -434,6 +461,10 @@ public class SimpleCanalConnector implements CanalConnector {
|
||||
}
|
||||
}
|
||||
|
||||
public SocketAddress getNextAddress() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public SocketAddress getAddress() {
|
||||
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;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -20,6 +21,7 @@ import com.alibaba.otter.canal.common.utils.BooleanMutex;
|
||||
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
||||
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
||||
import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils;
|
||||
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||
|
||||
/**
|
||||
* clinet running控制
|
||||
@@ -62,6 +64,8 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
|
||||
public void handleDataDeleted(String dataPath) throws Exception {
|
||||
MDC.put("destination", destination);
|
||||
mutex.set(false);
|
||||
// 触发一下退出,可能是人为干预的释放操作或者网络闪断引起的session expired timeout
|
||||
processActiveExit();
|
||||
if (!release && activeData != null && isMine(activeData.getAddress())) {
|
||||
// 如果上一次active的状态就是本机,则即时触发一下active抢占
|
||||
initRunning();
|
||||
@@ -96,7 +100,11 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
|
||||
releaseRunning(); // 尝试一下release
|
||||
}
|
||||
|
||||
public void initRunning() {
|
||||
// 改动记录:
|
||||
// 1,在方法上加synchronized关键字,保证同步顺序执行;
|
||||
// 2,判断Zk上已经存在的activeData是否是本机,是的话把mutex重置为true,否则会导致死锁
|
||||
// 3,增加异常处理,保证出现异常时,running节点能被删除,否则会导致死锁
|
||||
public synchronized void initRunning() {
|
||||
if (!isStart()) {
|
||||
return;
|
||||
}
|
||||
@@ -116,11 +124,22 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
|
||||
initRunning();
|
||||
} else {
|
||||
activeData = JsonUtils.unmarshalFromByte(bytes, ClientRunningData.class);
|
||||
// 如果发现已经存在,判断一下是否自己,避免活锁
|
||||
if (activeData.getAddress().contains(":") && isMine(activeData.getAddress())) {
|
||||
mutex.set(true);
|
||||
}
|
||||
}
|
||||
} catch (ZkNoNodeException e) {
|
||||
zkClient.createPersistent(ZookeeperPathUtils.getClientIdNodePath(this.destination, clientData.getClientId()),
|
||||
true); // 尝试创建父节点
|
||||
initRunning();
|
||||
} catch (Throwable t) {
|
||||
logger.error(MessageFormat.format("There is an error when execute initRunning method, with destination [{0}].",
|
||||
destination),
|
||||
t);
|
||||
// 出现任何异常尝试release
|
||||
releaseRunning();
|
||||
throw new CanalClientException("something goes wrong in initRunning method. ", t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="WARN">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>canal.common</artifactId>
|
||||
@@ -11,13 +11,18 @@
|
||||
<name>canal common module for otter ${project.version}</name>
|
||||
<url>http://b2b-doc.alibaba-inc.com/display/opentech/Otter</url>
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
</dependency>
|
||||
<!-- zk -->
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.sgroschupf</groupId>
|
||||
<groupId>com.101tec</groupId>
|
||||
<artifactId>zkclient</artifactId>
|
||||
</dependency>
|
||||
<!-- external -->
|
||||
@@ -28,11 +33,10 @@
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring</artifactId>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
@@ -59,10 +63,37 @@
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-orm</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<version>${spring_version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- junit -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
package com.alibaba.otter.canal.common.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
import com.alibaba.fastjson.parser.ParserConfig;
|
||||
import com.alibaba.fastjson.serializer.JSONSerializer;
|
||||
import com.alibaba.fastjson.serializer.ObjectSerializer;
|
||||
import com.alibaba.fastjson.serializer.PropertyFilter;
|
||||
import com.alibaba.fastjson.serializer.SerializeConfig;
|
||||
import com.alibaba.fastjson.serializer.SerializeWriter;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
|
||||
@@ -17,6 +25,13 @@ import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
*/
|
||||
public class JsonUtils {
|
||||
|
||||
static {
|
||||
SerializeConfig.getGlobalInstance().put(InetAddress.class, InetAddressSerializer.instance);
|
||||
SerializeConfig.getGlobalInstance().put(Inet4Address.class, InetAddressSerializer.instance);
|
||||
SerializeConfig.getGlobalInstance().put(Inet6Address.class, InetAddressSerializer.instance);
|
||||
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
|
||||
}
|
||||
|
||||
public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) {
|
||||
return (T) JSON.parseObject(bytes, targetClass);// 默认为UTF-8
|
||||
}
|
||||
@@ -70,4 +85,22 @@ public class JsonUtils {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static class InetAddressSerializer implements ObjectSerializer {
|
||||
|
||||
public static InetAddressSerializer instance = new InetAddressSerializer();
|
||||
|
||||
@Override
|
||||
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features)
|
||||
throws IOException {
|
||||
if (object == null) {
|
||||
serializer.writeNull();
|
||||
return;
|
||||
}
|
||||
|
||||
InetAddress address = (InetAddress) object;
|
||||
// 优先使用name
|
||||
serializer.write(address.getHostName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,20 +8,15 @@ import java.util.List;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.I0Itec.zkclient.IZkConnection;
|
||||
import org.I0Itec.zkclient.ZkConnection;
|
||||
import org.I0Itec.zkclient.exception.ZkException;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.zookeeper.ClientCnxn;
|
||||
import org.apache.zookeeper.CreateMode;
|
||||
import org.apache.zookeeper.KeeperException;
|
||||
import org.apache.zookeeper.Watcher;
|
||||
import org.apache.zookeeper.ZooDefs.Ids;
|
||||
import org.apache.zookeeper.ZooKeeper;
|
||||
import org.apache.zookeeper.ZooKeeper.States;
|
||||
import org.apache.zookeeper.client.ConnectStringParser;
|
||||
import org.apache.zookeeper.client.HostProvider;
|
||||
import org.apache.zookeeper.client.StaticHostProvider;
|
||||
import org.apache.zookeeper.data.Stat;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -32,7 +27,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
* @author jianghang 2012-7-10 下午02:31:42
|
||||
* @version 1.0.0
|
||||
*/
|
||||
public class ZooKeeperx implements IZkConnection {
|
||||
public class ZooKeeperx extends ZkConnection {
|
||||
|
||||
private static final String SERVER_COMMA = ";";
|
||||
private static final Logger logger = LoggerFactory.getLogger(ZooKeeperx.class);
|
||||
@@ -40,12 +35,12 @@ public class ZooKeeperx implements IZkConnection {
|
||||
private static final Field hostProviderField = ReflectionUtils.findField(ClientCnxn.class, "hostProvider");
|
||||
private static final Field serverAddressesField = ReflectionUtils.findField(StaticHostProvider.class,
|
||||
"serverAddresses");
|
||||
private static final Field zookeeperLockField = ReflectionUtils.findField(ZkConnection.class,
|
||||
"_zookeeperLock");
|
||||
private static final Field zookeeperFiled = ReflectionUtils.findField(ZkConnection.class, "_zk");
|
||||
private static final int DEFAULT_SESSION_TIMEOUT = 90000;
|
||||
|
||||
private ZooKeeper _zk = null;
|
||||
private Lock _zookeeperLock = new ReentrantLock();
|
||||
|
||||
private final List<String> _servers;
|
||||
private final List<String> _serversList;
|
||||
private final int _sessionTimeOut;
|
||||
|
||||
public ZooKeeperx(String zkServers){
|
||||
@@ -53,109 +48,55 @@ public class ZooKeeperx implements IZkConnection {
|
||||
}
|
||||
|
||||
public ZooKeeperx(String zkServers, int sessionTimeOut){
|
||||
_servers = Arrays.asList(StringUtils.split(zkServers, SERVER_COMMA));
|
||||
super(zkServers, sessionTimeOut);
|
||||
_serversList = Arrays.asList(StringUtils.split(this.getServers(), SERVER_COMMA));
|
||||
_sessionTimeOut = sessionTimeOut;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(Watcher watcher) {
|
||||
ReflectionUtils.makeAccessible(zookeeperLockField);
|
||||
ReflectionUtils.makeAccessible(zookeeperFiled);
|
||||
Lock _zookeeperLock = (ReentrantLock) ReflectionUtils.getField(zookeeperLockField, this);
|
||||
ZooKeeper _zk = (ZooKeeper) ReflectionUtils.getField(zookeeperFiled, this);
|
||||
|
||||
_zookeeperLock.lock();
|
||||
try {
|
||||
if (_zk != null) {
|
||||
throw new IllegalStateException("zk client has already been started");
|
||||
}
|
||||
String zkServers = _serversList.get(0);
|
||||
|
||||
try {
|
||||
logger.debug("Creating new ZookKeeper instance to connect to " + _servers + ".");
|
||||
_zk = new ZooKeeper(_servers.get(0), _sessionTimeOut, watcher);
|
||||
logger.debug("Creating new ZookKeeper instance to connect to " + zkServers + ".");
|
||||
_zk = new ZooKeeper(zkServers, _sessionTimeOut, watcher);
|
||||
configMutliCluster(_zk);
|
||||
ReflectionUtils.setField(zookeeperFiled, this, _zk);
|
||||
} catch (IOException e) {
|
||||
throw new ZkException("Unable to connect to " + _servers, e);
|
||||
throw new ZkException("Unable to connect to " + zkServers, e);
|
||||
}
|
||||
} finally {
|
||||
_zookeeperLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws InterruptedException {
|
||||
_zookeeperLock.lock();
|
||||
try {
|
||||
if (_zk != null) {
|
||||
logger.debug("Closing ZooKeeper connected to " + _servers);
|
||||
_zk.close();
|
||||
_zk = null;
|
||||
}
|
||||
} finally {
|
||||
_zookeeperLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public String create(String path, byte[] data, CreateMode mode) throws KeeperException, InterruptedException {
|
||||
return _zk.create(path, data, Ids.OPEN_ACL_UNSAFE, mode);
|
||||
}
|
||||
|
||||
public void delete(String path) throws InterruptedException, KeeperException {
|
||||
_zk.delete(path, -1);
|
||||
}
|
||||
|
||||
public boolean exists(String path, boolean watch) throws KeeperException, InterruptedException {
|
||||
return _zk.exists(path, watch) != null;
|
||||
}
|
||||
|
||||
public List<String> getChildren(final String path, final boolean watch) throws KeeperException,
|
||||
InterruptedException {
|
||||
return _zk.getChildren(path, watch);
|
||||
}
|
||||
|
||||
public byte[] readData(String path, Stat stat, boolean watch) throws KeeperException, InterruptedException {
|
||||
return _zk.getData(path, watch, stat);
|
||||
}
|
||||
|
||||
public void writeData(String path, byte[] data) throws KeeperException, InterruptedException {
|
||||
writeData(path, data, -1);
|
||||
}
|
||||
|
||||
public void writeData(String path, byte[] data, int version) throws KeeperException, InterruptedException {
|
||||
_zk.setData(path, data, version);
|
||||
}
|
||||
|
||||
public States getZookeeperState() {
|
||||
return _zk != null ? _zk.getState() : null;
|
||||
}
|
||||
|
||||
public ZooKeeper getZookeeper() {
|
||||
return _zk;
|
||||
}
|
||||
|
||||
public long getCreateTime(String path) throws KeeperException, InterruptedException {
|
||||
Stat stat = _zk.exists(path, false);
|
||||
if (stat != null) {
|
||||
return stat.getCtime();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public String getServers() {
|
||||
return StringUtils.join(_servers, SERVER_COMMA);
|
||||
}
|
||||
|
||||
// ===============================
|
||||
|
||||
public void configMutliCluster(ZooKeeper zk) {
|
||||
if (_servers.size() == 1) {
|
||||
if (_serversList.size() == 1) {
|
||||
return;
|
||||
}
|
||||
String cluster1 = _servers.get(0);
|
||||
String cluster1 = _serversList.get(0);
|
||||
try {
|
||||
if (_servers.size() > 1) {
|
||||
if (_serversList.size() > 1) {
|
||||
// 强制的声明accessible
|
||||
ReflectionUtils.makeAccessible(clientCnxnField);
|
||||
ReflectionUtils.makeAccessible(hostProviderField);
|
||||
ReflectionUtils.makeAccessible(serverAddressesField);
|
||||
|
||||
// 添加第二组集群列表
|
||||
for (int i = 1; i < _servers.size(); i++) {
|
||||
String cluster = _servers.get(i);
|
||||
for (int i = 1; i < _serversList.size(); i++) {
|
||||
String cluster = _serversList.get(i);
|
||||
// 强制获取zk中的地址信息
|
||||
ClientCnxn cnxn = (ClientCnxn) ReflectionUtils.getField(clientCnxnField, zk);
|
||||
HostProvider hostProvider = (HostProvider) ReflectionUtils.getField(hostProviderField, cnxn);
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.alibaba.otter.canal.common.CanalException;
|
||||
import org.I0Itec.zkclient.IZkDataListener;
|
||||
import org.I0Itec.zkclient.exception.ZkException;
|
||||
import org.I0Itec.zkclient.exception.ZkInterruptedException;
|
||||
@@ -87,18 +88,29 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
||||
|
||||
}
|
||||
|
||||
public void start() {
|
||||
super.start();
|
||||
public void init() {
|
||||
processStart();
|
||||
if (zkClient != null) {
|
||||
// 如果需要尽可能释放instance资源,不需要监听running节点,不然即使stop了这台机器,另一台机器立马会start
|
||||
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
|
||||
zkClient.subscribeDataChanges(path, dataListener);
|
||||
}
|
||||
|
||||
initRunning();
|
||||
} else {
|
||||
processActiveEnter();// 没有zk,直接启动
|
||||
public synchronized void start() {
|
||||
super.start();
|
||||
try {
|
||||
processStart();
|
||||
if (zkClient != null) {
|
||||
// 如果需要尽可能释放instance资源,不需要监听running节点,不然即使stop了这台机器,另一台机器立马会start
|
||||
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
|
||||
zkClient.subscribeDataChanges(path, dataListener);
|
||||
|
||||
initRunning();
|
||||
} else {
|
||||
processActiveEnter();// 没有zk,直接启动
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("start failed", e);
|
||||
// 没有正常启动,重置一下状态,避免干扰下一次start
|
||||
stop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void release() {
|
||||
@@ -109,7 +121,7 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
public synchronized void stop() {
|
||||
super.stop();
|
||||
|
||||
if (zkClient != null) {
|
||||
@@ -230,11 +242,7 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
||||
|
||||
private void processActiveEnter() {
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.processActiveEnter();
|
||||
} catch (Exception e) {
|
||||
logger.error("processActiveEnter failed", e);
|
||||
}
|
||||
listener.processActiveEnter();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -0,0 +1,418 @@
|
||||
package com.taobao.tddl.dbsync.binlog;
|
||||
|
||||
/**
|
||||
* 处理下MySQL json二进制转化为可读的字符串
|
||||
*
|
||||
* @author agapple 2016年6月30日 上午11:26:17
|
||||
* @since 1.0.22
|
||||
*/
|
||||
public class JsonConversion {
|
||||
|
||||
// JSON TYPE
|
||||
public static final int JSONB_TYPE_SMALL_OBJECT = 0x0;
|
||||
public static final int JSONB_TYPE_LARGE_OBJECT = 0x1;
|
||||
public static final int JSONB_TYPE_SMALL_ARRAY = 0x2;
|
||||
public static final int JSONB_TYPE_LARGE_ARRAY = 0x3;
|
||||
public static final int JSONB_TYPE_LITERAL = 0x4;
|
||||
public static final int JSONB_TYPE_INT16 = 0x5;
|
||||
public static final int JSONB_TYPE_UINT16 = 0x6;
|
||||
public static final int JSONB_TYPE_INT32 = 0x7;
|
||||
public static final int JSONB_TYPE_UINT32 = 0x8;
|
||||
public static final int JSONB_TYPE_INT64 = 0x9;
|
||||
public static final int JSONB_TYPE_UINT64 = 0xA;
|
||||
public static final int JSONB_TYPE_DOUBLE = 0xB;
|
||||
public static final int JSONB_TYPE_STRING = 0xC;
|
||||
public static final int JSONB_TYPE_OPAQUE = 0xF;
|
||||
public static final char JSONB_NULL_LITERAL = '\0';
|
||||
public static final char JSONB_TRUE_LITERAL = '\1';
|
||||
public static final char JSONB_FALSE_LITERAL = '\2';
|
||||
|
||||
/*
|
||||
* The size of offset or size fields in the small and the large storage
|
||||
* format for JSON objects and JSON arrays.
|
||||
*/
|
||||
public static final int SMALL_OFFSET_SIZE = 2;
|
||||
public static final int LARGE_OFFSET_SIZE = 4;
|
||||
|
||||
/*
|
||||
* The size of key entries for objects when using the small storage format
|
||||
* or the large storage format. In the small format it is 4 bytes (2 bytes
|
||||
* for key length and 2 bytes for key offset). In the large format it is 6
|
||||
* (2 bytes for length, 4 bytes for offset).
|
||||
*/
|
||||
public static final int KEY_ENTRY_SIZE_SMALL = (2 + SMALL_OFFSET_SIZE);
|
||||
public static final int KEY_ENTRY_SIZE_LARGE = (2 + LARGE_OFFSET_SIZE);
|
||||
|
||||
/*
|
||||
* The size of value entries for objects or arrays. When using the small
|
||||
* storage format, the entry size is 3 (1 byte for type, 2 bytes for
|
||||
* offset). When using the large storage format, it is 5 (1 byte for type, 4
|
||||
* bytes for offset).
|
||||
*/
|
||||
public static final int VALUE_ENTRY_SIZE_SMALL = (1 + SMALL_OFFSET_SIZE);
|
||||
public static final int VALUE_ENTRY_SIZE_LARGE = (1 + LARGE_OFFSET_SIZE);
|
||||
|
||||
public static Json_Value parse_value(int type, LogBuffer buffer, long len, String charsetName) {
|
||||
buffer = buffer.duplicate(buffer.position(), (int) len);
|
||||
switch (type) {
|
||||
case JSONB_TYPE_SMALL_OBJECT:
|
||||
return parse_array_or_object(Json_enum_type.OBJECT, buffer, len, false, charsetName);
|
||||
case JSONB_TYPE_LARGE_OBJECT:
|
||||
return parse_array_or_object(Json_enum_type.OBJECT, buffer, len, true, charsetName);
|
||||
case JSONB_TYPE_SMALL_ARRAY:
|
||||
return parse_array_or_object(Json_enum_type.ARRAY, buffer, len, false, charsetName);
|
||||
case JSONB_TYPE_LARGE_ARRAY:
|
||||
return parse_array_or_object(Json_enum_type.ARRAY, buffer, len, true, charsetName);
|
||||
default:
|
||||
return parse_scalar(type, buffer, len, charsetName);
|
||||
}
|
||||
}
|
||||
|
||||
private static Json_Value parse_array_or_object(Json_enum_type type, LogBuffer buffer, long len, boolean large,
|
||||
String charsetName) {
|
||||
long offset_size = large ? LARGE_OFFSET_SIZE : SMALL_OFFSET_SIZE;
|
||||
if (len < 2 * offset_size) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
long element_count = read_offset_or_size(buffer, large);
|
||||
long bytes = read_offset_or_size(buffer, large);
|
||||
|
||||
if (bytes > len) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
long header_size = 2 * offset_size;
|
||||
if (type == Json_enum_type.OBJECT) {
|
||||
header_size += element_count * (large ? KEY_ENTRY_SIZE_LARGE : KEY_ENTRY_SIZE_SMALL);
|
||||
}
|
||||
|
||||
header_size += element_count * (large ? VALUE_ENTRY_SIZE_LARGE : VALUE_ENTRY_SIZE_SMALL);
|
||||
if (header_size > bytes) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
return new Json_Value(type, buffer.rewind(), element_count, bytes, large);
|
||||
}
|
||||
|
||||
private static long read_offset_or_size(LogBuffer buffer, boolean large) {
|
||||
return large ? buffer.getUint32() : buffer.getUint16();
|
||||
}
|
||||
|
||||
private static Json_Value parse_scalar(int type, LogBuffer buffer, long len, String charsetName) {
|
||||
switch (type) {
|
||||
case JSONB_TYPE_LITERAL:
|
||||
/* purecov: inspected */
|
||||
int data = buffer.getUint8();
|
||||
switch (data) {
|
||||
case JSONB_NULL_LITERAL:
|
||||
return new Json_Value(Json_enum_type.LITERAL_NULL);
|
||||
case JSONB_TRUE_LITERAL:
|
||||
return new Json_Value(Json_enum_type.LITERAL_TRUE);
|
||||
case JSONB_FALSE_LITERAL:
|
||||
return new Json_Value(Json_enum_type.LITERAL_FALSE);
|
||||
default:
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
case JSONB_TYPE_INT16:
|
||||
return new Json_Value(Json_enum_type.INT, buffer.getInt16());
|
||||
case JSONB_TYPE_INT32:
|
||||
return new Json_Value(Json_enum_type.INT, buffer.getInt32());
|
||||
case JSONB_TYPE_INT64:
|
||||
return new Json_Value(Json_enum_type.INT, buffer.getLong64());
|
||||
case JSONB_TYPE_UINT16:
|
||||
return new Json_Value(Json_enum_type.UINT, buffer.getUint16());
|
||||
case JSONB_TYPE_UINT32:
|
||||
return new Json_Value(Json_enum_type.UINT, buffer.getUint32());
|
||||
case JSONB_TYPE_UINT64:
|
||||
return new Json_Value(Json_enum_type.UINT, buffer.getUlong64());
|
||||
case JSONB_TYPE_DOUBLE:
|
||||
return new Json_Value(Json_enum_type.DOUBLE, Double.valueOf(buffer.getDouble64()));
|
||||
case JSONB_TYPE_STRING:
|
||||
int max_bytes = (int) Math.min(len, 5);
|
||||
long tlen = 0;
|
||||
long str_len = 0;
|
||||
long n = 0;
|
||||
byte[] datas = buffer.getData(max_bytes);
|
||||
for (int i = 0; i < max_bytes; i++) {
|
||||
// Get the next 7 bits of the length.
|
||||
tlen |= (datas[i] & 0x7f) << (7 * i);
|
||||
if ((datas[i] & 0x80) == 0) {
|
||||
// The length shouldn't exceed 32 bits.
|
||||
if (tlen > 4294967296L) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
|
||||
// This was the last byte. Return successfully.
|
||||
n = i + 1;
|
||||
str_len = tlen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (len < n + str_len) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
return new Json_Value(Json_enum_type.STRING, buffer.rewind()
|
||||
.forward((int) n)
|
||||
.getFixString((int) str_len, charsetName));
|
||||
case JSONB_TYPE_OPAQUE:
|
||||
/*
|
||||
* There should always be at least one byte, which tells the
|
||||
* field type of the opaque value.
|
||||
*/
|
||||
// The type is encoded as a uint8 that maps to an
|
||||
// enum_field_types.
|
||||
int type_byte = buffer.getUint8();
|
||||
int position = buffer.position();
|
||||
// Then there's the length of the value.
|
||||
int q_max_bytes = (int) Math.min(len, 5);
|
||||
long q_tlen = 0;
|
||||
long q_str_len = 0;
|
||||
long q_n = 0;
|
||||
byte[] q_datas = buffer.getData(q_max_bytes);
|
||||
for (int i = 0; i < q_max_bytes; i++) {
|
||||
// Get the next 7 bits of the length.
|
||||
q_tlen |= (q_datas[i] & 0x7f) << (7 * i);
|
||||
if ((q_datas[i] & 0x80) == 0) {
|
||||
// The length shouldn't exceed 32 bits.
|
||||
if (q_tlen > 4294967296L) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
|
||||
// This was the last byte. Return successfully.
|
||||
q_n = i + 1;
|
||||
q_str_len = q_tlen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (q_str_len == 0 || len < q_n + q_str_len) {
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
return new Json_Value(type_byte, buffer.position(position).forward((int) q_n), q_str_len);
|
||||
default:
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
}
|
||||
|
||||
public static class Json_Value {
|
||||
|
||||
Json_enum_type m_type;
|
||||
int m_field_type;
|
||||
LogBuffer m_data;
|
||||
long m_element_count;
|
||||
long m_length;
|
||||
String m_string_value;
|
||||
Number m_int_value;
|
||||
double m_double_value;
|
||||
boolean m_large;
|
||||
|
||||
public Json_Value(Json_enum_type t){
|
||||
this.m_type = t;
|
||||
}
|
||||
|
||||
public Json_Value(Json_enum_type t, Number val){
|
||||
this.m_type = t;
|
||||
if (t == Json_enum_type.DOUBLE) {
|
||||
this.m_double_value = val.doubleValue();
|
||||
} else {
|
||||
this.m_int_value = val;
|
||||
}
|
||||
}
|
||||
|
||||
public Json_Value(Json_enum_type t, String value){
|
||||
this.m_type = t;
|
||||
this.m_string_value = value;
|
||||
}
|
||||
|
||||
public Json_Value(int field_type, LogBuffer data, long bytes){
|
||||
this.m_type = Json_enum_type.OPAQUE; // 不确定类型
|
||||
this.m_field_type = field_type;
|
||||
this.m_data = data;
|
||||
this.m_length = bytes;
|
||||
}
|
||||
|
||||
public Json_Value(Json_enum_type t, LogBuffer data, long element_count, long bytes, boolean large){
|
||||
this.m_type = t;
|
||||
this.m_data = data;
|
||||
this.m_element_count = element_count;
|
||||
this.m_length = bytes;
|
||||
this.m_large = large;
|
||||
}
|
||||
|
||||
public String key(int i, String charsetName) {
|
||||
m_data.rewind();
|
||||
int offset_size = m_large ? LARGE_OFFSET_SIZE : SMALL_OFFSET_SIZE;
|
||||
int key_entry_size = m_large ? KEY_ENTRY_SIZE_LARGE : KEY_ENTRY_SIZE_SMALL;
|
||||
int entry_offset = 2 * offset_size + key_entry_size * i;
|
||||
// The offset of the key is the first part of the key
|
||||
// entry.
|
||||
m_data.forward(entry_offset);
|
||||
long key_offset = read_offset_or_size(m_data, m_large);
|
||||
// The length of the key is the second part of the
|
||||
// entry, always two
|
||||
// bytes.
|
||||
long key_length = m_data.getUint16();
|
||||
return m_data.rewind().forward((int) key_offset).getFixString((int) key_length, charsetName);
|
||||
}
|
||||
|
||||
public Json_Value element(int i, String charsetName) {
|
||||
m_data.rewind();
|
||||
int offset_size = m_large ? LARGE_OFFSET_SIZE : SMALL_OFFSET_SIZE;
|
||||
int key_entry_size = m_large ? KEY_ENTRY_SIZE_LARGE : KEY_ENTRY_SIZE_SMALL;
|
||||
int value_entry_size = m_large ? VALUE_ENTRY_SIZE_LARGE : VALUE_ENTRY_SIZE_SMALL;
|
||||
int first_entry_offset = 2 * offset_size;
|
||||
if (m_type == Json_enum_type.OBJECT) {
|
||||
first_entry_offset += m_element_count * key_entry_size;
|
||||
}
|
||||
int entry_offset = first_entry_offset + value_entry_size * i;
|
||||
int type = m_data.forward(entry_offset).getUint8();
|
||||
if (type == JSONB_TYPE_INT16 || type == JSONB_TYPE_UINT16 || type == JSONB_TYPE_LITERAL
|
||||
|| (m_large && (type == JSONB_TYPE_INT32 || type == JSONB_TYPE_UINT32))) {
|
||||
return parse_scalar(type, m_data, value_entry_size - 1, charsetName);
|
||||
}
|
||||
int value_offset = (int) read_offset_or_size(m_data, m_large);
|
||||
return parse_value(type, m_data.rewind().forward(value_offset), (int) m_length - value_offset, charsetName);
|
||||
}
|
||||
|
||||
public StringBuilder toJsonString(StringBuilder buf, String charsetName) {
|
||||
switch (m_type) {
|
||||
case OBJECT:
|
||||
buf.append("{");
|
||||
for (int i = 0; i < m_element_count; ++i) {
|
||||
if (i > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append('"').append(key(i, charsetName)).append('"');
|
||||
buf.append(": ");
|
||||
element(i, charsetName).toJsonString(buf, charsetName);
|
||||
}
|
||||
buf.append("}");
|
||||
break;
|
||||
case ARRAY:
|
||||
buf.append("[");
|
||||
for (int i = 0; i < m_element_count; ++i) {
|
||||
if (i > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
element(i, charsetName).toJsonString(buf, charsetName);
|
||||
}
|
||||
buf.append("]");
|
||||
break;
|
||||
case DOUBLE:
|
||||
buf.append(Double.valueOf(m_double_value).toString());
|
||||
break;
|
||||
case INT:
|
||||
buf.append(m_int_value.toString());
|
||||
break;
|
||||
case UINT:
|
||||
buf.append(m_int_value.toString());
|
||||
break;
|
||||
case LITERAL_FALSE:
|
||||
buf.append("false");
|
||||
break;
|
||||
case LITERAL_TRUE:
|
||||
buf.append("true");
|
||||
break;
|
||||
case LITERAL_NULL:
|
||||
buf.append("NULL");
|
||||
break;
|
||||
case OPAQUE:
|
||||
String text = null;
|
||||
if (m_field_type == LogEvent.MYSQL_TYPE_NEWDECIMAL) {
|
||||
int precision = m_data.getInt8();
|
||||
int scale = m_data.getInt8();
|
||||
text = m_data.getDecimal(precision, scale).toPlainString();
|
||||
buf.append(text);
|
||||
} else if (m_field_type == LogEvent.MYSQL_TYPE_TIME) {
|
||||
long packed_value = m_data.getLong64();
|
||||
if (packed_value == 0) {
|
||||
text = "00:00:00";
|
||||
} else {
|
||||
long ultime = Math.abs(packed_value);
|
||||
long intpart = ultime >> 24;
|
||||
int frac = (int) (ultime % (1L << 24));
|
||||
text = String.format("%s%02d:%02d:%02d",
|
||||
packed_value >= 0 ? "" : "-",
|
||||
(int) ((intpart >> 12) % (1 << 10)),
|
||||
(int) ((intpart >> 6) % (1 << 6)),
|
||||
(int) (intpart % (1 << 6)));
|
||||
text = text + "." + usecondsToStr(frac, 6);
|
||||
}
|
||||
buf.append('"').append(text).append('"');
|
||||
} else if (m_field_type == LogEvent.MYSQL_TYPE_DATE || m_field_type == LogEvent.MYSQL_TYPE_DATETIME
|
||||
|| m_field_type == LogEvent.MYSQL_TYPE_TIMESTAMP) {
|
||||
long packed_value = m_data.getLong64();
|
||||
if (packed_value == 0) {
|
||||
text = "0000-00-00 00:00:00";
|
||||
} else {
|
||||
// 构造TimeStamp只处理到秒
|
||||
long ultime = Math.abs(packed_value);
|
||||
long intpart = ultime >> 24;
|
||||
int frac = (int) (ultime % (1L << 24));
|
||||
long ymd = intpart >> 17;
|
||||
long ym = ymd >> 5;
|
||||
long hms = intpart % (1 << 17);
|
||||
text = String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
||||
(int) (ym / 13),
|
||||
(int) (ym % 13),
|
||||
(int) (ymd % (1 << 5)),
|
||||
(int) (hms >> 12),
|
||||
(int) ((hms >> 6) % (1 << 6)),
|
||||
(int) (hms % (1 << 6)));
|
||||
text = text + "." + usecondsToStr(frac, 6);
|
||||
}
|
||||
buf.append('"').append(text).append('"');
|
||||
} else {
|
||||
text = m_data.getFixString((int) m_length, charsetName);
|
||||
buf.append('"').append(escapse(text)).append('"');
|
||||
}
|
||||
|
||||
break;
|
||||
case STRING:
|
||||
buf.append('"').append(escapse(m_string_value)).append('"');
|
||||
break;
|
||||
case ERROR:
|
||||
throw new IllegalArgumentException("illegal json data");
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
private static StringBuilder escapse(String data) {
|
||||
StringBuilder sb = new StringBuilder(data.length());
|
||||
int endIndex = data.length();
|
||||
for (int i = 0; i < endIndex; ++i) {
|
||||
char c = data.charAt(i);
|
||||
if (c == '"') {
|
||||
sb.append('\\');
|
||||
} else if (c == '\\') {
|
||||
sb.append("\\");
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
public static enum Json_enum_type {
|
||||
OBJECT, ARRAY, STRING, INT, UINT, DOUBLE, LITERAL_NULL, LITERAL_TRUE, LITERAL_FALSE, OPAQUE, ERROR
|
||||
}
|
||||
|
||||
private static String usecondsToStr(int frac, int meta) {
|
||||
String sec = String.valueOf(frac);
|
||||
if (meta > 6) {
|
||||
throw new IllegalArgumentException("unknow useconds meta : " + meta);
|
||||
}
|
||||
|
||||
if (sec.length() < 6) {
|
||||
StringBuilder result = new StringBuilder(6);
|
||||
int len = 6 - sec.length();
|
||||
for (; len > 0; len--) {
|
||||
result.append('0');
|
||||
}
|
||||
result.append(sec);
|
||||
sec = result.toString();
|
||||
}
|
||||
|
||||
return sec.substring(0, meta);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ public class LogBuffer {
|
||||
|
||||
protected int origin, limit;
|
||||
protected int position;
|
||||
protected int semival;
|
||||
|
||||
protected LogBuffer(){
|
||||
}
|
||||
@@ -168,8 +169,8 @@ public class LogBuffer {
|
||||
* @param newLimit The new limit value; must be non-negative and no larger
|
||||
* than this buffer's capacity
|
||||
* @return This buffer
|
||||
* @throws IllegalArgumentException If the preconditions on <tt>newLimit</tt>
|
||||
* do not hold
|
||||
* @throws IllegalArgumentException If the preconditions on
|
||||
* <tt>newLimit</tt> do not hold
|
||||
*/
|
||||
public final LogBuffer limit(int newLimit) {
|
||||
if (origin + newLimit > buffer.length || newLimit < 0) throw new IllegalArgumentException("capacity excceed: "
|
||||
|
||||
@@ -104,9 +104,14 @@ public final class LogDecoder {
|
||||
try {
|
||||
/* Decoding binary-log to event */
|
||||
event = decode(buffer, header, context);
|
||||
if (event != null) {
|
||||
event.setSemival(buffer.semival);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (logger.isWarnEnabled()) logger.warn("Decoding " + LogEvent.getTypeName(header.getType())
|
||||
+ " failed from: " + context.getLogPosition(), e);
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Decoding " + LogEvent.getTypeName(header.getType()) + " failed from: "
|
||||
+ context.getLogPosition(), e);
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
buffer.limit(limit); /* Restore limit */
|
||||
|
||||
@@ -267,6 +267,7 @@ public abstract class LogEvent {
|
||||
public static final int MYSQL_TYPE_TIMESTAMP2 = 17;
|
||||
public static final int MYSQL_TYPE_DATETIME2 = 18;
|
||||
public static final int MYSQL_TYPE_TIME2 = 19;
|
||||
public static final int MYSQL_TYPE_JSON = 245;
|
||||
public static final int MYSQL_TYPE_NEWDECIMAL = 246;
|
||||
public static final int MYSQL_TYPE_ENUM = 247;
|
||||
public static final int MYSQL_TYPE_SET = 248;
|
||||
@@ -358,8 +359,28 @@ public abstract class LogEvent {
|
||||
protected static final Log logger = LogFactory.getLog(LogEvent.class);
|
||||
|
||||
protected final LogHeader header;
|
||||
|
||||
/**
|
||||
* mysql半同步semi标识
|
||||
*
|
||||
* <pre>
|
||||
* 0不需要semi ack 给mysql
|
||||
* 1需要semi ack给mysql
|
||||
* </pre>
|
||||
*/
|
||||
protected int semival;
|
||||
|
||||
|
||||
|
||||
protected LogEvent(LogHeader header){
|
||||
public int getSemival() {
|
||||
return semival;
|
||||
}
|
||||
|
||||
public void setSemival(int semival) {
|
||||
this.semival = semival;
|
||||
}
|
||||
|
||||
protected LogEvent(LogHeader 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){
|
||||
this.binlogVersion = binlogVersion;
|
||||
|
||||
|
||||
@@ -330,17 +330,14 @@ public class QueryLogEvent extends LogEvent {
|
||||
* MariaDb type,
|
||||
* sec_part of NOW()
|
||||
*/
|
||||
+ 1 + (MAX_DBS_IN_EVENT_MTS * (1 + NAME_LEN)) + 3 + 1 + 16 + 1 + 60/*
|
||||
* type
|
||||
* ,
|
||||
* user_len
|
||||
* ,
|
||||
* user
|
||||
* ,
|
||||
* host_len
|
||||
* ,
|
||||
* host
|
||||
*/);
|
||||
+ 1 + (MAX_DBS_IN_EVENT_MTS * (1 + NAME_LEN)) + 3 + 1 + 32 * 3
|
||||
+ 1 + 60/*
|
||||
* type ,
|
||||
* user_len ,
|
||||
* user ,
|
||||
* host_len ,
|
||||
* host
|
||||
*/);
|
||||
/**
|
||||
* Fixed data part:
|
||||
* <ul>
|
||||
@@ -397,7 +394,7 @@ public class QueryLogEvent extends LogEvent {
|
||||
// inspection by the DBA
|
||||
private final long execTime;
|
||||
private final int errorCode;
|
||||
private final long sessionId; /* thread_id */
|
||||
private final long sessionId; /* thread_id */
|
||||
|
||||
/**
|
||||
* 'flags2' is a second set of flags (on top of those in Log_event), for
|
||||
@@ -499,26 +496,26 @@ public class QueryLogEvent extends LogEvent {
|
||||
}
|
||||
|
||||
/* query event post-header */
|
||||
public static final int Q_THREAD_ID_OFFSET = 0;
|
||||
public static final int Q_EXEC_TIME_OFFSET = 4;
|
||||
public static final int Q_DB_LEN_OFFSET = 8;
|
||||
public static final int Q_ERR_CODE_OFFSET = 9;
|
||||
public static final int Q_STATUS_VARS_LEN_OFFSET = 11;
|
||||
public static final int Q_DATA_OFFSET = QUERY_HEADER_LEN;
|
||||
public static final int Q_THREAD_ID_OFFSET = 0;
|
||||
public static final int Q_EXEC_TIME_OFFSET = 4;
|
||||
public static final int Q_DB_LEN_OFFSET = 8;
|
||||
public static final int Q_ERR_CODE_OFFSET = 9;
|
||||
public static final int Q_STATUS_VARS_LEN_OFFSET = 11;
|
||||
public static final int Q_DATA_OFFSET = QUERY_HEADER_LEN;
|
||||
|
||||
/* these are codes, not offsets; not more than 256 values (1 byte). */
|
||||
public static final int Q_FLAGS2_CODE = 0;
|
||||
public static final int Q_SQL_MODE_CODE = 1;
|
||||
public static final int Q_FLAGS2_CODE = 0;
|
||||
public static final int Q_SQL_MODE_CODE = 1;
|
||||
|
||||
/**
|
||||
* Q_CATALOG_CODE is catalog with end zero stored; it is used only by MySQL
|
||||
* 5.0.x where 0<=x<=3. We have to keep it to be able to replicate these old
|
||||
* masters.
|
||||
*/
|
||||
public static final int Q_CATALOG_CODE = 2;
|
||||
public static final int Q_AUTO_INCREMENT = 3;
|
||||
public static final int Q_CHARSET_CODE = 4;
|
||||
public static final int Q_TIME_ZONE_CODE = 5;
|
||||
public static final int Q_CATALOG_CODE = 2;
|
||||
public static final int Q_AUTO_INCREMENT = 3;
|
||||
public static final int Q_CHARSET_CODE = 4;
|
||||
public static final int Q_TIME_ZONE_CODE = 5;
|
||||
|
||||
/**
|
||||
* Q_CATALOG_NZ_CODE is catalog withOUT end zero stored; it is used by MySQL
|
||||
@@ -528,31 +525,47 @@ public class QueryLogEvent extends LogEvent {
|
||||
* would crash (segfault etc) because it would expect a 0 when there is
|
||||
* none.
|
||||
*/
|
||||
public static final int Q_CATALOG_NZ_CODE = 6;
|
||||
public static final int Q_CATALOG_NZ_CODE = 6;
|
||||
|
||||
public static final int Q_LC_TIME_NAMES_CODE = 7;
|
||||
public static final int Q_LC_TIME_NAMES_CODE = 7;
|
||||
|
||||
public static final int Q_CHARSET_DATABASE_CODE = 8;
|
||||
public static final int Q_CHARSET_DATABASE_CODE = 8;
|
||||
|
||||
public static final int Q_TABLE_MAP_FOR_UPDATE_CODE = 9;
|
||||
public static final int Q_TABLE_MAP_FOR_UPDATE_CODE = 9;
|
||||
|
||||
public static final int Q_MASTER_DATA_WRITTEN_CODE = 10;
|
||||
public static final int Q_MASTER_DATA_WRITTEN_CODE = 10;
|
||||
|
||||
public static final int Q_INVOKER = 11;
|
||||
public static final int Q_INVOKER = 11;
|
||||
|
||||
/**
|
||||
* Q_UPDATED_DB_NAMES status variable collects of the updated databases
|
||||
* total number and their names to be propagated to the slave in order to
|
||||
* facilitate the parallel applying of the Query events.
|
||||
*/
|
||||
public static final int Q_UPDATED_DB_NAMES = 12;
|
||||
public static final int Q_UPDATED_DB_NAMES = 12;
|
||||
|
||||
public static final int Q_MICROSECONDS = 13;
|
||||
public static final int Q_MICROSECONDS = 13;
|
||||
/**
|
||||
* A old (unused now) code for Query_log_event status similar to
|
||||
* G_COMMIT_TS.
|
||||
*/
|
||||
public static final int Q_COMMIT_TS = 14;
|
||||
/**
|
||||
* A code for Query_log_event status, similar to G_COMMIT_TS2.
|
||||
*/
|
||||
public static final int Q_COMMIT_TS2 = 15;
|
||||
/**
|
||||
* The master connection @@session.explicit_defaults_for_timestamp which is
|
||||
* recorded for queries, CREATE and ALTER table that is defined with a
|
||||
* TIMESTAMP column, that are dependent on that feature. For pre-WL6292
|
||||
* master's the associated with this code value is zero.
|
||||
*/
|
||||
public static final int Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP = 16;
|
||||
|
||||
/**
|
||||
* FROM MariaDB 5.5.34
|
||||
*/
|
||||
public static final int Q_HRNOW = 128;
|
||||
public static final int Q_HRNOW = 128;
|
||||
|
||||
private final void unpackVariables(LogBuffer buffer, final int end) throws IOException {
|
||||
int code = -1;
|
||||
@@ -632,6 +645,9 @@ public class QueryLogEvent extends LogEvent {
|
||||
mtsAccessedDbNames[i] = buffer.getFixString(length < NAME_LEN ? length : NAME_LEN);
|
||||
}
|
||||
break;
|
||||
case Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP:
|
||||
buffer.forward(1);
|
||||
break;
|
||||
case Q_HRNOW:
|
||||
// int when_sec_part = buffer.getUint24();
|
||||
buffer.forward(3);
|
||||
|
||||
@@ -90,6 +90,7 @@ public final class RotateLogEvent extends LogEvent {
|
||||
int filenameLen = buffer.limit() - filenameOffset;
|
||||
if (filenameLen > FN_REFLEN - 1) filenameLen = FN_REFLEN - 1;
|
||||
buffer.position(filenameOffset);
|
||||
|
||||
filename = buffer.getFixString(filenameLen);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import java.util.BitSet;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import com.taobao.tddl.dbsync.binlog.JsonConversion;
|
||||
import com.taobao.tddl.dbsync.binlog.JsonConversion.Json_Value;
|
||||
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||
|
||||
@@ -25,6 +27,7 @@ public final class RowsLogBuffer {
|
||||
public static final long DATETIMEF_INT_OFS = 0x8000000000L;
|
||||
public static final long TIMEF_INT_OFS = 0x800000L;
|
||||
public static final long TIMEF_OFS = 0x800000000000L;
|
||||
|
||||
private final LogBuffer buffer;
|
||||
private final int columnLen;
|
||||
private final String charsetName;
|
||||
@@ -163,10 +166,13 @@ public final class RowsLogBuffer {
|
||||
|
||||
case LogEvent.MYSQL_TYPE_TIMESTAMP:
|
||||
case LogEvent.MYSQL_TYPE_DATETIME:
|
||||
case LogEvent.MYSQL_TYPE_TIMESTAMP2:
|
||||
case LogEvent.MYSQL_TYPE_DATETIME2:
|
||||
javaType = Types.TIMESTAMP;
|
||||
break;
|
||||
|
||||
case LogEvent.MYSQL_TYPE_TIME:
|
||||
case LogEvent.MYSQL_TYPE_TIME2:
|
||||
javaType = Types.TIME;
|
||||
break;
|
||||
|
||||
@@ -942,6 +948,42 @@ public final class RowsLogBuffer {
|
||||
length = len;
|
||||
break;
|
||||
}
|
||||
case LogEvent.MYSQL_TYPE_JSON: {
|
||||
switch (meta) {
|
||||
case 1: {
|
||||
len = buffer.getUint8();
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
len = buffer.getUint16();
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
len = buffer.getUint24();
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
len = (int) buffer.getUint32();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException("!! Unknown JSON packlen = " + meta);
|
||||
}
|
||||
if (0 == len) {
|
||||
// fixed issue #1 by lava, json column of zero length has no value, value parsing should be skipped
|
||||
value = "";
|
||||
} else {
|
||||
int position = buffer.position();
|
||||
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(), buffer, len - 1, charsetName);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
jsonValue.toJsonString(builder, charsetName);
|
||||
value = builder.toString();
|
||||
buffer.position(position + len);
|
||||
}
|
||||
javaType = Types.VARCHAR;
|
||||
length = len;
|
||||
break;
|
||||
}
|
||||
case LogEvent.MYSQL_TYPE_GEOMETRY: {
|
||||
/*
|
||||
* MYSQL_TYPE_GEOMETRY: copy from BLOB or TEXT
|
||||
@@ -1023,4 +1065,5 @@ public final class RowsLogBuffer {
|
||||
|
||||
return sec.substring(0, meta);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -402,6 +402,7 @@ public final class TableMapLogEvent extends LogEvent {
|
||||
case MYSQL_TYPE_DOUBLE:
|
||||
case MYSQL_TYPE_FLOAT:
|
||||
case MYSQL_TYPE_GEOMETRY:
|
||||
case MYSQL_TYPE_JSON:
|
||||
/*
|
||||
* These types store a single byte.
|
||||
*/
|
||||
|
||||
@@ -13,9 +13,30 @@ import com.taobao.tddl.dbsync.binlog.event.LogHeader;
|
||||
*/
|
||||
public class MariaGtidLogEvent extends IgnorableLogEvent {
|
||||
|
||||
private long gtid;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* mariadb gtidlog event format
|
||||
* uint<8> GTID sequence
|
||||
* uint<4> Replication Domain ID
|
||||
* uint<1> Flags
|
||||
*
|
||||
* if flag & FL_GROUP_COMMIT_ID
|
||||
* uint<8> commit_id
|
||||
* else
|
||||
* uint<6> 0
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
public MariaGtidLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
||||
super(header, buffer, descriptionEvent);
|
||||
gtid = buffer.getUlong64().longValue();
|
||||
// do nothing , just ignore log event
|
||||
}
|
||||
|
||||
public long getGtid() {
|
||||
return gtid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -32,6 +32,8 @@ public class CanalConstants {
|
||||
|
||||
public static final String CANAL_DESTINATION_PROPERTY = ROOT + ".instance.destination";
|
||||
|
||||
public static final String CANAL_SOCKETCHANNEL = ROOT + "." + "socketChannel";
|
||||
|
||||
public static String getInstanceModeKey(String destination) {
|
||||
return MessageFormat.format(INSTANCE_MODE_TEMPLATE, destination);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Properties;
|
||||
|
||||
import org.I0Itec.zkclient.IZkStateListener;
|
||||
import org.I0Itec.zkclient.exception.ZkNoNodeException;
|
||||
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.zookeeper.Watcher.Event.KeeperState;
|
||||
@@ -84,13 +85,19 @@ public class CanalController {
|
||||
// 初始化instance config
|
||||
initInstanceConfig(properties);
|
||||
|
||||
// init socketChannel
|
||||
String socketChannel = getProperty(properties, CanalConstants.CANAL_SOCKETCHANNEL);
|
||||
if (StringUtils.isNotEmpty(socketChannel)) {
|
||||
System.setProperty(CanalConstants.CANAL_SOCKETCHANNEL, socketChannel);
|
||||
}
|
||||
|
||||
// 准备canal server
|
||||
cid = Long.valueOf(getProperty(properties, CanalConstants.CANAL_ID));
|
||||
ip = getProperty(properties, CanalConstants.CANAL_IP);
|
||||
port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT));
|
||||
embededCanalServer = new CanalServerWithEmbedded();
|
||||
embededCanalServer = CanalServerWithEmbedded.instance();
|
||||
embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator
|
||||
canalServer = new CanalServerWithNetty(embededCanalServer);
|
||||
canalServer = CanalServerWithNetty.instance();
|
||||
canalServer.setIp(ip);
|
||||
canalServer.setPort(port);
|
||||
|
||||
@@ -148,6 +155,11 @@ public class CanalController {
|
||||
public void handleNewSession() throws Exception {
|
||||
initCid(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSessionEstablishmentError(Throwable error) throws Exception {
|
||||
logger.error("failed to connect to zookeeper", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
@@ -172,6 +184,8 @@ public class CanalController {
|
||||
if (zkclientx != null) {
|
||||
runningMonitor.setZkClient(zkclientx);
|
||||
}
|
||||
// 触发创建一下cid节点
|
||||
runningMonitor.init();
|
||||
return runningMonitor;
|
||||
}
|
||||
}));
|
||||
@@ -189,10 +203,10 @@ public class CanalController {
|
||||
instanceConfigs.put(destination, config);
|
||||
}
|
||||
|
||||
if (!config.getLazy() && !embededCanalServer.isStart(destination)) {
|
||||
if (!embededCanalServer.isStart(destination)) {
|
||||
// HA机制启动
|
||||
ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination);
|
||||
if (!runningMonitor.isStart()) {
|
||||
if (!config.getLazy() && !runningMonitor.isStart()) {
|
||||
runningMonitor.start();
|
||||
}
|
||||
}
|
||||
@@ -231,7 +245,13 @@ public class CanalController {
|
||||
if (StringUtils.isEmpty(rootDir)) {
|
||||
rootDir = "../conf";
|
||||
}
|
||||
monitor.setRootConf(rootDir);
|
||||
|
||||
if (StringUtils.equals("otter-canal", System.getProperty("appName"))) {
|
||||
monitor.setRootConf(rootDir);
|
||||
} else {
|
||||
// eclipse debug模式
|
||||
monitor.setRootConf("src/main/resources/");
|
||||
}
|
||||
return monitor;
|
||||
} else if (mode.isManager()) {
|
||||
return new ManagerInstanceConfigMonitor();
|
||||
@@ -374,6 +394,11 @@ public class CanalController {
|
||||
public void handleNewSession() throws Exception {
|
||||
initCid(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSessionEstablishmentError(Throwable error) throws Exception {
|
||||
logger.error("failed to connect to zookeeper", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 优先启动embeded服务
|
||||
@@ -383,10 +408,10 @@ public class CanalController {
|
||||
final String destination = entry.getKey();
|
||||
InstanceConfig config = entry.getValue();
|
||||
// 创建destination的工作节点
|
||||
if (!config.getLazy() && !embededCanalServer.isStart(destination)) {
|
||||
if (!embededCanalServer.isStart(destination)) {
|
||||
// HA机制启动
|
||||
ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination);
|
||||
if (!runningMonitor.isStart()) {
|
||||
if (!config.getLazy() && !runningMonitor.isStart()) {
|
||||
runningMonitor.start();
|
||||
}
|
||||
}
|
||||
@@ -442,6 +467,9 @@ public class CanalController {
|
||||
String parentDir = path.substring(0, path.lastIndexOf('/'));
|
||||
zkclientx.createPersistent(parentDir, true);
|
||||
zkclientx.createEphemeral(path);
|
||||
} catch (ZkNodeExistsException e) {
|
||||
// ignore
|
||||
// 因为第一次启动时创建了cid,但在stop/start的时可能会关闭和新建,允许出现NodeExists问题s
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.io.FileInputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -21,6 +20,10 @@ public class CanalLauncher {
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
try {
|
||||
logger.info("## set default uncaught exception handler");
|
||||
setGlobalUncaughtExceptionHandler();
|
||||
|
||||
logger.info("## load canal configurations");
|
||||
String conf = System.getProperty("canal.conf", "classpath:canal.properties");
|
||||
Properties properties = new Properties();
|
||||
if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
|
||||
@@ -41,8 +44,7 @@ public class CanalLauncher {
|
||||
logger.info("## stop the canal server");
|
||||
controller.stop();
|
||||
} catch (Throwable e) {
|
||||
logger.warn("##something goes wrong when stopping canal Server:\n{}",
|
||||
ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn("##something goes wrong when stopping canal Server:", e);
|
||||
} finally {
|
||||
logger.info("## canal server is down.");
|
||||
}
|
||||
@@ -50,9 +52,17 @@ public class CanalLauncher {
|
||||
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
logger.error("## Something goes wrong when starting up the canal Server:\n{}",
|
||||
ExceptionUtils.getFullStackTrace(e));
|
||||
logger.error("## Something goes wrong when starting up the canal Server:", e);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setGlobalUncaughtExceptionHandler() {
|
||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
logger.error("UnCaughtException", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -49,6 +48,8 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1,
|
||||
new NamedThreadFactory("canal-instance-scan"));
|
||||
|
||||
private volatile boolean isFirst = true;
|
||||
|
||||
public void start() {
|
||||
super.start();
|
||||
Assert.notNull(rootConf, "root conf dir is null!");
|
||||
@@ -58,6 +59,9 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
public void run() {
|
||||
try {
|
||||
scan();
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
logger.error("scan failed", e);
|
||||
}
|
||||
@@ -122,13 +126,18 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
|
||||
if (!actions.containsKey(destination) && instanceConfigs.length > 0) {
|
||||
// 存在合法的instance.properties,并且第一次添加时,进行启动操作
|
||||
notifyStart(instanceDir, destination);
|
||||
notifyStart(instanceDir, destination, instanceConfigs);
|
||||
} else if (actions.containsKey(destination)) {
|
||||
// 历史已经启动过
|
||||
if (instanceConfigs.length == 0) { // 如果不存在合法的instance.properties
|
||||
notifyStop(destination);
|
||||
} else {
|
||||
InstanceConfigFiles lastFile = lastFiles.get(destination);
|
||||
// 历史启动过 所以配置文件信息必然存在
|
||||
if (!isFirst && CollectionUtils.isEmpty(lastFile.getInstanceFiles())) {
|
||||
logger.error("[{}] is started, but not found instance file info.", destination);
|
||||
}
|
||||
|
||||
boolean hasChanged = judgeFileChanged(instanceConfigs, lastFile.getInstanceFiles());
|
||||
// 通知变化
|
||||
if (hasChanged) {
|
||||
@@ -161,13 +170,22 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyStart(File instanceDir, String destination) {
|
||||
private void notifyStart(File instanceDir, String destination, File[] instanceConfigs) {
|
||||
try {
|
||||
defaultAction.start(destination);
|
||||
actions.put(destination, defaultAction);
|
||||
|
||||
// 启动成功后记录配置文件信息
|
||||
InstanceConfigFiles lastFile = lastFiles.get(destination);
|
||||
List<FileInfo> newFileInfo = new ArrayList<FileInfo>();
|
||||
for (File instanceConfig : instanceConfigs) {
|
||||
newFileInfo.add(new FileInfo(instanceConfig.getName(), instanceConfig.lastModified()));
|
||||
}
|
||||
lastFile.setInstanceFiles(newFileInfo);
|
||||
|
||||
logger.info("auto notify start {} successful.", destination);
|
||||
} catch (Throwable e) {
|
||||
logger.error("scan add found[{}] but start failed", destination, ExceptionUtils.getFullStackTrace(e));
|
||||
logger.error(String.format("scan add found[%s] but start failed", destination), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,9 +193,10 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
InstanceAction action = actions.remove(destination);
|
||||
try {
|
||||
action.stop(destination);
|
||||
lastFiles.remove(destination);
|
||||
logger.info("auto notify stop {} successful.", destination);
|
||||
} catch (Throwable e) {
|
||||
logger.error("scan delete found[{}] but stop failed", destination, ExceptionUtils.getFullStackTrace(e));
|
||||
logger.error(String.format("scan delete found[%s] but stop failed", destination), e);
|
||||
actions.put(destination, action);// 再重新加回去,下一次scan时再执行删除
|
||||
}
|
||||
}
|
||||
@@ -189,9 +208,7 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
action.reload(destination);
|
||||
logger.info("auto notify reload {} successful.", destination);
|
||||
} catch (Throwable e) {
|
||||
logger.error("scan reload found[{}] but reload failed",
|
||||
destination,
|
||||
ExceptionUtils.getFullStackTrace(e));
|
||||
logger.error(String.format("scan reload found[%s] but reload failed", destination), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -294,5 +311,4 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,10 +36,12 @@ canal.instance.network.sendBufferSize = 16384
|
||||
canal.instance.network.soTimeout = 30
|
||||
|
||||
# binlog filter config
|
||||
canal.instance.filter.druid.ddl = true
|
||||
canal.instance.filter.query.dcl = false
|
||||
canal.instance.filter.query.dml = false
|
||||
canal.instance.filter.query.ddl = false
|
||||
canal.instance.filter.table.error = false
|
||||
canal.instance.filter.rows = false
|
||||
|
||||
# binlog format/image check
|
||||
canal.instance.binlog.format = ROW,STATEMENT,MIXED
|
||||
@@ -58,9 +60,13 @@ canal.conf.dir = ../conf
|
||||
canal.auto.scan = true
|
||||
canal.auto.scan.interval = 5
|
||||
|
||||
canal.instance.tsdb.spring.xml=classpath:spring/tsdb/h2-tsdb.xml
|
||||
#canal.instance.tsdb.spring.xml=classpath:spring/tsdb/mysql-tsdb.xml
|
||||
|
||||
canal.instance.global.mode = spring
|
||||
canal.instance.global.lazy = false
|
||||
#canal.instance.global.manager.address = 127.0.0.1:1099
|
||||
#canal.instance.global.spring.xml = classpath:spring/local-instance.xml
|
||||
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
|
||||
canal.instance.global.spring.xml = classpath:spring/file-instance.xml
|
||||
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
|
||||
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
|
||||
|
||||
@@ -1,27 +1,33 @@
|
||||
#################################################
|
||||
## mysql serverId
|
||||
canal.instance.mysql.slaveId = 1234
|
||||
|
||||
canal.instance.mysql.slaveId=0
|
||||
# position info
|
||||
canal.instance.master.address = 127.0.0.1:3306
|
||||
canal.instance.master.journal.name =
|
||||
canal.instance.master.position =
|
||||
canal.instance.master.timestamp =
|
||||
canal.instance.master.address=127.0.0.1:3306
|
||||
canal.instance.master.journal.name=
|
||||
canal.instance.master.position=
|
||||
canal.instance.master.timestamp=
|
||||
|
||||
#canal.instance.standby.address =
|
||||
|
||||
# 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.journal.name =
|
||||
#canal.instance.standby.position =
|
||||
#canal.instance.standby.timestamp =
|
||||
|
||||
# username/password
|
||||
canal.instance.dbUsername = canal
|
||||
canal.instance.dbPassword = canal
|
||||
canal.instance.defaultDatabaseName =
|
||||
canal.instance.connectionCharset = UTF-8
|
||||
|
||||
canal.instance.dbUsername=canal
|
||||
canal.instance.dbPassword=canal
|
||||
canal.instance.defaultDatabaseName=test
|
||||
canal.instance.connectionCharset=UTF-8
|
||||
# table regex
|
||||
canal.instance.filter.regex = .*\\..*
|
||||
canal.instance.filter.regex=.*\\..*
|
||||
# table black regex
|
||||
canal.instance.filter.black.regex =
|
||||
|
||||
canal.instance.filter.black.regex=
|
||||
#################################################
|
||||
32
deployer/src/main/resources/example/rds_instance.properties
Normal file
32
deployer/src/main/resources/example/rds_instance.properties
Normal file
@@ -0,0 +1,32 @@
|
||||
#################################################
|
||||
# rds openapi binlog
|
||||
canal.instance.rds.open.url=https://rds.aliyuncs.com/
|
||||
canal.instance.rds.open.accesskey=
|
||||
canal.instance.rds.open.secretkey=
|
||||
canal.instance.rds.instanceId=
|
||||
canal.instance.rds.startTime=
|
||||
canal.instance.rds.endTime=
|
||||
|
||||
# local binlog dir
|
||||
canal.instance.parser.directory=${canal.file.data.dir:../conf}/${canal.instance.destination:}/binlog
|
||||
# position info
|
||||
canal.instance.master.address=127.0.0.1:3306
|
||||
|
||||
# table meta tsdb info
|
||||
canal.instance.tsdb.enable=true
|
||||
canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:}
|
||||
canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
|
||||
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
|
||||
#canal.instance.tsdb.dbUsername=canal
|
||||
#canal.instance.tsdb.dbPassword=canal
|
||||
|
||||
# username/password
|
||||
canal.instance.dbUsername=canal
|
||||
canal.instance.dbPassword=canal
|
||||
canal.instance.defaultDatabaseName=test
|
||||
canal.instance.connectionCharset=UTF-8
|
||||
# table regex
|
||||
canal.instance.filter.regex=test\\..*
|
||||
# table black regex
|
||||
canal.instance.filter.black.regex=
|
||||
#################################################
|
||||
@@ -75,9 +75,7 @@
|
||||
</logger>
|
||||
|
||||
<root level="WARN">
|
||||
<!--
|
||||
<appender-ref ref="STDOUT"/>
|
||||
-->
|
||||
<appender-ref ref="CANAL-ROOT" />
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||
@@ -128,14 +128,14 @@
|
||||
<!-- 解析位点记录 -->
|
||||
<property name="logPositionManager">
|
||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||
<property name="primary">
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||
</property>
|
||||
<property name="failback">
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||
<property name="metaManager" ref="metaManager" />
|
||||
<constructor-arg ref="metaManager"/>
|
||||
</bean>
|
||||
</property>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
@@ -178,8 +178,14 @@
|
||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
|
||||
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
|
||||
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||
|
||||
<!--表结构相关-->
|
||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||
</bean>
|
||||
</beans>
|
||||
</beans>
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||
@@ -113,14 +113,14 @@
|
||||
<!-- 解析位点记录 -->
|
||||
<property name="logPositionManager">
|
||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||
<property name="primary">
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||
</property>
|
||||
<property name="failback">
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||
<property name="metaManager" ref="metaManager" />
|
||||
<constructor-arg ref="metaManager"/>
|
||||
</bean>
|
||||
</property>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
@@ -163,8 +163,14 @@
|
||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
|
||||
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
|
||||
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||
|
||||
<!--表结构相关-->
|
||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||
</bean>
|
||||
</beans>
|
||||
</beans>
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
|
||||
<bean id="eventParser1" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||
@@ -160,6 +160,7 @@
|
||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
|
||||
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
|
||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||
@@ -167,7 +168,7 @@
|
||||
|
||||
<bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||
@@ -250,8 +251,10 @@
|
||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
|
||||
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
|
||||
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||
</bean>
|
||||
</beans>
|
||||
</beans>
|
||||
|
||||
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">
|
||||
<property name="destination" value="${canal.instance.destination}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
|
||||
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
|
||||
<!-- 心跳配置 -->
|
||||
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
|
||||
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
|
||||
@@ -151,8 +151,14 @@
|
||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
|
||||
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
|
||||
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
|
||||
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
|
||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||
|
||||
<!--表结构相关-->
|
||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:false}"/>
|
||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||
</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>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
@@ -33,10 +33,6 @@
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.netty</groupId>
|
||||
<artifactId>netty</artifactId>
|
||||
</dependency>
|
||||
<!-- test dependency -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
||||
@@ -2,19 +2,19 @@ package com.alibaba.otter.canal.parse.driver.mysql;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.ClientAuthenticationPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.QuitCommandPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ErrorPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.HandshakeInitializationPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.Reply323Packet;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannelPool;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.MySQLPasswordEncrypter;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
||||
|
||||
@@ -34,18 +34,26 @@ public class MysqlConnector {
|
||||
private byte charsetNumber = 33;
|
||||
private String defaultSchema = "retl";
|
||||
private int soTimeout = 30 * 1000;
|
||||
private int connTimeout = 5 * 1000;
|
||||
private int receiveBufferSize = 16 * 1024;
|
||||
private int sendBufferSize = 16 * 1024;
|
||||
|
||||
private SocketChannel channel;
|
||||
private volatile boolean dumping = false;
|
||||
// mysql connectinnId
|
||||
private long connectionId = -1;
|
||||
private AtomicBoolean connected = new AtomicBoolean(false);
|
||||
|
||||
public static final int timeout = 5 * 1000; // 5s
|
||||
|
||||
public MysqlConnector(){
|
||||
}
|
||||
|
||||
public MysqlConnector(InetSocketAddress address, String username, String password){
|
||||
|
||||
this.address = address;
|
||||
String addr = address.getHostString();
|
||||
int port = address.getPort();
|
||||
this.address = new InetSocketAddress(addr, port);
|
||||
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
@@ -61,14 +69,12 @@ public class MysqlConnector {
|
||||
public void connect() throws IOException {
|
||||
if (connected.compareAndSet(false, true)) {
|
||||
try {
|
||||
channel = SocketChannel.open();
|
||||
configChannel(channel);
|
||||
channel = SocketChannelPool.open(address);
|
||||
logger.info("connect MysqlConnection to {}...", address);
|
||||
channel.connect(address);
|
||||
negotiate(channel);
|
||||
} catch (Exception e) {
|
||||
disconnect();
|
||||
throw new IOException("connect " + this.address + " failure:" + ExceptionUtils.getStackTrace(e));
|
||||
throw new IOException("connect " + this.address + " failure", e);
|
||||
}
|
||||
} else {
|
||||
logger.error("the channel can't be connected twice.");
|
||||
@@ -86,10 +92,29 @@ public class MysqlConnector {
|
||||
if (channel != null) {
|
||||
channel.close();
|
||||
}
|
||||
|
||||
logger.info("disConnect MysqlConnection to {}...", address);
|
||||
} catch (Exception e) {
|
||||
throw new IOException("disconnect " + this.address + " failure:" + ExceptionUtils.getStackTrace(e));
|
||||
throw new IOException("disconnect " + this.address + " failure", e);
|
||||
}
|
||||
|
||||
// 执行一次quit
|
||||
if (dumping && connectionId >= 0) {
|
||||
MysqlConnector connector = null;
|
||||
try {
|
||||
connector = this.fork();
|
||||
connector.connect();
|
||||
MysqlUpdateExecutor executor = new MysqlUpdateExecutor(connector);
|
||||
executor.update("KILL CONNECTION " + connectionId);
|
||||
} catch (Exception e) {
|
||||
// 忽略具体异常
|
||||
logger.info("KILL DUMP " + connectionId + " failure", e);
|
||||
} finally {
|
||||
if (connector != null) {
|
||||
connector.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
dumping = false;
|
||||
}
|
||||
} else {
|
||||
logger.info("the channel {} is not connected", this.address);
|
||||
@@ -110,23 +135,23 @@ public class MysqlConnector {
|
||||
connector.setReceiveBufferSize(getReceiveBufferSize());
|
||||
connector.setSendBufferSize(getSendBufferSize());
|
||||
connector.setSoTimeout(getSoTimeout());
|
||||
connector.setConnTimeout(connTimeout);
|
||||
return connector;
|
||||
}
|
||||
|
||||
// ====================== help method ====================
|
||||
public void quit() throws IOException {
|
||||
QuitCommandPacket quit = new QuitCommandPacket();
|
||||
byte[] cmdBody = quit.toBytes();
|
||||
|
||||
private void configChannel(SocketChannel channel) throws IOException {
|
||||
channel.socket().setKeepAlive(true);
|
||||
channel.socket().setReuseAddress(true);
|
||||
channel.socket().setSoTimeout(soTimeout);
|
||||
channel.socket().setTcpNoDelay(true);
|
||||
channel.socket().setReceiveBufferSize(receiveBufferSize);
|
||||
channel.socket().setSendBufferSize(sendBufferSize);
|
||||
HeaderPacket quitHeader = new HeaderPacket();
|
||||
quitHeader.setPacketBodyLength(cmdBody.length);
|
||||
quitHeader.setPacketSequenceNumber((byte) 0x00);
|
||||
PacketManager.writePkg(channel, quitHeader.toBytes(), cmdBody);
|
||||
}
|
||||
|
||||
private void negotiate(SocketChannel channel) throws IOException {
|
||||
HeaderPacket header = PacketManager.readHeader(channel, 4);
|
||||
byte[] body = PacketManager.readBytes(channel, header.getPacketBodyLength());
|
||||
HeaderPacket header = PacketManager.readHeader(channel, 4, timeout);
|
||||
byte[] body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
||||
if (body[0] < 0) {// check field_count
|
||||
if (body[0] == -1) {
|
||||
ErrorPacket error = new ErrorPacket();
|
||||
@@ -140,6 +165,7 @@ public class MysqlConnector {
|
||||
}
|
||||
HandshakeInitializationPacket handshakePacket = new HandshakeInitializationPacket();
|
||||
handshakePacket.fromBytes(body);
|
||||
connectionId = handshakePacket.threadId; // 记录一下connection
|
||||
|
||||
logger.info("handshake initialization packet received, prepare the client authentication packet to send");
|
||||
|
||||
@@ -157,15 +183,14 @@ public class MysqlConnector {
|
||||
h.setPacketBodyLength(clientAuthPkgBody.length);
|
||||
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
|
||||
|
||||
PacketManager.write(channel,
|
||||
new ByteBuffer[] { ByteBuffer.wrap(h.toBytes()), ByteBuffer.wrap(clientAuthPkgBody) });
|
||||
PacketManager.writePkg(channel, h.toBytes(), clientAuthPkgBody);
|
||||
logger.info("client authentication packet is sent out.");
|
||||
|
||||
// check auth result
|
||||
header = null;
|
||||
header = PacketManager.readHeader(channel, 4);
|
||||
body = null;
|
||||
body = PacketManager.readBytes(channel, header.getPacketBodyLength());
|
||||
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
||||
assert body != null;
|
||||
if (body[0] < 0) {
|
||||
if (body[0] == -1) {
|
||||
@@ -194,7 +219,7 @@ public class MysqlConnector {
|
||||
h323.setPacketBodyLength(b323Body.length);
|
||||
h323.setPacketSequenceNumber((byte) (packetSequenceNumber + 1));
|
||||
|
||||
PacketManager.write(channel, new ByteBuffer[] { ByteBuffer.wrap(h323.toBytes()), ByteBuffer.wrap(b323Body) });
|
||||
PacketManager.writePkg(channel, h323.toBytes(), b323Body);
|
||||
logger.info("client 323 authentication packet is sent out.");
|
||||
// check auth result
|
||||
HeaderPacket header = PacketManager.readHeader(channel, 4);
|
||||
@@ -291,4 +316,32 @@ public class MysqlConnector {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public long getConnectionId() {
|
||||
return connectionId;
|
||||
}
|
||||
|
||||
public void setConnectionId(long connectionId) {
|
||||
this.connectionId = connectionId;
|
||||
}
|
||||
|
||||
public boolean isDumping() {
|
||||
return dumping;
|
||||
}
|
||||
|
||||
public void setDumping(boolean dumping) {
|
||||
this.dumping = dumping;
|
||||
}
|
||||
|
||||
public int getConnTimeout() {
|
||||
return connTimeout;
|
||||
}
|
||||
|
||||
public void setConnTimeout(int connTimeout) {
|
||||
this.connTimeout = connTimeout;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.QueryCommandPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.EOFPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ErrorPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetHeaderPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.RowDataPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
||||
|
||||
/**
|
||||
@@ -24,9 +25,9 @@ public class MysqlQueryExecutor {
|
||||
|
||||
private SocketChannel channel;
|
||||
|
||||
public MysqlQueryExecutor(MysqlConnector connector){
|
||||
public MysqlQueryExecutor(MysqlConnector connector) throws IOException{
|
||||
if (!connector.isConnected()) {
|
||||
throw new RuntimeException("should execute connector.connect() first");
|
||||
throw new IOException("should execute connector.connect() first");
|
||||
}
|
||||
|
||||
this.channel = connector.getChannel();
|
||||
@@ -51,7 +52,7 @@ public class MysqlQueryExecutor {
|
||||
QueryCommandPacket cmd = new QueryCommandPacket();
|
||||
cmd.setQueryString(queryString);
|
||||
byte[] bodyBytes = cmd.toBytes();
|
||||
PacketManager.write(channel, bodyBytes);
|
||||
PacketManager.writeBody(channel, bodyBytes);
|
||||
byte[] body = readNextPacket();
|
||||
|
||||
if (body[0] < 0) {
|
||||
@@ -88,16 +89,70 @@ public class MysqlQueryExecutor {
|
||||
for (RowDataPacket r : rowData) {
|
||||
resultSet.getFieldValues().addAll(r.getColumns());
|
||||
}
|
||||
resultSet.setSourceAddress(channel.socket().getRemoteSocketAddress());
|
||||
resultSet.setSourceAddress(channel.getRemoteSocketAddress());
|
||||
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
private void readEofPacket() throws IOException {
|
||||
public List<ResultSetPacket> queryMulti(String queryString) throws IOException {
|
||||
QueryCommandPacket cmd = new QueryCommandPacket();
|
||||
cmd.setQueryString(queryString);
|
||||
byte[] bodyBytes = cmd.toBytes();
|
||||
PacketManager.writeBody(channel, bodyBytes);
|
||||
List<ResultSetPacket> resultSets = new ArrayList<ResultSetPacket>();
|
||||
boolean moreResult = true;
|
||||
while (moreResult) {
|
||||
byte[] body = readNextPacket();
|
||||
if (body[0] < 0) {
|
||||
ErrorPacket packet = new ErrorPacket();
|
||||
packet.fromBytes(body);
|
||||
throw new IOException(packet + "\n with command: " + queryString);
|
||||
}
|
||||
|
||||
ResultSetHeaderPacket rsHeader = new ResultSetHeaderPacket();
|
||||
rsHeader.fromBytes(body);
|
||||
|
||||
List<FieldPacket> fields = new ArrayList<FieldPacket>();
|
||||
for (int i = 0; i < rsHeader.getColumnCount(); i++) {
|
||||
FieldPacket fp = new FieldPacket();
|
||||
fp.fromBytes(readNextPacket());
|
||||
fields.add(fp);
|
||||
}
|
||||
|
||||
moreResult = readEofPacket();
|
||||
|
||||
List<RowDataPacket> rowData = new ArrayList<RowDataPacket>();
|
||||
while (true) {
|
||||
body = readNextPacket();
|
||||
if (body[0] == -2) {
|
||||
break;
|
||||
}
|
||||
RowDataPacket rowDataPacket = new RowDataPacket();
|
||||
rowDataPacket.fromBytes(body);
|
||||
rowData.add(rowDataPacket);
|
||||
}
|
||||
|
||||
ResultSetPacket resultSet = new ResultSetPacket();
|
||||
resultSet.getFieldDescriptors().addAll(fields);
|
||||
for (RowDataPacket r : rowData) {
|
||||
resultSet.getFieldValues().addAll(r.getColumns());
|
||||
}
|
||||
resultSet.setSourceAddress(channel.getRemoteSocketAddress());
|
||||
resultSets.add(resultSet);
|
||||
}
|
||||
|
||||
return resultSets;
|
||||
}
|
||||
|
||||
private boolean readEofPacket() throws IOException {
|
||||
byte[] eofBody = readNextPacket();
|
||||
EOFPacket packet = new EOFPacket();
|
||||
packet.fromBytes(eofBody);
|
||||
if (eofBody[0] != -2) {
|
||||
throw new IOException("EOF Packet is expected, but packet with field_count=" + eofBody[0] + " is found.");
|
||||
}
|
||||
|
||||
return (packet.statusFlag & 0x0008) != 0;
|
||||
}
|
||||
|
||||
protected byte[] readNextPacket() throws IOException {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -21,28 +20,29 @@ public class MysqlUpdateExecutor {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MysqlUpdateExecutor.class);
|
||||
|
||||
private SocketChannel channel;
|
||||
private MysqlConnector connector;
|
||||
|
||||
public MysqlUpdateExecutor(MysqlConnector connector){
|
||||
public MysqlUpdateExecutor(MysqlConnector connector) throws IOException{
|
||||
if (!connector.isConnected()) {
|
||||
throw new RuntimeException("should execute connector.connect() first");
|
||||
throw new IOException("should execute connector.connect() first");
|
||||
}
|
||||
|
||||
this.channel = connector.getChannel();
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
public MysqlUpdateExecutor(SocketChannel ch){
|
||||
this.channel = ch;
|
||||
}
|
||||
/*
|
||||
* public MysqlUpdateExecutor(SocketChannel ch){ this.channel = ch; }
|
||||
*/
|
||||
|
||||
public OKPacket update(String updateString) throws IOException {
|
||||
QueryCommandPacket cmd = new QueryCommandPacket();
|
||||
cmd.setQueryString(updateString);
|
||||
byte[] bodyBytes = cmd.toBytes();
|
||||
PacketManager.write(channel, bodyBytes);
|
||||
PacketManager.writeBody(connector.getChannel(), bodyBytes);
|
||||
|
||||
logger.debug("read update result...");
|
||||
byte[] body = PacketManager.readBytes(channel, PacketManager.readHeader(channel, 4).getPacketBodyLength());
|
||||
byte[] body = PacketManager.readBytes(connector.getChannel(),
|
||||
PacketManager.readHeader(connector.getChannel(), 4).getPacketBodyLength());
|
||||
if (body[0] < 0) {
|
||||
ErrorPacket packet = new ErrorPacket();
|
||||
packet.fromBytes(body);
|
||||
|
||||
@@ -47,10 +47,11 @@ public class ClientAuthenticationPacket extends PacketWithHeaderPacket {
|
||||
/**
|
||||
* CLIENT_LONG_PASSWORD CLIENT_LONG_FLAG CLIENT_PROTOCOL_41
|
||||
* CLIENT_INTERACTIVE CLIENT_TRANSACTIONS CLIENT_SECURE_CONNECTION
|
||||
* CLIENT_MULTI_STATEMENTS;
|
||||
*/
|
||||
ByteHelper.writeUnsignedIntLittleEndian(1 | 4 | 512 | 8192 | 32768, out); // remove
|
||||
// client_interactive
|
||||
// feature
|
||||
ByteHelper.writeUnsignedIntLittleEndian(1 | 4 | 512 | 8192 | 32768 | 0x00010000, out); // remove
|
||||
// client_interactive
|
||||
// feature
|
||||
|
||||
// 2. write max_packet_size
|
||||
ByteHelper.writeUnsignedIntLittleEndian(MSC.MAX_PACKET_LENGTH, out);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql.packets.client;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.CommandPacket;
|
||||
|
||||
/**
|
||||
* quit cmd
|
||||
*
|
||||
* @author agapple 2016年3月1日 下午8:33:02
|
||||
* @since 1.0.22
|
||||
*/
|
||||
public class QuitCommandPacket extends CommandPacket {
|
||||
|
||||
public static final byte[] QUIT = new byte[] { 1, 0, 0, 0, 1 };
|
||||
|
||||
public QuitCommandPacket(){
|
||||
setCommand((byte) 0x01);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromBytes(byte[] data) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(getCommand());
|
||||
out.write(QUIT);
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql.packets.client;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.CommandPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
|
||||
|
||||
/**
|
||||
* COM_REGISTER_SLAVE
|
||||
*
|
||||
* @author zhibinliu
|
||||
* @since 1.0.24
|
||||
*/
|
||||
public class RegisterSlaveCommandPacket extends CommandPacket {
|
||||
|
||||
public String reportHost;
|
||||
public int reportPort;
|
||||
public String reportUser;
|
||||
public String reportPasswd;
|
||||
public long serverId;
|
||||
|
||||
public RegisterSlaveCommandPacket(){
|
||||
setCommand((byte) 0x15);
|
||||
}
|
||||
|
||||
public void fromBytes(byte[] data) {
|
||||
// bypass
|
||||
}
|
||||
|
||||
public static byte[] toLH(int n) {
|
||||
byte[] b = new byte[4];
|
||||
b[0] = (byte) (n & 0xff);
|
||||
b[1] = (byte) (n >> 8 & 0xff);
|
||||
b[2] = (byte) (n >> 16 & 0xff);
|
||||
b[3] = (byte) (n >> 24 & 0xff);
|
||||
return b;
|
||||
}
|
||||
|
||||
public byte[] toBytes() throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(getCommand());
|
||||
ByteHelper.writeUnsignedIntLittleEndian(serverId, out);
|
||||
out.write((byte) reportHost.getBytes().length);
|
||||
ByteHelper.writeFixedLengthBytesFromStart(reportHost.getBytes(), reportHost.getBytes().length, out);
|
||||
out.write((byte) reportUser.getBytes().length);
|
||||
ByteHelper.writeFixedLengthBytesFromStart(reportUser.getBytes(), reportUser.getBytes().length, out);
|
||||
out.write((byte) reportPasswd.getBytes().length);
|
||||
ByteHelper.writeFixedLengthBytesFromStart(reportPasswd.getBytes(), reportPasswd.getBytes().length, out);
|
||||
ByteHelper.writeUnsignedShortLittleEndian(reportPort, out);
|
||||
ByteHelper.writeUnsignedIntLittleEndian(0, out);// Fake
|
||||
// rpl_recovery_rank
|
||||
ByteHelper.writeUnsignedIntLittleEndian(0, out);// master id
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql.packets.client;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.CommandPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
|
||||
|
||||
/**
|
||||
* semi ack command
|
||||
*
|
||||
* @author amos_chen
|
||||
*/
|
||||
public class SemiAckCommandPacket extends CommandPacket {
|
||||
|
||||
public long binlogPosition;
|
||||
public String binlogFileName;
|
||||
|
||||
public SemiAckCommandPacket(){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromBytes(byte[] data) throws IOException {
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* Bytes Name
|
||||
* --------------------------------------------------------
|
||||
* Bytes Name
|
||||
* ----- ----
|
||||
* 1 semi mark
|
||||
* 8 binlog position to start at (little endian)
|
||||
* n binlog file name
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public byte[] toBytes() throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
// 0 write semi mark
|
||||
out.write(0xef);
|
||||
// 1 write 8 bytes for position
|
||||
ByteHelper.write8ByteUnsignedIntLittleEndian(binlogPosition, out);
|
||||
|
||||
// 2 write binlog filename
|
||||
if (StringUtils.isNotEmpty(binlogFileName)) {
|
||||
out.write(binlogFileName.getBytes());
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -74,6 +74,8 @@ public class FieldPacket extends PacketWithHeaderPacket {
|
||||
this.decimals = data[index];
|
||||
index++;
|
||||
//
|
||||
index += 2;// skip filter
|
||||
//
|
||||
if (index < data.length) {
|
||||
reader.setIndex(index);
|
||||
this.definition = reader.readLengthCodedString(data);
|
||||
|
||||
@@ -0,0 +1,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;
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,18 @@ public abstract class ByteHelper {
|
||||
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
public static void write8ByteUnsignedIntLittleEndian(long data, ByteArrayOutputStream out) {
|
||||
out.write((byte) (data & 0xFF));
|
||||
out.write((byte) (data >>> 8));
|
||||
out.write((byte) (data >>> 16));
|
||||
out.write((byte) (data >>> 24));
|
||||
out.write((byte) (data >>> 32));
|
||||
out.write((byte) (data >>> 40));
|
||||
out.write((byte) (data >>> 48));
|
||||
out.write((byte) (data >>> 56));
|
||||
}
|
||||
|
||||
public static void writeUnsignedIntLittleEndian(long data, ByteArrayOutputStream out) {
|
||||
out.write((byte) (data & 0xFF));
|
||||
out.write((byte) (data >>> 8));
|
||||
|
||||
@@ -1,69 +1,44 @@
|
||||
package com.alibaba.otter.canal.parse.driver.mysql.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||
|
||||
public abstract class PacketManager {
|
||||
|
||||
public static HeaderPacket readHeader(SocketChannel ch, int len) throws IOException {
|
||||
HeaderPacket header = new HeaderPacket();
|
||||
header.fromBytes(readBytesAsBuffer(ch, len).array());
|
||||
header.fromBytes(ch.read(len));
|
||||
return header;
|
||||
}
|
||||
|
||||
public static ByteBuffer readBytesAsBuffer(SocketChannel ch, int len) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(len);
|
||||
while (buffer.hasRemaining()) {
|
||||
int readNum = ch.read(buffer);
|
||||
if (readNum == -1) {
|
||||
throw new IOException("Unexpected End Stream");
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
public static HeaderPacket readHeader(SocketChannel ch, int len, int timeout) throws IOException {
|
||||
HeaderPacket header = new HeaderPacket();
|
||||
header.fromBytes(ch.read(len, timeout));
|
||||
return header;
|
||||
}
|
||||
|
||||
public static byte[] readBytes(SocketChannel ch, int len) throws IOException {
|
||||
return readBytesAsBuffer(ch, len).array();
|
||||
return ch.read(len);
|
||||
}
|
||||
|
||||
public static byte[] readBytes(SocketChannel ch, int len, int timeout) throws IOException {
|
||||
return ch.read(len, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since We r using blocking IO, so we will just write once and assert the
|
||||
* length to simplify the read operation.<br>
|
||||
* If the block write doesn't work as we expected, we will change this
|
||||
* implementation as per the result.
|
||||
*
|
||||
* @param ch
|
||||
* @param len
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void write(SocketChannel ch, ByteBuffer[] srcs) throws IOException {
|
||||
@SuppressWarnings("unused")
|
||||
long total = 0;
|
||||
for (ByteBuffer buffer : srcs) {
|
||||
total += buffer.remaining();
|
||||
}
|
||||
|
||||
public static void writePkg(SocketChannel ch, byte[]... srcs) throws IOException {
|
||||
ch.write(srcs);
|
||||
// https://github.com/alibaba/canal/issues/24
|
||||
// 部分windows用户会出现size != total的情况,jdk为java7/openjdk,估计和java版本有关,暂时不做检查
|
||||
// long size = ch.write(srcs);
|
||||
// if (size != total) {
|
||||
// throw new IOException("unexpected blocking io behavior");
|
||||
// }
|
||||
}
|
||||
|
||||
public static void write(SocketChannel ch, byte[] body) throws IOException {
|
||||
write(ch, body, (byte) 0);
|
||||
public static void writeBody(SocketChannel ch, byte[] body) throws IOException {
|
||||
writeBody0(ch, body, (byte) 0);
|
||||
}
|
||||
|
||||
public static void write(SocketChannel ch, byte[] body, byte packetSeqNumber) throws IOException {
|
||||
public static void writeBody0(SocketChannel ch, byte[] body, byte packetSeqNumber) throws IOException {
|
||||
HeaderPacket header = new HeaderPacket();
|
||||
header.setPacketBodyLength(body.length);
|
||||
header.setPacketSequenceNumber(packetSeqNumber);
|
||||
write(ch, new ByteBuffer[] { ByteBuffer.wrap(header.toBytes()), ByteBuffer.wrap(body) });
|
||||
ch.write(header.toBytes(), body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
90
example/src/main/bin/init.sh
Executable file
90
example/src/main/bin/init.sh
Executable file
@@ -0,0 +1,90 @@
|
||||
#! /bin/bash
|
||||
current_path=`pwd`
|
||||
case "`uname`" in
|
||||
Linux)
|
||||
bin_abs_path=$(readlink -f $(dirname $0))
|
||||
;;
|
||||
*)
|
||||
bin_abs_path=`cd $(dirname $0); pwd`
|
||||
;;
|
||||
esac
|
||||
base=${bin_abs_path}/..
|
||||
|
||||
# default values
|
||||
PORT=3306
|
||||
MYSQL_IP="0"
|
||||
|
||||
# parse arguments
|
||||
while getopts "h:p:" opt; do
|
||||
case $opt in
|
||||
p)
|
||||
if [[ $OPTARG -gt 0 ]]; then
|
||||
PORT=$OPTARG
|
||||
else
|
||||
echo "Invalid -p option: -$OPTARG, should be positive integer >&2"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
h)
|
||||
MYSQL_IP=$OPTARG
|
||||
;;
|
||||
\?)
|
||||
printUsage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
function printUsage
|
||||
{
|
||||
cat << __Usage
|
||||
|
||||
Initialise the instance config property file automatically. This tool will connect to Mysql master server and get the binlog file and position then update the instance config property file for you.
|
||||
|
||||
Usage: $0 -p [port] -h [hostname]
|
||||
Options:
|
||||
-p: mysql port, default to 3306
|
||||
-h: mysql host ip.
|
||||
For example:
|
||||
$0 -p 3306 -h 172.17.0.2
|
||||
|
||||
Enjoy!!!
|
||||
|
||||
__Usage
|
||||
}
|
||||
|
||||
if [[ $PORT -lt 0 ]]; then
|
||||
echo "invalid port argument $PORT"
|
||||
printUsage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if mysql ip is specified
|
||||
if [[ $MYSQL_IP = "0" ]]; then
|
||||
echo '-h mysql host ip argument is required.'
|
||||
printUsage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MYSQL_ADDR="$MYSQL_IP:$PORT"
|
||||
|
||||
MASTER_STATUS=`mysql -ucanal -pcanal -h$MYSQL_IP -P$PORT -e 'show master status' 2>/dev/null | head -n 2 | tail -n 1`
|
||||
|
||||
LOG_FILE=`echo "$MASTER_STATUS" | cut -f 1`
|
||||
LOG_POS=`echo "$MASTER_STATUS" | cut -f 2`
|
||||
|
||||
INSTANCE_CONF_FILE=$base/conf/example/instance.properties
|
||||
|
||||
if [ ! -f $INSTANCE_CONF_FILE ]; then
|
||||
echo "Can not find instance config file: $INSTANCE_CONF_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# replace the binlog file and position
|
||||
sed -i.bak -e "s/canal\.instance\.master\.address = .*/canal\.instance\.master\.address = $MYSQL_ADDR/" -e "s/canal\.instance\.master\.journal\.name = .*/canal\.instance\.master\.journal\.name = $LOG_FILE/" -e "s/canal\.instance\.master\.position = .*/canal\.instance\.master\.position = $LOG_POS/" $INSTANCE_CONF_FILE
|
||||
|
||||
cat $INSTANCE_CONF_FILE
|
||||
|
||||
echo
|
||||
echo 'Above is your instance config file, check if the settings are correct'
|
||||
echo
|
||||
@@ -56,10 +56,10 @@ public class AbstractCanalClientTest {
|
||||
context_format += "****************************************************" + SEP;
|
||||
|
||||
row_format = SEP
|
||||
+ "----------------> binlog[{}:{}] , name[{},{}] , eventType : {} , executeTime : {} , delay : {}ms"
|
||||
+ "----------------> binlog[{}:{}] , name[{},{}] , eventType : {} , executeTime : {}({}) , delay : {} ms"
|
||||
+ 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) {
|
||||
long executeTime = entry.getHeader().getExecuteTime();
|
||||
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) {
|
||||
@@ -178,7 +180,8 @@ public class AbstractCanalClientTest {
|
||||
logger.info(transaction_format,
|
||||
new Object[] { entry.getHeader().getLogfileName(),
|
||||
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());
|
||||
} else if (entry.getEntryType() == EntryType.TRANSACTIONEND) {
|
||||
TransactionEnd end = null;
|
||||
@@ -193,7 +196,8 @@ public class AbstractCanalClientTest {
|
||||
logger.info(transaction_format,
|
||||
new Object[] { entry.getHeader().getLogfileName(),
|
||||
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;
|
||||
@@ -213,7 +217,8 @@ public class AbstractCanalClientTest {
|
||||
new Object[] { entry.getHeader().getLogfileName(),
|
||||
String.valueOf(entry.getHeader().getLogfileOffset()), entry.getHeader().getSchemaName(),
|
||||
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()) {
|
||||
logger.info(" sql ----> " + rowChage.getSql() + SEP);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
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.CanalConnectors;
|
||||
|
||||
@@ -41,7 +39,7 @@ public class ClusterCanalClientTest extends AbstractCanalClientTest {
|
||||
logger.info("## stop the canal client");
|
||||
clientTest.stop();
|
||||
} 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 {
|
||||
logger.info("## canal client is down.");
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package com.alibaba.otter.canal.example;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
|
||||
import com.alibaba.otter.canal.client.CanalConnector;
|
||||
import com.alibaba.otter.canal.client.CanalConnectors;
|
||||
import com.alibaba.otter.canal.common.utils.AddressUtils;
|
||||
@@ -23,8 +21,11 @@ public class SimpleCanalClientTest extends AbstractCanalClientTest {
|
||||
public static void main(String args[]) {
|
||||
// 根据ip,直接创建链接,无HA的功能
|
||||
String destination = "example";
|
||||
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(AddressUtils.getHostIp(),
|
||||
11111), destination, "", "");
|
||||
String ip = AddressUtils.getHostIp();
|
||||
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(ip, 11111),
|
||||
destination,
|
||||
"",
|
||||
"");
|
||||
|
||||
final SimpleCanalClientTest clientTest = new SimpleCanalClientTest(destination);
|
||||
clientTest.setConnector(connector);
|
||||
@@ -36,7 +37,7 @@ public class SimpleCanalClientTest extends AbstractCanalClientTest {
|
||||
logger.info("## stop the canal client");
|
||||
clientTest.stop();
|
||||
} 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 {
|
||||
logger.info("## canal client is down.");
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.alibaba.otter.canal.filter;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
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.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>canal.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>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -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.zookeeper.ZkClientx;
|
||||
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
||||
import com.alibaba.otter.canal.instance.core.CanalInstance;
|
||||
import com.alibaba.otter.canal.instance.core.CanalInstanceSupport;
|
||||
import com.alibaba.otter.canal.instance.core.AbstractCanalInstance;
|
||||
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.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.StorageMode;
|
||||
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.PeriodMixedMetaManager;
|
||||
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.ZooKeeperLogPositionManager;
|
||||
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.sink.CanalEventSink;
|
||||
import com.alibaba.otter.canal.sink.entry.EntryEventSink;
|
||||
import com.alibaba.otter.canal.sink.entry.group.GroupEventSink;
|
||||
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.model.BatchMode;
|
||||
import com.alibaba.otter.canal.store.model.Event;
|
||||
|
||||
/**
|
||||
* 单个canal实例,比如一个destination会独立一个实例
|
||||
@@ -64,24 +57,11 @@ import com.alibaba.otter.canal.store.model.Event;
|
||||
* @author jianghang 2012-7-11 下午09:26:51
|
||||
* @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);
|
||||
protected Long canalId; // 和manager交互唯一标示
|
||||
protected String destination; // 队列名字
|
||||
protected String filter; // 过滤表达式
|
||||
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);
|
||||
}
|
||||
private static final Logger logger = LoggerFactory.getLogger(CanalInstanceWithManager.class);
|
||||
protected String filter; // 过滤表达式
|
||||
protected CanalParameter parameters; // 对应参数
|
||||
|
||||
public CanalInstanceWithManager(Canal canal, String filter){
|
||||
this.parameters = canal.getCanalParameter();
|
||||
@@ -89,7 +69,7 @@ public class CanalInstanceWithManager extends CanalInstanceSupport implements Ca
|
||||
this.destination = canal.getName();
|
||||
this.filter = filter;
|
||||
|
||||
logger.info("init CannalInstance for {}-{} with parameters:{}", canalId, destination, parameters);
|
||||
logger.info("init CanalInstance for {}-{} with parameters:{}", canalId, destination, parameters);
|
||||
// 初始化报警机制
|
||||
initAlarmHandler();
|
||||
// 初始化metaManager
|
||||
@@ -101,7 +81,7 @@ public class CanalInstanceWithManager extends CanalInstanceSupport implements Ca
|
||||
// 初始化eventParser;
|
||||
initEventParser();
|
||||
|
||||
// 基础工具,需要提前start,会有先订阅再根据filter条件启动paser的需求
|
||||
// 基础工具,需要提前start,会有先订阅再根据filter条件启动parse的需求
|
||||
if (!alarmHandler.isStart()) {
|
||||
alarmHandler.start();
|
||||
}
|
||||
@@ -113,98 +93,9 @@ public class CanalInstanceWithManager extends CanalInstanceSupport implements Ca
|
||||
}
|
||||
|
||||
public void start() {
|
||||
super.start();
|
||||
// 初始化metaManager
|
||||
logger.info("start CannalInstance for {}-{} with parameters:{}", canalId, destination, parameters);
|
||||
|
||||
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);
|
||||
}
|
||||
super.start();
|
||||
}
|
||||
|
||||
protected void initAlarmHandler() {
|
||||
@@ -455,29 +346,24 @@ public class CanalInstanceWithManager extends CanalInstanceSupport implements Ca
|
||||
protected CanalLogPositionManager initLogPositionManager() {
|
||||
logger.info("init logPositionPersistManager begin...");
|
||||
IndexMode indexMode = parameters.getIndexMode();
|
||||
CanalLogPositionManager logPositionManager = null;
|
||||
CanalLogPositionManager logPositionManager;
|
||||
if (indexMode.isMemory()) {
|
||||
logPositionManager = new MemoryLogPositionManager();
|
||||
} else if (indexMode.isZookeeper()) {
|
||||
logPositionManager = new ZooKeeperLogPositionManager();
|
||||
((ZooKeeperLogPositionManager) logPositionManager).setZkClientx(getZkclientx());
|
||||
logPositionManager = new ZooKeeperLogPositionManager(getZkclientx());
|
||||
} else if (indexMode.isMixed()) {
|
||||
logPositionManager = new PeriodMixedLogPositionManager();
|
||||
|
||||
ZooKeeperLogPositionManager zooKeeperLogPositionManager = new ZooKeeperLogPositionManager();
|
||||
zooKeeperLogPositionManager.setZkClientx(getZkclientx());
|
||||
((PeriodMixedLogPositionManager) logPositionManager).setZooKeeperLogPositionManager(zooKeeperLogPositionManager);
|
||||
MemoryLogPositionManager memoryLogPositionManager = new MemoryLogPositionManager();
|
||||
ZooKeeperLogPositionManager zooKeeperLogPositionManager = new ZooKeeperLogPositionManager(getZkclientx());
|
||||
logPositionManager = new PeriodMixedLogPositionManager(memoryLogPositionManager,
|
||||
zooKeeperLogPositionManager,
|
||||
1000L);
|
||||
} else if (indexMode.isMeta()) {
|
||||
logPositionManager = new MetaLogPositionManager();
|
||||
((MetaLogPositionManager) logPositionManager).setMetaManager(metaManager);
|
||||
logPositionManager = new MetaLogPositionManager(metaManager);
|
||||
} else if (indexMode.isMemoryMetaFailback()) {
|
||||
MemoryLogPositionManager primaryLogPositionManager = new MemoryLogPositionManager();
|
||||
MetaLogPositionManager failbackLogPositionManager = new MetaLogPositionManager();
|
||||
failbackLogPositionManager.setMetaManager(metaManager);
|
||||
MemoryLogPositionManager primary = new MemoryLogPositionManager();
|
||||
MetaLogPositionManager secondary = new MetaLogPositionManager(metaManager);
|
||||
|
||||
logPositionManager = new FailbackLogPositionManager();
|
||||
((FailbackLogPositionManager) logPositionManager).setPrimary(primaryLogPositionManager);
|
||||
((FailbackLogPositionManager) logPositionManager).setFailback(failbackLogPositionManager);
|
||||
logPositionManager = new FailbackLogPositionManager(primary, secondary);
|
||||
} else {
|
||||
throw new CanalException("unsupport indexMode for " + indexMode);
|
||||
}
|
||||
@@ -515,32 +401,6 @@ public class CanalInstanceWithManager extends CanalInstanceSupport implements Ca
|
||||
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) {
|
||||
this.alarmHandler = alarmHandler;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -2,20 +2,14 @@ package com.alibaba.otter.canal.instance.spring;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.otter.canal.common.alarm.CanalAlarmHandler;
|
||||
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
||||
import com.alibaba.otter.canal.instance.core.CanalInstance;
|
||||
import com.alibaba.otter.canal.instance.core.CanalInstanceSupport;
|
||||
import com.alibaba.otter.canal.instance.core.AbstractCanalInstance;
|
||||
import com.alibaba.otter.canal.meta.CanalMetaManager;
|
||||
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.ClientIdentity;
|
||||
import com.alibaba.otter.canal.sink.CanalEventSink;
|
||||
import com.alibaba.otter.canal.store.CanalEventStore;
|
||||
import com.alibaba.otter.canal.store.model.Event;
|
||||
@@ -27,130 +21,13 @@ import com.alibaba.otter.canal.store.model.Event;
|
||||
* @author zebin.xuzb
|
||||
* @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 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);
|
||||
}
|
||||
}
|
||||
private static final Logger logger = LoggerFactory.getLogger(CanalInstanceWithSpring.class);
|
||||
|
||||
public void start() {
|
||||
super.start();
|
||||
|
||||
logger.info("start CannalInstance for {}-{} ", new Object[] { 1, destination });
|
||||
if (!metaManager.isStart()) {
|
||||
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....");
|
||||
super.start();
|
||||
}
|
||||
|
||||
// ======== setter ========
|
||||
|
||||
@@ -120,14 +120,14 @@
|
||||
<!-- 解析位点记录 -->
|
||||
<property name="logPositionManager">
|
||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||
<property name="primary">
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||
</property>
|
||||
<property name="failback">
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||
<property name="metaManager" ref="metaManager" />
|
||||
<constructor-arg ref="metaManager"/>
|
||||
</bean>
|
||||
</property>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
|
||||
@@ -102,18 +102,20 @@
|
||||
<!-- 解析编码 -->
|
||||
<!-- property name="connectionCharsetNumber" value="${canal.instance.connectionCharsetNumber:33}" /-->
|
||||
<property name="connectionCharset" value="${canal.instance.connectionCharset:UTF-8}" />
|
||||
|
||||
|
||||
<!-- 解析位点记录 -->
|
||||
<property name="logPositionManager">
|
||||
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
|
||||
<property name="primary">
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
|
||||
</property>
|
||||
<property name="failback">
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
|
||||
<property name="metaManager" ref="metaManager" />
|
||||
<constructor-arg>
|
||||
<ref bean="metaManager"/>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
|
||||
@@ -367,7 +367,7 @@ public class FileMixedMetaManager extends MemoryMetaManager implements CanalMeta
|
||||
this.dataDir = new File(dataDir);
|
||||
}
|
||||
|
||||
public void setDataDir(File dataDir) {
|
||||
public void setDataDirByFile(File dataDir) {
|
||||
this.dataDir = dataDir;
|
||||
}
|
||||
|
||||
|
||||
@@ -142,8 +142,8 @@ public class PeriodMixedMetaManager extends MemoryMetaManager implements CanalMe
|
||||
}
|
||||
|
||||
public void updateCursor(ClientIdentity clientIdentity, Position position) throws CanalMetaManagerException {
|
||||
updateCursorTasks.add(clientIdentity);// 添加到任务队列中进行触发
|
||||
super.updateCursor(clientIdentity, position);
|
||||
updateCursorTasks.add(clientIdentity);// 添加到任务队列中进行触发
|
||||
}
|
||||
|
||||
public Position getCursor(ClientIdentity clientIdentity) throws CanalMetaManagerException {
|
||||
|
||||
@@ -5,9 +5,8 @@ import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -32,7 +31,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
||||
@Test
|
||||
public void testSubscribeAll() {
|
||||
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
||||
metaManager.setDataDir(dataDir);
|
||||
metaManager.setDataDirByFile(dataDir);
|
||||
metaManager.setPeriod(100);
|
||||
|
||||
metaManager.start();
|
||||
@@ -41,7 +40,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
||||
sleep(2000L);
|
||||
// 重新构建一次,能获得上一次zk上的记录
|
||||
FileMixedMetaManager metaManager2 = new FileMixedMetaManager();
|
||||
metaManager2.setDataDir(dataDir);
|
||||
metaManager2.setDataDirByFile(dataDir);
|
||||
metaManager2.setPeriod(100);
|
||||
metaManager2.start();
|
||||
|
||||
@@ -53,7 +52,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
||||
@Test
|
||||
public void testBatchAll() {
|
||||
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
||||
metaManager.setDataDir(dataDir);
|
||||
metaManager.setDataDirByFile(dataDir);
|
||||
metaManager.setPeriod(100);
|
||||
|
||||
metaManager.start();
|
||||
@@ -68,7 +67,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
||||
@Test
|
||||
public void testCursorAll() {
|
||||
FileMixedMetaManager metaManager = new FileMixedMetaManager();
|
||||
metaManager.setDataDir(dataDir);
|
||||
metaManager.setDataDirByFile(dataDir);
|
||||
metaManager.setPeriod(100);
|
||||
metaManager.start();
|
||||
|
||||
@@ -77,7 +76,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
|
||||
sleep(1000L);
|
||||
// 重新构建一次,能获得上一次zk上的记录
|
||||
FileMixedMetaManager metaManager2 = new FileMixedMetaManager();
|
||||
metaManager2.setDataDir(dataDir);
|
||||
metaManager2.setDataDirByFile(dataDir);
|
||||
metaManager2.setPeriod(100);
|
||||
metaManager2.start();
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.alibaba.otter.canal.meta;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
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.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
@@ -3,9 +3,8 @@ package com.alibaba.otter.canal.meta;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@ package com.alibaba.otter.canal.meta;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.otter</groupId>
|
||||
<artifactId>canal</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.26-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>canal.parse</artifactId>
|
||||
@@ -45,11 +45,45 @@
|
||||
<artifactId>canal.parse.driver</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</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 -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</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.TableIdNotFoundException;
|
||||
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.support.AuthenticationInfo;
|
||||
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||
@@ -97,6 +96,10 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
protected void preDump(ErosaConnection connection) {
|
||||
}
|
||||
|
||||
protected boolean processTableMeta(EntryPosition position) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void afterDump(ErosaConnection connection) {
|
||||
}
|
||||
|
||||
@@ -120,7 +123,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
throw new CanalParseException("consume failed!");
|
||||
}
|
||||
|
||||
LogPosition position = buildLastTranasctionPosition(transaction);
|
||||
LogPosition position = buildLastTransactionPosition(transaction);
|
||||
if (position != null) { // 可能position为空
|
||||
logPositionManager.persistLogPosition(AbstractEventParser.this.destination, position);
|
||||
}
|
||||
@@ -146,7 +149,6 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
ErosaConnection erosaConnection = null;
|
||||
while (running) {
|
||||
try {
|
||||
|
||||
// 开始执行replication
|
||||
// 1. 构造Erosa连接
|
||||
erosaConnection = buildErosaConnection();
|
||||
@@ -159,11 +161,17 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
|
||||
erosaConnection.connect();// 链接
|
||||
// 4. 获取最后的位置信息
|
||||
final EntryPosition startPosition = findStartPosition(erosaConnection);
|
||||
EntryPosition position = findStartPosition(erosaConnection);
|
||||
final EntryPosition startPosition = position;
|
||||
if (startPosition == null) {
|
||||
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过程中可能有状态,需要断开后重建
|
||||
erosaConnection.reconnect();
|
||||
|
||||
@@ -173,7 +181,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
|
||||
public boolean sink(EVENT event) {
|
||||
try {
|
||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event);
|
||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, false);
|
||||
|
||||
if (!running) {
|
||||
return false;
|
||||
@@ -190,9 +198,12 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
return running;
|
||||
} catch (TableIdNotFoundException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable e) {
|
||||
if (e.getCause() instanceof TableIdNotFoundException) {
|
||||
throw (TableIdNotFoundException) e.getCause();
|
||||
}
|
||||
// 记录一下,出错的位点信息
|
||||
processError(e,
|
||||
processSinkError(e,
|
||||
this.lastPosition,
|
||||
startPosition.getJournalName(),
|
||||
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 ",
|
||||
runningInfo.getAddress().toString()), e);
|
||||
} catch (Throwable e) {
|
||||
processDumpError(e);
|
||||
exception = e;
|
||||
if (!running) {
|
||||
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));
|
||||
}
|
||||
} finally {
|
||||
// 重新置为中断状态
|
||||
Thread.interrupted();
|
||||
// 关闭一下链接
|
||||
afterDump(erosaConnection);
|
||||
try {
|
||||
@@ -269,7 +283,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
parseThread.setUncaughtExceptionHandler(handler);
|
||||
parseThread.setName(String.format("destination = %s , address = %s , EventParser",
|
||||
destination,
|
||||
runningInfo == null ? null : runningInfo.getAddress().toString()));
|
||||
runningInfo == null ? null : runningInfo.getAddress()));
|
||||
parseThread.start();
|
||||
}
|
||||
|
||||
@@ -314,14 +328,13 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
return result;
|
||||
}
|
||||
|
||||
protected CanalEntry.Entry parseAndProfilingIfNecessary(EVENT bod) throws Exception {
|
||||
protected CanalEntry.Entry parseAndProfilingIfNecessary(EVENT bod, boolean isSeek) throws Exception {
|
||||
long startTs = -1;
|
||||
boolean enabled = getProfilingEnabled();
|
||||
if (enabled) {
|
||||
startTs = System.currentTimeMillis();
|
||||
}
|
||||
CanalEntry.Entry event = binlogParser.parse(bod);
|
||||
|
||||
CanalEntry.Entry event = binlogParser.parse(bod, isSeek);
|
||||
if (enabled) {
|
||||
this.parsingInterval = System.currentTimeMillis() - startTs;
|
||||
}
|
||||
@@ -336,20 +349,11 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
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--) {
|
||||
CanalEntry.Entry entry = entries.get(i);
|
||||
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {// 尽量记录一个事务做为position
|
||||
LogPosition logPosition = new LogPosition();
|
||||
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;
|
||||
return buildLastPosition(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,6 +366,8 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
position.setJournalName(entry.getHeader().getLogfileName());
|
||||
position.setPosition(entry.getHeader().getLogfileOffset());
|
||||
position.setTimestamp(entry.getHeader().getExecuteTime());
|
||||
// add serverId at 2016-06-28
|
||||
position.setServerId(entry.getHeader().getServerId());
|
||||
logPosition.setPostion(position);
|
||||
|
||||
LogIdentity identity = new LogIdentity(runningInfo.getAddress(), -1L);
|
||||
@@ -369,7 +375,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
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) {
|
||||
logger.warn(String.format("ERROR ## parse this event has an error , last position : [%s]",
|
||||
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) {
|
||||
lastEntryTime = 0L; // 初始化
|
||||
if (timer == null) {// lazy初始化一下
|
||||
String name = String.format("destination = %s , address = %s , HeartBeatTimeTask",
|
||||
destination,
|
||||
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) {
|
||||
timer = new Timer(name, true);
|
||||
}
|
||||
@@ -424,7 +438,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
||||
}
|
||||
|
||||
} 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 {
|
||||
|
||||
CanalEntry.Entry parse(T event) throws CanalParseException;
|
||||
CanalEntry.Entry parse(T event, boolean isSeek) throws CanalParseException;
|
||||
|
||||
void reset();
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ public interface ErosaConnection {
|
||||
|
||||
public void disconnect() throws IOException;
|
||||
|
||||
public boolean isConnected();
|
||||
|
||||
/**
|
||||
* 用于快速数据查找,和dump的区别在于,seek会只给出部分的数据
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.alibaba.otter.canal.parse.inbound;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
@@ -8,50 +9,126 @@ import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
||||
|
||||
/**
|
||||
* 描述数据meta对象,mysql binlog中对应的{@linkplain TableMapLogEvent}包含的信息不全
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
* 1. 主键信息
|
||||
* 2. column name
|
||||
* 3. unsigned字段
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @author jianghang 2013-1-18 下午12:24:59
|
||||
* @version 1.0.0
|
||||
*/
|
||||
public class TableMeta {
|
||||
|
||||
private String fullName; // schema.table
|
||||
private List<FieldMeta> fileds;
|
||||
private String schema;
|
||||
private String table;
|
||||
private List<FieldMeta> fields = new ArrayList<TableMeta.FieldMeta>();
|
||||
private String ddl; // 表结构的DDL语句
|
||||
|
||||
public TableMeta(String fullName, List<FieldMeta> fileds){
|
||||
this.fullName = fullName;
|
||||
this.fileds = fileds;
|
||||
public TableMeta(){
|
||||
|
||||
}
|
||||
|
||||
public TableMeta(String schema, String table, List<FieldMeta> fields){
|
||||
this.schema = schema;
|
||||
this.table = table;
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
return schema + "." + table;
|
||||
}
|
||||
|
||||
public void setFullName(String fullName) {
|
||||
this.fullName = fullName;
|
||||
public String getSchema() {
|
||||
return schema;
|
||||
}
|
||||
|
||||
public List<FieldMeta> getFileds() {
|
||||
return fileds;
|
||||
public void setSchema(String schema) {
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
public void setFileds(List<FieldMeta> fileds) {
|
||||
this.fileds = fileds;
|
||||
public String getTable() {
|
||||
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 {
|
||||
|
||||
private String columnName;
|
||||
private String columnType;
|
||||
private String isNullable;
|
||||
private String iskey;
|
||||
private String defaultValue;
|
||||
private String extra;
|
||||
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 columnType;
|
||||
private boolean nullable;
|
||||
private boolean key;
|
||||
private String defaultValue;
|
||||
private String extra;
|
||||
private boolean unique;
|
||||
|
||||
public String getColumnName() {
|
||||
return columnName;
|
||||
@@ -69,20 +146,8 @@ public class TableMeta {
|
||||
this.columnType = columnType;
|
||||
}
|
||||
|
||||
public String getIsNullable() {
|
||||
return isNullable;
|
||||
}
|
||||
|
||||
public void setIsNullable(String isNullable) {
|
||||
this.isNullable = isNullable;
|
||||
}
|
||||
|
||||
public String getIskey() {
|
||||
return iskey;
|
||||
}
|
||||
|
||||
public void setIskey(String iskey) {
|
||||
this.iskey = iskey;
|
||||
public void setNullable(boolean nullable) {
|
||||
this.nullable = nullable;
|
||||
}
|
||||
|
||||
public String getDefaultValue() {
|
||||
@@ -93,6 +158,22 @@ public class TableMeta {
|
||||
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() {
|
||||
return extra;
|
||||
}
|
||||
@@ -101,22 +182,21 @@ public class TableMeta {
|
||||
this.extra = extra;
|
||||
}
|
||||
|
||||
public boolean isUnsigned() {
|
||||
return StringUtils.containsIgnoreCase(columnType, "unsigned");
|
||||
public boolean isUnique() {
|
||||
return unique;
|
||||
}
|
||||
|
||||
public boolean isKey() {
|
||||
return StringUtils.equalsIgnoreCase(iskey, "PRI");
|
||||
}
|
||||
|
||||
public boolean isNullable() {
|
||||
return StringUtils.equalsIgnoreCase(isNullable, "YES");
|
||||
public void setUnique(boolean unique) {
|
||||
this.unique = unique;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FieldMeta [columnName=" + columnName + ", columnType=" + columnType + ", defaultValue="
|
||||
+ defaultValue + ", extra=" + extra + ", isNullable=" + isNullable + ", iskey=" + iskey + "]";
|
||||
return "FieldMeta [columnName=" + columnName + ", columnType=" + columnType + ", nullable=" + nullable
|
||||
+ ", 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.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.BinlogParser;
|
||||
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 {
|
||||
|
||||
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
protected static final long BINLOG_START_OFFEST = 4L;
|
||||
|
||||
protected boolean enableTsdb = false;
|
||||
protected String tsdbSpringXml;
|
||||
protected TableMetaTSDB tableMetaTSDB;
|
||||
// 编码信息
|
||||
protected byte connectionCharsetNumber = (byte) 33;
|
||||
protected Charset connectionCharset = Charset.forName("UTF-8");
|
||||
protected boolean filterQueryDcl = false;
|
||||
protected boolean filterQueryDml = false;
|
||||
protected boolean filterQueryDdl = false;
|
||||
protected boolean filterRows = false;
|
||||
protected boolean filterTableError = false;
|
||||
protected boolean useDruidDdlFilter = true;
|
||||
|
||||
protected BinlogParser buildParser() {
|
||||
LogEventConvert convert = new LogEventConvert();
|
||||
@@ -38,6 +47,7 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
||||
convert.setFilterQueryDcl(filterQueryDcl);
|
||||
convert.setFilterQueryDml(filterQueryDml);
|
||||
convert.setFilterQueryDdl(filterQueryDdl);
|
||||
convert.setFilterRows(filterRows);
|
||||
convert.setFilterTableError(filterTableError);
|
||||
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) {
|
||||
super.setEventBlackFilter(eventBlackFilter);
|
||||
|
||||
@@ -87,8 +131,40 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
||||
this.filterQueryDdl = filterQueryDdl;
|
||||
}
|
||||
|
||||
public void setFilterRows(boolean filterRows) {
|
||||
this.filterRows = filterRows;
|
||||
}
|
||||
|
||||
public void setFilterTableError(boolean 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.LogPosition;
|
||||
import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent;
|
||||
import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent;
|
||||
|
||||
/**
|
||||
* local bin log connection (not real connection)
|
||||
@@ -83,7 +84,10 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
context.setLogPosition(new LogPosition(binlogfilename, binlogPosition));
|
||||
while (running) {
|
||||
boolean needContinue = true;
|
||||
LogEvent event;
|
||||
LogEvent event = null;
|
||||
// 处理一下binlog文件名
|
||||
event = new RotateLogEvent(context.getLogPosition().getFileName(), 4);
|
||||
func.sink(event);
|
||||
while (fetcher.fetch()) {
|
||||
event = decoder.decode(fetcher, context);
|
||||
if (event == null) {
|
||||
@@ -94,14 +98,6 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
needContinue = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// do {
|
||||
// event = decoder.decode(fetcher, context);
|
||||
// if (event != null && !func.sink(event)) {
|
||||
// needContinue = false;
|
||||
// break;
|
||||
// }
|
||||
// } while (event != null);
|
||||
}
|
||||
|
||||
if (needContinue) {// 读取下一个
|
||||
@@ -119,7 +115,6 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
}
|
||||
|
||||
current = nextFile;
|
||||
|
||||
fetcher.open(current);
|
||||
context.setLogPosition(new LogPosition(nextFile.getName()));
|
||||
} else {
|
||||
@@ -145,6 +140,7 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
|
||||
FileLogFetcher fetcher = new FileLogFetcher(bufferSize);
|
||||
LogDecoder decoder = new LogDecoder();
|
||||
decoder.handle(LogEvent.FORMAT_DESCRIPTION_EVENT);
|
||||
decoder.handle(LogEvent.QUERY_EVENT);
|
||||
decoder.handle(LogEvent.XID_EVENT);
|
||||
LogContext context = new LogContext();
|
||||
@@ -158,30 +154,27 @@ public class LocalBinLogConnection implements ErosaConnection {
|
||||
|
||||
binlogFilename = lastXidLogFilename;
|
||||
binlogFileOffset = lastXidLogFileOffset;
|
||||
L: while (fetcher.fetch()) {
|
||||
LogEvent event;
|
||||
do {
|
||||
event = decoder.decode(fetcher, context);
|
||||
if (event != null) {
|
||||
if (event.getWhen() > timestampSeconds) {
|
||||
break L;
|
||||
}
|
||||
while (fetcher.fetch()) {
|
||||
LogEvent event = decoder.decode(fetcher, context);
|
||||
if (event != null) {
|
||||
if (event.getWhen() > timestampSeconds) {
|
||||
break;
|
||||
}
|
||||
|
||||
needContinue = false;
|
||||
if (LogEvent.QUERY_EVENT == event.getHeader().getType()) {
|
||||
if (StringUtils.endsWithIgnoreCase(((QueryLogEvent) event).getQuery(), "BEGIN")) {
|
||||
binlogFilename = lastXidLogFilename;
|
||||
binlogFileOffset = lastXidLogFileOffset;
|
||||
} else if (StringUtils.endsWithIgnoreCase(((QueryLogEvent) event).getQuery(), "COMMIT")) {
|
||||
lastXidLogFilename = current.getName();
|
||||
lastXidLogFileOffset = event.getLogPos();
|
||||
}
|
||||
} else if (LogEvent.XID_EVENT == event.getHeader().getType()) {
|
||||
needContinue = false;
|
||||
if (LogEvent.QUERY_EVENT == event.getHeader().getType()) {
|
||||
if (StringUtils.endsWithIgnoreCase(((QueryLogEvent) event).getQuery(), "BEGIN")) {
|
||||
binlogFilename = lastXidLogFilename;
|
||||
binlogFileOffset = lastXidLogFileOffset;
|
||||
} else if (StringUtils.endsWithIgnoreCase(((QueryLogEvent) event).getQuery(), "COMMIT")) {
|
||||
lastXidLogFilename = current.getName();
|
||||
lastXidLogFileOffset = event.getLogPos();
|
||||
}
|
||||
} else if (LogEvent.XID_EVENT == event.getHeader().getType()) {
|
||||
lastXidLogFilename = current.getName();
|
||||
lastXidLogFileOffset = event.getLogPos();
|
||||
}
|
||||
} while (event != null);
|
||||
}
|
||||
}
|
||||
|
||||
if (needContinue) {// 读取下一个
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||
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.support.AuthenticationInfo;
|
||||
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 {
|
||||
|
||||
// 数据库信息
|
||||
private AuthenticationInfo masterInfo;
|
||||
private EntryPosition masterPosition; // binlog信息
|
||||
protected AuthenticationInfo masterInfo;
|
||||
protected EntryPosition masterPosition; // binlog信息
|
||||
protected MysqlConnection metaConnection; // 查询meta信息的链接
|
||||
protected TableMetaCache tableMetaCache; // 对应meta
|
||||
|
||||
private String directory;
|
||||
private boolean needWait = false;
|
||||
private int bufferSize = 16 * 1024;
|
||||
protected String directory;
|
||||
protected boolean needWait = false;
|
||||
protected int bufferSize = 16 * 1024;
|
||||
|
||||
public LocalBinlogEventParser(){
|
||||
// this.runningInfo = new AuthenticationInfo();
|
||||
@@ -35,6 +42,37 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
|
||||
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 {
|
||||
if (runningInfo == null) { // 第一次链接主库
|
||||
runningInfo = masterInfo;
|
||||
@@ -43,6 +81,24 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
|
||||
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() {
|
||||
LocalBinLogConnection connection = new LocalBinLogConnection();
|
||||
|
||||
@@ -53,6 +109,19 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
|
||||
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
|
||||
protected EntryPosition findStartPosition(ErosaConnection connection) {
|
||||
// 处理逻辑
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
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.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
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.LoggerFactory;
|
||||
|
||||
@@ -15,37 +17,60 @@ 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.packets.HeaderPacket;
|
||||
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.utils.PacketManager;
|
||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
||||
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.LogDecoder;
|
||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||
|
||||
public class MysqlConnection implements ErosaConnection {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MysqlConnection.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(MysqlConnection.class);
|
||||
|
||||
private MysqlConnector connector;
|
||||
private long slaveId;
|
||||
private Charset charset = Charset.forName("UTF-8");
|
||||
private Charset charset = Charset.forName("UTF-8");
|
||||
private BinlogFormat binlogFormat;
|
||||
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(InetSocketAddress address, String username, String password){
|
||||
|
||||
authInfo = new AuthenticationInfo();
|
||||
authInfo.setAddress(address);
|
||||
authInfo.setUsername(username);
|
||||
authInfo.setPassword(password);
|
||||
connector = new MysqlConnector(address, username, password);
|
||||
// 将connection里面的参数透传下
|
||||
connector.setSoTimeout(soTimeout);
|
||||
connector.setConnTimeout(connTimeout);
|
||||
}
|
||||
|
||||
public MysqlConnection(InetSocketAddress address, String username, String password, byte charsetNumber,
|
||||
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);
|
||||
// 将connection里面的参数透传下
|
||||
connector.setSoTimeout(soTimeout);
|
||||
connector.setConnTimeout(connTimeout);
|
||||
}
|
||||
|
||||
public void connect() throws IOException {
|
||||
@@ -69,6 +94,11 @@ public class MysqlConnection implements ErosaConnection {
|
||||
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 {
|
||||
MysqlUpdateExecutor exector = new MysqlUpdateExecutor(connector);
|
||||
exector.update(cmd);
|
||||
@@ -105,6 +135,7 @@ public class MysqlConnection implements ErosaConnection {
|
||||
|
||||
public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
||||
updateSettings();
|
||||
sendRegisterSlave();
|
||||
sendBinlogDump(binlogfilename, binlogPosition);
|
||||
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
||||
fetcher.start(connector.getChannel());
|
||||
@@ -121,6 +152,10 @@ public class MysqlConnection implements ErosaConnection {
|
||||
if (!func.sink(event)) {
|
||||
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");
|
||||
}
|
||||
|
||||
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 {
|
||||
BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket();
|
||||
binlogDumpCmd.binlogFileName = binlogfilename;
|
||||
@@ -139,8 +204,22 @@ public class MysqlConnection implements ErosaConnection {
|
||||
HeaderPacket binlogDumpHeader = new HeaderPacket();
|
||||
binlogDumpHeader.setPacketBodyLength(cmdBody.length);
|
||||
binlogDumpHeader.setPacketSequenceNumber((byte) 0x00);
|
||||
PacketManager.write(connector.getChannel(), new ByteBuffer[] { ByteBuffer.wrap(binlogDumpHeader.toBytes()),
|
||||
ByteBuffer.wrap(cmdBody) });
|
||||
PacketManager.writePkg(connector.getChannel(), binlogDumpHeader.toBytes(), 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() {
|
||||
@@ -148,6 +227,8 @@ public class MysqlConnection implements ErosaConnection {
|
||||
connection.setCharset(getCharset());
|
||||
connection.setSlaveId(getSlaveId());
|
||||
connection.setConnector(connector.fork());
|
||||
// set authInfo
|
||||
connection.setAuthInfo(authInfo);
|
||||
return connection;
|
||||
}
|
||||
|
||||
@@ -168,25 +249,25 @@ public class MysqlConnection implements ErosaConnection {
|
||||
try {
|
||||
update("set wait_timeout=9999999");
|
||||
} catch (Exception e) {
|
||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn("update wait_timeout failed", e);
|
||||
}
|
||||
try {
|
||||
update("set net_write_timeout=1800");
|
||||
} catch (Exception e) {
|
||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn("update net_write_timeout failed", e);
|
||||
}
|
||||
|
||||
try {
|
||||
update("set net_read_timeout=1800");
|
||||
} catch (Exception e) {
|
||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn("update net_read_timeout failed", e);
|
||||
}
|
||||
|
||||
try {
|
||||
// 设置服务端返回结果时不做编码转化,直接按照数据库的二进制编码进行发送,由客户端自己根据需求进行编码转化
|
||||
update("set names 'binary'");
|
||||
} catch (Exception e) {
|
||||
logger.warn(ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn("update names failed", e);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -196,14 +277,40 @@ public class MysqlConnection implements ErosaConnection {
|
||||
// 但也不能乱设置,需要和mysql server的checksum配置一致,不然RotateLogEvent会出现乱码
|
||||
update("set @master_binlog_checksum= '@@global.binlog_checksum'");
|
||||
} 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 {
|
||||
// mariadb针对特殊的类型,需要设置session变量
|
||||
update("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
|
||||
} 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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
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.CanalHASwitchable;
|
||||
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.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.support.AuthenticationInfo;
|
||||
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.LogPosition;
|
||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||
@@ -67,7 +68,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验
|
||||
private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验
|
||||
|
||||
// 心跳检查
|
||||
// update by yishun.chen,特殊异常处理参数
|
||||
private int dumpErrorCount = 0; // binlogDump失败异常计数
|
||||
private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值
|
||||
|
||||
protected ErosaConnection buildErosaConnection() {
|
||||
return buildMysqlConnection(this.runningInfo);
|
||||
@@ -92,6 +95,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
for (BinlogFormat supportFormat : supportBinlogFormats) {
|
||||
if (supportFormat != null && format == supportFormat) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +110,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
for (BinlogImage supportImage : supportBinlogImages) {
|
||||
if (supportImage != null && image == supportImage) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -176,10 +187,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
}
|
||||
|
||||
protected void stopHeartBeat() {
|
||||
TimerTask heartBeatTimerTask = this.heartBeatTimerTask;
|
||||
super.stopHeartBeat();
|
||||
|
||||
if (heartBeatTimerTask != null) {
|
||||
|
||||
if (heartBeatTimerTask != null && heartBeatTimerTask instanceof MysqlDetectingTimeTask) {
|
||||
MysqlConnection mysqlConnection = ((MysqlDetectingTimeTask) heartBeatTimerTask).getMysqlConnection();
|
||||
try {
|
||||
mysqlConnection.disconnect();
|
||||
@@ -234,19 +244,19 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
((HeartBeatCallback) haController).onFailed(e);
|
||||
}
|
||||
reconnect = true;
|
||||
logger.warn("connect failed by " + ExceptionUtils.getStackTrace(e));
|
||||
logger.warn("connect failed by ", e);
|
||||
} catch (IOException e) {
|
||||
if (haController != null && haController instanceof HeartBeatCallback) {
|
||||
((HeartBeatCallback) haController).onFailed(e);
|
||||
}
|
||||
reconnect = true;
|
||||
logger.warn("connect failed by " + ExceptionUtils.getStackTrace(e));
|
||||
logger.warn("connect failed by ", e);
|
||||
} catch (Throwable e) {
|
||||
if (haController != null && haController instanceof HeartBeatCallback) {
|
||||
((HeartBeatCallback) haController).onFailed(e);
|
||||
}
|
||||
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().setSoTimeout(defaultConnectionTimeoutInSeconds * 1000);
|
||||
connection.setCharset(connectionCharset);
|
||||
// 随机生成slaveId
|
||||
if (this.slaveId <= 0) {
|
||||
this.slaveId = generateUniqueServerId();
|
||||
}
|
||||
connection.setSlaveId(this.slaveId);
|
||||
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 {
|
||||
EntryPosition startPosition = findStartPositionInternal(connection);
|
||||
if (needTransactionPosition.get()) {
|
||||
@@ -324,6 +359,47 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
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) {
|
||||
MysqlConnection mysqlConnection = (MysqlConnection) connection;
|
||||
LogPosition logPosition = logPositionManager.getLatestIndexBy(destination);
|
||||
@@ -337,7 +413,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
}
|
||||
|
||||
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());
|
||||
} else {
|
||||
logger.warn("prepare to find start position just show master status");
|
||||
return findEndPosition(mysqlConnection); // 默认从当前最后一个位置进行消费
|
||||
return findEndPositionWithMasterIdAndTimestamp(mysqlConnection); // 默认从当前最后一个位置进行消费
|
||||
}
|
||||
} else {
|
||||
if (entryPosition.getPosition() != null && entryPosition.getPosition() > 0L) {
|
||||
// 如果指定binlogName + offest,直接返回
|
||||
entryPosition = findPositionWithMasterIdAndTimestamp(mysqlConnection, entryPosition);
|
||||
logger.warn("prepare to find start position {}:{}:{}",
|
||||
new Object[] { entryPosition.getJournalName(), entryPosition.getPosition(), "" });
|
||||
new Object[] { entryPosition.getJournalName(), entryPosition.getPosition(),
|
||||
entryPosition.getTimestamp() });
|
||||
return entryPosition;
|
||||
} else {
|
||||
EntryPosition specificLogFilePosition = null;
|
||||
@@ -369,7 +447,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
specificLogFilePosition = findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
||||
entryPosition.getTimestamp(),
|
||||
endPosition,
|
||||
entryPosition.getJournalName());
|
||||
entryPosition.getJournalName(),
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,7 +463,27 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
}
|
||||
} else {
|
||||
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();
|
||||
} else {
|
||||
// 针对切换的情况,考虑回退时间
|
||||
@@ -400,83 +499,47 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
// 主要考虑一个事务执行时间可能会几秒种,如果仅仅按照timestamp相同,则可能会丢失事务的前半部分数据
|
||||
private Long findTransactionBeginPosition(ErosaConnection mysqlConnection, final EntryPosition entryPosition)
|
||||
throws IOException {
|
||||
// 尝试找到一个合适的位置
|
||||
final AtomicBoolean reDump = new AtomicBoolean(false);
|
||||
// 针对开始的第一条为非Begin记录,需要从该binlog扫描
|
||||
final AtomicLong preTransactionStartPosition = new AtomicLong(0L);
|
||||
mysqlConnection.reconnect();
|
||||
mysqlConnection.seek(entryPosition.getJournalName(), entryPosition.getPosition(), new SinkFunction<LogEvent>() {
|
||||
mysqlConnection.seek(entryPosition.getJournalName(), 4L, new SinkFunction<LogEvent>() {
|
||||
|
||||
private LogPosition lastPosition;
|
||||
|
||||
public boolean sink(LogEvent event) {
|
||||
try {
|
||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event);
|
||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, true);
|
||||
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;
|
||||
// 直接查询第一条业务数据,确认是否为事务Begin
|
||||
// 记录一下transaction begin position
|
||||
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
|
||||
&& entry.getHeader().getLogfileOffset() < entryPosition.getPosition()) {
|
||||
preTransactionStartPosition.set(entry.getHeader().getLogfileOffset());
|
||||
}
|
||||
|
||||
if (entry.getHeader().getLogfileOffset() >= entryPosition.getPosition()) {
|
||||
return false;// 退出
|
||||
}
|
||||
|
||||
lastPosition = buildLastPosition(entry);
|
||||
} catch (Exception e) {
|
||||
// 上一次记录的poistion可能为一条update/insert/delete变更事件,直接进行dump的话,会缺少tableMap事件,导致tableId未进行解析
|
||||
processError(e, lastPosition, entryPosition.getJournalName(), entryPosition.getPosition());
|
||||
reDump.set(true);
|
||||
processSinkError(e, lastPosition, entryPosition.getJournalName(), entryPosition.getPosition());
|
||||
return false;
|
||||
}
|
||||
|
||||
return running;
|
||||
}
|
||||
});
|
||||
// 针对开始的第一条为非Begin记录,需要从该binlog扫描
|
||||
if (reDump.get()) {
|
||||
final AtomicLong preTransactionStartPosition = new AtomicLong(0L);
|
||||
mysqlConnection.reconnect();
|
||||
mysqlConnection.seek(entryPosition.getJournalName(), 4L, new SinkFunction<LogEvent>() {
|
||||
|
||||
private LogPosition lastPosition;
|
||||
|
||||
public boolean sink(LogEvent event) {
|
||||
try {
|
||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event);
|
||||
if (entry == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 直接查询第一条业务数据,确认是否为事务Begin
|
||||
// 记录一下transaction begin position
|
||||
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
|
||||
&& entry.getHeader().getLogfileOffset() < entryPosition.getPosition()) {
|
||||
preTransactionStartPosition.set(entry.getHeader().getLogfileOffset());
|
||||
}
|
||||
|
||||
if (entry.getHeader().getLogfileOffset() >= entryPosition.getPosition()) {
|
||||
return false;// 退出
|
||||
}
|
||||
|
||||
lastPosition = buildLastPosition(entry);
|
||||
} catch (Exception e) {
|
||||
processError(e, lastPosition, entryPosition.getJournalName(), entryPosition.getPosition());
|
||||
return false;
|
||||
}
|
||||
|
||||
return running;
|
||||
}
|
||||
});
|
||||
|
||||
// 判断一下找到的最接近position的事务头的位置
|
||||
if (preTransactionStartPosition.get() > entryPosition.getPosition()) {
|
||||
logger.error("preTransactionEndPosition 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();
|
||||
} else {
|
||||
return entryPosition.getPosition();
|
||||
// 判断一下找到的最接近position的事务头的位置
|
||||
if (preTransactionStartPosition.get() > entryPosition.getPosition()) {
|
||||
logger.error("preTransactionEndPosition 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();
|
||||
}
|
||||
|
||||
// 根据时间查找binlog位置
|
||||
@@ -493,7 +556,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
EntryPosition entryPosition = findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
||||
startTimestamp,
|
||||
endPosition,
|
||||
startSearchBinlogFile);
|
||||
startSearchBinlogFile,
|
||||
false);
|
||||
if (entryPosition == null) {
|
||||
if (StringUtils.equalsIgnoreCase(minBinlogFileName, startSearchBinlogFile)) {
|
||||
// 已经找到最早的一个binlog,没必要往前找了
|
||||
@@ -520,9 +584,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
return entryPosition;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("the binlogfile:{} doesn't exist, to continue to search the next binlogfile , caused by {}",
|
||||
startSearchBinlogFile,
|
||||
ExceptionUtils.getFullStackTrace(e));
|
||||
logger.warn(String.format("the binlogfile:%s doesn't exist, to continue to search the next binlogfile , caused by",
|
||||
startSearchBinlogFile),
|
||||
e);
|
||||
int binlogSeqNum = Integer.parseInt(startSearchBinlogFile.substring(startSearchBinlogFile.indexOf(".") + 1));
|
||||
if (binlogSeqNum <= 1) {
|
||||
logger.warn("Didn't find the corresponding binlog files");
|
||||
@@ -540,6 +604,22 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
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位置
|
||||
*/
|
||||
@@ -625,7 +705,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
private EntryPosition findAsPerTimestampInSpecificLogFile(MysqlConnection mysqlConnection,
|
||||
final Long startTimestamp,
|
||||
final EntryPosition endPosition,
|
||||
final String searchBinlogFile) {
|
||||
final String searchBinlogFile,
|
||||
final Boolean justForPositionTimestamp) {
|
||||
|
||||
final LogPosition logPosition = new LogPosition();
|
||||
try {
|
||||
@@ -638,7 +719,16 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
public boolean sink(LogEvent event) {
|
||||
EntryPosition entryPosition = null;
|
||||
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) {
|
||||
return true;
|
||||
}
|
||||
@@ -646,11 +736,14 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
String logfilename = entry.getHeader().getLogfileName();
|
||||
Long logfileoffset = entry.getHeader().getLogfileOffset();
|
||||
Long logposTimestamp = entry.getHeader().getExecuteTime();
|
||||
Long serverId = entry.getHeader().getServerId();
|
||||
|
||||
if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())
|
||||
|| CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
||||
logger.debug("compare exit condition:{},{},{}, startTimestamp={}...", new Object[] {
|
||||
logfilename, logfileoffset, logposTimestamp, startTimestamp });
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("compare exit condition:{},{},{}, startTimestamp={}...", new Object[] {
|
||||
logfilename, logfileoffset, logposTimestamp, startTimestamp });
|
||||
}
|
||||
// 事务头和尾寻找第一条记录时间戳,如果最小的一条记录都不满足条件,可直接退出
|
||||
if (logposTimestamp >= startTimestamp) {
|
||||
return false;
|
||||
@@ -658,7 +751,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
}
|
||||
|
||||
if (StringUtils.equals(endPosition.getJournalName(), logfilename)
|
||||
&& endPosition.getPosition() <= (logfileoffset + event.getEventLen())) {
|
||||
&& endPosition.getPosition() < logfileoffset) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -666,23 +759,25 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
// position = current +
|
||||
// data.length,代表该事务的下一条offest,避免多余的事务重复
|
||||
if (CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
||||
entryPosition = new EntryPosition(logfilename,
|
||||
logfileoffset + event.getEventLen(),
|
||||
logposTimestamp);
|
||||
logger.debug("set {} to be pending start position before finding another proper one...",
|
||||
entryPosition);
|
||||
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp, serverId);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("set {} to be pending start position before finding another proper one...",
|
||||
entryPosition);
|
||||
}
|
||||
logPosition.setPostion(entryPosition);
|
||||
} else if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())) {
|
||||
// 当前事务开始位点
|
||||
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp);
|
||||
logger.debug("set {} to be pending start position before finding another proper one...",
|
||||
entryPosition);
|
||||
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp, serverId);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("set {} to be pending start position before finding another proper one...",
|
||||
entryPosition);
|
||||
}
|
||||
logPosition.setPostion(entryPosition);
|
||||
}
|
||||
|
||||
lastPosition = buildLastPosition(entry);
|
||||
} catch (Exception e) {
|
||||
processError(e, lastPosition, searchBinlogFile, 4L);
|
||||
} catch (Throwable e) {
|
||||
processSinkError(e, lastPosition, searchBinlogFile, 4L);
|
||||
}
|
||||
|
||||
return running;
|
||||
@@ -700,21 +795,17 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
}
|
||||
}
|
||||
|
||||
protected Entry parseAndProfilingIfNecessary(LogEvent bod) throws Exception {
|
||||
long startTs = -1;
|
||||
boolean enabled = getProfilingEnabled();
|
||||
if (enabled) {
|
||||
startTs = System.currentTimeMillis();
|
||||
}
|
||||
CanalEntry.Entry event = binlogParser.parse(bod);
|
||||
if (enabled) {
|
||||
this.parsingInterval = System.currentTimeMillis() - startTs;
|
||||
@Override
|
||||
protected void processDumpError(Throwable e) {
|
||||
if (e instanceof IOException) {
|
||||
String message = e.getMessage();
|
||||
if (StringUtils.contains(message, "errno = 1236")) {
|
||||
// 1236 errorCode代表ER_MASTER_FATAL_ERROR_READING_BINLOG
|
||||
dumpErrorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedEventCount.incrementAndGet() < 0) {
|
||||
parsedEventCount.set(0);
|
||||
}
|
||||
return event;
|
||||
super.processDumpError(e);
|
||||
}
|
||||
|
||||
public void setSupportBinlogFormats(String formatStrs) {
|
||||
@@ -800,4 +891,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
||||
public void setHaController(CanalHAController 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.InterruptedIOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedByInterruptException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
|
||||
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);
|
||||
|
||||
// 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 */
|
||||
public static final byte COM_BINLOG_DUMP = 18;
|
||||
|
||||
@@ -38,6 +42,8 @@ public class DirectLogFetcher extends LogFetcher {
|
||||
|
||||
private SocketChannel channel;
|
||||
|
||||
private boolean issemi = false;
|
||||
|
||||
// private BufferedInputStream input;
|
||||
|
||||
public DirectLogFetcher(){
|
||||
@@ -54,6 +60,10 @@ public class DirectLogFetcher extends LogFetcher {
|
||||
|
||||
public void start(SocketChannel channel) throws IOException {
|
||||
this.channel = channel;
|
||||
String dbsemi = System.getProperty("db.semi");
|
||||
if ("1".equals(dbsemi)) {
|
||||
issemi = true;
|
||||
}
|
||||
// 和mysql driver一样,提供buffer机制,提升读取binlog速度
|
||||
// this.input = new
|
||||
// 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.
|
||||
while (netlen == MAX_PACKET_LENGTH) {
|
||||
if (!fetch0(0, NET_HEADER_SIZE)) {
|
||||
@@ -123,7 +141,11 @@ public class DirectLogFetcher extends LogFetcher {
|
||||
}
|
||||
|
||||
// Preparing buffer variables to decoding.
|
||||
origin = NET_HEADER_SIZE + 1;
|
||||
if (issemi) {
|
||||
origin = NET_HEADER_SIZE + 3;
|
||||
} else {
|
||||
origin = NET_HEADER_SIZE + 1;
|
||||
}
|
||||
position = origin;
|
||||
limit -= origin;
|
||||
return true;
|
||||
@@ -149,20 +171,8 @@ public class DirectLogFetcher extends LogFetcher {
|
||||
private final boolean fetch0(final int off, final int len) throws IOException {
|
||||
ensureCapacity(off + len);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(this.buffer, off, len);
|
||||
while (buffer.hasRemaining()) {
|
||||
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;
|
||||
// }
|
||||
// }
|
||||
byte[] read = channel.read(len, READ_TIMEOUT_MILLISECONDS);
|
||||
System.arraycopy(read, 0, this.buffer, off, len);
|
||||
|
||||
if (limit < off + len) limit = off + len;
|
||||
return true;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user