mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
Compare commits
168 Commits
v1.0.25
...
canal-1.0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89eb3d6078 | ||
|
|
7b52568575 | ||
|
|
d520260fe8 | ||
|
|
77c1c65b83 | ||
|
|
fe38bc16d2 | ||
|
|
b4c6490c94 | ||
|
|
67f2e55d38 | ||
|
|
b9d7f0b6d0 | ||
|
|
b880d96a86 | ||
|
|
b9156936ae | ||
|
|
617f9bbc62 | ||
|
|
b19eaeb89a | ||
|
|
5582a38930 | ||
|
|
c6eee47af6 | ||
|
|
10bb800f85 | ||
|
|
89726a6365 | ||
|
|
8e856acac5 | ||
|
|
df8fe4a9eb | ||
|
|
33a2a6121a | ||
|
|
441d2d2449 | ||
|
|
bef6a66eaf | ||
|
|
c1805a4831 | ||
|
|
38eecd9aef | ||
|
|
cef431c8af | ||
|
|
3efd589cb1 | ||
|
|
92c570e967 | ||
|
|
cf7af17aec | ||
|
|
3308bf0662 | ||
|
|
be605617c3 | ||
|
|
bb8fec963b | ||
|
|
da8bf59d5b | ||
|
|
54168af790 | ||
|
|
f0bed4c762 | ||
|
|
4d4d97419c | ||
|
|
41eef9a090 | ||
|
|
9ae6c0c863 | ||
|
|
5f47809c8e | ||
|
|
f2e3ff2dae | ||
|
|
92778be5be | ||
|
|
5da8d391f8 | ||
|
|
f867b183ab | ||
|
|
ea555df315 | ||
|
|
4af1e5fb84 | ||
|
|
bf02db4cbc | ||
|
|
967f8d6b0f | ||
|
|
4f4b016da3 | ||
|
|
a4255a6377 | ||
|
|
dca1b20043 | ||
|
|
025d36604b | ||
|
|
e23a19cd85 | ||
|
|
593902d349 | ||
|
|
d014167226 | ||
|
|
df668a6f72 | ||
|
|
cc5eb1075b | ||
|
|
19f4c5ae1b | ||
|
|
4e41ebf746 | ||
|
|
31cbdb1824 | ||
|
|
1b6efdf463 | ||
|
|
cb6c7ef3e8 | ||
|
|
4bd2a79905 | ||
|
|
345e22bd2c | ||
|
|
9964bb75b1 | ||
|
|
ec072a45d8 | ||
|
|
efb7b32694 | ||
|
|
74bc6368f2 | ||
|
|
b96e4dd23a | ||
|
|
d991e6be60 | ||
|
|
e7846085fc | ||
|
|
6faa3fd586 | ||
|
|
fa34eb0e59 | ||
|
|
3e97e6b771 | ||
|
|
330da50a5e | ||
|
|
e35a06743c | ||
|
|
0d6757c257 | ||
|
|
e4cde2f6a1 | ||
|
|
9b7d62717e | ||
|
|
b306bde3ec | ||
|
|
31f72e1aac | ||
|
|
ad201660a6 | ||
|
|
77f1dfd324 | ||
|
|
11467fca2a | ||
|
|
cc8d5b4263 | ||
|
|
4022aa8b5a | ||
|
|
187b98fbda | ||
|
|
0f33c8c89c | ||
|
|
b6967dc0dc | ||
|
|
b734a32ae6 | ||
|
|
1f16310b7d | ||
|
|
5349a5c49b | ||
|
|
a2241e2521 | ||
|
|
fedc07352b | ||
|
|
e251de79f3 | ||
|
|
7076e3124d | ||
|
|
fca7da6bba | ||
|
|
57c2cc4c4e | ||
|
|
3c913d1e22 | ||
|
|
4141049bb9 | ||
|
|
912a7a12be | ||
|
|
5c2ed38d2f | ||
|
|
c8f1d54fa1 | ||
|
|
e0fa8e0f72 | ||
|
|
7eb6fb3e8a | ||
|
|
debfdb8873 | ||
|
|
eda7e4ca2b | ||
|
|
70e6bdde02 | ||
|
|
21f3f8c973 | ||
|
|
f46133d116 | ||
|
|
9b7de00efb | ||
|
|
c340bd6565 | ||
|
|
511734c3d8 | ||
|
|
af002f4e24 | ||
|
|
28ad12d0ea | ||
|
|
6229a8593f | ||
|
|
256056e14f | ||
|
|
499be8bd8c | ||
|
|
cd87ef00cc | ||
|
|
6b7109c66b | ||
|
|
b90c9fac41 | ||
|
|
88c79544f0 | ||
|
|
fddaff1bd9 | ||
|
|
847ebf9bf3 | ||
|
|
3e7e7b7f6e | ||
|
|
06e7f44114 | ||
|
|
8ad603fea4 | ||
|
|
863a61d626 | ||
|
|
7b38542183 | ||
|
|
0af789b1c7 | ||
|
|
2d49b709ca | ||
|
|
826a45c105 | ||
|
|
03452dcdda | ||
|
|
514876f60f | ||
|
|
89994c52f9 | ||
|
|
edc2fbe1dc | ||
|
|
cfa90af33c | ||
|
|
32d4174083 | ||
|
|
bb0f3b3a7e | ||
|
|
dc0010823f | ||
|
|
b1431f8827 | ||
|
|
b9e3629459 | ||
|
|
31ed4efe3a | ||
|
|
6da1507405 | ||
|
|
0b82ee8d10 | ||
|
|
00890f87e5 | ||
|
|
91ac0e30aa | ||
|
|
9316a3df6d | ||
|
|
2d31e6bf91 | ||
|
|
a965d2cd04 | ||
|
|
93be9d1909 | ||
|
|
7cf9375ac6 | ||
|
|
af289cd9fb | ||
|
|
d1f1c0716b | ||
|
|
0c7aed197a | ||
|
|
325d16bc66 | ||
|
|
3104b4cc24 | ||
|
|
9595e6abbd | ||
|
|
c6f4de9f88 | ||
|
|
d66839c965 | ||
|
|
ac773009f8 | ||
|
|
8b03f172ed | ||
|
|
5e206e4f7e | ||
|
|
448fc8bcde | ||
|
|
c60b04424b | ||
|
|
5aed505429 | ||
|
|
912f5db2c7 | ||
|
|
8b57b9a0a5 | ||
|
|
d1c83ea7bb | ||
|
|
7d485efd71 | ||
|
|
fa059804ce |
11
README.md
11
README.md
@@ -5,6 +5,7 @@
|
|||||||
<ol>
|
<ol>
|
||||||
<li>canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。</li>
|
<li>canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。</li>
|
||||||
<li>canal消费端项目开源: Otter(分布式数据库同步系统),地址:<a href="https://github.com/alibaba/otter">https://github.com/alibaba/otter</a></li>
|
<li>canal消费端项目开源: Otter(分布式数据库同步系统),地址:<a href="https://github.com/alibaba/otter">https://github.com/alibaba/otter</a></li>
|
||||||
|
<li>Canal已在阿里云推出商业化版本 <a href="https://www.aliyun.com/product/dts?spm=a2c4g.11186623.cloudEssentials.80.srdwr7">数据传输服务DTS</a>, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。<a href="https://help.aliyun.com/document_detail/26592.html?spm=a2c4g.11174283.6.539.t1Y91E">DTS产品使用文档</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<h1>背景</h1>
|
<h1>背景</h1>
|
||||||
@@ -36,7 +37,7 @@
|
|||||||
<li>slave重做中继日志中的事件,将改变反映它自己的数据。</li>
|
<li>slave重做中继日志中的事件,将改变反映它自己的数据。</li>
|
||||||
</ol>
|
</ol>
|
||||||
<h3>canal的工作原理:</h3>
|
<h3>canal的工作原理:</h3>
|
||||||
<p><img width="590" src="https://camo.githubusercontent.com/46c626b4cde399db43b2634a7911a04aecf273a0/687474703a2f2f646c2e69746579652e636f6d2f75706c6f61642f6174746163686d656e742f303038302f333130372f63383762363762612d333934632d333038362d393537372d3964623035626530346339352e6a7067" alt="" height="273">
|
<p><img width="590" src="http://dl.iteye.com/upload/attachment/0080/3107/c87b67ba-394c-3086-9577-9db05be04c95.jpg" alt="" height="273">
|
||||||
<p>原理相对比较简单:</p>
|
<p>原理相对比较简单:</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li>canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议</li>
|
<li>canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议</li>
|
||||||
@@ -73,6 +74,14 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
|
|||||||
<li>阿里巴巴去Oracle数据迁移同步工具(目标支持MySQL/DRDS):<a href="http://github.com/alibaba/yugong">http://github.com/alibaba/yugong</a></li>
|
<li>阿里巴巴去Oracle数据迁移同步工具(目标支持MySQL/DRDS):<a href="http://github.com/alibaba/yugong">http://github.com/alibaba/yugong</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
|
<h1>相关产品</h1>
|
||||||
|
<ol>
|
||||||
|
<li><a href="https://www.aliyun.com/product/drds?spm=5176.55326.cloudEssentials.71.69fd227dRPZj9K">阿里云分布式数据库DRDS</a></li>
|
||||||
|
<li><a href="https://www.aliyun.com/product/dts?spm=5176.7947010.cloudEssentials.80.33f734f4JOAxSP">阿里云数据传输服务DTS</a></li>
|
||||||
|
<li><a href="https://www.aliyun.com/product/dbs?spm=5176.54487.cloudEssentials.83.34b851a8GmVZg6">阿里云数据库备份服务DBS</a></li>
|
||||||
|
<li><a href="https://www.aliyun.com/product/dms?spm=5176.169464.cloudEssentials.81.2e1066feC1sBBL">阿里云数据管理服务DMS</a></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
<h1>问题反馈</h1>
|
<h1>问题反馈</h1>
|
||||||
<ol>
|
<ol>
|
||||||
<li>qq交流群: 161559791 </li>
|
<li>qq交流群: 161559791 </li>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
<links>
|
<links>
|
||||||
<link>https://github.com/alibaba/canal</link>
|
<link>https://github.com/alibaba/canal</link>
|
||||||
</links>
|
</links>
|
||||||
<outputDirectory>${project.build.directory}/apidocs/apidocs/${pom.version}</outputDirectory>
|
<outputDirectory>${project.build.directory}/apidocs/apidocs/${project.version}</outputDirectory>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|||||||
@@ -150,4 +150,10 @@ public interface CanalConnector {
|
|||||||
*/
|
*/
|
||||||
void rollback() throws CanalClientException;
|
void rollback() throws CanalClientException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中断的阻塞,用于优雅停止client
|
||||||
|
*
|
||||||
|
* @throws CanalClientException
|
||||||
|
*/
|
||||||
|
void stopRunning() throws CanalClientException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ public class CanalConnectors {
|
|||||||
public static CanalConnector newSingleConnector(SocketAddress address, String destination, String username,
|
public static CanalConnector newSingleConnector(SocketAddress address, String destination, String username,
|
||||||
String password) {
|
String password) {
|
||||||
SimpleCanalConnector canalConnector = new SimpleCanalConnector(address, username, password, destination);
|
SimpleCanalConnector canalConnector = new SimpleCanalConnector(address, username, password, destination);
|
||||||
canalConnector.setSoTimeout(30 * 1000);
|
canalConnector.setSoTimeout(60 * 1000);
|
||||||
|
canalConnector.setIdleTimeout(60 * 60 * 1000);
|
||||||
return canalConnector;
|
return canalConnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +49,8 @@ public class CanalConnectors {
|
|||||||
password,
|
password,
|
||||||
destination,
|
destination,
|
||||||
new SimpleNodeAccessStrategy(addresses));
|
new SimpleNodeAccessStrategy(addresses));
|
||||||
canalConnector.setSoTimeout(30 * 1000);
|
canalConnector.setSoTimeout(60 * 1000);
|
||||||
|
canalConnector.setIdleTimeout(60 * 60 * 1000);
|
||||||
return canalConnector;
|
return canalConnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +69,8 @@ public class CanalConnectors {
|
|||||||
password,
|
password,
|
||||||
destination,
|
destination,
|
||||||
new ClusterNodeAccessStrategy(destination, ZkClientx.getZkClient(zkServers)));
|
new ClusterNodeAccessStrategy(destination, ZkClientx.getZkClient(zkServers)));
|
||||||
canalConnector.setSoTimeout(30 * 1000);
|
canalConnector.setSoTimeout(60 * 1000);
|
||||||
|
canalConnector.setIdleTimeout(60 * 60 * 1000);
|
||||||
return canalConnector;
|
return canalConnector;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
private int soTimeout = 10000;
|
private int soTimeout = 60000;
|
||||||
private int retryTimes = 3;
|
private int idleTimeout = 60 * 60 * 1000;
|
||||||
|
private int retryTimes = 3; // 设置-1时可以subscribe阻塞等待时优雅停机
|
||||||
private int retryInterval = 5000; // 重试的时间间隔,默认5秒
|
private int retryInterval = 5000; // 重试的时间间隔,默认5秒
|
||||||
private CanalNodeAccessStrategy accessStrategy;
|
private CanalNodeAccessStrategy accessStrategy;
|
||||||
private SimpleCanalConnector currentConnector;
|
private SimpleCanalConnector currentConnector;
|
||||||
@@ -52,6 +53,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
};
|
};
|
||||||
currentConnector.setSoTimeout(soTimeout);
|
currentConnector.setSoTimeout(soTimeout);
|
||||||
|
currentConnector.setIdleTimeout(idleTimeout);
|
||||||
if (filter != null) {
|
if (filter != null) {
|
||||||
currentConnector.setFilter(filter);
|
currentConnector.setFilter(filter);
|
||||||
}
|
}
|
||||||
@@ -106,13 +108,17 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn(String.format(
|
if (retryTimes == -1 && t.getCause() instanceof InterruptedException) {
|
||||||
"something goes wrong when subscribing from server: %s",
|
logger.info("block waiting interrupted by other thread.");
|
||||||
currentConnector != null ? currentConnector.getAddress() : "null"),
|
return;
|
||||||
t);
|
} else {
|
||||||
times++;
|
logger.warn(String.format("something goes wrong when subscribing from server: %s",
|
||||||
restart();
|
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||||
logger.info("restart the connector for next round retry.");
|
times++;
|
||||||
|
restart();
|
||||||
|
logger.info("restart the connector for next round retry.");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +184,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
return msg;
|
return msg;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
||||||
currentConnector.getAddress()), t);
|
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -195,7 +201,7 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
return msg;
|
return msg;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
|
||||||
currentConnector.getAddress()), t);
|
currentConnector != null ? currentConnector.getAddress() : "null"), t);
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -212,7 +218,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
||||||
currentConnector.getAddress()), t);
|
currentConnector != null ? currentConnector.getAddress() : "null"),
|
||||||
|
t);
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -229,7 +236,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
|
||||||
currentConnector.getAddress()), t);
|
currentConnector != null ? currentConnector.getAddress() : "null"),
|
||||||
|
t);
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -247,7 +255,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.warn(String.format("something goes wrong when acking data from server:%s",
|
logger.warn(String.format("something goes wrong when acking data from server:%s",
|
||||||
currentConnector.getAddress()), t);
|
currentConnector != null ? currentConnector.getAddress() : "null"),
|
||||||
|
t);
|
||||||
times++;
|
times++;
|
||||||
restart();
|
restart();
|
||||||
logger.info("restart the connector for next round retry.");
|
logger.info("restart the connector for next round retry.");
|
||||||
@@ -294,6 +303,14 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
this.soTimeout = soTimeout;
|
this.soTimeout = soTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getIdleTimeout() {
|
||||||
|
return idleTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIdleTimeout(int idleTimeout) {
|
||||||
|
this.idleTimeout = idleTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
public int getRetryTimes() {
|
public int getRetryTimes() {
|
||||||
return retryTimes;
|
return retryTimes;
|
||||||
}
|
}
|
||||||
@@ -322,4 +339,8 @@ public class ClusterCanalConnector implements CanalConnector {
|
|||||||
return currentConnector;
|
return currentConnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void stopRunning() {
|
||||||
|
currentConnector.stopRunning();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
private int soTimeout = 60000; // milliseconds
|
private int soTimeout = 60000; // milliseconds
|
||||||
|
private int idleTimeout = 60 * 60 * 1000; // client和server之间的空闲链接超时的时间,默认为1小时
|
||||||
private String filter; // 记录上一次的filter提交值,便于自动重试时提交
|
private String filter; // 记录上一次的filter提交值,便于自动重试时提交
|
||||||
|
|
||||||
private final ByteBuffer readHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
private final ByteBuffer readHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
||||||
@@ -70,21 +71,29 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
private volatile boolean connected = false; // 代表connected是否已正常执行,因为有HA,不代表在工作中
|
private volatile boolean connected = false; // 代表connected是否已正常执行,因为有HA,不代表在工作中
|
||||||
private boolean rollbackOnConnect = true; // 是否在connect链接成功后,自动执行rollback操作
|
private boolean rollbackOnConnect = true; // 是否在connect链接成功后,自动执行rollback操作
|
||||||
private boolean rollbackOnDisConnect = false; // 是否在connect链接成功后,自动执行rollback操作
|
private boolean rollbackOnDisConnect = false; // 是否在connect链接成功后,自动执行rollback操作
|
||||||
|
private boolean lazyParseEntry = false; // 是否自动化解析Entry对象,如果考虑最大化性能可以延后解析
|
||||||
// 读写数据分别使用不同的锁进行控制,减小锁粒度,读也需要排他锁,并发度容易造成数据包混乱,反序列化失败
|
// 读写数据分别使用不同的锁进行控制,减小锁粒度,读也需要排他锁,并发度容易造成数据包混乱,反序列化失败
|
||||||
private Object readDataLock = new Object();
|
private Object readDataLock = new Object();
|
||||||
private Object writeDataLock = new Object();
|
private Object writeDataLock = new Object();
|
||||||
|
|
||||||
|
private volatile boolean running = false;
|
||||||
|
|
||||||
public SimpleCanalConnector(SocketAddress address, String username, String password, String destination){
|
public SimpleCanalConnector(SocketAddress address, String username, String password, String destination){
|
||||||
this(address, username, password, destination, 60000);
|
this(address, username, password, destination, 60000, 60 * 60 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SimpleCanalConnector(SocketAddress address, String username, String password, String destination,
|
public SimpleCanalConnector(SocketAddress address, String username, String password, String destination,
|
||||||
int soTimeout){
|
int soTimeout){
|
||||||
|
this(address, username, password, destination, soTimeout, 60 * 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleCanalConnector(SocketAddress address, String username, String password, String destination,
|
||||||
|
int soTimeout, int idleTimeout){
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.soTimeout = soTimeout;
|
this.soTimeout = soTimeout;
|
||||||
|
this.idleTimeout = idleTimeout;
|
||||||
this.clientIdentity = new ClientIdentity(destination, (short) 1001);
|
this.clientIdentity = new ClientIdentity(destination, (short) 1001);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +108,9 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
doConnect();
|
doConnect();
|
||||||
if (filter != null) { // 如果存在条件,说明是自动切换,基于上一次的条件订阅一次
|
if (filter != null) { // 如果存在条件,说明是自动切换,基于上一次的条件订阅一次
|
||||||
subscribe(filter);
|
subscribe(filter);
|
||||||
@@ -152,8 +164,8 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
ClientAuth ca = ClientAuth.newBuilder()
|
ClientAuth ca = ClientAuth.newBuilder()
|
||||||
.setUsername(username != null ? username : "")
|
.setUsername(username != null ? username : "")
|
||||||
.setPassword(ByteString.copyFromUtf8(password != null ? password : ""))
|
.setPassword(ByteString.copyFromUtf8(password != null ? password : ""))
|
||||||
.setNetReadTimeout(soTimeout)
|
.setNetReadTimeout(idleTimeout)
|
||||||
.setNetWriteTimeout(soTimeout)
|
.setNetWriteTimeout(idleTimeout)
|
||||||
.build();
|
.build();
|
||||||
writeWithHeader(Packet.newBuilder()
|
writeWithHeader(Packet.newBuilder()
|
||||||
.setType(PacketType.CLIENTAUTHENTICATION)
|
.setType(PacketType.CLIENTAUTHENTICATION)
|
||||||
@@ -208,6 +220,9 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
public void subscribe(String filter) throws CanalClientException {
|
public void subscribe(String filter) throws CanalClientException {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
writeWithHeader(Packet.newBuilder()
|
writeWithHeader(Packet.newBuilder()
|
||||||
.setType(PacketType.SUBSCRIPTION)
|
.setType(PacketType.SUBSCRIPTION)
|
||||||
@@ -234,6 +249,9 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
public void unsubscribe() throws CanalClientException {
|
public void unsubscribe() throws CanalClientException {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
writeWithHeader(Packet.newBuilder()
|
writeWithHeader(Packet.newBuilder()
|
||||||
.setType(PacketType.UNSUBSCRIPTION)
|
.setType(PacketType.UNSUBSCRIPTION)
|
||||||
@@ -271,6 +289,9 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
|
public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
int size = (batchSize <= 0) ? 1000 : batchSize;
|
int size = (batchSize <= 0) ? 1000 : batchSize;
|
||||||
long time = (timeout == null || timeout < 0) ? -1 : timeout; // -1代表不做timeout控制
|
long time = (timeout == null || timeout < 0) ? -1 : timeout; // -1代表不做timeout控制
|
||||||
@@ -298,7 +319,8 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Message receiveMessages() throws IOException {
|
private Message receiveMessages() throws IOException {
|
||||||
Packet p = Packet.parseFrom(readNextPacket());
|
byte[] data = readNextPacket();
|
||||||
|
Packet p = Packet.parseFrom(data);
|
||||||
switch (p.getType()) {
|
switch (p.getType()) {
|
||||||
case MESSAGES: {
|
case MESSAGES: {
|
||||||
if (!p.getCompression().equals(Compression.NONE)) {
|
if (!p.getCompression().equals(Compression.NONE)) {
|
||||||
@@ -307,8 +329,13 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
Messages messages = Messages.parseFrom(p.getBody());
|
Messages messages = Messages.parseFrom(p.getBody());
|
||||||
Message result = new Message(messages.getBatchId());
|
Message result = new Message(messages.getBatchId());
|
||||||
for (ByteString byteString : messages.getMessagesList()) {
|
if (lazyParseEntry) {
|
||||||
result.addEntry(Entry.parseFrom(byteString));
|
// byteString
|
||||||
|
result.setRawEntries(messages.getMessagesList());
|
||||||
|
} else {
|
||||||
|
for (ByteString byteString : messages.getMessagesList()) {
|
||||||
|
result.addEntry(Entry.parseFrom(byteString));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -324,6 +351,9 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
|
|
||||||
public void ack(long batchId) throws CanalClientException {
|
public void ack(long batchId) throws CanalClientException {
|
||||||
waitClientRunning();
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ClientAck ca = ClientAck.newBuilder()
|
ClientAck ca = ClientAck.newBuilder()
|
||||||
.setDestination(clientIdentity.getDestination())
|
.setDestination(clientIdentity.getDestination())
|
||||||
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
.setClientId(String.valueOf(clientIdentity.getClientId()))
|
||||||
@@ -445,7 +475,11 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
throw new CanalClientException("should connect first");
|
throw new CanalClientException("should connect first");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
running = true;
|
||||||
mutex.get();// 阻塞等待
|
mutex.get();// 阻塞等待
|
||||||
|
} else {
|
||||||
|
// 单机模式直接设置为running
|
||||||
|
running = true;
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
@@ -485,6 +519,14 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
this.soTimeout = soTimeout;
|
this.soTimeout = soTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getIdleTimeout() {
|
||||||
|
return idleTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIdleTimeout(int idleTimeout) {
|
||||||
|
this.idleTimeout = idleTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
public void setZkClientx(ZkClientx zkClientx) {
|
public void setZkClientx(ZkClientx zkClientx) {
|
||||||
this.zkClientx = zkClientx;
|
this.zkClientx = zkClientx;
|
||||||
initClientRunningMonitor(this.clientIdentity);
|
initClientRunningMonitor(this.clientIdentity);
|
||||||
@@ -502,4 +544,21 @@ public class SimpleCanalConnector implements CanalConnector {
|
|||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isLazyParseEntry() {
|
||||||
|
return lazyParseEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLazyParseEntry(boolean lazyParseEntry) {
|
||||||
|
this.lazyParseEntry = lazyParseEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopRunning() {
|
||||||
|
if (running) {
|
||||||
|
running = false; // 设置为非running状态
|
||||||
|
if (!mutex.state()) {
|
||||||
|
mutex.set(true); // 中断阻塞
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,10 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
String path = ZookeeperPathUtils.getDestinationClientRunning(this.destination, clientData.getClientId());
|
String path = ZookeeperPathUtils.getDestinationClientRunning(this.destination, clientData.getClientId());
|
||||||
zkClient.unsubscribeDataChanges(path, dataListener);
|
zkClient.unsubscribeDataChanges(path, dataListener);
|
||||||
releaseRunning(); // 尝试一下release
|
releaseRunning(); // 尝试一下release
|
||||||
|
//Fix issue #697
|
||||||
|
if (delayExector != null) {
|
||||||
|
delayExector.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 改动记录:
|
// 改动记录:
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.alibaba.fastjson.TypeReference;
|
import com.alibaba.fastjson.TypeReference;
|
||||||
|
import com.alibaba.fastjson.parser.ParserConfig;
|
||||||
import com.alibaba.fastjson.serializer.JSONSerializer;
|
import com.alibaba.fastjson.serializer.JSONSerializer;
|
||||||
import com.alibaba.fastjson.serializer.ObjectSerializer;
|
import com.alibaba.fastjson.serializer.ObjectSerializer;
|
||||||
import com.alibaba.fastjson.serializer.PropertyFilter;
|
import com.alibaba.fastjson.serializer.PropertyFilter;
|
||||||
@@ -28,6 +29,7 @@ public class JsonUtils {
|
|||||||
SerializeConfig.getGlobalInstance().put(InetAddress.class, InetAddressSerializer.instance);
|
SerializeConfig.getGlobalInstance().put(InetAddress.class, InetAddressSerializer.instance);
|
||||||
SerializeConfig.getGlobalInstance().put(Inet4Address.class, InetAddressSerializer.instance);
|
SerializeConfig.getGlobalInstance().put(Inet4Address.class, InetAddressSerializer.instance);
|
||||||
SerializeConfig.getGlobalInstance().put(Inet6Address.class, InetAddressSerializer.instance);
|
SerializeConfig.getGlobalInstance().put(Inet6Address.class, InetAddressSerializer.instance);
|
||||||
|
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) {
|
public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) {
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ public class NamedThreadFactory implements ThreadFactory {
|
|||||||
|
|
||||||
public void uncaughtException(Thread t,
|
public void uncaughtException(Thread t,
|
||||||
Throwable e) {
|
Throwable e) {
|
||||||
|
if (e instanceof InterruptedException
|
||||||
|
|| (e.getCause() != null && e.getCause() instanceof InterruptedException)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
logger.error("from " + t.getName(), e);
|
logger.error("from " + t.getName(), e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -91,18 +91,25 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
processStart();
|
processStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public synchronized void start() {
|
||||||
super.start();
|
super.start();
|
||||||
processStart();
|
try {
|
||||||
if (zkClient != null) {
|
processStart();
|
||||||
// 如果需要尽可能释放instance资源,不需要监听running节点,不然即使stop了这台机器,另一台机器立马会start
|
if (zkClient != null) {
|
||||||
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
|
// 如果需要尽可能释放instance资源,不需要监听running节点,不然即使stop了这台机器,另一台机器立马会start
|
||||||
zkClient.subscribeDataChanges(path, dataListener);
|
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
|
||||||
|
zkClient.subscribeDataChanges(path, dataListener);
|
||||||
|
|
||||||
initRunning();
|
initRunning();
|
||||||
} else {
|
} else {
|
||||||
processActiveEnter();// 没有zk,直接启动
|
processActiveEnter();// 没有zk,直接启动
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("start failed", e);
|
||||||
|
// 没有正常启动,重置一下状态,避免干扰下一次start
|
||||||
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void release() {
|
public void release() {
|
||||||
@@ -113,7 +120,7 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public synchronized void stop() {
|
||||||
super.stop();
|
super.stop();
|
||||||
|
|
||||||
if (zkClient != null) {
|
if (zkClient != null) {
|
||||||
@@ -234,11 +241,7 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
|
|||||||
|
|
||||||
private void processActiveEnter() {
|
private void processActiveEnter() {
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
try {
|
listener.processActiveEnter();
|
||||||
listener.processActiveEnter();
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("processActiveEnter failed", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ public final class CharsetConversion {
|
|||||||
putEntry(42, "latin7", "latin7_general_cs", "ISO8859_7");
|
putEntry(42, "latin7", "latin7_general_cs", "ISO8859_7");
|
||||||
putEntry(43, "macce", "macce_bin", "MacCentralEurope");
|
putEntry(43, "macce", "macce_bin", "MacCentralEurope");
|
||||||
putEntry(44, "cp1250", "cp1250_croatian_ci", "Cp1250");
|
putEntry(44, "cp1250", "cp1250_croatian_ci", "Cp1250");
|
||||||
putEntry(45, "utf8mb4", "utf8mb4_general_ci", "MacCentralEurope");
|
putEntry(45, "utf8mb4", "utf8mb4_general_ci", "UTF-8");
|
||||||
putEntry(46, "utf8mb4", "utf8mb4_bin", "MacCentralEurope");
|
putEntry(46, "utf8mb4", "utf8mb4_bin", "UTF-8");
|
||||||
putEntry(47, "latin1", "latin1_bin", "ISO8859_1");
|
putEntry(47, "latin1", "latin1_bin", "ISO8859_1");
|
||||||
putEntry(48, "latin1", "latin1_general_ci", "ISO8859_1");
|
putEntry(48, "latin1", "latin1_general_ci", "ISO8859_1");
|
||||||
putEntry(49, "latin1", "latin1_general_cs", "ISO8859_1");
|
putEntry(49, "latin1", "latin1_general_cs", "ISO8859_1");
|
||||||
|
|||||||
@@ -75,7 +75,9 @@ public final class FileLogFetcher extends LogFetcher {
|
|||||||
fin = new FileInputStream(file);
|
fin = new FileInputStream(file);
|
||||||
|
|
||||||
ensureCapacity(BIN_LOG_HEADER_SIZE);
|
ensureCapacity(BIN_LOG_HEADER_SIZE);
|
||||||
if (BIN_LOG_HEADER_SIZE != fin.read(buffer, 0, BIN_LOG_HEADER_SIZE)) throw new IOException("No binlog file header");
|
if (BIN_LOG_HEADER_SIZE != fin.read(buffer, 0, BIN_LOG_HEADER_SIZE)) {
|
||||||
|
throw new IOException("No binlog file header");
|
||||||
|
}
|
||||||
|
|
||||||
if (buffer[0] != BINLOG_MAGIC[0] || buffer[1] != BINLOG_MAGIC[1] || buffer[2] != BINLOG_MAGIC[2]
|
if (buffer[0] != BINLOG_MAGIC[0] || buffer[1] != BINLOG_MAGIC[1] || buffer[2] != BINLOG_MAGIC[2]
|
||||||
|| buffer[3] != BINLOG_MAGIC[3]) {
|
|| buffer[3] != BINLOG_MAGIC[3]) {
|
||||||
@@ -128,6 +130,19 @@ public final class FileLogFetcher extends LogFetcher {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (limit > 0) {
|
} else if (limit > 0) {
|
||||||
|
if (limit >= FormatDescriptionLogEvent.LOG_EVENT_HEADER_LEN) {
|
||||||
|
int lenPosition = position + 4 + 1 + 4;
|
||||||
|
long eventLen = ((long) (0xff & buffer[lenPosition++])) | ((long) (0xff & buffer[lenPosition++]) << 8)
|
||||||
|
| ((long) (0xff & buffer[lenPosition++]) << 16)
|
||||||
|
| ((long) (0xff & buffer[lenPosition++]) << 24);
|
||||||
|
|
||||||
|
if (limit >= eventLen) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
ensureCapacity((int) eventLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
System.arraycopy(buffer, origin, buffer, 0, limit);
|
System.arraycopy(buffer, origin, buffer, 0, limit);
|
||||||
position -= origin;
|
position -= origin;
|
||||||
origin = 0;
|
origin = 0;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public class LogBuffer {
|
|||||||
|
|
||||||
protected int origin, limit;
|
protected int origin, limit;
|
||||||
protected int position;
|
protected int position;
|
||||||
|
protected int semival;
|
||||||
|
|
||||||
protected LogBuffer(){
|
protected LogBuffer(){
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ public final class LogContext {
|
|||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
|
||||||
|
|
||||||
mapOfTable.clear();
|
mapOfTable.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,15 +30,19 @@ import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent;
|
|||||||
import com.taobao.tddl.dbsync.binlog.event.StartLogEventV3;
|
import com.taobao.tddl.dbsync.binlog.event.StartLogEventV3;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.StopLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.StopLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.TransactionContextLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.UnknownLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.UnknownLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.UserVarLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.UserVarLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.ViewChangeEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.XaPrepareLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.XidLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.XidLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent;
|
import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.mariadb.BinlogCheckPointLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.mariadb.BinlogCheckPointLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.mariadb.MariaGtidListLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.mariadb.MariaGtidListLogEvent;
|
||||||
import com.taobao.tddl.dbsync.binlog.event.mariadb.MariaGtidLogEvent;
|
import com.taobao.tddl.dbsync.binlog.event.mariadb.MariaGtidLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.mariadb.StartEncryptionLogEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a binary-log decoder.
|
* Implements a binary-log decoder.
|
||||||
@@ -104,9 +108,16 @@ public final class LogDecoder {
|
|||||||
try {
|
try {
|
||||||
/* Decoding binary-log to event */
|
/* Decoding binary-log to event */
|
||||||
event = decode(buffer, header, context);
|
event = decode(buffer, header, context);
|
||||||
|
if (event != null) {
|
||||||
|
// set logFileName
|
||||||
|
event.getHeader().setLogFileName(context.getLogPosition().getFileName());
|
||||||
|
event.setSemival(buffer.semival);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (logger.isWarnEnabled()) logger.warn("Decoding " + LogEvent.getTypeName(header.getType())
|
if (logger.isWarnEnabled()) {
|
||||||
+ " failed from: " + context.getLogPosition(), e);
|
logger.warn("Decoding " + LogEvent.getTypeName(header.getType()) + " failed from: "
|
||||||
|
+ context.getLogPosition(), e);
|
||||||
|
}
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
buffer.limit(limit); /* Restore limit */
|
buffer.limit(limit); /* Restore limit */
|
||||||
@@ -361,6 +372,24 @@ public final class LogDecoder {
|
|||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
case LogEvent.TRANSACTION_CONTEXT_EVENT: {
|
||||||
|
TransactionContextLogEvent event = new TransactionContextLogEvent(header, buffer, descriptionEvent);
|
||||||
|
/* updating position in context */
|
||||||
|
logPosition.position = header.getLogPos();
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
case LogEvent.VIEW_CHANGE_EVENT: {
|
||||||
|
ViewChangeEvent event = new ViewChangeEvent(header, buffer, descriptionEvent);
|
||||||
|
/* updating position in context */
|
||||||
|
logPosition.position = header.getLogPos();
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
case LogEvent.XA_PREPARE_LOG_EVENT: {
|
||||||
|
XaPrepareLogEvent event = new XaPrepareLogEvent(header, buffer, descriptionEvent);
|
||||||
|
/* updating position in context */
|
||||||
|
logPosition.position = header.getLogPos();
|
||||||
|
return event;
|
||||||
|
}
|
||||||
case LogEvent.ANNOTATE_ROWS_EVENT: {
|
case LogEvent.ANNOTATE_ROWS_EVENT: {
|
||||||
AnnotateRowsEvent event = new AnnotateRowsEvent(header, buffer, descriptionEvent);
|
AnnotateRowsEvent event = new AnnotateRowsEvent(header, buffer, descriptionEvent);
|
||||||
/* updating position in context */
|
/* updating position in context */
|
||||||
@@ -385,6 +414,12 @@ public final class LogDecoder {
|
|||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
case LogEvent.START_ENCRYPTION_EVENT: {
|
||||||
|
StartEncryptionLogEvent event = new StartEncryptionLogEvent(header, buffer, descriptionEvent);
|
||||||
|
/* updating position in context */
|
||||||
|
logPosition.position = header.getLogPos();
|
||||||
|
return event;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
/*
|
/*
|
||||||
* Create an object of Ignorable_log_event for unrecognized
|
* Create an object of Ignorable_log_event for unrecognized
|
||||||
@@ -397,9 +432,10 @@ public final class LogDecoder {
|
|||||||
logPosition.position = header.getLogPos();
|
logPosition.position = header.getLogPos();
|
||||||
return event;
|
return event;
|
||||||
} else {
|
} else {
|
||||||
if (logger.isWarnEnabled()) logger.warn("Skipping unrecognized binlog event "
|
if (logger.isWarnEnabled()) {
|
||||||
+ LogEvent.getTypeName(header.getType()) + " from: "
|
logger.warn("Skipping unrecognized binlog event " + LogEvent.getTypeName(header.getType())
|
||||||
+ context.getLogPosition());
|
+ " from: " + context.getLogPosition());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,9 +161,17 @@ public abstract class LogEvent {
|
|||||||
|
|
||||||
public static final int PREVIOUS_GTIDS_LOG_EVENT = 35;
|
public static final int PREVIOUS_GTIDS_LOG_EVENT = 35;
|
||||||
|
|
||||||
|
/* MySQL 5.7 events */
|
||||||
|
public static final int TRANSACTION_CONTEXT_EVENT = 36;
|
||||||
|
|
||||||
|
public static final int VIEW_CHANGE_EVENT = 37;
|
||||||
|
|
||||||
|
/* Prepared XA transaction terminal event similar to Xid */
|
||||||
|
public static final int XA_PREPARE_LOG_EVENT = 38;
|
||||||
|
|
||||||
// mariaDb 5.5.34
|
// mariaDb 5.5.34
|
||||||
/* New MySQL/Sun events are to be added right above this comment */
|
/* New MySQL/Sun events are to be added right above this comment */
|
||||||
public static final int MYSQL_EVENTS_END = 36;
|
public static final int MYSQL_EVENTS_END = 39;
|
||||||
|
|
||||||
public static final int MARIA_EVENTS_BEGIN = 160;
|
public static final int MARIA_EVENTS_BEGIN = 160;
|
||||||
/* New Maria event numbers start from here */
|
/* New Maria event numbers start from here */
|
||||||
@@ -189,8 +197,10 @@ public abstract class LogEvent {
|
|||||||
*/
|
*/
|
||||||
public static final int GTID_LIST_EVENT = 163;
|
public static final int GTID_LIST_EVENT = 163;
|
||||||
|
|
||||||
|
public static final int START_ENCRYPTION_EVENT = 164;
|
||||||
|
|
||||||
/** end marker */
|
/** end marker */
|
||||||
public static final int ENUM_END_EVENT = 164;
|
public static final int ENUM_END_EVENT = 165;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1 byte length, 1 byte format Length is total length in bytes, including 2
|
* 1 byte length, 1 byte format Length is total length in bytes, including 2
|
||||||
@@ -360,6 +370,24 @@ public abstract class LogEvent {
|
|||||||
|
|
||||||
protected final LogHeader header;
|
protected final LogHeader header;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mysql半同步semi标识
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 0不需要semi ack 给mysql
|
||||||
|
* 1需要semi ack给mysql
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
protected int semival;
|
||||||
|
|
||||||
|
public int getSemival() {
|
||||||
|
return semival;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSemival(int semival) {
|
||||||
|
this.semival = semival;
|
||||||
|
}
|
||||||
|
|
||||||
protected LogEvent(LogHeader header){
|
protected LogEvent(LogHeader header){
|
||||||
this.header = header;
|
this.header = header;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,10 +59,15 @@ public final class FormatDescriptionLogEvent extends StartLogEventV3 {
|
|||||||
public static final int HEARTBEAT_HEADER_LEN = 0;
|
public static final int HEARTBEAT_HEADER_LEN = 0;
|
||||||
public static final int IGNORABLE_HEADER_LEN = 0;
|
public static final int IGNORABLE_HEADER_LEN = 0;
|
||||||
public static final int ROWS_HEADER_LEN_V2 = 10;
|
public static final int ROWS_HEADER_LEN_V2 = 10;
|
||||||
|
public static final int TRANSACTION_CONTEXT_HEADER_LEN = 18;
|
||||||
|
public static final int VIEW_CHANGE_HEADER_LEN = 52;
|
||||||
|
public static final int XA_PREPARE_HEADER_LEN = 0;
|
||||||
|
|
||||||
public static final int ANNOTATE_ROWS_HEADER_LEN = 0;
|
public static final int ANNOTATE_ROWS_HEADER_LEN = 0;
|
||||||
public static final int BINLOG_CHECKPOINT_HEADER_LEN = 4;
|
public static final int BINLOG_CHECKPOINT_HEADER_LEN = 4;
|
||||||
public static final int GTID_HEADER_LEN = 19;
|
public static final int GTID_HEADER_LEN = 19;
|
||||||
public static final int GTID_LIST_HEADER_LEN = 4;
|
public static final int GTID_LIST_HEADER_LEN = 4;
|
||||||
|
public static final int START_ENCRYPTION_HEADER_LEN = 0;
|
||||||
|
|
||||||
public static final int POST_HEADER_LENGTH = 11;
|
public static final int POST_HEADER_LENGTH = 11;
|
||||||
|
|
||||||
@@ -202,11 +207,17 @@ public final class FormatDescriptionLogEvent extends StartLogEventV3 {
|
|||||||
postHeaderLen[GTID_LOG_EVENT - 1] = POST_HEADER_LENGTH;
|
postHeaderLen[GTID_LOG_EVENT - 1] = POST_HEADER_LENGTH;
|
||||||
postHeaderLen[ANONYMOUS_GTID_LOG_EVENT - 1] = POST_HEADER_LENGTH;
|
postHeaderLen[ANONYMOUS_GTID_LOG_EVENT - 1] = POST_HEADER_LENGTH;
|
||||||
postHeaderLen[PREVIOUS_GTIDS_LOG_EVENT - 1] = IGNORABLE_HEADER_LEN;
|
postHeaderLen[PREVIOUS_GTIDS_LOG_EVENT - 1] = IGNORABLE_HEADER_LEN;
|
||||||
|
|
||||||
|
postHeaderLen[TRANSACTION_CONTEXT_EVENT - 1] = TRANSACTION_CONTEXT_HEADER_LEN;
|
||||||
|
postHeaderLen[VIEW_CHANGE_EVENT - 1] = VIEW_CHANGE_HEADER_LEN;
|
||||||
|
postHeaderLen[XA_PREPARE_LOG_EVENT - 1] = XA_PREPARE_HEADER_LEN;
|
||||||
|
|
||||||
// mariadb 10
|
// mariadb 10
|
||||||
postHeaderLen[ANNOTATE_ROWS_EVENT - 1] = ANNOTATE_ROWS_HEADER_LEN;
|
postHeaderLen[ANNOTATE_ROWS_EVENT - 1] = ANNOTATE_ROWS_HEADER_LEN;
|
||||||
postHeaderLen[BINLOG_CHECKPOINT_EVENT - 1] = BINLOG_CHECKPOINT_HEADER_LEN;
|
postHeaderLen[BINLOG_CHECKPOINT_EVENT - 1] = BINLOG_CHECKPOINT_HEADER_LEN;
|
||||||
postHeaderLen[GTID_EVENT - 1] = GTID_HEADER_LEN;
|
postHeaderLen[GTID_EVENT - 1] = GTID_HEADER_LEN;
|
||||||
postHeaderLen[GTID_LIST_EVENT - 1] = GTID_LIST_HEADER_LEN;
|
postHeaderLen[GTID_LIST_EVENT - 1] = GTID_LIST_HEADER_LEN;
|
||||||
|
postHeaderLen[START_ENCRYPTION_EVENT - 1] = START_ENCRYPTION_HEADER_LEN;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3: /* 4.0.x x>=2 */
|
case 3: /* 4.0.x x>=2 */
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.taobao.tddl.dbsync.binlog.event;
|
package com.taobao.tddl.dbsync.binlog.event;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
|
||||||
@@ -14,8 +17,13 @@ public class GtidLogEvent extends LogEvent {
|
|||||||
public static final int ENCODED_FLAG_LENGTH = 1;
|
public static final int ENCODED_FLAG_LENGTH = 1;
|
||||||
// / Length of SID in event encoding
|
// / Length of SID in event encoding
|
||||||
public static final int ENCODED_SID_LENGTH = 16;
|
public static final int ENCODED_SID_LENGTH = 16;
|
||||||
|
public static final int LOGICAL_TIMESTAMP_TYPE_CODE = 2;
|
||||||
|
|
||||||
private boolean commitFlag;
|
private boolean commitFlag;
|
||||||
|
private UUID sid;
|
||||||
|
private long gno;
|
||||||
|
private Long lastCommitted;
|
||||||
|
private Long sequenceNumber;
|
||||||
|
|
||||||
public GtidLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
public GtidLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
||||||
super(header);
|
super(header);
|
||||||
@@ -27,6 +35,22 @@ public class GtidLogEvent extends LogEvent {
|
|||||||
buffer.position(commonHeaderLen);
|
buffer.position(commonHeaderLen);
|
||||||
commitFlag = (buffer.getUint8() != 0); // ENCODED_FLAG_LENGTH
|
commitFlag = (buffer.getUint8() != 0); // ENCODED_FLAG_LENGTH
|
||||||
|
|
||||||
|
byte[] bs = buffer.getData(ENCODED_SID_LENGTH);
|
||||||
|
ByteBuffer bb = ByteBuffer.wrap(bs);
|
||||||
|
long high = bb.getLong();
|
||||||
|
long low = bb.getLong();
|
||||||
|
sid = new UUID(high, low);
|
||||||
|
|
||||||
|
gno = buffer.getLong64();
|
||||||
|
|
||||||
|
// support gtid lastCommitted and sequenceNumber
|
||||||
|
// 42 = 1+16+8+1+8+8
|
||||||
|
if (buffer.capacity() > 42 && buffer.getUint8() == LOGICAL_TIMESTAMP_TYPE_CODE) {
|
||||||
|
lastCommitted = buffer.getLong64();
|
||||||
|
sequenceNumber = buffer.getLong64();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ignore gtid info read
|
// ignore gtid info read
|
||||||
// sid.copy_from((uchar *)ptr_buffer);
|
// sid.copy_from((uchar *)ptr_buffer);
|
||||||
// ptr_buffer+= ENCODED_SID_LENGTH;
|
// ptr_buffer+= ENCODED_SID_LENGTH;
|
||||||
@@ -42,4 +66,19 @@ public class GtidLogEvent extends LogEvent {
|
|||||||
return commitFlag;
|
return commitFlag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UUID getSid() {
|
||||||
|
return sid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getGno() {
|
||||||
|
return gno;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getLastCommitted() {
|
||||||
|
return lastCommitted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getSequenceNumber() {
|
||||||
|
return sequenceNumber;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,11 @@ public final class LogHeader {
|
|||||||
*/
|
*/
|
||||||
protected long crc; // ha_checksum
|
protected long crc; // ha_checksum
|
||||||
|
|
||||||
|
/**
|
||||||
|
* binlog fileName
|
||||||
|
*/
|
||||||
|
protected String logFileName;
|
||||||
|
|
||||||
/* for Start_event_v3 */
|
/* for Start_event_v3 */
|
||||||
public LogHeader(final int type){
|
public LogHeader(final int type){
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@@ -270,6 +275,14 @@ public final class LogHeader {
|
|||||||
return checksumAlg;
|
return checksumAlg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getLogFileName() {
|
||||||
|
return logFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogFileName(String logFileName) {
|
||||||
|
this.logFileName = logFileName;
|
||||||
|
}
|
||||||
|
|
||||||
private void processCheckSum(LogBuffer buffer) {
|
private void processCheckSum(LogBuffer buffer) {
|
||||||
if (checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_OFF && checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_UNDEF) {
|
if (checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_OFF && checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_UNDEF) {
|
||||||
crc = buffer.getUint32(eventLen - LogEvent.BINLOG_CHECKSUM_LEN);
|
crc = buffer.getUint32(eventLen - LogEvent.BINLOG_CHECKSUM_LEN);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ public final class RowsLogBuffer {
|
|||||||
public static final long DATETIMEF_INT_OFS = 0x8000000000L;
|
public static final long DATETIMEF_INT_OFS = 0x8000000000L;
|
||||||
public static final long TIMEF_INT_OFS = 0x800000L;
|
public static final long TIMEF_INT_OFS = 0x800000L;
|
||||||
public static final long TIMEF_OFS = 0x800000000000L;
|
public static final long TIMEF_OFS = 0x800000000000L;
|
||||||
|
private static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
||||||
|
|
||||||
private final LogBuffer buffer;
|
private final LogBuffer buffer;
|
||||||
private final int columnLen;
|
private final int columnLen;
|
||||||
@@ -480,13 +481,28 @@ public final class RowsLogBuffer {
|
|||||||
// cal.set(d / 10000, (d % 10000) / 100 - 1, d % 100, t /
|
// cal.set(d / 10000, (d % 10000) / 100 - 1, d % 100, t /
|
||||||
// 10000, (t % 10000) / 100, t % 100);
|
// 10000, (t % 10000) / 100, t % 100);
|
||||||
// value = new Timestamp(cal.getTimeInMillis());
|
// value = new Timestamp(cal.getTimeInMillis());
|
||||||
value = String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
// value = String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
||||||
d / 10000,
|
// d / 10000,
|
||||||
(d % 10000) / 100,
|
// (d % 10000) / 100,
|
||||||
d % 100,
|
// d % 100,
|
||||||
t / 10000,
|
// t / 10000,
|
||||||
(t % 10000) / 100,
|
// (t % 10000) / 100,
|
||||||
t % 100);
|
// t % 100);
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append(26);
|
||||||
|
appendNumber4(builder, d / 10000);
|
||||||
|
builder.append('-');
|
||||||
|
appendNumber2(builder, (d % 10000) / 100);
|
||||||
|
builder.append('-');
|
||||||
|
appendNumber2(builder, d % 100);
|
||||||
|
builder.append(' ');
|
||||||
|
appendNumber2(builder, t / 10000);
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, (t % 10000) / 100);
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, t % 100);
|
||||||
|
value = builder.toString();
|
||||||
}
|
}
|
||||||
javaType = Types.TIMESTAMP;
|
javaType = Types.TIMESTAMP;
|
||||||
length = 8;
|
length = 8;
|
||||||
@@ -540,13 +556,27 @@ public final class RowsLogBuffer {
|
|||||||
// % (1 << 5)), (int) (hms >> 12),
|
// % (1 << 5)), (int) (hms >> 12),
|
||||||
// (int) ((hms >> 6) % (1 << 6)), (int) (hms % (1 << 6)));
|
// (int) ((hms >> 6) % (1 << 6)), (int) (hms % (1 << 6)));
|
||||||
// value = new Timestamp(cal.getTimeInMillis());
|
// value = new Timestamp(cal.getTimeInMillis());
|
||||||
second = String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
// second = String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
||||||
(int) (ym / 13),
|
// (int) (ym / 13),
|
||||||
(int) (ym % 13),
|
// (int) (ym % 13),
|
||||||
(int) (ymd % (1 << 5)),
|
// (int) (ymd % (1 << 5)),
|
||||||
(int) (hms >> 12),
|
// (int) (hms >> 12),
|
||||||
(int) ((hms >> 6) % (1 << 6)),
|
// (int) ((hms >> 6) % (1 << 6)),
|
||||||
(int) (hms % (1 << 6)));
|
// (int) (hms % (1 << 6)));
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder(26);
|
||||||
|
appendNumber4(builder, (int) (ym / 13));
|
||||||
|
builder.append('-');
|
||||||
|
appendNumber2(builder, (int) (ym % 13));
|
||||||
|
builder.append('-');
|
||||||
|
appendNumber2(builder, (int) (ymd % (1 << 5)));
|
||||||
|
builder.append(' ');
|
||||||
|
appendNumber2(builder, (int) (hms >> 12));
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, (int) ((hms >> 6) % (1 << 6)));
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, (int) (hms % (1 << 6)));
|
||||||
|
second = builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta >= 1) {
|
if (meta >= 1) {
|
||||||
@@ -575,11 +605,22 @@ public final class RowsLogBuffer {
|
|||||||
// cal.set(70, 0, 1, i32 / 10000, (i32 % 10000) / 100, i32 %
|
// cal.set(70, 0, 1, i32 / 10000, (i32 % 10000) / 100, i32 %
|
||||||
// 100);
|
// 100);
|
||||||
// value = new Time(cal.getTimeInMillis());
|
// value = new Time(cal.getTimeInMillis());
|
||||||
value = String.format("%s%02d:%02d:%02d",
|
// value = String.format("%s%02d:%02d:%02d",
|
||||||
(i32 >= 0) ? "" : "-",
|
// (i32 >= 0) ? "" : "-",
|
||||||
u32 / 10000,
|
// u32 / 10000,
|
||||||
(u32 % 10000) / 100,
|
// (u32 % 10000) / 100,
|
||||||
u32 % 100);
|
// u32 % 100);
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder(12);
|
||||||
|
if (i32 < 0) {
|
||||||
|
builder.append('-');
|
||||||
|
}
|
||||||
|
appendNumber2(builder, u32 / 10000);
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, (u32 % 10000) / 100);
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, u32 % 100);
|
||||||
|
value = builder.toString();
|
||||||
}
|
}
|
||||||
javaType = Types.TIME;
|
javaType = Types.TIME;
|
||||||
length = 3;
|
length = 3;
|
||||||
@@ -673,11 +714,22 @@ public final class RowsLogBuffer {
|
|||||||
// value = new Time(cal.getTimeInMillis());
|
// value = new Time(cal.getTimeInMillis());
|
||||||
long ultime = Math.abs(ltime);
|
long ultime = Math.abs(ltime);
|
||||||
intpart = ultime >> 24;
|
intpart = ultime >> 24;
|
||||||
second = String.format("%s%02d:%02d:%02d",
|
// second = String.format("%s%02d:%02d:%02d",
|
||||||
ltime >= 0 ? "" : "-",
|
// ltime >= 0 ? "" : "-",
|
||||||
(int) ((intpart >> 12) % (1 << 10)),
|
// (int) ((intpart >> 12) % (1 << 10)),
|
||||||
(int) ((intpart >> 6) % (1 << 6)),
|
// (int) ((intpart >> 6) % (1 << 6)),
|
||||||
(int) (intpart % (1 << 6)));
|
// (int) (intpart % (1 << 6)));
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder(12);
|
||||||
|
if (ltime < 0) {
|
||||||
|
builder.append('-');
|
||||||
|
}
|
||||||
|
appendNumber2(builder, (int) ((intpart >> 12) % (1 << 10)));
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, (int) ((intpart >> 6) % (1 << 6)));
|
||||||
|
builder.append(':');
|
||||||
|
appendNumber2(builder, (int) (intpart % (1 << 6)));
|
||||||
|
second = builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta >= 1) {
|
if (meta >= 1) {
|
||||||
@@ -717,7 +769,16 @@ public final class RowsLogBuffer {
|
|||||||
// cal.set((i32 / (16 * 32)), (i32 / 32 % 16) - 1, (i32 %
|
// cal.set((i32 / (16 * 32)), (i32 / 32 % 16) - 1, (i32 %
|
||||||
// 32));
|
// 32));
|
||||||
// value = new java.sql.Date(cal.getTimeInMillis());
|
// value = new java.sql.Date(cal.getTimeInMillis());
|
||||||
value = String.format("%04d-%02d-%02d", i32 / (16 * 32), i32 / 32 % 16, i32 % 32);
|
// value = String.format("%04d-%02d-%02d", i32 / (16 * 32),
|
||||||
|
// i32 / 32 % 16, i32 % 32);
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder(12);
|
||||||
|
appendNumber4(builder, i32 / (16 * 32));
|
||||||
|
builder.append('-');
|
||||||
|
appendNumber2(builder, i32 / 32 % 16);
|
||||||
|
builder.append('-');
|
||||||
|
appendNumber2(builder, i32 % 32);
|
||||||
|
value = builder.toString();
|
||||||
}
|
}
|
||||||
javaType = Types.DATE;
|
javaType = Types.DATE;
|
||||||
length = 3;
|
length = 3;
|
||||||
@@ -969,17 +1030,18 @@ public final class RowsLogBuffer {
|
|||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("!! Unknown JSON packlen = " + meta);
|
throw new IllegalArgumentException("!! Unknown JSON packlen = " + meta);
|
||||||
}
|
}
|
||||||
// len = buffer.getUint16();
|
if (0 == len) {
|
||||||
// buffer.forward(meta - 4);
|
// fixed issue #1 by lava, json column of zero length has no
|
||||||
int position = buffer.position();
|
// value, value parsing should be skipped
|
||||||
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(), buffer, len - 1, charsetName);
|
value = "";
|
||||||
StringBuilder builder = new StringBuilder();
|
} else {
|
||||||
jsonValue.toJsonString(builder, charsetName);
|
int position = buffer.position();
|
||||||
value = builder.toString();
|
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(), buffer, len - 1, charsetName);
|
||||||
buffer.position(position + len);
|
StringBuilder builder = new StringBuilder();
|
||||||
// byte[] binary = new byte[len];
|
jsonValue.toJsonString(builder, charsetName);
|
||||||
// buffer.fillBytes(binary, 0, len);
|
value = builder.toString();
|
||||||
// value = binary;
|
buffer.position(position + len);
|
||||||
|
}
|
||||||
javaType = Types.VARCHAR;
|
javaType = Types.VARCHAR;
|
||||||
length = len;
|
length = len;
|
||||||
break;
|
break;
|
||||||
@@ -1066,4 +1128,34 @@ public final class RowsLogBuffer {
|
|||||||
return sec.substring(0, meta);
|
return sec.substring(0, meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void appendNumber4(StringBuilder builder, int d) {
|
||||||
|
if (d >= 1000) {
|
||||||
|
builder.append(digits[d / 1000])
|
||||||
|
.append(digits[(d / 100) % 10])
|
||||||
|
.append(digits[(d / 10) % 10])
|
||||||
|
.append(digits[d % 10]);
|
||||||
|
} else if (d >= 100) {
|
||||||
|
builder.append('0');
|
||||||
|
appendNumber3(builder, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendNumber3(StringBuilder builder, int d) {
|
||||||
|
if (d >= 100) {
|
||||||
|
builder.append(digits[d / 100])
|
||||||
|
.append(digits[(d / 10) % 10])
|
||||||
|
.append(digits[d % 10]);
|
||||||
|
} else {
|
||||||
|
builder.append('0');
|
||||||
|
appendNumber2(builder, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendNumber2(StringBuilder builder, int d) {
|
||||||
|
if (d >= 10) {
|
||||||
|
builder.append(digits[(d / 10) % 10]).append(digits[d % 10]);
|
||||||
|
} else {
|
||||||
|
builder.append('0').append(digits[d]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ public abstract class RowsLogEvent extends LogEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final RowsLogBuffer getRowsBuf(String charsetName) {
|
public final RowsLogBuffer getRowsBuf(String charsetName) {
|
||||||
return new RowsLogBuffer(rowsBuf.duplicate(), columnLen, charsetName);
|
return new RowsLogBuffer(rowsBuf, columnLen, charsetName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final int getFlags(final int flags) {
|
public final int getFlags(final int flags) {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.taobao.tddl.dbsync.binlog.event;
|
||||||
|
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author agapple 2018年5月7日 下午7:05:39
|
||||||
|
* @version 1.0.26
|
||||||
|
* @since mysql 5.7
|
||||||
|
*/
|
||||||
|
public class TransactionContextLogEvent extends LogEvent {
|
||||||
|
|
||||||
|
public TransactionContextLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
||||||
|
super(header);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.taobao.tddl.dbsync.binlog.event;
|
||||||
|
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author agapple 2018年5月7日 下午7:05:39
|
||||||
|
* @version 1.0.26
|
||||||
|
* @since mysql 5.7
|
||||||
|
*/
|
||||||
|
public class ViewChangeEvent extends LogEvent {
|
||||||
|
|
||||||
|
public ViewChangeEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
||||||
|
super(header);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.taobao.tddl.dbsync.binlog.event;
|
||||||
|
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author agapple 2018年5月7日 下午7:05:39
|
||||||
|
* @version 1.0.26
|
||||||
|
* @since mysql 5.7
|
||||||
|
*/
|
||||||
|
public class XaPrepareLogEvent extends LogEvent {
|
||||||
|
|
||||||
|
private boolean onePhase;
|
||||||
|
private int formatId;
|
||||||
|
private int gtridLength;
|
||||||
|
private int bqualLength;
|
||||||
|
private byte[] data;
|
||||||
|
|
||||||
|
public XaPrepareLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
||||||
|
super(header);
|
||||||
|
|
||||||
|
final int commonHeaderLen = descriptionEvent.getCommonHeaderLen();
|
||||||
|
final int postHeaderLen = descriptionEvent.getPostHeaderLen()[header.getType() - 1];
|
||||||
|
|
||||||
|
int offset = commonHeaderLen + postHeaderLen;
|
||||||
|
buffer.position(offset);
|
||||||
|
|
||||||
|
onePhase = (buffer.getInt8() == 0x00 ? false : true);
|
||||||
|
|
||||||
|
formatId = buffer.getInt32();
|
||||||
|
gtridLength = buffer.getInt32();
|
||||||
|
bqualLength = buffer.getInt32();
|
||||||
|
|
||||||
|
int MY_XIDDATASIZE = 128;
|
||||||
|
if (MY_XIDDATASIZE >= gtridLength + bqualLength && gtridLength >= 0 && gtridLength <= 64 && bqualLength >= 0
|
||||||
|
&& bqualLength <= 64) {
|
||||||
|
data = buffer.getData(gtridLength + bqualLength);
|
||||||
|
} else {
|
||||||
|
formatId = -1;
|
||||||
|
gtridLength = 0;
|
||||||
|
bqualLength = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOnePhase() {
|
||||||
|
return onePhase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFormatId() {
|
||||||
|
return formatId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getGtridLength() {
|
||||||
|
return gtridLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBqualLength() {
|
||||||
|
return bqualLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,9 +13,30 @@ import com.taobao.tddl.dbsync.binlog.event.LogHeader;
|
|||||||
*/
|
*/
|
||||||
public class MariaGtidLogEvent extends IgnorableLogEvent {
|
public class MariaGtidLogEvent extends IgnorableLogEvent {
|
||||||
|
|
||||||
|
private long gtid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* mariadb gtidlog event format
|
||||||
|
* uint<8> GTID sequence
|
||||||
|
* uint<4> Replication Domain ID
|
||||||
|
* uint<1> Flags
|
||||||
|
*
|
||||||
|
* if flag & FL_GROUP_COMMIT_ID
|
||||||
|
* uint<8> commit_id
|
||||||
|
* else
|
||||||
|
* uint<6> 0
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
|
||||||
public MariaGtidLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
public MariaGtidLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
||||||
super(header, buffer, descriptionEvent);
|
super(header, buffer, descriptionEvent);
|
||||||
|
gtid = buffer.getUlong64().longValue();
|
||||||
// do nothing , just ignore log event
|
// do nothing , just ignore log event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getGtid() {
|
||||||
|
return gtid;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.taobao.tddl.dbsync.binlog.event.mariadb;
|
||||||
|
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.LogHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mariadb的Start_encryption_log_event
|
||||||
|
*
|
||||||
|
* @author agapple 2018年5月7日 下午7:23:02
|
||||||
|
* @version 1.0.26
|
||||||
|
*/
|
||||||
|
public class StartEncryptionLogEvent extends LogEvent {
|
||||||
|
|
||||||
|
public StartEncryptionLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
|
||||||
|
super(header);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.taobao.tddl.dbsync;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import com.taobao.tddl.dbsync.binlog.DirectLogFetcher;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
|
|
||||||
|
public class FetcherPerformanceTest {
|
||||||
|
|
||||||
|
public static void main(String args[]) {
|
||||||
|
DirectLogFetcher fetcher = new DirectLogFetcher();
|
||||||
|
try {
|
||||||
|
Class.forName("com.mysql.jdbc.Driver");
|
||||||
|
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306",
|
||||||
|
"root",
|
||||||
|
"hello");
|
||||||
|
Statement statement = connection.createStatement();
|
||||||
|
statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'");
|
||||||
|
statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
|
||||||
|
|
||||||
|
fetcher.open(connection, "mysql-bin.000006", 120L, 2);
|
||||||
|
|
||||||
|
AtomicLong sum = new AtomicLong(0);
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
long last = 0;
|
||||||
|
long end = 0;
|
||||||
|
|
||||||
|
while (fetcher.fetch()) {
|
||||||
|
sum.incrementAndGet();
|
||||||
|
long current = sum.get();
|
||||||
|
if (current - last >= 100000) {
|
||||||
|
end = System.currentTimeMillis();
|
||||||
|
long tps = ((current - last) * 1000) / (end - start);
|
||||||
|
System.out.println(" total : " + sum + " , cost : " + (end - start) + " , tps : " + tps);
|
||||||
|
last = current;
|
||||||
|
start = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
fetcher.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,15 +8,6 @@ import java.sql.Statement;
|
|||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.XidLogEvent;
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent;
|
|
||||||
|
|
||||||
public class DirectLogFetcherTest extends BaseLogFetcherTest {
|
public class DirectLogFetcherTest extends BaseLogFetcherTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -24,56 +15,54 @@ public class DirectLogFetcherTest extends BaseLogFetcherTest {
|
|||||||
DirectLogFetcher fecther = new DirectLogFetcher();
|
DirectLogFetcher fecther = new DirectLogFetcher();
|
||||||
try {
|
try {
|
||||||
Class.forName("com.mysql.jdbc.Driver");
|
Class.forName("com.mysql.jdbc.Driver");
|
||||||
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306", "root", "hello");
|
Connection connection = DriverManager.getConnection("jdbc:mysql://100.81.154.142:3306", "root", "hello");
|
||||||
Statement statement = connection.createStatement();
|
Statement statement = connection.createStatement();
|
||||||
statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'");
|
statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'");
|
||||||
statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
|
statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
|
||||||
|
|
||||||
fecther.open(connection, "mysql-bin.000001", 4L, 2);
|
fecther.open(connection, "mysql-bin.000006", 120L, 2);
|
||||||
|
|
||||||
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.UNKNOWN_EVENT);
|
||||||
LogContext context = new LogContext();
|
LogContext context = new LogContext();
|
||||||
while (fecther.fetch()) {
|
while (fecther.fetch()) {
|
||||||
LogEvent event = null;
|
decoder.decode(fecther, context);
|
||||||
event = decoder.decode(fecther, context);
|
continue;
|
||||||
|
// if (event == null) {
|
||||||
if (event == null) {
|
// continue;
|
||||||
continue;
|
// }
|
||||||
// throw new RuntimeException("parse failed");
|
//
|
||||||
}
|
// int eventType = event.getHeader().getType();
|
||||||
|
// switch (eventType) {
|
||||||
int eventType = event.getHeader().getType();
|
// case LogEvent.ROTATE_EVENT:
|
||||||
switch (eventType) {
|
// binlogFileName = ((RotateLogEvent) event).getFilename();
|
||||||
case LogEvent.ROTATE_EVENT:
|
// break;
|
||||||
binlogFileName = ((RotateLogEvent) event).getFilename();
|
// case LogEvent.WRITE_ROWS_EVENT_V1:
|
||||||
break;
|
// case LogEvent.WRITE_ROWS_EVENT:
|
||||||
case LogEvent.WRITE_ROWS_EVENT_V1:
|
// parseRowsEvent((WriteRowsLogEvent) event);
|
||||||
case LogEvent.WRITE_ROWS_EVENT:
|
// break;
|
||||||
parseRowsEvent((WriteRowsLogEvent) event);
|
// case LogEvent.UPDATE_ROWS_EVENT_V1:
|
||||||
break;
|
// case LogEvent.UPDATE_ROWS_EVENT:
|
||||||
case LogEvent.UPDATE_ROWS_EVENT_V1:
|
// parseRowsEvent((UpdateRowsLogEvent) event);
|
||||||
case LogEvent.UPDATE_ROWS_EVENT:
|
// break;
|
||||||
parseRowsEvent((UpdateRowsLogEvent) event);
|
// case LogEvent.DELETE_ROWS_EVENT_V1:
|
||||||
break;
|
// case LogEvent.DELETE_ROWS_EVENT:
|
||||||
case LogEvent.DELETE_ROWS_EVENT_V1:
|
// parseRowsEvent((DeleteRowsLogEvent) event);
|
||||||
case LogEvent.DELETE_ROWS_EVENT:
|
// break;
|
||||||
parseRowsEvent((DeleteRowsLogEvent) event);
|
// case LogEvent.QUERY_EVENT:
|
||||||
break;
|
// parseQueryEvent((QueryLogEvent) event);
|
||||||
case LogEvent.QUERY_EVENT:
|
// break;
|
||||||
parseQueryEvent((QueryLogEvent) event);
|
// case LogEvent.ROWS_QUERY_LOG_EVENT:
|
||||||
break;
|
// parseRowsQueryEvent((RowsQueryLogEvent) event);
|
||||||
case LogEvent.ROWS_QUERY_LOG_EVENT:
|
// break;
|
||||||
parseRowsQueryEvent((RowsQueryLogEvent) event);
|
// case LogEvent.ANNOTATE_ROWS_EVENT:
|
||||||
break;
|
// parseAnnotateRowsEvent((AnnotateRowsEvent) event);
|
||||||
case LogEvent.ANNOTATE_ROWS_EVENT:
|
// break;
|
||||||
parseAnnotateRowsEvent((AnnotateRowsEvent) event);
|
// case LogEvent.XID_EVENT:
|
||||||
break;
|
// parseXidEvent((XidLogEvent) event);
|
||||||
case LogEvent.XID_EVENT:
|
// break;
|
||||||
parseXidEvent((XidLogEvent) event);
|
// default:
|
||||||
break;
|
// break;
|
||||||
default:
|
// }
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|||||||
5
deployer/src/main/bin/restart.sh
Normal file
5
deployer/src/main/bin/restart.sh
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
sh stop.sh
|
||||||
|
|
||||||
|
sh startup.sh
|
||||||
@@ -16,6 +16,7 @@ public class CanalConstants {
|
|||||||
public static final String CANAL_IP = ROOT + "." + "ip";
|
public static final String CANAL_IP = ROOT + "." + "ip";
|
||||||
public static final String CANAL_PORT = ROOT + "." + "port";
|
public static final String CANAL_PORT = ROOT + "." + "port";
|
||||||
public static final String CANAL_ZKSERVERS = ROOT + "." + "zkServers";
|
public static final String CANAL_ZKSERVERS = ROOT + "." + "zkServers";
|
||||||
|
public static final String CANAL_WITHOUT_NETTY = ROOT + "." + "withoutNetty";
|
||||||
|
|
||||||
public static final String CANAL_DESTINATIONS = ROOT + "." + "destinations";
|
public static final String CANAL_DESTINATIONS = ROOT + "." + "destinations";
|
||||||
public static final String CANAL_AUTO_SCAN = ROOT + "." + "auto.scan";
|
public static final String CANAL_AUTO_SCAN = ROOT + "." + "auto.scan";
|
||||||
@@ -32,6 +33,8 @@ public class CanalConstants {
|
|||||||
|
|
||||||
public static final String CANAL_DESTINATION_PROPERTY = ROOT + ".instance.destination";
|
public static final String CANAL_DESTINATION_PROPERTY = ROOT + ".instance.destination";
|
||||||
|
|
||||||
|
public static final String CANAL_SOCKETCHANNEL = ROOT + "." + "socketChannel";
|
||||||
|
|
||||||
public static String getInstanceModeKey(String destination) {
|
public static String getInstanceModeKey(String destination) {
|
||||||
return MessageFormat.format(INSTANCE_MODE_TEMPLATE, destination);
|
return MessageFormat.format(INSTANCE_MODE_TEMPLATE, destination);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,15 +85,24 @@ public class CanalController {
|
|||||||
// 初始化instance config
|
// 初始化instance config
|
||||||
initInstanceConfig(properties);
|
initInstanceConfig(properties);
|
||||||
|
|
||||||
|
// init socketChannel
|
||||||
|
String socketChannel = getProperty(properties, CanalConstants.CANAL_SOCKETCHANNEL);
|
||||||
|
if (StringUtils.isNotEmpty(socketChannel)) {
|
||||||
|
System.setProperty(CanalConstants.CANAL_SOCKETCHANNEL, socketChannel);
|
||||||
|
}
|
||||||
|
|
||||||
// 准备canal server
|
// 准备canal server
|
||||||
cid = Long.valueOf(getProperty(properties, CanalConstants.CANAL_ID));
|
cid = Long.valueOf(getProperty(properties, CanalConstants.CANAL_ID));
|
||||||
ip = getProperty(properties, CanalConstants.CANAL_IP);
|
ip = getProperty(properties, CanalConstants.CANAL_IP);
|
||||||
port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT));
|
port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT));
|
||||||
embededCanalServer = CanalServerWithEmbedded.instance();
|
embededCanalServer = CanalServerWithEmbedded.instance();
|
||||||
embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator
|
embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator
|
||||||
canalServer = CanalServerWithNetty.instance();
|
String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY);
|
||||||
canalServer.setIp(ip);
|
if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) {
|
||||||
canalServer.setPort(port);
|
canalServer = CanalServerWithNetty.instance();
|
||||||
|
canalServer.setIp(ip);
|
||||||
|
canalServer.setPort(port);
|
||||||
|
}
|
||||||
|
|
||||||
// 处理下ip为空,默认使用hostIp暴露到zk中
|
// 处理下ip为空,默认使用hostIp暴露到zk中
|
||||||
if (StringUtils.isEmpty(ip)) {
|
if (StringUtils.isEmpty(ip)) {
|
||||||
@@ -425,11 +434,15 @@ public class CanalController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 启动网络接口
|
// 启动网络接口
|
||||||
canalServer.start();
|
if (canalServer != null) {
|
||||||
|
canalServer.start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() throws Throwable {
|
public void stop() throws Throwable {
|
||||||
canalServer.stop();
|
if (canalServer != null) {
|
||||||
|
canalServer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
if (autoScan) {
|
if (autoScan) {
|
||||||
for (InstanceConfigMonitor monitor : instanceConfigMonitors.values()) {
|
for (InstanceConfigMonitor monitor : instanceConfigMonitors.values()) {
|
||||||
@@ -448,6 +461,10 @@ public class CanalController {
|
|||||||
// 释放canal的工作节点
|
// 释放canal的工作节点
|
||||||
releaseCid(ZookeeperPathUtils.getCanalClusterNode(ip + ":" + port));
|
releaseCid(ZookeeperPathUtils.getCanalClusterNode(ip + ":" + port));
|
||||||
logger.info("## stop the canal server[{}:{}]", ip, port);
|
logger.info("## stop the canal server[{}:{}]", ip, port);
|
||||||
|
|
||||||
|
if (zkclientx != null) {
|
||||||
|
zkclientx.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initCid(String path) {
|
private void initCid(String path) {
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ public class CanalLauncher {
|
|||||||
|
|
||||||
private static void setGlobalUncaughtExceptionHandler() {
|
private static void setGlobalUncaughtExceptionHandler() {
|
||||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void uncaughtException(Thread t, Throwable e) {
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
logger.error("UnCaughtException", e);
|
logger.error("UnCaughtException", e);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ canal.port= 11111
|
|||||||
canal.zkServers=
|
canal.zkServers=
|
||||||
# flush data to zk
|
# flush data to zk
|
||||||
canal.zookeeper.flush.period = 1000
|
canal.zookeeper.flush.period = 1000
|
||||||
|
canal.withoutNetty = false
|
||||||
# flush meta cursor/parse position to file
|
# flush meta cursor/parse position to file
|
||||||
canal.file.data.dir = ${canal.conf.dir}
|
canal.file.data.dir = ${canal.conf.dir}
|
||||||
canal.file.flush.period = 1000
|
canal.file.flush.period = 1000
|
||||||
@@ -50,6 +51,13 @@ canal.instance.binlog.image = FULL,MINIMAL,NOBLOB
|
|||||||
# binlog ddl isolation
|
# binlog ddl isolation
|
||||||
canal.instance.get.ddl.isolation = false
|
canal.instance.get.ddl.isolation = false
|
||||||
|
|
||||||
|
# parallel parser config
|
||||||
|
canal.instance.parser.parallel = true
|
||||||
|
## concurrent thread number, default 60% available processors, suggest not to exceed Runtime.getRuntime().availableProcessors()
|
||||||
|
#canal.instance.parser.parallelThreadSize = 16
|
||||||
|
## disruptor ringbuffer size, must be power of 2
|
||||||
|
canal.instance.parser.parallelBufferSize = 256
|
||||||
|
|
||||||
#################################################
|
#################################################
|
||||||
######### destinations #############
|
######### destinations #############
|
||||||
#################################################
|
#################################################
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
#################################################
|
#################################################
|
||||||
## mysql serverId
|
## mysql serverId
|
||||||
canal.instance.mysql.slaveId=0
|
canal.instance.mysql.slaveId=0
|
||||||
|
|
||||||
# position info
|
# position info
|
||||||
canal.instance.master.address=127.0.0.1:3306
|
canal.instance.master.address=127.0.0.1:3306
|
||||||
|
# enable gtid use true/false
|
||||||
|
canal.instance.gtidon=false
|
||||||
canal.instance.master.journal.name=
|
canal.instance.master.journal.name=
|
||||||
canal.instance.master.position=
|
canal.instance.master.position=
|
||||||
canal.instance.master.timestamp=
|
canal.instance.master.timestamp=
|
||||||
|
canal.instance.master.gtid=
|
||||||
|
|
||||||
# table meta tsdb info
|
# table meta tsdb info
|
||||||
canal.instance.tsdb.enable=true
|
canal.instance.tsdb.enable=true
|
||||||
@@ -21,6 +24,7 @@ canal.instance.tsdb.dbPassword=canal
|
|||||||
#canal.instance.standby.journal.name =
|
#canal.instance.standby.journal.name =
|
||||||
#canal.instance.standby.position =
|
#canal.instance.standby.position =
|
||||||
#canal.instance.standby.timestamp =
|
#canal.instance.standby.timestamp =
|
||||||
|
#canal.instance.standby.gtid=
|
||||||
# username/password
|
# username/password
|
||||||
canal.instance.dbUsername=canal
|
canal.instance.dbUsername=canal
|
||||||
canal.instance.dbPassword=canal
|
canal.instance.dbPassword=canal
|
||||||
|
|||||||
@@ -73,6 +73,10 @@
|
|||||||
<level value="INFO" />
|
<level value="INFO" />
|
||||||
<appender-ref ref="CANAL-META" />
|
<appender-ref ref="CANAL-META" />
|
||||||
</logger>
|
</logger>
|
||||||
|
<logger name="com.alibaba.otter.canal.kafka" additivity="false">
|
||||||
|
<level value="INFO" />
|
||||||
|
<appender-ref ref="CANAL-ROOT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
<root level="WARN">
|
<root level="WARN">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.master.position}" />
|
<property name="position" value="${canal.instance.master.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.master.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="standbyPosition">
|
<property name="standbyPosition">
|
||||||
@@ -173,6 +174,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.standby.journal.name}" />
|
<property name="journalName" value="${canal.instance.standby.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.standby.position}" />
|
<property name="position" value="${canal.instance.standby.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
|
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.standby.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
@@ -187,5 +189,13 @@
|
|||||||
<!--表结构相关-->
|
<!--表结构相关-->
|
||||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||||
|
|
||||||
|
<!--是否启用GTID模式-->
|
||||||
|
<property name="isGTIDMode" value="${canal.instance.gtidon:false}"/>
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@@ -151,6 +151,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.master.position}" />
|
<property name="position" value="${canal.instance.master.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.master.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="standbyPosition">
|
<property name="standbyPosition">
|
||||||
@@ -158,6 +159,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.standby.journal.name}" />
|
<property name="journalName" value="${canal.instance.standby.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.standby.position}" />
|
<property name="position" value="${canal.instance.standby.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
|
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.standby.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
@@ -172,5 +174,13 @@
|
|||||||
<!--表结构相关-->
|
<!--表结构相关-->
|
||||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||||
|
|
||||||
|
<!--是否启用GTID模式-->
|
||||||
|
<property name="isGTIDMode" value="${canal.instance.gtidon:false}"/>
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@@ -148,6 +148,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.master1.journal.name}" />
|
<property name="journalName" value="${canal.instance.master1.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.master1.position}" />
|
<property name="position" value="${canal.instance.master1.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.master1.timestamp}" />
|
<property name="timestamp" value="${canal.instance.master1.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.master1.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="standbyPosition">
|
<property name="standbyPosition">
|
||||||
@@ -155,6 +156,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.standby1.journal.name}" />
|
<property name="journalName" value="${canal.instance.standby1.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.standby1.position}" />
|
<property name="position" value="${canal.instance.standby1.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.standby1.timestamp}" />
|
<property name="timestamp" value="${canal.instance.standby1.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.standby1.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
@@ -164,6 +166,11 @@
|
|||||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
<bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
|
||||||
@@ -239,6 +246,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.master2.journal.name}" />
|
<property name="journalName" value="${canal.instance.master2.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.master2.position}" />
|
<property name="position" value="${canal.instance.master2.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.master2.timestamp}" />
|
<property name="timestamp" value="${canal.instance.master2.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.master2.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="standbyPosition">
|
<property name="standbyPosition">
|
||||||
@@ -246,6 +254,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.standby2.journal.name}" />
|
<property name="journalName" value="${canal.instance.standby2.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.standby2.position}" />
|
<property name="position" value="${canal.instance.standby2.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.standby2.timestamp}" />
|
<property name="timestamp" value="${canal.instance.standby2.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.standby2.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
@@ -256,5 +265,10 @@
|
|||||||
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
|
||||||
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
|
||||||
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@@ -137,5 +137,10 @@
|
|||||||
<!--表结构相关-->
|
<!--表结构相关-->
|
||||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
|
||||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
@@ -139,6 +139,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
<property name="journalName" value="${canal.instance.master.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.master.position}" />
|
<property name="position" value="${canal.instance.master.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
<property name="timestamp" value="${canal.instance.master.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.master.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="standbyPosition">
|
<property name="standbyPosition">
|
||||||
@@ -146,6 +147,7 @@
|
|||||||
<property name="journalName" value="${canal.instance.standby.journal.name}" />
|
<property name="journalName" value="${canal.instance.standby.journal.name}" />
|
||||||
<property name="position" value="${canal.instance.standby.position}" />
|
<property name="position" value="${canal.instance.standby.position}" />
|
||||||
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
|
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
|
||||||
|
<property name="gtid" value="${canal.instance.standby.gtid}" />
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
|
||||||
@@ -160,5 +162,13 @@
|
|||||||
<!--表结构相关-->
|
<!--表结构相关-->
|
||||||
<property name="enableTsdb" value="${canal.instance.tsdb.enable:false}"/>
|
<property name="enableTsdb" value="${canal.instance.tsdb.enable:false}"/>
|
||||||
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
|
||||||
|
|
||||||
|
<!--是否启用GTID模式-->
|
||||||
|
<property name="isGTIDMode" value="${canal.instance.gtidon:false}"/>
|
||||||
|
|
||||||
|
<!-- parallel parser -->
|
||||||
|
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
|
||||||
|
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
|
||||||
|
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
|
||||||
</bean>
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@@ -31,8 +31,8 @@
|
|||||||
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
|
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
|
||||||
<property name="driverClassName" value="org.h2.Driver" />
|
<property name="driverClassName" value="org.h2.Driver" />
|
||||||
<property name="url" value="${canal.instance.tsdb.url:}" />
|
<property name="url" value="${canal.instance.tsdb.url:}" />
|
||||||
<property name="username" value="${canal.instance.tsdb.dbUsername:canal}" />
|
<property name="username" value="${canal.instance.tsdb.dbUsername:}" />
|
||||||
<property name="password" value="${canal.instance.tsdb.dbPassword:canal}" />
|
<property name="password" value="${canal.instance.tsdb.dbPassword:}" />
|
||||||
<property name="maxActive" value="30" />
|
<property name="maxActive" value="30" />
|
||||||
<property name="initialSize" value="0" />
|
<property name="initialSize" value="0" />
|
||||||
<property name="minIdle" value="1" />
|
<property name="minIdle" value="1" />
|
||||||
|
|||||||
@@ -31,8 +31,8 @@
|
|||||||
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
|
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
|
||||||
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
|
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
|
||||||
<property name="url" value="${canal.instance.tsdb.url:}" />
|
<property name="url" value="${canal.instance.tsdb.url:}" />
|
||||||
<property name="username" value="${canal.instance.tsdb.dbUsername:canal}" />
|
<property name="username" value="${canal.instance.tsdb.dbUsername:}" />
|
||||||
<property name="password" value="${canal.instance.tsdb.dbPassword:canal}" />
|
<property name="password" value="${canal.instance.tsdb.dbPassword:}" />
|
||||||
<property name="maxActive" value="30" />
|
<property name="maxActive" value="30" />
|
||||||
<property name="initialSize" value="0" />
|
<property name="initialSize" value="0" />
|
||||||
<property name="minIdle" value="1" />
|
<property name="minIdle" value="1" />
|
||||||
|
|||||||
@@ -44,11 +44,16 @@ public class MysqlConnector {
|
|||||||
private long connectionId = -1;
|
private long connectionId = -1;
|
||||||
private AtomicBoolean connected = new AtomicBoolean(false);
|
private AtomicBoolean connected = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
public static final int timeout = 5 * 1000; // 5s
|
||||||
|
|
||||||
public MysqlConnector(){
|
public MysqlConnector(){
|
||||||
}
|
}
|
||||||
|
|
||||||
public MysqlConnector(InetSocketAddress address, String username, String password){
|
public MysqlConnector(InetSocketAddress address, String username, String password){
|
||||||
this.address = address;
|
String addr = address.getHostString();
|
||||||
|
int port = address.getPort();
|
||||||
|
this.address = new InetSocketAddress(addr, port);
|
||||||
|
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
}
|
}
|
||||||
@@ -145,8 +150,8 @@ public class MysqlConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void negotiate(SocketChannel channel) throws IOException {
|
private void negotiate(SocketChannel channel) throws IOException {
|
||||||
HeaderPacket header = PacketManager.readHeader(channel, 4);
|
HeaderPacket header = PacketManager.readHeader(channel, 4, timeout);
|
||||||
byte[] body = PacketManager.readBytes(channel, header.getPacketBodyLength());
|
byte[] body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
||||||
if (body[0] < 0) {// check field_count
|
if (body[0] < 0) {// check field_count
|
||||||
if (body[0] == -1) {
|
if (body[0] == -1) {
|
||||||
ErrorPacket error = new ErrorPacket();
|
ErrorPacket error = new ErrorPacket();
|
||||||
@@ -185,7 +190,7 @@ public class MysqlConnector {
|
|||||||
header = null;
|
header = null;
|
||||||
header = PacketManager.readHeader(channel, 4);
|
header = PacketManager.readHeader(channel, 4);
|
||||||
body = null;
|
body = null;
|
||||||
body = PacketManager.readBytes(channel, header.getPacketBodyLength());
|
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
|
||||||
assert body != null;
|
assert body != null;
|
||||||
if (body[0] < 0) {
|
if (body[0] < 0) {
|
||||||
if (body[0] == -1) {
|
if (body[0] == -1) {
|
||||||
@@ -334,4 +339,9 @@ public class MysqlConnector {
|
|||||||
public void setConnTimeout(int connTimeout) {
|
public void setConnTimeout(int connTimeout) {
|
||||||
this.connTimeout = connTimeout;
|
this.connTimeout = connTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.packets;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by hiwjd on 2018/4/23. hiwjd0@gmail.com
|
||||||
|
*/
|
||||||
|
public interface GTIDSet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化成字节数组
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
byte[] encode() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新当前实例
|
||||||
|
*
|
||||||
|
* @param str
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
void update(String str);
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.packets;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by hiwjd on 2018/4/23. hiwjd0@gmail.com
|
||||||
|
*/
|
||||||
|
public class MysqlGTIDSet implements GTIDSet {
|
||||||
|
|
||||||
|
public Map<String, UUIDSet> sets;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encode() throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
ByteHelper.writeUnsignedInt64LittleEndian(sets.size(), out);
|
||||||
|
|
||||||
|
for (Map.Entry<String, UUIDSet> entry : sets.entrySet()) {
|
||||||
|
out.write(entry.getValue().encode());
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(String str) {
|
||||||
|
UUIDSet us = UUIDSet.parse(str);
|
||||||
|
String sid = us.SID.toString();
|
||||||
|
if (sets.containsKey(sid)) {
|
||||||
|
sets.get(sid).intervals.addAll(us.intervals);
|
||||||
|
sets.get(sid).intervals = UUIDSet.combine(sets.get(sid).intervals);
|
||||||
|
} else {
|
||||||
|
sets.put(sid, us);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null) return false;
|
||||||
|
if (this == o) return true;
|
||||||
|
|
||||||
|
MysqlGTIDSet gs = (MysqlGTIDSet) o;
|
||||||
|
if (gs.sets == null) return false;
|
||||||
|
|
||||||
|
for (Map.Entry<String, UUIDSet> entry : sets.entrySet()) {
|
||||||
|
if (!entry.getValue().equals(gs.sets.get(entry.getKey()))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析如下格式的字符串为MysqlGTIDSet: 726757ad-4455-11e8-ae04-0242ac110002:1 =>
|
||||||
|
* MysqlGTIDSet{ sets: { 726757ad-4455-11e8-ae04-0242ac110002: UUIDSet{ SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:2}] } }
|
||||||
|
* } 726757ad-4455-11e8-ae04-0242ac110002:1-3 => MysqlGTIDSet{ sets: {
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002: UUIDSet{ SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4}] } }
|
||||||
|
* } 726757ad-4455-11e8-ae04-0242ac110002:1-3:4 => MysqlGTIDSet{ sets: {
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002: UUIDSet{ SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:5}] } }
|
||||||
|
* } 726757ad-4455-11e8-ae04-0242ac110002:1-3:7-9 => MysqlGTIDSet{ sets: {
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002: UUIDSet{ SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4},
|
||||||
|
* {start:7, stop: 10}] } } }
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002:1-3,726757
|
||||||
|
* ad-4455-11e8-ae04-0242ac110003:4 => MysqlGTIDSet{ sets: {
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002: UUIDSet{ SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4}] },
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110003: UUIDSet{ SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:4, stop:5}] } }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param gtidData
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static MysqlGTIDSet parse(String gtidData) {
|
||||||
|
Map<String, UUIDSet> m;
|
||||||
|
|
||||||
|
if (gtidData == null || gtidData.length() < 1) {
|
||||||
|
m = new HashMap<String, UUIDSet>();
|
||||||
|
} else {
|
||||||
|
String[] uuidStrs = gtidData.split(",");
|
||||||
|
m = new HashMap<String, UUIDSet>(uuidStrs.length);
|
||||||
|
for (int i = 0; i < uuidStrs.length; i++) {
|
||||||
|
UUIDSet uuidSet = UUIDSet.parse(uuidStrs[i]);
|
||||||
|
m.put(uuidSet.SID.toString(), uuidSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MysqlGTIDSet gs = new MysqlGTIDSet();
|
||||||
|
gs.sets = m;
|
||||||
|
|
||||||
|
return gs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for (Map.Entry<String, UUIDSet> entry : sets.entrySet()) {
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
sb.append(entry.getValue().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.packets;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by hiwjd on 2018/4/23. hiwjd0@gmail.com
|
||||||
|
*/
|
||||||
|
public class UUIDSet {
|
||||||
|
|
||||||
|
public UUID SID;
|
||||||
|
public List<Interval> intervals;
|
||||||
|
|
||||||
|
public byte[] encode() throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
|
||||||
|
bb.putLong(SID.getMostSignificantBits());
|
||||||
|
bb.putLong(SID.getLeastSignificantBits());
|
||||||
|
|
||||||
|
out.write(bb.array());
|
||||||
|
|
||||||
|
ByteHelper.writeUnsignedInt64LittleEndian(intervals.size(), out);
|
||||||
|
|
||||||
|
for (Interval interval : intervals) {
|
||||||
|
ByteHelper.writeUnsignedInt64LittleEndian(interval.start, out);
|
||||||
|
ByteHelper.writeUnsignedInt64LittleEndian(interval.stop, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null) return false;
|
||||||
|
if (this == o) return true;
|
||||||
|
|
||||||
|
UUIDSet us = (UUIDSet) o;
|
||||||
|
Collections.sort(intervals);
|
||||||
|
Collections.sort(us.intervals);
|
||||||
|
if (SID.equals(us.SID) && intervals.equals(us.intervals)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Interval implements Comparable<Interval> {
|
||||||
|
|
||||||
|
public long start;
|
||||||
|
public long stop;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
Interval interval = (Interval) o;
|
||||||
|
|
||||||
|
if (start != interval.start) return false;
|
||||||
|
return stop == interval.stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = (int) (start ^ (start >>> 32));
|
||||||
|
result = 31 * result + (int) (stop ^ (stop >>> 32));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Interval o) {
|
||||||
|
if (equals(o)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return start > o.start ? 1 : (start == o.start ? 0 : -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析如下格式字符串为UUIDSet: 726757ad-4455-11e8-ae04-0242ac110002:1 => UUIDSet{SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:2}]}
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002:1-3 => UUIDSet{SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4}]}
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002:1-3:4 UUIDSet{SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:5}]}
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002:1-3:7-9 UUIDSet{SID:
|
||||||
|
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4},
|
||||||
|
* {start:7, stop:10}]}
|
||||||
|
*
|
||||||
|
* @param str
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static UUIDSet parse(String str) {
|
||||||
|
String[] ss = str.split(":");
|
||||||
|
|
||||||
|
if (ss.length < 2) {
|
||||||
|
throw new RuntimeException(String.format("parseUUIDSet failed due to wrong format: %s", str));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Interval> intervals = new ArrayList<Interval>();
|
||||||
|
for (int i = 1; i < ss.length; i++) {
|
||||||
|
intervals.add(parseInterval(ss[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
UUIDSet uuidSet = new UUIDSet();
|
||||||
|
uuidSet.SID = UUID.fromString(ss[0]);
|
||||||
|
uuidSet.intervals = combine(intervals);
|
||||||
|
|
||||||
|
return uuidSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append(SID.toString());
|
||||||
|
for (Interval interval : intervals) {
|
||||||
|
if (interval.start == interval.stop - 1) {
|
||||||
|
sb.append(":");
|
||||||
|
sb.append(interval.start);
|
||||||
|
} else {
|
||||||
|
sb.append(":");
|
||||||
|
sb.append(interval.start);
|
||||||
|
sb.append("-");
|
||||||
|
sb.append(interval.stop - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析如下格式字符串为Interval: 1 => Interval{start:1, stop:2} 1-3 =>
|
||||||
|
* Interval{start:1, stop:4} 注意!字符串格式表达时[n,m]是两侧都包含的,Interval表达时[n,m)右侧开
|
||||||
|
*
|
||||||
|
* @param str
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Interval parseInterval(String str) {
|
||||||
|
String[] ss = str.split("-");
|
||||||
|
|
||||||
|
Interval interval = new Interval();
|
||||||
|
switch (ss.length) {
|
||||||
|
case 1:
|
||||||
|
interval.start = Long.parseLong(ss[0]);
|
||||||
|
interval.stop = interval.start + 1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
interval.start = Long.parseLong(ss[0]);
|
||||||
|
interval.stop = Long.parseLong(ss[1]) + 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new RuntimeException(String.format("parseInterval failed due to wrong format: %s", str));
|
||||||
|
}
|
||||||
|
|
||||||
|
return interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把{start,stop}连续的合并掉: [{start:1, stop:4},{start:4, stop:5}] => [{start:1,
|
||||||
|
* stop:5}]
|
||||||
|
*
|
||||||
|
* @param intervals
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static List<Interval> combine(List<Interval> intervals) {
|
||||||
|
List<Interval> combined = new ArrayList<Interval>();
|
||||||
|
Collections.sort(intervals);
|
||||||
|
int len = intervals.size();
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
combined.add(intervals.get(i));
|
||||||
|
|
||||||
|
int j;
|
||||||
|
for (j = i + 1; j < len; j++) {
|
||||||
|
if (intervals.get(i).stop >= intervals.get(j).start) {
|
||||||
|
intervals.get(i).stop = intervals.get(j).stop;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = j - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,7 +54,7 @@ public class BinlogDumpCommandPacket extends CommandPacket {
|
|||||||
// 1. write 4 bytes bin-log position to start at
|
// 1. write 4 bytes bin-log position to start at
|
||||||
ByteHelper.writeUnsignedIntLittleEndian(binlogPosition, out);
|
ByteHelper.writeUnsignedIntLittleEndian(binlogPosition, out);
|
||||||
// 2. write 2 bytes bin-log flags
|
// 2. write 2 bytes bin-log flags
|
||||||
int binlog_flags = 0;
|
int binlog_flags = BINLOG_DUMP_NON_BLOCK;
|
||||||
binlog_flags |= BINLOG_SEND_ANNOTATE_ROWS_EVENT;
|
binlog_flags |= BINLOG_SEND_ANNOTATE_ROWS_EVENT;
|
||||||
out.write(binlog_flags);
|
out.write(binlog_flags);
|
||||||
out.write(0x00);
|
out.write(0x00);
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.packets.client;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.CommandPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by hiwjd on 2018/4/24. hiwjd0@gmail.com
|
||||||
|
* https://dev.mysql.com/doc/internals/en/com-binlog-dump-gtid.html
|
||||||
|
*/
|
||||||
|
public class BinlogDumpGTIDCommandPacket extends CommandPacket {
|
||||||
|
|
||||||
|
public static final int BINLOG_DUMP_NON_BLOCK = 0x01;
|
||||||
|
public static final int BINLOG_THROUGH_POSITION = 0x02;
|
||||||
|
public static final int BINLOG_THROUGH_GTID = 0x04;
|
||||||
|
|
||||||
|
public long slaveServerId;
|
||||||
|
public GTIDSet gtidSet;
|
||||||
|
|
||||||
|
public BinlogDumpGTIDCommandPacket(){
|
||||||
|
setCommand((byte) 0x1e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fromBytes(byte[] data) throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] toBytes() throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
// 0. [1] write command number
|
||||||
|
out.write(getCommand());
|
||||||
|
// 1. [2] flags
|
||||||
|
ByteHelper.writeUnsignedShortLittleEndian(BINLOG_THROUGH_GTID, out);
|
||||||
|
// 2. [4] server-id
|
||||||
|
ByteHelper.writeUnsignedIntLittleEndian(slaveServerId, out);
|
||||||
|
// 3. [4] binlog-filename-len
|
||||||
|
ByteHelper.writeUnsignedIntLittleEndian(0, out);
|
||||||
|
// 4. [] binlog-filename
|
||||||
|
// skip
|
||||||
|
// 5. [8] binlog-pos
|
||||||
|
ByteHelper.writeUnsignedInt64LittleEndian(4, out);
|
||||||
|
// if flags & BINLOG_THROUGH_GTID {
|
||||||
|
byte[] bs = gtidSet.encode();
|
||||||
|
// 6. [4] data-size
|
||||||
|
ByteHelper.writeUnsignedIntLittleEndian(bs.length, out);
|
||||||
|
// 7, [] data
|
||||||
|
// [8] n_sids // 文档写的是4个字节,其实是8个字节
|
||||||
|
// for n_sids {
|
||||||
|
// [16] SID
|
||||||
|
// [8] n_intervals
|
||||||
|
// for n_intervals {
|
||||||
|
// [8] start (signed)
|
||||||
|
// [8] end (signed)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
out.write(bs);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.packets.client;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.CommandPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* COM_REGISTER_SLAVE
|
||||||
|
*
|
||||||
|
* @author zhibinliu
|
||||||
|
* @since 1.0.24
|
||||||
|
*/
|
||||||
|
public class RegisterSlaveCommandPacket extends CommandPacket {
|
||||||
|
|
||||||
|
public String reportHost;
|
||||||
|
public int reportPort;
|
||||||
|
public String reportUser;
|
||||||
|
public String reportPasswd;
|
||||||
|
public long serverId;
|
||||||
|
|
||||||
|
public RegisterSlaveCommandPacket(){
|
||||||
|
setCommand((byte) 0x15);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fromBytes(byte[] data) {
|
||||||
|
// bypass
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] toLH(int n) {
|
||||||
|
byte[] b = new byte[4];
|
||||||
|
b[0] = (byte) (n & 0xff);
|
||||||
|
b[1] = (byte) (n >> 8 & 0xff);
|
||||||
|
b[2] = (byte) (n >> 16 & 0xff);
|
||||||
|
b[3] = (byte) (n >> 24 & 0xff);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] toBytes() throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
out.write(getCommand());
|
||||||
|
ByteHelper.writeUnsignedIntLittleEndian(serverId, out);
|
||||||
|
out.write((byte) reportHost.getBytes().length);
|
||||||
|
ByteHelper.writeFixedLengthBytesFromStart(reportHost.getBytes(), reportHost.getBytes().length, out);
|
||||||
|
out.write((byte) reportUser.getBytes().length);
|
||||||
|
ByteHelper.writeFixedLengthBytesFromStart(reportUser.getBytes(), reportUser.getBytes().length, out);
|
||||||
|
out.write((byte) reportPasswd.getBytes().length);
|
||||||
|
ByteHelper.writeFixedLengthBytesFromStart(reportPasswd.getBytes(), reportPasswd.getBytes().length, out);
|
||||||
|
ByteHelper.writeUnsignedShortLittleEndian(reportPort, out);
|
||||||
|
ByteHelper.writeUnsignedIntLittleEndian(0, out);// Fake
|
||||||
|
// rpl_recovery_rank
|
||||||
|
ByteHelper.writeUnsignedIntLittleEndian(0, out);// master id
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.packets.client;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.CommandPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* semi ack command
|
||||||
|
*
|
||||||
|
* @author amos_chen
|
||||||
|
*/
|
||||||
|
public class SemiAckCommandPacket extends CommandPacket {
|
||||||
|
|
||||||
|
public long binlogPosition;
|
||||||
|
public String binlogFileName;
|
||||||
|
|
||||||
|
public SemiAckCommandPacket(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fromBytes(byte[] data) throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Bytes Name
|
||||||
|
* --------------------------------------------------------
|
||||||
|
* Bytes Name
|
||||||
|
* ----- ----
|
||||||
|
* 1 semi mark
|
||||||
|
* 8 binlog position to start at (little endian)
|
||||||
|
* n binlog file name
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public byte[] toBytes() throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
// 0 write semi mark
|
||||||
|
out.write(0xef);
|
||||||
|
// 1 write 8 bytes for position
|
||||||
|
ByteHelper.write8ByteUnsignedIntLittleEndian(binlogPosition, out);
|
||||||
|
|
||||||
|
// 2 write binlog filename
|
||||||
|
if (StringUtils.isNotEmpty(binlogFileName)) {
|
||||||
|
out.write(binlogFileName.getBytes());
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.nio.channels.ClosedByInterruptException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用BIO进行dump
|
||||||
|
*
|
||||||
|
* @author chuanyi
|
||||||
|
*/
|
||||||
|
public class BioSocketChannel implements SocketChannel {
|
||||||
|
|
||||||
|
static final int DEFAULT_CONNECT_TIMEOUT = 10 * 1000;
|
||||||
|
static final int SO_TIMEOUT = 1000;
|
||||||
|
private Socket socket;
|
||||||
|
private InputStream input;
|
||||||
|
private OutputStream output;
|
||||||
|
|
||||||
|
BioSocketChannel(Socket socket) throws IOException{
|
||||||
|
this.socket = socket;
|
||||||
|
this.input = new BufferedInputStream(socket.getInputStream(), 16384);
|
||||||
|
this.output = socket.getOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(byte[]... buf) throws IOException {
|
||||||
|
OutputStream output = this.output;
|
||||||
|
if (output != null) {
|
||||||
|
for (byte[] bs : buf) {
|
||||||
|
output.write(bs);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new SocketException("Socket already closed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] read(int readSize) throws IOException {
|
||||||
|
InputStream input = this.input;
|
||||||
|
byte[] data = new byte[readSize];
|
||||||
|
int remain = readSize;
|
||||||
|
if (input == null) {
|
||||||
|
throw new SocketException("Socket already closed.");
|
||||||
|
}
|
||||||
|
while (remain > 0) {
|
||||||
|
try {
|
||||||
|
int read = input.read(data, readSize - remain, remain);
|
||||||
|
if (read > -1) {
|
||||||
|
remain -= read;
|
||||||
|
} else {
|
||||||
|
throw new IOException("EOF encountered.");
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException te) {
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new ClosedByInterruptException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] read(int readSize, int timeout) throws IOException {
|
||||||
|
InputStream input = this.input;
|
||||||
|
byte[] data = new byte[readSize];
|
||||||
|
int remain = readSize;
|
||||||
|
int accTimeout = 0;
|
||||||
|
if (input == null) {
|
||||||
|
throw new SocketException("Socket already closed.");
|
||||||
|
}
|
||||||
|
while (remain > 0 && accTimeout < timeout) {
|
||||||
|
try {
|
||||||
|
int read = input.read(data, readSize - remain, remain);
|
||||||
|
if (read > -1) {
|
||||||
|
remain -= read;
|
||||||
|
} else {
|
||||||
|
throw new IOException("EOF encountered.");
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException te) {
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new ClosedByInterruptException();
|
||||||
|
}
|
||||||
|
accTimeout += SO_TIMEOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (remain > 0 && accTimeout >= timeout) {
|
||||||
|
throw new SocketTimeoutException("Timeout occurred, failed to read " + readSize + " bytes in " + timeout
|
||||||
|
+ " milliseconds.");
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read(byte[] data, int off, int len, int timeout) throws IOException {
|
||||||
|
InputStream input = this.input;
|
||||||
|
int accTimeout = 0;
|
||||||
|
if (input == null) {
|
||||||
|
throw new SocketException("Socket already closed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int n = 0;
|
||||||
|
while (n < len && accTimeout < timeout) {
|
||||||
|
try {
|
||||||
|
int read = input.read(data, off + n, len - n);
|
||||||
|
if (read > -1) {
|
||||||
|
n += read;
|
||||||
|
} else {
|
||||||
|
throw new IOException("EOF encountered.");
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException te) {
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new ClosedByInterruptException();
|
||||||
|
}
|
||||||
|
accTimeout += SO_TIMEOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n < len && accTimeout >= timeout) {
|
||||||
|
throw new SocketTimeoutException("Timeout occurred, failed to read " + len + " bytes in " + timeout
|
||||||
|
+ " milliseconds.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
Socket socket = this.socket;
|
||||||
|
if (socket != null) {
|
||||||
|
return socket.isConnected();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAddress getRemoteSocketAddress() {
|
||||||
|
Socket socket = this.socket;
|
||||||
|
if (socket != null) {
|
||||||
|
return socket.getRemoteSocketAddress();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
Socket socket = this.socket;
|
||||||
|
if (socket != null) {
|
||||||
|
try {
|
||||||
|
socket.shutdownInput();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore, could not do anymore
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
socket.shutdownOutput();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore, could not do anymore
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore, could not do anymore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.input = null;
|
||||||
|
this.output = null;
|
||||||
|
this.socket = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author luoyaogui 实现channel的管理(监听连接、读数据、回收) 2016-12-28
|
||||||
|
* @author chuanyi 2018-3-3 保留<code>open</code>减少文件变更数量
|
||||||
|
*/
|
||||||
|
public abstract class BioSocketChannelPool {
|
||||||
|
|
||||||
|
public static BioSocketChannel open(SocketAddress address) throws Exception {
|
||||||
|
Socket socket = new Socket();
|
||||||
|
socket.setSoTimeout(BioSocketChannel.SO_TIMEOUT);
|
||||||
|
socket.setTcpNoDelay(true);
|
||||||
|
socket.setKeepAlive(true);
|
||||||
|
socket.setReuseAddress(true);
|
||||||
|
socket.connect(address, BioSocketChannel.DEFAULT_CONNECT_TIMEOUT);
|
||||||
|
return new BioSocketChannel(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.PooledByteBufAllocator;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.util.internal.OutOfDirectMemoryError;
|
||||||
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
import io.netty.util.internal.SystemPropertyUtil;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.NotImplementedException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 封装netty的通信channel和数据接收缓存,实现读、写、连接校验的功能。 2016-12-28
|
||||||
|
*
|
||||||
|
* @author luoyaogui
|
||||||
|
*/
|
||||||
|
public class NettySocketChannel implements SocketChannel {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(SocketChannel.class);
|
||||||
|
private static final int WAIT_PERIOD = 10; // milliseconds
|
||||||
|
private static final int DEFAULT_INIT_BUFFER_SIZE = 1024 * 1024; // 1MB,默认初始缓存大小
|
||||||
|
// 参考 mysql-connector-java-5.1.40.jar: com.mysql.jdbc.MysqlIO.maxThreeBytes
|
||||||
|
// < 256 * 256 * 256 = 16MB
|
||||||
|
private static final int DEFAULT_MAX_BUFFER_SIZE = 16 * DEFAULT_INIT_BUFFER_SIZE; // 16MB,默认最大缓存大小
|
||||||
|
private Channel channel = null;
|
||||||
|
private Object lock = new Object();
|
||||||
|
private ByteBuf cache = PooledByteBufAllocator.DEFAULT.directBuffer(DEFAULT_INIT_BUFFER_SIZE); // 缓存大小
|
||||||
|
private int maxDirectBuffer = cache.maxCapacity();
|
||||||
|
|
||||||
|
public Channel getChannel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChannel(Channel channel) {
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeCache(ByteBuf buf) throws InterruptedException, IOException {
|
||||||
|
synchronized (lock) {
|
||||||
|
while (true) {
|
||||||
|
if (null == cache) {
|
||||||
|
throw new IOException("socket is closed !");
|
||||||
|
}
|
||||||
|
|
||||||
|
// source buffer is empty.
|
||||||
|
if (!buf.isReadable()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认缓存大小不够用时需自动清理或扩充,否则将因缓存空间不足而造成I/O超时假象
|
||||||
|
int length = buf.readableBytes();
|
||||||
|
int deltaSize = length - cache.writableBytes();
|
||||||
|
if (deltaSize > 0) {
|
||||||
|
// 首先避免频繁分配内存(扩容/收缩),其次避免频繁移动内存(清理)
|
||||||
|
if (cache.readerIndex() >= deltaSize) { // 可以清理
|
||||||
|
// 回收已读空间,重置读写指针
|
||||||
|
cache.discardReadBytes();
|
||||||
|
// 恢复自动扩充的过大缓存到默认初始缓存大小,释放空间
|
||||||
|
int oldCapacity = cache.capacity();
|
||||||
|
if (oldCapacity > DEFAULT_MAX_BUFFER_SIZE) { // 尝试收缩
|
||||||
|
int newCapacity = cache.writerIndex();
|
||||||
|
newCapacity = ((newCapacity - 1) / DEFAULT_INIT_BUFFER_SIZE + 1) * DEFAULT_INIT_BUFFER_SIZE; // 对齐
|
||||||
|
int quarter = (newCapacity >> 2); // 至少留空四分之一
|
||||||
|
quarter = ((quarter - 1) / DEFAULT_INIT_BUFFER_SIZE + 1) * DEFAULT_INIT_BUFFER_SIZE; // 对齐
|
||||||
|
newCapacity += quarter; // 留空四分之一
|
||||||
|
if (newCapacity < (oldCapacity >> 1)) { // 至少收缩二分之一
|
||||||
|
try {
|
||||||
|
cache.capacity(newCapacity);
|
||||||
|
logger.info("shrink cache capacity: {} - {} = {} bytes",
|
||||||
|
oldCapacity,
|
||||||
|
oldCapacity - newCapacity,
|
||||||
|
newCapacity);
|
||||||
|
} catch (OutOfMemoryError ignore) {
|
||||||
|
maxDirectBuffer = oldCapacity; // 未来不再超过当前容量,记录日志后继续
|
||||||
|
logger.warn("cache OutOfMemoryError: {} bytes", newCapacity, ignore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // 尝试扩容
|
||||||
|
int oldCapacity = cache.capacity();
|
||||||
|
if (oldCapacity < maxDirectBuffer) {
|
||||||
|
int quarter = (oldCapacity >> 2); // 至少扩容四分之一
|
||||||
|
quarter = ((quarter - 1) / DEFAULT_INIT_BUFFER_SIZE + 1) * DEFAULT_INIT_BUFFER_SIZE; // 对齐
|
||||||
|
deltaSize = ((deltaSize - 1) / quarter + 1) * quarter; // 对齐
|
||||||
|
int newCapacity = oldCapacity + deltaSize;
|
||||||
|
if (newCapacity > maxDirectBuffer) {
|
||||||
|
newCapacity = maxDirectBuffer;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
cache.capacity(newCapacity);
|
||||||
|
logger.info("expand cache capacity: {} + {} = {} bytes",
|
||||||
|
oldCapacity,
|
||||||
|
newCapacity - oldCapacity,
|
||||||
|
newCapacity);
|
||||||
|
} catch (OutOfDirectMemoryError e) {
|
||||||
|
// failed to allocate 885571168 byte(s) of
|
||||||
|
// direct memory (used: 1002946176, max:
|
||||||
|
// 1888485376)
|
||||||
|
long maxDirectMemory = SystemPropertyUtil.getLong("io.netty.maxDirectMemory", -1);
|
||||||
|
if (maxDirectMemory < 0) {
|
||||||
|
maxDirectMemory = PlatformDependent.maxDirectMemory();
|
||||||
|
}
|
||||||
|
if (maxDirectBuffer > maxDirectMemory) {
|
||||||
|
maxDirectBuffer = (int) maxDirectMemory;
|
||||||
|
newCapacity = maxDirectBuffer;
|
||||||
|
logger.warn("resize maxDirectBuffer: {} bytes", maxDirectBuffer, e);
|
||||||
|
try {
|
||||||
|
cache.capacity(newCapacity);
|
||||||
|
logger.info("expand cache capacity: {} + {} = {} bytes",
|
||||||
|
oldCapacity,
|
||||||
|
newCapacity - oldCapacity,
|
||||||
|
newCapacity);
|
||||||
|
} catch (OutOfMemoryError ignore) {
|
||||||
|
maxDirectBuffer = oldCapacity; // 未来不再超过当前容量,记录日志后继续
|
||||||
|
logger.warn("cache OutOfMemoryError: {} bytes", newCapacity, ignore);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
maxDirectBuffer = oldCapacity; // 未来不再超过当前容量,记录日志后继续
|
||||||
|
logger.warn("cache OutOfDirectMemoryError: {} bytes", newCapacity, e);
|
||||||
|
}
|
||||||
|
} catch (OutOfMemoryError ignore) {
|
||||||
|
maxDirectBuffer = oldCapacity; // 未来不再超过当前容量,记录日志后继续
|
||||||
|
logger.warn("cache OutOfMemoryError: {} bytes", newCapacity, ignore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deltaSize = length - cache.writableBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deltaSize != length) {
|
||||||
|
// deltaSize <= 0 可全部写入,deltaSize > 0 只能部分写入
|
||||||
|
if (deltaSize <= 0) {
|
||||||
|
cache.writeBytes(buf, length);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
cache.writeBytes(buf, length - deltaSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// dest buffer is full.
|
||||||
|
lock.wait(WAIT_PERIOD);
|
||||||
|
// 回收已读空间,重置读写指针
|
||||||
|
cache.discardReadBytes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(byte[]... buf) throws IOException {
|
||||||
|
if (channel != null && channel.isWritable()) {
|
||||||
|
channel.writeAndFlush(Unpooled.copiedBuffer(buf));
|
||||||
|
} else {
|
||||||
|
throw new IOException("write failed ! please checking !");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] read(int readSize) throws IOException {
|
||||||
|
return read(readSize, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] read(int readSize, int timeout) throws IOException {
|
||||||
|
int accumulatedWaitTime = 0;
|
||||||
|
|
||||||
|
// 若读取内容较长,则自动扩充超时时间,以初始缓存大小为基准计算倍数
|
||||||
|
if (timeout > 0 && readSize > DEFAULT_INIT_BUFFER_SIZE) {
|
||||||
|
timeout *= (readSize / DEFAULT_INIT_BUFFER_SIZE + 1);
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
if (readSize > cache.readableBytes()) {
|
||||||
|
if (null == channel) {
|
||||||
|
throw new IOException("socket has Interrupted !");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeout > 0) {
|
||||||
|
accumulatedWaitTime += WAIT_PERIOD;
|
||||||
|
if (accumulatedWaitTime > timeout) {
|
||||||
|
StringBuilder sb = new StringBuilder("socket read timeout occured !");
|
||||||
|
sb.append(" readSize = ").append(readSize);
|
||||||
|
sb.append(", readableBytes = ").append(cache.readableBytes());
|
||||||
|
sb.append(", timeout = ").append(timeout);
|
||||||
|
throw new IOException(sb.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
try {
|
||||||
|
wait(WAIT_PERIOD);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new IOException("socket has Interrupted !");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
byte[] back = new byte[readSize];
|
||||||
|
synchronized (lock) {
|
||||||
|
cache.readBytes(back);
|
||||||
|
}
|
||||||
|
return back;
|
||||||
|
}
|
||||||
|
} while (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read(byte[] data, int off, int len, int timeout) throws IOException {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return channel != null ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAddress getRemoteSocketAddress() {
|
||||||
|
return channel != null ? channel.remoteAddress() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
if (channel != null) {
|
||||||
|
channel.close();
|
||||||
|
}
|
||||||
|
channel = null;
|
||||||
|
// A fatal error has been detected by the Java Runtime Environment:
|
||||||
|
// EXCEPTION_ACCESS_VIOLATION (0xc0000005)
|
||||||
|
synchronized (lock) {
|
||||||
|
cache.discardReadBytes();// 回收已占用的内存
|
||||||
|
cache.release();// 释放整个内存
|
||||||
|
cache = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||||
|
|
||||||
|
import io.netty.bootstrap.Bootstrap;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.PooledByteBufAllocator;
|
||||||
|
import io.netty.channel.AdaptiveRecvByteBufAllocator;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInitializer;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.channel.EventLoopGroup;
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author luoyaogui 实现channel的管理(监听连接、读数据、回收) 2016-12-28
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "rawtypes", "deprecation" })
|
||||||
|
public abstract class NettySocketChannelPool {
|
||||||
|
|
||||||
|
private static EventLoopGroup group = new NioEventLoopGroup(); // 非阻塞IO线程组
|
||||||
|
private static Bootstrap boot = new Bootstrap(); // 主
|
||||||
|
private static Map<Channel, SocketChannel> chManager = new ConcurrentHashMap<Channel, SocketChannel>();
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(NettySocketChannelPool.class);
|
||||||
|
|
||||||
|
static {
|
||||||
|
boot.group(group)
|
||||||
|
.channel(NioSocketChannel.class)
|
||||||
|
.option(ChannelOption.TCP_NODELAY, true)
|
||||||
|
// 如果是延时敏感型应用,建议关闭Nagle算法
|
||||||
|
.option(ChannelOption.SO_KEEPALIVE, true)
|
||||||
|
.option(ChannelOption.SO_REUSEADDR, true)
|
||||||
|
.option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)
|
||||||
|
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
|
||||||
|
//
|
||||||
|
.handler(new ChannelInitializer() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initChannel(Channel ch) throws Exception {
|
||||||
|
ch.pipeline().addLast(new BusinessHandler());// 命令过滤和handler添加管理
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SocketChannel open(SocketAddress address) throws Exception {
|
||||||
|
SocketChannel socket = null;
|
||||||
|
ChannelFuture future = boot.connect(address).sync();
|
||||||
|
|
||||||
|
if (future.isSuccess()) {
|
||||||
|
future.channel().pipeline().get(BusinessHandler.class).latch.await();
|
||||||
|
socket = chManager.get(future.channel());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null == socket) {
|
||||||
|
throw new IOException("can't create socket!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BusinessHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||||
|
|
||||||
|
private NettySocketChannel socket = null;
|
||||||
|
private final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
socket.setChannel(null);
|
||||||
|
chManager.remove(ctx.channel());// 移除
|
||||||
|
super.channelInactive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
socket = new NettySocketChannel();
|
||||||
|
socket.setChannel(ctx.channel());
|
||||||
|
chManager.put(ctx.channel(), socket);
|
||||||
|
latch.countDown();
|
||||||
|
super.channelActive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
|
||||||
|
if (socket != null) {
|
||||||
|
socket.writeCache(msg);
|
||||||
|
} else {
|
||||||
|
// TODO: need graceful error handler.
|
||||||
|
logger.error("no socket available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
|
// need output error for troubeshooting.
|
||||||
|
logger.error("business error.", cause);
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,85 +1,25 @@
|
|||||||
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.buffer.PooledByteBufAllocator;
|
|
||||||
import io.netty.buffer.Unpooled;
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 封装netty的通信channel和数据接收缓存,实现读、写、连接校验的功能。 2016-12-28
|
* @author agapple 2018年3月12日 下午10:36:44
|
||||||
*
|
* @since 1.0.26
|
||||||
* @author luoyaogui
|
|
||||||
*/
|
*/
|
||||||
public class SocketChannel {
|
public interface SocketChannel {
|
||||||
|
|
||||||
private Channel channel = null;
|
public void write(byte[]... buf) throws IOException;
|
||||||
private Object lock = new Object();
|
|
||||||
private ByteBuf cache = PooledByteBufAllocator.DEFAULT.directBuffer(1024 * 1024); // 缓存大小
|
|
||||||
|
|
||||||
public Channel getChannel() {
|
public byte[] read(int readSize) throws IOException;
|
||||||
return channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChannel(Channel channel) {
|
public byte[] read(int readSize, int timeout) throws IOException;
|
||||||
this.channel = channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeCache(ByteBuf buf) {
|
public void read(byte[] data, int off, int len, int timeout) throws IOException;
|
||||||
synchronized (lock) {
|
|
||||||
cache.discardReadBytes();// 回收内存
|
|
||||||
cache.writeBytes(buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeChannel(byte[]... buf) throws IOException {
|
public boolean isConnected();
|
||||||
if (channel != null && channel.isWritable()) {
|
|
||||||
channel.writeAndFlush(Unpooled.copiedBuffer(buf));
|
|
||||||
} else {
|
|
||||||
throw new IOException("write failed ! please checking !");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] read(int readSize) throws IOException {
|
public SocketAddress getRemoteSocketAddress();
|
||||||
do {
|
|
||||||
if (readSize > cache.readableBytes()) {
|
|
||||||
if (null == channel) {
|
|
||||||
throw new java.nio.channels.ClosedByInterruptException();
|
|
||||||
}
|
|
||||||
synchronized (this) {
|
|
||||||
try {
|
|
||||||
wait(100);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new java.nio.channels.ClosedByInterruptException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
byte[] back = new byte[readSize];
|
|
||||||
synchronized (lock) {
|
|
||||||
cache.readBytes(back);
|
|
||||||
}
|
|
||||||
return back;
|
|
||||||
}
|
|
||||||
} while (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isConnected() {
|
public void close();
|
||||||
return channel != null ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SocketAddress getRemoteSocketAddress() {
|
|
||||||
return channel != null ? channel.remoteAddress() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close() {
|
|
||||||
if (channel != null) {
|
|
||||||
channel.close();
|
|
||||||
}
|
|
||||||
channel = null;
|
|
||||||
cache.discardReadBytes();// 回收已占用的内存
|
|
||||||
cache.release();// 释放整个内存
|
|
||||||
cache = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,103 +1,35 @@
|
|||||||
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
package com.alibaba.otter.canal.parse.driver.mysql.socket;
|
||||||
|
|
||||||
import io.netty.bootstrap.Bootstrap;
|
|
||||||
import 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.ChannelFutureListener;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
|
||||||
import io.netty.channel.ChannelInitializer;
|
|
||||||
import io.netty.channel.ChannelOption;
|
|
||||||
import io.netty.channel.EventLoopGroup;
|
|
||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
|
||||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
|
||||||
import io.netty.util.ReferenceCountUtil;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import com.alibaba.otter.canal.common.utils.BooleanMutex;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author luoyaogui 实现channel的管理(监听连接、读数据、回收) 2016-12-28
|
* @author agapple 2018年3月12日 下午10:46:22
|
||||||
|
* @since 1.0.26
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "rawtypes", "deprecation" })
|
|
||||||
public abstract class SocketChannelPool {
|
public abstract class SocketChannelPool {
|
||||||
|
|
||||||
private static EventLoopGroup group = new NioEventLoopGroup(); // 非阻塞IO线程组
|
|
||||||
private static Bootstrap boot = new Bootstrap(); // 主
|
|
||||||
private static Map<Channel, SocketChannel> chManager = new ConcurrentHashMap<Channel, SocketChannel>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
boot.group(group)
|
|
||||||
.channel(NioSocketChannel.class)
|
|
||||||
.option(ChannelOption.SO_RCVBUF, 32 * 1024)
|
|
||||||
.option(ChannelOption.SO_SNDBUF, 32 * 1024)
|
|
||||||
.option(ChannelOption.TCP_NODELAY, true)
|
|
||||||
// 如果是延时敏感型应用,建议关闭Nagle算法
|
|
||||||
.option(ChannelOption.SO_KEEPALIVE, true)
|
|
||||||
.option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)
|
|
||||||
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
|
|
||||||
//
|
|
||||||
.handler(new ChannelInitializer() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initChannel(Channel arg0) throws Exception {
|
|
||||||
arg0.pipeline().addLast(new BusinessHandler());// 命令过滤和handler添加管理
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SocketChannel open(SocketAddress address) throws Exception {
|
public static SocketChannel open(SocketAddress address) throws Exception {
|
||||||
final SocketChannel socket = new SocketChannel();
|
String type = chooseSocketChannel();
|
||||||
final BooleanMutex mutex = new BooleanMutex(false);
|
if ("netty".equalsIgnoreCase(type)) {
|
||||||
boot.connect(address).addListener(new ChannelFutureListener() {
|
return NettySocketChannelPool.open(address);
|
||||||
|
} else {
|
||||||
@Override
|
return BioSocketChannelPool.open(address);
|
||||||
public void operationComplete(ChannelFuture arg0) throws Exception {
|
|
||||||
if (arg0.isSuccess()) {
|
|
||||||
socket.setChannel(arg0.channel());
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex.set(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// wait for complete
|
|
||||||
mutex.get();
|
|
||||||
if (null == socket.getChannel()) {
|
|
||||||
throw new IOException("can't create socket!");
|
|
||||||
}
|
}
|
||||||
chManager.put(socket.getChannel(), socket);
|
|
||||||
return socket;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class BusinessHandler extends ChannelInboundHandlerAdapter {
|
private static String chooseSocketChannel() {
|
||||||
|
String socketChannel = System.getenv("canal.socketChannel");
|
||||||
private SocketChannel socket = null;
|
if (StringUtils.isEmpty(socketChannel)) {
|
||||||
|
socketChannel = System.getProperty("canal.socketChannel");
|
||||||
@Override
|
|
||||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
socket.setChannel(null);
|
|
||||||
chManager.remove(ctx.channel());// 移除
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
if (StringUtils.isEmpty(socketChannel)) {
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
socketChannel = "bio"; // bio or netty
|
||||||
if (null == socket) socket = chManager.get(ctx.channel());
|
|
||||||
if (socket != null) {
|
|
||||||
socket.writeCache((ByteBuf) msg);
|
|
||||||
}
|
|
||||||
ReferenceCountUtil.release(msg);// 添加防止内存泄漏的
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
return socketChannel;
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
||||||
ctx.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,17 @@ public abstract class ByteHelper {
|
|||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void write8ByteUnsignedIntLittleEndian(long data, ByteArrayOutputStream out) {
|
||||||
|
out.write((byte) (data & 0xFF));
|
||||||
|
out.write((byte) (data >>> 8));
|
||||||
|
out.write((byte) (data >>> 16));
|
||||||
|
out.write((byte) (data >>> 24));
|
||||||
|
out.write((byte) (data >>> 32));
|
||||||
|
out.write((byte) (data >>> 40));
|
||||||
|
out.write((byte) (data >>> 48));
|
||||||
|
out.write((byte) (data >>> 56));
|
||||||
|
}
|
||||||
|
|
||||||
public static void writeUnsignedIntLittleEndian(long data, ByteArrayOutputStream out) {
|
public static void writeUnsignedIntLittleEndian(long data, ByteArrayOutputStream out) {
|
||||||
out.write((byte) (data & 0xFF));
|
out.write((byte) (data & 0xFF));
|
||||||
out.write((byte) (data >>> 8));
|
out.write((byte) (data >>> 8));
|
||||||
@@ -116,6 +127,17 @@ public abstract class ByteHelper {
|
|||||||
out.write((byte) (data >>> 24));
|
out.write((byte) (data >>> 24));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void writeUnsignedInt64LittleEndian(long data, ByteArrayOutputStream out) {
|
||||||
|
out.write((byte) (data & 0xFF));
|
||||||
|
out.write((byte) (data >>> 8));
|
||||||
|
out.write((byte) (data >>> 16));
|
||||||
|
out.write((byte) (data >>> 24));
|
||||||
|
out.write((byte) (data >>> 32));
|
||||||
|
out.write((byte) (data >>> 40));
|
||||||
|
out.write((byte) (data >>> 48));
|
||||||
|
out.write((byte) (data >>> 56));
|
||||||
|
}
|
||||||
|
|
||||||
public static void writeUnsignedShortLittleEndian(int data, ByteArrayOutputStream out) {
|
public static void writeUnsignedShortLittleEndian(int data, ByteArrayOutputStream out) {
|
||||||
out.write((byte) (data & 0xFF));
|
out.write((byte) (data & 0xFF));
|
||||||
out.write((byte) ((data >>> 8) & 0xFF));
|
out.write((byte) ((data >>> 8) & 0xFF));
|
||||||
|
|||||||
@@ -13,12 +13,22 @@ public abstract class PacketManager {
|
|||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static HeaderPacket readHeader(SocketChannel ch, int len, int timeout) throws IOException {
|
||||||
|
HeaderPacket header = new HeaderPacket();
|
||||||
|
header.fromBytes(ch.read(len, timeout));
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
public static byte[] readBytes(SocketChannel ch, int len) throws IOException {
|
public static byte[] readBytes(SocketChannel ch, int len) throws IOException {
|
||||||
return ch.read(len);
|
return ch.read(len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] readBytes(SocketChannel ch, int len, int timeout) throws IOException {
|
||||||
|
return ch.read(len, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
public static void writePkg(SocketChannel ch, byte[]... srcs) throws IOException {
|
public static void writePkg(SocketChannel ch, byte[]... srcs) throws IOException {
|
||||||
ch.writeChannel(srcs);
|
ch.write(srcs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void writeBody(SocketChannel ch, byte[] body) throws IOException {
|
public static void writeBody(SocketChannel ch, byte[] body) throws IOException {
|
||||||
@@ -29,6 +39,6 @@ public abstract class PacketManager {
|
|||||||
HeaderPacket header = new HeaderPacket();
|
HeaderPacket header = new HeaderPacket();
|
||||||
header.setPacketBodyLength(body.length);
|
header.setPacketBodyLength(body.length);
|
||||||
header.setPacketSequenceNumber(packetSeqNumber);
|
header.setPacketSequenceNumber(packetSeqNumber);
|
||||||
ch.writeChannel(header.toBytes(), body);
|
ch.write(header.toBytes(), body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.UUIDSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by hiwjd on 2018/4/25. hiwjd0@gmail.com
|
||||||
|
*/
|
||||||
|
public class MysqlGTIDSetTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncode() throws IOException {
|
||||||
|
GTIDSet gtidSet = MysqlGTIDSet.parse("726757ad-4455-11e8-ae04-0242ac110002:1");
|
||||||
|
byte[] bytes = gtidSet.encode();
|
||||||
|
|
||||||
|
byte[] expected = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x67, 0x57, (byte) 0xad, 0x44, 0x55,
|
||||||
|
0x11, (byte) 0xe8, (byte) 0xae, 0x04, 0x02, 0x42, (byte) 0xac, 0x11, 0x00, 0x02, 0x01, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00 };
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length; i++) {
|
||||||
|
assertEquals(expected[i], bytes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParse() {
|
||||||
|
Map<String, MysqlGTIDSet> cases = new HashMap<String, MysqlGTIDSet>(5);
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1",
|
||||||
|
buildForTest(new Material("726757ad-4455-11e8-ae04-0242ac110002", 1, 2)));
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3",
|
||||||
|
buildForTest(new Material("726757ad-4455-11e8-ae04-0242ac110002", 1, 4)));
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3:4",
|
||||||
|
buildForTest(new Material("726757ad-4455-11e8-ae04-0242ac110002", 1, 5)));
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3:7-9",
|
||||||
|
buildForTest(new Material("726757ad-4455-11e8-ae04-0242ac110002", 1, 4, 7, 10)));
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3,726757ad-4455-11e8-ae04-0242ac110003:4",
|
||||||
|
buildForTest(Arrays.asList(new Material("726757ad-4455-11e8-ae04-0242ac110002", 1, 4),
|
||||||
|
new Material("726757ad-4455-11e8-ae04-0242ac110003", 4, 5))));
|
||||||
|
|
||||||
|
for (Map.Entry<String, MysqlGTIDSet> entry : cases.entrySet()) {
|
||||||
|
MysqlGTIDSet expected = entry.getValue();
|
||||||
|
MysqlGTIDSet actual = MysqlGTIDSet.parse(entry.getKey());
|
||||||
|
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Material {
|
||||||
|
|
||||||
|
public Material(String uuid, long start, long stop){
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.start = start;
|
||||||
|
this.stop = stop;
|
||||||
|
this.start1 = 0;
|
||||||
|
this.stop1 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Material(String uuid, long start, long stop, long start1, long stop1){
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.start = start;
|
||||||
|
this.stop = stop;
|
||||||
|
this.start1 = start1;
|
||||||
|
this.stop1 = stop1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String uuid;
|
||||||
|
public long start;
|
||||||
|
public long stop;
|
||||||
|
public long start1;
|
||||||
|
public long stop1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MysqlGTIDSet buildForTest(Material material) {
|
||||||
|
return buildForTest(Arrays.asList(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
private MysqlGTIDSet buildForTest(List<Material> materials) {
|
||||||
|
Map<String, UUIDSet> sets = new HashMap<String, UUIDSet>();
|
||||||
|
for (Material a : materials) {
|
||||||
|
UUIDSet.Interval interval = new UUIDSet.Interval();
|
||||||
|
interval.start = a.start;
|
||||||
|
interval.stop = a.stop;
|
||||||
|
List<UUIDSet.Interval> intervals = new ArrayList<UUIDSet.Interval>();
|
||||||
|
intervals.add(interval);
|
||||||
|
|
||||||
|
if (a.start1 > 0 && a.stop1 > 0) {
|
||||||
|
UUIDSet.Interval interval1 = new UUIDSet.Interval();
|
||||||
|
interval1.start = a.start1;
|
||||||
|
interval1.stop = a.stop1;
|
||||||
|
intervals.add(interval1);
|
||||||
|
}
|
||||||
|
|
||||||
|
UUIDSet us = new UUIDSet();
|
||||||
|
us.SID = UUID.fromString(a.uuid);
|
||||||
|
us.intervals = intervals;
|
||||||
|
|
||||||
|
sets.put(a.uuid, us);
|
||||||
|
}
|
||||||
|
|
||||||
|
MysqlGTIDSet gs = new MysqlGTIDSet();
|
||||||
|
gs.sets = sets;
|
||||||
|
|
||||||
|
return gs;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.driver.mysql;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.UUIDSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by hiwjd on 2018/4/26. hiwjd0@gmail.com
|
||||||
|
*/
|
||||||
|
public class UUIDSetTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToString() {
|
||||||
|
Map<String, String> cases = new HashMap<String, String>(4);
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1", "726757ad-4455-11e8-ae04-0242ac110002:1");
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3", "726757ad-4455-11e8-ae04-0242ac110002:1-3");
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3:4-6", "726757ad-4455-11e8-ae04-0242ac110002:1-6");
|
||||||
|
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3:5-7", "726757ad-4455-11e8-ae04-0242ac110002:1-3:5-7");
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> entry : cases.entrySet()) {
|
||||||
|
String expected = entry.getValue();
|
||||||
|
assertEquals(expected, UUIDSet.parse(entry.getKey()).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import java.text.SimpleDateFormat;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang.SystemUtils;
|
import org.apache.commons.lang.SystemUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -16,6 +17,7 @@ import com.alibaba.otter.canal.protocol.CanalEntry.Column;
|
|||||||
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
|
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
|
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry.Pair;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
|
import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
|
import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
|
||||||
import com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin;
|
import com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin;
|
||||||
@@ -56,10 +58,12 @@ public class AbstractCanalClientTest {
|
|||||||
context_format += "****************************************************" + SEP;
|
context_format += "****************************************************" + SEP;
|
||||||
|
|
||||||
row_format = SEP
|
row_format = SEP
|
||||||
+ "----------------> binlog[{}:{}] , name[{},{}] , eventType : {} , executeTime : {} , delay : {}ms"
|
+ "----------------> binlog[{}:{}] , name[{},{}] , eventType : {} , executeTime : {}({}) , gtid : ({}) , delay : {} ms"
|
||||||
+ SEP;
|
+ SEP;
|
||||||
|
|
||||||
transaction_format = SEP + "================> binlog[{}:{}] , executeTime : {} , delay : {}ms" + SEP;
|
transaction_format = SEP
|
||||||
|
+ "================> binlog[{}:{}] , executeTime : {}({}) , gtid : ({}) , delay : {}ms"
|
||||||
|
+ SEP;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,14 +86,15 @@ public class AbstractCanalClientTest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
thread.setUncaughtExceptionHandler(handler);
|
thread.setUncaughtExceptionHandler(handler);
|
||||||
thread.start();
|
|
||||||
running = true;
|
running = true;
|
||||||
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void stop() {
|
protected void stop() {
|
||||||
if (!running) {
|
if (!running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
connector.stopRunning();
|
||||||
running = false;
|
running = false;
|
||||||
if (thread != null) {
|
if (thread != null) {
|
||||||
try {
|
try {
|
||||||
@@ -157,14 +162,20 @@ public class AbstractCanalClientTest {
|
|||||||
long time = entry.getHeader().getExecuteTime();
|
long time = entry.getHeader().getExecuteTime();
|
||||||
Date date = new Date(time);
|
Date date = new Date(time);
|
||||||
SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
|
SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
|
||||||
return entry.getHeader().getLogfileName() + ":" + entry.getHeader().getLogfileOffset() + ":"
|
String position = entry.getHeader().getLogfileName() + ":" + entry.getHeader().getLogfileOffset() + ":"
|
||||||
+ entry.getHeader().getExecuteTime() + "(" + format.format(date) + ")";
|
+ entry.getHeader().getExecuteTime() + "(" + format.format(date) + ")";
|
||||||
|
if (StringUtils.isNotEmpty(entry.getHeader().getGtid())) {
|
||||||
|
position += " gtid(" + entry.getHeader().getGtid() + ")";
|
||||||
|
}
|
||||||
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void printEntry(List<Entry> entrys) {
|
protected void printEntry(List<Entry> entrys) {
|
||||||
for (Entry entry : entrys) {
|
for (Entry entry : entrys) {
|
||||||
long executeTime = entry.getHeader().getExecuteTime();
|
long executeTime = entry.getHeader().getExecuteTime();
|
||||||
long delayTime = new Date().getTime() - executeTime;
|
long delayTime = new Date().getTime() - executeTime;
|
||||||
|
Date date = new Date(entry.getHeader().getExecuteTime());
|
||||||
|
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
|
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
|
||||||
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN) {
|
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN) {
|
||||||
@@ -178,8 +189,10 @@ public class AbstractCanalClientTest {
|
|||||||
logger.info(transaction_format,
|
logger.info(transaction_format,
|
||||||
new Object[] { entry.getHeader().getLogfileName(),
|
new Object[] { entry.getHeader().getLogfileName(),
|
||||||
String.valueOf(entry.getHeader().getLogfileOffset()),
|
String.valueOf(entry.getHeader().getLogfileOffset()),
|
||||||
String.valueOf(entry.getHeader().getExecuteTime()), String.valueOf(delayTime) });
|
String.valueOf(entry.getHeader().getExecuteTime()), simpleDateFormat.format(date),
|
||||||
|
entry.getHeader().getGtid(), String.valueOf(delayTime) });
|
||||||
logger.info(" BEGIN ----> Thread id: {}", begin.getThreadId());
|
logger.info(" BEGIN ----> Thread id: {}", begin.getThreadId());
|
||||||
|
printXAInfo(begin.getPropsList());
|
||||||
} else if (entry.getEntryType() == EntryType.TRANSACTIONEND) {
|
} else if (entry.getEntryType() == EntryType.TRANSACTIONEND) {
|
||||||
TransactionEnd end = null;
|
TransactionEnd end = null;
|
||||||
try {
|
try {
|
||||||
@@ -190,10 +203,12 @@ public class AbstractCanalClientTest {
|
|||||||
// 打印事务提交信息,事务id
|
// 打印事务提交信息,事务id
|
||||||
logger.info("----------------\n");
|
logger.info("----------------\n");
|
||||||
logger.info(" END ----> transaction id: {}", end.getTransactionId());
|
logger.info(" END ----> transaction id: {}", end.getTransactionId());
|
||||||
|
printXAInfo(end.getPropsList());
|
||||||
logger.info(transaction_format,
|
logger.info(transaction_format,
|
||||||
new Object[] { entry.getHeader().getLogfileName(),
|
new Object[] { entry.getHeader().getLogfileName(),
|
||||||
String.valueOf(entry.getHeader().getLogfileOffset()),
|
String.valueOf(entry.getHeader().getLogfileOffset()),
|
||||||
String.valueOf(entry.getHeader().getExecuteTime()), String.valueOf(delayTime) });
|
String.valueOf(entry.getHeader().getExecuteTime()), simpleDateFormat.format(date),
|
||||||
|
entry.getHeader().getGtid(), String.valueOf(delayTime) });
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -213,13 +228,15 @@ public class AbstractCanalClientTest {
|
|||||||
new Object[] { entry.getHeader().getLogfileName(),
|
new Object[] { entry.getHeader().getLogfileName(),
|
||||||
String.valueOf(entry.getHeader().getLogfileOffset()), entry.getHeader().getSchemaName(),
|
String.valueOf(entry.getHeader().getLogfileOffset()), entry.getHeader().getSchemaName(),
|
||||||
entry.getHeader().getTableName(), eventType,
|
entry.getHeader().getTableName(), eventType,
|
||||||
String.valueOf(entry.getHeader().getExecuteTime()), String.valueOf(delayTime) });
|
String.valueOf(entry.getHeader().getExecuteTime()), simpleDateFormat.format(date),
|
||||||
|
entry.getHeader().getGtid(), String.valueOf(delayTime) });
|
||||||
|
|
||||||
if (eventType == EventType.QUERY || rowChage.getIsDdl()) {
|
if (eventType == EventType.QUERY || rowChage.getIsDdl()) {
|
||||||
logger.info(" sql ----> " + rowChage.getSql() + SEP);
|
logger.info(" sql ----> " + rowChage.getSql() + SEP);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printXAInfo(rowChage.getPropsList());
|
||||||
for (RowData rowData : rowChage.getRowDatasList()) {
|
for (RowData rowData : rowChage.getRowDatasList()) {
|
||||||
if (eventType == EventType.DELETE) {
|
if (eventType == EventType.DELETE) {
|
||||||
printColumn(rowData.getBeforeColumnsList());
|
printColumn(rowData.getBeforeColumnsList());
|
||||||
@@ -246,6 +263,27 @@ public class AbstractCanalClientTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void printXAInfo(List<Pair> pairs) {
|
||||||
|
if (pairs == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String xaType = null;
|
||||||
|
String xaXid = null;
|
||||||
|
for (Pair pair : pairs) {
|
||||||
|
String key = pair.getKey();
|
||||||
|
if (StringUtils.endsWithIgnoreCase(key, "XA_TYPE")) {
|
||||||
|
xaType = pair.getValue();
|
||||||
|
} else if (StringUtils.endsWithIgnoreCase(key, "XA_XID")) {
|
||||||
|
xaXid = pair.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xaType != null && xaXid != null) {
|
||||||
|
logger.info(" ------> " + xaType + " " + xaXid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setConnector(CanalConnector connector) {
|
public void setConnector(CanalConnector connector) {
|
||||||
this.connector = connector;
|
this.connector = connector;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.alibaba.otter.canal.example;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.CanalConnector;
|
||||||
|
import com.alibaba.otter.canal.client.CanalConnectors;
|
||||||
|
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
public class SimpleCanalClientPermanceTest {
|
||||||
|
|
||||||
|
public static void main(String args[]) {
|
||||||
|
String destination = "example";
|
||||||
|
String ip = "127.0.0.1";
|
||||||
|
int batchSize = 1024;
|
||||||
|
int count = 0;
|
||||||
|
int sum = 0;
|
||||||
|
int perSum = 0;
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
long end = 0;
|
||||||
|
final ArrayBlockingQueue<Long> queue = new ArrayBlockingQueue<Long>(100);
|
||||||
|
try {
|
||||||
|
final CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(ip, 11111),
|
||||||
|
destination,
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
Thread ackThread = new Thread(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
long batchId = queue.take();
|
||||||
|
connector.ack(batchId);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ackThread.start();
|
||||||
|
|
||||||
|
((SimpleCanalConnector) connector).setLazyParseEntry(true);
|
||||||
|
connector.connect();
|
||||||
|
connector.subscribe();
|
||||||
|
while (true) {
|
||||||
|
Message message = connector.getWithoutAck(batchSize);
|
||||||
|
long batchId = message.getId();
|
||||||
|
int size = message.getRawEntries().size();
|
||||||
|
sum += size;
|
||||||
|
perSum += size;
|
||||||
|
count++;
|
||||||
|
queue.add(batchId);
|
||||||
|
if (count % 10 == 0) {
|
||||||
|
end = System.currentTimeMillis();
|
||||||
|
long tps = (perSum * 1000) / (end - start);
|
||||||
|
System.out.println(" total : " + sum + " , current : " + perSum + " , cost : " + (end - start)
|
||||||
|
+ " , tps : " + tps);
|
||||||
|
start = end;
|
||||||
|
perSum = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -263,7 +263,7 @@ public class CanalInstanceWithManager extends AbstractCanalInstance {
|
|||||||
mysqlEventParser.setMasterPosition(masterPosition);
|
mysqlEventParser.setMasterPosition(masterPosition);
|
||||||
|
|
||||||
if (parameters.getPositions().size() > 1) {
|
if (parameters.getPositions().size() > 1) {
|
||||||
EntryPosition standbyPosition = JsonUtils.unmarshalFromString(parameters.getPositions().get(0),
|
EntryPosition standbyPosition = JsonUtils.unmarshalFromString(parameters.getPositions().get(1),
|
||||||
EntryPosition.class);
|
EntryPosition.class);
|
||||||
mysqlEventParser.setStandbyPosition(standbyPosition);
|
mysqlEventParser.setStandbyPosition(standbyPosition);
|
||||||
}
|
}
|
||||||
|
|||||||
111
kafka-client/pom.xml
Normal file
111
kafka-client/pom.xml
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<artifactId>canal</artifactId>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.kafka.client</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>canal kafka client module for otter ${project.version}</name>
|
||||||
|
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.protocol</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.kafka</groupId>
|
||||||
|
<artifactId>kafka-clients</artifactId>
|
||||||
|
<version>0.9.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- junit -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>dev</id>
|
||||||
|
<activation>
|
||||||
|
<activeByDefault>true</activeByDefault>
|
||||||
|
<property>
|
||||||
|
<name>env</name>
|
||||||
|
<value>!javadoc</value>
|
||||||
|
</property>
|
||||||
|
</activation>
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
<id>javadoc</id>
|
||||||
|
<activation>
|
||||||
|
<property>
|
||||||
|
<name>env</name>
|
||||||
|
<value>javadoc</value>
|
||||||
|
</property>
|
||||||
|
</activation>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>2.9.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-javadocs</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<aggregate>true</aggregate>
|
||||||
|
<show>public</show>
|
||||||
|
<nohelp>true</nohelp>
|
||||||
|
<header>${project.artifactId}-${project.version}</header>
|
||||||
|
<footer>${project.artifactId}-${project.version}</footer>
|
||||||
|
<doctitle>${project.artifactId}-${project.version}</doctitle>
|
||||||
|
<links>
|
||||||
|
<link>https://github.com/alibaba/canal</link>
|
||||||
|
</links>
|
||||||
|
<outputDirectory>${project.build.directory}/apidocs/apidocs/${project.version}</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-scm-publish-plugin</artifactId>
|
||||||
|
<version>1.0-beta-2</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-javadocs</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>publish-scm</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<checkoutDirectory>${project.build.directory}/scmpublish</checkoutDirectory>
|
||||||
|
<checkinComment>Publishing javadoc for ${project.artifactId}:${project.version}</checkinComment>
|
||||||
|
<content>${project.build.directory}/apidocs</content>
|
||||||
|
<skipDeletedFiles>true</skipDeletedFiles>
|
||||||
|
<pubScmUrl>scm:git:git@github.com:alibaba/canal.git</pubScmUrl>
|
||||||
|
<scmBranch>gh-pages</scmBranch>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
package com.alibaba.otter.canal.kafka.client;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.common.utils.AddressUtils;
|
||||||
|
import com.alibaba.otter.canal.common.utils.BooleanMutex;
|
||||||
|
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
||||||
|
import com.alibaba.otter.canal.kafka.client.running.ClientRunningData;
|
||||||
|
import com.alibaba.otter.canal.kafka.client.running.ClientRunningListener;
|
||||||
|
import com.alibaba.otter.canal.kafka.client.running.ClientRunningMonitor;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||||
|
import org.apache.kafka.clients.consumer.ConsumerRecords;
|
||||||
|
import org.apache.kafka.clients.consumer.KafkaConsumer;
|
||||||
|
import org.apache.kafka.common.TopicPartition;
|
||||||
|
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* canal kafka 数据操作客户端
|
||||||
|
*
|
||||||
|
* @author machengyuan @ 2018-6-12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class KafkaCanalConnector {
|
||||||
|
|
||||||
|
private KafkaConsumer<String, Message> kafkaConsumer;
|
||||||
|
private String topic;
|
||||||
|
private Integer partition;
|
||||||
|
private Properties properties;
|
||||||
|
private ClientRunningMonitor runningMonitor; // 运行控制
|
||||||
|
private ZkClientx zkClientx;
|
||||||
|
private BooleanMutex mutex = new BooleanMutex(false);
|
||||||
|
private volatile boolean connected = false;
|
||||||
|
private volatile boolean running = false;
|
||||||
|
|
||||||
|
public KafkaCanalConnector(String zkServers, String servers, String topic, Integer partition, String groupId) {
|
||||||
|
this.topic = topic;
|
||||||
|
this.partition = partition;
|
||||||
|
|
||||||
|
properties = new Properties();
|
||||||
|
properties.put("bootstrap.servers", servers);
|
||||||
|
properties.put("group.id", groupId);
|
||||||
|
properties.put("enable.auto.commit", false);
|
||||||
|
properties.put("auto.commit.interval.ms", "1000");
|
||||||
|
properties.put("auto.offset.reset", "latest"); //如果没有offset则从最后的offset开始读
|
||||||
|
properties.put("request.timeout.ms", "40000"); // 必须大于session.timeout.ms的设置
|
||||||
|
properties.put("session.timeout.ms", "30000"); // 默认为30秒
|
||||||
|
properties.put("max.poll.records", "1"); // 所以一次只取一条数据
|
||||||
|
properties.put("key.deserializer", StringDeserializer.class.getName());
|
||||||
|
properties.put("value.deserializer", MessageDeserializer.class.getName());
|
||||||
|
|
||||||
|
if (zkServers != null) {
|
||||||
|
zkClientx = new ZkClientx(zkServers);
|
||||||
|
|
||||||
|
ClientRunningData clientData = new ClientRunningData();
|
||||||
|
clientData.setGroupId(groupId);
|
||||||
|
clientData.setAddress(AddressUtils.getHostIp());
|
||||||
|
|
||||||
|
runningMonitor = new ClientRunningMonitor();
|
||||||
|
runningMonitor.setTopic(topic);
|
||||||
|
runningMonitor.setZkClient(zkClientx);
|
||||||
|
runningMonitor.setClientData(clientData);
|
||||||
|
runningMonitor.setListener(new ClientRunningListener() {
|
||||||
|
public void processActiveEnter() {
|
||||||
|
mutex.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processActiveExit() {
|
||||||
|
mutex.set(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新设置sessionTime
|
||||||
|
*
|
||||||
|
* @param timeout
|
||||||
|
* @param unit
|
||||||
|
*/
|
||||||
|
public void setSessionTimeout(Long timeout, TimeUnit unit) {
|
||||||
|
long t = unit.toMillis(timeout);
|
||||||
|
properties.put("request.timeout.ms", String.valueOf(t + 60000));
|
||||||
|
properties.put("session.timeout.ms", String.valueOf(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开连接
|
||||||
|
*/
|
||||||
|
public void connect() {
|
||||||
|
if (connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningMonitor != null) {
|
||||||
|
if (!runningMonitor.isStart()) {
|
||||||
|
runningMonitor.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connected = true;
|
||||||
|
|
||||||
|
if (kafkaConsumer == null) {
|
||||||
|
kafkaConsumer = new KafkaConsumer<String, Message>(properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭链接
|
||||||
|
*/
|
||||||
|
public void disconnnect() {
|
||||||
|
kafkaConsumer.close();
|
||||||
|
|
||||||
|
connected = false;
|
||||||
|
if (runningMonitor.isStart()) {
|
||||||
|
runningMonitor.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitClientRunning() {
|
||||||
|
try {
|
||||||
|
if (zkClientx != null) {
|
||||||
|
if (!connected) {// 未调用connect
|
||||||
|
throw new CanalClientException("should connect first");
|
||||||
|
}
|
||||||
|
|
||||||
|
running = true;
|
||||||
|
mutex.get();// 阻塞等待
|
||||||
|
} else {
|
||||||
|
// 单机模式直接设置为running
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new CanalClientException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean checkValid() {
|
||||||
|
if (zkClientx != null) {
|
||||||
|
return mutex.state();
|
||||||
|
} else {
|
||||||
|
return true;// 默认都放过
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅topic
|
||||||
|
*/
|
||||||
|
public void subscribe() {
|
||||||
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partition == null) {
|
||||||
|
kafkaConsumer.subscribe(Collections.singletonList(topic));
|
||||||
|
} else {
|
||||||
|
TopicPartition topicPartition = new TopicPartition(topic, partition);
|
||||||
|
kafkaConsumer.assign(Collections.singletonList(topicPartition));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消订阅
|
||||||
|
*/
|
||||||
|
public void unsubscribe() {
|
||||||
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
kafkaConsumer.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据,自动进行确认
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Message get() {
|
||||||
|
return get(100L, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message get(Long timeout, TimeUnit unit) {
|
||||||
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Message message = getWithoutAck(timeout, unit);
|
||||||
|
this.ack();
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message getWithoutAck() {
|
||||||
|
return getWithoutAck(100L, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据,不进行确认,等待处理完成手工确认
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Message getWithoutAck(Long timeout, TimeUnit unit) {
|
||||||
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsumerRecords<String, Message> records = kafkaConsumer.poll(unit.toMillis(timeout)); // 基于配置,最多只能poll到一条数据
|
||||||
|
|
||||||
|
if (!records.isEmpty()) {
|
||||||
|
return records.iterator().next().value();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交offset,如果超过 session.timeout.ms 设置的时间没有ack则会抛出异常,ack失败
|
||||||
|
*/
|
||||||
|
public void ack() {
|
||||||
|
waitClientRunning();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
kafkaConsumer.commitSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopRunning() {
|
||||||
|
if (running) {
|
||||||
|
running = false; // 设置为非running状态
|
||||||
|
if (!mutex.state()) {
|
||||||
|
mutex.set(true); // 中断阻塞
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.alibaba.otter.canal.kafka.client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* canal kafka connectors创建工具类
|
||||||
|
*
|
||||||
|
* @author machengyuan @ 2018-6-12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class KafkaCanalConnectors {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建kafka客户端链接,独立运行不注册zk信息
|
||||||
|
*
|
||||||
|
* @param servers
|
||||||
|
* @param topic
|
||||||
|
* @param partition
|
||||||
|
* @param groupId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static KafkaCanalConnector newKafkaConnector(String servers, String topic, Integer partition, String groupId) {
|
||||||
|
return new KafkaCanalConnector(null, servers, topic, partition, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建kafka客户端链接,独立运行不注册zk信息
|
||||||
|
*
|
||||||
|
* @param servers
|
||||||
|
* @param topic
|
||||||
|
* @param groupId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static KafkaCanalConnector newKafkaConnector(String servers, String topic, String groupId) {
|
||||||
|
return new KafkaCanalConnector(null, servers, topic, null, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建kafka客户端链接
|
||||||
|
*
|
||||||
|
* @param zkServers
|
||||||
|
* @param servers
|
||||||
|
* @param topic
|
||||||
|
* @param partition
|
||||||
|
* @param groupId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static KafkaCanalConnector newKafkaConnector(String zkServers, String servers, String topic, Integer partition, String groupId) {
|
||||||
|
return new KafkaCanalConnector(zkServers, servers, topic, partition, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建kafka客户端链接
|
||||||
|
*
|
||||||
|
* @param zkServers
|
||||||
|
* @param servers
|
||||||
|
* @param topic
|
||||||
|
* @param groupId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static KafkaCanalConnector newKafkaConnector(String zkServers, String servers, String topic, String groupId) {
|
||||||
|
return new KafkaCanalConnector(zkServers, servers, topic, null, groupId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.alibaba.otter.canal.kafka.client;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.kafka.common.serialization.Deserializer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalPacket;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kafka Message类的反序列化
|
||||||
|
*
|
||||||
|
* @author machengyuan @ 2018-6-12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class MessageDeserializer implements Deserializer<Message> {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(MessageDeserializer.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(Map<String, ?> configs, boolean isKey) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message deserialize(String topic, byte[] data) {
|
||||||
|
try {
|
||||||
|
if (data == null) return null;
|
||||||
|
else {
|
||||||
|
CanalPacket.Packet p = CanalPacket.Packet.parseFrom(data);
|
||||||
|
switch (p.getType()) {
|
||||||
|
case MESSAGES: {
|
||||||
|
if (!p.getCompression().equals(CanalPacket.Compression.NONE)) {
|
||||||
|
throw new CanalClientException("compression is not supported in this connector");
|
||||||
|
}
|
||||||
|
|
||||||
|
CanalPacket.Messages messages = CanalPacket.Messages.parseFrom(p.getBody());
|
||||||
|
Message result = new Message(messages.getBatchId());
|
||||||
|
for (ByteString byteString : messages.getMessagesList()) {
|
||||||
|
result.addEntry(CanalEntry.Entry.parseFrom(byteString));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error when deserializing byte[] to message ");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.alibaba.otter.canal.kafka.client.running;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* client running状态信息
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-06-20 下午04:10:12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class ClientRunningData {
|
||||||
|
|
||||||
|
private String groupId;
|
||||||
|
private String address;
|
||||||
|
private boolean active = true;
|
||||||
|
|
||||||
|
public String getGroupId() {
|
||||||
|
return groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupId(String groupId) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddress(String address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isActive() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActive(boolean active) {
|
||||||
|
this.active = active;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.alibaba.otter.canal.kafka.client.running;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* client running状态信息
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-06-20 下午04:10:12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public interface ClientRunningListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发现在轮到自己做为active
|
||||||
|
*/
|
||||||
|
public void processActiveEnter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发一下当前active模式失败
|
||||||
|
*/
|
||||||
|
public void processActiveExit();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
package com.alibaba.otter.canal.kafka.client.running;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
||||||
|
import com.alibaba.otter.canal.common.utils.AddressUtils;
|
||||||
|
import com.alibaba.otter.canal.common.utils.BooleanMutex;
|
||||||
|
import com.alibaba.otter.canal.common.utils.JsonUtils;
|
||||||
|
import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
|
||||||
|
import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils;
|
||||||
|
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
|
||||||
|
import org.I0Itec.zkclient.IZkDataListener;
|
||||||
|
import org.I0Itec.zkclient.exception.ZkException;
|
||||||
|
import org.I0Itec.zkclient.exception.ZkInterruptedException;
|
||||||
|
import org.I0Itec.zkclient.exception.ZkNoNodeException;
|
||||||
|
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
|
||||||
|
import org.apache.zookeeper.CreateMode;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* kafka client running状态信息
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-06-20 下午04:10:12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class ClientRunningMonitor extends AbstractCanalLifeCycle {
|
||||||
|
|
||||||
|
private static final String TOPIC_ROOT_NODE = ZookeeperPathUtils.CANAL_ROOT_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + "topics";
|
||||||
|
|
||||||
|
private static final String TOPIC_NODE = TOPIC_ROOT_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + "{0}";
|
||||||
|
|
||||||
|
private static final String TOPIC_CLIENTID_NODE = TOPIC_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + "{1}";
|
||||||
|
|
||||||
|
private static final String TOPIC_CLIENTID_RUNNING_NODE = TOPIC_CLIENTID_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + ZookeeperPathUtils.RUNNING_NODE;
|
||||||
|
|
||||||
|
private static String getTopicClientRunning(String topic, String groupId) {
|
||||||
|
return MessageFormat.format(TOPIC_CLIENTID_RUNNING_NODE, topic, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getClientIdNodePath(String topic, String groupId) {
|
||||||
|
return MessageFormat.format(TOPIC_CLIENTID_NODE, topic, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ClientRunningMonitor.class);
|
||||||
|
private ZkClientx zkClient;
|
||||||
|
private String topic;
|
||||||
|
private ClientRunningData clientData;
|
||||||
|
private IZkDataListener dataListener;
|
||||||
|
private BooleanMutex mutex = new BooleanMutex(false);
|
||||||
|
private volatile boolean release = false;
|
||||||
|
private volatile ClientRunningData activeData;
|
||||||
|
private ScheduledExecutorService delayExector = Executors.newScheduledThreadPool(1);
|
||||||
|
private ClientRunningListener listener;
|
||||||
|
private int delayTime = 5;
|
||||||
|
|
||||||
|
private static Integer virtualPort;
|
||||||
|
|
||||||
|
public ClientRunningMonitor() {
|
||||||
|
if (virtualPort == null) {
|
||||||
|
Random rand = new Random();
|
||||||
|
virtualPort = rand.nextInt(9000) + 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataListener = new IZkDataListener() {
|
||||||
|
|
||||||
|
public void handleDataChange(String dataPath, Object data) throws Exception {
|
||||||
|
MDC.put("kafkaTopic", topic);
|
||||||
|
ClientRunningData runningData = JsonUtils.unmarshalFromByte((byte[]) data, ClientRunningData.class);
|
||||||
|
if (!isMine(runningData.getAddress())) {
|
||||||
|
mutex.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!runningData.isActive() && isMine(runningData.getAddress())) { // 说明出现了主动释放的操作,并且本机之前是active
|
||||||
|
release = true;
|
||||||
|
releaseRunning();// 彻底释放mainstem
|
||||||
|
}
|
||||||
|
|
||||||
|
activeData = (ClientRunningData) runningData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleDataDeleted(String dataPath) throws Exception {
|
||||||
|
MDC.put("kafkaTopic", topic);
|
||||||
|
|
||||||
|
mutex.set(false);
|
||||||
|
// 触发一下退出,可能是人为干预的释放操作或者网络闪断引起的session expired timeout
|
||||||
|
processActiveExit();
|
||||||
|
if (!release && activeData != null && isMine(activeData.getAddress())) {
|
||||||
|
// 如果上一次active的状态就是本机,则即时触发一下active抢占
|
||||||
|
initRunning();
|
||||||
|
} else {
|
||||||
|
// 否则就是等待delayTime,避免因网络瞬端或者zk异常,导致出现频繁的切换操作
|
||||||
|
delayExector.schedule(new Runnable() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
initRunning();
|
||||||
|
}
|
||||||
|
}, delayTime, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
super.start();
|
||||||
|
|
||||||
|
String path = getTopicClientRunning(this.topic, clientData.getGroupId());
|
||||||
|
|
||||||
|
zkClient.subscribeDataChanges(path, dataListener);
|
||||||
|
initRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
super.stop();
|
||||||
|
String path = getTopicClientRunning(this.topic, clientData.getGroupId());
|
||||||
|
zkClient.unsubscribeDataChanges(path, dataListener);
|
||||||
|
releaseRunning(); // 尝试一下release
|
||||||
|
//Fix issue #697
|
||||||
|
if (delayExector != null) {
|
||||||
|
delayExector.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 改动记录:
|
||||||
|
// 1,在方法上加synchronized关键字,保证同步顺序执行;
|
||||||
|
// 2,判断Zk上已经存在的activeData是否是本机,是的话把mutex重置为true,否则会导致死锁
|
||||||
|
// 3,增加异常处理,保证出现异常时,running节点能被删除,否则会导致死锁
|
||||||
|
public synchronized void initRunning() {
|
||||||
|
if (!isStart()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String path = getTopicClientRunning(this.topic, clientData.getGroupId());
|
||||||
|
// 序列化
|
||||||
|
byte[] bytes = JsonUtils.marshalToByte(clientData);
|
||||||
|
try {
|
||||||
|
mutex.set(false);
|
||||||
|
zkClient.create(path, bytes, CreateMode.EPHEMERAL);
|
||||||
|
processActiveEnter();// 触发一下事件
|
||||||
|
activeData = clientData;
|
||||||
|
mutex.set(true);
|
||||||
|
} catch (ZkNodeExistsException e) {
|
||||||
|
bytes = zkClient.readData(path, true);
|
||||||
|
if (bytes == null) {// 如果不存在节点,立即尝试一次
|
||||||
|
initRunning();
|
||||||
|
} else {
|
||||||
|
activeData = JsonUtils.unmarshalFromByte(bytes, ClientRunningData.class);
|
||||||
|
// 如果发现已经存在,判断一下是否自己,避免活锁
|
||||||
|
if (activeData.getAddress().contains(":") && isMine(activeData.getAddress())) {
|
||||||
|
mutex.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ZkNoNodeException e) {
|
||||||
|
zkClient.createPersistent(getClientIdNodePath(this.topic, clientData.getGroupId()),
|
||||||
|
true); // 尝试创建父节点
|
||||||
|
initRunning();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.error(MessageFormat.format("There is an error when execute initRunning method, with destination [{0}].",
|
||||||
|
topic),
|
||||||
|
t);
|
||||||
|
// 出现任何异常尝试release
|
||||||
|
releaseRunning();
|
||||||
|
throw new CanalClientException("something goes wrong in initRunning method. ", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阻塞等待自己成为active,如果自己成为active,立马返回
|
||||||
|
*
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public void waitForActive() throws InterruptedException {
|
||||||
|
initRunning();
|
||||||
|
mutex.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查当前的状态
|
||||||
|
*/
|
||||||
|
public boolean check() {
|
||||||
|
String path = getTopicClientRunning(this.topic, clientData.getGroupId());
|
||||||
|
//ZookeeperPathUtils.getDestinationClientRunning(this.destination, clientData.getClientId());
|
||||||
|
try {
|
||||||
|
byte[] bytes = zkClient.readData(path);
|
||||||
|
ClientRunningData eventData = JsonUtils.unmarshalFromByte(bytes, ClientRunningData.class);
|
||||||
|
activeData = eventData;// 更新下为最新值
|
||||||
|
// 检查下nid是否为自己
|
||||||
|
boolean result = isMine(activeData.getAddress());
|
||||||
|
if (!result) {
|
||||||
|
logger.warn("canal is running in [{}] , but not in [{}]",
|
||||||
|
activeData.getAddress(),
|
||||||
|
clientData.getAddress());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (ZkNoNodeException e) {
|
||||||
|
logger.warn("canal is not run any in node");
|
||||||
|
return false;
|
||||||
|
} catch (ZkInterruptedException e) {
|
||||||
|
logger.warn("canal check is interrupt");
|
||||||
|
Thread.interrupted();// 清除interrupt标记
|
||||||
|
return check();
|
||||||
|
} catch (ZkException e) {
|
||||||
|
logger.warn("canal check is failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean releaseRunning() {
|
||||||
|
if (check()) {
|
||||||
|
String path = getTopicClientRunning(this.topic, clientData.getGroupId());
|
||||||
|
zkClient.delete(path);
|
||||||
|
mutex.set(false);
|
||||||
|
processActiveExit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== helper method ======================
|
||||||
|
|
||||||
|
private boolean isMine(String address) {
|
||||||
|
return address.equals(clientData.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processActiveEnter() {
|
||||||
|
if (listener != null) {
|
||||||
|
// 触发回调
|
||||||
|
listener.processActiveEnter();
|
||||||
|
this.clientData.setAddress(/*address*/AddressUtils.getHostIp() + ":" + virtualPort);
|
||||||
|
|
||||||
|
String path = getTopicClientRunning(this.topic, clientData.getGroupId());
|
||||||
|
// 序列化
|
||||||
|
byte[] bytes = JsonUtils.marshalToByte(clientData);
|
||||||
|
zkClient.writeData(path, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processActiveExit() {
|
||||||
|
if (listener != null) {
|
||||||
|
listener.processActiveExit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setListener(ClientRunningListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== setter / getter =======================
|
||||||
|
|
||||||
|
public void setTopic(String topic) {
|
||||||
|
this.topic = topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientData(ClientRunningData clientData) {
|
||||||
|
this.clientData = clientData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDelayTime(int delayTime) {
|
||||||
|
this.delayTime = delayTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZkClient(ZkClientx zkClient) {
|
||||||
|
this.zkClient = zkClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.alibaba.otter.canal.kafka.client.running;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kafka 测试基类
|
||||||
|
*
|
||||||
|
* @author machengyuan @ 2018-6-12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractKafkaTest {
|
||||||
|
|
||||||
|
public static String topic = "example";
|
||||||
|
public static Integer partition = null;
|
||||||
|
public static String groupId = "g4";
|
||||||
|
public static String servers = "slave1:6667,slave2:6667,slave3:6667";
|
||||||
|
public static String zkServers = "slave1:2181,slave2:2181,slave3:2181";
|
||||||
|
|
||||||
|
public void sleep(long time) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(time);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Assert.fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package com.alibaba.otter.canal.kafka.client.running;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.kafka.common.errors.WakeupException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.kafka.client.KafkaCanalConnector;
|
||||||
|
import com.alibaba.otter.canal.kafka.client.KafkaCanalConnectors;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kafka client example
|
||||||
|
*
|
||||||
|
* @author machengyuan @ 2018-6-12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalKafkaClientExample {
|
||||||
|
|
||||||
|
protected final static Logger logger = LoggerFactory.getLogger(CanalKafkaClientExample.class);
|
||||||
|
|
||||||
|
private KafkaCanalConnector connector;
|
||||||
|
|
||||||
|
private static volatile boolean running = false;
|
||||||
|
|
||||||
|
private Thread thread = null;
|
||||||
|
|
||||||
|
private Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() {
|
||||||
|
|
||||||
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
|
logger.error("parse events has an error", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public CanalKafkaClientExample(String zkServers, String servers, String topic, Integer partition, String groupId) {
|
||||||
|
connector = KafkaCanalConnectors.newKafkaConnector(zkServers, servers, topic, partition, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
final CanalKafkaClientExample kafkaCanalClientExample = new CanalKafkaClientExample(AbstractKafkaTest.zkServers,
|
||||||
|
AbstractKafkaTest.servers,
|
||||||
|
AbstractKafkaTest.topic,
|
||||||
|
AbstractKafkaTest.partition,
|
||||||
|
AbstractKafkaTest.groupId);
|
||||||
|
logger.info("## start the kafka consumer: {}-{}", AbstractKafkaTest.topic, AbstractKafkaTest.groupId);
|
||||||
|
kafkaCanalClientExample.start();
|
||||||
|
logger.info("## the canal kafka consumer is running now ......");
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
logger.info("## stop the kafka consumer");
|
||||||
|
kafkaCanalClientExample.stop();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("##something goes wrong when stopping kafka consumer:", e);
|
||||||
|
} finally {
|
||||||
|
logger.info("## kafka consumer is down.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
while (running)
|
||||||
|
;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("## Something goes wrong when starting up the kafka consumer:", e);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
Assert.notNull(connector, "connector is null");
|
||||||
|
thread = new Thread(new Runnable() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.setUncaughtExceptionHandler(handler);
|
||||||
|
thread.start();
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
connector.stopRunning();
|
||||||
|
running = false;
|
||||||
|
if (thread != null) {
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process() {
|
||||||
|
while (!running)
|
||||||
|
;
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
connector.connect();
|
||||||
|
connector.subscribe();
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
Message message = connector.getWithoutAck(1L, TimeUnit.SECONDS); // 获取message
|
||||||
|
if (message == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
long batchId = message.getId();
|
||||||
|
int size = message.getEntries().size();
|
||||||
|
if (batchId == -1 || size == 0) {
|
||||||
|
// try {
|
||||||
|
// Thread.sleep(1000);
|
||||||
|
// } catch (InterruptedException e) {
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
// printSummary(message, batchId, size);
|
||||||
|
// printEntry(message.getEntries());
|
||||||
|
logger.info(message.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
connector.ack(); // 提交确认
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
connector.unsubscribe();
|
||||||
|
} catch (WakeupException e) {
|
||||||
|
// No-op. Continue process
|
||||||
|
}
|
||||||
|
connector.disconnnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.alibaba.otter.canal.kafka.client.running;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.kafka.common.errors.WakeupException;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.kafka.client.KafkaCanalConnector;
|
||||||
|
import com.alibaba.otter.canal.kafka.client.KafkaCanalConnectors;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kafka consumer获取Message的测试例子
|
||||||
|
*
|
||||||
|
* @author machengyuan @ 2018-6-12
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class KafkaClientRunningTest extends AbstractKafkaTest {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(KafkaClientRunningTest.class);
|
||||||
|
|
||||||
|
private boolean running = true;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKafkaConsumer() {
|
||||||
|
final ExecutorService executor = Executors.newFixedThreadPool(1);
|
||||||
|
|
||||||
|
final KafkaCanalConnector connector = KafkaCanalConnectors.newKafkaConnector(servers, topic, partition, groupId);
|
||||||
|
|
||||||
|
executor.submit(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
connector.connect();
|
||||||
|
connector.subscribe();
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
Message message = connector.getWithoutAck(3L, TimeUnit.SECONDS);
|
||||||
|
if (message != null) {
|
||||||
|
System.out.println(message);
|
||||||
|
}
|
||||||
|
connector.ack();
|
||||||
|
} catch (WakeupException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connector.unsubscribe();
|
||||||
|
connector.disconnnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(60000);
|
||||||
|
running = false;
|
||||||
|
executor.shutdown();
|
||||||
|
logger.info("shutdown completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
19
kafka-client/src/test/resources/logback.xml
Normal file
19
kafka-client/src/test/resources/logback.xml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<configuration scan="true" scanPeriod=" 5 seconds">
|
||||||
|
|
||||||
|
<jmxConfigurator />
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
|
||||||
|
</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="org.apache.kafka" additivity="false">
|
||||||
|
<level value="ERROR" />
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
140
kafka/pom.xml
Normal file
140
kafka/pom.xml
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<artifactId>canal</artifactId>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<version>1.0.26-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.kafka</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>canal kafka module for otter ${project.version}</name>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.deployer</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
<version>1.17</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.kafka</groupId>
|
||||||
|
<artifactId>kafka_2.11</artifactId>
|
||||||
|
<version>0.9.0.1</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-log4j12</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<!-- deploy模块的packaging通常是jar,如果项目中没有java 源代码或资源文件,加上这一段配置使项目能通过构建 -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<addMavenDescriptor>true</addMavenDescriptor>
|
||||||
|
</archive>
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/logback.xml</exclude>
|
||||||
|
<exclude>**/canal.properties</exclude>
|
||||||
|
<exclude>**/spring/**</exclude>
|
||||||
|
<exclude>**/example/**</exclude>
|
||||||
|
<exclude>**/kafka.yml</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<!-- 这是最新版本,推荐使用这个版本 -->
|
||||||
|
<version>2.2.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>assemble</id>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
<phase>package</phase>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<appendAssemblyId>false</appendAssemblyId>
|
||||||
|
<attach>false</attach>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>dev</id>
|
||||||
|
<activation>
|
||||||
|
<activeByDefault>true</activeByDefault>
|
||||||
|
<property>
|
||||||
|
<name>env</name>
|
||||||
|
<value>!release</value>
|
||||||
|
</property>
|
||||||
|
</activation>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<!-- maven assembly插件需要一个描述文件 来告诉插件包的结构以及打包所需的文件来自哪里 -->
|
||||||
|
<descriptors>
|
||||||
|
<descriptor>${basedir}/src/main/assembly/dev.xml</descriptor>
|
||||||
|
</descriptors>
|
||||||
|
<finalName>canal</finalName>
|
||||||
|
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
<id>release</id>
|
||||||
|
<activation>
|
||||||
|
<property>
|
||||||
|
<name>env</name>
|
||||||
|
<value>release</value>
|
||||||
|
</property>
|
||||||
|
</activation>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<!-- 发布模式使用的maven assembly插件描述文件 -->
|
||||||
|
<descriptors>
|
||||||
|
<descriptor>${basedir}/src/main/assembly/release.xml</descriptor>
|
||||||
|
</descriptors>
|
||||||
|
<!-- 如果一个应用的包含多个deploy模块,如果使用同样的包名, 如果把它们复制的一个目录中可能会失败,所以包名加了 artifactId以示区分 -->
|
||||||
|
<finalName>${project.artifactId}-${project.version}</finalName>
|
||||||
|
<!-- scm 要求 release 模式打出的包放到顶级目录下的target子目录中 -->
|
||||||
|
<outputDirectory>${project.parent.build.directory}</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
</project>
|
||||||
64
kafka/src/main/assembly/dev.xml
Normal file
64
kafka/src/main/assembly/dev.xml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
|
||||||
|
<id>dist</id>
|
||||||
|
<formats>
|
||||||
|
<format>dir</format>
|
||||||
|
</formats>
|
||||||
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
|
<fileSets>
|
||||||
|
<fileSet>
|
||||||
|
<directory>.</directory>
|
||||||
|
<outputDirectory>/</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>README*</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>./src/main/bin</directory>
|
||||||
|
<outputDirectory>bin</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
<fileMode>0755</fileMode>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>../deployer/src/main/conf</directory>
|
||||||
|
<outputDirectory>/conf</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>../deployer/src/main/resources</directory>
|
||||||
|
<outputDirectory>/conf</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
<excludes>
|
||||||
|
<exclude>logback.xml</exclude>
|
||||||
|
</excludes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>./src/main/resources</directory>
|
||||||
|
<outputDirectory>/conf</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>target</directory>
|
||||||
|
<outputDirectory>logs</outputDirectory>
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/*</exclude>
|
||||||
|
</excludes>
|
||||||
|
</fileSet>
|
||||||
|
</fileSets>
|
||||||
|
<dependencySets>
|
||||||
|
<dependencySet>
|
||||||
|
<outputDirectory>lib</outputDirectory>
|
||||||
|
<excludes>
|
||||||
|
<exclude>junit:junit</exclude>
|
||||||
|
</excludes>
|
||||||
|
</dependencySet>
|
||||||
|
</dependencySets>
|
||||||
|
</assembly>
|
||||||
64
kafka/src/main/assembly/release.xml
Normal file
64
kafka/src/main/assembly/release.xml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
|
||||||
|
<id>dist</id>
|
||||||
|
<formats>
|
||||||
|
<format>tar.gz</format>
|
||||||
|
</formats>
|
||||||
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
|
<fileSets>
|
||||||
|
<fileSet>
|
||||||
|
<directory>.</directory>
|
||||||
|
<outputDirectory>/</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>README*</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>./src/main/bin</directory>
|
||||||
|
<outputDirectory>bin</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
<fileMode>0755</fileMode>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>../deployer/src/main/conf</directory>
|
||||||
|
<outputDirectory>/conf</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>../deployer/src/main/resources</directory>
|
||||||
|
<outputDirectory>/conf</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
<excludes>
|
||||||
|
<exclude>logback.xml</exclude>
|
||||||
|
</excludes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>./src/main/resources</directory>
|
||||||
|
<outputDirectory>/conf</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>target</directory>
|
||||||
|
<outputDirectory>logs</outputDirectory>
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/*</exclude>
|
||||||
|
</excludes>
|
||||||
|
</fileSet>
|
||||||
|
</fileSets>
|
||||||
|
<dependencySets>
|
||||||
|
<dependencySet>
|
||||||
|
<outputDirectory>lib</outputDirectory>
|
||||||
|
<excludes>
|
||||||
|
<exclude>junit:junit</exclude>
|
||||||
|
</excludes>
|
||||||
|
</dependencySet>
|
||||||
|
</dependencySets>
|
||||||
|
</assembly>
|
||||||
25
kafka/src/main/bin/startup.bat
Executable file
25
kafka/src/main/bin/startup.bat
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
@echo off
|
||||||
|
@if not "%ECHO%" == "" echo %ECHO%
|
||||||
|
@if "%OS%" == "Windows_NT" setlocal
|
||||||
|
|
||||||
|
set ENV_PATH=.\
|
||||||
|
if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0%
|
||||||
|
|
||||||
|
set conf_dir=%ENV_PATH%\..\conf
|
||||||
|
set canal_conf=%conf_dir%\canal.properties
|
||||||
|
set logback_configurationFile=%conf_dir%\logback.xml
|
||||||
|
|
||||||
|
set CLASSPATH=%conf_dir%
|
||||||
|
set CLASSPATH=%conf_dir%\..\lib\*;%CLASSPATH%
|
||||||
|
|
||||||
|
set JAVA_MEM_OPTS= -Xms128m -Xmx512m -XX:PermSize=128m
|
||||||
|
set JAVA_OPTS_EXT= -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dapplication.codeset=UTF-8 -Dfile.encoding=UTF-8
|
||||||
|
set JAVA_DEBUG_OPT= -server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=9099,server=y,suspend=n
|
||||||
|
set CANAL_OPTS= -DappName=otter-canal -Dlogback.configurationFile="%logback_configurationFile%" -Dcanal.conf="%canal_conf%"
|
||||||
|
|
||||||
|
set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %JAVA_DEBUG_OPT% %CANAL_OPTS%
|
||||||
|
|
||||||
|
set CMD_STR= java %JAVA_OPTS% -classpath "%CLASSPATH%" java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.kafka.CanalLauncher
|
||||||
|
echo start cmd : %CMD_STR%
|
||||||
|
|
||||||
|
java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.kafka.CanalLauncher
|
||||||
104
kafka/src/main/bin/startup.sh
Normal file
104
kafka/src/main/bin/startup.sh
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
current_path=`pwd`
|
||||||
|
case "`uname`" in
|
||||||
|
Linux)
|
||||||
|
bin_abs_path=$(readlink -f $(dirname $0))
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
bin_abs_path=`cd $(dirname $0); pwd`
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
base=${bin_abs_path}/..
|
||||||
|
canal_conf=$base/conf/canal.properties
|
||||||
|
logback_configurationFile=$base/conf/logback.xml
|
||||||
|
export LANG=en_US.UTF-8
|
||||||
|
export BASE=$base
|
||||||
|
|
||||||
|
if [ -f $base/bin/canal.pid ] ; then
|
||||||
|
echo "found canal.pid , Please run stop.sh first ,then startup.sh" 2>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d $base/logs/canal ] ; then
|
||||||
|
mkdir -p $base/logs/canal
|
||||||
|
fi
|
||||||
|
|
||||||
|
## set java path
|
||||||
|
if [ -z "$JAVA" ] ; then
|
||||||
|
JAVA=$(which java)
|
||||||
|
fi
|
||||||
|
|
||||||
|
ALIBABA_JAVA="/usr/alibaba/java/bin/java"
|
||||||
|
TAOBAO_JAVA="/opt/taobao/java/bin/java"
|
||||||
|
if [ -z "$JAVA" ]; then
|
||||||
|
if [ -f $ALIBABA_JAVA ] ; then
|
||||||
|
JAVA=$ALIBABA_JAVA
|
||||||
|
elif [ -f $TAOBAO_JAVA ] ; then
|
||||||
|
JAVA=$TAOBAO_JAVA
|
||||||
|
else
|
||||||
|
echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH." 2>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$#"
|
||||||
|
in
|
||||||
|
0 )
|
||||||
|
;;
|
||||||
|
1 )
|
||||||
|
var=$*
|
||||||
|
if [ -f $var ] ; then
|
||||||
|
canal_conf=$var
|
||||||
|
else
|
||||||
|
echo "THE PARAMETER IS NOT CORRECT.PLEASE CHECK AGAIN."
|
||||||
|
exit
|
||||||
|
fi;;
|
||||||
|
2 )
|
||||||
|
var=$1
|
||||||
|
if [ -f $var ] ; then
|
||||||
|
canal_conf=$var
|
||||||
|
else
|
||||||
|
if [ "$1" = "debug" ]; then
|
||||||
|
DEBUG_PORT=$2
|
||||||
|
DEBUG_SUSPEND="n"
|
||||||
|
JAVA_DEBUG_OPT="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=$DEBUG_PORT,server=y,suspend=$DEBUG_SUSPEND"
|
||||||
|
fi
|
||||||
|
fi;;
|
||||||
|
* )
|
||||||
|
echo "THE PARAMETERS MUST BE TWO OR LESS.PLEASE CHECK AGAIN."
|
||||||
|
exit;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
str=`file -L $JAVA | grep 64-bit`
|
||||||
|
if [ -n "$str" ]; then
|
||||||
|
JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -Xss256k -XX:-UseAdaptiveSizePolicy -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError"
|
||||||
|
else
|
||||||
|
JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m "
|
||||||
|
fi
|
||||||
|
|
||||||
|
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"
|
||||||
|
CANAL_OPTS="-DappName=otter-canal -Dlogback.configurationFile=$logback_configurationFile -Dcanal.conf=$canal_conf"
|
||||||
|
|
||||||
|
if [ -e $canal_conf -a -e $logback_configurationFile ]
|
||||||
|
then
|
||||||
|
|
||||||
|
for i in $base/lib/*;
|
||||||
|
do CLASSPATH=$i:"$CLASSPATH";
|
||||||
|
done
|
||||||
|
CLASSPATH="$base/conf:$CLASSPATH";
|
||||||
|
|
||||||
|
echo "cd to $bin_abs_path for workaround relative path"
|
||||||
|
cd $bin_abs_path
|
||||||
|
|
||||||
|
echo LOG CONFIGURATION : $logback_configurationFile
|
||||||
|
echo canal conf : $canal_conf
|
||||||
|
echo CLASSPATH :$CLASSPATH
|
||||||
|
$JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.kafka.CanalLauncher 1>>$base/logs/canal/canal.log 2>&1 &
|
||||||
|
echo $! > $base/bin/canal.pid
|
||||||
|
|
||||||
|
echo "cd to $current_path for continue"
|
||||||
|
cd $current_path
|
||||||
|
else
|
||||||
|
echo "canal conf("$canal_conf") OR log configration file($logback_configurationFile) is not exist,please create then first!"
|
||||||
|
fi
|
||||||
65
kafka/src/main/bin/stop.sh
Normal file
65
kafka/src/main/bin/stop.sh
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cygwin=false;
|
||||||
|
linux=false;
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN*)
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Linux*)
|
||||||
|
linux=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
get_pid() {
|
||||||
|
STR=$1
|
||||||
|
PID=$2
|
||||||
|
if $cygwin; then
|
||||||
|
JAVA_CMD="$JAVA_HOME\bin\java"
|
||||||
|
JAVA_CMD=`cygpath --path --unix $JAVA_CMD`
|
||||||
|
JAVA_PID=`ps |grep $JAVA_CMD |awk '{print $1}'`
|
||||||
|
else
|
||||||
|
if $linux; then
|
||||||
|
if [ ! -z "$PID" ]; then
|
||||||
|
JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'`
|
||||||
|
else
|
||||||
|
JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep -v grep|awk '{print $2}'`
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ ! -z "$PID" ]; then
|
||||||
|
JAVA_PID=`ps aux |grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'`
|
||||||
|
else
|
||||||
|
JAVA_PID=`ps aux |grep "$STR"|grep -v grep|awk '{print $2}'`
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo $JAVA_PID;
|
||||||
|
}
|
||||||
|
|
||||||
|
base=`dirname $0`/..
|
||||||
|
pidfile=$base/bin/canal.pid
|
||||||
|
if [ ! -f "$pidfile" ];then
|
||||||
|
echo "canal is not running. exists"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
pid=`cat $pidfile`
|
||||||
|
if [ "$pid" == "" ] ; then
|
||||||
|
pid=`get_pid "appName=otter-canal"`
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "`hostname`: stopping canal $pid ... "
|
||||||
|
kill $pid
|
||||||
|
|
||||||
|
LOOPS=0
|
||||||
|
while (true);
|
||||||
|
do
|
||||||
|
gpid=`get_pid "appName=otter-canal" "$pid"`
|
||||||
|
if [ "$gpid" == "" ] ; then
|
||||||
|
echo "Oook! cost:$LOOPS"
|
||||||
|
`rm $pidfile`
|
||||||
|
break;
|
||||||
|
fi
|
||||||
|
let LOOPS=LOOPS+1
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.alibaba.otter.canal.kafka;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.kafka.producer.CanalKafkaStarter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* canal-kafka独立版本启动的入口类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-6-11 下午05:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalLauncher {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
CanalServerStarter.init();
|
||||||
|
CanalKafkaStarter.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.alibaba.otter.canal.kafka;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.deployer.CanalController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* canal server 启动类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-6-11 下午05:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalServerStarter {
|
||||||
|
|
||||||
|
private static final String CLASSPATH_URL_PREFIX = "classpath:";
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(CanalServerStarter.class);
|
||||||
|
private volatile static boolean running = false;
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
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)) {
|
||||||
|
conf = StringUtils.substringAfter(conf, CLASSPATH_URL_PREFIX);
|
||||||
|
properties.load(CanalLauncher.class.getClassLoader().getResourceAsStream(conf));
|
||||||
|
} else {
|
||||||
|
properties.load(new FileInputStream(conf));
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("## start the canal server.");
|
||||||
|
final CanalController controller = new CanalController(properties);
|
||||||
|
controller.start();
|
||||||
|
running = true;
|
||||||
|
logger.info("## the canal server is running now ......");
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
logger.info("## stop the canal server");
|
||||||
|
running = false;
|
||||||
|
controller.stop();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("##something goes wrong when stopping canal Server:", e);
|
||||||
|
} finally {
|
||||||
|
logger.info("## canal server is down.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("## Something goes wrong when starting up the canal Server:", e);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isRunning() {
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setGlobalUncaughtExceptionHandler() {
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
|
logger.error("UnCaughtException", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.alibaba.otter.canal.kafka.producer;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.apache.kafka.clients.producer.KafkaProducer;
|
||||||
|
import org.apache.kafka.clients.producer.Producer;
|
||||||
|
import org.apache.kafka.clients.producer.ProducerRecord;
|
||||||
|
import org.apache.kafka.common.serialization.StringSerializer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.kafka.producer.KafkaProperties.Topic;
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* kafka producer 主操作类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-6-11 下午05:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalKafkaProducer {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(CanalKafkaProducer.class);
|
||||||
|
|
||||||
|
private Producer<String, Message> producer;
|
||||||
|
|
||||||
|
public void init(KafkaProperties kafkaProperties) {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.put("bootstrap.servers", kafkaProperties.getServers());
|
||||||
|
properties.put("acks", "all");
|
||||||
|
properties.put("retries", kafkaProperties.getRetries());
|
||||||
|
properties.put("batch.size", kafkaProperties.getBatchSize());
|
||||||
|
properties.put("linger.ms", kafkaProperties.getLingerMs());
|
||||||
|
properties.put("buffer.memory", kafkaProperties.getBufferMemory());
|
||||||
|
properties.put("key.serializer", StringSerializer.class.getName());
|
||||||
|
properties.put("value.serializer", MessageSerializer.class.getName());
|
||||||
|
producer = new KafkaProducer<String, Message>(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
try {
|
||||||
|
logger.info("## stop the kafka producer");
|
||||||
|
producer.close();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("##something goes wrong when stopping kafka producer:", e);
|
||||||
|
} finally {
|
||||||
|
logger.info("## kafka producer is down.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(Topic topic, Message message) {
|
||||||
|
boolean valid = false;
|
||||||
|
if (message != null && !message.getEntries().isEmpty()) {
|
||||||
|
for (CanalEntry.Entry entry : message.getEntries()) {
|
||||||
|
if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN
|
||||||
|
&& entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) {
|
||||||
|
valid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProducerRecord<String, Message> record;
|
||||||
|
if (topic.getPartition() != null) {
|
||||||
|
record = new ProducerRecord<String, Message>(topic.getTopic(), topic.getPartition(), null, message);
|
||||||
|
} else {
|
||||||
|
record = new ProducerRecord<String, Message>(topic.getTopic(), message);
|
||||||
|
}
|
||||||
|
producer.send(record);
|
||||||
|
logger.debug("send message to kafka topic: {} \n {}", topic.getTopic(), message.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package com.alibaba.otter.canal.kafka.producer;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.kafka.CanalServerStarter;
|
||||||
|
import com.alibaba.otter.canal.kafka.producer.KafkaProperties.CanalDestination;
|
||||||
|
import com.alibaba.otter.canal.kafka.producer.KafkaProperties.Topic;
|
||||||
|
import com.alibaba.otter.canal.protocol.ClientIdentity;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* kafka 启动类
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-6-11 下午05:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class CanalKafkaStarter {
|
||||||
|
|
||||||
|
private static final String CLASSPATH_URL_PREFIX = "classpath:";
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(CanalKafkaStarter.class);
|
||||||
|
|
||||||
|
private volatile static boolean running = false;
|
||||||
|
|
||||||
|
private static ExecutorService executorService;
|
||||||
|
|
||||||
|
private static CanalKafkaProducer canalKafkaProducer;
|
||||||
|
|
||||||
|
private static KafkaProperties kafkaProperties;
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
try {
|
||||||
|
logger.info("## load kafka configurations");
|
||||||
|
String conf = System.getProperty("kafka.conf", "classpath:kafka.yml");
|
||||||
|
|
||||||
|
if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
|
||||||
|
conf = StringUtils.substringAfter(conf, CLASSPATH_URL_PREFIX);
|
||||||
|
kafkaProperties = new Yaml().loadAs(CanalKafkaStarter.class.getClassLoader().getResourceAsStream(conf),
|
||||||
|
KafkaProperties.class);
|
||||||
|
} else {
|
||||||
|
kafkaProperties = new Yaml().loadAs(new FileInputStream(conf), KafkaProperties.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 kafka producer
|
||||||
|
canalKafkaProducer = new CanalKafkaProducer();
|
||||||
|
canalKafkaProducer.init(kafkaProperties);
|
||||||
|
|
||||||
|
// 对应每个instance启动一个worker线程
|
||||||
|
List<CanalDestination> destinations = kafkaProperties.getCanalDestinations();
|
||||||
|
|
||||||
|
executorService = Executors.newFixedThreadPool(destinations.size());
|
||||||
|
|
||||||
|
logger.info("## start the kafka workers.");
|
||||||
|
for (final CanalDestination destination : destinations) {
|
||||||
|
executorService.execute(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
worker(destination);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
running = true;
|
||||||
|
logger.info("## the kafka workers is running now ......");
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
logger.info("## stop the kafka workers");
|
||||||
|
running = false;
|
||||||
|
executorService.shutdown();
|
||||||
|
canalKafkaProducer.stop();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("##something goes wrong when stopping kafka workers:", e);
|
||||||
|
} finally {
|
||||||
|
logger.info("## canal kafka is down.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("## Something goes wrong when starting up the canal kafka workers:", e);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void worker(CanalDestination destination) {
|
||||||
|
while (!running)
|
||||||
|
;
|
||||||
|
while (!CanalServerStarter.isRunning())
|
||||||
|
; // 等待server启动完成
|
||||||
|
logger.info("## start the canal consumer: {}.", destination.getCanalDestination());
|
||||||
|
CanalServerWithEmbedded server = CanalServerWithEmbedded.instance();
|
||||||
|
ClientIdentity clientIdentity = new ClientIdentity(destination.getCanalDestination(), (short) 1001, "");
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
if (!server.getCanalInstances().containsKey(clientIdentity.getDestination())) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(3000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
server.subscribe(clientIdentity);
|
||||||
|
logger.info("## the canal consumer {} is running now ......", destination.getCanalDestination());
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
Message message = server.getWithoutAck(clientIdentity, kafkaProperties.getCanalBatchSize()); // 获取指定数量的数据
|
||||||
|
long batchId = message.getId();
|
||||||
|
try {
|
||||||
|
int size = message.getEntries().size();
|
||||||
|
if (batchId != -1 && size != 0) {
|
||||||
|
if (!StringUtils.isEmpty(destination.getTopic())) {
|
||||||
|
Topic topic = new Topic();
|
||||||
|
topic.setTopic(destination.getTopic());
|
||||||
|
topic.setPartition(destination.getPartition());
|
||||||
|
destination.getTopics().add(topic);
|
||||||
|
}
|
||||||
|
for (Topic topic : destination.getTopics()) {
|
||||||
|
canalKafkaProducer.send(topic, message); // 发送message到所有topic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batchId != -1) {
|
||||||
|
server.ack(clientIdentity, batchId); // 提交确认
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
server.rollback(clientIdentity);
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("process error!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package com.alibaba.otter.canal.kafka.producer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* kafka 配置项
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-6-11 下午05:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class KafkaProperties {
|
||||||
|
|
||||||
|
private String servers = "localhost:6667";
|
||||||
|
private int retries = 0;
|
||||||
|
private int batchSize = 16384;
|
||||||
|
private int lingerMs = 1;
|
||||||
|
private long bufferMemory = 33554432L;
|
||||||
|
|
||||||
|
private int canalBatchSize = 5;
|
||||||
|
|
||||||
|
private List<CanalDestination> canalDestinations = new ArrayList<CanalDestination>();
|
||||||
|
|
||||||
|
public static class CanalDestination {
|
||||||
|
|
||||||
|
private String canalDestination;
|
||||||
|
private String topic;
|
||||||
|
private Integer partition;
|
||||||
|
private Set<Topic> topics = new HashSet<Topic>();
|
||||||
|
|
||||||
|
public String getCanalDestination() {
|
||||||
|
return canalDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanalDestination(String canalDestination) {
|
||||||
|
this.canalDestination = canalDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTopic() {
|
||||||
|
return topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTopic(String topic) {
|
||||||
|
this.topic = topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPartition() {
|
||||||
|
return partition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPartition(Integer partition) {
|
||||||
|
this.partition = partition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Topic> getTopics() {
|
||||||
|
return topics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTopics(Set<Topic> topics) {
|
||||||
|
this.topics = topics;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Topic {
|
||||||
|
|
||||||
|
private String topic;
|
||||||
|
private Integer partition;
|
||||||
|
|
||||||
|
public String getTopic() {
|
||||||
|
return topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTopic(String topic) {
|
||||||
|
this.topic = topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPartition() {
|
||||||
|
return partition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPartition(Integer partition) {
|
||||||
|
this.partition = partition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
Topic topic1 = (Topic) o;
|
||||||
|
|
||||||
|
if (topic != null ? !topic.equals(topic1.topic) : topic1.topic != null) return false;
|
||||||
|
return partition != null ? partition.equals(topic1.partition) : topic1.partition == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = topic != null ? topic.hashCode() : 0;
|
||||||
|
result = 31 * result + (partition != null ? partition.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServers() {
|
||||||
|
return servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServers(String servers) {
|
||||||
|
this.servers = servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRetries() {
|
||||||
|
return retries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRetries(int retries) {
|
||||||
|
this.retries = retries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBatchSize() {
|
||||||
|
return batchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBatchSize(int batchSize) {
|
||||||
|
this.batchSize = batchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLingerMs() {
|
||||||
|
return lingerMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLingerMs(int lingerMs) {
|
||||||
|
this.lingerMs = lingerMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getBufferMemory() {
|
||||||
|
return bufferMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBufferMemory(long bufferMemory) {
|
||||||
|
this.bufferMemory = bufferMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCanalBatchSize() {
|
||||||
|
return canalBatchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanalBatchSize(int canalBatchSize) {
|
||||||
|
this.canalBatchSize = canalBatchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CanalDestination> getCanalDestinations() {
|
||||||
|
return canalDestinations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanalDestinations(List<CanalDestination> canalDestinations) {
|
||||||
|
this.canalDestinations = canalDestinations;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.alibaba.otter.canal.kafka.producer;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.kafka.common.errors.SerializationException;
|
||||||
|
import org.apache.kafka.common.serialization.Serializer;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalPacket;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kafka Message类的序列化
|
||||||
|
*
|
||||||
|
* @author machengyuan 2018-6-11 下午05:30:49
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class MessageSerializer implements Serializer<Message> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(Map<String, ?> configs, boolean isKey) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] serialize(String topic, Message data) {
|
||||||
|
try {
|
||||||
|
if (data == null) return null;
|
||||||
|
else {
|
||||||
|
CanalPacket.Messages.Builder messageBuilder = CanalPacket.Messages.newBuilder();
|
||||||
|
if (data.getId() != -1 && !CollectionUtils.isEmpty(data.getEntries())) {
|
||||||
|
for (CanalEntry.Entry entry : data.getEntries()) {
|
||||||
|
messageBuilder.addMessages(entry.toByteString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CanalPacket.Packet.Builder packetBuilder = CanalPacket.Packet.newBuilder();
|
||||||
|
packetBuilder.setType(CanalPacket.PacketType.MESSAGES);
|
||||||
|
packetBuilder.setBody(messageBuilder.build().toByteString());
|
||||||
|
return packetBuilder.build().toByteArray();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SerializationException("Error when serializing message to byte[] ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
}
|
||||||
18
kafka/src/main/resources/kafka.yml
Normal file
18
kafka/src/main/resources/kafka.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
servers: slave1:6667,slave2:6667,slave3:6667
|
||||||
|
retries: 0
|
||||||
|
batchSize: 16384
|
||||||
|
lingerMs: 1
|
||||||
|
bufferMemory: 33554432
|
||||||
|
# canal的批次大小,单位 k
|
||||||
|
canalBatchSize: 50
|
||||||
|
|
||||||
|
canalDestinations:
|
||||||
|
- canalDestination: example
|
||||||
|
topic: example
|
||||||
|
partition:
|
||||||
|
# 一个destination可以对应多个topic
|
||||||
|
# topics:
|
||||||
|
# - topics: example
|
||||||
|
# partition:
|
||||||
|
|
||||||
|
|
||||||
85
kafka/src/main/resources/logback.xml
Normal file
85
kafka/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<configuration scan="true" scanPeriod=" 5 seconds">
|
||||||
|
<jmxConfigurator />
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
|
||||||
|
</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="CANAL-ROOT" class="ch.qos.logback.classic.sift.SiftingAppender">
|
||||||
|
<discriminator>
|
||||||
|
<Key>destination</Key>
|
||||||
|
<DefaultValue>canal</DefaultValue>
|
||||||
|
</discriminator>
|
||||||
|
<sift>
|
||||||
|
<appender name="FILE-${destination}" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<File>../logs/${destination}/${destination}.log</File>
|
||||||
|
<rollingPolicy
|
||||||
|
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<!-- rollover daily -->
|
||||||
|
<fileNamePattern>../logs/${destination}/%d{yyyy-MM-dd}/${destination}-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
|
||||||
|
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||||
|
<!-- or whenever the file size reaches 100MB -->
|
||||||
|
<maxFileSize>512MB</maxFileSize>
|
||||||
|
</timeBasedFileNamingAndTriggeringPolicy>
|
||||||
|
<maxHistory>60</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>
|
||||||
|
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
|
||||||
|
</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
</sift>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="CANAL-META" class="ch.qos.logback.classic.sift.SiftingAppender">
|
||||||
|
<discriminator>
|
||||||
|
<Key>destination</Key>
|
||||||
|
<DefaultValue>canal</DefaultValue>
|
||||||
|
</discriminator>
|
||||||
|
<sift>
|
||||||
|
<appender name="META-FILE-${destination}" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<File>../logs/${destination}/meta.log</File>
|
||||||
|
<rollingPolicy
|
||||||
|
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<!-- rollover daily -->
|
||||||
|
<fileNamePattern>../logs/${destination}/%d{yyyy-MM-dd}/meta-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
|
||||||
|
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||||
|
<!-- or whenever the file size reaches 100MB -->
|
||||||
|
<maxFileSize>32MB</maxFileSize>
|
||||||
|
</timeBasedFileNamingAndTriggeringPolicy>
|
||||||
|
<maxHistory>60</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>
|
||||||
|
%d{yyyy-MM-dd HH:mm:ss.SSS} - %msg%n
|
||||||
|
</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
</sift>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="com.alibaba.otter.canal.instance" additivity="false">
|
||||||
|
<level value="INFO" />
|
||||||
|
<appender-ref ref="CANAL-ROOT" />
|
||||||
|
</logger>
|
||||||
|
<logger name="com.alibaba.otter.canal.deployer" additivity="false">
|
||||||
|
<level value="INFO" />
|
||||||
|
<appender-ref ref="CANAL-ROOT" />
|
||||||
|
</logger>
|
||||||
|
<logger name="com.alibaba.otter.canal.meta.FileMixedMetaManager" additivity="false">
|
||||||
|
<level value="INFO" />
|
||||||
|
<appender-ref ref="CANAL-META" />
|
||||||
|
</logger>
|
||||||
|
<logger name="com.alibaba.otter.canal.kafka" additivity="false">
|
||||||
|
<level value="INFO" />
|
||||||
|
<appender-ref ref="CANAL-ROOT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<root level="WARN">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
<appender-ref ref="CANAL-ROOT" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
@@ -109,10 +109,11 @@ public class FileMixedMetaManager extends MemoryMetaManager implements CanalMeta
|
|||||||
// 定时将内存中的最新值刷到file中,多次变更只刷一次
|
// 定时将内存中的最新值刷到file中,多次变更只刷一次
|
||||||
if (logger.isInfoEnabled()) {
|
if (logger.isInfoEnabled()) {
|
||||||
LogPosition cursor = (LogPosition) getCursor(clientIdentity);
|
LogPosition cursor = (LogPosition) getCursor(clientIdentity);
|
||||||
logger.info("clientId:{} cursor:[{},{},{}] address[{}]",
|
logger.info("clientId:{} cursor:[{},{},{},{},{}] address[{}]", new Object[] {
|
||||||
new Object[] { clientIdentity.getClientId(), cursor.getPostion().getJournalName(),
|
clientIdentity.getClientId(), cursor.getPostion().getJournalName(),
|
||||||
cursor.getPostion().getPosition(), cursor.getPostion().getTimestamp(),
|
cursor.getPostion().getPosition(), cursor.getPostion().getTimestamp(),
|
||||||
cursor.getIdentity().getSourceAddress().toString() });
|
cursor.getPostion().getServerId(), cursor.getPostion().getGtid(),
|
||||||
|
cursor.getIdentity().getSourceAddress().toString() });
|
||||||
}
|
}
|
||||||
flushDataToFile(clientIdentity.getDestination());
|
flushDataToFile(clientIdentity.getDestination());
|
||||||
updateCursorTasks.remove(clientIdentity);
|
updateCursorTasks.remove(clientIdentity);
|
||||||
|
|||||||
@@ -83,7 +83,8 @@ public class MemoryMetaManager extends AbstractCanalLifeCycle implements CanalMe
|
|||||||
}
|
}
|
||||||
|
|
||||||
public synchronized List<ClientIdentity> listAllSubscribeInfo(String destination) throws CanalMetaManagerException {
|
public synchronized List<ClientIdentity> listAllSubscribeInfo(String destination) throws CanalMetaManagerException {
|
||||||
return destinations.get(destination);
|
// fixed issue #657, fixed ConcurrentModificationException
|
||||||
|
return Lists.newArrayList(destinations.get(destination));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Position getCursor(ClientIdentity clientIdentity) throws CanalMetaManagerException {
|
public Position getCursor(ClientIdentity clientIdentity) throws CanalMetaManagerException {
|
||||||
|
|||||||
@@ -49,6 +49,10 @@
|
|||||||
<groupId>com.alibaba</groupId>
|
<groupId>com.alibaba</groupId>
|
||||||
<artifactId>druid</artifactId>
|
<artifactId>druid</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.fastsql</groupId>
|
||||||
|
<artifactId>fastsql</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>mysql</groupId>
|
<groupId>mysql</groupId>
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
@@ -69,6 +73,10 @@
|
|||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-compress</artifactId>
|
<artifactId>commons-compress</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.lmax</groupId>
|
||||||
|
<artifactId>disruptor</artifactId>
|
||||||
|
</dependency>
|
||||||
<!-- test dependency -->
|
<!-- test dependency -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
|||||||
import com.alibaba.otter.canal.common.alarm.CanalAlarmHandler;
|
import com.alibaba.otter.canal.common.alarm.CanalAlarmHandler;
|
||||||
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
||||||
import com.alibaba.otter.canal.parse.CanalEventParser;
|
import com.alibaba.otter.canal.parse.CanalEventParser;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet;
|
||||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
|
import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
|
||||||
import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer.TransactionFlushCallback;
|
import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer.TransactionFlushCallback;
|
||||||
@@ -87,10 +88,19 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
protected TimerTask heartBeatTimerTask;
|
protected TimerTask heartBeatTimerTask;
|
||||||
protected Throwable exception = null;
|
protected Throwable exception = null;
|
||||||
|
|
||||||
|
protected boolean isGTIDMode = false; // 是否是GTID模式
|
||||||
|
protected boolean parallel = true; // 是否开启并行解析模式
|
||||||
|
protected Integer parallelThreadSize = Runtime.getRuntime()
|
||||||
|
.availableProcessors() * 60 / 100; // 60%的能力跑解析,剩余部分处理网络
|
||||||
|
protected int parallelBufferSize = 256; // 必须为2的幂
|
||||||
|
protected MultiStageCoprocessor multiStageCoprocessor;
|
||||||
|
|
||||||
protected abstract BinlogParser buildParser();
|
protected abstract BinlogParser buildParser();
|
||||||
|
|
||||||
protected abstract ErosaConnection buildErosaConnection();
|
protected abstract ErosaConnection buildErosaConnection();
|
||||||
|
|
||||||
|
protected abstract MultiStageCoprocessor buildMultiStageCoprocessor();
|
||||||
|
|
||||||
protected abstract EntryPosition findStartPosition(ErosaConnection connection) throws IOException;
|
protected abstract EntryPosition findStartPosition(ErosaConnection connection) throws IOException;
|
||||||
|
|
||||||
protected void preDump(ErosaConnection connection) {
|
protected void preDump(ErosaConnection connection) {
|
||||||
@@ -171,7 +181,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
throw new CanalParseException("can't find init table meta for " + destination
|
throw new CanalParseException("can't find init table meta for " + destination
|
||||||
+ " with position : " + startPosition);
|
+ " with position : " + startPosition);
|
||||||
}
|
}
|
||||||
logger.info("find start position : {}", startPosition.toString());
|
logger.warn("find start position : {}", startPosition.toString());
|
||||||
// 重新链接,因为在找position过程中可能有状态,需要断开后重建
|
// 重新链接,因为在找position过程中可能有状态,需要断开后重建
|
||||||
erosaConnection.reconnect();
|
erosaConnection.reconnect();
|
||||||
|
|
||||||
@@ -214,14 +224,39 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 4. 开始dump数据
|
// 4. 开始dump数据
|
||||||
if (StringUtils.isEmpty(startPosition.getJournalName()) && startPosition.getTimestamp() != null) {
|
if (parallel) {
|
||||||
erosaConnection.dump(startPosition.getTimestamp(), sinkHandler);
|
// build stage processor
|
||||||
} else {
|
multiStageCoprocessor = buildMultiStageCoprocessor();
|
||||||
erosaConnection.dump(startPosition.getJournalName(),
|
multiStageCoprocessor.start();
|
||||||
startPosition.getPosition(),
|
|
||||||
sinkHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (isGTIDMode()) {
|
||||||
|
// 判断所属instance是否启用GTID模式,是的话调用ErosaConnection中GTID对应方法dump数据
|
||||||
|
erosaConnection.dump(MysqlGTIDSet.parse(startPosition.getGtid()), multiStageCoprocessor);
|
||||||
|
} else {
|
||||||
|
if (StringUtils.isEmpty(startPosition.getJournalName())
|
||||||
|
&& startPosition.getTimestamp() != null) {
|
||||||
|
erosaConnection.dump(startPosition.getTimestamp(), multiStageCoprocessor);
|
||||||
|
} else {
|
||||||
|
erosaConnection.dump(startPosition.getJournalName(),
|
||||||
|
startPosition.getPosition(),
|
||||||
|
multiStageCoprocessor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isGTIDMode()) {
|
||||||
|
// 判断所属instance是否启用GTID模式,是的话调用ErosaConnection中GTID对应方法dump数据
|
||||||
|
erosaConnection.dump(MysqlGTIDSet.parse(startPosition.getGtid()), sinkHandler);
|
||||||
|
} else {
|
||||||
|
if (StringUtils.isEmpty(startPosition.getJournalName())
|
||||||
|
&& startPosition.getTimestamp() != null) {
|
||||||
|
erosaConnection.dump(startPosition.getTimestamp(), sinkHandler);
|
||||||
|
} else {
|
||||||
|
erosaConnection.dump(startPosition.getJournalName(),
|
||||||
|
startPosition.getPosition(),
|
||||||
|
sinkHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (TableIdNotFoundException e) {
|
} catch (TableIdNotFoundException e) {
|
||||||
exception = e;
|
exception = e;
|
||||||
// 特殊处理TableIdNotFound异常,出现这样的异常,一种可能就是起始的position是一个事务当中,导致tablemap
|
// 特殊处理TableIdNotFound异常,出现这样的异常,一种可能就是起始的position是一个事务当中,导致tablemap
|
||||||
@@ -267,6 +302,9 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
eventSink.interrupt();
|
eventSink.interrupt();
|
||||||
transactionBuffer.reset();// 重置一下缓冲队列,重新记录数据
|
transactionBuffer.reset();// 重置一下缓冲队列,重新记录数据
|
||||||
binlogParser.reset();// 重新置位
|
binlogParser.reset();// 重新置位
|
||||||
|
if (multiStageCoprocessor != null) {
|
||||||
|
multiStageCoprocessor.reset();
|
||||||
|
}
|
||||||
|
|
||||||
if (running) {
|
if (running) {
|
||||||
// sleep一段时间再进行重试
|
// sleep一段时间再进行重试
|
||||||
@@ -293,6 +331,11 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
stopHeartBeat(); // 先停止心跳
|
stopHeartBeat(); // 先停止心跳
|
||||||
parseThread.interrupt(); // 尝试中断
|
parseThread.interrupt(); // 尝试中断
|
||||||
eventSink.interrupt();
|
eventSink.interrupt();
|
||||||
|
|
||||||
|
if (multiStageCoprocessor != null && multiStageCoprocessor.isStart()) {
|
||||||
|
multiStageCoprocessor.stop();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
parseThread.join();// 等待其结束
|
parseThread.join();// 等待其结束
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
@@ -368,6 +411,9 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
position.setTimestamp(entry.getHeader().getExecuteTime());
|
position.setTimestamp(entry.getHeader().getExecuteTime());
|
||||||
// add serverId at 2016-06-28
|
// add serverId at 2016-06-28
|
||||||
position.setServerId(entry.getHeader().getServerId());
|
position.setServerId(entry.getHeader().getServerId());
|
||||||
|
// set gtid
|
||||||
|
position.setGtid(entry.getHeader().getGtid());
|
||||||
|
|
||||||
logPosition.setPostion(position);
|
logPosition.setPostion(position);
|
||||||
|
|
||||||
LogIdentity identity = new LogIdentity(runningInfo.getAddress(), -1L);
|
LogIdentity identity = new LogIdentity(runningInfo.getAddress(), -1L);
|
||||||
@@ -530,4 +576,38 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
|
|||||||
return exception;
|
return exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isGTIDMode() {
|
||||||
|
return isGTIDMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsGTIDMode(boolean isGTIDMode) {
|
||||||
|
this.isGTIDMode = isGTIDMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isParallel() {
|
||||||
|
return parallel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParallel(boolean parallel) {
|
||||||
|
this.parallel = parallel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getParallelThreadSize() {
|
||||||
|
return parallelThreadSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParallelThreadSize(Integer parallelThreadSize) {
|
||||||
|
if (parallelThreadSize != null) {
|
||||||
|
this.parallelThreadSize = parallelThreadSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getParallelBufferSize() {
|
||||||
|
return parallelBufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParallelBufferSize(int parallelBufferSize) {
|
||||||
|
this.parallelBufferSize = parallelBufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.alibaba.otter.canal.parse.inbound;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用的Erosa的链接接口, 用于一般化处理mysql/oracle的解析过程
|
* 通用的Erosa的链接接口, 用于一般化处理mysql/oracle的解析过程
|
||||||
*
|
*
|
||||||
@@ -24,5 +26,18 @@ public interface ErosaConnection {
|
|||||||
|
|
||||||
public void dump(long timestamp, SinkFunction func) throws IOException;
|
public void dump(long timestamp, SinkFunction func) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过GTID同步binlog
|
||||||
|
*/
|
||||||
|
public void dump(GTIDSet gtidSet, SinkFunction func) throws IOException;
|
||||||
|
|
||||||
|
// -------------
|
||||||
|
|
||||||
|
public void dump(String binlogfilename, Long binlogPosition, MultiStageCoprocessor coprocessor) throws IOException;
|
||||||
|
|
||||||
|
public void dump(long timestamp, MultiStageCoprocessor coprocessor) throws IOException;
|
||||||
|
|
||||||
|
public void dump(GTIDSet gtidSet, MultiStageCoprocessor coprocessor) throws IOException;
|
||||||
|
|
||||||
ErosaConnection fork();
|
ErosaConnection fork();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.inbound;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.common.CanalLifeCycle;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 针对解析器提供一个多阶段协同的处理
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 1. 网络接收 (单线程)
|
||||||
|
* 2. 事件基本解析 (单线程,事件类型、DDL解析构造TableMeta、维护位点信息)
|
||||||
|
* 3. 事件深度解析 (多线程, DML事件数据的完整解析)
|
||||||
|
* 4. 投递到store (单线程)
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author agapple 2018年7月3日 下午4:54:17
|
||||||
|
* @since 1.0.26
|
||||||
|
*/
|
||||||
|
public interface MultiStageCoprocessor extends CanalLifeCycle {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网络数据投递
|
||||||
|
*/
|
||||||
|
public boolean publish(LogBuffer buffer);
|
||||||
|
|
||||||
|
public boolean publish(LogBuffer buffer, String binlogFileName);
|
||||||
|
|
||||||
|
public void reset();
|
||||||
|
}
|
||||||
@@ -3,9 +3,10 @@ package com.alibaba.otter.canal.parse.inbound;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 描述数据meta对象,mysql binlog中对应的{@linkplain TableMapLogEvent}包含的信息不全
|
* 描述数据meta对象,mysql binlog中对应的{@linkplain TableMapLogEvent}包含的信息不全
|
||||||
*
|
*
|
||||||
@@ -127,6 +128,7 @@ public class TableMeta {
|
|||||||
private boolean key;
|
private boolean key;
|
||||||
private String defaultValue;
|
private String defaultValue;
|
||||||
private String extra;
|
private String extra;
|
||||||
|
private boolean unique;
|
||||||
|
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
return columnName;
|
return columnName;
|
||||||
@@ -180,10 +182,21 @@ public class TableMeta {
|
|||||||
this.extra = extra;
|
this.extra = extra;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public boolean isUnique() {
|
||||||
return "FieldMeta [columnName=" + columnName + ", columnType=" + columnType + ", defaultValue="
|
return unique;
|
||||||
+ defaultValue + ", nullable=" + nullable + ", key=" + key + "]";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUnique(boolean unique) {
|
||||||
|
this.unique = unique;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "FieldMeta [columnName=" + columnName + ", columnType=" + columnType + ", nullable=" + nullable
|
||||||
|
+ ", key=" + key + ", defaultValue=" + defaultValue + ", extra=" + extra + ", unique=" + unique
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
import com.alibaba.otter.canal.filter.CanalEventFilter;
|
||||||
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet;
|
||||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
|
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
|
||||||
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
import com.alibaba.otter.canal.parse.inbound.BinlogParser;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.MultiStageCoprocessor;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDB;
|
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDB;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDBBuilder;
|
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDBBuilder;
|
||||||
@@ -68,7 +70,18 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
protected boolean processTableMeta(EntryPosition position) {
|
protected boolean processTableMeta(EntryPosition position) {
|
||||||
|
if (isGTIDMode()) {
|
||||||
|
if (binlogParser instanceof LogEventConvert) {
|
||||||
|
// 记录gtid
|
||||||
|
((LogEventConvert) binlogParser).setGtidSet(MysqlGTIDSet.parse(position.getGtid()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (tableMetaTSDB != null) {
|
if (tableMetaTSDB != null) {
|
||||||
|
if (position.getTimestamp() == null || position.getTimestamp() <= 0) {
|
||||||
|
throw new CanalParseException("use gtid and TableMeta TSDB should be config timestamp > 0");
|
||||||
|
}
|
||||||
|
|
||||||
return tableMetaTSDB.rollback(position);
|
return tableMetaTSDB.rollback(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +118,14 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected MultiStageCoprocessor buildMultiStageCoprocessor() {
|
||||||
|
return new MysqlMultiStageCoprocessor(parallelBufferSize,
|
||||||
|
parallelThreadSize,
|
||||||
|
(LogEventConvert) binlogParser,
|
||||||
|
transactionBuffer,
|
||||||
|
destination);
|
||||||
|
}
|
||||||
|
|
||||||
// ============================ setter / getter =========================
|
// ============================ setter / getter =========================
|
||||||
|
|
||||||
public void setConnectionCharsetNumber(byte connectionCharsetNumber) {
|
public void setConnectionCharsetNumber(byte connectionCharsetNumber) {
|
||||||
@@ -159,10 +180,11 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
|
|||||||
|
|
||||||
public void setTsdbSpringXml(String tsdbSpringXml) {
|
public void setTsdbSpringXml(String tsdbSpringXml) {
|
||||||
this.tsdbSpringXml = tsdbSpringXml;
|
this.tsdbSpringXml = tsdbSpringXml;
|
||||||
|
if (this.enableTsdb) {
|
||||||
if (tableMetaTSDB == null) {
|
if (tableMetaTSDB == null) {
|
||||||
// 初始化
|
// 初始化
|
||||||
tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml);
|
tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,18 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.NotImplementedException;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.MultiStageCoprocessor;
|
||||||
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.local.BinLogFileQueue;
|
import com.alibaba.otter.canal.parse.inbound.mysql.local.BinLogFileQueue;
|
||||||
import com.taobao.tddl.dbsync.binlog.FileLogFetcher;
|
import com.taobao.tddl.dbsync.binlog.FileLogFetcher;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogContext;
|
import com.taobao.tddl.dbsync.binlog.LogContext;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
@@ -91,7 +94,7 @@ public class LocalBinLogConnection implements ErosaConnection {
|
|||||||
while (fetcher.fetch()) {
|
while (fetcher.fetch()) {
|
||||||
event = decoder.decode(fetcher, context);
|
event = decoder.decode(fetcher, context);
|
||||||
if (event == null) {
|
if (event == null) {
|
||||||
throw new CanalParseException("parse failed");
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!func.sink(event)) {
|
if (!func.sink(event)) {
|
||||||
@@ -201,6 +204,137 @@ public class LocalBinLogConnection implements ErosaConnection {
|
|||||||
dump(binlogFilename, binlogFileOffset, func);
|
dump(binlogFilename, binlogFileOffset, func);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(GTIDSet gtidSet, SinkFunction func) throws IOException {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(String binlogfilename, Long binlogPosition, MultiStageCoprocessor coprocessor) throws IOException {
|
||||||
|
File current = new File(directory, binlogfilename);
|
||||||
|
|
||||||
|
FileLogFetcher fetcher = new FileLogFetcher(bufferSize);
|
||||||
|
try {
|
||||||
|
fetcher.open(current, binlogPosition);
|
||||||
|
while (running) {
|
||||||
|
boolean needContinue = true;
|
||||||
|
while (fetcher.fetch()) {
|
||||||
|
LogBuffer buffer = fetcher.duplicate();
|
||||||
|
fetcher.consume(fetcher.limit());
|
||||||
|
// set filename
|
||||||
|
if (!coprocessor.publish(buffer, binlogfilename)) {
|
||||||
|
needContinue = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needContinue) {// 读取下一个
|
||||||
|
fetcher.close(); // 关闭上一个文件
|
||||||
|
|
||||||
|
File nextFile;
|
||||||
|
if (needWait) {
|
||||||
|
nextFile = binlogs.waitForNextFile(current);
|
||||||
|
} else {
|
||||||
|
nextFile = binlogs.getNextFile(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextFile == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = nextFile;
|
||||||
|
fetcher.open(current);
|
||||||
|
binlogfilename = nextFile.getName();
|
||||||
|
} else {
|
||||||
|
break;// 跳出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.warn("LocalBinLogConnection dump interrupted");
|
||||||
|
} finally {
|
||||||
|
if (fetcher != null) {
|
||||||
|
fetcher.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(long timestampMills, MultiStageCoprocessor coprocessor) throws IOException {
|
||||||
|
List<File> currentBinlogs = binlogs.currentBinlogs();
|
||||||
|
File current = currentBinlogs.get(currentBinlogs.size() - 1);
|
||||||
|
long timestampSeconds = timestampMills / 1000;
|
||||||
|
|
||||||
|
String binlogFilename = null;
|
||||||
|
long binlogFileOffset = 0;
|
||||||
|
|
||||||
|
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();
|
||||||
|
try {
|
||||||
|
fetcher.open(current);
|
||||||
|
context.setLogPosition(new LogPosition(current.getName()));
|
||||||
|
while (running) {
|
||||||
|
boolean needContinue = true;
|
||||||
|
String lastXidLogFilename = current.getName();
|
||||||
|
long lastXidLogFileOffset = 0;
|
||||||
|
|
||||||
|
binlogFilename = lastXidLogFilename;
|
||||||
|
binlogFileOffset = lastXidLogFileOffset;
|
||||||
|
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()) {
|
||||||
|
lastXidLogFilename = current.getName();
|
||||||
|
lastXidLogFileOffset = event.getLogPos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needContinue) {// 读取下一个
|
||||||
|
fetcher.close(); // 关闭上一个文件
|
||||||
|
|
||||||
|
File nextFile = binlogs.getBefore(current);
|
||||||
|
if (nextFile == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = nextFile;
|
||||||
|
fetcher.open(current);
|
||||||
|
context.setLogPosition(new LogPosition(current.getName()));
|
||||||
|
} else {
|
||||||
|
break;// 跳出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (fetcher != null) {
|
||||||
|
fetcher.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dump(binlogFilename, binlogFileOffset, coprocessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(GTIDSet gtidSet, MultiStageCoprocessor coprocessor) throws IOException {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public ErosaConnection fork() {
|
public ErosaConnection fork() {
|
||||||
LocalBinLogConnection connection = new LocalBinLogConnection();
|
LocalBinLogConnection connection = new LocalBinLogConnection();
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package com.alibaba.otter.canal.parse.inbound.mysql;
|
package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||||
|
|
||||||
|
import static com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher.MASTER_HEARTBEAT_PERIOD_SECONDS;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -12,15 +15,22 @@ import org.slf4j.LoggerFactory;
|
|||||||
import com.alibaba.otter.canal.parse.driver.mysql.MysqlConnector;
|
import com.alibaba.otter.canal.parse.driver.mysql.MysqlConnector;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.MysqlQueryExecutor;
|
import com.alibaba.otter.canal.parse.driver.mysql.MysqlQueryExecutor;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.MysqlUpdateExecutor;
|
import com.alibaba.otter.canal.parse.driver.mysql.MysqlUpdateExecutor;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.BinlogDumpCommandPacket;
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.BinlogDumpCommandPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.BinlogDumpGTIDCommandPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.RegisterSlaveCommandPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.SemiAckCommandPacket;
|
||||||
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ErrorPacket;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
|
||||||
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
|
||||||
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.MultiStageCoprocessor;
|
||||||
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
import com.alibaba.otter.canal.parse.inbound.SinkFunction;
|
||||||
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher;
|
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher;
|
||||||
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogContext;
|
import com.taobao.tddl.dbsync.binlog.LogContext;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
import com.taobao.tddl.dbsync.binlog.LogDecoder;
|
||||||
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
import com.taobao.tddl.dbsync.binlog.LogEvent;
|
||||||
@@ -129,6 +139,7 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
|
|
||||||
public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException {
|
||||||
updateSettings();
|
updateSettings();
|
||||||
|
sendRegisterSlave();
|
||||||
sendBinlogDump(binlogfilename, binlogPosition);
|
sendBinlogDump(binlogfilename, binlogPosition);
|
||||||
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
||||||
fetcher.start(connector.getChannel());
|
fetcher.start(connector.getChannel());
|
||||||
@@ -145,6 +156,37 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
if (!func.sink(event)) {
|
if (!func.sink(event)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.getSemival() == 1) {
|
||||||
|
sendSemiAck(context.getLogPosition().getFileName(), context.getLogPosition().getPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(GTIDSet gtidSet, SinkFunction func) throws IOException {
|
||||||
|
updateSettings();
|
||||||
|
sendBinlogDumpGTID(gtidSet);
|
||||||
|
|
||||||
|
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
||||||
|
try {
|
||||||
|
fetcher.start(connector.getChannel());
|
||||||
|
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
||||||
|
LogContext context = new LogContext();
|
||||||
|
while (fetcher.fetch()) {
|
||||||
|
LogEvent event = null;
|
||||||
|
event = decoder.decode(fetcher, context);
|
||||||
|
|
||||||
|
if (event == null) {
|
||||||
|
throw new CanalParseException("parse failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!func.sink(event)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fetcher.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,11 +194,87 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
throw new NullPointerException("Not implement yet");
|
throw new NullPointerException("Not implement yet");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(String binlogfilename, Long binlogPosition, MultiStageCoprocessor coprocessor) throws IOException {
|
||||||
|
updateSettings();
|
||||||
|
sendRegisterSlave();
|
||||||
|
sendBinlogDump(binlogfilename, binlogPosition);
|
||||||
|
((MysqlMultiStageCoprocessor) coprocessor).setConnection(this);
|
||||||
|
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
||||||
|
try {
|
||||||
|
fetcher.start(connector.getChannel());
|
||||||
|
while (fetcher.fetch()) {
|
||||||
|
LogBuffer buffer = fetcher.duplicate();
|
||||||
|
fetcher.consume(fetcher.limit());
|
||||||
|
if (!coprocessor.publish(buffer)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fetcher.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(long timestamp, MultiStageCoprocessor coprocessor) throws IOException {
|
||||||
|
throw new NullPointerException("Not implement yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(GTIDSet gtidSet, MultiStageCoprocessor coprocessor) throws IOException {
|
||||||
|
updateSettings();
|
||||||
|
sendBinlogDumpGTID(gtidSet);
|
||||||
|
|
||||||
|
((MysqlMultiStageCoprocessor) coprocessor).setConnection(this);
|
||||||
|
DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize());
|
||||||
|
try {
|
||||||
|
fetcher.start(connector.getChannel());
|
||||||
|
while (fetcher.fetch()) {
|
||||||
|
LogBuffer buffer = fetcher.duplicate();
|
||||||
|
fetcher.consume(fetcher.limit());
|
||||||
|
if (!coprocessor.publish(buffer)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fetcher.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendRegisterSlave() throws IOException {
|
||||||
|
RegisterSlaveCommandPacket cmd = new RegisterSlaveCommandPacket();
|
||||||
|
cmd.reportHost = authInfo.getAddress().getAddress().getHostAddress();
|
||||||
|
cmd.reportPasswd = authInfo.getPassword();
|
||||||
|
cmd.reportUser = authInfo.getUsername();
|
||||||
|
cmd.reportPort = authInfo.getAddress().getPort(); // 暂时先用master节点的port
|
||||||
|
cmd.serverId = this.slaveId;
|
||||||
|
byte[] cmdBody = cmd.toBytes();
|
||||||
|
|
||||||
|
logger.info("Register slave {}", cmd);
|
||||||
|
|
||||||
|
HeaderPacket header = new HeaderPacket();
|
||||||
|
header.setPacketBodyLength(cmdBody.length);
|
||||||
|
header.setPacketSequenceNumber((byte) 0x00);
|
||||||
|
PacketManager.writePkg(connector.getChannel(), header.toBytes(), cmdBody);
|
||||||
|
|
||||||
|
header = PacketManager.readHeader(connector.getChannel(), 4);
|
||||||
|
byte[] body = PacketManager.readBytes(connector.getChannel(), header.getPacketBodyLength());
|
||||||
|
assert body != null;
|
||||||
|
if (body[0] < 0) {
|
||||||
|
if (body[0] == -1) {
|
||||||
|
ErrorPacket err = new ErrorPacket();
|
||||||
|
err.fromBytes(body);
|
||||||
|
throw new IOException("Error When doing Register slave:" + err.toString());
|
||||||
|
} else {
|
||||||
|
throw new IOException("unpexpected packet with field_count=" + body[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void sendBinlogDump(String binlogfilename, Long binlogPosition) throws IOException {
|
private void sendBinlogDump(String binlogfilename, Long binlogPosition) throws IOException {
|
||||||
BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket();
|
BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket();
|
||||||
binlogDumpCmd.binlogFileName = binlogfilename;
|
binlogDumpCmd.binlogFileName = binlogfilename;
|
||||||
binlogDumpCmd.binlogPosition = binlogPosition;
|
binlogDumpCmd.binlogPosition = binlogPosition;
|
||||||
// binlogDumpCmd.slaveServerId = this.slaveId;
|
|
||||||
binlogDumpCmd.slaveServerId = this.slaveId;
|
binlogDumpCmd.slaveServerId = this.slaveId;
|
||||||
byte[] cmdBody = binlogDumpCmd.toBytes();
|
byte[] cmdBody = binlogDumpCmd.toBytes();
|
||||||
|
|
||||||
@@ -168,6 +286,34 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
connector.setDumping(true);
|
connector.setDumping(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendBinlogDumpGTID(GTIDSet gtidSet) throws IOException {
|
||||||
|
BinlogDumpGTIDCommandPacket binlogDumpCmd = new BinlogDumpGTIDCommandPacket();
|
||||||
|
binlogDumpCmd.slaveServerId = this.slaveId;
|
||||||
|
binlogDumpCmd.gtidSet = gtidSet;
|
||||||
|
byte[] cmdBody = binlogDumpCmd.toBytes();
|
||||||
|
|
||||||
|
logger.info("COM_BINLOG_DUMP_GTID:{}", binlogDumpCmd);
|
||||||
|
HeaderPacket binlogDumpHeader = new HeaderPacket();
|
||||||
|
binlogDumpHeader.setPacketBodyLength(cmdBody.length);
|
||||||
|
binlogDumpHeader.setPacketSequenceNumber((byte) 0x00);
|
||||||
|
PacketManager.writePkg(connector.getChannel(), binlogDumpHeader.toBytes(), cmdBody);
|
||||||
|
connector.setDumping(true);
|
||||||
|
}
|
||||||
|
|
||||||
public MysqlConnection fork() {
|
public MysqlConnection fork() {
|
||||||
MysqlConnection connection = new MysqlConnection();
|
MysqlConnection connection = new MysqlConnection();
|
||||||
connection.setCharset(getCharset());
|
connection.setCharset(getCharset());
|
||||||
@@ -242,6 +388,22 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("update mariadb_slave_capability failed", 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -429,4 +591,5 @@ public class MysqlConnection implements ErosaConnection {
|
|||||||
public void setAuthInfo(AuthenticationInfo authInfo) {
|
public void setAuthInfo(AuthenticationInfo authInfo) {
|
||||||
this.authInfo = authInfo;
|
this.authInfo = authInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
@@ -345,6 +344,18 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected EntryPosition findStartPosition(ErosaConnection connection) throws IOException {
|
protected EntryPosition findStartPosition(ErosaConnection connection) throws IOException {
|
||||||
|
if (isGTIDMode()) {
|
||||||
|
// GTID模式下,CanalLogPositionManager里取最后的gtid,没有则取instanc配置中的
|
||||||
|
LogPosition logPosition = getLogPositionManager().getLatestIndexBy(destination);
|
||||||
|
if (logPosition != null) {
|
||||||
|
return logPosition.getPostion();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotEmpty(masterPosition.getGtid())) {
|
||||||
|
return masterPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EntryPosition startPosition = findStartPositionInternal(connection);
|
EntryPosition startPosition = findStartPositionInternal(connection);
|
||||||
if (needTransactionPosition.get()) {
|
if (needTransactionPosition.get()) {
|
||||||
logger.warn("prepare to find last position : {}", startPosition.toString());
|
logger.warn("prepare to find last position : {}", startPosition.toString());
|
||||||
@@ -374,7 +385,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
return findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
return findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
||||||
startTimestamp,
|
startTimestamp,
|
||||||
endPosition,
|
endPosition,
|
||||||
endPosition.getJournalName());
|
endPosition.getJournalName(),
|
||||||
|
true);
|
||||||
} else {
|
} else {
|
||||||
return endPosition;
|
return endPosition;
|
||||||
}
|
}
|
||||||
@@ -385,10 +397,16 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
if (tableMetaTSDB != null && (fixedPosition.getTimestamp() == null || fixedPosition.getTimestamp() <= 0)) {
|
if (tableMetaTSDB != null && (fixedPosition.getTimestamp() == null || fixedPosition.getTimestamp() <= 0)) {
|
||||||
// 使用一个未来极大的时间,基于位点进行定位
|
// 使用一个未来极大的时间,基于位点进行定位
|
||||||
long startTimestamp = System.currentTimeMillis() + 102L * 365 * 24 * 3600 * 1000; // 当前时间的未来102年
|
long startTimestamp = System.currentTimeMillis() + 102L * 365 * 24 * 3600 * 1000; // 当前时间的未来102年
|
||||||
return findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
EntryPosition entryPosition = findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
||||||
startTimestamp,
|
startTimestamp,
|
||||||
fixedPosition,
|
fixedPosition,
|
||||||
fixedPosition.getJournalName());
|
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 {
|
} else {
|
||||||
return fixedPosition;
|
return fixedPosition;
|
||||||
}
|
}
|
||||||
@@ -441,7 +459,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
specificLogFilePosition = findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
specificLogFilePosition = findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
||||||
entryPosition.getTimestamp(),
|
entryPosition.getTimestamp(),
|
||||||
endPosition,
|
endPosition,
|
||||||
entryPosition.getJournalName());
|
entryPosition.getJournalName(),
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,10 +511,10 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
// 主要考虑一个事务执行时间可能会几秒种,如果仅仅按照timestamp相同,则可能会丢失事务的前半部分数据
|
// 主要考虑一个事务执行时间可能会几秒种,如果仅仅按照timestamp相同,则可能会丢失事务的前半部分数据
|
||||||
private Long findTransactionBeginPosition(ErosaConnection mysqlConnection, final EntryPosition entryPosition)
|
private Long findTransactionBeginPosition(ErosaConnection mysqlConnection, final EntryPosition entryPosition)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
// 尝试找到一个合适的位置
|
// 针对开始的第一条为非Begin记录,需要从该binlog扫描
|
||||||
final AtomicBoolean reDump = new AtomicBoolean(false);
|
final AtomicLong preTransactionStartPosition = new AtomicLong(0L);
|
||||||
mysqlConnection.reconnect();
|
mysqlConnection.reconnect();
|
||||||
mysqlConnection.seek(entryPosition.getJournalName(), entryPosition.getPosition(), new SinkFunction<LogEvent>() {
|
mysqlConnection.seek(entryPosition.getJournalName(), 4L, new SinkFunction<LogEvent>() {
|
||||||
|
|
||||||
private LogPosition lastPosition;
|
private LogPosition lastPosition;
|
||||||
|
|
||||||
@@ -506,69 +525,33 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接查询第一条业务数据,确认是否为事务Begin/End
|
// 直接查询第一条业务数据,确认是否为事务Begin
|
||||||
if (CanalEntry.EntryType.TRANSACTIONBEGIN == entry.getEntryType()
|
// 记录一下transaction begin position
|
||||||
|| CanalEntry.EntryType.TRANSACTIONEND == entry.getEntryType()) {
|
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
|
||||||
lastPosition = buildLastPosition(entry);
|
&& entry.getHeader().getLogfileOffset() < entryPosition.getPosition()) {
|
||||||
return false;
|
preTransactionStartPosition.set(entry.getHeader().getLogfileOffset());
|
||||||
} else {
|
|
||||||
reDump.set(true);
|
|
||||||
lastPosition = buildLastPosition(entry);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entry.getHeader().getLogfileOffset() >= entryPosition.getPosition()) {
|
||||||
|
return false;// 退出
|
||||||
|
}
|
||||||
|
|
||||||
|
lastPosition = buildLastPosition(entry);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 上一次记录的poistion可能为一条update/insert/delete变更事件,直接进行dump的话,会缺少tableMap事件,导致tableId未进行解析
|
|
||||||
processSinkError(e, lastPosition, entryPosition.getJournalName(), entryPosition.getPosition());
|
processSinkError(e, lastPosition, entryPosition.getJournalName(), entryPosition.getPosition());
|
||||||
reDump.set(true);
|
|
||||||
return false;
|
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;
|
// 判断一下找到的最接近position的事务头的位置
|
||||||
|
if (preTransactionStartPosition.get() > entryPosition.getPosition()) {
|
||||||
public boolean sink(LogEvent event) {
|
logger.error("preTransactionEndPosition greater than startPosition from zk or localconf, maybe lost data");
|
||||||
try {
|
throw new CanalParseException("preTransactionStartPosition greater than startPosition from zk or localconf, maybe lost data");
|
||||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, true);
|
|
||||||
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) {
|
|
||||||
processSinkError(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();
|
|
||||||
}
|
}
|
||||||
|
return preTransactionStartPosition.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据时间查找binlog位置
|
// 根据时间查找binlog位置
|
||||||
@@ -585,7 +568,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
EntryPosition entryPosition = findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
EntryPosition entryPosition = findAsPerTimestampInSpecificLogFile(mysqlConnection,
|
||||||
startTimestamp,
|
startTimestamp,
|
||||||
endPosition,
|
endPosition,
|
||||||
startSearchBinlogFile);
|
startSearchBinlogFile,
|
||||||
|
false);
|
||||||
if (entryPosition == null) {
|
if (entryPosition == null) {
|
||||||
if (StringUtils.equalsIgnoreCase(minBinlogFileName, startSearchBinlogFile)) {
|
if (StringUtils.equalsIgnoreCase(minBinlogFileName, startSearchBinlogFile)) {
|
||||||
// 已经找到最早的一个binlog,没必要往前找了
|
// 已经找到最早的一个binlog,没必要往前找了
|
||||||
@@ -733,7 +717,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
private EntryPosition findAsPerTimestampInSpecificLogFile(MysqlConnection mysqlConnection,
|
private EntryPosition findAsPerTimestampInSpecificLogFile(MysqlConnection mysqlConnection,
|
||||||
final Long startTimestamp,
|
final Long startTimestamp,
|
||||||
final EntryPosition endPosition,
|
final EntryPosition endPosition,
|
||||||
final String searchBinlogFile) {
|
final String searchBinlogFile,
|
||||||
|
final Boolean justForPositionTimestamp) {
|
||||||
|
|
||||||
final LogPosition logPosition = new LogPosition();
|
final LogPosition logPosition = new LogPosition();
|
||||||
try {
|
try {
|
||||||
@@ -747,6 +732,15 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
EntryPosition entryPosition = null;
|
EntryPosition entryPosition = null;
|
||||||
try {
|
try {
|
||||||
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, true);
|
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, true);
|
||||||
|
if (justForPositionTimestamp && logPosition.getPostion() == null && event.getWhen() > 0) {
|
||||||
|
// 初始位点
|
||||||
|
entryPosition = new EntryPosition(searchBinlogFile,
|
||||||
|
event.getLogPos(),
|
||||||
|
event.getWhen() * 1000,
|
||||||
|
event.getServerId());
|
||||||
|
logPosition.setPostion(entryPosition);
|
||||||
|
}
|
||||||
|
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -758,8 +752,10 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
|
|
||||||
if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())
|
if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())
|
||||||
|| CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
|| CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
||||||
logger.debug("compare exit condition:{},{},{}, startTimestamp={}...", new Object[] {
|
if (logger.isDebugEnabled()) {
|
||||||
logfilename, logfileoffset, logposTimestamp, startTimestamp });
|
logger.debug("compare exit condition:{},{},{}, startTimestamp={}...", new Object[] {
|
||||||
|
logfilename, logfileoffset, logposTimestamp, startTimestamp });
|
||||||
|
}
|
||||||
// 事务头和尾寻找第一条记录时间戳,如果最小的一条记录都不满足条件,可直接退出
|
// 事务头和尾寻找第一条记录时间戳,如果最小的一条记录都不满足条件,可直接退出
|
||||||
if (logposTimestamp >= startTimestamp) {
|
if (logposTimestamp >= startTimestamp) {
|
||||||
return false;
|
return false;
|
||||||
@@ -776,14 +772,18 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
|
|||||||
// data.length,代表该事务的下一条offest,避免多余的事务重复
|
// data.length,代表该事务的下一条offest,避免多余的事务重复
|
||||||
if (CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
if (CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
|
||||||
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp, serverId);
|
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp, serverId);
|
||||||
logger.debug("set {} to be pending start position before finding another proper one...",
|
if (logger.isDebugEnabled()) {
|
||||||
entryPosition);
|
logger.debug("set {} to be pending start position before finding another proper one...",
|
||||||
|
entryPosition);
|
||||||
|
}
|
||||||
logPosition.setPostion(entryPosition);
|
logPosition.setPostion(entryPosition);
|
||||||
} else if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())) {
|
} else if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())) {
|
||||||
// 当前事务开始位点
|
// 当前事务开始位点
|
||||||
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp, serverId);
|
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp, serverId);
|
||||||
logger.debug("set {} to be pending start position before finding another proper one...",
|
if (logger.isDebugEnabled()) {
|
||||||
entryPosition);
|
logger.debug("set {} to be pending start position before finding another proper one...",
|
||||||
|
entryPosition);
|
||||||
|
}
|
||||||
logPosition.setPostion(entryPosition);
|
logPosition.setPostion(entryPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,435 @@
|
|||||||
|
package com.alibaba.otter.canal.parse.inbound.mysql;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.locks.LockSupport;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
|
||||||
|
import com.alibaba.otter.canal.common.utils.NamedThreadFactory;
|
||||||
|
import com.alibaba.otter.canal.parse.exception.CanalParseException;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.MultiStageCoprocessor;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.TableMeta;
|
||||||
|
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
|
import com.lmax.disruptor.BatchEventProcessor;
|
||||||
|
import com.lmax.disruptor.BlockingWaitStrategy;
|
||||||
|
import com.lmax.disruptor.EventFactory;
|
||||||
|
import com.lmax.disruptor.EventHandler;
|
||||||
|
import com.lmax.disruptor.ExceptionHandler;
|
||||||
|
import com.lmax.disruptor.InsufficientCapacityException;
|
||||||
|
import com.lmax.disruptor.LifecycleAware;
|
||||||
|
import com.lmax.disruptor.RingBuffer;
|
||||||
|
import com.lmax.disruptor.Sequence;
|
||||||
|
import com.lmax.disruptor.SequenceBarrier;
|
||||||
|
import com.lmax.disruptor.WorkHandler;
|
||||||
|
import com.lmax.disruptor.WorkerPool;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogBuffer;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.LogContext;
|
||||||
|
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.DeleteRowsLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent;
|
||||||
|
import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 针对解析器提供一个多阶段协同的处理
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 1. 网络接收 (单线程)
|
||||||
|
* 2. 事件基本解析 (单线程,事件类型、DDL解析构造TableMeta、维护位点信息)
|
||||||
|
* 3. 事件深度解析 (多线程, DML事件数据的完整解析)
|
||||||
|
* 4. 投递到store (单线程)
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author agapple 2018年7月3日 下午4:54:17
|
||||||
|
* @since 1.0.26
|
||||||
|
*/
|
||||||
|
public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implements MultiStageCoprocessor {
|
||||||
|
|
||||||
|
private LogEventConvert logEventConvert;
|
||||||
|
private EventTransactionBuffer transactionBuffer;
|
||||||
|
private ErosaConnection connection;
|
||||||
|
|
||||||
|
private int parserThreadCount;
|
||||||
|
private int ringBufferSize;
|
||||||
|
private RingBuffer<MessageEvent> disruptorMsgBuffer;
|
||||||
|
private ExecutorService parserExecutor;
|
||||||
|
private ExecutorService stageExecutor;
|
||||||
|
private String destination;
|
||||||
|
private CanalParseException exception;
|
||||||
|
|
||||||
|
public MysqlMultiStageCoprocessor(int ringBufferSize, int parserThreadCount, LogEventConvert logEventConvert,
|
||||||
|
EventTransactionBuffer transactionBuffer, String destination){
|
||||||
|
this.ringBufferSize = ringBufferSize;
|
||||||
|
this.parserThreadCount = parserThreadCount;
|
||||||
|
this.logEventConvert = logEventConvert;
|
||||||
|
this.transactionBuffer = transactionBuffer;
|
||||||
|
this.destination = destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
super.start();
|
||||||
|
this.exception = null;
|
||||||
|
this.disruptorMsgBuffer = RingBuffer.createSingleProducer(new MessageEventFactory(),
|
||||||
|
ringBufferSize,
|
||||||
|
new BlockingWaitStrategy());
|
||||||
|
|
||||||
|
this.parserExecutor = Executors.newFixedThreadPool(parserThreadCount,
|
||||||
|
new NamedThreadFactory("MultiStageCoprocessor-Parser-" + destination));
|
||||||
|
|
||||||
|
this.stageExecutor = Executors.newFixedThreadPool(2, new NamedThreadFactory("MultiStageCoprocessor-other-"
|
||||||
|
+ destination));
|
||||||
|
SequenceBarrier sequenceBarrier = disruptorMsgBuffer.newBarrier();
|
||||||
|
ExceptionHandler exceptionHandler = new SimpleFatalExceptionHandler();
|
||||||
|
// stage 2
|
||||||
|
BatchEventProcessor<MessageEvent> simpleParserStage = new BatchEventProcessor<MessageEvent>(disruptorMsgBuffer,
|
||||||
|
sequenceBarrier,
|
||||||
|
new SimpleParserStage());
|
||||||
|
simpleParserStage.setExceptionHandler(exceptionHandler);
|
||||||
|
disruptorMsgBuffer.addGatingSequences(simpleParserStage.getSequence());
|
||||||
|
|
||||||
|
// stage 3
|
||||||
|
SequenceBarrier dmlParserSequenceBarrier = disruptorMsgBuffer.newBarrier(simpleParserStage.getSequence());
|
||||||
|
WorkHandler<MessageEvent>[] workHandlers = new DmlParserStage[parserThreadCount];
|
||||||
|
for (int i = 0; i < parserThreadCount; i++) {
|
||||||
|
workHandlers[i] = new DmlParserStage();
|
||||||
|
}
|
||||||
|
WorkerPool<MessageEvent> workerPool = new WorkerPool<MessageEvent>(disruptorMsgBuffer,
|
||||||
|
dmlParserSequenceBarrier,
|
||||||
|
exceptionHandler,
|
||||||
|
workHandlers);
|
||||||
|
Sequence[] sequence = workerPool.getWorkerSequences();
|
||||||
|
disruptorMsgBuffer.addGatingSequences(sequence);
|
||||||
|
|
||||||
|
// stage 4
|
||||||
|
SequenceBarrier sinkSequenceBarrier = disruptorMsgBuffer.newBarrier(sequence);
|
||||||
|
BatchEventProcessor<MessageEvent> sinkStoreStage = new BatchEventProcessor<MessageEvent>(disruptorMsgBuffer,
|
||||||
|
sinkSequenceBarrier,
|
||||||
|
new SinkStoreStage());
|
||||||
|
sinkStoreStage.setExceptionHandler(exceptionHandler);
|
||||||
|
disruptorMsgBuffer.addGatingSequences(sinkStoreStage.getSequence());
|
||||||
|
|
||||||
|
// start work
|
||||||
|
stageExecutor.submit(simpleParserStage);
|
||||||
|
stageExecutor.submit(sinkStoreStage);
|
||||||
|
workerPool.start(parserExecutor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
try {
|
||||||
|
parserExecutor.shutdownNow();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
stageExecutor.shutdownNow();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
super.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网络数据投递
|
||||||
|
*/
|
||||||
|
public boolean publish(LogBuffer buffer) {
|
||||||
|
return publish(buffer, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean publish(LogBuffer buffer, String binlogFileName) {
|
||||||
|
if (!isStart()) {
|
||||||
|
if (exception != null) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean interupted = false;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
long next = disruptorMsgBuffer.tryNext();
|
||||||
|
MessageEvent event = disruptorMsgBuffer.get(next);
|
||||||
|
event.setBuffer(buffer);
|
||||||
|
if (binlogFileName != null) {
|
||||||
|
event.setBinlogFileName(binlogFileName);
|
||||||
|
}
|
||||||
|
disruptorMsgBuffer.publish(next);
|
||||||
|
break;
|
||||||
|
} catch (InsufficientCapacityException e) {
|
||||||
|
// park
|
||||||
|
LockSupport.parkNanos(1L);
|
||||||
|
interupted = Thread.interrupted();
|
||||||
|
}
|
||||||
|
} while (!interupted && isStart());
|
||||||
|
|
||||||
|
if (exception != null) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
if (isStart()) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SimpleParserStage implements EventHandler<MessageEvent>, LifecycleAware {
|
||||||
|
|
||||||
|
private LogDecoder decoder;
|
||||||
|
private LogContext context;
|
||||||
|
|
||||||
|
public SimpleParserStage(){
|
||||||
|
decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
|
||||||
|
context = new LogContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEvent(MessageEvent event, long sequence, boolean endOfBatch) throws Exception {
|
||||||
|
try {
|
||||||
|
LogBuffer buffer = event.getBuffer();
|
||||||
|
if (StringUtils.isNotEmpty(event.getBinlogFileName())
|
||||||
|
&& !context.getLogPosition().getFileName().equals(event.getBinlogFileName())) {
|
||||||
|
// set roate binlog file name
|
||||||
|
context.setLogPosition(new LogPosition(event.getBinlogFileName(), context.getLogPosition()
|
||||||
|
.getPosition()));
|
||||||
|
}
|
||||||
|
|
||||||
|
LogEvent logEvent = decoder.decode(buffer, context);
|
||||||
|
event.setEvent(logEvent);
|
||||||
|
|
||||||
|
int eventType = logEvent.getHeader().getType();
|
||||||
|
TableMeta tableMeta = null;
|
||||||
|
boolean needDmlParse = false;
|
||||||
|
switch (eventType) {
|
||||||
|
case LogEvent.WRITE_ROWS_EVENT_V1:
|
||||||
|
case LogEvent.WRITE_ROWS_EVENT:
|
||||||
|
tableMeta = logEventConvert.parseRowsEventForTableMeta((WriteRowsLogEvent) logEvent);
|
||||||
|
needDmlParse = true;
|
||||||
|
break;
|
||||||
|
case LogEvent.UPDATE_ROWS_EVENT_V1:
|
||||||
|
case LogEvent.UPDATE_ROWS_EVENT:
|
||||||
|
tableMeta = logEventConvert.parseRowsEventForTableMeta((UpdateRowsLogEvent) logEvent);
|
||||||
|
needDmlParse = true;
|
||||||
|
break;
|
||||||
|
case LogEvent.DELETE_ROWS_EVENT_V1:
|
||||||
|
case LogEvent.DELETE_ROWS_EVENT:
|
||||||
|
tableMeta = logEventConvert.parseRowsEventForTableMeta((DeleteRowsLogEvent) logEvent);
|
||||||
|
needDmlParse = true;
|
||||||
|
break;
|
||||||
|
case LogEvent.ROWS_QUERY_LOG_EVENT:
|
||||||
|
needDmlParse = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
CanalEntry.Entry entry = logEventConvert.parse(event.getEvent(), false);
|
||||||
|
event.setEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录一下DML的表结构
|
||||||
|
event.setNeedDmlParse(needDmlParse);
|
||||||
|
event.setTable(tableMeta);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
exception = new CanalParseException(e);
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShutdown() {
|
||||||
|
if (isStart()) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DmlParserStage implements WorkHandler<MessageEvent>, LifecycleAware {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvent(MessageEvent event) throws Exception {
|
||||||
|
try {
|
||||||
|
if (event.isNeedDmlParse()) {
|
||||||
|
int eventType = event.getEvent().getHeader().getType();
|
||||||
|
CanalEntry.Entry entry = null;
|
||||||
|
switch (eventType) {
|
||||||
|
case LogEvent.ROWS_QUERY_LOG_EVENT:
|
||||||
|
entry = logEventConvert.parse(event.getEvent(), false);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// 单独解析dml事件
|
||||||
|
entry = logEventConvert.parseRowsEvent((RowsLogEvent) event.getEvent(), event.getTable());
|
||||||
|
}
|
||||||
|
|
||||||
|
event.setEntry(entry);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
exception = new CanalParseException(e);
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShutdown() {
|
||||||
|
if (isStart()) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SinkStoreStage implements EventHandler<MessageEvent>, LifecycleAware {
|
||||||
|
|
||||||
|
public void onEvent(MessageEvent event, long sequence, boolean endOfBatch) throws Exception {
|
||||||
|
try {
|
||||||
|
if (event.getEntry() != null) {
|
||||||
|
transactionBuffer.add(event.getEntry());
|
||||||
|
}
|
||||||
|
|
||||||
|
LogEvent logEvent = event.getEvent();
|
||||||
|
if (connection instanceof MysqlConnection && logEvent.getSemival() == 1) {
|
||||||
|
// semi ack回报
|
||||||
|
((MysqlConnection) connection).sendSemiAck(logEvent.getHeader().getLogFileName(),
|
||||||
|
logEvent.getHeader().getLogPos());
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear for gc
|
||||||
|
event.setBuffer(null);
|
||||||
|
event.setBinlogFileName(null);
|
||||||
|
event.setEvent(null);
|
||||||
|
event.setTable(null);
|
||||||
|
event.setEntry(null);
|
||||||
|
event.setNeedDmlParse(false);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
exception = new CanalParseException(e);
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShutdown() {
|
||||||
|
if (isStart()) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageEvent {
|
||||||
|
|
||||||
|
private String binlogFileName; // for local binlog parse
|
||||||
|
private LogBuffer buffer;
|
||||||
|
private CanalEntry.Entry entry;
|
||||||
|
private boolean needDmlParse = false;
|
||||||
|
private TableMeta table;
|
||||||
|
private LogEvent event;
|
||||||
|
|
||||||
|
public String getBinlogFileName() {
|
||||||
|
return binlogFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBinlogFileName(String binlogFileName) {
|
||||||
|
this.binlogFileName = binlogFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogBuffer getBuffer() {
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBuffer(LogBuffer buffer) {
|
||||||
|
this.buffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogEvent getEvent() {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEvent(LogEvent event) {
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CanalEntry.Entry getEntry() {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntry(CanalEntry.Entry entry) {
|
||||||
|
this.entry = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNeedDmlParse() {
|
||||||
|
return needDmlParse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNeedDmlParse(boolean needDmlParse) {
|
||||||
|
this.needDmlParse = needDmlParse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableMeta getTable() {
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTable(TableMeta table) {
|
||||||
|
this.table = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleFatalExceptionHandler implements ExceptionHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleEventException(final Throwable ex, final long sequence, final Object event) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleOnStartException(final Throwable ex) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleOnShutdownException(final Throwable ex) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageEventFactory implements EventFactory<MessageEvent> {
|
||||||
|
|
||||||
|
public MessageEvent newInstance() {
|
||||||
|
return new MessageEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogEventConvert(LogEventConvert logEventConvert) {
|
||||||
|
this.logEventConvert = logEventConvert;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTransactionBuffer(EventTransactionBuffer transactionBuffer) {
|
||||||
|
this.transactionBuffer = transactionBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConnection(ErosaConnection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user