Compare commits

...
Author SHA1 Message Date
lcybo 67a3a1d955 Merge pull request #827 from lcybo/metrics_support
Merge master updates
2018-08-09 22:50:32 +08:00
Chuanyi Li a857b87388 Merge branch 'master' into metrics_support 2018-08-09 22:44:39 +08:00
七锋 b57ac0bcdf fixed merge and blob encoding example 2018-08-09 16:56:13 +08:00
charles.lin ea6391d1c3 Merge branch 'branch-rds-support' 2018-08-09 16:44:12 +08:00
agapple 89a32aaabd Merge pull request #825 from lcybo/master
Remove dirty test case
2018-08-09 11:38:48 +08:00
charles.lin b53131553e Commit test case 2018-08-09 02:01:44 +08:00
charles.lin 68d8afae3e Merge commit '9e816bc48f9955f9e2839873993cef9627c2f389' into branch-rds-support
Conflicts:
	instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java
	instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java
	parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java
	parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java
2018-08-09 02:01:00 +08:00
lcybo a2eb128698 Merge pull request #826 from lcybo/metrics_support
Metrics support
2018-08-08 22:56:44 +08:00
Chuanyi Li 760f2dda80 Enhance heartbeat, rowscount. 2018-08-08 22:53:08 +08:00
Chuanyi Li bf9dc0073d Merge branch 'master' into metrics_support 2018-08-08 21:01:22 +08:00
Chuanyi Li 92f24bd02e Remove dirty test case 2018-08-08 20:59:09 +08:00
Chuanyi Li d4797a8fb9 Merge master branch. 2018-08-08 20:35:14 +08:00
Chuanyi Li 060bd8e31d Reformat code. 2018-08-08 20:10:34 +08:00
charles.lin 61c7f7738d rds binlog 下载支持 2018-08-08 19:29:52 +08:00
七锋 9e816bc48f support otter use tableMetaTSDB 2018-08-08 18:54:57 +08:00
agapple 377a72e1c0 Merge pull request #814 from payonxp/pyf2
schema history storage interface
2018-08-08 10:36:41 +08:00
Chuanyi Li 88de747ed5 Improve sink blocking time resolution. 2018-08-08 07:53:44 +08:00
lcybo 725d36aa98 Merge pull request #819 from lcybo/metrics_support
canal 性能监控改动版,merge到开发分支(metrics_support)
2018-08-07 23:02:37 +08:00
Chuanyi Li d24e10fb3e Canal metrics snapshot version. 2018-08-07 22:52:30 +08:00
Chuanyi Li fdae646e26 Merge branch 'master' into metrics_support 2018-08-07 19:46:30 +08:00
Payon b1dc5bd59f Merge branch 'master' into pyf2 2018-08-07 19:29:27 +08:00
七锋 23f7b3b5d8 fixed issue #818 , tableMeta tsdb bugfix 2018-08-07 18:51:23 +08:00
agapple 09339333bb Merge pull request #816 from wingerx/master
fix ci: fixed compiler java 1.7
2018-08-07 18:23:41 +08:00
七锋 f36ce647e6 fixed issue #781 , upgrade fastsql 540 2018-08-07 17:46:36 +08:00
Chuanyi Li L 40c3e65341 Refactor metrics code. 2018-08-07 17:17:33 +08:00
winger 6d3584b6fb 移除canal.instance.spring 依赖后,需要加入druid与mysql connector的依赖 2. PropertyPlaceholderConfigurer由canal.instance.spring移至db 2018-08-07 16:59:55 +08:00
七锋 4006cedaad fixed issue #781, 修复部分DDL解析异常 2018-08-07 16:44:40 +08:00
七锋 cd0bf60824 fixed compiler java 1.7 2018-08-07 16:19:58 +08:00
agapple c4c45d9e0b Merge pull request #813 from wingerx/master
fix #802: 从otter中剥离MySQL 相关同步实现,作为example的参考
2018-08-07 15:50:09 +08:00
yfpeng 02868d8fec 为了解决回追数据时Rowdata与当前schema不匹配
对TableMeta提供持久化接口,可以根据timestamp匹配历史schema
2018-08-07 13:42:29 +08:00
winger 7d9e2dc69b fix #802: 从otter中剥离MySQL 相关同步实现,作为example的参考 2018-08-07 12:04:57 +08:00
Chuanyi Li b5a1898cec Prometheus refactor. 2018-08-07 08:18:01 +08:00
Chuanyi Li L 680faea2bb Add listener to write. 2018-08-06 17:22:28 +08:00
winger 1cd77bf551 fix bug: kafka get row data for performance 2018-08-06 15:29:14 +08:00
qmzhang 9786faba9a instance由manager管理时,CanalParameter#MetaMode增加本地文件方式,本地meta.dat存储位点信息; 2018-08-06 15:29:14 +08:00
七锋 325fe89aed fixed issue #801 , optimizer docker 2018-08-06 15:29:13 +08:00
七锋 ffeec8fc86 fixed issue #801 , support canal docker 2018-08-06 15:29:13 +08:00
winger 7662c9241e 支持GTID模式下,通过获取当前gtid值 2018-08-06 15:29:13 +08:00
Chuanyi Li L d01195ac82 Merge branch 'master' into metrics_support 2018-08-06 15:26:44 +08:00
agapple 7c7ce43455 Merge pull request #810 from wingerx/master
fix bug: kafka get row data for performance
2018-08-06 12:19:08 +08:00
winger 119c5a6971 Merge remote-tracking branch 'upstream/master' 2018-08-06 11:19:58 +08:00
winger 8a952d3a5f fix bug: kafka get row data for performance 2018-08-06 11:11:57 +08:00
agapple 764e1b6dc0 Merge pull request #807 from qmz/master
instance with manager模式一些微调
2018-08-06 10:11:53 +08:00
Chuanyi Li 8b186d1330 canal server metrics refactor. 2018-08-06 08:12:59 +08:00
qmzhang 4e5e2f84e0 Merge branch 'master' of https://github.com/alibaba/canal 2018-08-05 19:45:59 +08:00
qmzhang 3c716e2a43 instance由manager管理时,CanalParameter#MetaMode增加本地文件方式,本地meta.dat存储位点信息; 2018-08-05 19:40:14 +08:00
七锋 872dad8ce8 fixed issue #801 , optimizer docker 2018-08-04 10:37:55 +08:00
agapple bd6ab1cc80 Merge pull request #806 from wingerx/master
支持GTID模式下,默认获取当前gtid值作为起始值
2018-08-03 19:48:42 +08:00
七锋 7fd958a490 fixed issue #801 , support canal docker 2018-08-03 17:48:32 +08:00
Chuanyi Li L b7431eb7d0 Reformat code. 2018-08-03 17:42:57 +08:00
Chuanyi Li L c7fc149e7e Netty server/client profiler skeleton. 2018-08-03 17:32:48 +08:00
winger da1ce51a0f 支持GTID模式下,通过获取当前gtid值 2018-08-03 11:11:38 +08:00
Chuanyi Li L 368554166c Builder of Aggregator. 2018-08-02 17:36:22 +08:00
Chuanyi Li L c4ee350717 Merge branch 'master' of https://github.com/alibaba/canal 2018-08-02 14:55:01 +08:00
七锋 26edbeaa28 fixed kafka performance 2018-08-02 13:58:41 +08:00
七锋 93ebc7b973 fixed unused repo 2018-08-01 18:25:49 +08:00
Chuanyi Li ae0f43d1c7 netty3 future listener for metrics. 2018-08-01 08:17:55 +08:00
agapple 50d1ec583b Merge pull request #790 from wingerx/master
kafka producer 适配 row data for performance #726
2018-07-31 13:11:49 +08:00
winger 3d5f13fec8 kafka producer 适配 row data for performance #726 2018-07-31 10:34:19 +08:00
文哥 fe12a8481f Merge pull request #788 from lcybo/master 2018-07-29 22:19:43 +08:00
Chuanyi Li 1eaf198acf Revert test code. 2018-07-29 15:06:30 +08:00
Chuanyi Li c0a40499b0 Canal monitoring(prometheus module) snapshot version. 2018-07-29 14:55:38 +08:00
七锋 8cfb70e080 fixed issue #782 , 重复了exception 2018-07-26 16:05:33 +08:00
agapple d624358cb5 Merge pull request #782 from lcybo/master
MysqlMultiStageCoprocessor健壮性enhance
2018-07-26 15:51:39 +08:00
Chuanyi Li L 72b907a75f Further improvement for issue #771. 2018-07-26 15:22:38 +08:00
agapple 698573b14a Merge pull request #780 from wingerx/master
fix bug #776
2018-07-26 15:13:25 +08:00
Chuanyi Li L 129051a56e Further improvement for issue #771. 2018-07-26 14:30:42 +08:00
winger e608e1d601 fix bug #776 2018-07-26 12:33:10 +08:00
agapple 22a0d88fe9 Merge pull request #777 from wingerx/master
fix issue #771 #776 #756
2018-07-26 10:07:36 +08:00
winger ccd1e52411 fix issue #771 #776 756 2018-07-25 16:38:38 +08:00
winger aaf18ccd7e fix issue #771 #756 2018-07-25 10:18:15 +08:00
agapple 62b1c1ea93 Merge pull request #768 from CAI1987/patch-4
修改Readme.md,新增数据订阅活动公告
2018-07-23 15:44:34 +08:00
CAI1987 20807e58c8 Update README.md 2018-07-23 15:39:23 +08:00
七锋 89eb3d6078 fixed test code 2018-07-20 16:45:09 +08:00
七锋 7b52568575 fixed unused code 2018-07-20 16:43:00 +08:00
七锋 d520260fe8 remove unused 2018-07-20 14:46:52 +08:00
七锋 77c1c65b83 fixed issue #726 , 提交测试用例 2018-07-20 14:32:51 +08:00
七锋 fe38bc16d2 fixed issue #726 , 优化完毕 2018-07-20 14:32:13 +08:00
agapple b4c6490c94 Merge pull request #762 from CAI1987/patch-3
修改Readme.md 新增DTS说明
2018-07-19 20:24:44 +08:00
CAI1987 67f2e55d38 Update README.md 2018-07-19 20:14:49 +08:00
CAI1987 b9d7f0b6d0 Update README.md 2018-07-19 20:14:19 +08:00
CAI1987 b880d96a86 新增商业化版本说明 2018-07-19 20:13:01 +08:00
CAI1987 b9156936ae 新增商业版本说明 2018-07-19 20:12:03 +08:00
CAI1987 617f9bbc62 添加Canal商业化版本更新说明 2018-07-19 19:24:37 +08:00
七锋 b19eaeb89a fixed issue #726 , 单链接复用ByteBuffer数组 2018-07-19 17:55:53 +08:00
七锋 5582a38930 fixed issue #726, 优化SessionHandler里直接拼byte[],绕过protobuf的多次拷贝 2018-07-19 13:35:38 +08:00
agapple c6eee47af6 Merge pull request #759 from wingerx/master
append gtid lastCommitted and sequenceNumber support
2018-07-18 19:26:51 +08:00
七锋 10bb800f85 fixed issue #726 , 优化SessionHandler的传输处理,提前序列化Entry 2018-07-18 18:21:45 +08:00
winger 89726a6365 append gtid lastCommitted and sequenceNumber support 2018-07-18 17:07:39 +08:00
七锋 8e856acac5 fixed issue #742, improve h2 config 2018-07-17 22:32:57 +08:00
七锋 df8fe4a9eb fixed issue #756 , improve exception logger 2018-07-17 22:15:42 +08:00
agapple 33a2a6121a Merge pull request #754 from wingerx/master
fix issue #751
2018-07-17 21:04:18 +08:00
winger 441d2d2449 fix issue #751 2018-07-17 12:04:27 +08:00
七锋 bef6a66eaf fixed issue #296, support getWithoutAck/ack/rollback for NPE 2018-07-16 23:00:35 +08:00
七锋 c1805a4831 format DruidDdlParser 2018-07-16 22:54:35 +08:00
agapple 38eecd9aef Merge pull request #747 from DinoZhang/master
add restart.sh
2018-07-16 22:52:46 +08:00
agapple cef431c8af Merge pull request #734 from qmz/master
add create database support
2018-07-16 22:52:04 +08:00
七锋 3efd589cb1 fixed issue #724 , retry for broke pipe 2018-07-16 22:45:16 +08:00
DinoZhang 92c570e967 Merge pull request #1 from DinoZhang/DinoZhang-patch-1
Create restart.sh
2018-07-12 15:53:49 +08:00
DinoZhang cf7af17aec Create restart.sh 2018-07-12 15:42:54 +08:00
agapple 3308bf0662 Merge pull request #737 from lcybo/master
Parallel参数细节的完善
2018-07-06 15:32:45 +08:00
Chuanyi Li be605617c3 Parallel参数细节的完善 2018-07-05 20:24:42 +08:00
qmzhang bb8fec963b add create/drop databases event support 2018-07-05 16:57:36 +08:00
七锋 da8bf59d5b fixed issue #726 , 多线程解析binlog DML事件,提升性能 2018-07-04 23:37:20 +08:00
qmzhang 54168af790 add create database support 2018-07-03 17:25:08 +08:00
七锋 f0bed4c762 fixed issue #726 优化时间类型的解析,速度从20MB提升到45MB 2018-07-03 14:52:48 +08:00
七锋 4d4d97419c fixed issue #726, 去掉socket receiveBuffer/sendBuffer的设置,影响网络吞吐量 2018-07-03 13:42:31 +08:00
七锋 41eef9a090 fixed issue #729 , isDdl bug 2018-07-02 14:34:46 +08:00
七锋 9ae6c0c863 fixed find event position 2018-06-27 18:33:48 +08:00
agapple 5f47809c8e Merge pull request #709 from rewerma/master
增加canal-kafka-client的running状态监控
2018-06-21 14:25:22 +08:00
rewerma f2e3ff2dae 增加canal-kafka-client的running状态监控 2018-06-21 11:30:11 +08:00
七锋 92778be5be fixed fastsql version 2018-06-21 10:44:07 +08:00
agapple 5da8d391f8 Merge pull request #700 from rewerma/master
降低kafka依赖版本,申明server的running为volatile
2018-06-21 10:42:47 +08:00
mcy f867b183ab 降低kafka依赖的版本为0.9.0.1
申明server的running为volatile
2018-06-18 12:15:28 +08:00
rewerma ea555df315 Merge pull request #4 from alibaba/master
merge alibaba/canal
2018-06-15 15:59:37 +08:00
agapple 4af1e5fb84 Merge pull request #698 from nbqyqx/master
ClientRunningMonitor shutdown delayExector threadpool #697
2018-06-15 13:25:04 +08:00
machey bf02db4cbc fastsql 186 for build 2018-06-14 22:08:08 +08:00
Neal Hu 967f8d6b0f Fixes #697 ClientRunningMonitor shutdown delayExector threadpool 2018-06-14 21:28:10 +08:00
Neal Hu 4f4b016da3 Merge pull request #2 from alibaba/master
merge master
2018-06-14 21:19:45 +08:00
七锋 a4255a6377 fixed format 2018-06-14 19:25:34 +08:00
agapple dca1b20043 Merge pull request #695 from rewerma/master
增加kafka对canal binlog信息的生成消费支持
2018-06-14 17:55:31 +08:00
rewerma 025d36604b Merge pull request #2 from alibaba/master
fixed client idleTimeout 1 hour
2018-06-13 18:31:04 +08:00
rewerma e23a19cd85 整理代码 2018-06-13 18:29:15 +08:00
rewerma 593902d349 修改example的一些bug 2018-06-13 18:25:27 +08:00
rewerma d014167226 kafka consumer 默认session timeout 修改 2018-06-13 17:57:49 +08:00
rewerma df668a6f72 kafka 修改日志输出问题 2018-06-13 17:20:16 +08:00
rewerma cc5eb1075b kafka consumer example 修改 2018-06-13 15:51:28 +08:00
rewerma 19f4c5ae1b kafka consumer 测试类修改 2018-06-13 15:40:49 +08:00
rewerma 4e41ebf746 kafka consumer close方式 2018-06-13 15:38:24 +08:00
rewerma 31cbdb1824 kafka consumer close方式 2018-06-13 15:36:21 +08:00
rewerma 1b6efdf463 修改kafka consumer关闭方式 2018-06-13 15:25:43 +08:00
rewerma cb6c7ef3e8 完善注释 2018-06-13 14:24:32 +08:00
rewerma 4bd2a79905 kafka pom调整 2018-06-13 14:17:07 +08:00
rewerma 345e22bd2c kafka 设置独立logback.xml 2018-06-13 14:16:37 +08:00
rewerma 9964bb75b1 kafka 客户端 example 完善 2018-06-13 14:01:30 +08:00
rewerma ec072a45d8 kafka 客户端 example 2018-06-13 13:49:15 +08:00
rewerma efb7b32694 kafka producer 修改配置,一个destination可以对应多个kafka topic 2018-06-13 13:36:28 +08:00
Neal Hu 74bc6368f2 stop running in example 2018-06-13 11:15:01 +08:00
rewerma b96e4dd23a kafka 客户端测试类 2018-06-13 11:13:35 +08:00
rewerma d991e6be60 kafka 客户端消费代码整理 2018-06-13 10:34:28 +08:00
rewerma e7846085fc kafka 客户端消费完成基本测试 2018-06-12 19:25:03 +08:00
rewerma 6faa3fd586 增加对message的验证,过滤transaction begin end 类型 2018-06-12 13:28:49 +08:00
七锋 fa34eb0e59 fixed client idleTimeout 1 hour 2018-06-12 13:20:41 +08:00
rewerma 3e97e6b771 整理pom格式 2018-06-12 10:53:46 +08:00
rewerma 330da50a5e 修改kafka配置项属性 2018-06-12 10:23:50 +08:00
rewerma e35a06743c 增加kafka的日志 2018-06-12 09:46:54 +08:00
rewerma 0d6757c257 增加kafka的日志 2018-06-12 09:46:01 +08:00
rewerma e4cde2f6a1 修改withoutNetty的bug 2018-06-12 09:00:55 +08:00
machengyuan4081 9b7d62717e kafka system.properties 的 bug 2018-06-11 23:31:02 +08:00
machengyuan4081 b306bde3ec kafka producer 2018-06-11 23:19:28 +08:00
rewerma 31f72e1aac 增加 kafaka producer 直接从canal server消费数据发送 2018-06-11 19:52:55 +08:00
rewerma ad201660a6 Merge pull request #1 from alibaba/master
pull
2018-06-11 17:21:09 +08:00
七锋 77f1dfd324 fixed heartbeat 2018-06-08 15:03:49 +08:00
七锋 11467fca2a fixed issue #639 , support rename 2018-06-08 10:56:20 +08:00
agapple cc8d5b4263 Merge pull request #682 from nbqyqx/master
Fix #631 Boolean java type support
2018-06-08 10:45:25 +08:00
Neal Hu 4022aa8b5a stopRunning in example 2018-06-08 00:00:29 +08:00
Neal Hu 187b98fbda Boolean Type support. https://github.com/alibaba/canal/issues/631 2018-06-07 23:52:13 +08:00
七锋 0f33c8c89c fixed issue #677 , add gtidon default value 2018-06-07 14:03:33 +08:00
Neal Hu b6967dc0dc Merge pull request #1 from alibaba/master
pr
2018-06-07 12:37:14 +08:00
七锋 b734a32ae6 fixed issue #626, support xa rollback/commit event 2018-06-06 11:39:59 +08:00
七锋 1f16310b7d fixed issue #663, ddl parse 2018-06-05 16:05:48 +08:00
七锋 5349a5c49b fixed issue #657, fixed ConcurrentModificationException 2018-06-05 16:04:07 +08:00
七锋 a2241e2521 fixed issue #641, support uk parse 2018-06-05 15:41:03 +08:00
七锋 fedc07352b fixed issue #662 2018-06-05 14:59:33 +08:00
七锋 e251de79f3 fixed issue #610 , utf8mb4 encode support 2018-05-10 19:53:52 +08:00
七锋 7076e3124d fixed fastsql 2018-05-10 19:35:17 +08:00
agapple fca7da6bba Merge pull request #623 from rewerma/master
在CanalConnector接口加入stopRunning方法,用于优雅停止client
2018-05-10 19:34:24 +08:00
七锋 57c2cc4c4e fixed issue #626 , support mysql xa parse 2018-05-07 21:25:29 +08:00
七锋 3c913d1e22 fixed issue #126, support gtid 2018-05-07 18:33:37 +08:00
rewerma 4141049bb9 在CanalConnector接口加入stopRunning方法,用于优雅停止client 2018-05-07 15:13:24 +08:00
agapple 912a7a12be Merge pull request #618 from hiwjd/feature/gtid-support
支持通过GTID同步binlog
2018-05-07 10:30:58 +08:00
agapple 5c2ed38d2f Merge pull request #621 from spccold/master
close zkclientx if necessary when stop canal and fix CanalInstanceWithManager#doInitEventParser bug
2018-05-07 10:21:50 +08:00
七锋 c8f1d54fa1 fixed issue #619, improve code 2018-05-07 10:18:25 +08:00
agapple e0fa8e0f72 Merge pull request #622 from nbqyqx/master
block waiting interrupted  by stop request
2018-05-07 10:12:50 +08:00
spccold 7eb6fb3e8a fix CanalInstanceWithManager#doInitEventParser bug 2018-05-06 21:50:34 +08:00
Neal Hu debfdb8873 abstract canal client test waiting until running == true 2018-05-05 23:55:57 +08:00
Neal Hu eda7e4ca2b block waiting interrupted by stop request 2018-05-05 23:06:16 +08:00
spccold 70e6bdde02 close zkclientx if necessary when stop canal 2018-05-05 11:35:41 +08:00
hiwjd 21f3f8c973 支持通过GTID同步binlog 2018-05-04 16:47:03 +08:00
七锋 f46133d116 upgrade fastsql 228 2018-04-18 13:30:31 +08:00
七锋 9b7de00efb fixed performace 2018-04-12 20:10:48 +08:00
七锋 c340bd6565 upgrade fastsql 159 2018-03-16 15:10:02 +08:00
七锋 511734c3d8 fixed compiler error 2018-03-15 20:21:42 +08:00
七锋 af002f4e24 fixed bio socket 2018-03-15 20:20:29 +08:00
七锋 28ad12d0ea upgrade fastsql 2.0.0_preview_151 2018-03-15 15:48:16 +08:00
agapple 6229a8593f Merge pull request #555 from whaon/patch-1
renew an InetSocketAddress to resolve address again
2018-03-15 14:33:05 +08:00
whaon 256056e14f renew an InetSocketAddress to resolve address again
resolve [issue427](https://github.com/alibaba/otter/issues/427)
2018-03-14 09:55:20 +08:00
agapple 499be8bd8c upgrade druid to fastsql 2018-03-13 16:28:08 +08:00
agapple cd87ef00cc fixed issue #553 , merge code 2018-03-13 10:00:11 +08:00
agapple 6b7109c66b fixed issue #539 , support bio&netty 2018-03-12 23:21:43 +08:00
七锋 b90c9fac41 fixed druid version 1.1.9 2018-03-12 17:14:12 +08:00
agapple 88c79544f0 Merge pull request #532 from LavaCoref/hotfix/lava/1_json
fix(dbsync): json column of zero length has no value, value parsing s…
2018-03-12 16:37:50 +08:00
agapple fddaff1bd9 Merge pull request #536 from Wu-Jianqiang/master
主要修复 SocketChannel 默认缓存大小(1MB)不够用时需自动扩充,否则将因缓存空间不足而造成I/O超时假象
2018-03-12 16:36:07 +08:00
七锋 847ebf9bf3 fixed issue #535 , use show create table to get tableMeta 2018-03-12 16:27:58 +08:00
Wu-Jianqiang 3e7e7b7f6e 修改:变量命名和super调用 2018-02-26 16:37:21 +08:00
Wu-Jianqiang 06e7f44114 修复:MASTER_HEARTBEAT_PERIOD 的计量单位是秒(可以包含3位小数表示毫秒),但不是纳秒。 2018-02-26 16:31:58 +08:00
Wu-Jianqiang 8ad603fea4 修复:SocketChannel 默认缓存大小(1MB)不够用时需自动扩充,否则将因缓存空间不足而造成I/O超时假象 2018-02-26 16:26:01 +08:00
WU Jianqiang 863a61d626 Merge pull request #2 from alibaba/master
merge
2018-02-23 23:17:46 +08:00
lvtianyu 7b38542183 fix(dbsync): json column of zero length has no value, value parsing should be skipped 2018-02-23 10:41:49 +08:00
agapple 0af789b1c7 Merge pull request #529 from yakirChen/master
TimelineTransactionBarrier import CanalSinkException; Maven Compile 的一些 Warning
2018-02-12 11:43:57 +08:00
agapple 2d49b709ca fixed socket wait period 10ms 2018-02-12 11:43:03 +08:00
agapple 826a45c105 Merge pull request #528 from lcybo/timeout
Use mysql master heartbeat to detect phycial tcp connection failure.
2018-02-12 11:41:55 +08:00
yakirChen 03452dcdda Merge branch 'master' of github.com:yakirChen/canal 2018-02-12 11:29:16 +08:00
yakirChen 514876f60f [x] Fix Compile Error : TimelineTransactionBarrier.java:[70,27] 找不到符号: 类 CanalSinkException
[x] Fix Maven Wranning: The expression ${pom.version} is deprecated. Please use ${project.version} instead.
[x] Fix MavenWranning : 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-jar-plugin is missing.
2018-02-12 11:28:42 +08:00
yakirChen 89994c52f9 [x] Fix Compile Error : TimelineTransactionBarrier.java:[70,27] 找不到符号: 类 CanalSinkException
[x] Fix Maven Wranning: The expression ${pom.version} is deprecated. Please use ${project.version} instead.
[x] Fix MavenWranning : 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-jar-plugin is missing.
2018-02-12 11:17:54 +08:00
agapple edc2fbe1dc fixed issue #507 2018-02-12 11:15:43 +08:00
agapple cfa90af33c fixed unique and upgrade druid 2018-02-12 11:01:53 +08:00
Chuanyi Li 32d4174083 Merge branch 'timeout' of https://github.com/lcybo/canal into timeout 2018-02-11 00:16:07 +08:00
Chuanyi Li bb0f3b3a7e [MI] Use mysql master heartbeat to detect phycial tcp connection
failure.
2018-02-11 00:10:16 +08:00
Chuanyi Li dc0010823f [MI] Use mysql master heartbeat to detect phycial tcp connection
failure.
2018-02-10 23:42:06 +08:00
agapple b1431f8827 Merge pull request #521 from lcybo/monitor
修改InstanceConfig未生成或已销毁时造成ServerRunningMonitor running状态与实际不一致的问题
2018-02-10 11:22:48 +08:00
agapple b9e3629459 Merge pull request #522 from lulu2panpan/lulu2panpan-patch-1
修复死锁bug
2018-02-10 11:18:22 +08:00
agapple 31ed4efe3a Merge pull request #497 from lcybo/master
通信模块log小优化
2018-02-10 11:15:44 +08:00
Chuanyi Li 6da1507405 Rollback running state and znode when start failed. 2018-02-06 20:54:33 +08:00
WU Jianqiang 0b82ee8d10 Merge pull request #1 from alibaba/master
pull & merge
2018-02-02 16:13:21 +08:00
Chuanyi Li 00890f87e5 Communication log optimization. 2018-01-18 21:11:46 +08:00
agapple 91ac0e30aa fixed issue #483 , support show slave hosts 2018-01-10 14:35:26 +08:00
agapple 9316a3df6d fixed issue #482 , retry for getTableMetaByDB 2018-01-10 13:49:25 +08:00
agapple 2d31e6bf91 Merge pull request #487 from jasonhx140/master
dump connection is disconnected
2018-01-10 13:41:30 +08:00
jason huang a965d2cd04 dump connection is disconnected
dump connection is disconnected since SocketChannel.cache is overflow.
2018-01-09 18:25:51 +08:00
jason huang 93be9d1909 dump connection disconnected
dump connection will be disconnected since SocketChannel.cache is overflow.
2018-01-09 18:21:14 +08:00
jason huang 7cf9375ac6 Merge pull request #2 from alibaba/master
merge new release
2018-01-09 18:10:19 +08:00
agapple af289cd9fb fixed compiler error 2018-01-05 19:09:46 +08:00
agapple d1f1c0716b Merge pull request #473 from jasonhx140/master
connect mysql server failed when reconnect mysql server frequently
2018-01-05 18:55:18 +08:00
jason huang 0c7aed197a cannot build mysql connection.
when invoke connect(), it will return a ChannelFuture instance, and then the "addListener" method is invoked by biz thread, however, "channelRead" method in BusinessHandler is executed by netty I/O thread.

ChannelFutureListener -> operationComplete() cannot guarantee that which is executed before than BusinessHandler -> channelRead() method.

reproduce it :
set below jvm parameter:
-Dio.netty.eventLoopThreads=1

It will caused new connection is timeout when AbstractEventParser -> start() -> parseThread ->erosaConnection.reconnect() is invoked.
2018-01-02 23:29:15 +08:00
jason huang 325d16bc66 Merge pull request #1 from alibaba/master
merge latest version
2018-01-02 23:03:52 +08:00
agapple 3104b4cc24 fixed issue #449 , optimize findTransactionBeginPosition 2017-12-27 17:34:46 +08:00
agapple 9595e6abbd fixed semi sync format & refactor 2017-12-27 16:53:09 +08:00
agapple c6f4de9f88 Merge pull request #455 from lucifax301/master
mysql semi support and mariadb gtid parse
2017-12-27 16:36:29 +08:00
lucifax301 d66839c965 mysql semi support and mariadb gtid parse 2017-12-18 14:10:53 +08:00
agapple ac773009f8 Merge pull request #447 from zhmz1326/master
解决destination无限连接等待的bug
2017-12-13 10:31:48 +08:00
zhengmingzhi 8b03f172ed 解决destination无限连接等待的bug 2017-12-12 19:17:53 +08:00
agapple 5e206e4f7e fixed issue #440 , optimize find position 2017-12-10 23:05:28 +08:00
agapple 448fc8bcde fixed issue #440 2017-12-10 22:27:14 +08:00
agapple c60b04424b fixed issue #440 , optime find position 2017-12-10 22:24:24 +08:00
agapple 5aed505429 fixed issue #440, enableTsdb 2017-12-08 23:21:40 +08:00
agapple 912f5db2c7 fixed issue #440, for null position 2017-12-08 23:20:15 +08:00
agapple 8b57b9a0a5 fixed issue #442 , compareTableMetaDbAndMemory 2017-12-08 22:54:22 +08:00
agapple d1c83ea7bb fixed fastjson autotype 2017-12-05 19:46:49 +08:00
agapple 7d485efd71 Update README.md 2017-12-05 17:01:15 +08:00
agapple 20942533e6 [maven-release-plugin] prepare for next development iteration 2017-12-04 14:43:29 +08:00
agapple 6b2d80a145 [maven-release-plugin] prepare release canal-1.0.25 2017-12-04 14:43:16 +08:00
agapple 01ef15223c fixed issue #415, ignore flush privileges 2017-12-04 13:19:58 +08:00
agapple da3290c381 Merge pull request #424 from lcybo/master
1.0.23里面修复的KILL DUMP exception问题在(#334)里面被覆盖回去了
2017-12-03 21:36:33 -06:00
agapple a9284b1b39 fixed compiler error 2017-12-04 11:35:02 +08:00
Chuanyi Li 3409472eb9 kill CONNECTION exception fix revert. 2017-11-23 22:25:47 +08:00
agapple 5348994b4a fixed alisql heartbeat 2017-11-15 10:42:58 +08:00
agapple a3b9f6f1eb fixed NPE 2017-11-14 23:44:15 +08:00
agapple 2dc019709b fixed quota ddl 2017-11-14 23:31:46 +08:00
agapple c56f58deae upgrade druid to 1.1.5 2017-11-01 20:29:47 +08:00
lulu2panpan fa059804ce 修复死锁bug
当通过zk-cursor启动的时候,第一个Event类型是TransactionEnd,那么txState变为2的触发条件不仅仅有ddl和dcl,clear方法中isTransactionEnd的判断应该放到txState.intValue() == 2的后面,否则将导致死锁
2017-10-19 10:50:32 +08:00
agapple 8d6f19cc33 fixed default value 2017-10-15 16:19:02 +08:00
agapple 1463463f1e fixed data 2017-10-15 16:08:29 +08:00
agapple 77342663e3 fixed rds binlog local 2017-10-15 15:55:30 +08:00
agapple e01e48ff65 fixed h2 support 2017-10-14 01:14:13 +08:00
agapple 4f00f5ea8d fixed tsdb test 2017-10-14 00:12:43 +08:00
agapple 19707f92d4 merge tsdb meta support and refactor 2017-09-30 23:21:17 +08:00
agapple 3930ea0f9b Merge pull request #393 from KaimingWan/table_meta_support
Table meta support
2017-09-30 15:48:53 +08:00
wanshao 676cf47987 Merge branch 'master' into table_meta_support
# Conflicts:
#	parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java
2017-09-30 15:47:02 +08:00
agapple 788c1d9b84 fixed issue #360 , socket pool bug 2017-09-23 11:50:25 +08:00
agapple 84a9aeb46c Merge pull request #374 from alexandnpu/feature/avoid_use_multiline_exception_directly
try to avoid use multiline exception directly
2017-09-23 11:11:27 +08:00
kaimingwan e8da1c884b table meta support 2017-09-22 22:01:57 +08:00
alex.zheng 041cbf04c7 add uncaughtexception handler 2017-09-06 12:07:50 +08:00
alex.zheng a1cf292baa try to avoid use multiline exception directly 2017-08-29 16:05:12 +08:00
agapple 67436b2bc4 fixed issue #330 & #350 , json length & escapse 2017-08-03 21:15:09 +08:00
agapple ca350a79bd Merge pull request #343 from toruneko/bugfix-when-filterqueryddl
Bugfix when filterqueryddl
2017-08-02 00:20:25 +08:00
jianhao.dai 214e404e1b 挪到最前面 2017-07-20 11:37:24 +08:00
jianhao.dai 34bcfbc3cb 不管有没有filter,都应该对meta cache更新一下 2017-07-20 11:35:19 +08:00
HaiDai 402245dbbf Merge pull request #2 from alibaba/master
merge master
2017-07-20 11:29:50 +08:00
agapple 2cebfa04dd fixed test 2017-07-05 12:48:52 +08:00
agapple 62e8ff6ec8 fixed format 2017-07-05 12:46:32 +08:00
agapple b265bf4669 Merge pull request #319 from day6/fixbug-clientTimeout
fixbug: soTimeout doesn't work.
2017-07-05 12:44:50 +08:00
agapple 989a69e7be fixed format & import 2017-07-05 12:41:11 +08:00
agapple 780b4f715b Merge pull request #333 from jasonhx140/master
fix bug:异常网络连接导致canal server 无法关闭
2017-07-05 12:38:33 +08:00
agapple 8e88dc87f7 fixed https://github.com/alibaba/canal/pull/334 2017-07-05 12:35:42 +08:00
agapple 867d6607d4 Merge pull request #334 from luoyaogui/master
针对使用netty取代JDK的socketchannel实现的优化
2017-07-04 21:50:07 +08:00
agapple 22e3d2f3f1 Merge branch 'master' into master 2017-07-04 21:49:22 +08:00
agapple ecdd619da7 Merge pull request #331 from alexandnpu/feature/upgrade_zkclient
update zkclient to 0.10
2017-07-04 21:38:30 +08:00
luoyaogui 9ba93bae5f Update DirectLogFetcher.java 2017-06-27 15:08:34 +08:00
luoyaogui 0b2f37a272 Update MysqlConnection.java 2017-06-27 15:07:52 +08:00
luoyaogui 0bd4a1146c Update DirectLogFetcherTest.java 2017-06-27 15:07:07 +08:00
luoyaogui 3dba400624 Update MysqlUpdateExecutor.java 2017-06-27 15:05:33 +08:00
luoyaogui 523130cf8b Update MysqlQueryExecutor.java 2017-06-27 15:05:00 +08:00
luoyaogui 525a00ea17 Update MysqlConnector.java 2017-06-27 15:04:39 +08:00
luoyaogui b6090da0e5 Update PacketManager.java 2017-06-27 15:03:53 +08:00
luoyaogui 9ea88c7863 Update SocketChannelPool.java 2017-06-27 15:03:16 +08:00
luoyaogui 48c108806a Update SocketChannel.java 2017-06-27 15:02:45 +08:00
Jason Huang 8600e9825b fix bug:
现实运用中出现:client已经不存在但server端的连接仍然是存活状态。
在此种网络异常状态时,stop canal server会一直hang住。
原因:异常网络连接由于无数据传输,导致 io worker线程一直loop selector而无法退出。

解决方案:1. 设置keepalive 由os 监控。
        2. 设置netty idle 机制,定时检测并主动close socket。(fix 时间单位bug)
        3. 每次新创建socket channel时存储在一个容器内,在stop canal
server时,从容器内主动释放所有socket channel。
2017-06-26 19:24:15 +08:00
alex.zheng 29815c41b0 make zookeeperx work 2017-06-23 20:16:25 +08:00
alex.zheng 717e2cb7f3 update zkclient to 0.10 2017-06-23 14:48:28 +08:00
agapple 3e5b3e1467 fixed issue #321 , process TableIdNotFoundException 2017-06-22 13:37:11 +08:00
baiju a46ac3b814 fixbug: soTimeout doesn't work. 2017-06-10 10:07:23 +08:00
agapple f6c2eb8963 fixed issue #296 , NPE 2017-05-11 14:05:58 +08:00
agapple 07b979f242 Merge pull request #293 from MarkLinHz/master
fix bug of repeat log manager
2017-04-08 17:02:53 +08:00
yinxiu 6b20959a2d fix bug of repeat log manager 2017-04-06 20:13:59 +08:00
agapple fdd40224d5 fixed format & import 2017-04-01 22:20:00 +08:00
agapple a6604978ed Merge pull request #291 from MarkLinHz/master
重构LogPositionManager并适配XML配置
2017-04-01 22:06:54 +08:00
agapple 4d624cf184 [maven-release-plugin] prepare for next development iteration 2017-04-01 21:18:31 +08:00
agapple 7e026f2495 [maven-release-plugin] prepare release canal-1.0.24 2017-04-01 21:18:11 +08:00
agapple 2e3796cb07 fixed format 2017-04-01 21:04:21 +08:00
agapple b2a5f482ee fixed issue #284 , add slave_uuid 2017-04-01 21:01:07 +08:00
yinxiu a78622ae02 rename log manager module and ajust xml 2017-03-28 20:33:14 +08:00
agapple e5444ae658 Merge pull request #259 from GitHub-BruceTong/dev
fix bug for #202(canal.instance.filter.regex 修改后未生效)
2017-03-28 09:49:18 +08:00
agapple 73627df082 fixed issue #287 , support chinese & empty string valuewq 2017-03-28 00:25:11 +08:00
yinxiu c0f2d274d7 refactoring log position manager module 2017-03-18 11:41:56 +08:00
agapple 2eb05b5934 fixed compiler error 2017-03-17 21:15:33 +08:00
agapple 83689ee519 fixed issue #274 , support mysql 5.7.17 2017-03-17 20:36:40 +08:00
agapple d78b389737 revert pull request https://github.com/alibaba/canal/pull/256 2017-03-17 20:00:13 +08:00
agapple 20d8ac67dc fixed issue #280 , refactor MysqlQueryExecutor use IOException 2017-03-17 19:40:12 +08:00
agapple 19a56fb77e Merge pull request #282 from sun7059/patch-1
Update JsonUtils.java
2017-03-17 19:11:25 +08:00
agapple c629e8c899 Merge pull request #283 from keyganker/master
upgrade fastjson for security
2017-03-17 19:10:58 +08:00
fengyong 75a5a4d7df 升级fastjson 2017-03-16 20:25:21 +08:00
孙鹏 045a2e5a50 Update JsonUtils.java
修复升级fastjson到1.2.28后JSONUtils报错的问题
2017-03-16 20:01:42 +08:00
agapple 11d0712291 Update README.md 2017-02-07 19:50:22 +08:00
GitHub-BruceTong 03ff9512dd fix bug for #202(canal.instance.filter.regex 修改后未生效) 2017-01-22 15:52:04 +08:00
agapple 0ee2e767d2 fixed stop failed 2017-01-19 13:22:40 +08:00
agapple ff97dcbbc9 format & import 2017-01-19 13:07:53 +08:00
agapple 92c1a51726 Merge pull request #256 from luoyaogui/master
使用netty重构socketChannel(jdk内置),修改了PacketManager实现,以及其他相关引用的类。
2017-01-19 12:47:23 +08:00
agapple 36216e63ea Merge branch 'master' into master 2017-01-19 12:46:41 +08:00
agapple db4fc68a55 fixed issue #257 , heartBeatTimerTask stop bugfix 2017-01-19 12:34:05 +08:00
agapple eef58005d9 fixed format 2017-01-12 11:31:43 +08:00
agapple 8141ddc4e8 Merge pull request #250 from toruneko/fix-ha-switch
[BUGFIX] 在MySQL5.6做主备库切换时出现的filename乱码修复
2017-01-12 11:30:39 +08:00
agapple 004f26465f Merge pull request #248 from z070204z/patch-1
修复mysql5.6以下报java.lang.NullPointerException
2017-01-12 11:29:52 +08:00
jianhao.dai 5dc62bbda8 seek method also need load checksum 2017-01-04 11:14:01 +08:00
z070204z f45f1ac74f Update MysqlConnection.java
修复mysql5.6以下报java.lang.NullPointerException
2017-01-03 18:03:42 +08:00
luoyaogui 5c11c71783 使用netty重构socketChannel(jdk内置),修改了PacketManager实现,以及其他相关引用的类。 2017-01-03 14:18:58 +08:00
agapple f745c698e9 [maven-release-plugin] prepare for next development iteration 2016-12-30 14:57:19 +08:00
agapple 5f4bbab722 [maven-release-plugin] prepare release canal-1.0.23 2016-12-30 14:57:07 +08:00
agapple a483f03711 fixed #227 , loadBinlogChecksum NPE 2016-12-30 14:51:22 +08:00
agapple a1207060bf format 2016-12-30 14:32:52 +08:00
agapple 5055fdb556 Merge pull request #245 from dangxia/master
issues#244
2016-12-30 14:30:00 +08:00
XuehuiHe a69b69c724 issues#244 2016-12-30 11:39:02 +08:00
agapple 90e6d34fb4 Merge pull request #243 from deeplinux/master
原来的example内的startup.bat脚本直接运行时报语法错误
2016-12-29 13:45:32 +08:00
deeplinux 907ecd43e4 修改批处理中的!=为NEQ 2016-12-28 08:06:18 +08:00
deeplinux c57a921a1a 修正批处理中的语法错误 2016-12-27 17:57:07 +08:00
agapple 83fee4ffbb Merge pull request #231 from alexwangx/alexwang
fixed issue #227 , fixed Mysql5.1.73 throw NullPointerException
2016-12-02 18:06:36 +08:00
alexwang bc7927fe75 fixed issue #227 , fixed Mysql5.1.73 throw NullPointerException 2016-12-01 17:39:51 +08:00
agapple e67f27018c fixed support json inteAddress 2016-11-28 14:11:12 +08:00
agapple 3f38ad6f49 fixed issue #227 , fixed binlog_checksum variables 2016-11-28 14:10:57 +08:00
agapple 96dca0b9e6 fixed issue #136 , support rds no primary key && mysql.ha_health_check 2016-11-17 17:52:52 +08:00
agapple ad2a96b3f1 fixed json type cast exception 2016-11-10 11:12:35 +08:00
agapple 6284a429a0 fixed lazy 2016-10-11 21:37:39 +08:00
agapple 83d8a39d16 add ServerRunningMonitor.init 2016-10-11 19:39:55 +08:00
agapple 4898829384 Merge pull request #213 from lulu2panpan/master
修复issue #212
2016-10-11 19:25:06 +08:00
agapple 69a7e6d083 Merge pull request #208 from jianan789/master
mysql5.6.29'@@global.binlog_checksum'带单引号导致master退出,优化binlogFormat判断
2016-10-11 19:16:34 +08:00
agapple a5d6c3b747 Merge pull request #210 from xujianhai/patch-1
不好的设计
2016-10-11 19:14:22 +08:00
agapple 86378cad32 Merge pull request #209 from lcybo/master
Fix a minor fault: Canal instance reload again after startup.
2016-10-11 19:11:30 +08:00
lubiao 8626c6ee24 1、SessionHandler类中执行SUBSCRIPTION时,应该先尝试启动runningMonitor再调用embeddedServer的subscribe方法
2、embeddedServer的subscribe方法中应该首先进行checkStart操作,否则可能引起metaManager的脏启动
3、SessionHandler中增加日志
2016-10-11 18:46:30 +08:00
lubiao 350f8637c3 1、BinLogFileQueue的listBinlogFiles方法中对binlog文件名进行更严格的验证,必须以数字结尾
2、LocalBinLogConnection类中解决如下bug
	2.1第二个dump方法中增加代码"decoder.handle(LogEvent.FORMAT_DESCRIPTION_EVENT);",解决"((QueryLogEvent)event).getQuery()"语句出现乱码的问题(BEGIN后面多了4个字节),出现乱码不会进入if语句,导致找到的binlog位点不准确
	2.2第一个dump方法中,被注释的decode代码才是正确的,和DirectLogFetcher不同,此处需要构造子循环进行遍历
	2.3第一个dump方法中,将每个binlog-file的首个event设置为RotateLogEvent,否则会导致所有event的binlogFileName都是默认的mysql-bin.000001。这么改完之后,和DirectLogFetcher的行为相契合,后者在每个binlogfile的首个event就是RotateLogEvent
3、LocalBinlogEventParser中增加元数据处理的相关代码,不加的话,所有的event都不带列名,基本上没法儿使用
2016-10-11 17:28:24 +08:00
李建安 96454d4387 修复开启checksum后rotateLogEvent数据包解析时binlog name乱码 2016-10-10 18:30:09 +08:00
徐建海 8a293e4b0c Update AbstractEventParser.java 2016-10-06 19:13:50 +08:00
徐建海 006dd63646 不好的设计 2016-10-06 15:48:05 +08:00
Chuanyi Li b17c11868c Fix a minor fault: Canal instance reload again after startup. 2016-10-02 13:43:17 +08:00
李建安 0c9eecd758 mysql5.6.29'@@global.binlog_checksum'带单引号导致master退出,优化binlogFormat判断 2016-09-29 17:21:59 +08:00
agapple 5d9d5b1e44 fixed testcase 2016-09-29 11:43:35 +08:00
agapple b23e814c48 Merge pull request #203 from kevinKaiF/master
fixed issue # 201
2016-09-29 10:31:29 +08:00
zikaifeng b349b400cf 修复issue # 201 2016-09-27 16:22:48 +08:00
agapple c86fba6092 Merge pull request #199 from MarkLinHz/master
upgrade protobuf version
2016-09-19 14:03:20 +08:00
yinxiu 9742fdc9d9 upgrade protobuf version 2016-09-19 12:24:06 +08:00
agapple c99b4b8cd1 fixed NPE #200 2016-09-07 12:37:21 +08:00
agapple aa8ea96572 fixed issue #195 2016-08-26 18:25:52 +08:00
agapple f6cf88a6bd support 5.7 2016-08-04 22:53:59 +08:00
agapple 14a5762fbb support 5.7 2016-08-04 22:53:22 +08:00
agapple eda24df3fe fixed kill CONNECTION exception 2016-07-26 10:59:09 +08:00
agapple 3a5a4b06a9 [maven-release-plugin] prepare for next development iteration 2016-06-30 16:59:17 +08:00
303 changed files with 34593 additions and 11066 deletions
+2
View File
@@ -14,3 +14,5 @@ jtester.properties
.idea/
*.iml
.DS_Store
*.tar.gz
*.rpm
+19 -2
View File
@@ -5,11 +5,13 @@
<ol>
<li>canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。</li>
<li>canal消费端项目开源: Otter(分布式数据库同步系统),地址:<a href="https://github.com/alibaba/otter">https://github.com/alibaba/otter</a></li>
<li>Canal已在阿里云推出商业化版本 <a href="https://www.aliyun.com/product/dts?spm=a2c4g.11186623.cloudEssentials.80.srdwr7">数据传输服务DTS</a>, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。<a href="https://help.aliyun.com/document_detail/26592.html?spm=a2c4g.11174283.6.539.t1Y91E">DTS产品使用文档</a></li>
DTS支持阿里云RDS&DRDS的Binlog日志实时订阅,现推出首月免费体验,限时限量,<a href="https://common-buy.aliyun.com/?commodityCode=dtspre&request=%7b%22dts_function%22%3a%22data_subscribe%22%7d">立即体验>>></a>
</ol>
<h1>背景</h1>
<p style="font-size: 14px;"> 早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&amp;消费的业务,从此开启了一段新纪元。</p>
<p style="font-size: 14px;"> ps. 目前内部版本已经支持mysql和oracle部分版本的日志解析,当前的canal开源版本支持5.6及以下的版本(阿里内部mysql 5.6.10, mysql 5.5.18和5.1.40/48)</p>
<p style="font-size: 14px;"> ps. 目前内部版本已经支持mysql和oracle部分版本的日志解析,当前的canal开源版本支持5.7及以下的版本(阿里内部mysql 5.7.13, 5.6.10, mysql 5.5.18和5.1.40/48)</p>
<p style="font-size: 14px;"> </p>
<p style="font-size: 14px;">基于日志增量订阅&amp;消费支持的业务:</p>
<ol style="font-size: 14px;">
@@ -36,7 +38,7 @@
<li>slave重做中继日志中的事件,将改变反映它自己的数据。</li>
</ol>
<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>
<ol>
<li>canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议</li>
@@ -73,6 +75,14 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
<li>阿里巴巴去Oracle数据迁移同步工具(目标支持MySQL/DRDS)<a href="http://github.com/alibaba/yugong">http://github.com/alibaba/yugong</a></li>
</ol>
<h1>相关产品</h1>
<ol>
<li><a href="https://www.aliyun.com/product/drds?spm=5176.55326.cloudEssentials.71.69fd227dRPZj9K">阿里云分布式数据库DRDS</a></li>
<li><a href="https://www.aliyun.com/product/dts?spm=5176.7947010.cloudEssentials.80.33f734f4JOAxSP">阿里云数据传输服务DTS</a></li>
<li><a href="https://www.aliyun.com/product/dbs?spm=5176.54487.cloudEssentials.83.34b851a8GmVZg6">阿里云数据库备份服务DBS</a></li>
<li><a href="https://www.aliyun.com/product/dms?spm=5176.169464.cloudEssentials.81.2e1066feC1sBBL">阿里云数据管理服务DMS</a></li>
</ol>
<h1>问题反馈</h1>
<ol>
<li>qq交流群: 161559791 </li>
@@ -80,3 +90,10 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
<li>新浪微博: agapple0002 </li>
<li>报告issue<a href="https://github.com/alibaba/canal/issues">issues</a></li>
</ol>
<pre>
【招聘】阿里巴巴中间件团队招聘JAVA高级工程师
岗位主要为技术型内容(非业务部门),阿里中间件整个体系对于未来想在技术上有所沉淀的同学还是非常有帮助的
工作地点:杭州、北京均可. ps. 阿里待遇向来都是不错的,有意者可以QQ、微博私聊.
具体招聘内容:https://job.alibaba.com/zhaopin/position_detail.htm?positionId=32666
</pre>
+2 -2
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId>
<version>1.0.22</version>
<version>1.0.26-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.alibaba.otter</groupId>
@@ -69,7 +69,7 @@
<links>
<link>https://github.com/alibaba/canal</link>
</links>
<outputDirectory>${project.build.directory}/apidocs/apidocs/${pom.version}</outputDirectory>
<outputDirectory>${project.build.directory}/apidocs/apidocs/${project.version}</outputDirectory>
</configuration>
</plugin>
<plugin>
@@ -150,4 +150,10 @@ public interface CanalConnector {
*/
void rollback() throws CanalClientException;
/**
* 中断的阻塞,用于优雅停止client
*
* @throws CanalClientException
*/
void stopRunning() throws CanalClientException;
}
@@ -29,7 +29,8 @@ public class CanalConnectors {
public static CanalConnector newSingleConnector(SocketAddress address, String destination, String username,
String password) {
SimpleCanalConnector canalConnector = new SimpleCanalConnector(address, username, password, destination);
canalConnector.setSoTimeout(30 * 1000);
canalConnector.setSoTimeout(60 * 1000);
canalConnector.setIdleTimeout(60 * 60 * 1000);
return canalConnector;
}
@@ -48,7 +49,8 @@ public class CanalConnectors {
password,
destination,
new SimpleNodeAccessStrategy(addresses));
canalConnector.setSoTimeout(30 * 1000);
canalConnector.setSoTimeout(60 * 1000);
canalConnector.setIdleTimeout(60 * 60 * 1000);
return canalConnector;
}
@@ -67,7 +69,8 @@ public class CanalConnectors {
password,
destination,
new ClusterNodeAccessStrategy(destination, ZkClientx.getZkClient(zkServers)));
canalConnector.setSoTimeout(30 * 1000);
canalConnector.setSoTimeout(60 * 1000);
canalConnector.setIdleTimeout(60 * 60 * 1000);
return canalConnector;
}
}
@@ -3,7 +3,6 @@ package com.alibaba.otter.canal.client.impl;
import java.net.SocketAddress;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -23,8 +22,9 @@ public class ClusterCanalConnector implements CanalConnector {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private String username;
private String password;
private int soTimeout = 10000;
private int retryTimes = 3;
private int soTimeout = 60000;
private int idleTimeout = 60 * 60 * 1000;
private int retryTimes = 3; // 设置-1时可以subscribe阻塞等待时优雅停机
private int retryInterval = 5000; // 重试的时间间隔,默认5秒
private CanalNodeAccessStrategy accessStrategy;
private SimpleCanalConnector currentConnector;
@@ -53,6 +53,7 @@ public class ClusterCanalConnector implements CanalConnector {
};
currentConnector.setSoTimeout(soTimeout);
currentConnector.setIdleTimeout(idleTimeout);
if (filter != null) {
currentConnector.setFilter(filter);
}
@@ -107,12 +108,17 @@ public class ClusterCanalConnector implements CanalConnector {
this.filter = filter;
return;
} catch (Throwable t) {
logger.warn("something goes wrong when subscribing from server:{}\n{}",
currentConnector.getAddress(),
ExceptionUtils.getFullStackTrace(t));
times++;
restart();
logger.info("restart the connector for next round retry.");
if (retryTimes == -1 && t.getCause() instanceof InterruptedException) {
logger.info("block waiting interrupted by other thread.");
return;
} else {
logger.warn(String.format("something goes wrong when subscribing from server: %s",
currentConnector != null ? currentConnector.getAddress() : "null"), t);
times++;
restart();
logger.info("restart the connector for next round retry.");
}
}
}
@@ -126,9 +132,8 @@ public class ClusterCanalConnector implements CanalConnector {
currentConnector.unsubscribe();
return;
} catch (Throwable t) {
logger.warn("something goes wrong when unsubscribing from server:{}\n{}",
currentConnector.getAddress(),
ExceptionUtils.getFullStackTrace(t));
logger.warn(String.format("something goes wrong when unsubscribing from server:%s",
currentConnector != null ? currentConnector.getAddress() : "null"), t);
times++;
restart();
logger.info("restart the connector for next round retry.");
@@ -144,9 +149,8 @@ public class ClusterCanalConnector implements CanalConnector {
Message msg = currentConnector.get(batchSize);
return msg;
} catch (Throwable t) {
logger.warn("something goes wrong when getting data from server:{}\n{}",
currentConnector.getAddress(),
ExceptionUtils.getFullStackTrace(t));
logger.warn(String.format("something goes wrong when getting data from server:%s",
currentConnector != null ? currentConnector.getAddress() : "null"), t);
times++;
restart();
logger.info("restart the connector for next round retry.");
@@ -162,9 +166,8 @@ public class ClusterCanalConnector implements CanalConnector {
Message msg = currentConnector.get(batchSize, timeout, unit);
return msg;
} catch (Throwable t) {
logger.warn("something goes wrong when getting data from server:{}\n{}",
currentConnector.getAddress(),
ExceptionUtils.getFullStackTrace(t));
logger.warn(String.format("something goes wrong when getting data from server:%s",
currentConnector != null ? currentConnector.getAddress() : "null"), t);
times++;
restart();
logger.info("restart the connector for next round retry.");
@@ -180,9 +183,8 @@ public class ClusterCanalConnector implements CanalConnector {
Message msg = currentConnector.getWithoutAck(batchSize);
return msg;
} catch (Throwable t) {
logger.warn("something goes wrong when getWithoutAck data from server:{}\n{}",
currentConnector.getAddress(),
ExceptionUtils.getFullStackTrace(t));
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
currentConnector != null ? currentConnector.getAddress() : "null"), t);
times++;
restart();
logger.info("restart the connector for next round retry.");
@@ -198,9 +200,8 @@ public class ClusterCanalConnector implements CanalConnector {
Message msg = currentConnector.getWithoutAck(batchSize, timeout, unit);
return msg;
} catch (Throwable t) {
logger.warn("something goes wrong when getWithoutAck data from server:{}\n{}",
currentConnector.getAddress(),
ExceptionUtils.getFullStackTrace(t));
logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
currentConnector != null ? currentConnector.getAddress() : "null"), t);
times++;
restart();
logger.info("restart the connector for next round retry.");
@@ -216,9 +217,9 @@ public class ClusterCanalConnector implements CanalConnector {
currentConnector.rollback(batchId);
return;
} catch (Throwable t) {
logger.warn("something goes wrong when rollbacking data from server:{}\n{}",
currentConnector.getAddress(),
ExceptionUtils.getFullStackTrace(t));
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
currentConnector != null ? currentConnector.getAddress() : "null"),
t);
times++;
restart();
logger.info("restart the connector for next round retry.");
@@ -234,9 +235,9 @@ public class ClusterCanalConnector implements CanalConnector {
currentConnector.rollback();
return;
} catch (Throwable t) {
logger.warn("something goes wrong when rollbacking data from server:{}\n{}",
currentConnector.getAddress(),
ExceptionUtils.getFullStackTrace(t));
logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
currentConnector != null ? currentConnector.getAddress() : "null"),
t);
times++;
restart();
logger.info("restart the connector for next round retry.");
@@ -253,9 +254,9 @@ public class ClusterCanalConnector implements CanalConnector {
currentConnector.ack(batchId);
return;
} catch (Throwable t) {
logger.warn("something goes wrong when acking data from server:{}\n{}",
currentConnector.getAddress(),
ExceptionUtils.getFullStackTrace(t));
logger.warn(String.format("something goes wrong when acking data from server:%s",
currentConnector != null ? currentConnector.getAddress() : "null"),
t);
times++;
restart();
logger.info("restart the connector for next round retry.");
@@ -302,6 +303,14 @@ public class ClusterCanalConnector implements CanalConnector {
this.soTimeout = soTimeout;
}
public int getIdleTimeout() {
return idleTimeout;
}
public void setIdleTimeout(int idleTimeout) {
this.idleTimeout = idleTimeout;
}
public int getRetryTimes() {
return retryTimes;
}
@@ -330,4 +339,8 @@ public class ClusterCanalConnector implements CanalConnector {
return currentConnector;
}
public void stopRunning() {
currentConnector.stopRunning();
}
}
@@ -114,5 +114,4 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
return zkClient;
}
}
@@ -5,7 +5,11 @@ import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channel;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -37,11 +41,10 @@ import com.alibaba.otter.canal.protocol.ClientIdentity;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
/**
* 基于{@linkplain CanalServerWithNetty}定义的网络协议接口,对于canal数据进行get/rollback/ack等操作
*
*
* @author jianghang 2012-10-24 下午05:37:20
* @version 1.0.0
*/
@@ -52,11 +55,14 @@ public class SimpleCanalConnector implements CanalConnector {
private String username;
private String password;
private int soTimeout = 60000; // milliseconds
private int idleTimeout = 60 * 60 * 1000; // client和server之间的空闲链接超时的时间,默认为1小时
private String filter; // 记录上一次的filter提交值,便于自动重试时提交
private final ByteBuffer readHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
private final ByteBuffer writeHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
private SocketChannel channel;
private ReadableByteChannel readableChannel;
private WritableByteChannel writableChannel;
private List<Compression> supportedCompressions = new ArrayList<Compression>();
private ClientIdentity clientIdentity;
private ClientRunningMonitor runningMonitor; // 运行控制
@@ -65,21 +71,29 @@ public class SimpleCanalConnector implements CanalConnector {
private volatile boolean connected = false; // 代表connected是否已正常执行,因为有HA,不代表在工作中
private boolean rollbackOnConnect = true; // 是否在connect链接成功后,自动执行rollback操作
private boolean rollbackOnDisConnect = false; // 是否在connect链接成功后,自动执行rollback操作
private boolean lazyParseEntry = false; // 是否自动化解析Entry对象,如果考虑最大化性能可以延后解析
// 读写数据分别使用不同的锁进行控制,减小锁粒度,读也需要排他锁,并发度容易造成数据包混乱,反序列化失败
private Object readDataLock = new Object();
private Object writeDataLock = new Object();
private volatile boolean running = false;
public SimpleCanalConnector(SocketAddress address, String username, String password, String destination){
this(address, username, password, destination, 60000);
this(address, username, password, destination, 60000, 60 * 60 * 1000);
}
public SimpleCanalConnector(SocketAddress address, String username, String password, String destination,
int soTimeout){
this(address, username, password, destination, soTimeout, 60 * 60 * 1000);
}
public SimpleCanalConnector(SocketAddress address, String username, String password, String destination,
int soTimeout, int idleTimeout){
this.address = address;
this.username = username;
this.password = password;
this.soTimeout = soTimeout;
this.idleTimeout = idleTimeout;
this.clientIdentity = new ClientIdentity(destination, (short) 1001);
}
@@ -94,6 +108,9 @@ public class SimpleCanalConnector implements CanalConnector {
}
} else {
waitClientRunning();
if (!running) {
return;
}
doConnect();
if (filter != null) { // 如果存在条件,说明是自动切换,基于上一次的条件订阅一次
subscribe(filter);
@@ -130,7 +147,9 @@ public class SimpleCanalConnector implements CanalConnector {
address = getNextAddress();
}
channel.connect(address);
Packet p = Packet.parseFrom(readNextPacket(channel));
readableChannel = Channels.newChannel(channel.socket().getInputStream());
writableChannel = Channels.newChannel(channel.socket().getOutputStream());
Packet p = Packet.parseFrom(readNextPacket());
if (p.getVersion() != 1) {
throw new CanalClientException("unsupported version at this client.");
}
@@ -144,17 +163,17 @@ public class SimpleCanalConnector implements CanalConnector {
//
ClientAuth ca = ClientAuth.newBuilder()
.setUsername(username != null ? username : "")
.setNetReadTimeout(soTimeout)
.setNetWriteTimeout(soTimeout)
.setPassword(ByteString.copyFromUtf8(password != null ? password : ""))
.setNetReadTimeout(idleTimeout)
.setNetWriteTimeout(idleTimeout)
.build();
writeWithHeader(channel,
Packet.newBuilder()
.setType(PacketType.CLIENTAUTHENTICATION)
.setBody(ca.toByteString())
.build()
.toByteArray());
writeWithHeader(Packet.newBuilder()
.setType(PacketType.CLIENTAUTHENTICATION)
.setBody(ca.toByteString())
.build()
.toByteArray());
//
Packet ack = Packet.parseFrom(readNextPacket(channel));
Packet ack = Packet.parseFrom(readNextPacket());
if (ack.getType() != PacketType.ACK) {
throw new CanalClientException("unexpected packet type when ack is expected");
}
@@ -173,36 +192,50 @@ public class SimpleCanalConnector implements CanalConnector {
}
private void doDisconnnect() throws CanalClientException {
if (readableChannel != null) {
quietlyClose(readableChannel);
readableChannel = null;
}
if (writableChannel != null) {
quietlyClose(writableChannel);
writableChannel = null;
}
if (channel != null) {
try {
channel.close();
} catch (IOException e) {
logger.warn("exception on closing channel:{} \n {}", channel, e);
}
quietlyClose(channel);
channel = null;
}
}
private void quietlyClose(Channel channel) {
try {
channel.close();
} catch (IOException e) {
logger.warn("exception on closing channel:{} \n {}", channel, e);
}
}
public void subscribe() throws CanalClientException {
subscribe(""); // 传递空字符即可
}
public void subscribe(String filter) throws CanalClientException {
waitClientRunning();
if (!running) {
return;
}
try {
writeWithHeader(channel,
Packet.newBuilder()
.setType(PacketType.SUBSCRIPTION)
.setBody(Sub.newBuilder()
.setDestination(clientIdentity.getDestination())
.setClientId(String.valueOf(clientIdentity.getClientId()))
.setFilter(filter != null ? filter : "")
.build()
.toByteString())
writeWithHeader(Packet.newBuilder()
.setType(PacketType.SUBSCRIPTION)
.setBody(Sub.newBuilder()
.setDestination(clientIdentity.getDestination())
.setClientId(String.valueOf(clientIdentity.getClientId()))
.setFilter(filter != null ? filter : "")
.build()
.toByteArray());
.toByteString())
.build()
.toByteArray());
//
Packet p = Packet.parseFrom(readNextPacket(channel));
Packet p = Packet.parseFrom(readNextPacket());
Ack ack = Ack.parseFrom(p.getBody());
if (ack.getErrorCode() > 0) {
throw new CanalClientException("failed to subscribe with reason: " + ack.getErrorMessage());
@@ -216,19 +249,21 @@ public class SimpleCanalConnector implements CanalConnector {
public void unsubscribe() throws CanalClientException {
waitClientRunning();
if (!running) {
return;
}
try {
writeWithHeader(channel,
Packet.newBuilder()
.setType(PacketType.UNSUBSCRIPTION)
.setBody(Unsub.newBuilder()
.setDestination(clientIdentity.getDestination())
.setClientId(String.valueOf(clientIdentity.getClientId()))
.build()
.toByteString())
writeWithHeader(Packet.newBuilder()
.setType(PacketType.UNSUBSCRIPTION)
.setBody(Unsub.newBuilder()
.setDestination(clientIdentity.getDestination())
.setClientId(String.valueOf(clientIdentity.getClientId()))
.build()
.toByteArray());
.toByteString())
.build()
.toByteArray());
//
Packet p = Packet.parseFrom(readNextPacket(channel));
Packet p = Packet.parseFrom(readNextPacket());
Ack ack = Ack.parseFrom(p.getBody());
if (ack.getErrorCode() > 0) {
throw new CanalClientException("failed to unSubscribe with reason: " + ack.getErrorMessage());
@@ -254,6 +289,9 @@ public class SimpleCanalConnector implements CanalConnector {
public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
waitClientRunning();
if (!running) {
return null;
}
try {
int size = (batchSize <= 0) ? 1000 : batchSize;
long time = (timeout == null || timeout < 0) ? -1 : timeout; // -1代表不做timeout控制
@@ -261,29 +299,28 @@ public class SimpleCanalConnector implements CanalConnector {
unit = TimeUnit.MILLISECONDS;
}
writeWithHeader(channel,
Packet.newBuilder()
.setType(PacketType.GET)
.setBody(Get.newBuilder()
.setAutoAck(false)
.setDestination(clientIdentity.getDestination())
.setClientId(String.valueOf(clientIdentity.getClientId()))
.setFetchSize(size)
.setTimeout(time)
.setUnit(unit.ordinal())
.build()
.toByteString())
writeWithHeader(Packet.newBuilder()
.setType(PacketType.GET)
.setBody(Get.newBuilder()
.setAutoAck(false)
.setDestination(clientIdentity.getDestination())
.setClientId(String.valueOf(clientIdentity.getClientId()))
.setFetchSize(size)
.setTimeout(time)
.setUnit(unit.ordinal())
.build()
.toByteArray());
.toByteString())
.build()
.toByteArray());
return receiveMessages();
} catch (IOException e) {
throw new CanalClientException(e);
}
}
private Message receiveMessages() throws InvalidProtocolBufferException, IOException {
Packet p = Packet.parseFrom(readNextPacket(channel));
private Message receiveMessages() throws IOException {
byte[] data = readNextPacket();
Packet p = Packet.parseFrom(data);
switch (p.getType()) {
case MESSAGES: {
if (!p.getCompression().equals(Compression.NONE)) {
@@ -292,8 +329,13 @@ public class SimpleCanalConnector implements CanalConnector {
Messages messages = Messages.parseFrom(p.getBody());
Message result = new Message(messages.getBatchId());
for (ByteString byteString : messages.getMessagesList()) {
result.addEntry(Entry.parseFrom(byteString));
if (lazyParseEntry) {
// byteString
result.setRawEntries(messages.getMessagesList());
} else {
for (ByteString byteString : messages.getMessagesList()) {
result.addEntry(Entry.parseFrom(byteString));
}
}
return result;
}
@@ -309,13 +351,16 @@ public class SimpleCanalConnector implements CanalConnector {
public void ack(long batchId) throws CanalClientException {
waitClientRunning();
if (!running) {
return;
}
ClientAck ca = ClientAck.newBuilder()
.setDestination(clientIdentity.getDestination())
.setClientId(String.valueOf(clientIdentity.getClientId()))
.setBatchId(batchId)
.build();
try {
writeWithHeader(channel, Packet.newBuilder()
writeWithHeader(Packet.newBuilder()
.setType(PacketType.CLIENTACK)
.setBody(ca.toByteString())
.build()
@@ -333,7 +378,7 @@ public class SimpleCanalConnector implements CanalConnector {
.setBatchId(batchId)
.build();
try {
writeWithHeader(channel, Packet.newBuilder()
writeWithHeader(Packet.newBuilder()
.setType(PacketType.CLIENTROLLBACK)
.setBody(ca.toByteString())
.build()
@@ -350,7 +395,15 @@ public class SimpleCanalConnector implements CanalConnector {
// ==================== helper method ====================
private void writeWithHeader(SocketChannel channel, byte[] body) throws IOException {
private void writeWithHeader(byte[] body) throws IOException {
writeWithHeader(writableChannel, body);
}
private byte[] readNextPacket() throws IOException {
return readNextPacket(readableChannel);
}
private void writeWithHeader(WritableByteChannel channel, byte[] body) throws IOException {
synchronized (writeDataLock) {
writeHeader.clear();
writeHeader.putInt(body.length);
@@ -360,7 +413,7 @@ public class SimpleCanalConnector implements CanalConnector {
}
}
private byte[] readNextPacket(SocketChannel channel) throws IOException {
private byte[] readNextPacket(ReadableByteChannel channel) throws IOException {
synchronized (readDataLock) {
readHeader.clear();
read(channel, readHeader);
@@ -371,7 +424,7 @@ public class SimpleCanalConnector implements CanalConnector {
}
}
private void read(SocketChannel channel, ByteBuffer buffer) throws IOException {
private void read(ReadableByteChannel channel, ByteBuffer buffer) throws IOException {
while (buffer.hasRemaining()) {
int r = channel.read(buffer);
if (r == -1) {
@@ -422,7 +475,11 @@ public class SimpleCanalConnector implements CanalConnector {
throw new CanalClientException("should connect first");
}
running = true;
mutex.get();// 阻塞等待
} else {
// 单机模式直接设置为running
running = true;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
@@ -462,6 +519,14 @@ public class SimpleCanalConnector implements CanalConnector {
this.soTimeout = soTimeout;
}
public int getIdleTimeout() {
return idleTimeout;
}
public void setIdleTimeout(int idleTimeout) {
this.idleTimeout = idleTimeout;
}
public void setZkClientx(ZkClientx zkClientx) {
this.zkClientx = zkClientx;
initClientRunningMonitor(this.clientIdentity);
@@ -479,4 +544,21 @@ public class SimpleCanalConnector implements CanalConnector {
this.filter = filter;
}
public boolean isLazyParseEntry() {
return lazyParseEntry;
}
public void setLazyParseEntry(boolean lazyParseEntry) {
this.lazyParseEntry = lazyParseEntry;
}
public void stopRunning() {
if (running) {
running = false; // 设置为非running状态
if (!mutex.state()) {
mutex.set(true); // 中断阻塞
}
}
}
}
@@ -98,6 +98,10 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
String path = ZookeeperPathUtils.getDestinationClientRunning(this.destination, clientData.getClientId());
zkClient.unsubscribeDataChanges(path, dataListener);
releaseRunning(); // 尝试一下release
//Fix issue #697
if (delayExector != null) {
delayExector.shutdown();
}
}
// 改动记录:
+1 -1
View File
@@ -8,7 +8,7 @@
</encoder>
</appender>
<root level="WARN">
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
+39 -8
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId>
<version>1.0.22</version>
<version>1.0.26-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>canal.common</artifactId>
@@ -11,13 +11,18 @@
<name>canal common module for otter ${project.version}</name>
<url>http://b2b-doc.alibaba-inc.com/display/opentech/Otter</url>
<dependencies>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<!-- zk -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
</dependency>
<!-- external -->
@@ -28,11 +33,10 @@
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
@@ -59,10 +63,37 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring_version}</version>
<scope>test</scope>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</dependencies>
</project>
@@ -1,12 +1,20 @@
package com.alibaba.otter.canal.common.utils;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.ObjectSerializer;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializeWriter;
import com.alibaba.fastjson.serializer.SerializerFeature;
@@ -17,6 +25,13 @@ import com.alibaba.fastjson.serializer.SerializerFeature;
*/
public class JsonUtils {
static {
SerializeConfig.getGlobalInstance().put(InetAddress.class, InetAddressSerializer.instance);
SerializeConfig.getGlobalInstance().put(Inet4Address.class, InetAddressSerializer.instance);
SerializeConfig.getGlobalInstance().put(Inet6Address.class, InetAddressSerializer.instance);
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) {
return (T) JSON.parseObject(bytes, targetClass);// 默认为UTF-8
}
@@ -70,4 +85,22 @@ public class JsonUtils {
out.close();
}
}
public static class InetAddressSerializer implements ObjectSerializer {
public static InetAddressSerializer instance = new InetAddressSerializer();
@Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features)
throws IOException {
if (object == null) {
serializer.writeNull();
return;
}
InetAddress address = (InetAddress) object;
// 优先使用name
serializer.write(address.getHostName());
}
}
}
@@ -23,6 +23,11 @@ public class NamedThreadFactory implements ThreadFactory {
public void uncaughtException(Thread t,
Throwable e) {
if (e instanceof InterruptedException
|| (e.getCause() != null && e.getCause() instanceof InterruptedException)) {
return;
}
logger.error("from " + t.getName(), e);
}
};
@@ -8,20 +8,15 @@ import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.I0Itec.zkclient.IZkConnection;
import org.I0Itec.zkclient.ZkConnection;
import org.I0Itec.zkclient.exception.ZkException;
import org.apache.commons.lang.StringUtils;
import org.apache.zookeeper.ClientCnxn;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooKeeper.States;
import org.apache.zookeeper.client.ConnectStringParser;
import org.apache.zookeeper.client.HostProvider;
import org.apache.zookeeper.client.StaticHostProvider;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
@@ -32,7 +27,7 @@ import org.springframework.util.ReflectionUtils;
* @author jianghang 2012-7-10 下午02:31:42
* @version 1.0.0
*/
public class ZooKeeperx implements IZkConnection {
public class ZooKeeperx extends ZkConnection {
private static final String SERVER_COMMA = ";";
private static final Logger logger = LoggerFactory.getLogger(ZooKeeperx.class);
@@ -40,12 +35,12 @@ public class ZooKeeperx implements IZkConnection {
private static final Field hostProviderField = ReflectionUtils.findField(ClientCnxn.class, "hostProvider");
private static final Field serverAddressesField = ReflectionUtils.findField(StaticHostProvider.class,
"serverAddresses");
private static final Field zookeeperLockField = ReflectionUtils.findField(ZkConnection.class,
"_zookeeperLock");
private static final Field zookeeperFiled = ReflectionUtils.findField(ZkConnection.class, "_zk");
private static final int DEFAULT_SESSION_TIMEOUT = 90000;
private ZooKeeper _zk = null;
private Lock _zookeeperLock = new ReentrantLock();
private final List<String> _servers;
private final List<String> _serversList;
private final int _sessionTimeOut;
public ZooKeeperx(String zkServers){
@@ -53,109 +48,55 @@ public class ZooKeeperx implements IZkConnection {
}
public ZooKeeperx(String zkServers, int sessionTimeOut){
_servers = Arrays.asList(StringUtils.split(zkServers, SERVER_COMMA));
super(zkServers, sessionTimeOut);
_serversList = Arrays.asList(StringUtils.split(this.getServers(), SERVER_COMMA));
_sessionTimeOut = sessionTimeOut;
}
@Override
public void connect(Watcher watcher) {
ReflectionUtils.makeAccessible(zookeeperLockField);
ReflectionUtils.makeAccessible(zookeeperFiled);
Lock _zookeeperLock = (ReentrantLock) ReflectionUtils.getField(zookeeperLockField, this);
ZooKeeper _zk = (ZooKeeper) ReflectionUtils.getField(zookeeperFiled, this);
_zookeeperLock.lock();
try {
if (_zk != null) {
throw new IllegalStateException("zk client has already been started");
}
String zkServers = _serversList.get(0);
try {
logger.debug("Creating new ZookKeeper instance to connect to " + _servers + ".");
_zk = new ZooKeeper(_servers.get(0), _sessionTimeOut, watcher);
logger.debug("Creating new ZookKeeper instance to connect to " + zkServers + ".");
_zk = new ZooKeeper(zkServers, _sessionTimeOut, watcher);
configMutliCluster(_zk);
ReflectionUtils.setField(zookeeperFiled, this, _zk);
} catch (IOException e) {
throw new ZkException("Unable to connect to " + _servers, e);
throw new ZkException("Unable to connect to " + zkServers, e);
}
} finally {
_zookeeperLock.unlock();
}
}
public void close() throws InterruptedException {
_zookeeperLock.lock();
try {
if (_zk != null) {
logger.debug("Closing ZooKeeper connected to " + _servers);
_zk.close();
_zk = null;
}
} finally {
_zookeeperLock.unlock();
}
}
public String create(String path, byte[] data, CreateMode mode) throws KeeperException, InterruptedException {
return _zk.create(path, data, Ids.OPEN_ACL_UNSAFE, mode);
}
public void delete(String path) throws InterruptedException, KeeperException {
_zk.delete(path, -1);
}
public boolean exists(String path, boolean watch) throws KeeperException, InterruptedException {
return _zk.exists(path, watch) != null;
}
public List<String> getChildren(final String path, final boolean watch) throws KeeperException,
InterruptedException {
return _zk.getChildren(path, watch);
}
public byte[] readData(String path, Stat stat, boolean watch) throws KeeperException, InterruptedException {
return _zk.getData(path, watch, stat);
}
public void writeData(String path, byte[] data) throws KeeperException, InterruptedException {
writeData(path, data, -1);
}
public void writeData(String path, byte[] data, int version) throws KeeperException, InterruptedException {
_zk.setData(path, data, version);
}
public States getZookeeperState() {
return _zk != null ? _zk.getState() : null;
}
public ZooKeeper getZookeeper() {
return _zk;
}
public long getCreateTime(String path) throws KeeperException, InterruptedException {
Stat stat = _zk.exists(path, false);
if (stat != null) {
return stat.getCtime();
}
return -1;
}
public String getServers() {
return StringUtils.join(_servers, SERVER_COMMA);
}
// ===============================
public void configMutliCluster(ZooKeeper zk) {
if (_servers.size() == 1) {
if (_serversList.size() == 1) {
return;
}
String cluster1 = _servers.get(0);
String cluster1 = _serversList.get(0);
try {
if (_servers.size() > 1) {
if (_serversList.size() > 1) {
// 强制的声明accessible
ReflectionUtils.makeAccessible(clientCnxnField);
ReflectionUtils.makeAccessible(hostProviderField);
ReflectionUtils.makeAccessible(serverAddressesField);
// 添加第二组集群列表
for (int i = 1; i < _servers.size(); i++) {
String cluster = _servers.get(i);
for (int i = 1; i < _serversList.size(); i++) {
String cluster = _serversList.get(i);
// 强制获取zk中的地址信息
ClientCnxn cnxn = (ClientCnxn) ReflectionUtils.getField(clientCnxnField, zk);
HostProvider hostProvider = (HostProvider) ReflectionUtils.getField(hostProviderField, cnxn);
@@ -87,18 +87,29 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
}
public void start() {
super.start();
public void init() {
processStart();
if (zkClient != null) {
// 如果需要尽可能释放instance资源,不需要监听running节点,不然即使stop了这台机器,另一台机器立马会start
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
zkClient.subscribeDataChanges(path, dataListener);
}
initRunning();
} else {
processActiveEnter();// 没有zk,直接启动
public synchronized void start() {
super.start();
try {
processStart();
if (zkClient != null) {
// 如果需要尽可能释放instance资源,不需要监听running节点,不然即使stop了这台机器,另一台机器立马会start
String path = ZookeeperPathUtils.getDestinationServerRunning(destination);
zkClient.subscribeDataChanges(path, dataListener);
initRunning();
} else {
processActiveEnter();// 没有zk,直接启动
}
} catch (Exception e) {
logger.error("start failed", e);
// 没有正常启动,重置一下状态,避免干扰下一次start
stop();
}
}
public void release() {
@@ -109,7 +120,7 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
}
}
public void stop() {
public synchronized void stop() {
super.stop();
if (zkClient != null) {
@@ -230,11 +241,7 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
private void processActiveEnter() {
if (listener != null) {
try {
listener.processActiveEnter();
} catch (Exception e) {
logger.error("processActiveEnter failed", e);
}
listener.processActiveEnter();
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId>
<version>1.0.22</version>
<version>1.0.26-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.alibaba.otter</groupId>
@@ -103,8 +103,8 @@ public final class CharsetConversion {
putEntry(42, "latin7", "latin7_general_cs", "ISO8859_7");
putEntry(43, "macce", "macce_bin", "MacCentralEurope");
putEntry(44, "cp1250", "cp1250_croatian_ci", "Cp1250");
putEntry(45, "utf8mb4", "utf8mb4_general_ci", "MacCentralEurope");
putEntry(46, "utf8mb4", "utf8mb4_bin", "MacCentralEurope");
putEntry(45, "utf8mb4", "utf8mb4_general_ci", "UTF-8");
putEntry(46, "utf8mb4", "utf8mb4_bin", "UTF-8");
putEntry(47, "latin1", "latin1_bin", "ISO8859_1");
putEntry(48, "latin1", "latin1_general_ci", "ISO8859_1");
putEntry(49, "latin1", "latin1_general_cs", "ISO8859_1");
@@ -75,7 +75,9 @@ public final class FileLogFetcher extends LogFetcher {
fin = new FileInputStream(file);
ensureCapacity(BIN_LOG_HEADER_SIZE);
if (BIN_LOG_HEADER_SIZE != fin.read(buffer, 0, BIN_LOG_HEADER_SIZE)) throw new IOException("No binlog file header");
if (BIN_LOG_HEADER_SIZE != fin.read(buffer, 0, BIN_LOG_HEADER_SIZE)) {
throw new IOException("No binlog file header");
}
if (buffer[0] != BINLOG_MAGIC[0] || buffer[1] != BINLOG_MAGIC[1] || buffer[2] != BINLOG_MAGIC[2]
|| buffer[3] != BINLOG_MAGIC[3]) {
@@ -128,6 +130,19 @@ public final class FileLogFetcher extends LogFetcher {
return true;
}
} else if (limit > 0) {
if (limit >= FormatDescriptionLogEvent.LOG_EVENT_HEADER_LEN) {
int lenPosition = position + 4 + 1 + 4;
long eventLen = ((long) (0xff & buffer[lenPosition++])) | ((long) (0xff & buffer[lenPosition++]) << 8)
| ((long) (0xff & buffer[lenPosition++]) << 16)
| ((long) (0xff & buffer[lenPosition++]) << 24);
if (limit >= eventLen) {
return true;
} else {
ensureCapacity((int) eventLen);
}
}
System.arraycopy(buffer, origin, buffer, 0, limit);
position -= origin;
origin = 0;
@@ -52,23 +52,24 @@ public class JsonConversion {
public static final int VALUE_ENTRY_SIZE_SMALL = (1 + SMALL_OFFSET_SIZE);
public static final int VALUE_ENTRY_SIZE_LARGE = (1 + LARGE_OFFSET_SIZE);
public static Json_Value parse_value(int type, LogBuffer buffer, long len) {
public static Json_Value parse_value(int type, LogBuffer buffer, long len, String charsetName) {
buffer = buffer.duplicate(buffer.position(), (int) len);
switch (type) {
case JSONB_TYPE_SMALL_OBJECT:
return parse_array_or_object(Json_enum_type.OBJECT, buffer, len, false);
return parse_array_or_object(Json_enum_type.OBJECT, buffer, len, false, charsetName);
case JSONB_TYPE_LARGE_OBJECT:
return parse_array_or_object(Json_enum_type.OBJECT, buffer, len, true);
return parse_array_or_object(Json_enum_type.OBJECT, buffer, len, true, charsetName);
case JSONB_TYPE_SMALL_ARRAY:
return parse_array_or_object(Json_enum_type.ARRAY, buffer, len, false);
return parse_array_or_object(Json_enum_type.ARRAY, buffer, len, false, charsetName);
case JSONB_TYPE_LARGE_ARRAY:
return parse_array_or_object(Json_enum_type.ARRAY, buffer, len, true);
return parse_array_or_object(Json_enum_type.ARRAY, buffer, len, true, charsetName);
default:
return parse_scalar(type, buffer, len);
return parse_scalar(type, buffer, len, charsetName);
}
}
private static Json_Value parse_array_or_object(Json_enum_type type, LogBuffer buffer, long len, boolean large) {
private static Json_Value parse_array_or_object(Json_enum_type type, LogBuffer buffer, long len, boolean large,
String charsetName) {
long offset_size = large ? LARGE_OFFSET_SIZE : SMALL_OFFSET_SIZE;
if (len < 2 * offset_size) {
throw new IllegalArgumentException("illegal json data");
@@ -95,7 +96,7 @@ public class JsonConversion {
return large ? buffer.getUint32() : buffer.getUint16();
}
private static Json_Value parse_scalar(int type, LogBuffer buffer, long len) {
private static Json_Value parse_scalar(int type, LogBuffer buffer, long len, String charsetName) {
switch (type) {
case JSONB_TYPE_LITERAL:
/* purecov: inspected */
@@ -146,12 +147,12 @@ public class JsonConversion {
}
}
if (str_len == 0 || len < n + str_len) {
if (len < n + str_len) {
throw new IllegalArgumentException("illegal json data");
}
return new Json_Value(Json_enum_type.STRING, buffer.rewind()
.forward((int) n)
.getFixString((int) str_len));
.getFixString((int) str_len, charsetName));
case JSONB_TYPE_OPAQUE:
/*
* There should always be at least one byte, which tells the
@@ -237,7 +238,7 @@ public class JsonConversion {
this.m_large = large;
}
public String key(int i) {
public String key(int i, String charsetName) {
m_data.rewind();
int offset_size = m_large ? LARGE_OFFSET_SIZE : SMALL_OFFSET_SIZE;
int key_entry_size = m_large ? KEY_ENTRY_SIZE_LARGE : KEY_ENTRY_SIZE_SMALL;
@@ -250,10 +251,10 @@ public class JsonConversion {
// entry, always two
// bytes.
long key_length = m_data.getUint16();
return m_data.rewind().forward((int) key_offset).getFixString((int) key_length);
return m_data.rewind().forward((int) key_offset).getFixString((int) key_length, charsetName);
}
public Json_Value element(int i) {
public Json_Value element(int i, String charsetName) {
m_data.rewind();
int offset_size = m_large ? LARGE_OFFSET_SIZE : SMALL_OFFSET_SIZE;
int key_entry_size = m_large ? KEY_ENTRY_SIZE_LARGE : KEY_ENTRY_SIZE_SMALL;
@@ -266,13 +267,13 @@ public class JsonConversion {
int type = m_data.forward(entry_offset).getUint8();
if (type == JSONB_TYPE_INT16 || type == JSONB_TYPE_UINT16 || type == JSONB_TYPE_LITERAL
|| (m_large && (type == JSONB_TYPE_INT32 || type == JSONB_TYPE_UINT32))) {
return parse_scalar(type, m_data, value_entry_size - 1);
return parse_scalar(type, m_data, value_entry_size - 1, charsetName);
}
int value_offset = (int) read_offset_or_size(m_data, m_large);
return parse_value(type, m_data.rewind().forward(value_offset), (int) m_length - value_offset);
return parse_value(type, m_data.rewind().forward(value_offset), (int) m_length - value_offset, charsetName);
}
public StringBuilder toJsonString(StringBuilder buf) {
public StringBuilder toJsonString(StringBuilder buf, String charsetName) {
switch (m_type) {
case OBJECT:
buf.append("{");
@@ -280,9 +281,9 @@ public class JsonConversion {
if (i > 0) {
buf.append(", ");
}
buf.append('"').append(key(i)).append('"');
buf.append(" :");
element(i).toJsonString(buf);
buf.append('"').append(key(i, charsetName)).append('"');
buf.append(": ");
element(i, charsetName).toJsonString(buf, charsetName);
}
buf.append("}");
break;
@@ -292,7 +293,7 @@ public class JsonConversion {
if (i > 0) {
buf.append(", ");
}
element(i).toJsonString(buf);
element(i, charsetName).toJsonString(buf, charsetName);
}
buf.append("]");
break;
@@ -361,13 +362,13 @@ public class JsonConversion {
}
buf.append('"').append(text).append('"');
} else {
text = m_data.getFixString((int) m_length);
buf.append('"').append(text).append('"');
text = m_data.getFixString((int) m_length, charsetName);
buf.append('"').append(escapse(text)).append('"');
}
break;
case STRING:
buf.append('"').append(m_string_value).append('"');
buf.append('"').append(escapse(m_string_value)).append('"');
break;
case ERROR:
throw new IllegalArgumentException("illegal json data");
@@ -377,6 +378,21 @@ public class JsonConversion {
}
}
private static StringBuilder escapse(String data) {
StringBuilder sb = new StringBuilder(data.length());
int endIndex = data.length();
for (int i = 0; i < endIndex; ++i) {
char c = data.charAt(i);
if (c == '"') {
sb.append('\\');
} else if (c == '\\') {
sb.append("\\");
}
sb.append(c);
}
return sb;
}
public static enum Json_enum_type {
OBJECT, ARRAY, STRING, INT, UINT, DOUBLE, LITERAL_NULL, LITERAL_TRUE, LITERAL_FALSE, OPAQUE, ERROR
}
@@ -20,6 +20,7 @@ public class LogBuffer {
protected int origin, limit;
protected int position;
protected int semival;
protected LogBuffer(){
}
@@ -168,8 +169,8 @@ public class LogBuffer {
* @param newLimit The new limit value; must be non-negative and no larger
* than this buffer's capacity
* @return This buffer
* @throws IllegalArgumentException If the preconditions on <tt>newLimit</tt>
* do not hold
* @throws IllegalArgumentException If the preconditions on
* <tt>newLimit</tt> do not hold
*/
public final LogBuffer limit(int newLimit) {
if (origin + newLimit > buffer.length || newLimit < 0) throw new IllegalArgumentException("capacity excceed: "
@@ -58,7 +58,6 @@ public final class LogContext {
public void reset() {
formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
mapOfTable.clear();
}
}
@@ -30,15 +30,19 @@ import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent;
import com.taobao.tddl.dbsync.binlog.event.StartLogEventV3;
import com.taobao.tddl.dbsync.binlog.event.StopLogEvent;
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
import com.taobao.tddl.dbsync.binlog.event.TransactionContextLogEvent;
import com.taobao.tddl.dbsync.binlog.event.UnknownLogEvent;
import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent;
import com.taobao.tddl.dbsync.binlog.event.UserVarLogEvent;
import com.taobao.tddl.dbsync.binlog.event.ViewChangeEvent;
import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent;
import com.taobao.tddl.dbsync.binlog.event.XaPrepareLogEvent;
import com.taobao.tddl.dbsync.binlog.event.XidLogEvent;
import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent;
import com.taobao.tddl.dbsync.binlog.event.mariadb.BinlogCheckPointLogEvent;
import com.taobao.tddl.dbsync.binlog.event.mariadb.MariaGtidListLogEvent;
import com.taobao.tddl.dbsync.binlog.event.mariadb.MariaGtidLogEvent;
import com.taobao.tddl.dbsync.binlog.event.mariadb.StartEncryptionLogEvent;
/**
* Implements a binary-log decoder.
@@ -104,9 +108,16 @@ public final class LogDecoder {
try {
/* Decoding binary-log to event */
event = decode(buffer, header, context);
if (event != null) {
// set logFileName
event.getHeader().setLogFileName(context.getLogPosition().getFileName());
event.setSemival(buffer.semival);
}
} catch (IOException e) {
if (logger.isWarnEnabled()) logger.warn("Decoding " + LogEvent.getTypeName(header.getType())
+ " failed from: " + context.getLogPosition(), e);
if (logger.isWarnEnabled()) {
logger.warn("Decoding " + LogEvent.getTypeName(header.getType()) + " failed from: "
+ context.getLogPosition(), e);
}
throw e;
} finally {
buffer.limit(limit); /* Restore limit */
@@ -361,6 +372,24 @@ public final class LogDecoder {
logPosition.position = header.getLogPos();
return event;
}
case LogEvent.TRANSACTION_CONTEXT_EVENT: {
TransactionContextLogEvent event = new TransactionContextLogEvent(header, buffer, descriptionEvent);
/* updating position in context */
logPosition.position = header.getLogPos();
return event;
}
case LogEvent.VIEW_CHANGE_EVENT: {
ViewChangeEvent event = new ViewChangeEvent(header, buffer, descriptionEvent);
/* updating position in context */
logPosition.position = header.getLogPos();
return event;
}
case LogEvent.XA_PREPARE_LOG_EVENT: {
XaPrepareLogEvent event = new XaPrepareLogEvent(header, buffer, descriptionEvent);
/* updating position in context */
logPosition.position = header.getLogPos();
return event;
}
case LogEvent.ANNOTATE_ROWS_EVENT: {
AnnotateRowsEvent event = new AnnotateRowsEvent(header, buffer, descriptionEvent);
/* updating position in context */
@@ -385,6 +414,12 @@ public final class LogDecoder {
logPosition.position = header.getLogPos();
return event;
}
case LogEvent.START_ENCRYPTION_EVENT: {
StartEncryptionLogEvent event = new StartEncryptionLogEvent(header, buffer, descriptionEvent);
/* updating position in context */
logPosition.position = header.getLogPos();
return event;
}
default:
/*
* Create an object of Ignorable_log_event for unrecognized
@@ -397,9 +432,10 @@ public final class LogDecoder {
logPosition.position = header.getLogPos();
return event;
} else {
if (logger.isWarnEnabled()) logger.warn("Skipping unrecognized binlog event "
+ LogEvent.getTypeName(header.getType()) + " from: "
+ context.getLogPosition());
if (logger.isWarnEnabled()) {
logger.warn("Skipping unrecognized binlog event " + LogEvent.getTypeName(header.getType())
+ " from: " + context.getLogPosition());
}
}
}
@@ -161,9 +161,17 @@ public abstract class LogEvent {
public static final int PREVIOUS_GTIDS_LOG_EVENT = 35;
/* MySQL 5.7 events */
public static final int TRANSACTION_CONTEXT_EVENT = 36;
public static final int VIEW_CHANGE_EVENT = 37;
/* Prepared XA transaction terminal event similar to Xid */
public static final int XA_PREPARE_LOG_EVENT = 38;
// mariaDb 5.5.34
/* New MySQL/Sun events are to be added right above this comment */
public static final int MYSQL_EVENTS_END = 36;
public static final int MYSQL_EVENTS_END = 39;
public static final int MARIA_EVENTS_BEGIN = 160;
/* New Maria event numbers start from here */
@@ -189,8 +197,10 @@ public abstract class LogEvent {
*/
public static final int GTID_LIST_EVENT = 163;
public static final int START_ENCRYPTION_EVENT = 164;
/** end marker */
public static final int ENUM_END_EVENT = 164;
public static final int ENUM_END_EVENT = 165;
/**
* 1 byte length, 1 byte format Length is total length in bytes, including 2
@@ -360,6 +370,24 @@ public abstract class LogEvent {
protected final LogHeader header;
/**
* mysql半同步semi标识
*
* <pre>
* 0不需要semi ack 给mysql
* 1需要semi ack给mysql
* </pre>
*/
protected int semival;
public int getSemival() {
return semival;
}
public void setSemival(int semival) {
this.semival = semival;
}
protected LogEvent(LogHeader header){
this.header = header;
}
@@ -59,10 +59,15 @@ public final class FormatDescriptionLogEvent extends StartLogEventV3 {
public static final int HEARTBEAT_HEADER_LEN = 0;
public static final int IGNORABLE_HEADER_LEN = 0;
public static final int ROWS_HEADER_LEN_V2 = 10;
public static final int TRANSACTION_CONTEXT_HEADER_LEN = 18;
public static final int VIEW_CHANGE_HEADER_LEN = 52;
public static final int XA_PREPARE_HEADER_LEN = 0;
public static final int ANNOTATE_ROWS_HEADER_LEN = 0;
public static final int BINLOG_CHECKPOINT_HEADER_LEN = 4;
public static final int GTID_HEADER_LEN = 19;
public static final int GTID_LIST_HEADER_LEN = 4;
public static final int START_ENCRYPTION_HEADER_LEN = 0;
public static final int POST_HEADER_LENGTH = 11;
@@ -141,6 +146,11 @@ public final class FormatDescriptionLogEvent extends StartLogEventV3 {
}
}
public FormatDescriptionLogEvent(final int binlogVersion, int binlogChecksum){
this(binlogVersion);
this.header.checksumAlg = binlogChecksum;
}
public FormatDescriptionLogEvent(final int binlogVersion){
this.binlogVersion = binlogVersion;
@@ -197,11 +207,17 @@ public final class FormatDescriptionLogEvent extends StartLogEventV3 {
postHeaderLen[GTID_LOG_EVENT - 1] = POST_HEADER_LENGTH;
postHeaderLen[ANONYMOUS_GTID_LOG_EVENT - 1] = POST_HEADER_LENGTH;
postHeaderLen[PREVIOUS_GTIDS_LOG_EVENT - 1] = IGNORABLE_HEADER_LEN;
postHeaderLen[TRANSACTION_CONTEXT_EVENT - 1] = TRANSACTION_CONTEXT_HEADER_LEN;
postHeaderLen[VIEW_CHANGE_EVENT - 1] = VIEW_CHANGE_HEADER_LEN;
postHeaderLen[XA_PREPARE_LOG_EVENT - 1] = XA_PREPARE_HEADER_LEN;
// mariadb 10
postHeaderLen[ANNOTATE_ROWS_EVENT - 1] = ANNOTATE_ROWS_HEADER_LEN;
postHeaderLen[BINLOG_CHECKPOINT_EVENT - 1] = BINLOG_CHECKPOINT_HEADER_LEN;
postHeaderLen[GTID_EVENT - 1] = GTID_HEADER_LEN;
postHeaderLen[GTID_LIST_EVENT - 1] = GTID_LIST_HEADER_LEN;
postHeaderLen[START_ENCRYPTION_EVENT - 1] = START_ENCRYPTION_HEADER_LEN;
break;
case 3: /* 4.0.x x>=2 */
@@ -1,5 +1,8 @@
package com.taobao.tddl.dbsync.binlog.event;
import java.nio.ByteBuffer;
import java.util.UUID;
import com.taobao.tddl.dbsync.binlog.LogBuffer;
import com.taobao.tddl.dbsync.binlog.LogEvent;
@@ -14,8 +17,13 @@ public class GtidLogEvent extends LogEvent {
public static final int ENCODED_FLAG_LENGTH = 1;
// / Length of SID in event encoding
public static final int ENCODED_SID_LENGTH = 16;
public static final int LOGICAL_TIMESTAMP_TYPE_CODE = 2;
private boolean commitFlag;
private UUID sid;
private long gno;
private Long lastCommitted;
private Long sequenceNumber;
public GtidLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
super(header);
@@ -27,6 +35,23 @@ public class GtidLogEvent extends LogEvent {
buffer.position(commonHeaderLen);
commitFlag = (buffer.getUint8() != 0); // ENCODED_FLAG_LENGTH
byte[] bs = buffer.getData(ENCODED_SID_LENGTH);
ByteBuffer bb = ByteBuffer.wrap(bs);
long high = bb.getLong();
long low = bb.getLong();
sid = new UUID(high, low);
gno = buffer.getLong64();
// support gtid lastCommitted and sequenceNumber
// fix bug #776
if (buffer.hasRemaining() && buffer.remaining() > 16 && buffer.getUint8() == LOGICAL_TIMESTAMP_TYPE_CODE) {
lastCommitted = buffer.getLong64();
sequenceNumber = buffer.getLong64();
}
// ignore gtid info read
// sid.copy_from((uchar *)ptr_buffer);
// ptr_buffer+= ENCODED_SID_LENGTH;
@@ -42,4 +67,19 @@ public class GtidLogEvent extends LogEvent {
return commitFlag;
}
public UUID getSid() {
return sid;
}
public long getGno() {
return gno;
}
public Long getLastCommitted() {
return lastCommitted;
}
public Long getSequenceNumber() {
return sequenceNumber;
}
}
@@ -115,6 +115,11 @@ public final class LogHeader {
*/
protected long crc; // ha_checksum
/**
* binlog fileName
*/
protected String logFileName;
/* for Start_event_v3 */
public LogHeader(final int type){
this.type = type;
@@ -270,6 +275,14 @@ public final class LogHeader {
return checksumAlg;
}
public String getLogFileName() {
return logFileName;
}
public void setLogFileName(String logFileName) {
this.logFileName = logFileName;
}
private void processCheckSum(LogBuffer buffer) {
if (checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_OFF && checksumAlg != LogEvent.BINLOG_CHECKSUM_ALG_UNDEF) {
crc = buffer.getUint32(eventLen - LogEvent.BINLOG_CHECKSUM_LEN);
@@ -330,17 +330,14 @@ public class QueryLogEvent extends LogEvent {
* MariaDb type,
* sec_part of NOW()
*/
+ 1 + (MAX_DBS_IN_EVENT_MTS * (1 + NAME_LEN)) + 3 + 1 + 16 + 1 + 60/*
* type
* ,
* user_len
* ,
* user
* ,
* host_len
* ,
* host
*/);
+ 1 + (MAX_DBS_IN_EVENT_MTS * (1 + NAME_LEN)) + 3 + 1 + 32 * 3
+ 1 + 60/*
* type ,
* user_len ,
* user ,
* host_len ,
* host
*/);
/**
* Fixed data part:
* <ul>
@@ -397,7 +394,7 @@ public class QueryLogEvent extends LogEvent {
// inspection by the DBA
private final long execTime;
private final int errorCode;
private final long sessionId; /* thread_id */
private final long sessionId; /* thread_id */
/**
* 'flags2' is a second set of flags (on top of those in Log_event), for
@@ -499,26 +496,26 @@ public class QueryLogEvent extends LogEvent {
}
/* query event post-header */
public static final int Q_THREAD_ID_OFFSET = 0;
public static final int Q_EXEC_TIME_OFFSET = 4;
public static final int Q_DB_LEN_OFFSET = 8;
public static final int Q_ERR_CODE_OFFSET = 9;
public static final int Q_STATUS_VARS_LEN_OFFSET = 11;
public static final int Q_DATA_OFFSET = QUERY_HEADER_LEN;
public static final int Q_THREAD_ID_OFFSET = 0;
public static final int Q_EXEC_TIME_OFFSET = 4;
public static final int Q_DB_LEN_OFFSET = 8;
public static final int Q_ERR_CODE_OFFSET = 9;
public static final int Q_STATUS_VARS_LEN_OFFSET = 11;
public static final int Q_DATA_OFFSET = QUERY_HEADER_LEN;
/* these are codes, not offsets; not more than 256 values (1 byte). */
public static final int Q_FLAGS2_CODE = 0;
public static final int Q_SQL_MODE_CODE = 1;
public static final int Q_FLAGS2_CODE = 0;
public static final int Q_SQL_MODE_CODE = 1;
/**
* Q_CATALOG_CODE is catalog with end zero stored; it is used only by MySQL
* 5.0.x where 0<=x<=3. We have to keep it to be able to replicate these old
* masters.
*/
public static final int Q_CATALOG_CODE = 2;
public static final int Q_AUTO_INCREMENT = 3;
public static final int Q_CHARSET_CODE = 4;
public static final int Q_TIME_ZONE_CODE = 5;
public static final int Q_CATALOG_CODE = 2;
public static final int Q_AUTO_INCREMENT = 3;
public static final int Q_CHARSET_CODE = 4;
public static final int Q_TIME_ZONE_CODE = 5;
/**
* Q_CATALOG_NZ_CODE is catalog withOUT end zero stored; it is used by MySQL
@@ -528,31 +525,47 @@ public class QueryLogEvent extends LogEvent {
* would crash (segfault etc) because it would expect a 0 when there is
* none.
*/
public static final int Q_CATALOG_NZ_CODE = 6;
public static final int Q_CATALOG_NZ_CODE = 6;
public static final int Q_LC_TIME_NAMES_CODE = 7;
public static final int Q_LC_TIME_NAMES_CODE = 7;
public static final int Q_CHARSET_DATABASE_CODE = 8;
public static final int Q_CHARSET_DATABASE_CODE = 8;
public static final int Q_TABLE_MAP_FOR_UPDATE_CODE = 9;
public static final int Q_TABLE_MAP_FOR_UPDATE_CODE = 9;
public static final int Q_MASTER_DATA_WRITTEN_CODE = 10;
public static final int Q_MASTER_DATA_WRITTEN_CODE = 10;
public static final int Q_INVOKER = 11;
public static final int Q_INVOKER = 11;
/**
* Q_UPDATED_DB_NAMES status variable collects of the updated databases
* total number and their names to be propagated to the slave in order to
* facilitate the parallel applying of the Query events.
*/
public static final int Q_UPDATED_DB_NAMES = 12;
public static final int Q_UPDATED_DB_NAMES = 12;
public static final int Q_MICROSECONDS = 13;
public static final int Q_MICROSECONDS = 13;
/**
* A old (unused now) code for Query_log_event status similar to
* G_COMMIT_TS.
*/
public static final int Q_COMMIT_TS = 14;
/**
* A code for Query_log_event status, similar to G_COMMIT_TS2.
*/
public static final int Q_COMMIT_TS2 = 15;
/**
* The master connection @@session.explicit_defaults_for_timestamp which is
* recorded for queries, CREATE and ALTER table that is defined with a
* TIMESTAMP column, that are dependent on that feature. For pre-WL6292
* master's the associated with this code value is zero.
*/
public static final int Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP = 16;
/**
* FROM MariaDB 5.5.34
*/
public static final int Q_HRNOW = 128;
public static final int Q_HRNOW = 128;
private final void unpackVariables(LogBuffer buffer, final int end) throws IOException {
int code = -1;
@@ -632,6 +645,9 @@ public class QueryLogEvent extends LogEvent {
mtsAccessedDbNames[i] = buffer.getFixString(length < NAME_LEN ? length : NAME_LEN);
}
break;
case Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP:
buffer.forward(1);
break;
case Q_HRNOW:
// int when_sec_part = buffer.getUint24();
buffer.forward(3);
@@ -90,6 +90,7 @@ public final class RotateLogEvent extends LogEvent {
int filenameLen = buffer.limit() - filenameOffset;
if (filenameLen > FN_REFLEN - 1) filenameLen = FN_REFLEN - 1;
buffer.position(filenameOffset);
filename = buffer.getFixString(filenameLen);
}
@@ -27,6 +27,7 @@ public final class RowsLogBuffer {
public static final long DATETIMEF_INT_OFS = 0x8000000000L;
public static final long TIMEF_INT_OFS = 0x800000L;
public static final long TIMEF_OFS = 0x800000000000L;
private static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
private final LogBuffer buffer;
private final int columnLen;
@@ -480,13 +481,27 @@ public final class RowsLogBuffer {
// cal.set(d / 10000, (d % 10000) / 100 - 1, d % 100, t /
// 10000, (t % 10000) / 100, t % 100);
// value = new Timestamp(cal.getTimeInMillis());
value = String.format("%04d-%02d-%02d %02d:%02d:%02d",
d / 10000,
(d % 10000) / 100,
d % 100,
t / 10000,
(t % 10000) / 100,
t % 100);
// value = String.format("%04d-%02d-%02d %02d:%02d:%02d",
// d / 10000,
// (d % 10000) / 100,
// d % 100,
// t / 10000,
// (t % 10000) / 100,
// t % 100);
StringBuilder builder = new StringBuilder();
appendNumber4(builder, d / 10000);
builder.append('-');
appendNumber2(builder, (d % 10000) / 100);
builder.append('-');
appendNumber2(builder, d % 100);
builder.append(' ');
appendNumber2(builder, t / 10000);
builder.append(':');
appendNumber2(builder, (t % 10000) / 100);
builder.append(':');
appendNumber2(builder, t % 100);
value = builder.toString();
}
javaType = Types.TIMESTAMP;
length = 8;
@@ -540,13 +555,27 @@ public final class RowsLogBuffer {
// % (1 << 5)), (int) (hms >> 12),
// (int) ((hms >> 6) % (1 << 6)), (int) (hms % (1 << 6)));
// value = new Timestamp(cal.getTimeInMillis());
second = String.format("%04d-%02d-%02d %02d:%02d:%02d",
(int) (ym / 13),
(int) (ym % 13),
(int) (ymd % (1 << 5)),
(int) (hms >> 12),
(int) ((hms >> 6) % (1 << 6)),
(int) (hms % (1 << 6)));
// second = String.format("%04d-%02d-%02d %02d:%02d:%02d",
// (int) (ym / 13),
// (int) (ym % 13),
// (int) (ymd % (1 << 5)),
// (int) (hms >> 12),
// (int) ((hms >> 6) % (1 << 6)),
// (int) (hms % (1 << 6)));
StringBuilder builder = new StringBuilder(26);
appendNumber4(builder, (int) (ym / 13));
builder.append('-');
appendNumber2(builder, (int) (ym % 13));
builder.append('-');
appendNumber2(builder, (int) (ymd % (1 << 5)));
builder.append(' ');
appendNumber2(builder, (int) (hms >> 12));
builder.append(':');
appendNumber2(builder, (int) ((hms >> 6) % (1 << 6)));
builder.append(':');
appendNumber2(builder, (int) (hms % (1 << 6)));
second = builder.toString();
}
if (meta >= 1) {
@@ -575,11 +604,28 @@ public final class RowsLogBuffer {
// cal.set(70, 0, 1, i32 / 10000, (i32 % 10000) / 100, i32 %
// 100);
// value = new Time(cal.getTimeInMillis());
value = String.format("%s%02d:%02d:%02d",
(i32 >= 0) ? "" : "-",
u32 / 10000,
(u32 % 10000) / 100,
u32 % 100);
// value = String.format("%s%02d:%02d:%02d",
// (i32 >= 0) ? "" : "-",
// u32 / 10000,
// (u32 % 10000) / 100,
// u32 % 100);
StringBuilder builder = new StringBuilder(12);
if (i32 < 0) {
builder.append('-');
}
int d = u32 / 10000;
if (d > 100) {
builder.append(String.valueOf(d));
} else {
appendNumber2(builder, d);
}
builder.append(':');
appendNumber2(builder, (u32 % 10000) / 100);
builder.append(':');
appendNumber2(builder, u32 % 100);
value = builder.toString();
}
javaType = Types.TIME;
length = 3;
@@ -673,11 +719,28 @@ public final class RowsLogBuffer {
// value = new Time(cal.getTimeInMillis());
long ultime = Math.abs(ltime);
intpart = ultime >> 24;
second = String.format("%s%02d:%02d:%02d",
ltime >= 0 ? "" : "-",
(int) ((intpart >> 12) % (1 << 10)),
(int) ((intpart >> 6) % (1 << 6)),
(int) (intpart % (1 << 6)));
// second = String.format("%s%02d:%02d:%02d",
// ltime >= 0 ? "" : "-",
// (int) ((intpart >> 12) % (1 << 10)),
// (int) ((intpart >> 6) % (1 << 6)),
// (int) (intpart % (1 << 6)));
StringBuilder builder = new StringBuilder(12);
if (ltime < 0) {
builder.append('-');
}
int d = (int) ((intpart >> 12) % (1 << 10));
if (d > 100) {
builder.append(String.valueOf(d));
} else {
appendNumber2(builder, d);
}
builder.append(':');
appendNumber2(builder, (int) ((intpart >> 6) % (1 << 6)));
builder.append(':');
appendNumber2(builder, (int) (intpart % (1 << 6)));
second = builder.toString();
}
if (meta >= 1) {
@@ -717,7 +780,16 @@ public final class RowsLogBuffer {
// cal.set((i32 / (16 * 32)), (i32 / 32 % 16) - 1, (i32 %
// 32));
// value = new java.sql.Date(cal.getTimeInMillis());
value = String.format("%04d-%02d-%02d", i32 / (16 * 32), i32 / 32 % 16, i32 % 32);
// value = String.format("%04d-%02d-%02d", i32 / (16 * 32),
// i32 / 32 % 16, i32 % 32);
StringBuilder builder = new StringBuilder(12);
appendNumber4(builder, i32 / (16 * 32));
builder.append('-');
appendNumber2(builder, i32 / 32 % 16);
builder.append('-');
appendNumber2(builder, i32 % 32);
value = builder.toString();
}
javaType = Types.DATE;
length = 3;
@@ -949,19 +1021,39 @@ public final class RowsLogBuffer {
break;
}
case LogEvent.MYSQL_TYPE_JSON: {
len = buffer.getUint16();
buffer.forward(meta - 2);
int position = buffer.position();
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(), buffer, len - 1);
StringBuilder builder = new StringBuilder();
jsonValue.toJsonString(builder);
value = builder.toString();
buffer.position(position + len);
// byte[] binary = new byte[len];
// buffer.fillBytes(binary, 0, len);
// value = binary;
javaType = Types.VARBINARY;
switch (meta) {
case 1: {
len = buffer.getUint8();
break;
}
case 2: {
len = buffer.getUint16();
break;
}
case 3: {
len = buffer.getUint24();
break;
}
case 4: {
len = (int) buffer.getUint32();
break;
}
default:
throw new IllegalArgumentException("!! Unknown JSON packlen = " + meta);
}
if (0 == len) {
// fixed issue #1 by lava, json column of zero length has no
// value, value parsing should be skipped
value = "";
} else {
int position = buffer.position();
Json_Value jsonValue = JsonConversion.parse_value(buffer.getUint8(), buffer, len - 1, charsetName);
StringBuilder builder = new StringBuilder();
jsonValue.toJsonString(builder, charsetName);
value = builder.toString();
buffer.position(position + len);
}
javaType = Types.VARCHAR;
length = len;
break;
}
@@ -1047,4 +1139,32 @@ public final class RowsLogBuffer {
return sec.substring(0, meta);
}
private void appendNumber4(StringBuilder builder, int d) {
if (d >= 1000) {
builder.append(digits[d / 1000])
.append(digits[(d / 100) % 10])
.append(digits[(d / 10) % 10])
.append(digits[d % 10]);
} else {
builder.append('0');
appendNumber3(builder, d);
}
}
private void appendNumber3(StringBuilder builder, int d) {
if (d >= 100) {
builder.append(digits[d / 100]).append(digits[(d / 10) % 10]).append(digits[d % 10]);
} else {
builder.append('0');
appendNumber2(builder, d);
}
}
private void appendNumber2(StringBuilder builder, int d) {
if (d >= 10) {
builder.append(digits[(d / 10) % 10]).append(digits[d % 10]);
} else {
builder.append('0').append(digits[d]);
}
}
}
@@ -194,7 +194,7 @@ public abstract class RowsLogEvent extends LogEvent {
}
public final RowsLogBuffer getRowsBuf(String charsetName) {
return new RowsLogBuffer(rowsBuf.duplicate(), columnLen, charsetName);
return new RowsLogBuffer(rowsBuf, columnLen, charsetName);
}
public final int getFlags(final int flags) {
@@ -0,0 +1,16 @@
package com.taobao.tddl.dbsync.binlog.event;
import com.taobao.tddl.dbsync.binlog.LogBuffer;
import com.taobao.tddl.dbsync.binlog.LogEvent;
/**
* @author agapple 2018年5月7日 下午7:05:39
* @version 1.0.26
* @since mysql 5.7
*/
public class TransactionContextLogEvent extends LogEvent {
public TransactionContextLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
super(header);
}
}
@@ -0,0 +1,16 @@
package com.taobao.tddl.dbsync.binlog.event;
import com.taobao.tddl.dbsync.binlog.LogBuffer;
import com.taobao.tddl.dbsync.binlog.LogEvent;
/**
* @author agapple 2018年5月7日 下午7:05:39
* @version 1.0.26
* @since mysql 5.7
*/
public class ViewChangeEvent extends LogEvent {
public ViewChangeEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
super(header);
}
}
@@ -0,0 +1,65 @@
package com.taobao.tddl.dbsync.binlog.event;
import com.taobao.tddl.dbsync.binlog.LogBuffer;
import com.taobao.tddl.dbsync.binlog.LogEvent;
/**
* @author agapple 2018年5月7日 下午7:05:39
* @version 1.0.26
* @since mysql 5.7
*/
public class XaPrepareLogEvent extends LogEvent {
private boolean onePhase;
private int formatId;
private int gtridLength;
private int bqualLength;
private byte[] data;
public XaPrepareLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
super(header);
final int commonHeaderLen = descriptionEvent.getCommonHeaderLen();
final int postHeaderLen = descriptionEvent.getPostHeaderLen()[header.getType() - 1];
int offset = commonHeaderLen + postHeaderLen;
buffer.position(offset);
onePhase = (buffer.getInt8() == 0x00 ? false : true);
formatId = buffer.getInt32();
gtridLength = buffer.getInt32();
bqualLength = buffer.getInt32();
int MY_XIDDATASIZE = 128;
if (MY_XIDDATASIZE >= gtridLength + bqualLength && gtridLength >= 0 && gtridLength <= 64 && bqualLength >= 0
&& bqualLength <= 64) {
data = buffer.getData(gtridLength + bqualLength);
} else {
formatId = -1;
gtridLength = 0;
bqualLength = 0;
}
}
public boolean isOnePhase() {
return onePhase;
}
public int getFormatId() {
return formatId;
}
public int getGtridLength() {
return gtridLength;
}
public int getBqualLength() {
return bqualLength;
}
public byte[] getData() {
return data;
}
}
@@ -13,9 +13,30 @@ import com.taobao.tddl.dbsync.binlog.event.LogHeader;
*/
public class MariaGtidLogEvent extends IgnorableLogEvent {
private long gtid;
/**
* <pre>
* mariadb gtidlog event format
* uint<8> GTID sequence
* uint<4> Replication Domain ID
* uint<1> Flags
*
* if flag & FL_GROUP_COMMIT_ID
* uint<8> commit_id
* else
* uint<6> 0
* </pre>
*/
public MariaGtidLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
super(header, buffer, descriptionEvent);
gtid = buffer.getUlong64().longValue();
// do nothing , just ignore log event
}
public long getGtid() {
return gtid;
}
}
@@ -0,0 +1,19 @@
package com.taobao.tddl.dbsync.binlog.event.mariadb;
import com.taobao.tddl.dbsync.binlog.LogBuffer;
import com.taobao.tddl.dbsync.binlog.LogEvent;
import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent;
import com.taobao.tddl.dbsync.binlog.event.LogHeader;
/**
* mariadb的Start_encryption_log_event
*
* @author agapple 2018年5月7日 下午7:23:02
* @version 1.0.26
*/
public class StartEncryptionLogEvent extends LogEvent {
public StartEncryptionLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
super(header);
}
}
@@ -0,0 +1,52 @@
package com.taobao.tddl.dbsync;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.concurrent.atomic.AtomicLong;
import com.taobao.tddl.dbsync.binlog.DirectLogFetcher;
import com.taobao.tddl.dbsync.binlog.LogEvent;
public class FetcherPerformanceTest {
public static void main(String args[]) {
DirectLogFetcher fetcher = new DirectLogFetcher();
try {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306",
"root",
"hello");
Statement statement = connection.createStatement();
statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'");
statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
fetcher.open(connection, "mysql-bin.000006", 120L, 2);
AtomicLong sum = new AtomicLong(0);
long start = System.currentTimeMillis();
long last = 0;
long end = 0;
while (fetcher.fetch()) {
sum.incrementAndGet();
long current = sum.get();
if (current - last >= 100000) {
end = System.currentTimeMillis();
long tps = ((current - last) * 1000) / (end - start);
System.out.println(" total : " + sum + " , cost : " + (end - start) + " , tps : " + tps);
last = current;
start = end;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
fetcher.close();
} catch (IOException e) {
}
}
}
}
@@ -8,15 +8,6 @@ import java.sql.Statement;
import org.junit.Assert;
import org.junit.Test;
import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent;
import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent;
import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent;
import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent;
import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent;
import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent;
import com.taobao.tddl.dbsync.binlog.event.XidLogEvent;
import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent;
public class DirectLogFetcherTest extends BaseLogFetcherTest {
@Test
@@ -24,56 +15,54 @@ public class DirectLogFetcherTest extends BaseLogFetcherTest {
DirectLogFetcher fecther = new DirectLogFetcher();
try {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306", "root", "hello");
Connection connection = DriverManager.getConnection("jdbc:mysql://100.81.154.142:3306", "root", "hello");
Statement statement = connection.createStatement();
statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'");
statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
fecther.open(connection, "mysql-bin.000001", 4L, 2);
fecther.open(connection, "mysql-bin.000006", 120L, 2);
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT);
LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.UNKNOWN_EVENT);
LogContext context = new LogContext();
while (fecther.fetch()) {
LogEvent event = null;
event = decoder.decode(fecther, context);
if (event == null) {
continue;
// throw new RuntimeException("parse failed");
}
int eventType = event.getHeader().getType();
switch (eventType) {
case LogEvent.ROTATE_EVENT:
binlogFileName = ((RotateLogEvent) event).getFilename();
break;
case LogEvent.WRITE_ROWS_EVENT_V1:
case LogEvent.WRITE_ROWS_EVENT:
parseRowsEvent((WriteRowsLogEvent) event);
break;
case LogEvent.UPDATE_ROWS_EVENT_V1:
case LogEvent.UPDATE_ROWS_EVENT:
parseRowsEvent((UpdateRowsLogEvent) event);
break;
case LogEvent.DELETE_ROWS_EVENT_V1:
case LogEvent.DELETE_ROWS_EVENT:
parseRowsEvent((DeleteRowsLogEvent) event);
break;
case LogEvent.QUERY_EVENT:
parseQueryEvent((QueryLogEvent) event);
break;
case LogEvent.ROWS_QUERY_LOG_EVENT:
parseRowsQueryEvent((RowsQueryLogEvent) event);
break;
case LogEvent.ANNOTATE_ROWS_EVENT:
parseAnnotateRowsEvent((AnnotateRowsEvent) event);
break;
case LogEvent.XID_EVENT:
parseXidEvent((XidLogEvent) event);
break;
default:
break;
}
decoder.decode(fecther, context);
continue;
// if (event == null) {
// continue;
// }
//
// int eventType = event.getHeader().getType();
// switch (eventType) {
// case LogEvent.ROTATE_EVENT:
// binlogFileName = ((RotateLogEvent) event).getFilename();
// break;
// case LogEvent.WRITE_ROWS_EVENT_V1:
// case LogEvent.WRITE_ROWS_EVENT:
// parseRowsEvent((WriteRowsLogEvent) event);
// break;
// case LogEvent.UPDATE_ROWS_EVENT_V1:
// case LogEvent.UPDATE_ROWS_EVENT:
// parseRowsEvent((UpdateRowsLogEvent) event);
// break;
// case LogEvent.DELETE_ROWS_EVENT_V1:
// case LogEvent.DELETE_ROWS_EVENT:
// parseRowsEvent((DeleteRowsLogEvent) event);
// break;
// case LogEvent.QUERY_EVENT:
// parseQueryEvent((QueryLogEvent) event);
// break;
// case LogEvent.ROWS_QUERY_LOG_EVENT:
// parseRowsQueryEvent((RowsQueryLogEvent) event);
// break;
// case LogEvent.ANNOTATE_ROWS_EVENT:
// parseAnnotateRowsEvent((AnnotateRowsEvent) event);
// break;
// case LogEvent.XID_EVENT:
// parseXidEvent((XidLogEvent) event);
// break;
// default:
// break;
// }
}
} catch (Exception e) {
e.printStackTrace();
+9 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId>
<version>1.0.22</version>
<version>1.0.26-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.alibaba.otter</groupId>
@@ -16,6 +16,14 @@
<artifactId>canal.server</artifactId>
<version>${project.version}</version>
</dependency>
<!-- 这里指定runtime的metrics provider-->
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.prometheus</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
sh stop.sh
sh startup.sh
@@ -16,6 +16,7 @@ public class CanalConstants {
public static final String CANAL_IP = ROOT + "." + "ip";
public static final String CANAL_PORT = ROOT + "." + "port";
public static final String CANAL_ZKSERVERS = ROOT + "." + "zkServers";
public static final String CANAL_WITHOUT_NETTY = ROOT + "." + "withoutNetty";
public static final String CANAL_DESTINATIONS = ROOT + "." + "destinations";
public static final String CANAL_AUTO_SCAN = ROOT + "." + "auto.scan";
@@ -32,6 +33,8 @@ public class CanalConstants {
public static final String CANAL_DESTINATION_PROPERTY = ROOT + ".instance.destination";
public static final String CANAL_SOCKETCHANNEL = ROOT + "." + "socketChannel";
public static String getInstanceModeKey(String destination) {
return MessageFormat.format(INSTANCE_MODE_TEMPLATE, destination);
}
@@ -5,6 +5,7 @@ import java.util.Properties;
import org.I0Itec.zkclient.IZkStateListener;
import org.I0Itec.zkclient.exception.ZkNoNodeException;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.zookeeper.Watcher.Event.KeeperState;
@@ -33,6 +34,7 @@ import com.alibaba.otter.canal.instance.core.CanalInstanceGenerator;
import com.alibaba.otter.canal.instance.manager.CanalConfigClient;
import com.alibaba.otter.canal.instance.manager.ManagerCanalInstanceGenerator;
import com.alibaba.otter.canal.instance.spring.SpringCanalInstanceGenerator;
import com.alibaba.otter.canal.parse.CanalEventParser;
import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded;
import com.alibaba.otter.canal.server.exception.CanalServerException;
import com.alibaba.otter.canal.server.netty.CanalServerWithNetty;
@@ -42,7 +44,7 @@ import com.google.common.collect.MigrateMap;
/**
* canal调度控制器
*
*
* @author jianghang 2012-11-8 下午12:03:11
* @version 1.0.0
*/
@@ -84,15 +86,24 @@ public class CanalController {
// 初始化instance config
initInstanceConfig(properties);
// init socketChannel
String socketChannel = getProperty(properties, CanalConstants.CANAL_SOCKETCHANNEL);
if (StringUtils.isNotEmpty(socketChannel)) {
System.setProperty(CanalConstants.CANAL_SOCKETCHANNEL, socketChannel);
}
// 准备canal server
cid = Long.valueOf(getProperty(properties, CanalConstants.CANAL_ID));
ip = getProperty(properties, CanalConstants.CANAL_IP);
port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT));
embededCanalServer = CanalServerWithEmbedded.instance();
embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator
canalServer = CanalServerWithNetty.instance();
canalServer.setIp(ip);
canalServer.setPort(port);
String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY);
if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) {
canalServer = CanalServerWithNetty.instance();
canalServer.setIp(ip);
canalServer.setPort(port);
}
// 处理下ip为空,默认使用hostIp暴露到zk中
if (StringUtils.isEmpty(ip)) {
@@ -148,6 +159,11 @@ public class CanalController {
public void handleNewSession() throws Exception {
initCid(path);
}
@Override
public void handleSessionEstablishmentError(Throwable error) throws Exception {
logger.error("failed to connect to zookeeper", error);
}
});
}
} finally {
@@ -172,6 +188,8 @@ public class CanalController {
if (zkclientx != null) {
runningMonitor.setZkClient(zkclientx);
}
// 触发创建一下cid节点
runningMonitor.init();
return runningMonitor;
}
}));
@@ -189,10 +207,10 @@ public class CanalController {
instanceConfigs.put(destination, config);
}
if (!config.getLazy() && !embededCanalServer.isStart(destination)) {
if (!embededCanalServer.isStart(destination)) {
// HA机制启动
ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination);
if (!runningMonitor.isStart()) {
if (!config.getLazy() && !runningMonitor.isStart()) {
runningMonitor.start();
}
}
@@ -286,7 +304,7 @@ public class CanalController {
return instanceGenerator.generate(destination);
} else if (config.getMode().isSpring()) {
SpringCanalInstanceGenerator instanceGenerator = new SpringCanalInstanceGenerator();
synchronized (this) {
synchronized (CanalEventParser.class) {
try {
// 设置当前正在加载的通道,加载spring查找文件时会用到该变量
System.setProperty(CanalConstants.CANAL_DESTINATION_PROPERTY, destination);
@@ -362,7 +380,18 @@ public class CanalController {
}
private String getProperty(Properties properties, String key) {
return StringUtils.trim(properties.getProperty(StringUtils.trim(key)));
key = StringUtils.trim(key);
String value = System.getProperty(key);
if (value == null) {
value = System.getenv(key);
}
if (value == null) {
value = properties.getProperty(key);
}
return StringUtils.trim(value);
}
public void start() throws Throwable {
@@ -380,6 +409,11 @@ public class CanalController {
public void handleNewSession() throws Exception {
initCid(path);
}
@Override
public void handleSessionEstablishmentError(Throwable error) throws Exception {
logger.error("failed to connect to zookeeper", error);
}
});
}
// 优先启动embeded服务
@@ -389,10 +423,10 @@ public class CanalController {
final String destination = entry.getKey();
InstanceConfig config = entry.getValue();
// 创建destination的工作节点
if (!config.getLazy() && !embededCanalServer.isStart(destination)) {
if (!embededCanalServer.isStart(destination)) {
// HA机制启动
ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination);
if (!runningMonitor.isStart()) {
if (!config.getLazy() && !runningMonitor.isStart()) {
runningMonitor.start();
}
}
@@ -412,11 +446,15 @@ public class CanalController {
}
// 启动网络接口
canalServer.start();
if (canalServer != null) {
canalServer.start();
}
}
public void stop() throws Throwable {
canalServer.stop();
if (canalServer != null) {
canalServer.stop();
}
if (autoScan) {
for (InstanceConfigMonitor monitor : instanceConfigMonitors.values()) {
@@ -435,6 +473,10 @@ public class CanalController {
// 释放canal的工作节点
releaseCid(ZookeeperPathUtils.getCanalClusterNode(ip + ":" + port));
logger.info("## stop the canal server[{}:{}]", ip, port);
if (zkclientx != null) {
zkclientx.close();
}
}
private void initCid(String path) {
@@ -448,6 +490,9 @@ public class CanalController {
String parentDir = path.substring(0, path.lastIndexOf('/'));
zkclientx.createPersistent(parentDir, true);
zkclientx.createEphemeral(path);
} catch (ZkNodeExistsException e) {
// ignore
// 因为第一次启动时创建了cid,但在stop/start的时可能会关闭和新建,允许出现NodeExists问题s
}
}
@@ -4,7 +4,6 @@ import java.io.FileInputStream;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,6 +20,10 @@ public class CanalLauncher {
public static void main(String[] args) throws Throwable {
try {
logger.info("## set default uncaught exception handler");
setGlobalUncaughtExceptionHandler();
logger.info("## load canal configurations");
String conf = System.getProperty("canal.conf", "classpath:canal.properties");
Properties properties = new Properties();
if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
@@ -41,8 +44,7 @@ public class CanalLauncher {
logger.info("## stop the canal server");
controller.stop();
} catch (Throwable e) {
logger.warn("##something goes wrong when stopping canal Server:\n{}",
ExceptionUtils.getFullStackTrace(e));
logger.warn("##something goes wrong when stopping canal Server:", e);
} finally {
logger.info("## canal server is down.");
}
@@ -50,9 +52,18 @@ public class CanalLauncher {
});
} catch (Throwable e) {
logger.error("## Something goes wrong when starting up the canal Server:\n{}",
ExceptionUtils.getFullStackTrace(e));
logger.error("## Something goes wrong when starting up the canal Server:", e);
System.exit(0);
}
}
private static void setGlobalUncaughtExceptionHandler() {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
logger.error("UnCaughtException", e);
}
});
}
}
@@ -13,7 +13,6 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
@@ -49,6 +48,8 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1,
new NamedThreadFactory("canal-instance-scan"));
private volatile boolean isFirst = true;
public void start() {
super.start();
Assert.notNull(rootConf, "root conf dir is null!");
@@ -58,6 +59,9 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
public void run() {
try {
scan();
if (isFirst) {
isFirst = false;
}
} catch (Throwable e) {
logger.error("scan failed", e);
}
@@ -122,13 +126,18 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
if (!actions.containsKey(destination) && instanceConfigs.length > 0) {
// 存在合法的instance.properties,并且第一次添加时,进行启动操作
notifyStart(instanceDir, destination);
notifyStart(instanceDir, destination, instanceConfigs);
} else if (actions.containsKey(destination)) {
// 历史已经启动过
if (instanceConfigs.length == 0) { // 如果不存在合法的instance.properties
notifyStop(destination);
} else {
InstanceConfigFiles lastFile = lastFiles.get(destination);
// 历史启动过 所以配置文件信息必然存在
if (!isFirst && CollectionUtils.isEmpty(lastFile.getInstanceFiles())) {
logger.error("[{}] is started, but not found instance file info.", destination);
}
boolean hasChanged = judgeFileChanged(instanceConfigs, lastFile.getInstanceFiles());
// 通知变化
if (hasChanged) {
@@ -161,13 +170,22 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
}
}
private void notifyStart(File instanceDir, String destination) {
private void notifyStart(File instanceDir, String destination, File[] instanceConfigs) {
try {
defaultAction.start(destination);
actions.put(destination, defaultAction);
// 启动成功后记录配置文件信息
InstanceConfigFiles lastFile = lastFiles.get(destination);
List<FileInfo> newFileInfo = new ArrayList<FileInfo>();
for (File instanceConfig : instanceConfigs) {
newFileInfo.add(new FileInfo(instanceConfig.getName(), instanceConfig.lastModified()));
}
lastFile.setInstanceFiles(newFileInfo);
logger.info("auto notify start {} successful.", destination);
} catch (Throwable e) {
logger.error("scan add found[{}] but start failed", destination, ExceptionUtils.getFullStackTrace(e));
logger.error(String.format("scan add found[%s] but start failed", destination), e);
}
}
@@ -175,9 +193,10 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
InstanceAction action = actions.remove(destination);
try {
action.stop(destination);
lastFiles.remove(destination);
logger.info("auto notify stop {} successful.", destination);
} catch (Throwable e) {
logger.error("scan delete found[{}] but stop failed", destination, ExceptionUtils.getFullStackTrace(e));
logger.error(String.format("scan delete found[%s] but stop failed", destination), e);
actions.put(destination, action);// 再重新加回去,下一次scan时再执行删除
}
}
@@ -189,9 +208,7 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
action.reload(destination);
logger.info("auto notify reload {} successful.", destination);
} catch (Throwable e) {
logger.error("scan reload found[{}] but reload failed",
destination,
ExceptionUtils.getFullStackTrace(e));
logger.error(String.format("scan reload found[%s] but reload failed", destination), e);
}
}
}
@@ -294,5 +311,4 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
}
}
}
+22 -1
View File
@@ -3,10 +3,11 @@
#################################################
canal.id= 1
canal.ip=
canal.port= 11111
canal.port=11111
canal.zkServers=
# flush data to zk
canal.zookeeper.flush.period = 1000
canal.withoutNetty = false
# flush meta cursor/parse position to file
canal.file.data.dir = ${canal.conf.dir}
canal.file.flush.period = 1000
@@ -36,11 +37,13 @@ canal.instance.network.sendBufferSize = 16384
canal.instance.network.soTimeout = 30
# binlog filter config
canal.instance.filter.druid.ddl = true
canal.instance.filter.query.dcl = false
canal.instance.filter.query.dml = false
canal.instance.filter.query.ddl = false
canal.instance.filter.table.error = false
canal.instance.filter.rows = false
canal.instance.filter.transaction.entry = false
# binlog format/image check
canal.instance.binlog.format = ROW,STATEMENT,MIXED
@@ -49,6 +52,20 @@ canal.instance.binlog.image = FULL,MINIMAL,NOBLOB
# binlog ddl isolation
canal.instance.get.ddl.isolation = false
# parallel parser config
canal.instance.parser.parallel = true
## concurrent thread number, default 60% available processors, suggest not to exceed Runtime.getRuntime().availableProcessors()
#canal.instance.parser.parallelThreadSize = 16
## disruptor ringbuffer size, must be power of 2
canal.instance.parser.parallelBufferSize = 256
# table meta tsdb info
canal.instance.tsdb.enable=true
canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:}
canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
canal.instance.tsdb.dbUsername=canal
canal.instance.tsdb.dbPassword=canal
#################################################
######### destinations #############
#################################################
@@ -59,9 +76,13 @@ canal.conf.dir = ../conf
canal.auto.scan = true
canal.auto.scan.interval = 5
canal.instance.tsdb.spring.xml=classpath:spring/tsdb/h2-tsdb.xml
#canal.instance.tsdb.spring.xml=classpath:spring/tsdb/mysql-tsdb.xml
canal.instance.global.mode = spring
canal.instance.global.lazy = false
#canal.instance.global.manager.address = 127.0.0.1:1099
#canal.instance.global.spring.xml = classpath:spring/local-instance.xml
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
canal.instance.global.spring.xml = classpath:spring/file-instance.xml
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
@@ -1,27 +1,36 @@
#################################################
## mysql serverId
canal.instance.mysql.slaveId = 1234
## mysql serverId , v1.0.26+ will autoGen
# canal.instance.mysql.slaveId=0
# enable gtid use true/false
canal.instance.gtidon=false
# position info
canal.instance.master.address = 127.0.0.1:3306
canal.instance.master.journal.name =
canal.instance.master.position =
canal.instance.master.timestamp =
canal.instance.master.address=127.0.0.1:3306
canal.instance.master.journal.name=
canal.instance.master.position=
canal.instance.master.timestamp=
canal.instance.master.gtid=
#canal.instance.standby.address =
# table meta tsdb info
canal.instance.tsdb.enable=true
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
#canal.instance.tsdb.dbUsername=canal
#canal.instance.tsdb.dbPassword=canal
#canal.instance.standby.address =
#canal.instance.standby.journal.name =
#canal.instance.standby.position =
#canal.instance.standby.timestamp =
#canal.instance.standby.timestamp =
#canal.instance.standby.gtid=
# username/password
canal.instance.dbUsername = canal
canal.instance.dbPassword = canal
canal.instance.defaultDatabaseName =
canal.instance.connectionCharset = UTF-8
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal
canal.instance.connectionCharset=UTF-8
# table regex
canal.instance.filter.regex = .*\\..*
canal.instance.filter.regex=.*\\..*
# table black regex
canal.instance.filter.black.regex =
canal.instance.filter.black.regex=
#################################################
@@ -0,0 +1,32 @@
#################################################
# rds openapi binlog
canal.instance.rds.open.url=https://rds.aliyuncs.com/
canal.instance.rds.open.accesskey=
canal.instance.rds.open.secretkey=
canal.instance.rds.instanceId=
canal.instance.rds.startTime=
canal.instance.rds.endTime=
# local binlog dir
canal.instance.parser.directory=${canal.file.data.dir:../conf}/${canal.instance.destination:}/binlog
# position info
canal.instance.master.address=127.0.0.1:3306
# table meta tsdb info
canal.instance.tsdb.enable=true
canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:}
canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
#canal.instance.tsdb.dbUsername=canal
#canal.instance.tsdb.dbPassword=canal
# username/password
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal
canal.instance.defaultDatabaseName=test
canal.instance.connectionCharset=UTF-8
# table regex
canal.instance.filter.regex=test\\..*
# table black regex
canal.instance.filter.black.regex=
#################################################
+5 -3
View File
@@ -70,14 +70,16 @@
<appender-ref ref="CANAL-ROOT" />
</logger>
<logger name="com.alibaba.otter.canal.meta.FileMixedMetaManager" additivity="false">
<level value="INFO" />
<level value="INFO" />
<appender-ref ref="CANAL-META" />
</logger>
<logger name="com.alibaba.otter.canal.kafka" additivity="false">
<level value="INFO" />
<appender-ref ref="CANAL-ROOT" />
</logger>
<root level="WARN">
<!--
<appender-ref ref="STDOUT"/>
-->
<appender-ref ref="CANAL-ROOT" />
</root>
</configuration>
@@ -81,11 +81,12 @@
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
<property name="eventStore" ref="eventStore" />
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
</bean>
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
<property name="destination" value="${canal.instance.destination}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
<!-- 心跳配置 -->
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
@@ -128,14 +129,14 @@
<!-- 解析位点记录 -->
<property name="logPositionManager">
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
<property name="primary">
<constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
</property>
<property name="failback">
</constructor-arg>
<constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
<property name="metaManager" ref="metaManager" />
<constructor-arg ref="metaManager"/>
</bean>
</property>
</constructor-arg>
</bean>
</property>
@@ -148,7 +149,7 @@
<property name="address" value="${canal.instance.master.address}" />
<property name="username" value="${canal.instance.dbUsername:retl}" />
<property name="password" value="${canal.instance.dbPassword:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
</bean>
</property>
<property name="standbyInfo">
@@ -156,7 +157,7 @@
<property name="address" value="${canal.instance.standby.address}" />
<property name="username" value="${canal.instance.dbUsername:retl}" />
<property name="password" value="${canal.instance.dbPassword:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
</bean>
</property>
@@ -166,6 +167,7 @@
<property name="journalName" value="${canal.instance.master.journal.name}" />
<property name="position" value="${canal.instance.master.position}" />
<property name="timestamp" value="${canal.instance.master.timestamp}" />
<property name="gtid" value="${canal.instance.master.gtid}" />
</bean>
</property>
<property name="standbyPosition">
@@ -173,14 +175,28 @@
<property name="journalName" value="${canal.instance.standby.journal.name}" />
<property name="position" value="${canal.instance.standby.position}" />
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
<property name="gtid" value="${canal.instance.standby.gtid}" />
</bean>
</property>
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
<!--表结构相关-->
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
<!--是否启用GTID模式-->
<property name="isGTIDMode" value="${canal.instance.gtidon:false}"/>
<!-- parallel parser -->
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
</bean>
</beans>
@@ -67,11 +67,12 @@
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
<property name="eventStore" ref="eventStore" />
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
</bean>
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
<property name="destination" value="${canal.instance.destination}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
<!-- 心跳配置 -->
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
@@ -113,14 +114,14 @@
<!-- 解析位点记录 -->
<property name="logPositionManager">
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
<property name="primary">
<constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
</property>
<property name="failback">
</constructor-arg>
<constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
<property name="metaManager" ref="metaManager" />
<constructor-arg ref="metaManager"/>
</bean>
</property>
</constructor-arg>
</bean>
</property>
@@ -133,7 +134,7 @@
<property name="address" value="${canal.instance.master.address}" />
<property name="username" value="${canal.instance.dbUsername:retl}" />
<property name="password" value="${canal.instance.dbPassword:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
</bean>
</property>
<property name="standbyInfo">
@@ -141,7 +142,7 @@
<property name="address" value="${canal.instance.standby.address}" />
<property name="username" value="${canal.instance.dbUsername:retl}" />
<property name="password" value="${canal.instance.dbPassword:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
</bean>
</property>
@@ -151,6 +152,7 @@
<property name="journalName" value="${canal.instance.master.journal.name}" />
<property name="position" value="${canal.instance.master.position}" />
<property name="timestamp" value="${canal.instance.master.timestamp}" />
<property name="gtid" value="${canal.instance.master.gtid}" />
</bean>
</property>
<property name="standbyPosition">
@@ -158,14 +160,28 @@
<property name="journalName" value="${canal.instance.standby.journal.name}" />
<property name="position" value="${canal.instance.standby.position}" />
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
<property name="gtid" value="${canal.instance.standby.gtid}" />
</bean>
</property>
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
<!--表结构相关-->
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
<!--是否启用GTID模式-->
<property name="isGTIDMode" value="${canal.instance.gtidon:false}"/>
<!-- parallel parser -->
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
</bean>
</beans>
@@ -64,6 +64,7 @@
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
<property name="eventStore" ref="eventStore" />
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
</bean>
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.group.GroupEventParser">
@@ -77,7 +78,7 @@
<bean id="eventParser1" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
<property name="destination" value="${canal.instance.destination}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
<!-- 心跳配置 -->
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
@@ -130,7 +131,7 @@
<property name="address" value="${canal.instance.master1.address}" />
<property name="username" value="${canal.instance.dbUsername:retl}" />
<property name="password" value="${canal.instance.dbPassword:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
</bean>
</property>
<property name="standbyInfo">
@@ -138,7 +139,7 @@
<property name="address" value="${canal.instance.standby1.address}" />
<property name="username" value="${canal.instance.dbUsername:retl}" />
<property name="password" value="${canal.instance.dbPassword:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
</bean>
</property>
@@ -148,6 +149,7 @@
<property name="journalName" value="${canal.instance.master1.journal.name}" />
<property name="position" value="${canal.instance.master1.position}" />
<property name="timestamp" value="${canal.instance.master1.timestamp}" />
<property name="gtid" value="${canal.instance.master1.gtid}" />
</bean>
</property>
<property name="standbyPosition">
@@ -155,19 +157,26 @@
<property name="journalName" value="${canal.instance.standby1.journal.name}" />
<property name="position" value="${canal.instance.standby1.position}" />
<property name="timestamp" value="${canal.instance.standby1.timestamp}" />
<property name="gtid" value="${canal.instance.standby1.gtid}" />
</bean>
</property>
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
<!-- parallel parser -->
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
</bean>
<bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
<property name="destination" value="${canal.instance.destination}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
<!-- 心跳配置 -->
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
@@ -220,7 +229,7 @@
<property name="address" value="${canal.instance.master2.address}" />
<property name="username" value="${canal.instance.dbUsername:retl}" />
<property name="password" value="${canal.instance.dbPassword:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
</bean>
</property>
<property name="standbyInfo">
@@ -228,7 +237,7 @@
<property name="address" value="${canal.instance.standby2.address}" />
<property name="username" value="${canal.instance.dbUsername:retl}" />
<property name="password" value="${canal.instance.dbPassword:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
</bean>
</property>
@@ -238,6 +247,7 @@
<property name="journalName" value="${canal.instance.master2.journal.name}" />
<property name="position" value="${canal.instance.master2.position}" />
<property name="timestamp" value="${canal.instance.master2.timestamp}" />
<property name="gtid" value="${canal.instance.master2.gtid}" />
</bean>
</property>
<property name="standbyPosition">
@@ -245,14 +255,21 @@
<property name="journalName" value="${canal.instance.standby2.journal.name}" />
<property name="position" value="${canal.instance.standby2.position}" />
<property name="timestamp" value="${canal.instance.standby2.timestamp}" />
<property name="gtid" value="${canal.instance.standby2.gtid}" />
</bean>
</property>
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
<!-- parallel parser -->
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
</bean>
</beans>
@@ -0,0 +1,147 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName">
<!-- properties -->
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
<property name="ignoreResourceNotFound" value="true" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
<property name="locationNames">
<list>
<value>classpath:canal.properties</value>
<value>classpath:${canal.instance.destination:}/instance.properties</value>
</list>
</property>
</bean>
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="socketAddressEditor" />
</list>
</property>
</bean>
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
<property name="destination" value="${canal.instance.destination}" />
<property name="eventParser">
<ref local="eventParser" />
</property>
<property name="eventSink">
<ref local="eventSink" />
</property>
<property name="eventStore">
<ref local="eventStore" />
</property>
<property name="metaManager">
<ref local="metaManager" />
</property>
<property name="alarmHandler">
<ref local="alarmHandler" />
</property>
</bean>
<!-- 报警处理类 -->
<bean id="alarmHandler" class="com.alibaba.otter.canal.common.alarm.LogAlarmHandler" />
<bean id="metaManager" class="com.alibaba.otter.canal.meta.FileMixedMetaManager">
<property name="dataDir" value="${canal.file.data.dir:../conf}" />
<property name="period" value="${canal.file.flush.period:1000}" />
</bean>
<bean id="eventStore" class="com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer">
<property name="bufferSize" value="${canal.instance.memory.buffer.size:16384}" />
<property name="bufferMemUnit" value="${canal.instance.memory.buffer.memunit:1024}" />
<property name="batchMode" value="${canal.instance.memory.batch.mode:MEMSIZE}" />
<property name="ddlIsolation" value="${canal.instance.get.ddl.isolation:false}" />
</bean>
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
<property name="eventStore" ref="eventStore" />
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
</bean>
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsLocalBinlogEventParser">
<property name="destination" value="${canal.instance.destination}" />
<property name="alarmHandler" ref="alarmHandler" />
<!-- 解析过滤处理 -->
<property name="eventFilter">
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
<constructor-arg index="0" value="${canal.instance.filter.regex:.*\..*}" />
</bean>
</property>
<property name="eventBlackFilter">
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
<constructor-arg index="0" value="${canal.instance.filter.black.regex:}" />
<constructor-arg index="1" value="false" />
</bean>
</property>
<!-- 最大事务解析大小,超过该大小后事务将被切分为多个事务投递 -->
<property name="transactionSize" value="${canal.instance.transaction.size:1024}" />
<!-- 解析编码 -->
<property name="connectionCharset" value="${canal.instance.connectionCharset:UTF-8}" />
<!-- 解析位点记录 -->
<property name="logPositionManager">
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
<constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
</constructor-arg>
<constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
<constructor-arg ref="metaManager"/>
</bean>
</constructor-arg>
</bean>
</property>
<!-- 解析数据库信息 -->
<property name="masterInfo">
<bean class="com.alibaba.otter.canal.parse.support.AuthenticationInfo">
<property name="address" value="${canal.instance.master.address}" />
<property name="username" value="${canal.instance.dbUsername:retl}" />
<property name="password" value="${canal.instance.dbPassword:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
</bean>
</property>
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
<property name="needWait" value="${canal.instance.parser.needWait:false}"/>
<property name="directory" value="${canal.instance.parser.directory:}"/>
<!-- rds相关 -->
<property name="url" value="${canal.instance.rds.open.url:}"/>
<property name="accesskey" value="${canal.instance.rds.open.accesskey:}"/>
<property name="secretkey" value="${canal.instance.rds.open.secretkey:}"/>
<property name="instanceId" value="${canal.instance.rds.instanceId:}"/>
<property name="startTime" value="${canal.instance.rds.startTime:}"/>
<property name="endTime" value="${canal.instance.rds.endTime:}"/>
<!--表结构相关-->
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
<!-- parallel parser -->
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
</bean>
</beans>
@@ -64,11 +64,12 @@
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
<property name="eventStore" ref="eventStore" />
<property name="filterTransactionEntry" value="${canal.instance.filter.transaction.entry:false}"/>
</bean>
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
<property name="destination" value="${canal.instance.destination}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
<!-- 心跳配置 -->
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
<property name="detectingSQL" value="${canal.instance.detecting.sql}" />
@@ -121,7 +122,7 @@
<property name="address" value="${canal.instance.master.address}" />
<property name="username" value="${canal.instance.dbUsername:retl}" />
<property name="password" value="${canal.instance.dbPassword:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
</bean>
</property>
<property name="standbyInfo">
@@ -129,7 +130,7 @@
<property name="address" value="${canal.instance.standby.address}" />
<property name="username" value="${canal.instance.dbUsername:retl}" />
<property name="password" value="${canal.instance.dbPassword:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:test}" />
</bean>
</property>
@@ -139,6 +140,7 @@
<property name="journalName" value="${canal.instance.master.journal.name}" />
<property name="position" value="${canal.instance.master.position}" />
<property name="timestamp" value="${canal.instance.master.timestamp}" />
<property name="gtid" value="${canal.instance.master.gtid}" />
</bean>
</property>
<property name="standbyPosition">
@@ -146,14 +148,28 @@
<property name="journalName" value="${canal.instance.standby.journal.name}" />
<property name="position" value="${canal.instance.standby.position}" />
<property name="timestamp" value="${canal.instance.standby.timestamp}" />
<property name="gtid" value="${canal.instance.standby.gtid}" />
</bean>
</property>
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
<!--表结构相关-->
<property name="enableTsdb" value="${canal.instance.tsdb.enable:false}"/>
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
<!--是否启用GTID模式-->
<property name="isGTIDMode" value="${canal.instance.gtidon:false}"/>
<!-- parallel parser -->
<property name="parallel" value="${canal.instance.parser.parallel:true}" />
<property name="parallelThreadSize" value="${canal.instance.parser.parallelThreadSize}" />
<property name="parallelBufferSize" value="${canal.instance.parser.parallelBufferSize:256}" />
</bean>
</beans>
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName">
<!-- properties -->
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
<property name="ignoreResourceNotFound" value="true" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
<property name="locationNames">
<list>
<value>classpath:canal.properties</value>
<value>classpath:${canal.instance.destination:}/instance.properties</value>
</list>
</property>
</bean>
<!-- 基于db的实现 -->
<bean id="tableMetaTSDB" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta">
<property name="metaHistoryDAO" ref="metaHistoryDAO"/>
<property name="metaSnapshotDAO" ref="metaSnapshotDAO"/>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="${canal.instance.tsdb.url:}" />
<property name="username" value="${canal.instance.tsdb.dbUsername:}" />
<property name="password" value="${canal.instance.tsdb.dbPassword:}" />
<property name="maxActive" value="30" />
<property name="initialSize" value="0" />
<property name="minIdle" value="1" />
<property name="maxWait" value="10000" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="useUnfairLock" value="true" />
</bean>
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:spring/tsdb/sql-map/sqlmap-config.xml"/>
</bean>
<bean id="metaHistoryDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDAO">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
<bean id="metaSnapshotDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDAO">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
</beans>
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName">
<!-- properties -->
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
<property name="ignoreResourceNotFound" value="true" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
<property name="locationNames">
<list>
<value>classpath:canal.properties</value>
<value>classpath:${canal.instance.destination:}/instance.properties</value>
</list>
</property>
</bean>
<!-- 基于db的实现 -->
<bean id="tableMetaTSDB" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta">
<property name="metaHistoryDAO" ref="metaHistoryDAO"/>
<property name="metaSnapshotDAO" ref="metaSnapshotDAO"/>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="${canal.instance.tsdb.url:}" />
<property name="username" value="${canal.instance.tsdb.dbUsername:}" />
<property name="password" value="${canal.instance.tsdb.dbPassword:}" />
<property name="maxActive" value="30" />
<property name="initialSize" value="0" />
<property name="minIdle" value="1" />
<property name="maxWait" value="10000" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 1" />
<property name="exceptionSorterClassName" value="com.alibaba.druid.pool.vendor.MySqlExceptionSorter" />
<property name="validConnectionCheckerClassName" value="com.alibaba.druid.pool.vendor.MySqlValidConnectionChecker" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="useUnfairLock" value="true" />
</bean>
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:spring/tsdb/sql-map/sqlmap-config.xml"/>
</bean>
<bean id="metaHistoryDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDAO">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
<bean id="metaSnapshotDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDAO">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
</beans>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<settings useStatementNamespaces="true"/>
<sqlMap resource="spring/tsdb/sql-map/sqlmap_history.xml"/>
<sqlMap resource="spring/tsdb/sql-map/sqlmap_snapshot.xml"/>
</sqlMapConfig>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd" >
<sqlMap namespace="meta_history">
<typeAlias alias="metaHistoryDO" type="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDO"/>
<sql id="allColumns">
<![CDATA[
gmt_create,gmt_modified,destination,binlog_file,binlog_offest,binlog_master_id,binlog_timestamp,use_schema,sql_schema,sql_table,sql_text,sql_type,extra
]]>
</sql>
<sql id="allVOColumns">
<![CDATA[
a.id as id,a.gmt_create as gmtCreate,a.gmt_modified as gmtModified,
a.destination as destination,a.binlog_file as binlogFile,a.binlog_offest as binlogOffest,a.binlog_master_id as binlogMasterId,a.binlog_timestamp as binlogTimestamp,
a.use_schema as useSchema,a.sql_schema as sqlSchema,a.sql_table as sqlTable,a.sql_text as sqlText,a.sql_type as sqlType,a.extra as extra
]]>
</sql>
<select id="findByTimestamp" parameterClass="java.util.Map" resultClass="metaHistoryDO">
select
<include refid="allVOColumns"/>
from meta_history a
<![CDATA[
where destination = #destination# and binlog_timestamp >= #snapshotTimestamp# and binlog_timestamp <= #timestamp#
order by binlog_timestamp asc,id asc
]]>
</select>
<insert id="insert" parameterClass="metaHistoryDO">
insert into meta_history (<include refid="allColumns"/>)
values(CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,#destination#,#binlogFile#,#binlogOffest#,#binlogMasterId#,#binlogTimestamp#,#useSchema#,#sqlSchema#,#sqlTable#,#sqlText#,#sqlType#,#extra#)
</insert>
<delete id="deleteByName" parameterClass="java.util.Map">
delete from meta_history
where destination=#destination#
</delete>
<delete id="deleteByGmtModified" parameterClass="java.util.Map">
<![CDATA[
delete from meta_history
where gmt_modified < timestamp(#timestamp#)
]]>
</delete>
</sqlMap>
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd" >
<sqlMap namespace="meta_snapshot">
<typeAlias alias="metaSnapshotDO" type="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDO"/>
<typeAlias alias="tableMetaSnapshotDO"
type="com.alibaba.middleware.jingwei.biz.dataobject.CanalTableMetaSnapshotDO"/>
<sql id="allColumns">
<![CDATA[
gmt_create,gmt_modified,destination,binlog_file,binlog_offest,binlog_master_id,binlog_timestamp,data,extra
]]>
</sql>
<sql id="allVOColumns">
<![CDATA[
a.id as id,a.gmt_create as gmtCreate,a.gmt_modified as gmtModified,
a.destination as destination,a.binlog_file as binlogFile,a.binlog_offest as binlogOffest,a.binlog_master_id as binlogMasterId,a.binlog_timestamp as binlogTimestamp,a.data as data,a.extra as extra
]]>
</sql>
<select id="findByTimestamp" parameterClass="java.util.Map" resultClass="metaSnapshotDO">
select <include refid="allVOColumns"/>
<![CDATA[
from meta_snapshot a
where destination = #destination# and binlog_timestamp < #timestamp#
order by binlog_timestamp desc,id desc
limit 1
]]>
</select>
<insert id="insert" parameterClass="metaSnapshotDO">
insert into meta_snapshot (<include refid="allColumns"/>)
values(CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,#destination#,#binlogFile#,#binlogOffest#,#binlogMasterId#,#binlogTimestamp#,#data#,#extra#)
</insert>
<update id="update" parameterClass="metaSnapshotDO">
update meta_snapshot set gmt_modified=now(),
binlog_file=#binlogFile#,binlog_offest=#binlogOffest#,binlog_master_id=#binlogMasterId#,binlog_timestamp=#binlogTimestamp#,data=#data#,extra=#extra#
where destination=#destination# and binlog_timestamp=0
</update>
<delete id="deleteByName" parameterClass="java.util.Map">
delete from meta_snapshot
where destination=#destination#
</delete>
<delete id="deleteByGmtModified" parameterClass="java.util.Map">
<![CDATA[
delete from meta_snapshot
where gmt_modified < timestamp(#timestamp#)
]]>
</delete>
</sqlMap>
@@ -0,0 +1,39 @@
CREATE TABLE IF NOT EXISTS `meta_snapshot` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`destination` varchar(128) DEFAULT NULL COMMENT '通道名称',
`binlog_file` varchar(64) DEFAULT NULL COMMENT 'binlog文件名',
`binlog_offest` bigint(20) DEFAULT NULL COMMENT 'binlog偏移量',
`binlog_master_id` varchar(64) DEFAULT NULL COMMENT 'binlog节点id',
`binlog_timestamp` bigint(20) DEFAULT NULL COMMENT 'binlog应用的时间戳',
`data` longtext DEFAULT NULL COMMENT '表结构数据',
`extra` text DEFAULT NULL COMMENT '额外的扩展信息',
PRIMARY KEY (`id`),
UNIQUE KEY binlog_file_offest(`destination`,`binlog_master_id`,`binlog_file`,`binlog_offest`),
KEY `destination` (`destination`),
KEY `destination_timestamp` (`destination`,`binlog_timestamp`),
KEY `gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='表结构记录表快照表';
CREATE TABLE IF NOT EXISTS `meta_history` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`destination` varchar(128) DEFAULT NULL COMMENT '通道名称',
`binlog_file` varchar(64) DEFAULT NULL COMMENT 'binlog文件名',
`binlog_offest` bigint(20) DEFAULT NULL COMMENT 'binlog偏移量',
`binlog_master_id` varchar(64) DEFAULT NULL COMMENT 'binlog节点id',
`binlog_timestamp` bigint(20) DEFAULT NULL COMMENT 'binlog应用的时间戳',
`use_schema` varchar(1024) DEFAULT NULL COMMENT '执行sql时对应的schema',
`schema` varchar(1024) DEFAULT NULL COMMENT '对应的schema',
`table` varchar(1024) DEFAULT NULL COMMENT '对应的table',
`sql` longtext DEFAULT NULL COMMENT '执行的sql',
`type` varchar(256) DEFAULT NULL COMMENT 'sql类型',
`extra` text DEFAULT NULL COMMENT '额外的扩展信息',
PRIMARY KEY (`id`),
UNIQUE KEY binlog_file_offest(`destination`,`binlog_master_id`,`binlog_file`,`binlog_offest`),
KEY `destination` (`destination`),
KEY `destination_timestamp` (`destination`,`binlog_timestamp`),
KEY `gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='表结构变化明细表';
+67
View File
@@ -0,0 +1,67 @@
FROM centos:centos6.7
MAINTAINER agapple (jianghang115@gmail.com)
# install system
RUN \
/bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo 'root:Hello1234' | chpasswd && \
groupadd -r admin && useradd -g admin admin && \
yum install -y man && \
yum install -y dstat && \
yum install -y unzip && \
yum install -y nc && \
yum install -y openssh-server && \
yum install -y tar && \
yum install -y which && \
yum install -y wget && \
yum install -y perl && \
yum install -y file && \
ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key && \
ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key && \
sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd && \
sed -i -e 's/^#Port 22$/Port 2222/' /etc/ssh/sshd_config && \
mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh && \
yum install -y cronie && \
sed -i '/session required pam_loginuid.so/d' /etc/pam.d/crond && \
yum clean all && \
true
# install canal
COPY image/ /tmp/docker/
COPY canal.deployer-*.tar.gz /home/admin/
COPY jdk-8-linux-x64.rpm /tmp/
RUN \
cp -R /tmp/docker/alidata /alidata && \
chmod +x /alidata/bin/* && \
mkdir -p /home/admin && \
cp -R /tmp/docker/admin/* /home/admin/ && \
/bin/cp -f alidata/bin/lark-wait /usr/bin/lark-wait && \
touch /var/lib/rpm/* && \
yum -y install /tmp/jdk-8-linux-x64.rpm && \
/bin/rm -f /tmp/jdk-8-linux-x64.rpm && \
echo "export JAVA_HOME=/usr/java/latest" >> /etc/profile && \
echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile && \
/bin/mv /home/admin/bin/clean_log /etc/cron.d && \
mkdir -p /home/admin/canal-server && \
tar -xzvf /home/admin/canal.deployer-*.tar.gz -C /home/admin/canal-server && \
/bin/rm -f /home/admin/canal.deployer-*.tar.gz && \
mkdir -p home/admin/canal-server/logs && \
chmod +x /home/admin/*.sh && \
chmod +x /home/admin/bin/*.sh && \
chown admin: -R /home/admin && \
yum clean all && \
true
# 2222 sys , 8080 web , 8000 debug , 11111 canal
EXPOSE 2222 11111 8000 8080
WORKDIR /home/admin
ENTRYPOINT [ "/alidata/bin/main.sh" ]
CMD [ "/home/admin/app.sh" ]
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
current_path=`pwd`
case "`uname`" in
Darwin)
bin_abs_path=`cd $(dirname $0); pwd`
;;
Linux)
bin_abs_path=$(readlink -f $(dirname $0))
;;
*)
bin_abs_path=`cd $(dirname $0); pwd`
;;
esac
BASE=${bin_abs_path}
if [ ! -f $BASE/jdk*.rpm ] ; then
DOWNLOAD_LINK="http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.tar.gz"
wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=xxx; oraclelicense=accept-securebackup-cookie" "$DOWNLOAD_LINK" -O $BASE/jdk-8-linux-x64.rpm
fi
cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ;
if [ "$1" == "kafka" ] ; then
cp $BASE/../target/canal-kafka-*.tar.gz $BASE/
docker build --no-cache -t canal/canal-server $BASE/
else
cp $BASE/../target/canal.deployer-*.tar.gz $BASE/
docker build --no-cache -t canal/canal-server $BASE/
fi
+117
View File
@@ -0,0 +1,117 @@
#!/bin/bash
set -e
source /etc/profile
export JAVA_HOME=/usr/java/latest
export PATH=$JAVA_HOME/bin:$PATH
touch /tmp/start.log
chown admin: /tmp/start.log
chown -R admin: /home/admin/canal-server
host=`hostname -i`
# waitterm
# wait TERM/INT signal.
# see: http://veithen.github.io/2014/11/16/sigterm-propagation.html
waitterm() {
local PID
# any process to block
tail -f /dev/null &
PID="$!"
# setup trap, could do nothing, or just kill the blocker
trap "kill -TERM ${PID}" TERM INT
# wait for signal, ignore wait exit code
wait "${PID}" || true
# clear trap
trap - TERM INT
# wait blocker, ignore blocker exit code
wait "${PID}" 2>/dev/null || true
}
# waittermpid "${PIDFILE}".
# monitor process by pidfile && wait TERM/INT signal.
# if the process disappeared, return 1, means exit with ERROR.
# if TERM or INT signal received, return 0, means OK to exit.
waittermpid() {
local PIDFILE PID do_run error
PIDFILE="${1?}"
do_run=true
error=0
trap "do_run=false" TERM INT
while "${do_run}" ; do
PID="$(cat "${PIDFILE}")"
if ! ps -p "${PID}" >/dev/null 2>&1 ; then
do_run=false
error=1
else
sleep 1
fi
done
trap - TERM INT
return "${error}"
}
function checkStart() {
local name=$1
local cmd=$2
local timeout=$3
cost=5
while [ $timeout -gt 0 ]; do
ST=`eval $cmd`
if [ "$ST" == "0" ]; then
sleep 1
let timeout=timeout-1
let cost=cost+1
elif [ "$ST" == "" ]; then
sleep 1
let timeout=timeout-1
let cost=cost+1
else
break
fi
done
echo "start $name successful"
}
function start_canal() {
echo "start canal ..."
serverPort=`perl -le 'print $ENV{"canal.port"}'`
if [ -z "$serverPort" ] ; then
serverPort=11111
fi
destination=`perl -le 'print $ENV{"canal.destinations"}'`
if [[ "$destination" =~ ',' ]]; then
echo "multi destination:$destination is not support"
exit 1;
else
mv /home/admin/canal-server/conf/example /home/admin/canal-server/conf/$destination
fi
su admin -c 'cd /home/admin/canal-server/bin/ && sh restart.sh 1>>/tmp/start.log 2>&1'
sleep 5
#check start
checkStart "canal" "nc 127.0.0.1 $serverPort -w 1 -z | wc -l" 30
}
function stop_canal() {
echo "stop canal"
su admin -c 'cd /home/admin/canal-server/bin/ && sh stop.sh 1>>/tmp/start.log 2>&1'
echo "stop canal successful ..."
}
echo "==> START ..."
start_canal
echo "==> START SUCCESSFUL ..."
tail -f /dev/null &
# wait TERM signal
waitterm
echo "==> STOP"
stop_canal
echo "==> STOP SUCCESSFUL ..."
+2
View File
@@ -0,0 +1,2 @@
# cron clean log once per minute
*/2 * * * * admin /home/admin/bin/clean_log.sh >>/tmp/clean_log.log 2>&1
+45
View File
@@ -0,0 +1,45 @@
#!/bin/bash
# Global Settings
PATH="$HOME/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:/root/bin"
export PATH
CUTOFF="85"
#获取磁盘使用率最高的分区
USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1)
before=$USAGE
baseClean(){
#删除tmp目录15天前的文件。
#更新文档时间戳
if [ -d /tmp/hsperfdata_admin ]
then
touch /tmp/hsperfdata_admin
touch /tmp/hsperfdata_admin/*
fi
find /tmp/ -type f -mtime +15 | xargs -t rm -rf >/dev/null 2>&1
now=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1)
echo "before:$before; now:$now"
}
CANAL_DIR="/home/admin/canal-server/logs"
if [[ -d $CANAL_DIR ]]; then
USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1)
if [[ $USAGE -ge 90 ]]; then
find $CANAL_DIR -type f -mtime +7 | xargs rm -rf {}
fi
USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1)
if [[ $USAGE -ge 80 ]]; then
find $CANAL_DIR -type f -mtime +3 | xargs rm -rf {}
fi
USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1)
if [[ $USAGE -ge 80 ]]; then
find $CANAL_DIR -type d -empty -mtime +3 | grep -v canal | xargs rm -rf {}
find $CANAL_DIR -type f -iname '*.tmp' | xargs rm -rf {}
fi
baseClean
exit 0
fi
+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
CHECK_URL="http://127.0.0.1:8080/metrics"
CHECK_POINT="success"
CHECK_COUNT=`curl -s --connect-timeout 7 --max-time 7 $CHECK_URL | grep -c $CHECK_POINT`
if [ $CHECK_COUNT -eq 0 ]; then
echo "[FAILED]"
status=0
error=1
else
echo "[ OK ]"
status=1
error=0
fi
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
if [ "${SKIP_EXEC_RC_LOCAL}" = "YES" ] ; then
echo "skip /etc/rc.local: SKIP_EXEC_RC_LOCAL=${SKIP_EXEC_RC_LOCAL}"
exit
fi
if [ "${DOCKER_DEPLOY_TYPE}" = "HOST" ] ; then
echo "skip /etc/rc.local: DOCKER_DEPLOY_TYPE=${DOCKER_DEPLOY_TYPE}"
exit
fi
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
set -e
chown admin: -R /home/admin/
source /alidata/lib/proc.sh
waitterm
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
[ -n "${DOCKER_DEPLOY_TYPE}" ] || DOCKER_DEPLOY_TYPE="VM"
echo "DOCKER_DEPLOY_TYPE=${DOCKER_DEPLOY_TYPE}"
# run init scripts
for e in $(ls /alidata/init/*) ; do
[ -x "${e}" ] || continue
echo "==> INIT $e"
$e
echo "==> EXIT CODE: $?"
done
echo "==> INIT DEFAULT"
service sshd start
service crond start
#echo "check hostname -i: `hostname -i`"
#hti_num=`hostname -i|awk '{print NF}'`
#if [ $hti_num -gt 1 ];then
# echo "hostname -i result error:`hostname -i`"
# exit 120
#fi
echo "==> INIT DONE"
echo "==> RUN ${*}"
exec "${@}"
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# set port
if [ -z "${SSHD_PORT}" ] ; then
SSHD_PORT=22
[ "${DOCKER_DEPLOY_TYPE}" = "HOST" ] && SSHD_PORT=2222
fi
sed -r -i '/^OPTIONS=/ d' /etc/sysconfig/sshd
echo 'OPTIONS="-p '"${SSHD_PORT}"'"' >> /etc/sysconfig/sshd
# set admin ssh pulic key
if [ "${USE_ADMIN_PASSAGE}" = "YES" ] ; then
echo "set admin passage"
mkdir -p /home/admin/.ssh
chown admin:admin /home/admin/.ssh
chown admin:admin /home/admin/.ssh/authorized_keys
chmod 644 /home/admin/.ssh/authorized_keys
fi
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#****************************************************************#
# Create Date: 2017-01-06 17:58
#***************************************************************#
import socket
import shutil
from time import gmtime, strftime
# get host_name
host_name = socket.gethostname()
tmp_file = "/tmp/.lark-fix-host.hosts"
host_file = "/etc/hosts"
bak_file_name = "/tmp/hosts-fix-bak.%s" % ( strftime("%Y-%m-%d_%H-%M-%S", gmtime()) )
# load /etc/hosts file context
FH = open(host_file,"r")
file_lines = [ i.rstrip() for i in FH.readlines()]
FH.close()
file_lines_reverse = file_lines[::-1]
new_lines = []
bad_lines = []
last_match_line = ""
for line in file_lines_reverse:
if line.find(host_name) < 0: # 不匹配的行直接跳过
new_lines.append(line + "\n")
continue
cols = line.split()
new_cols = []
if cols[0].startswith("#"): # 跳过已经注释掉的行
new_lines.append(line + "\n")
continue
for col in cols:
if not col == host_name: # 跳过不匹配的列
new_cols.append(col)
continue
if cols[0] == "127.0.0.1": # 如果第一列是 127.0.0.1 就跳过匹配的列, 防止 hostname -i 返回 127.0.0.1
continue
# 如果已经发现过匹配的列, 就丢掉重复的列
if not len(last_match_line) == 0:
continue
new_cols.append(col)
last_match_line = line
# 跳过 xx.xx.xx.xx hostname 这样的重复列
if len(new_cols) == 1:
continue
new_l = "%s\n" % " ".join(new_cols)
new_lines.append(new_l)
# save tmp hosts
FH2=file(tmp_file,"w+")
FH2.writelines( new_lines[::-1])
FH2.close()
# mv to /etc/hosts
shutil.copy(host_file, bak_file_name)
shutil.move(tmp_file, host_file)
+40
View File
@@ -0,0 +1,40 @@
# waitterm
# wait TERM/INT signal.
# see: http://veithen.github.io/2014/11/16/sigterm-propagation.html
waitterm() {
local PID
# any process to block
tail -f /dev/null &
PID="$!"
# setup trap, could do nothing, or just kill the blocker
trap "kill -TERM ${PID}" TERM INT
# wait for signal, ignore wait exit code
wait "${PID}" || true
# clear trap
trap - TERM INT
# wait blocker, ignore blocker exit code
wait "${PID}" 2>/dev/null || true
}
# waittermpid "${PIDFILE}".
# monitor process by pidfile && wait TERM/INT signal.
# if the process disappeared, return 1, means exit with ERROR.
# if TERM or INT signal received, return 0, means OK to exit.
waittermpid() {
local PIDFILE PID do_run error
PIDFILE="${1?}"
do_run=true
error=0
trap "do_run=false" TERM INT
while "${do_run}" ; do
PID="$(cat "${PIDFILE}")"
if ! ps -p "${PID}" >/dev/null 2>&1 ; then
do_run=false
error=1
else
sleep 1
fi
done
trap - TERM INT
return "${error}"
}
+92
View File
@@ -0,0 +1,92 @@
#!/bin/bash
function usage() {
echo "Usage:"
echo " run.sh [CONFIG]"
echo "example:"
echo " run.sh -e canal.instance.master.address=127.0.0.1:3306 \\"
echo " -e canal.instance.dbUsername=canal \\"
echo " -e canal.instance.dbPassword=canal \\"
echo " -e canal.instance.connectionCharset=UTF-8 \\"
echo " -e canal.instance.tsdb.enable=true \\"
echo " -e canal.instance.gtidon=false \\"
echo " -e canal.instance.filter.regex=.*\\..* "
exit
}
function check_port() {
local port=$1
local TL=$(which telnet)
if [ -f $TL ]; then
data=`echo quit | telnet 127.0.0.1 $port| grep -ic connected`
echo $data
return
fi
local NC=$(which nc)
if [ -f $NC ]; then
data=`nc -z -w 1 127.0.0.1 $port | grep -ic succeeded`
echo $data
return
fi
echo "0"
return
}
function getMyIp() {
case "`uname`" in
Darwin)
myip=`echo "show State:/Network/Global/IPv4" | scutil | grep PrimaryInterface | awk '{print $3}' | xargs ifconfig | grep inet | grep -v inet6 | awk '{print $2}'`
;;
*)
myip=`ip route get 1 | awk '{print $NF;exit}'`
;;
esac
echo $myip
}
NET_MODE=""
case "`uname`" in
Darwin)
bin_abs_path=`cd $(dirname $0); pwd`
;;
Linux)
bin_abs_path=$(readlink -f $(dirname $0))
NET_MODE="--net=host"
;;
*)
NET_MODE="--net=host"
bin_abs_path=`cd $(dirname $0); pwd`
;;
esac
BASE=${bin_abs_path}
if [ $# -eq 0 ]; then
usage
elif [ "$1" == "-h" ] ; then
usage
elif [ "$1" == "help" ] ; then
usage
fi
DATA="$BASE/data"
mkdir -p $DATA
CONFIG=${@:1}
#VOLUMNS="-v $DATA:/home/admin/canal-server/logs"
PORTLIST="8000 8080 2222 11111"
PORTS=""
for PORT in $PORTLIST ; do
#exist=`check_port $PORT`
exist="0"
if [ "$exist" == "0" ]; then
PORTS="$PORTS -p $PORT:$PORT"
else
echo "port $PORT is used , pls check"
exit 1
fi
done
MEMORY="-m 4096m"
LOCALHOST=`getMyIp`
cmd="docker run -it -h $LOCALHOST $CONFIG --name=canal-server $VOLUMNS $NET_MODE $PORTS $MEMORY canal/canal-server"
echo $cmd
eval $cmd
+1 -5
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId>
<version>1.0.22</version>
<version>1.0.26-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.alibaba.otter</groupId>
@@ -33,10 +33,6 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.netty</groupId>
<artifactId>netty</artifactId>
</dependency>
<!-- test dependency -->
<dependency>
<groupId>junit</groupId>
@@ -2,11 +2,8 @@ package com.alibaba.otter.canal.parse.driver.mysql;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -16,6 +13,8 @@ import com.alibaba.otter.canal.parse.driver.mysql.packets.client.QuitCommandPack
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ErrorPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.HandshakeInitializationPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.Reply323Packet;
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannelPool;
import com.alibaba.otter.canal.parse.driver.mysql.utils.MySQLPasswordEncrypter;
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
@@ -33,8 +32,9 @@ public class MysqlConnector {
private String password;
private byte charsetNumber = 33;
private String defaultSchema = "retl";
private String defaultSchema = "test";
private int soTimeout = 30 * 1000;
private int connTimeout = 5 * 1000;
private int receiveBufferSize = 16 * 1024;
private int sendBufferSize = 16 * 1024;
@@ -44,11 +44,16 @@ public class MysqlConnector {
private long connectionId = -1;
private AtomicBoolean connected = new AtomicBoolean(false);
public static final int timeout = 5 * 1000; // 5s
public MysqlConnector(){
}
public MysqlConnector(InetSocketAddress address, String username, String password){
this.address = address;
String addr = address.getHostString();
int port = address.getPort();
this.address = new InetSocketAddress(addr, port);
this.username = username;
this.password = password;
}
@@ -64,14 +69,12 @@ public class MysqlConnector {
public void connect() throws IOException {
if (connected.compareAndSet(false, true)) {
try {
channel = SocketChannel.open();
configChannel(channel);
channel = SocketChannelPool.open(address);
logger.info("connect MysqlConnection to {}...", address);
channel.connect(address);
negotiate(channel);
} catch (Exception e) {
disconnect();
throw new IOException("connect " + this.address + " failure:" + ExceptionUtils.getStackTrace(e));
throw new IOException("connect " + this.address + " failure", e);
}
} else {
logger.error("the channel can't be connected twice.");
@@ -91,7 +94,7 @@ public class MysqlConnector {
}
logger.info("disConnect MysqlConnection to {}...", address);
} catch (Exception e) {
throw new IOException("disconnect " + this.address + " failure:" + ExceptionUtils.getStackTrace(e));
throw new IOException("disconnect " + this.address + " failure", e);
}
// 执行一次quit
@@ -103,7 +106,8 @@ public class MysqlConnector {
MysqlUpdateExecutor executor = new MysqlUpdateExecutor(connector);
executor.update("KILL CONNECTION " + connectionId);
} catch (Exception e) {
throw new IOException("KILL DUMP " + connectionId + " failure:" + ExceptionUtils.getStackTrace(e));
// 忽略具体异常
logger.info("KILL DUMP " + connectionId + " failure", e);
} finally {
if (connector != null) {
connector.disconnect();
@@ -131,6 +135,7 @@ public class MysqlConnector {
connector.setReceiveBufferSize(getReceiveBufferSize());
connector.setSendBufferSize(getSendBufferSize());
connector.setSoTimeout(getSoTimeout());
connector.setConnTimeout(connTimeout);
return connector;
}
@@ -141,24 +146,12 @@ public class MysqlConnector {
HeaderPacket quitHeader = new HeaderPacket();
quitHeader.setPacketBodyLength(cmdBody.length);
quitHeader.setPacketSequenceNumber((byte) 0x00);
PacketManager.write(channel,
new ByteBuffer[] { ByteBuffer.wrap(quitHeader.toBytes()), ByteBuffer.wrap(cmdBody) });
}
// ====================== help method ====================
private void configChannel(SocketChannel channel) throws IOException {
channel.socket().setKeepAlive(true);
channel.socket().setReuseAddress(true);
channel.socket().setSoTimeout(soTimeout);
channel.socket().setTcpNoDelay(true);
channel.socket().setReceiveBufferSize(receiveBufferSize);
channel.socket().setSendBufferSize(sendBufferSize);
PacketManager.writePkg(channel, quitHeader.toBytes(), cmdBody);
}
private void negotiate(SocketChannel channel) throws IOException {
HeaderPacket header = PacketManager.readHeader(channel, 4);
byte[] body = PacketManager.readBytes(channel, header.getPacketBodyLength());
HeaderPacket header = PacketManager.readHeader(channel, 4, timeout);
byte[] body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
if (body[0] < 0) {// check field_count
if (body[0] == -1) {
ErrorPacket error = new ErrorPacket();
@@ -190,15 +183,14 @@ public class MysqlConnector {
h.setPacketBodyLength(clientAuthPkgBody.length);
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
PacketManager.write(channel,
new ByteBuffer[] { ByteBuffer.wrap(h.toBytes()), ByteBuffer.wrap(clientAuthPkgBody) });
PacketManager.writePkg(channel, h.toBytes(), clientAuthPkgBody);
logger.info("client authentication packet is sent out.");
// check auth result
header = null;
header = PacketManager.readHeader(channel, 4);
body = null;
body = PacketManager.readBytes(channel, header.getPacketBodyLength());
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
assert body != null;
if (body[0] < 0) {
if (body[0] == -1) {
@@ -227,7 +219,7 @@ public class MysqlConnector {
h323.setPacketBodyLength(b323Body.length);
h323.setPacketSequenceNumber((byte) (packetSequenceNumber + 1));
PacketManager.write(channel, new ByteBuffer[] { ByteBuffer.wrap(h323.toBytes()), ByteBuffer.wrap(b323Body) });
PacketManager.writePkg(channel, h323.toBytes(), b323Body);
logger.info("client 323 authentication packet is sent out.");
// check auth result
HeaderPacket header = PacketManager.readHeader(channel, 4);
@@ -340,4 +332,16 @@ public class MysqlConnector {
this.dumping = dumping;
}
public int getConnTimeout() {
return connTimeout;
}
public void setConnTimeout(int connTimeout) {
this.connTimeout = connTimeout;
}
public String getPassword() {
return password;
}
}
@@ -1,17 +1,18 @@
package com.alibaba.otter.canal.parse.driver.mysql;
import java.io.IOException;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.QueryCommandPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.EOFPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ErrorPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetHeaderPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.RowDataPacket;
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
/**
@@ -24,9 +25,9 @@ public class MysqlQueryExecutor {
private SocketChannel channel;
public MysqlQueryExecutor(MysqlConnector connector){
public MysqlQueryExecutor(MysqlConnector connector) throws IOException{
if (!connector.isConnected()) {
throw new RuntimeException("should execute connector.connect() first");
throw new IOException("should execute connector.connect() first");
}
this.channel = connector.getChannel();
@@ -51,7 +52,7 @@ public class MysqlQueryExecutor {
QueryCommandPacket cmd = new QueryCommandPacket();
cmd.setQueryString(queryString);
byte[] bodyBytes = cmd.toBytes();
PacketManager.write(channel, bodyBytes);
PacketManager.writeBody(channel, bodyBytes);
byte[] body = readNextPacket();
if (body[0] < 0) {
@@ -88,16 +89,70 @@ public class MysqlQueryExecutor {
for (RowDataPacket r : rowData) {
resultSet.getFieldValues().addAll(r.getColumns());
}
resultSet.setSourceAddress(channel.socket().getRemoteSocketAddress());
resultSet.setSourceAddress(channel.getRemoteSocketAddress());
return resultSet;
}
private void readEofPacket() throws IOException {
public List<ResultSetPacket> queryMulti(String queryString) throws IOException {
QueryCommandPacket cmd = new QueryCommandPacket();
cmd.setQueryString(queryString);
byte[] bodyBytes = cmd.toBytes();
PacketManager.writeBody(channel, bodyBytes);
List<ResultSetPacket> resultSets = new ArrayList<ResultSetPacket>();
boolean moreResult = true;
while (moreResult) {
byte[] body = readNextPacket();
if (body[0] < 0) {
ErrorPacket packet = new ErrorPacket();
packet.fromBytes(body);
throw new IOException(packet + "\n with command: " + queryString);
}
ResultSetHeaderPacket rsHeader = new ResultSetHeaderPacket();
rsHeader.fromBytes(body);
List<FieldPacket> fields = new ArrayList<FieldPacket>();
for (int i = 0; i < rsHeader.getColumnCount(); i++) {
FieldPacket fp = new FieldPacket();
fp.fromBytes(readNextPacket());
fields.add(fp);
}
moreResult = readEofPacket();
List<RowDataPacket> rowData = new ArrayList<RowDataPacket>();
while (true) {
body = readNextPacket();
if (body[0] == -2) {
break;
}
RowDataPacket rowDataPacket = new RowDataPacket();
rowDataPacket.fromBytes(body);
rowData.add(rowDataPacket);
}
ResultSetPacket resultSet = new ResultSetPacket();
resultSet.getFieldDescriptors().addAll(fields);
for (RowDataPacket r : rowData) {
resultSet.getFieldValues().addAll(r.getColumns());
}
resultSet.setSourceAddress(channel.getRemoteSocketAddress());
resultSets.add(resultSet);
}
return resultSets;
}
private boolean readEofPacket() throws IOException {
byte[] eofBody = readNextPacket();
EOFPacket packet = new EOFPacket();
packet.fromBytes(eofBody);
if (eofBody[0] != -2) {
throw new IOException("EOF Packet is expected, but packet with field_count=" + eofBody[0] + " is found.");
}
return (packet.statusFlag & 0x0008) != 0;
}
protected byte[] readNextPacket() throws IOException {
@@ -1,7 +1,6 @@
package com.alibaba.otter.canal.parse.driver.mysql;
import java.io.IOException;
import java.nio.channels.SocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,28 +20,29 @@ public class MysqlUpdateExecutor {
private static final Logger logger = LoggerFactory.getLogger(MysqlUpdateExecutor.class);
private SocketChannel channel;
private MysqlConnector connector;
public MysqlUpdateExecutor(MysqlConnector connector){
public MysqlUpdateExecutor(MysqlConnector connector) throws IOException{
if (!connector.isConnected()) {
throw new RuntimeException("should execute connector.connect() first");
throw new IOException("should execute connector.connect() first");
}
this.channel = connector.getChannel();
this.connector = connector;
}
public MysqlUpdateExecutor(SocketChannel ch){
this.channel = ch;
}
/*
* public MysqlUpdateExecutor(SocketChannel ch){ this.channel = ch; }
*/
public OKPacket update(String updateString) throws IOException {
QueryCommandPacket cmd = new QueryCommandPacket();
cmd.setQueryString(updateString);
byte[] bodyBytes = cmd.toBytes();
PacketManager.write(channel, bodyBytes);
PacketManager.writeBody(connector.getChannel(), bodyBytes);
logger.debug("read update result...");
byte[] body = PacketManager.readBytes(channel, PacketManager.readHeader(channel, 4).getPacketBodyLength());
byte[] body = PacketManager.readBytes(connector.getChannel(),
PacketManager.readHeader(connector.getChannel(), 4).getPacketBodyLength());
if (body[0] < 0) {
ErrorPacket packet = new ErrorPacket();
packet.fromBytes(body);
@@ -0,0 +1,24 @@
package com.alibaba.otter.canal.parse.driver.mysql.packets;
import java.io.IOException;
/**
* Created by hiwjd on 2018/4/23. hiwjd0@gmail.com
*/
public interface GTIDSet {
/**
* 序列化成字节数组
*
* @return
*/
byte[] encode() throws IOException;
/**
* 更新当前实例
*
* @param str
* @throws Exception
*/
void update(String str);
}
@@ -0,0 +1,116 @@
package com.alibaba.otter.canal.parse.driver.mysql.packets;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
/**
* Created by hiwjd on 2018/4/23. hiwjd0@gmail.com
*/
public class MysqlGTIDSet implements GTIDSet {
public Map<String, UUIDSet> sets;
@Override
public byte[] encode() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteHelper.writeUnsignedInt64LittleEndian(sets.size(), out);
for (Map.Entry<String, UUIDSet> entry : sets.entrySet()) {
out.write(entry.getValue().encode());
}
return out.toByteArray();
}
@Override
public void update(String str) {
UUIDSet us = UUIDSet.parse(str);
String sid = us.SID.toString();
if (sets.containsKey(sid)) {
sets.get(sid).intervals.addAll(us.intervals);
sets.get(sid).intervals = UUIDSet.combine(sets.get(sid).intervals);
} else {
sets.put(sid, us);
}
}
@Override
public boolean equals(Object o) {
if (o == null) return false;
if (this == o) return true;
MysqlGTIDSet gs = (MysqlGTIDSet) o;
if (gs.sets == null) return false;
for (Map.Entry<String, UUIDSet> entry : sets.entrySet()) {
if (!entry.getValue().equals(gs.sets.get(entry.getKey()))) {
return false;
}
}
return true;
}
/**
* 解析如下格式的字符串为MysqlGTIDSet: 726757ad-4455-11e8-ae04-0242ac110002:1 =>
* MysqlGTIDSet{ sets: { 726757ad-4455-11e8-ae04-0242ac110002: UUIDSet{ SID:
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:2}] } }
* } 726757ad-4455-11e8-ae04-0242ac110002:1-3 => MysqlGTIDSet{ sets: {
* 726757ad-4455-11e8-ae04-0242ac110002: UUIDSet{ SID:
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4}] } }
* } 726757ad-4455-11e8-ae04-0242ac110002:1-3:4 => MysqlGTIDSet{ sets: {
* 726757ad-4455-11e8-ae04-0242ac110002: UUIDSet{ SID:
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:5}] } }
* } 726757ad-4455-11e8-ae04-0242ac110002:1-3:7-9 => MysqlGTIDSet{ sets: {
* 726757ad-4455-11e8-ae04-0242ac110002: UUIDSet{ SID:
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4},
* {start:7, stop: 10}] } } }
* 726757ad-4455-11e8-ae04-0242ac110002:1-3,726757
* ad-4455-11e8-ae04-0242ac110003:4 => MysqlGTIDSet{ sets: {
* 726757ad-4455-11e8-ae04-0242ac110002: UUIDSet{ SID:
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4}] },
* 726757ad-4455-11e8-ae04-0242ac110003: UUIDSet{ SID:
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:4, stop:5}] } }
* }
*
* @param gtidData
* @return
*/
public static MysqlGTIDSet parse(String gtidData) {
Map<String, UUIDSet> m;
if (gtidData == null || gtidData.length() < 1) {
m = new HashMap<String, UUIDSet>();
} else {
String[] uuidStrs = gtidData.split(",");
m = new HashMap<String, UUIDSet>(uuidStrs.length);
for (int i = 0; i < uuidStrs.length; i++) {
UUIDSet uuidSet = UUIDSet.parse(uuidStrs[i]);
m.put(uuidSet.SID.toString(), uuidSet);
}
}
MysqlGTIDSet gs = new MysqlGTIDSet();
gs.sets = m;
return gs;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, UUIDSet> entry : sets.entrySet()) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(entry.getValue().toString());
}
return sb.toString();
}
}
@@ -0,0 +1,194 @@
package com.alibaba.otter.canal.parse.driver.mysql.packets;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
/**
* Created by hiwjd on 2018/4/23. hiwjd0@gmail.com
*/
public class UUIDSet {
public UUID SID;
public List<Interval> intervals;
public byte[] encode() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(SID.getMostSignificantBits());
bb.putLong(SID.getLeastSignificantBits());
out.write(bb.array());
ByteHelper.writeUnsignedInt64LittleEndian(intervals.size(), out);
for (Interval interval : intervals) {
ByteHelper.writeUnsignedInt64LittleEndian(interval.start, out);
ByteHelper.writeUnsignedInt64LittleEndian(interval.stop, out);
}
return out.toByteArray();
}
@Override
public boolean equals(Object o) {
if (o == null) return false;
if (this == o) return true;
UUIDSet us = (UUIDSet) o;
Collections.sort(intervals);
Collections.sort(us.intervals);
if (SID.equals(us.SID) && intervals.equals(us.intervals)) {
return true;
}
return false;
}
public static class Interval implements Comparable<Interval> {
public long start;
public long stop;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Interval interval = (Interval) o;
if (start != interval.start) return false;
return stop == interval.stop;
}
@Override
public int hashCode() {
int result = (int) (start ^ (start >>> 32));
result = 31 * result + (int) (stop ^ (stop >>> 32));
return result;
}
@Override
public int compareTo(Interval o) {
if (equals(o)) {
return 1;
}
return start > o.start ? 1 : (start == o.start ? 0 : -1);
}
}
/**
* 解析如下格式字符串为UUIDSet: 726757ad-4455-11e8-ae04-0242ac110002:1 => UUIDSet{SID:
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:2}]}
* 726757ad-4455-11e8-ae04-0242ac110002:1-3 => UUIDSet{SID:
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4}]}
* 726757ad-4455-11e8-ae04-0242ac110002:1-3:4 UUIDSet{SID:
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:5}]}
* 726757ad-4455-11e8-ae04-0242ac110002:1-3:7-9 UUIDSet{SID:
* 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4},
* {start:7, stop:10}]}
*
* @param str
* @return
*/
public static UUIDSet parse(String str) {
String[] ss = str.split(":");
if (ss.length < 2) {
throw new RuntimeException(String.format("parseUUIDSet failed due to wrong format: %s", str));
}
List<Interval> intervals = new ArrayList<Interval>();
for (int i = 1; i < ss.length; i++) {
intervals.add(parseInterval(ss[i]));
}
UUIDSet uuidSet = new UUIDSet();
uuidSet.SID = UUID.fromString(ss[0]);
uuidSet.intervals = combine(intervals);
return uuidSet;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(SID.toString());
for (Interval interval : intervals) {
if (interval.start == interval.stop - 1) {
sb.append(":");
sb.append(interval.start);
} else {
sb.append(":");
sb.append(interval.start);
sb.append("-");
sb.append(interval.stop - 1);
}
}
return sb.toString();
}
/**
* 解析如下格式字符串为Interval: 1 => Interval{start:1, stop:2} 1-3 =>
* Interval{start:1, stop:4} 注意!字符串格式表达时[n,m]是两侧都包含的,Interval表达时[n,m)右侧开
*
* @param str
* @return
*/
public static Interval parseInterval(String str) {
String[] ss = str.split("-");
Interval interval = new Interval();
switch (ss.length) {
case 1:
interval.start = Long.parseLong(ss[0]);
interval.stop = interval.start + 1;
break;
case 2:
interval.start = Long.parseLong(ss[0]);
interval.stop = Long.parseLong(ss[1]) + 1;
break;
default:
throw new RuntimeException(String.format("parseInterval failed due to wrong format: %s", str));
}
return interval;
}
/**
* 把{start,stop}连续的合并掉: [{start:1, stop:4},{start:4, stop:5}] => [{start:1,
* stop:5}]
*
* @param intervals
* @return
*/
public static List<Interval> combine(List<Interval> intervals) {
List<Interval> combined = new ArrayList<Interval>();
Collections.sort(intervals);
int len = intervals.size();
for (int i = 0; i < len; i++) {
combined.add(intervals.get(i));
int j;
for (j = i + 1; j < len; j++) {
if (intervals.get(i).stop >= intervals.get(j).start) {
intervals.get(i).stop = intervals.get(j).stop;
} else {
break;
}
}
i = j - 1;
}
return combined;
}
}
@@ -0,0 +1,66 @@
package com.alibaba.otter.canal.parse.driver.mysql.packets.client;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import com.alibaba.otter.canal.parse.driver.mysql.packets.CommandPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
/**
* Created by hiwjd on 2018/4/24. hiwjd0@gmail.com
* https://dev.mysql.com/doc/internals/en/com-binlog-dump-gtid.html
*/
public class BinlogDumpGTIDCommandPacket extends CommandPacket {
public static final int BINLOG_DUMP_NON_BLOCK = 0x01;
public static final int BINLOG_THROUGH_POSITION = 0x02;
public static final int BINLOG_THROUGH_GTID = 0x04;
public long slaveServerId;
public GTIDSet gtidSet;
public BinlogDumpGTIDCommandPacket(){
setCommand((byte) 0x1e);
}
@Override
public void fromBytes(byte[] data) throws IOException {
}
@Override
public byte[] toBytes() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 0. [1] write command number
out.write(getCommand());
// 1. [2] flags
ByteHelper.writeUnsignedShortLittleEndian(BINLOG_THROUGH_GTID, out);
// 2. [4] server-id
ByteHelper.writeUnsignedIntLittleEndian(slaveServerId, out);
// 3. [4] binlog-filename-len
ByteHelper.writeUnsignedIntLittleEndian(0, out);
// 4. [] binlog-filename
// skip
// 5. [8] binlog-pos
ByteHelper.writeUnsignedInt64LittleEndian(4, out);
// if flags & BINLOG_THROUGH_GTID {
byte[] bs = gtidSet.encode();
// 6. [4] data-size
ByteHelper.writeUnsignedIntLittleEndian(bs.length, out);
// 7, [] data
// [8] n_sids // 文档写的是4个字节,其实是8个字节
// for n_sids {
// [16] SID
// [8] n_intervals
// for n_intervals {
// [8] start (signed)
// [8] end (signed)
// }
// }
out.write(bs);
// }
return out.toByteArray();
}
}
@@ -47,10 +47,11 @@ public class ClientAuthenticationPacket extends PacketWithHeaderPacket {
/**
* CLIENT_LONG_PASSWORD CLIENT_LONG_FLAG CLIENT_PROTOCOL_41
* CLIENT_INTERACTIVE CLIENT_TRANSACTIONS CLIENT_SECURE_CONNECTION
* CLIENT_MULTI_STATEMENTS;
*/
ByteHelper.writeUnsignedIntLittleEndian(1 | 4 | 512 | 8192 | 32768, out); // remove
// client_interactive
// feature
ByteHelper.writeUnsignedIntLittleEndian(1 | 4 | 512 | 8192 | 32768 | 0x00010000, out); // remove
// client_interactive
// feature
// 2. write max_packet_size
ByteHelper.writeUnsignedIntLittleEndian(MSC.MAX_PACKET_LENGTH, out);
@@ -0,0 +1,57 @@
package com.alibaba.otter.canal.parse.driver.mysql.packets.client;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import com.alibaba.otter.canal.parse.driver.mysql.packets.CommandPacket;
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
/**
* COM_REGISTER_SLAVE
*
* @author zhibinliu
* @since 1.0.24
*/
public class RegisterSlaveCommandPacket extends CommandPacket {
public String reportHost;
public int reportPort;
public String reportUser;
public String reportPasswd;
public long serverId;
public RegisterSlaveCommandPacket(){
setCommand((byte) 0x15);
}
public void fromBytes(byte[] data) {
// bypass
}
public static byte[] toLH(int n) {
byte[] b = new byte[4];
b[0] = (byte) (n & 0xff);
b[1] = (byte) (n >> 8 & 0xff);
b[2] = (byte) (n >> 16 & 0xff);
b[3] = (byte) (n >> 24 & 0xff);
return b;
}
public byte[] toBytes() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(getCommand());
ByteHelper.writeUnsignedIntLittleEndian(serverId, out);
out.write((byte) reportHost.getBytes().length);
ByteHelper.writeFixedLengthBytesFromStart(reportHost.getBytes(), reportHost.getBytes().length, out);
out.write((byte) reportUser.getBytes().length);
ByteHelper.writeFixedLengthBytesFromStart(reportUser.getBytes(), reportUser.getBytes().length, out);
out.write((byte) reportPasswd.getBytes().length);
ByteHelper.writeFixedLengthBytesFromStart(reportPasswd.getBytes(), reportPasswd.getBytes().length, out);
ByteHelper.writeUnsignedShortLittleEndian(reportPort, out);
ByteHelper.writeUnsignedIntLittleEndian(0, out);// Fake
// rpl_recovery_rank
ByteHelper.writeUnsignedIntLittleEndian(0, out);// master id
return out.toByteArray();
}
}
@@ -0,0 +1,55 @@
package com.alibaba.otter.canal.parse.driver.mysql.packets.client;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.apache.commons.lang.StringUtils;
import com.alibaba.otter.canal.parse.driver.mysql.packets.CommandPacket;
import com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper;
/**
* semi ack command
*
* @author amos_chen
*/
public class SemiAckCommandPacket extends CommandPacket {
public long binlogPosition;
public String binlogFileName;
public SemiAckCommandPacket(){
}
@Override
public void fromBytes(byte[] data) throws IOException {
}
/**
* <pre>
* Bytes Name
* --------------------------------------------------------
* Bytes Name
* ----- ----
* 1 semi mark
* 8 binlog position to start at (little endian)
* n binlog file name
*
* </pre>
*/
public byte[] toBytes() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 0 write semi mark
out.write(0xef);
// 1 write 8 bytes for position
ByteHelper.write8ByteUnsignedIntLittleEndian(binlogPosition, out);
// 2 write binlog filename
if (StringUtils.isNotEmpty(binlogFileName)) {
out.write(binlogFileName.getBytes());
}
return out.toByteArray();
}
}
@@ -74,7 +74,7 @@ public class FieldPacket extends PacketWithHeaderPacket {
this.decimals = data[index];
index++;
//
index += 2;//skip filter
index += 2;// skip filter
//
if (index < data.length) {
reader.setIndex(index);
@@ -0,0 +1,168 @@
package com.alibaba.otter.canal.parse.driver.mysql.socket;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.channels.ClosedByInterruptException;
/**
* 使用BIO进行dump
*
* @author chuanyi
*/
public class BioSocketChannel implements SocketChannel {
static final int DEFAULT_CONNECT_TIMEOUT = 10 * 1000;
static final int SO_TIMEOUT = 1000;
private Socket socket;
private InputStream input;
private OutputStream output;
BioSocketChannel(Socket socket) throws IOException{
this.socket = socket;
this.input = new BufferedInputStream(socket.getInputStream(), 16384);
this.output = socket.getOutputStream();
}
public void write(byte[]... buf) throws IOException {
OutputStream output = this.output;
if (output != null) {
for (byte[] bs : buf) {
output.write(bs);
}
} else {
throw new SocketException("Socket already closed.");
}
}
public byte[] read(int readSize) throws IOException {
InputStream input = this.input;
byte[] data = new byte[readSize];
int remain = readSize;
if (input == null) {
throw new SocketException("Socket already closed.");
}
while (remain > 0) {
try {
int read = input.read(data, readSize - remain, remain);
if (read > -1) {
remain -= read;
} else {
throw new IOException("EOF encountered.");
}
} catch (SocketTimeoutException te) {
if (Thread.interrupted()) {
throw new ClosedByInterruptException();
}
}
}
return data;
}
public byte[] read(int readSize, int timeout) throws IOException {
InputStream input = this.input;
byte[] data = new byte[readSize];
int remain = readSize;
int accTimeout = 0;
if (input == null) {
throw new SocketException("Socket already closed.");
}
while (remain > 0 && accTimeout < timeout) {
try {
int read = input.read(data, readSize - remain, remain);
if (read > -1) {
remain -= read;
} else {
throw new IOException("EOF encountered.");
}
} catch (SocketTimeoutException te) {
if (Thread.interrupted()) {
throw new ClosedByInterruptException();
}
accTimeout += SO_TIMEOUT;
}
}
if (remain > 0 && accTimeout >= timeout) {
throw new SocketTimeoutException("Timeout occurred, failed to read " + readSize + " bytes in " + timeout
+ " milliseconds.");
}
return data;
}
@Override
public void read(byte[] data, int off, int len, int timeout) throws IOException {
InputStream input = this.input;
int accTimeout = 0;
if (input == null) {
throw new SocketException("Socket already closed.");
}
int n = 0;
while (n < len && accTimeout < timeout) {
try {
int read = input.read(data, off + n, len - n);
if (read > -1) {
n += read;
} else {
throw new IOException("EOF encountered.");
}
} catch (SocketTimeoutException te) {
if (Thread.interrupted()) {
throw new ClosedByInterruptException();
}
accTimeout += SO_TIMEOUT;
}
}
if (n < len && accTimeout >= timeout) {
throw new SocketTimeoutException("Timeout occurred, failed to read " + len + " bytes in " + timeout
+ " milliseconds.");
}
}
public boolean isConnected() {
Socket socket = this.socket;
if (socket != null) {
return socket.isConnected();
}
return false;
}
public SocketAddress getRemoteSocketAddress() {
Socket socket = this.socket;
if (socket != null) {
return socket.getRemoteSocketAddress();
}
return null;
}
public void close() {
Socket socket = this.socket;
if (socket != null) {
try {
socket.shutdownInput();
} catch (IOException e) {
// Ignore, could not do anymore
}
try {
socket.shutdownOutput();
} catch (IOException e) {
// Ignore, could not do anymore
}
try {
socket.close();
} catch (IOException e) {
// Ignore, could not do anymore
}
}
this.input = null;
this.output = null;
this.socket = null;
}
}
@@ -0,0 +1,22 @@
package com.alibaba.otter.canal.parse.driver.mysql.socket;
import java.net.Socket;
import java.net.SocketAddress;
/**
* @author luoyaogui 实现channel的管理(监听连接、读数据、回收) 2016-12-28
* @author chuanyi 2018-3-3 保留<code>open</code>减少文件变更数量
*/
public abstract class BioSocketChannelPool {
public static BioSocketChannel open(SocketAddress address) throws Exception {
Socket socket = new Socket();
socket.setSoTimeout(BioSocketChannel.SO_TIMEOUT);
socket.setTcpNoDelay(true);
socket.setKeepAlive(true);
socket.setReuseAddress(true);
socket.connect(address, BioSocketChannel.DEFAULT_CONNECT_TIMEOUT);
return new BioSocketChannel(socket);
}
}
@@ -0,0 +1,234 @@
package com.alibaba.otter.canal.parse.driver.mysql.socket;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.util.internal.OutOfDirectMemoryError;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SystemPropertyUtil;
import java.io.IOException;
import java.net.SocketAddress;
import org.apache.commons.lang.NotImplementedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 封装netty的通信channel和数据接收缓存,实现读、写、连接校验的功能。 2016-12-28
*
* @author luoyaogui
*/
public class NettySocketChannel implements SocketChannel {
private static final Logger logger = LoggerFactory.getLogger(SocketChannel.class);
private static final int WAIT_PERIOD = 10; // milliseconds
private static final int DEFAULT_INIT_BUFFER_SIZE = 1024 * 1024; // 1MB,默认初始缓存大小
// 参考 mysql-connector-java-5.1.40.jar: com.mysql.jdbc.MysqlIO.maxThreeBytes
// < 256 * 256 * 256 = 16MB
private static final int DEFAULT_MAX_BUFFER_SIZE = 16 * DEFAULT_INIT_BUFFER_SIZE; // 16MB,默认最大缓存大小
private Channel channel = null;
private Object lock = new Object();
private ByteBuf cache = PooledByteBufAllocator.DEFAULT.directBuffer(DEFAULT_INIT_BUFFER_SIZE); // 缓存大小
private int maxDirectBuffer = cache.maxCapacity();
public Channel getChannel() {
return channel;
}
public void setChannel(Channel channel) {
this.channel = channel;
}
public void writeCache(ByteBuf buf) throws InterruptedException, IOException {
synchronized (lock) {
while (true) {
if (null == cache) {
throw new IOException("socket is closed !");
}
// source buffer is empty.
if (!buf.isReadable()) {
break;
}
// 默认缓存大小不够用时需自动清理或扩充,否则将因缓存空间不足而造成I/O超时假象
int length = buf.readableBytes();
int deltaSize = length - cache.writableBytes();
if (deltaSize > 0) {
// 首先避免频繁分配内存(扩容/收缩),其次避免频繁移动内存(清理)
if (cache.readerIndex() >= deltaSize) { // 可以清理
// 回收已读空间,重置读写指针
cache.discardReadBytes();
// 恢复自动扩充的过大缓存到默认初始缓存大小,释放空间
int oldCapacity = cache.capacity();
if (oldCapacity > DEFAULT_MAX_BUFFER_SIZE) { // 尝试收缩
int newCapacity = cache.writerIndex();
newCapacity = ((newCapacity - 1) / DEFAULT_INIT_BUFFER_SIZE + 1) * DEFAULT_INIT_BUFFER_SIZE; // 对齐
int quarter = (newCapacity >> 2); // 至少留空四分之一
quarter = ((quarter - 1) / DEFAULT_INIT_BUFFER_SIZE + 1) * DEFAULT_INIT_BUFFER_SIZE; // 对齐
newCapacity += quarter; // 留空四分之一
if (newCapacity < (oldCapacity >> 1)) { // 至少收缩二分之一
try {
cache.capacity(newCapacity);
logger.info("shrink cache capacity: {} - {} = {} bytes",
oldCapacity,
oldCapacity - newCapacity,
newCapacity);
} catch (OutOfMemoryError ignore) {
maxDirectBuffer = oldCapacity; // 未来不再超过当前容量,记录日志后继续
logger.warn("cache OutOfMemoryError: {} bytes", newCapacity, ignore);
}
}
}
} else { // 尝试扩容
int oldCapacity = cache.capacity();
if (oldCapacity < maxDirectBuffer) {
int quarter = (oldCapacity >> 2); // 至少扩容四分之一
quarter = ((quarter - 1) / DEFAULT_INIT_BUFFER_SIZE + 1) * DEFAULT_INIT_BUFFER_SIZE; // 对齐
deltaSize = ((deltaSize - 1) / quarter + 1) * quarter; // 对齐
int newCapacity = oldCapacity + deltaSize;
if (newCapacity > maxDirectBuffer) {
newCapacity = maxDirectBuffer;
}
try {
cache.capacity(newCapacity);
logger.info("expand cache capacity: {} + {} = {} bytes",
oldCapacity,
newCapacity - oldCapacity,
newCapacity);
} catch (OutOfDirectMemoryError e) {
// failed to allocate 885571168 byte(s) of
// direct memory (used: 1002946176, max:
// 1888485376)
long maxDirectMemory = SystemPropertyUtil.getLong("io.netty.maxDirectMemory", -1);
if (maxDirectMemory < 0) {
maxDirectMemory = PlatformDependent.maxDirectMemory();
}
if (maxDirectBuffer > maxDirectMemory) {
maxDirectBuffer = (int) maxDirectMemory;
newCapacity = maxDirectBuffer;
logger.warn("resize maxDirectBuffer: {} bytes", maxDirectBuffer, e);
try {
cache.capacity(newCapacity);
logger.info("expand cache capacity: {} + {} = {} bytes",
oldCapacity,
newCapacity - oldCapacity,
newCapacity);
} catch (OutOfMemoryError ignore) {
maxDirectBuffer = oldCapacity; // 未来不再超过当前容量,记录日志后继续
logger.warn("cache OutOfMemoryError: {} bytes", newCapacity, ignore);
}
} else {
maxDirectBuffer = oldCapacity; // 未来不再超过当前容量,记录日志后继续
logger.warn("cache OutOfDirectMemoryError: {} bytes", newCapacity, e);
}
} catch (OutOfMemoryError ignore) {
maxDirectBuffer = oldCapacity; // 未来不再超过当前容量,记录日志后继续
logger.warn("cache OutOfMemoryError: {} bytes", newCapacity, ignore);
}
}
}
deltaSize = length - cache.writableBytes();
}
if (deltaSize != length) {
// deltaSize <= 0 可全部写入,deltaSize > 0 只能部分写入
if (deltaSize <= 0) {
cache.writeBytes(buf, length);
break;
} else {
cache.writeBytes(buf, length - deltaSize);
}
}
// dest buffer is full.
lock.wait(WAIT_PERIOD);
// 回收已读空间,重置读写指针
cache.discardReadBytes();
}
}
}
public void write(byte[]... buf) throws IOException {
if (channel != null && channel.isWritable()) {
channel.writeAndFlush(Unpooled.copiedBuffer(buf));
} else {
throw new IOException("write failed ! please checking !");
}
}
public byte[] read(int readSize) throws IOException {
return read(readSize, 0);
}
public byte[] read(int readSize, int timeout) throws IOException {
int accumulatedWaitTime = 0;
// 若读取内容较长,则自动扩充超时时间,以初始缓存大小为基准计算倍数
if (timeout > 0 && readSize > DEFAULT_INIT_BUFFER_SIZE) {
timeout *= (readSize / DEFAULT_INIT_BUFFER_SIZE + 1);
}
do {
if (readSize > cache.readableBytes()) {
if (null == channel) {
throw new IOException("socket has Interrupted !");
}
if (timeout > 0) {
accumulatedWaitTime += WAIT_PERIOD;
if (accumulatedWaitTime > timeout) {
StringBuilder sb = new StringBuilder("socket read timeout occured !");
sb.append(" readSize = ").append(readSize);
sb.append(", readableBytes = ").append(cache.readableBytes());
sb.append(", timeout = ").append(timeout);
throw new IOException(sb.toString());
}
}
synchronized (this) {
try {
wait(WAIT_PERIOD);
} catch (InterruptedException e) {
throw new IOException("socket has Interrupted !");
}
}
} else {
byte[] back = new byte[readSize];
synchronized (lock) {
cache.readBytes(back);
}
return back;
}
} while (true);
}
@Override
public void read(byte[] data, int off, int len, int timeout) throws IOException {
throw new NotImplementedException();
}
public boolean isConnected() {
return channel != null ? true : false;
}
public SocketAddress getRemoteSocketAddress() {
return channel != null ? channel.remoteAddress() : null;
}
public void close() {
if (channel != null) {
channel.close();
}
channel = null;
// A fatal error has been detected by the Java Runtime Environment:
// EXCEPTION_ACCESS_VIOLATION (0xc0000005)
synchronized (lock) {
cache.discardReadBytes();// 回收已占用的内存
cache.release();// 释放整个内存
cache = null;
}
}
}
@@ -0,0 +1,110 @@
package com.alibaba.otter.canal.parse.driver.mysql.socket;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.AdaptiveRecvByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.io.IOException;
import java.net.SocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author luoyaogui 实现channel的管理(监听连接、读数据、回收) 2016-12-28
*/
@SuppressWarnings({ "rawtypes", "deprecation" })
public abstract class NettySocketChannelPool {
private static EventLoopGroup group = new NioEventLoopGroup(); // 非阻塞IO线程组
private static Bootstrap boot = new Bootstrap(); // 主
private static Map<Channel, SocketChannel> chManager = new ConcurrentHashMap<Channel, SocketChannel>();
private static final Logger logger = LoggerFactory.getLogger(NettySocketChannelPool.class);
static {
boot.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
// 如果是延时敏感型应用,建议关闭Nagle算法
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.SO_REUSEADDR, true)
.option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
//
.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new BusinessHandler());// 命令过滤和handler添加管理
}
});
}
public static SocketChannel open(SocketAddress address) throws Exception {
SocketChannel socket = null;
ChannelFuture future = boot.connect(address).sync();
if (future.isSuccess()) {
future.channel().pipeline().get(BusinessHandler.class).latch.await();
socket = chManager.get(future.channel());
}
if (null == socket) {
throw new IOException("can't create socket!");
}
return socket;
}
public static class BusinessHandler extends SimpleChannelInboundHandler<ByteBuf> {
private NettySocketChannel socket = null;
private final CountDownLatch latch = new CountDownLatch(1);
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
socket.setChannel(null);
chManager.remove(ctx.channel());// 移除
super.channelInactive(ctx);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
socket = new NettySocketChannel();
socket.setChannel(ctx.channel());
chManager.put(ctx.channel(), socket);
latch.countDown();
super.channelActive(ctx);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
if (socket != null) {
socket.writeCache(msg);
} else {
// TODO: need graceful error handler.
logger.error("no socket available.");
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// need output error for troubeshooting.
logger.error("business error.", cause);
ctx.close();
}
}
}
@@ -0,0 +1,25 @@
package com.alibaba.otter.canal.parse.driver.mysql.socket;
import java.io.IOException;
import java.net.SocketAddress;
/**
* @author agapple 2018年3月12日 下午10:36:44
* @since 1.0.26
*/
public interface SocketChannel {
public void write(byte[]... buf) throws IOException;
public byte[] read(int readSize) throws IOException;
public byte[] read(int readSize, int timeout) throws IOException;
public void read(byte[] data, int off, int len, int timeout) throws IOException;
public boolean isConnected();
public SocketAddress getRemoteSocketAddress();
public void close();
}
@@ -0,0 +1,35 @@
package com.alibaba.otter.canal.parse.driver.mysql.socket;
import java.net.SocketAddress;
import org.apache.commons.lang.StringUtils;
/**
* @author agapple 2018年3月12日 下午10:46:22
* @since 1.0.26
*/
public abstract class SocketChannelPool {
public static SocketChannel open(SocketAddress address) throws Exception {
String type = chooseSocketChannel();
if ("netty".equalsIgnoreCase(type)) {
return NettySocketChannelPool.open(address);
} else {
return BioSocketChannelPool.open(address);
}
}
private static String chooseSocketChannel() {
String socketChannel = System.getenv("canal.socketChannel");
if (StringUtils.isEmpty(socketChannel)) {
socketChannel = System.getProperty("canal.socketChannel");
}
if (StringUtils.isEmpty(socketChannel)) {
socketChannel = "bio"; // bio or netty
}
return socketChannel;
}
}
@@ -109,6 +109,17 @@ public abstract class ByteHelper {
return out.toByteArray();
}
public static void write8ByteUnsignedIntLittleEndian(long data, ByteArrayOutputStream out) {
out.write((byte) (data & 0xFF));
out.write((byte) (data >>> 8));
out.write((byte) (data >>> 16));
out.write((byte) (data >>> 24));
out.write((byte) (data >>> 32));
out.write((byte) (data >>> 40));
out.write((byte) (data >>> 48));
out.write((byte) (data >>> 56));
}
public static void writeUnsignedIntLittleEndian(long data, ByteArrayOutputStream out) {
out.write((byte) (data & 0xFF));
out.write((byte) (data >>> 8));
@@ -116,6 +127,17 @@ public abstract class ByteHelper {
out.write((byte) (data >>> 24));
}
public static void writeUnsignedInt64LittleEndian(long data, ByteArrayOutputStream out) {
out.write((byte) (data & 0xFF));
out.write((byte) (data >>> 8));
out.write((byte) (data >>> 16));
out.write((byte) (data >>> 24));
out.write((byte) (data >>> 32));
out.write((byte) (data >>> 40));
out.write((byte) (data >>> 48));
out.write((byte) (data >>> 56));
}
public static void writeUnsignedShortLittleEndian(int data, ByteArrayOutputStream out) {
out.write((byte) (data & 0xFF));
out.write((byte) ((data >>> 8) & 0xFF));
@@ -1,69 +1,44 @@
package com.alibaba.otter.canal.parse.driver.mysql.utils;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
public abstract class PacketManager {
public static HeaderPacket readHeader(SocketChannel ch, int len) throws IOException {
HeaderPacket header = new HeaderPacket();
header.fromBytes(readBytesAsBuffer(ch, len).array());
header.fromBytes(ch.read(len));
return header;
}
public static ByteBuffer readBytesAsBuffer(SocketChannel ch, int len) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(len);
while (buffer.hasRemaining()) {
int readNum = ch.read(buffer);
if (readNum == -1) {
throw new IOException("Unexpected End Stream");
}
}
return buffer;
public static HeaderPacket readHeader(SocketChannel ch, int len, int timeout) throws IOException {
HeaderPacket header = new HeaderPacket();
header.fromBytes(ch.read(len, timeout));
return header;
}
public static byte[] readBytes(SocketChannel ch, int len) throws IOException {
return readBytesAsBuffer(ch, len).array();
return ch.read(len);
}
/**
* Since We r using blocking IO, so we will just write once and assert the
* length to simplify the read operation.<br>
* If the block write doesn't work as we expected, we will change this
* implementation as per the result.
*
* @param ch
* @param len
* @return
* @throws IOException
*/
public static void write(SocketChannel ch, ByteBuffer[] srcs) throws IOException {
@SuppressWarnings("unused")
long total = 0;
for (ByteBuffer buffer : srcs) {
total += buffer.remaining();
}
public static byte[] readBytes(SocketChannel ch, int len, int timeout) throws IOException {
return ch.read(len, timeout);
}
public static void writePkg(SocketChannel ch, byte[]... srcs) throws IOException {
ch.write(srcs);
// https://github.com/alibaba/canal/issues/24
// 部分windows用户会出现size != total的情况,jdk为java7/openjdk,估计和java版本有关,暂时不做检查
// long size = ch.write(srcs);
// if (size != total) {
// throw new IOException("unexpected blocking io behavior");
// }
}
public static void write(SocketChannel ch, byte[] body) throws IOException {
write(ch, body, (byte) 0);
public static void writeBody(SocketChannel ch, byte[] body) throws IOException {
writeBody0(ch, body, (byte) 0);
}
public static void write(SocketChannel ch, byte[] body, byte packetSeqNumber) throws IOException {
public static void writeBody0(SocketChannel ch, byte[] body, byte packetSeqNumber) throws IOException {
HeaderPacket header = new HeaderPacket();
header.setPacketBodyLength(body.length);
header.setPacketSequenceNumber(packetSeqNumber);
write(ch, new ByteBuffer[] { ByteBuffer.wrap(header.toBytes()), ByteBuffer.wrap(body) });
ch.write(header.toBytes(), body);
}
}
@@ -4,7 +4,6 @@ import java.io.IOException;
import java.net.InetSocketAddress;
import org.junit.Assert;
import org.junit.Test;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
@@ -0,0 +1,119 @@
package com.alibaba.otter.canal.parse.driver.mysql;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.junit.Test;
import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet;
import com.alibaba.otter.canal.parse.driver.mysql.packets.UUIDSet;
/**
* Created by hiwjd on 2018/4/25. hiwjd0@gmail.com
*/
public class MysqlGTIDSetTest {
@Test
public void testEncode() throws IOException {
GTIDSet gtidSet = MysqlGTIDSet.parse("726757ad-4455-11e8-ae04-0242ac110002:1");
byte[] bytes = gtidSet.encode();
byte[] expected = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x67, 0x57, (byte) 0xad, 0x44, 0x55,
0x11, (byte) 0xe8, (byte) 0xae, 0x04, 0x02, 0x42, (byte) 0xac, 0x11, 0x00, 0x02, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00 };
for (int i = 0; i < bytes.length; i++) {
assertEquals(expected[i], bytes[i]);
}
}
@Test
public void testParse() {
Map<String, MysqlGTIDSet> cases = new HashMap<String, MysqlGTIDSet>(5);
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1",
buildForTest(new Material("726757ad-4455-11e8-ae04-0242ac110002", 1, 2)));
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3",
buildForTest(new Material("726757ad-4455-11e8-ae04-0242ac110002", 1, 4)));
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3:4",
buildForTest(new Material("726757ad-4455-11e8-ae04-0242ac110002", 1, 5)));
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3:7-9",
buildForTest(new Material("726757ad-4455-11e8-ae04-0242ac110002", 1, 4, 7, 10)));
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3,726757ad-4455-11e8-ae04-0242ac110003:4",
buildForTest(Arrays.asList(new Material("726757ad-4455-11e8-ae04-0242ac110002", 1, 4),
new Material("726757ad-4455-11e8-ae04-0242ac110003", 4, 5))));
for (Map.Entry<String, MysqlGTIDSet> entry : cases.entrySet()) {
MysqlGTIDSet expected = entry.getValue();
MysqlGTIDSet actual = MysqlGTIDSet.parse(entry.getKey());
assertEquals(expected, actual);
}
}
private static class Material {
public Material(String uuid, long start, long stop){
this.uuid = uuid;
this.start = start;
this.stop = stop;
this.start1 = 0;
this.stop1 = 0;
}
public Material(String uuid, long start, long stop, long start1, long stop1){
this.uuid = uuid;
this.start = start;
this.stop = stop;
this.start1 = start1;
this.stop1 = stop1;
}
public String uuid;
public long start;
public long stop;
public long start1;
public long stop1;
}
private MysqlGTIDSet buildForTest(Material material) {
return buildForTest(Arrays.asList(material));
}
private MysqlGTIDSet buildForTest(List<Material> materials) {
Map<String, UUIDSet> sets = new HashMap<String, UUIDSet>();
for (Material a : materials) {
UUIDSet.Interval interval = new UUIDSet.Interval();
interval.start = a.start;
interval.stop = a.stop;
List<UUIDSet.Interval> intervals = new ArrayList<UUIDSet.Interval>();
intervals.add(interval);
if (a.start1 > 0 && a.stop1 > 0) {
UUIDSet.Interval interval1 = new UUIDSet.Interval();
interval1.start = a.start1;
interval1.stop = a.stop1;
intervals.add(interval1);
}
UUIDSet us = new UUIDSet();
us.SID = UUID.fromString(a.uuid);
us.intervals = intervals;
sets.put(a.uuid, us);
}
MysqlGTIDSet gs = new MysqlGTIDSet();
gs.sets = sets;
return gs;
}
}
@@ -0,0 +1,30 @@
package com.alibaba.otter.canal.parse.driver.mysql;
import static org.junit.Assert.assertEquals;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import com.alibaba.otter.canal.parse.driver.mysql.packets.UUIDSet;
/**
* Created by hiwjd on 2018/4/26. hiwjd0@gmail.com
*/
public class UUIDSetTest {
@Test
public void testToString() {
Map<String, String> cases = new HashMap<String, String>(4);
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1", "726757ad-4455-11e8-ae04-0242ac110002:1");
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3", "726757ad-4455-11e8-ae04-0242ac110002:1-3");
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3:4-6", "726757ad-4455-11e8-ae04-0242ac110002:1-6");
cases.put("726757ad-4455-11e8-ae04-0242ac110002:1-3:5-7", "726757ad-4455-11e8-ae04-0242ac110002:1-3:5-7");
for (Map.Entry<String, String> entry : cases.entrySet()) {
String expected = entry.getValue();
assertEquals(expected, UUIDSet.parse(entry.getKey()).toString());
}
}
}
+77 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId>
<version>1.0.22</version>
<version>1.0.26-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.alibaba.otter</groupId>
@@ -21,6 +21,82 @@
<artifactId>canal.protocol</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.ddlutils</groupId>
<artifactId>ddlutils</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils-core</artifactId>
</exclusion>
<exclusion>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</exclusion>
<exclusion>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
</exclusion>
<exclusion>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging-api</artifactId>
</exclusion>
<exclusion>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
</exclusion>
<exclusion>
<groupId>stax</groupId>
<artifactId>stax-api</artifactId>
</exclusion>
<exclusion>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</exclusion>
<exclusion>
<groupId>commons-digester</groupId>
<artifactId>commons-digester</artifactId>
</exclusion>
<exclusion>
<groupId>commons-betwixt</groupId>
<artifactId>commons-betwixt</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2</version>
</dependency>
<!-- test dependency -->
<dependency>
<groupId>junit</groupId>
@@ -1,9 +1,11 @@
package com.alibaba.otter.canal.example;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -16,6 +18,7 @@ import com.alibaba.otter.canal.protocol.CanalEntry.Column;
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
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.RowData;
import com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin;
@@ -56,10 +59,12 @@ public class AbstractCanalClientTest {
context_format += "****************************************************" + SEP;
row_format = SEP
+ "----------------> binlog[{}:{}] , name[{},{}] , eventType : {} , executeTime : {} , delay : {}ms"
+ "----------------> binlog[{}:{}] , name[{},{}] , eventType : {} , executeTime : {}({}) , gtid : ({}) , delay : {} ms"
+ SEP;
transaction_format = SEP + "================> binlog[{}:{}] , executeTime : {} , delay : {}ms" + SEP;
transaction_format = SEP
+ "================> binlog[{}:{}] , executeTime : {}({}) , gtid : ({}) , delay : {}ms"
+ SEP;
}
@@ -82,14 +87,15 @@ public class AbstractCanalClientTest {
});
thread.setUncaughtExceptionHandler(handler);
thread.start();
running = true;
thread.start();
}
protected void stop() {
if (!running) {
return;
}
connector.stopRunning();
running = false;
if (thread != null) {
try {
@@ -157,14 +163,20 @@ public class AbstractCanalClientTest {
long time = entry.getHeader().getExecuteTime();
Date date = new Date(time);
SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
return entry.getHeader().getLogfileName() + ":" + entry.getHeader().getLogfileOffset() + ":"
+ entry.getHeader().getExecuteTime() + "(" + format.format(date) + ")";
String position = entry.getHeader().getLogfileName() + ":" + entry.getHeader().getLogfileOffset() + ":"
+ 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) {
for (Entry entry : entrys) {
long executeTime = entry.getHeader().getExecuteTime();
long delayTime = new Date().getTime() - executeTime;
Date date = new Date(entry.getHeader().getExecuteTime());
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN) {
@@ -178,8 +190,10 @@ public class AbstractCanalClientTest {
logger.info(transaction_format,
new Object[] { entry.getHeader().getLogfileName(),
String.valueOf(entry.getHeader().getLogfileOffset()),
String.valueOf(entry.getHeader().getExecuteTime()), String.valueOf(delayTime) });
String.valueOf(entry.getHeader().getExecuteTime()), simpleDateFormat.format(date),
entry.getHeader().getGtid(), String.valueOf(delayTime) });
logger.info(" BEGIN ----> Thread id: {}", begin.getThreadId());
printXAInfo(begin.getPropsList());
} else if (entry.getEntryType() == EntryType.TRANSACTIONEND) {
TransactionEnd end = null;
try {
@@ -190,10 +204,12 @@ public class AbstractCanalClientTest {
// 打印事务提交信息,事务id
logger.info("----------------\n");
logger.info(" END ----> transaction id: {}", end.getTransactionId());
printXAInfo(end.getPropsList());
logger.info(transaction_format,
new Object[] { entry.getHeader().getLogfileName(),
String.valueOf(entry.getHeader().getLogfileOffset()),
String.valueOf(entry.getHeader().getExecuteTime()), String.valueOf(delayTime) });
String.valueOf(entry.getHeader().getExecuteTime()), simpleDateFormat.format(date),
entry.getHeader().getGtid(), String.valueOf(delayTime) });
}
continue;
@@ -213,13 +229,15 @@ public class AbstractCanalClientTest {
new Object[] { entry.getHeader().getLogfileName(),
String.valueOf(entry.getHeader().getLogfileOffset()), entry.getHeader().getSchemaName(),
entry.getHeader().getTableName(), eventType,
String.valueOf(entry.getHeader().getExecuteTime()), String.valueOf(delayTime) });
String.valueOf(entry.getHeader().getExecuteTime()), simpleDateFormat.format(date),
entry.getHeader().getGtid(), String.valueOf(delayTime) });
if (eventType == EventType.QUERY || rowChage.getIsDdl()) {
logger.info(" sql ----> " + rowChage.getSql() + SEP);
continue;
}
printXAInfo(rowChage.getPropsList());
for (RowData rowData : rowChage.getRowDatasList()) {
if (eventType == EventType.DELETE) {
printColumn(rowData.getBeforeColumnsList());
@@ -236,7 +254,18 @@ public class AbstractCanalClientTest {
protected void printColumn(List<Column> columns) {
for (Column column : columns) {
StringBuilder builder = new StringBuilder();
builder.append(column.getName() + " : " + column.getValue());
try {
if (StringUtils.containsIgnoreCase(column.getMysqlType(), "BLOB")
|| StringUtils.containsIgnoreCase(column.getMysqlType(), "BINARY")) {
// get value bytes
builder.append(column.getName() + " : "
+ new String(column.getValue().getBytes("ISO-8859-1"), "UTF-8"));
} else {
builder.append(column.getName() + " : " + column.getValue());
}
} catch (UnsupportedEncodingException e) {
}
builder.append(" type=" + column.getMysqlType());
if (column.getUpdated()) {
builder.append(" update=" + column.getUpdated());
@@ -246,6 +275,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) {
this.connector = connector;
}
@@ -1,7 +1,5 @@
package com.alibaba.otter.canal.example;
import org.apache.commons.lang.exception.ExceptionUtils;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
@@ -41,7 +39,7 @@ public class ClusterCanalClientTest extends AbstractCanalClientTest {
logger.info("## stop the canal client");
clientTest.stop();
} catch (Throwable e) {
logger.warn("##something goes wrong when stopping canal:\n{}", ExceptionUtils.getFullStackTrace(e));
logger.warn("##something goes wrong when stopping canal:", e);
} finally {
logger.info("## canal client is down.");
}
@@ -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();
}
}
}
@@ -2,8 +2,6 @@ package com.alibaba.otter.canal.example;
import java.net.InetSocketAddress;
import org.apache.commons.lang.exception.ExceptionUtils;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.common.utils.AddressUtils;
@@ -23,8 +21,11 @@ public class SimpleCanalClientTest extends AbstractCanalClientTest {
public static void main(String args[]) {
// 根据ip,直接创建链接,无HA的功能
String destination = "example";
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(AddressUtils.getHostIp(),
11111), destination, "", "");
String ip = AddressUtils.getHostIp();
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(ip, 11111),
destination,
"",
"");
final SimpleCanalClientTest clientTest = new SimpleCanalClientTest(destination);
clientTest.setConnector(connector);
@@ -36,7 +37,7 @@ public class SimpleCanalClientTest extends AbstractCanalClientTest {
logger.info("## stop the canal client");
clientTest.stop();
} catch (Throwable e) {
logger.warn("##something goes wrong when stopping canal:\n{}", ExceptionUtils.getFullStackTrace(e));
logger.warn("##something goes wrong when stopping canal:", e);
} finally {
logger.info("## canal client is down.");
}
@@ -0,0 +1,144 @@
package com.alibaba.otter.canal.example.db;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.Message;
import org.slf4j.MDC;
import java.util.Date;
import java.util.List;
public abstract class AbstractDbClient extends CanalConnectorClient {
public abstract void insert(CanalEntry.Header header, List<CanalEntry.Column> afterColumns);
public abstract void update(CanalEntry.Header header, List<CanalEntry.Column> afterColumns);
public abstract void delete(CanalEntry.Header header, List<CanalEntry.Column> beforeColumns);
@Override
public synchronized void start() {
if (running) {
return;
}
super.start();
}
@Override
public synchronized void stop() {
if (!running) {
return;
}
super.stop();
MDC.remove("destination");
}
@Override
protected void processMessage(Message message) {
long batchId = message.getId();
//遍历每条消息
for (CanalEntry.Entry entry : message.getEntries()) {
session(entry);//no exception
}
//ack all the time。
connector.ack(batchId);
}
private void session(CanalEntry.Entry entry) {
CanalEntry.EntryType entryType = entry.getEntryType();
int times = 0;
boolean success = false;
while (!success) {
if (times > 0) {
/**
* 1:retry,重试,重试默认为3次,由retryTimes参数决定,如果重试次数达到阈值,则跳过,并且记录日志。
* 2:ignore,直接忽略,不重试,记录日志。
*/
if (exceptionStrategy == ExceptionStrategy.RETRY.code) {
if (times >= retryTimes) {
break;
}
} else {
break;
}
}
try {
switch (entryType) {
case TRANSACTIONBEGIN:
transactionBegin(entry);
break;
case TRANSACTIONEND:
transactionEnd(entry);
break;
case ROWDATA:
rowData(entry);
break;
default:
break;
}
success = true;
} catch (Exception e) {
times++;
logger.error("parse event has an error ,times: + " + times + ", data:" + entry.toString(), e);
}
}
}
private void rowData(CanalEntry.Entry entry) throws Exception {
CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
CanalEntry.EventType eventType = rowChange.getEventType();
CanalEntry.Header header = entry.getHeader();
long executeTime = header.getExecuteTime();
long delayTime = new Date().getTime() - executeTime;
String sql = rowChange.getSql();
try {
if (!isDML(eventType) || rowChange.getIsDdl()) {
processDDL(header, eventType, sql);
return;
}
//处理DML数据
processDML(header, eventType, rowChange, sql);
} catch (Exception e) {
logger.error("process event error ,", e);
logger.error(rowFormat,
new Object[]{header.getLogfileName(), String.valueOf(header.getLogfileOffset()),
header.getSchemaName(), header.getTableName(), eventType,
String.valueOf(executeTime), String.valueOf(delayTime)});
throw e;//重新抛出
}
}
/**
* 处理 dml 数据
*
* @param header
* @param eventType
* @param rowChange
* @param sql
*/
protected void processDML(CanalEntry.Header header, CanalEntry.EventType eventType, CanalEntry.RowChange rowChange, String sql) {
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
switch (eventType) {
case DELETE:
delete(header, rowData.getBeforeColumnsList());
break;
case INSERT:
insert(header, rowData.getAfterColumnsList());
break;
case UPDATE:
update(header, rowData.getAfterColumnsList());
break;
default:
whenOthers(header, sql);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More