Compare commits

...

180 Commits

Author SHA1 Message Date
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
agapple
ba173fcd48 [maven-release-plugin] prepare release canal-1.0.22 2016-06-30 16:59:05 +08:00
agapple
69170f58df fixed issue #172, support rds 2016-06-30 16:45:42 +08:00
agapple
c3e38e50e2 fixed issue #187 ,support MySQL 5.7 Json 2016-06-30 16:23:05 +08:00
agapple
ae05a3f39f fixed issue #172, 精确识别主备切换,尝试基于时间戳重新定位位点,确保不丢数据 2016-06-28 22:58:55 +08:00
agapple
6eafc73a65 Merge pull request #177 from chenyishun/master
RDS主从库切换时候异常处理
2016-05-10 18:06:11 +08:00
Yishun.Chen
63b3c4ab54 IOException限制retry次数,超过次数后跳过binlog解析 2016-05-03 16:30:38 +08:00
Yishun.Chen
ecba855bf9 IOException限制retry次数,超过次数后跳过binlog解析 2016-05-03 16:30:01 +08:00
Yishun.Chen
b599846470 IOException限制retry次数,超过次数后跳过binlog解析 2016-05-03 16:29:23 +08:00
agapple
8aefd7e79b fixed issue #171, getNextAddress 2016-04-29 17:44:06 +08:00
agapple
4e76a4bd6e fixed issue #176 , handle running deleted 2016-04-29 17:43:16 +08:00
agapple
f1a1cb26cc fixed issue #171, compiler error 2016-04-29 11:29:16 +08:00
agapple
4f1e3bafe7 fixed issue #171 , client HA bug , modify SimpleCanalConnector 2016-04-29 11:26:37 +08:00
agapple
6a03ffaf7b Merge pull request #173 from lulu2panpan/master
Canal客户端高可用bug修复
2016-04-29 11:16:24 +08:00
lulu2panpan
d7d969915e Update ClientRunningMonitor.java 2016-04-29 09:50:37 +08:00
lulu2panpan
4898ed7b83 Update SimpleCanalConnector.java 2016-04-29 09:40:27 +08:00
lulu2panpan
11e1d1d600 Update CanalConnectors.java 2016-04-29 09:31:40 +08:00
lulu2panpan
e614c6141d Update ClusterCanalConnector.java 2016-04-29 09:28:48 +08:00
lulu2panpan
8ded94a09e Update ClusterCanalConnector.java 2016-04-29 09:28:12 +08:00
agapple
38cc81b7da fixed issue #162 , fixed IDE CanalLauncher 2016-04-07 11:25:59 +08:00
agapple
533c5cb8f6 fixed issue #119 , fixed batch data changeColumns 2016-04-05 23:42:08 +08:00
agapple
5932e1e31f edit url 2016-03-05 18:46:21 +08:00
agapple
70dda221f1 add yugong 2016-03-05 18:44:57 +08:00
agapple
c32a5d0882 fixed issue #139 , KILL CONNECTION binlogdump thread 2016-03-01 22:19:02 +08:00
agapple
fbd99acac2 Merge pull request #145 from OpenZYK/master
添加rowdata过滤
2016-02-25 18:14:20 +08:00
zhangyukun
51cfa7fa96 Update AbstractMysqlEventParser.java 2016-02-25 13:56:07 +08:00
zhangyukun
652101035e 添加Rows过滤 2016-02-25 11:44:22 +08:00
zhangyukun
1813c1eff9 Update canal.properties 2016-02-25 11:39:10 +08:00
zhangyukun
31ea9fa8f0 新增canal.instance.filter.rows过滤 2016-02-25 11:38:09 +08:00
zhangyukun
369acd8eed 新增canal.instance.filter.rows过滤 2016-02-25 11:37:45 +08:00
zhangyukun
891eb960ca 新增canal.instance.filter.rows过滤 2016-02-25 11:37:17 +08:00
zhangyukun
2e5a165b88 新增canal.instance.filter.rows过滤
为parseRowsEvent方法添加过滤
2016-02-25 11:35:46 +08:00
zhangyukun
6b9c1305ff 添加dml过滤判断
修复parseRowsEvent方法中没有dml过滤的判断而导致dml操作的数据总是会输出到store中的bug
2016-02-24 21:18:01 +08:00
agapple
4f504aab31 fixed testcase compiler error 2016-02-24 17:05:14 +08:00
agapple
dc28c68bf0 fixed format 2016-02-23 17:05:12 +08:00
agapple
38f2f98da3 Merge pull request #144 from dingjun84/master
注销父类已经有的属性,避免super.start()调用了没有在子类初始化的同名属性
2016-02-23 17:03:20 +08:00
dingjun
efebbe301a 注销父类已经有的属性,避免super.start()调用了没有在子类初始化的同名属性 2016-02-23 10:40:42 +08:00
agapple
bf10b16138 migrate 2016-02-04 11:22:36 +08:00
agapple
37478f3409 Merge pull request #138 from MarkLinHz/master
make canal server become a single instance
2016-02-04 11:02:23 +08:00
yinxiu
d2f98cda87 remove duplicate code 2016-01-14 10:59:40 +08:00
yinxiu
ed74d934d3 add setAlarmHandler for otter 2016-01-11 16:15:41 +08:00
yinxiu
7b29828f60 add AbstractCanalInstance and remove duplicate code 2016-01-08 20:32:08 +08:00
yinxiu
9164fe4ca7 make canal server become a single instance 2016-01-07 17:23:58 +08:00
agapple
51ac311aa1 fixed git ignore 2015-12-31 14:31:55 +08:00
agapple
643bd5cb71 [maven-release-plugin] prepare for next development iteration 2015-12-30 16:23:30 +08:00
agapple
8375ea4b7d [maven-release-plugin] prepare release canal-1.0.21 2015-12-30 16:23:20 +08:00
agapple
72ceb58fa3 fixed issue #135 , isUpdate bugfix 2015-12-02 14:37:06 +08:00
agapple
44f2b74862 fixed issue #130 , support mysql5.6 time2/timestamp2/datetime2 javaType 2015-11-13 14:16:12 +08:00
agapple
5a34195201 Merge pull request #128 from likeloveluck/simple_ddl_pattern
fix rename table ddl parser tablename and schemaname error
2015-09-07 15:49:12 +08:00
jiawei
d976932177 fix rename table ddl parser tablename and schemaname error 2015-09-01 12:17:41 +08:00
agapple
d887c8f763 Merge pull request #127 from dozer47528/master
fix not skip filter and read the error data
2015-08-04 11:24:14 +08:00
Dozer
197a1446b7 fix not skip filter and read the error data 2015-07-31 17:18:42 +08:00
agapple
155c00b256 Merge pull request #124 from smilingleo/master
fixed a hard coded mysql host ip address
2015-07-28 13:51:44 +08:00
Leo
b16f4844a1 fixed a hard coded mysql host ip address 2015-07-23 10:42:03 +08:00
agapple
f2f2bfb538 add rename testcase 2015-07-17 14:43:15 +08:00
agapple
5f6580399c fixed format 2015-07-17 14:41:39 +08:00
agapple
10930b9e3d Merge pull request #122 from likeloveluck/rename_bug_fix
修复rename table命令中包含多个表,解析失败问题
2015-07-17 14:33:38 +08:00
jiawei
d1afeb7540 change result reference 2015-07-16 19:25:46 +08:00
jiawei
119af9fca1 change rename operate 2015-07-15 19:20:30 +08:00
agapple
f0ce4b569e Merge pull request #121 from huyanping/patch-1
Update README.md
2015-07-14 16:59:24 +08:00
Jenner
4459b8acdf Update README.md 2015-07-14 14:42:13 +08:00
agapple
bb5d5749bd Merge pull request #120 from smilingleo/master
增加了一个脚本来自动获取mysql master binlog信息,并自动更新instance配置文件
2015-07-05 11:10:56 +08:00
smilingleo
169baafb8b correct format 2015-07-03 10:32:40 +08:00
smilingleo
199f30de87 add a script to fetch mysql master binlog file and sync position automatically 2015-07-03 10:29:58 +08:00
smilingleo
87c14f0b51 Revert "test"
This reverts commit f35d965f63.
2015-07-03 10:08:03 +08:00
smilingleo
f35d965f63 test 2015-07-03 10:02:10 +08:00
agapple
9956b3557f [maven-release-plugin] prepare for next development iteration 2015-06-30 10:23:54 +08:00
171 changed files with 29297 additions and 16282 deletions

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@ otter.properties
jtester.properties jtester.properties
.idea/ .idea/
*.iml *.iml
.DS_Store

View File

@@ -9,7 +9,7 @@
<h1>背景</h1> <h1>背景</h1>
<p style="font-size: 14px;"> 早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&amp;消费的业务,从此开启了一段新纪元。</p> <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;"> </p>
<p style="font-size: 14px;">基于日志增量订阅&amp;消费支持的业务:</p> <p style="font-size: 14px;">基于日志增量订阅&amp;消费支持的业务:</p>
<ol style="font-size: 14px;"> <ol style="font-size: 14px;">
@@ -36,7 +36,7 @@
<li>slave重做中继日志中的事件,将改变反映它自己的数据。</li> <li>slave重做中继日志中的事件,将改变反映它自己的数据。</li>
</ol> </ol>
<h3>canal的工作原理:</h3> <h3>canal的工作原理:</h3>
<p><img width="590" src="http://dl.iteye.com/upload/attachment/0080/3107/c87b67ba-394c-3086-9577-9db05be04c95.jpg" alt="" height="273"> <p><img width="590" src="https://camo.githubusercontent.com/46c626b4cde399db43b2634a7911a04aecf273a0/687474703a2f2f646c2e69746579652e636f6d2f75706c6f61642f6174746163686d656e742f303038302f333130372f63383762363762612d333934632d333038362d393537372d3964623035626530346339352e6a7067" alt="" height="273">
<p>原理相对比较简单:</p> <p>原理相对比较简单:</p>
<ol> <ol>
<li>canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议</li> <li>canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议</li>
@@ -62,11 +62,16 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
<li><a href="https://github.com/alibaba/canal/releases">Download</a></li> <li><a href="https://github.com/alibaba/canal/releases">Download</a></li>
</ul> </ul>
<h1>相关资料: </h1> <h1>相关资料</h1>
* ADC阿里技术嘉年华分享ppt (放在google docs上,可能需要翻墙): <a href="https://docs.google.com/presentation/d/1MkszUPYRDkfVPz9IqOT1LLT5d9tuwde_WC8GZvjaDRg/edit?usp=sharing">ppt下载</href> * ADC阿里技术嘉年华分享ppt (放在google docs上,可能需要翻墙): <a href="https://docs.google.com/presentation/d/1MkszUPYRDkfVPz9IqOT1LLT5d9tuwde_WC8GZvjaDRg/edit?usp=sharing">ppt下载</href>
* [与阿里巴巴的RocketMQ配合使用](https://github.com/alibaba/RocketMQ) * [与阿里巴巴的RocketMQ配合使用](https://github.com/alibaba/RocketMQ)
<h1>相关开源</h1>
<ol>
<li>阿里巴巴分布式数据库同步系统(解决中美异地机房):<a href="http://github.com/alibaba/otter">http://github.com/alibaba/otter</a></li>
<li>阿里巴巴去Oracle数据迁移同步工具(目标支持MySQL/DRDS):<a href="http://github.com/alibaba/yugong">http://github.com/alibaba/yugong</a></li>
</ol>
<h1>问题反馈</h1> <h1>问题反馈</h1>
<ol> <ol>
@@ -75,3 +80,10 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
<li>新浪微博: agapple0002 </li> <li>新浪微博: agapple0002 </li>
<li>报告issue:<a href="https://github.com/alibaba/canal/issues">issues</a></li> <li>报告issue:<a href="https://github.com/alibaba/canal/issues">issues</a></li>
</ol> </ol>
<pre>
【招聘】阿里巴巴中间件团队招聘JAVA高级工程师
岗位主要为技术型内容(非业务部门),阿里中间件整个体系对于未来想在技术上有所沉淀的同学还是非常有帮助的
工作地点:杭州、北京均可. ps. 阿里待遇向来都是不错的,有意者可以QQ、微博私聊.
具体招聘内容:https://job.alibaba.com/zhaopin/position_detail.htm?positionId=32666
</pre>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.20</version> <version>1.0.25-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -10,5 +10,7 @@ import java.net.SocketAddress;
*/ */
public interface CanalNodeAccessStrategy { public interface CanalNodeAccessStrategy {
SocketAddress currentNode();
SocketAddress nextNode(); SocketAddress nextNode();
} }

View File

@@ -3,7 +3,6 @@ package com.alibaba.otter.canal.client.impl;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -41,11 +40,17 @@ public class ClusterCanalConnector implements CanalConnector {
public void connect() throws CanalClientException { public void connect() throws CanalClientException {
while (currentConnector == null) { while (currentConnector == null) {
SocketAddress nextAddress = this.accessStrategy.nextNode();
int times = 0; int times = 0;
while (true) { while (true) {
try { try {
currentConnector = new SimpleCanalConnector(nextAddress, username, password, destination); currentConnector = new SimpleCanalConnector(null, username, password, destination) {
@Override
public SocketAddress getNextAddress() {
return accessStrategy.nextNode();
}
};
currentConnector.setSoTimeout(soTimeout); currentConnector.setSoTimeout(soTimeout);
if (filter != null) { if (filter != null) {
currentConnector.setFilter(filter); currentConnector.setFilter(filter);
@@ -57,7 +62,7 @@ public class ClusterCanalConnector implements CanalConnector {
currentConnector.connect(); currentConnector.connect();
break; break;
} catch (Exception e) { } catch (Exception e) {
logger.warn("failed to connect to:{} after retry {} times", nextAddress, times); logger.warn("failed to connect to:{} after retry {} times", accessStrategy.currentNode(), times);
currentConnector.disconnect(); currentConnector.disconnect();
currentConnector = null; currentConnector = null;
// retry for #retryTimes for each node when trying to // retry for #retryTimes for each node when trying to
@@ -101,9 +106,10 @@ public class ClusterCanalConnector implements CanalConnector {
this.filter = filter; this.filter = filter;
return; return;
} catch (Throwable t) { } catch (Throwable t) {
logger.warn("something goes wrong when subscribing from server:{}\n{}", logger.warn(String.format(
currentConnector.getAddress(), "something goes wrong when subscribing from server: %s",
ExceptionUtils.getFullStackTrace(t)); currentConnector != null ? currentConnector.getAddress() : "null"),
t);
times++; times++;
restart(); restart();
logger.info("restart the connector for next round retry."); logger.info("restart the connector for next round retry.");
@@ -120,9 +126,8 @@ public class ClusterCanalConnector implements CanalConnector {
currentConnector.unsubscribe(); currentConnector.unsubscribe();
return; return;
} catch (Throwable t) { } catch (Throwable t) {
logger.warn("something goes wrong when unsubscribing from server:{}\n{}", logger.warn(String.format("something goes wrong when unsubscribing from server:%s",
currentConnector.getAddress(), currentConnector != null ? currentConnector.getAddress() : "null"), t);
ExceptionUtils.getFullStackTrace(t));
times++; times++;
restart(); restart();
logger.info("restart the connector for next round retry."); logger.info("restart the connector for next round retry.");
@@ -138,9 +143,8 @@ public class ClusterCanalConnector implements CanalConnector {
Message msg = currentConnector.get(batchSize); Message msg = currentConnector.get(batchSize);
return msg; return msg;
} catch (Throwable t) { } catch (Throwable t) {
logger.warn("something goes wrong when getting data from server:{}\n{}", logger.warn(String.format("something goes wrong when getting data from server:%s",
currentConnector.getAddress(), currentConnector != null ? currentConnector.getAddress() : "null"), t);
ExceptionUtils.getFullStackTrace(t));
times++; times++;
restart(); restart();
logger.info("restart the connector for next round retry."); logger.info("restart the connector for next round retry.");
@@ -156,9 +160,8 @@ public class ClusterCanalConnector implements CanalConnector {
Message msg = currentConnector.get(batchSize, timeout, unit); Message msg = currentConnector.get(batchSize, timeout, unit);
return msg; return msg;
} catch (Throwable t) { } catch (Throwable t) {
logger.warn("something goes wrong when getting data from server:{}\n{}", logger.warn(String.format("something goes wrong when getting data from server:%s",
currentConnector.getAddress(), currentConnector != null ? currentConnector.getAddress() : "null"), t);
ExceptionUtils.getFullStackTrace(t));
times++; times++;
restart(); restart();
logger.info("restart the connector for next round retry."); logger.info("restart the connector for next round retry.");
@@ -174,9 +177,8 @@ public class ClusterCanalConnector implements CanalConnector {
Message msg = currentConnector.getWithoutAck(batchSize); Message msg = currentConnector.getWithoutAck(batchSize);
return msg; return msg;
} catch (Throwable t) { } catch (Throwable t) {
logger.warn("something goes wrong when getWithoutAck data from server:{}\n{}", logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
currentConnector.getAddress(), currentConnector.getAddress()), t);
ExceptionUtils.getFullStackTrace(t));
times++; times++;
restart(); restart();
logger.info("restart the connector for next round retry."); logger.info("restart the connector for next round retry.");
@@ -192,9 +194,8 @@ public class ClusterCanalConnector implements CanalConnector {
Message msg = currentConnector.getWithoutAck(batchSize, timeout, unit); Message msg = currentConnector.getWithoutAck(batchSize, timeout, unit);
return msg; return msg;
} catch (Throwable t) { } catch (Throwable t) {
logger.warn("something goes wrong when getWithoutAck data from server:{}\n{}", logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s",
currentConnector.getAddress(), currentConnector.getAddress()), t);
ExceptionUtils.getFullStackTrace(t));
times++; times++;
restart(); restart();
logger.info("restart the connector for next round retry."); logger.info("restart the connector for next round retry.");
@@ -210,9 +211,8 @@ public class ClusterCanalConnector implements CanalConnector {
currentConnector.rollback(batchId); currentConnector.rollback(batchId);
return; return;
} catch (Throwable t) { } catch (Throwable t) {
logger.warn("something goes wrong when rollbacking data from server:{}\n{}", logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
currentConnector.getAddress(), currentConnector.getAddress()), t);
ExceptionUtils.getFullStackTrace(t));
times++; times++;
restart(); restart();
logger.info("restart the connector for next round retry."); logger.info("restart the connector for next round retry.");
@@ -228,9 +228,8 @@ public class ClusterCanalConnector implements CanalConnector {
currentConnector.rollback(); currentConnector.rollback();
return; return;
} catch (Throwable t) { } catch (Throwable t) {
logger.warn("something goes wrong when rollbacking data from server:{}\n{}", logger.warn(String.format("something goes wrong when rollbacking data from server:%s",
currentConnector.getAddress(), currentConnector.getAddress()), t);
ExceptionUtils.getFullStackTrace(t));
times++; times++;
restart(); restart();
logger.info("restart the connector for next round retry."); logger.info("restart the connector for next round retry.");
@@ -247,9 +246,8 @@ public class ClusterCanalConnector implements CanalConnector {
currentConnector.ack(batchId); currentConnector.ack(batchId);
return; return;
} catch (Throwable t) { } catch (Throwable t) {
logger.warn("something goes wrong when acking data from server:{}\n{}", logger.warn(String.format("something goes wrong when acking data from server:%s",
currentConnector.getAddress(), currentConnector.getAddress()), t);
ExceptionUtils.getFullStackTrace(t));
times++; times++;
restart(); restart();
logger.info("restart the connector for next round retry."); logger.info("restart the connector for next round retry.");

View File

@@ -62,6 +62,11 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
initRunning(this.zkClient.readData(runningPath, true)); initRunning(this.zkClient.readData(runningPath, true));
} }
@Override
public SocketAddress currentNode() {
return nextNode();
}
public SocketAddress nextNode() { public SocketAddress nextNode() {
if (runningAddress != null) {// 如果服务已经启动,直接选择当前正在工作的节点 if (runningAddress != null) {// 如果服务已经启动,直接选择当前正在工作的节点
return runningAddress; return runningAddress;

View File

@@ -5,7 +5,11 @@ import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.nio.channels.Channel;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel; import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -37,7 +41,6 @@ import com.alibaba.otter.canal.protocol.ClientIdentity;
import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.exception.CanalClientException; import com.alibaba.otter.canal.protocol.exception.CanalClientException;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
/** /**
* 基于{@linkplain CanalServerWithNetty}定义的网络协议接口,对于canal数据进行get/rollback/ack等操作 * 基于{@linkplain CanalServerWithNetty}定义的网络协议接口,对于canal数据进行get/rollback/ack等操作
@@ -57,6 +60,8 @@ public class SimpleCanalConnector implements CanalConnector {
private final ByteBuffer readHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN); private final ByteBuffer readHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
private final ByteBuffer writeHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN); private final ByteBuffer writeHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
private SocketChannel channel; private SocketChannel channel;
private ReadableByteChannel readableChannel;
private WritableByteChannel writableChannel;
private List<Compression> supportedCompressions = new ArrayList<Compression>(); private List<Compression> supportedCompressions = new ArrayList<Compression>();
private ClientIdentity clientIdentity; private ClientIdentity clientIdentity;
private ClientRunningMonitor runningMonitor; // 运行控制 private ClientRunningMonitor runningMonitor; // 运行控制
@@ -125,8 +130,14 @@ public class SimpleCanalConnector implements CanalConnector {
try { try {
channel = SocketChannel.open(); channel = SocketChannel.open();
channel.socket().setSoTimeout(soTimeout); channel.socket().setSoTimeout(soTimeout);
SocketAddress address = getAddress();
if (address == null) {
address = getNextAddress();
}
channel.connect(address); channel.connect(address);
Packet p = Packet.parseFrom(readNextPacket(channel)); readableChannel = Channels.newChannel(channel.socket().getInputStream());
writableChannel = Channels.newChannel(channel.socket().getOutputStream());
Packet p = Packet.parseFrom(readNextPacket());
if (p.getVersion() != 1) { if (p.getVersion() != 1) {
throw new CanalClientException("unsupported version at this client."); throw new CanalClientException("unsupported version at this client.");
} }
@@ -140,17 +151,17 @@ public class SimpleCanalConnector implements CanalConnector {
// //
ClientAuth ca = ClientAuth.newBuilder() ClientAuth ca = ClientAuth.newBuilder()
.setUsername(username != null ? username : "") .setUsername(username != null ? username : "")
.setPassword(ByteString.copyFromUtf8(password != null ? password : ""))
.setNetReadTimeout(soTimeout) .setNetReadTimeout(soTimeout)
.setNetWriteTimeout(soTimeout) .setNetWriteTimeout(soTimeout)
.build(); .build();
writeWithHeader(channel, writeWithHeader(Packet.newBuilder()
Packet.newBuilder()
.setType(PacketType.CLIENTAUTHENTICATION) .setType(PacketType.CLIENTAUTHENTICATION)
.setBody(ca.toByteString()) .setBody(ca.toByteString())
.build() .build()
.toByteArray()); .toByteArray());
// //
Packet ack = Packet.parseFrom(readNextPacket(channel)); Packet ack = Packet.parseFrom(readNextPacket());
if (ack.getType() != PacketType.ACK) { if (ack.getType() != PacketType.ACK) {
throw new CanalClientException("unexpected packet type when ack is expected"); throw new CanalClientException("unexpected packet type when ack is expected");
} }
@@ -169,14 +180,26 @@ public class SimpleCanalConnector implements CanalConnector {
} }
private void doDisconnnect() throws CanalClientException { private void doDisconnnect() throws CanalClientException {
if (readableChannel != null) {
quietlyClose(readableChannel);
readableChannel = null;
}
if (writableChannel != null) {
quietlyClose(writableChannel);
writableChannel = null;
}
if (channel != null) { if (channel != null) {
quietlyClose(channel);
channel = null;
}
}
private void quietlyClose(Channel channel) {
try { try {
channel.close(); channel.close();
} catch (IOException e) { } catch (IOException e) {
logger.warn("exception on closing channel:{} \n {}", channel, e); logger.warn("exception on closing channel:{} \n {}", channel, e);
} }
channel = null;
}
} }
public void subscribe() throws CanalClientException { public void subscribe() throws CanalClientException {
@@ -186,8 +209,7 @@ public class SimpleCanalConnector implements CanalConnector {
public void subscribe(String filter) throws CanalClientException { public void subscribe(String filter) throws CanalClientException {
waitClientRunning(); waitClientRunning();
try { try {
writeWithHeader(channel, writeWithHeader(Packet.newBuilder()
Packet.newBuilder()
.setType(PacketType.SUBSCRIPTION) .setType(PacketType.SUBSCRIPTION)
.setBody(Sub.newBuilder() .setBody(Sub.newBuilder()
.setDestination(clientIdentity.getDestination()) .setDestination(clientIdentity.getDestination())
@@ -198,7 +220,7 @@ public class SimpleCanalConnector implements CanalConnector {
.build() .build()
.toByteArray()); .toByteArray());
// //
Packet p = Packet.parseFrom(readNextPacket(channel)); Packet p = Packet.parseFrom(readNextPacket());
Ack ack = Ack.parseFrom(p.getBody()); Ack ack = Ack.parseFrom(p.getBody());
if (ack.getErrorCode() > 0) { if (ack.getErrorCode() > 0) {
throw new CanalClientException("failed to subscribe with reason: " + ack.getErrorMessage()); throw new CanalClientException("failed to subscribe with reason: " + ack.getErrorMessage());
@@ -213,8 +235,7 @@ public class SimpleCanalConnector implements CanalConnector {
public void unsubscribe() throws CanalClientException { public void unsubscribe() throws CanalClientException {
waitClientRunning(); waitClientRunning();
try { try {
writeWithHeader(channel, writeWithHeader(Packet.newBuilder()
Packet.newBuilder()
.setType(PacketType.UNSUBSCRIPTION) .setType(PacketType.UNSUBSCRIPTION)
.setBody(Unsub.newBuilder() .setBody(Unsub.newBuilder()
.setDestination(clientIdentity.getDestination()) .setDestination(clientIdentity.getDestination())
@@ -224,7 +245,7 @@ public class SimpleCanalConnector implements CanalConnector {
.build() .build()
.toByteArray()); .toByteArray());
// //
Packet p = Packet.parseFrom(readNextPacket(channel)); Packet p = Packet.parseFrom(readNextPacket());
Ack ack = Ack.parseFrom(p.getBody()); Ack ack = Ack.parseFrom(p.getBody());
if (ack.getErrorCode() > 0) { if (ack.getErrorCode() > 0) {
throw new CanalClientException("failed to unSubscribe with reason: " + ack.getErrorMessage()); throw new CanalClientException("failed to unSubscribe with reason: " + ack.getErrorMessage());
@@ -257,8 +278,7 @@ public class SimpleCanalConnector implements CanalConnector {
unit = TimeUnit.MILLISECONDS; unit = TimeUnit.MILLISECONDS;
} }
writeWithHeader(channel, writeWithHeader(Packet.newBuilder()
Packet.newBuilder()
.setType(PacketType.GET) .setType(PacketType.GET)
.setBody(Get.newBuilder() .setBody(Get.newBuilder()
.setAutoAck(false) .setAutoAck(false)
@@ -271,15 +291,14 @@ public class SimpleCanalConnector implements CanalConnector {
.toByteString()) .toByteString())
.build() .build()
.toByteArray()); .toByteArray());
return receiveMessages(); return receiveMessages();
} catch (IOException e) { } catch (IOException e) {
throw new CanalClientException(e); throw new CanalClientException(e);
} }
} }
private Message receiveMessages() throws InvalidProtocolBufferException, IOException { private Message receiveMessages() throws IOException {
Packet p = Packet.parseFrom(readNextPacket(channel)); Packet p = Packet.parseFrom(readNextPacket());
switch (p.getType()) { switch (p.getType()) {
case MESSAGES: { case MESSAGES: {
if (!p.getCompression().equals(Compression.NONE)) { if (!p.getCompression().equals(Compression.NONE)) {
@@ -311,7 +330,7 @@ public class SimpleCanalConnector implements CanalConnector {
.setBatchId(batchId) .setBatchId(batchId)
.build(); .build();
try { try {
writeWithHeader(channel, Packet.newBuilder() writeWithHeader(Packet.newBuilder()
.setType(PacketType.CLIENTACK) .setType(PacketType.CLIENTACK)
.setBody(ca.toByteString()) .setBody(ca.toByteString())
.build() .build()
@@ -329,7 +348,7 @@ public class SimpleCanalConnector implements CanalConnector {
.setBatchId(batchId) .setBatchId(batchId)
.build(); .build();
try { try {
writeWithHeader(channel, Packet.newBuilder() writeWithHeader(Packet.newBuilder()
.setType(PacketType.CLIENTROLLBACK) .setType(PacketType.CLIENTROLLBACK)
.setBody(ca.toByteString()) .setBody(ca.toByteString())
.build() .build()
@@ -346,7 +365,15 @@ public class SimpleCanalConnector implements CanalConnector {
// ==================== helper method ==================== // ==================== helper method ====================
private void writeWithHeader(SocketChannel channel, byte[] body) throws IOException { private void writeWithHeader(byte[] body) throws IOException {
writeWithHeader(writableChannel, body);
}
private byte[] readNextPacket() throws IOException {
return readNextPacket(readableChannel);
}
private void writeWithHeader(WritableByteChannel channel, byte[] body) throws IOException {
synchronized (writeDataLock) { synchronized (writeDataLock) {
writeHeader.clear(); writeHeader.clear();
writeHeader.putInt(body.length); writeHeader.putInt(body.length);
@@ -356,7 +383,7 @@ public class SimpleCanalConnector implements CanalConnector {
} }
} }
private byte[] readNextPacket(SocketChannel channel) throws IOException { private byte[] readNextPacket(ReadableByteChannel channel) throws IOException {
synchronized (readDataLock) { synchronized (readDataLock) {
readHeader.clear(); readHeader.clear();
read(channel, readHeader); read(channel, readHeader);
@@ -367,7 +394,7 @@ public class SimpleCanalConnector implements CanalConnector {
} }
} }
private void read(SocketChannel channel, ByteBuffer buffer) throws IOException { private void read(ReadableByteChannel channel, ByteBuffer buffer) throws IOException {
while (buffer.hasRemaining()) { while (buffer.hasRemaining()) {
int r = channel.read(buffer); int r = channel.read(buffer);
if (r == -1) { if (r == -1) {
@@ -434,6 +461,10 @@ public class SimpleCanalConnector implements CanalConnector {
} }
} }
public SocketAddress getNextAddress() {
return null;
}
public SocketAddress getAddress() { public SocketAddress getAddress() {
return address; return address;
} }

View File

@@ -32,4 +32,9 @@ public class SimpleNodeAccessStrategy implements CanalNodeAccessStrategy {
} }
} }
@Override
public SocketAddress currentNode() {
return nodes.get(index);
}
} }

View File

@@ -1,6 +1,7 @@
package com.alibaba.otter.canal.client.impl.running; package com.alibaba.otter.canal.client.impl.running;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.text.MessageFormat;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -20,6 +21,7 @@ import com.alibaba.otter.canal.common.utils.BooleanMutex;
import com.alibaba.otter.canal.common.utils.JsonUtils; import com.alibaba.otter.canal.common.utils.JsonUtils;
import com.alibaba.otter.canal.common.zookeeper.ZkClientx; import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils; import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils;
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
/** /**
* clinet running控制 * clinet running控制
@@ -62,6 +64,8 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
public void handleDataDeleted(String dataPath) throws Exception { public void handleDataDeleted(String dataPath) throws Exception {
MDC.put("destination", destination); MDC.put("destination", destination);
mutex.set(false); mutex.set(false);
// 触发一下退出,可能是人为干预的释放操作或者网络闪断引起的session expired timeout
processActiveExit();
if (!release && activeData != null && isMine(activeData.getAddress())) { if (!release && activeData != null && isMine(activeData.getAddress())) {
// 如果上一次active的状态就是本机,则即时触发一下active抢占 // 如果上一次active的状态就是本机,则即时触发一下active抢占
initRunning(); initRunning();
@@ -96,7 +100,11 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
releaseRunning(); // 尝试一下release releaseRunning(); // 尝试一下release
} }
public void initRunning() { // 改动记录:
// 1,在方法上加synchronized关键字,保证同步顺序执行;
// 2,判断Zk上已经存在的activeData是否是本机,是的话把mutex重置为true,否则会导致死锁
// 3,增加异常处理,保证出现异常时,running节点能被删除,否则会导致死锁
public synchronized void initRunning() {
if (!isStart()) { if (!isStart()) {
return; return;
} }
@@ -116,11 +124,22 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle {
initRunning(); initRunning();
} else { } else {
activeData = JsonUtils.unmarshalFromByte(bytes, ClientRunningData.class); activeData = JsonUtils.unmarshalFromByte(bytes, ClientRunningData.class);
// 如果发现已经存在,判断一下是否自己,避免活锁
if (activeData.getAddress().contains(":") && isMine(activeData.getAddress())) {
mutex.set(true);
}
} }
} catch (ZkNoNodeException e) { } catch (ZkNoNodeException e) {
zkClient.createPersistent(ZookeeperPathUtils.getClientIdNodePath(this.destination, clientData.getClientId()), zkClient.createPersistent(ZookeeperPathUtils.getClientIdNodePath(this.destination, clientData.getClientId()),
true); // 尝试创建父节点 true); // 尝试创建父节点
initRunning(); initRunning();
} catch (Throwable t) {
logger.error(MessageFormat.format("There is an error when execute initRunning method, with destination [{0}].",
destination),
t);
// 出现任何异常尝试release
releaseRunning();
throw new CanalClientException("something goes wrong in initRunning method. ", t);
} }
} }

View File

@@ -8,7 +8,7 @@
</encoder> </encoder>
</appender> </appender>
<root level="WARN"> <root level="INFO">
<appender-ref ref="STDOUT"/> <appender-ref ref="STDOUT"/>
</root> </root>
</configuration> </configuration>

View File

@@ -1,9 +1,10 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.20</version> <version>1.0.25-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<artifactId>canal.common</artifactId> <artifactId>canal.common</artifactId>
@@ -11,13 +12,18 @@
<name>canal common module for otter ${project.version}</name> <name>canal common module for otter ${project.version}</name>
<url>http://b2b-doc.alibaba-inc.com/display/opentech/Otter</url> <url>http://b2b-doc.alibaba-inc.com/display/opentech/Otter</url>
<dependencies> <dependencies>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<!-- zk --> <!-- zk -->
<dependency> <dependency>
<groupId>org.apache.zookeeper</groupId> <groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId> <artifactId>zookeeper</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.sgroschupf</groupId> <groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId> <artifactId>zkclient</artifactId>
</dependency> </dependency>
<!-- external --> <!-- external -->
@@ -28,11 +34,10 @@
<dependency> <dependency>
<groupId>commons-lang</groupId> <groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId> <artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>commons-codec</groupId>
<artifactId>spring</artifactId> <artifactId>commons-codec</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
@@ -59,10 +64,37 @@
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring_version}</version>
<scope>test</scope>
</dependency>
<!-- junit --> <!-- junit -->
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -1,12 +1,19 @@
package com.alibaba.otter.canal.common.utils; package com.alibaba.otter.canal.common.utils;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference; import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.serializer.JSONSerializer; import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.ObjectSerializer;
import com.alibaba.fastjson.serializer.PropertyFilter; import com.alibaba.fastjson.serializer.PropertyFilter;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializeWriter; import com.alibaba.fastjson.serializer.SerializeWriter;
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.serializer.SerializerFeature;
@@ -17,6 +24,12 @@ import com.alibaba.fastjson.serializer.SerializerFeature;
*/ */
public class JsonUtils { public class JsonUtils {
static {
SerializeConfig.getGlobalInstance().put(InetAddress.class, InetAddressSerializer.instance);
SerializeConfig.getGlobalInstance().put(Inet4Address.class, InetAddressSerializer.instance);
SerializeConfig.getGlobalInstance().put(Inet6Address.class, InetAddressSerializer.instance);
}
public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) { public static <T> T unmarshalFromByte(byte[] bytes, Class<T> targetClass) {
return (T) JSON.parseObject(bytes, targetClass);// 默认为UTF-8 return (T) JSON.parseObject(bytes, targetClass);// 默认为UTF-8
} }
@@ -70,4 +83,22 @@ public class JsonUtils {
out.close(); out.close();
} }
} }
public static class InetAddressSerializer implements ObjectSerializer {
public static InetAddressSerializer instance = new InetAddressSerializer();
@Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features)
throws IOException {
if (object == null) {
serializer.writeNull();
return;
}
InetAddress address = (InetAddress) object;
// 优先使用name
serializer.write(address.getHostName());
}
}
} }

View File

@@ -8,20 +8,15 @@ import java.util.List;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import org.I0Itec.zkclient.IZkConnection; import org.I0Itec.zkclient.ZkConnection;
import org.I0Itec.zkclient.exception.ZkException; import org.I0Itec.zkclient.exception.ZkException;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.zookeeper.ClientCnxn; import org.apache.zookeeper.ClientCnxn;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooKeeper.States;
import org.apache.zookeeper.client.ConnectStringParser; import org.apache.zookeeper.client.ConnectStringParser;
import org.apache.zookeeper.client.HostProvider; import org.apache.zookeeper.client.HostProvider;
import org.apache.zookeeper.client.StaticHostProvider; import org.apache.zookeeper.client.StaticHostProvider;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@@ -32,7 +27,7 @@ import org.springframework.util.ReflectionUtils;
* @author jianghang 2012-7-10 下午02:31:42 * @author jianghang 2012-7-10 下午02:31:42
* @version 1.0.0 * @version 1.0.0
*/ */
public class ZooKeeperx implements IZkConnection { public class ZooKeeperx extends ZkConnection {
private static final String SERVER_COMMA = ";"; private static final String SERVER_COMMA = ";";
private static final Logger logger = LoggerFactory.getLogger(ZooKeeperx.class); private static final Logger logger = LoggerFactory.getLogger(ZooKeeperx.class);
@@ -40,12 +35,12 @@ public class ZooKeeperx implements IZkConnection {
private static final Field hostProviderField = ReflectionUtils.findField(ClientCnxn.class, "hostProvider"); private static final Field hostProviderField = ReflectionUtils.findField(ClientCnxn.class, "hostProvider");
private static final Field serverAddressesField = ReflectionUtils.findField(StaticHostProvider.class, private static final Field serverAddressesField = ReflectionUtils.findField(StaticHostProvider.class,
"serverAddresses"); "serverAddresses");
private static final Field zookeeperLockField = ReflectionUtils.findField(ZkConnection.class,
"_zookeeperLock");
private static final Field zookeeperFiled = ReflectionUtils.findField(ZkConnection.class, "_zk");
private static final int DEFAULT_SESSION_TIMEOUT = 90000; private static final int DEFAULT_SESSION_TIMEOUT = 90000;
private ZooKeeper _zk = null; private final List<String> _serversList;
private Lock _zookeeperLock = new ReentrantLock();
private final List<String> _servers;
private final int _sessionTimeOut; private final int _sessionTimeOut;
public ZooKeeperx(String zkServers){ public ZooKeeperx(String zkServers){
@@ -53,109 +48,55 @@ public class ZooKeeperx implements IZkConnection {
} }
public ZooKeeperx(String zkServers, int sessionTimeOut){ public ZooKeeperx(String zkServers, int sessionTimeOut){
_servers = Arrays.asList(StringUtils.split(zkServers, SERVER_COMMA)); super(zkServers, sessionTimeOut);
_serversList = Arrays.asList(StringUtils.split(this.getServers(), SERVER_COMMA));
_sessionTimeOut = sessionTimeOut; _sessionTimeOut = sessionTimeOut;
} }
@Override @Override
public void connect(Watcher watcher) { public void connect(Watcher watcher) {
ReflectionUtils.makeAccessible(zookeeperLockField);
ReflectionUtils.makeAccessible(zookeeperFiled);
Lock _zookeeperLock = (ReentrantLock) ReflectionUtils.getField(zookeeperLockField, this);
ZooKeeper _zk = (ZooKeeper) ReflectionUtils.getField(zookeeperFiled, this);
_zookeeperLock.lock(); _zookeeperLock.lock();
try { try {
if (_zk != null) { if (_zk != null) {
throw new IllegalStateException("zk client has already been started"); throw new IllegalStateException("zk client has already been started");
} }
String zkServers = _serversList.get(0);
try { try {
logger.debug("Creating new ZookKeeper instance to connect to " + _servers + "."); logger.debug("Creating new ZookKeeper instance to connect to " + zkServers + ".");
_zk = new ZooKeeper(_servers.get(0), _sessionTimeOut, watcher); _zk = new ZooKeeper(zkServers, _sessionTimeOut, watcher);
configMutliCluster(_zk); configMutliCluster(_zk);
ReflectionUtils.setField(zookeeperFiled, this, _zk);
} catch (IOException e) { } catch (IOException e) {
throw new ZkException("Unable to connect to " + _servers, e); throw new ZkException("Unable to connect to " + zkServers, e);
} }
} finally { } finally {
_zookeeperLock.unlock(); _zookeeperLock.unlock();
} }
} }
public void close() throws InterruptedException {
_zookeeperLock.lock();
try {
if (_zk != null) {
logger.debug("Closing ZooKeeper connected to " + _servers);
_zk.close();
_zk = null;
}
} finally {
_zookeeperLock.unlock();
}
}
public String create(String path, byte[] data, CreateMode mode) throws KeeperException, InterruptedException {
return _zk.create(path, data, Ids.OPEN_ACL_UNSAFE, mode);
}
public void delete(String path) throws InterruptedException, KeeperException {
_zk.delete(path, -1);
}
public boolean exists(String path, boolean watch) throws KeeperException, InterruptedException {
return _zk.exists(path, watch) != null;
}
public List<String> getChildren(final String path, final boolean watch) throws KeeperException,
InterruptedException {
return _zk.getChildren(path, watch);
}
public byte[] readData(String path, Stat stat, boolean watch) throws KeeperException, InterruptedException {
return _zk.getData(path, watch, stat);
}
public void writeData(String path, byte[] data) throws KeeperException, InterruptedException {
writeData(path, data, -1);
}
public void writeData(String path, byte[] data, int version) throws KeeperException, InterruptedException {
_zk.setData(path, data, version);
}
public States getZookeeperState() {
return _zk != null ? _zk.getState() : null;
}
public ZooKeeper getZookeeper() {
return _zk;
}
public long getCreateTime(String path) throws KeeperException, InterruptedException {
Stat stat = _zk.exists(path, false);
if (stat != null) {
return stat.getCtime();
}
return -1;
}
public String getServers() {
return StringUtils.join(_servers, SERVER_COMMA);
}
// =============================== // ===============================
public void configMutliCluster(ZooKeeper zk) { public void configMutliCluster(ZooKeeper zk) {
if (_servers.size() == 1) { if (_serversList.size() == 1) {
return; return;
} }
String cluster1 = _servers.get(0); String cluster1 = _serversList.get(0);
try { try {
if (_servers.size() > 1) { if (_serversList.size() > 1) {
// 强制的声明accessible // 强制的声明accessible
ReflectionUtils.makeAccessible(clientCnxnField); ReflectionUtils.makeAccessible(clientCnxnField);
ReflectionUtils.makeAccessible(hostProviderField); ReflectionUtils.makeAccessible(hostProviderField);
ReflectionUtils.makeAccessible(serverAddressesField); ReflectionUtils.makeAccessible(serverAddressesField);
// 添加第二组集群列表 // 添加第二组集群列表
for (int i = 1; i < _servers.size(); i++) { for (int i = 1; i < _serversList.size(); i++) {
String cluster = _servers.get(i); String cluster = _serversList.get(i);
// 强制获取zk中的地址信息 // 强制获取zk中的地址信息
ClientCnxn cnxn = (ClientCnxn) ReflectionUtils.getField(clientCnxnField, zk); ClientCnxn cnxn = (ClientCnxn) ReflectionUtils.getField(clientCnxnField, zk);
HostProvider hostProvider = (HostProvider) ReflectionUtils.getField(hostProviderField, cnxn); HostProvider hostProvider = (HostProvider) ReflectionUtils.getField(hostProviderField, cnxn);

View File

@@ -87,6 +87,10 @@ public class ServerRunningMonitor extends AbstractCanalLifeCycle {
} }
public void init() {
processStart();
}
public void start() { public void start() {
super.start(); super.start();
processStart(); processStart();

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.20</version> <version>1.0.25-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -0,0 +1,418 @@
package com.taobao.tddl.dbsync.binlog;
/**
* 处理下MySQL json二进制转化为可读的字符串
*
* @author agapple 2016年6月30日 上午11:26:17
* @since 1.0.22
*/
public class JsonConversion {
// JSON TYPE
public static final int JSONB_TYPE_SMALL_OBJECT = 0x0;
public static final int JSONB_TYPE_LARGE_OBJECT = 0x1;
public static final int JSONB_TYPE_SMALL_ARRAY = 0x2;
public static final int JSONB_TYPE_LARGE_ARRAY = 0x3;
public static final int JSONB_TYPE_LITERAL = 0x4;
public static final int JSONB_TYPE_INT16 = 0x5;
public static final int JSONB_TYPE_UINT16 = 0x6;
public static final int JSONB_TYPE_INT32 = 0x7;
public static final int JSONB_TYPE_UINT32 = 0x8;
public static final int JSONB_TYPE_INT64 = 0x9;
public static final int JSONB_TYPE_UINT64 = 0xA;
public static final int JSONB_TYPE_DOUBLE = 0xB;
public static final int JSONB_TYPE_STRING = 0xC;
public static final int JSONB_TYPE_OPAQUE = 0xF;
public static final char JSONB_NULL_LITERAL = '\0';
public static final char JSONB_TRUE_LITERAL = '\1';
public static final char JSONB_FALSE_LITERAL = '\2';
/*
* The size of offset or size fields in the small and the large storage
* format for JSON objects and JSON arrays.
*/
public static final int SMALL_OFFSET_SIZE = 2;
public static final int LARGE_OFFSET_SIZE = 4;
/*
* The size of key entries for objects when using the small storage format
* or the large storage format. In the small format it is 4 bytes (2 bytes
* for key length and 2 bytes for key offset). In the large format it is 6
* (2 bytes for length, 4 bytes for offset).
*/
public static final int KEY_ENTRY_SIZE_SMALL = (2 + SMALL_OFFSET_SIZE);
public static final int KEY_ENTRY_SIZE_LARGE = (2 + LARGE_OFFSET_SIZE);
/*
* The size of value entries for objects or arrays. When using the small
* storage format, the entry size is 3 (1 byte for type, 2 bytes for
* offset). When using the large storage format, it is 5 (1 byte for type, 4
* bytes for offset).
*/
public static final int VALUE_ENTRY_SIZE_SMALL = (1 + SMALL_OFFSET_SIZE);
public static final int VALUE_ENTRY_SIZE_LARGE = (1 + LARGE_OFFSET_SIZE);
public static Json_Value parse_value(int type, LogBuffer buffer, long len, String charsetName) {
buffer = buffer.duplicate(buffer.position(), (int) len);
switch (type) {
case JSONB_TYPE_SMALL_OBJECT:
return parse_array_or_object(Json_enum_type.OBJECT, buffer, len, false, charsetName);
case JSONB_TYPE_LARGE_OBJECT:
return parse_array_or_object(Json_enum_type.OBJECT, buffer, len, true, charsetName);
case JSONB_TYPE_SMALL_ARRAY:
return parse_array_or_object(Json_enum_type.ARRAY, buffer, len, false, charsetName);
case JSONB_TYPE_LARGE_ARRAY:
return parse_array_or_object(Json_enum_type.ARRAY, buffer, len, true, charsetName);
default:
return parse_scalar(type, buffer, len, charsetName);
}
}
private static Json_Value parse_array_or_object(Json_enum_type type, LogBuffer buffer, long len, boolean large,
String charsetName) {
long offset_size = large ? LARGE_OFFSET_SIZE : SMALL_OFFSET_SIZE;
if (len < 2 * offset_size) {
throw new IllegalArgumentException("illegal json data");
}
long element_count = read_offset_or_size(buffer, large);
long bytes = read_offset_or_size(buffer, large);
if (bytes > len) {
throw new IllegalArgumentException("illegal json data");
}
long header_size = 2 * offset_size;
if (type == Json_enum_type.OBJECT) {
header_size += element_count * (large ? KEY_ENTRY_SIZE_LARGE : KEY_ENTRY_SIZE_SMALL);
}
header_size += element_count * (large ? VALUE_ENTRY_SIZE_LARGE : VALUE_ENTRY_SIZE_SMALL);
if (header_size > bytes) {
throw new IllegalArgumentException("illegal json data");
}
return new Json_Value(type, buffer.rewind(), element_count, bytes, large);
}
private static long read_offset_or_size(LogBuffer buffer, boolean large) {
return large ? buffer.getUint32() : buffer.getUint16();
}
private static Json_Value parse_scalar(int type, LogBuffer buffer, long len, String charsetName) {
switch (type) {
case JSONB_TYPE_LITERAL:
/* purecov: inspected */
int data = buffer.getUint8();
switch (data) {
case JSONB_NULL_LITERAL:
return new Json_Value(Json_enum_type.LITERAL_NULL);
case JSONB_TRUE_LITERAL:
return new Json_Value(Json_enum_type.LITERAL_TRUE);
case JSONB_FALSE_LITERAL:
return new Json_Value(Json_enum_type.LITERAL_FALSE);
default:
throw new IllegalArgumentException("illegal json data");
}
case JSONB_TYPE_INT16:
return new Json_Value(Json_enum_type.INT, buffer.getInt16());
case JSONB_TYPE_INT32:
return new Json_Value(Json_enum_type.INT, buffer.getInt32());
case JSONB_TYPE_INT64:
return new Json_Value(Json_enum_type.INT, buffer.getLong64());
case JSONB_TYPE_UINT16:
return new Json_Value(Json_enum_type.UINT, buffer.getUint16());
case JSONB_TYPE_UINT32:
return new Json_Value(Json_enum_type.UINT, buffer.getUint32());
case JSONB_TYPE_UINT64:
return new Json_Value(Json_enum_type.UINT, buffer.getUlong64());
case JSONB_TYPE_DOUBLE:
return new Json_Value(Json_enum_type.DOUBLE, Double.valueOf(buffer.getDouble64()));
case JSONB_TYPE_STRING:
int max_bytes = (int) Math.min(len, 5);
long tlen = 0;
long str_len = 0;
long n = 0;
byte[] datas = buffer.getData(max_bytes);
for (int i = 0; i < max_bytes; i++) {
// Get the next 7 bits of the length.
tlen |= (datas[i] & 0x7f) << (7 * i);
if ((datas[i] & 0x80) == 0) {
// The length shouldn't exceed 32 bits.
if (tlen > 4294967296L) {
throw new IllegalArgumentException("illegal json data");
}
// This was the last byte. Return successfully.
n = i + 1;
str_len = tlen;
break;
}
}
if (len < n + str_len) {
throw new IllegalArgumentException("illegal json data");
}
return new Json_Value(Json_enum_type.STRING, buffer.rewind()
.forward((int) n)
.getFixString((int) str_len, charsetName));
case JSONB_TYPE_OPAQUE:
/*
* There should always be at least one byte, which tells the
* field type of the opaque value.
*/
// The type is encoded as a uint8 that maps to an
// enum_field_types.
int type_byte = buffer.getUint8();
int position = buffer.position();
// Then there's the length of the value.
int q_max_bytes = (int) Math.min(len, 5);
long q_tlen = 0;
long q_str_len = 0;
long q_n = 0;
byte[] q_datas = buffer.getData(q_max_bytes);
for (int i = 0; i < q_max_bytes; i++) {
// Get the next 7 bits of the length.
q_tlen |= (q_datas[i] & 0x7f) << (7 * i);
if ((q_datas[i] & 0x80) == 0) {
// The length shouldn't exceed 32 bits.
if (q_tlen > 4294967296L) {
throw new IllegalArgumentException("illegal json data");
}
// This was the last byte. Return successfully.
q_n = i + 1;
q_str_len = q_tlen;
break;
}
}
if (q_str_len == 0 || len < q_n + q_str_len) {
throw new IllegalArgumentException("illegal json data");
}
return new Json_Value(type_byte, buffer.position(position).forward((int) q_n), q_str_len);
default:
throw new IllegalArgumentException("illegal json data");
}
}
public static class Json_Value {
Json_enum_type m_type;
int m_field_type;
LogBuffer m_data;
long m_element_count;
long m_length;
String m_string_value;
Number m_int_value;
double m_double_value;
boolean m_large;
public Json_Value(Json_enum_type t){
this.m_type = t;
}
public Json_Value(Json_enum_type t, Number val){
this.m_type = t;
if (t == Json_enum_type.DOUBLE) {
this.m_double_value = val.doubleValue();
} else {
this.m_int_value = val;
}
}
public Json_Value(Json_enum_type t, String value){
this.m_type = t;
this.m_string_value = value;
}
public Json_Value(int field_type, LogBuffer data, long bytes){
this.m_type = Json_enum_type.OPAQUE; // 不确定类型
this.m_field_type = field_type;
this.m_data = data;
this.m_length = bytes;
}
public Json_Value(Json_enum_type t, LogBuffer data, long element_count, long bytes, boolean large){
this.m_type = t;
this.m_data = data;
this.m_element_count = element_count;
this.m_length = bytes;
this.m_large = large;
}
public String key(int i, String charsetName) {
m_data.rewind();
int offset_size = m_large ? LARGE_OFFSET_SIZE : SMALL_OFFSET_SIZE;
int key_entry_size = m_large ? KEY_ENTRY_SIZE_LARGE : KEY_ENTRY_SIZE_SMALL;
int entry_offset = 2 * offset_size + key_entry_size * i;
// The offset of the key is the first part of the key
// entry.
m_data.forward(entry_offset);
long key_offset = read_offset_or_size(m_data, m_large);
// The length of the key is the second part of the
// entry, always two
// bytes.
long key_length = m_data.getUint16();
return m_data.rewind().forward((int) key_offset).getFixString((int) key_length, charsetName);
}
public Json_Value element(int i, String charsetName) {
m_data.rewind();
int offset_size = m_large ? LARGE_OFFSET_SIZE : SMALL_OFFSET_SIZE;
int key_entry_size = m_large ? KEY_ENTRY_SIZE_LARGE : KEY_ENTRY_SIZE_SMALL;
int value_entry_size = m_large ? VALUE_ENTRY_SIZE_LARGE : VALUE_ENTRY_SIZE_SMALL;
int first_entry_offset = 2 * offset_size;
if (m_type == Json_enum_type.OBJECT) {
first_entry_offset += m_element_count * key_entry_size;
}
int entry_offset = first_entry_offset + value_entry_size * i;
int type = m_data.forward(entry_offset).getUint8();
if (type == JSONB_TYPE_INT16 || type == JSONB_TYPE_UINT16 || type == JSONB_TYPE_LITERAL
|| (m_large && (type == JSONB_TYPE_INT32 || type == JSONB_TYPE_UINT32))) {
return parse_scalar(type, m_data, value_entry_size - 1, charsetName);
}
int value_offset = (int) read_offset_or_size(m_data, m_large);
return parse_value(type, m_data.rewind().forward(value_offset), (int) m_length - value_offset, charsetName);
}
public StringBuilder toJsonString(StringBuilder buf, String charsetName) {
switch (m_type) {
case OBJECT:
buf.append("{");
for (int i = 0; i < m_element_count; ++i) {
if (i > 0) {
buf.append(", ");
}
buf.append('"').append(key(i, charsetName)).append('"');
buf.append(": ");
element(i, charsetName).toJsonString(buf, charsetName);
}
buf.append("}");
break;
case ARRAY:
buf.append("[");
for (int i = 0; i < m_element_count; ++i) {
if (i > 0) {
buf.append(", ");
}
element(i, charsetName).toJsonString(buf, charsetName);
}
buf.append("]");
break;
case DOUBLE:
buf.append(Double.valueOf(m_double_value).toString());
break;
case INT:
buf.append(m_int_value.toString());
break;
case UINT:
buf.append(m_int_value.toString());
break;
case LITERAL_FALSE:
buf.append("false");
break;
case LITERAL_TRUE:
buf.append("true");
break;
case LITERAL_NULL:
buf.append("NULL");
break;
case OPAQUE:
String text = null;
if (m_field_type == LogEvent.MYSQL_TYPE_NEWDECIMAL) {
int precision = m_data.getInt8();
int scale = m_data.getInt8();
text = m_data.getDecimal(precision, scale).toPlainString();
buf.append(text);
} else if (m_field_type == LogEvent.MYSQL_TYPE_TIME) {
long packed_value = m_data.getLong64();
if (packed_value == 0) {
text = "00:00:00";
} else {
long ultime = Math.abs(packed_value);
long intpart = ultime >> 24;
int frac = (int) (ultime % (1L << 24));
text = String.format("%s%02d:%02d:%02d",
packed_value >= 0 ? "" : "-",
(int) ((intpart >> 12) % (1 << 10)),
(int) ((intpart >> 6) % (1 << 6)),
(int) (intpart % (1 << 6)));
text = text + "." + usecondsToStr(frac, 6);
}
buf.append('"').append(text).append('"');
} else if (m_field_type == LogEvent.MYSQL_TYPE_DATE || m_field_type == LogEvent.MYSQL_TYPE_DATETIME
|| m_field_type == LogEvent.MYSQL_TYPE_TIMESTAMP) {
long packed_value = m_data.getLong64();
if (packed_value == 0) {
text = "0000-00-00 00:00:00";
} else {
// 构造TimeStamp只处理到秒
long ultime = Math.abs(packed_value);
long intpart = ultime >> 24;
int frac = (int) (ultime % (1L << 24));
long ymd = intpart >> 17;
long ym = ymd >> 5;
long hms = intpart % (1 << 17);
text = String.format("%04d-%02d-%02d %02d:%02d:%02d",
(int) (ym / 13),
(int) (ym % 13),
(int) (ymd % (1 << 5)),
(int) (hms >> 12),
(int) ((hms >> 6) % (1 << 6)),
(int) (hms % (1 << 6)));
text = text + "." + usecondsToStr(frac, 6);
}
buf.append('"').append(text).append('"');
} else {
text = m_data.getFixString((int) m_length, charsetName);
buf.append('"').append(escapse(text)).append('"');
}
break;
case STRING:
buf.append('"').append(escapse(m_string_value)).append('"');
break;
case ERROR:
throw new IllegalArgumentException("illegal json data");
}
return buf;
}
}
private static StringBuilder escapse(String data) {
StringBuilder sb = new StringBuilder(data.length());
int endIndex = data.length();
for (int i = 0; i < endIndex; ++i) {
char c = data.charAt(i);
if (c == '"') {
sb.append('\\');
} else if (c == '\\') {
sb.append("\\");
}
sb.append(c);
}
return sb;
}
public static enum Json_enum_type {
OBJECT, ARRAY, STRING, INT, UINT, DOUBLE, LITERAL_NULL, LITERAL_TRUE, LITERAL_FALSE, OPAQUE, ERROR
}
private static String usecondsToStr(int frac, int meta) {
String sec = String.valueOf(frac);
if (meta > 6) {
throw new IllegalArgumentException("unknow useconds meta : " + meta);
}
if (sec.length() < 6) {
StringBuilder result = new StringBuilder(6);
int len = 6 - sec.length();
for (; len > 0; len--) {
result.append('0');
}
result.append(sec);
sec = result.toString();
}
return sec.substring(0, meta);
}
}

View File

@@ -168,8 +168,8 @@ public class LogBuffer {
* @param newLimit The new limit value; must be non-negative and no larger * @param newLimit The new limit value; must be non-negative and no larger
* than this buffer's capacity * than this buffer's capacity
* @return This buffer * @return This buffer
* @throws IllegalArgumentException If the preconditions on <tt>newLimit</tt> * @throws IllegalArgumentException If the preconditions on
* do not hold * <tt>newLimit</tt> do not hold
*/ */
public final LogBuffer limit(int newLimit) { public final LogBuffer limit(int newLimit) {
if (origin + newLimit > buffer.length || newLimit < 0) throw new IllegalArgumentException("capacity excceed: " if (origin + newLimit > buffer.length || newLimit < 0) throw new IllegalArgumentException("capacity excceed: "

View File

@@ -267,6 +267,7 @@ public abstract class LogEvent {
public static final int MYSQL_TYPE_TIMESTAMP2 = 17; public static final int MYSQL_TYPE_TIMESTAMP2 = 17;
public static final int MYSQL_TYPE_DATETIME2 = 18; public static final int MYSQL_TYPE_DATETIME2 = 18;
public static final int MYSQL_TYPE_TIME2 = 19; public static final int MYSQL_TYPE_TIME2 = 19;
public static final int MYSQL_TYPE_JSON = 245;
public static final int MYSQL_TYPE_NEWDECIMAL = 246; public static final int MYSQL_TYPE_NEWDECIMAL = 246;
public static final int MYSQL_TYPE_ENUM = 247; public static final int MYSQL_TYPE_ENUM = 247;
public static final int MYSQL_TYPE_SET = 248; public static final int MYSQL_TYPE_SET = 248;

View File

@@ -141,6 +141,11 @@ public final class FormatDescriptionLogEvent extends StartLogEventV3 {
} }
} }
public FormatDescriptionLogEvent(final int binlogVersion, int binlogChecksum){
this(binlogVersion);
this.header.checksumAlg = binlogChecksum;
}
public FormatDescriptionLogEvent(final int binlogVersion){ public FormatDescriptionLogEvent(final int binlogVersion){
this.binlogVersion = binlogVersion; this.binlogVersion = binlogVersion;

View File

@@ -330,15 +330,12 @@ public class QueryLogEvent extends LogEvent {
* MariaDb type, * MariaDb type,
* sec_part of NOW() * sec_part of NOW()
*/ */
+ 1 + (MAX_DBS_IN_EVENT_MTS * (1 + NAME_LEN)) + 3 + 1 + 16 + 1 + 60/* + 1 + (MAX_DBS_IN_EVENT_MTS * (1 + NAME_LEN)) + 3 + 1 + 32 * 3
* type + 1 + 60/*
* , * type ,
* user_len * user_len ,
* , * user ,
* user * host_len ,
* ,
* host_len
* ,
* host * host
*/); */);
/** /**
@@ -548,6 +545,22 @@ public class QueryLogEvent extends LogEvent {
public static final int Q_UPDATED_DB_NAMES = 12; public static final int Q_UPDATED_DB_NAMES = 12;
public static final int Q_MICROSECONDS = 13; public static final int Q_MICROSECONDS = 13;
/**
* A old (unused now) code for Query_log_event status similar to
* G_COMMIT_TS.
*/
public static final int Q_COMMIT_TS = 14;
/**
* A code for Query_log_event status, similar to G_COMMIT_TS2.
*/
public static final int Q_COMMIT_TS2 = 15;
/**
* The master connection @@session.explicit_defaults_for_timestamp which is
* recorded for queries, CREATE and ALTER table that is defined with a
* TIMESTAMP column, that are dependent on that feature. For pre-WL6292
* master's the associated with this code value is zero.
*/
public static final int Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP = 16;
/** /**
* FROM MariaDB 5.5.34 * FROM MariaDB 5.5.34
@@ -632,6 +645,9 @@ public class QueryLogEvent extends LogEvent {
mtsAccessedDbNames[i] = buffer.getFixString(length < NAME_LEN ? length : NAME_LEN); mtsAccessedDbNames[i] = buffer.getFixString(length < NAME_LEN ? length : NAME_LEN);
} }
break; break;
case Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP:
buffer.forward(1);
break;
case Q_HRNOW: case Q_HRNOW:
// int when_sec_part = buffer.getUint24(); // int when_sec_part = buffer.getUint24();
buffer.forward(3); buffer.forward(3);

View File

@@ -90,6 +90,7 @@ public final class RotateLogEvent extends LogEvent {
int filenameLen = buffer.limit() - filenameOffset; int filenameLen = buffer.limit() - filenameOffset;
if (filenameLen > FN_REFLEN - 1) filenameLen = FN_REFLEN - 1; if (filenameLen > FN_REFLEN - 1) filenameLen = FN_REFLEN - 1;
buffer.position(filenameOffset); buffer.position(filenameOffset);
filename = buffer.getFixString(filenameLen); filename = buffer.getFixString(filenameLen);
} }

View File

@@ -8,6 +8,8 @@ import java.util.BitSet;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import com.taobao.tddl.dbsync.binlog.JsonConversion;
import com.taobao.tddl.dbsync.binlog.JsonConversion.Json_Value;
import com.taobao.tddl.dbsync.binlog.LogBuffer; import com.taobao.tddl.dbsync.binlog.LogBuffer;
import com.taobao.tddl.dbsync.binlog.LogEvent; import com.taobao.tddl.dbsync.binlog.LogEvent;
@@ -25,6 +27,7 @@ public final class RowsLogBuffer {
public static final long DATETIMEF_INT_OFS = 0x8000000000L; public static final long DATETIMEF_INT_OFS = 0x8000000000L;
public static final long TIMEF_INT_OFS = 0x800000L; public static final long TIMEF_INT_OFS = 0x800000L;
public static final long TIMEF_OFS = 0x800000000000L; public static final long TIMEF_OFS = 0x800000000000L;
private final LogBuffer buffer; private final LogBuffer buffer;
private final int columnLen; private final int columnLen;
private final String charsetName; private final String charsetName;
@@ -163,10 +166,13 @@ public final class RowsLogBuffer {
case LogEvent.MYSQL_TYPE_TIMESTAMP: case LogEvent.MYSQL_TYPE_TIMESTAMP:
case LogEvent.MYSQL_TYPE_DATETIME: case LogEvent.MYSQL_TYPE_DATETIME:
case LogEvent.MYSQL_TYPE_TIMESTAMP2:
case LogEvent.MYSQL_TYPE_DATETIME2:
javaType = Types.TIMESTAMP; javaType = Types.TIMESTAMP;
break; break;
case LogEvent.MYSQL_TYPE_TIME: case LogEvent.MYSQL_TYPE_TIME:
case LogEvent.MYSQL_TYPE_TIME2:
javaType = Types.TIME; javaType = Types.TIME;
break; break;
@@ -942,6 +948,42 @@ public final class RowsLogBuffer {
length = len; length = len;
break; break;
} }
case LogEvent.MYSQL_TYPE_JSON: {
switch (meta) {
case 1: {
len = buffer.getUint8();
break;
}
case 2: {
len = buffer.getUint16();
break;
}
case 3: {
len = buffer.getUint24();
break;
}
case 4: {
len = (int) buffer.getUint32();
break;
}
default:
throw new IllegalArgumentException("!! Unknown JSON packlen = " + meta);
}
// len = buffer.getUint16();
// buffer.forward(meta - 4);
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);
// byte[] binary = new byte[len];
// buffer.fillBytes(binary, 0, len);
// value = binary;
javaType = Types.VARCHAR;
length = len;
break;
}
case LogEvent.MYSQL_TYPE_GEOMETRY: { case LogEvent.MYSQL_TYPE_GEOMETRY: {
/* /*
* MYSQL_TYPE_GEOMETRY: copy from BLOB or TEXT * MYSQL_TYPE_GEOMETRY: copy from BLOB or TEXT
@@ -1023,4 +1065,5 @@ public final class RowsLogBuffer {
return sec.substring(0, meta); return sec.substring(0, meta);
} }
} }

View File

@@ -402,6 +402,7 @@ public final class TableMapLogEvent extends LogEvent {
case MYSQL_TYPE_DOUBLE: case MYSQL_TYPE_DOUBLE:
case MYSQL_TYPE_FLOAT: case MYSQL_TYPE_FLOAT:
case MYSQL_TYPE_GEOMETRY: case MYSQL_TYPE_GEOMETRY:
case MYSQL_TYPE_JSON:
/* /*
* These types store a single byte. * These types store a single byte.
*/ */

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.20</version> <version>1.0.25-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -5,6 +5,7 @@ import java.util.Properties;
import org.I0Itec.zkclient.IZkStateListener; import org.I0Itec.zkclient.IZkStateListener;
import org.I0Itec.zkclient.exception.ZkNoNodeException; import org.I0Itec.zkclient.exception.ZkNoNodeException;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.Watcher.Event.KeeperState;
@@ -88,9 +89,9 @@ public class CanalController {
cid = Long.valueOf(getProperty(properties, CanalConstants.CANAL_ID)); cid = Long.valueOf(getProperty(properties, CanalConstants.CANAL_ID));
ip = getProperty(properties, CanalConstants.CANAL_IP); ip = getProperty(properties, CanalConstants.CANAL_IP);
port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT)); port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT));
embededCanalServer = new CanalServerWithEmbedded(); embededCanalServer = CanalServerWithEmbedded.instance();
embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator
canalServer = new CanalServerWithNetty(embededCanalServer); canalServer = CanalServerWithNetty.instance();
canalServer.setIp(ip); canalServer.setIp(ip);
canalServer.setPort(port); canalServer.setPort(port);
@@ -148,6 +149,11 @@ public class CanalController {
public void handleNewSession() throws Exception { public void handleNewSession() throws Exception {
initCid(path); initCid(path);
} }
@Override
public void handleSessionEstablishmentError(Throwable error) throws Exception {
logger.error("failed to connect to zookeeper", error);
}
}); });
} }
} finally { } finally {
@@ -172,6 +178,8 @@ public class CanalController {
if (zkclientx != null) { if (zkclientx != null) {
runningMonitor.setZkClient(zkclientx); runningMonitor.setZkClient(zkclientx);
} }
// 触发创建一下cid节点
runningMonitor.init();
return runningMonitor; return runningMonitor;
} }
})); }));
@@ -189,10 +197,10 @@ public class CanalController {
instanceConfigs.put(destination, config); instanceConfigs.put(destination, config);
} }
if (!config.getLazy() && !embededCanalServer.isStart(destination)) { if (!embededCanalServer.isStart(destination)) {
// HA机制启动 // HA机制启动
ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination); ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination);
if (!runningMonitor.isStart()) { if (!config.getLazy() && !runningMonitor.isStart()) {
runningMonitor.start(); runningMonitor.start();
} }
} }
@@ -231,7 +239,13 @@ public class CanalController {
if (StringUtils.isEmpty(rootDir)) { if (StringUtils.isEmpty(rootDir)) {
rootDir = "../conf"; rootDir = "../conf";
} }
if (StringUtils.equals("otter-canal", System.getProperty("appName"))) {
monitor.setRootConf(rootDir); monitor.setRootConf(rootDir);
} else {
// eclipse debug模式
monitor.setRootConf("src/main/resources/");
}
return monitor; return monitor;
} else if (mode.isManager()) { } else if (mode.isManager()) {
return new ManagerInstanceConfigMonitor(); return new ManagerInstanceConfigMonitor();
@@ -374,6 +388,11 @@ public class CanalController {
public void handleNewSession() throws Exception { public void handleNewSession() throws Exception {
initCid(path); initCid(path);
} }
@Override
public void handleSessionEstablishmentError(Throwable error) throws Exception {
logger.error("failed to connect to zookeeper", error);
}
}); });
} }
// 优先启动embeded服务 // 优先启动embeded服务
@@ -383,10 +402,10 @@ public class CanalController {
final String destination = entry.getKey(); final String destination = entry.getKey();
InstanceConfig config = entry.getValue(); InstanceConfig config = entry.getValue();
// 创建destination的工作节点 // 创建destination的工作节点
if (!config.getLazy() && !embededCanalServer.isStart(destination)) { if (!embededCanalServer.isStart(destination)) {
// HA机制启动 // HA机制启动
ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination); ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination);
if (!runningMonitor.isStart()) { if (!config.getLazy() && !runningMonitor.isStart()) {
runningMonitor.start(); runningMonitor.start();
} }
} }
@@ -442,6 +461,9 @@ public class CanalController {
String parentDir = path.substring(0, path.lastIndexOf('/')); String parentDir = path.substring(0, path.lastIndexOf('/'));
zkclientx.createPersistent(parentDir, true); zkclientx.createPersistent(parentDir, true);
zkclientx.createEphemeral(path); zkclientx.createEphemeral(path);
} catch (ZkNodeExistsException e) {
// ignore
// 因为第一次启动时创建了cid,但在stop/start的时可能会关闭和新建,允许出现NodeExists问题s
} }
} }

View File

@@ -4,7 +4,6 @@ import java.io.FileInputStream;
import java.util.Properties; import java.util.Properties;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -21,6 +20,10 @@ public class CanalLauncher {
public static void main(String[] args) throws Throwable { public static void main(String[] args) throws Throwable {
try { try {
logger.info("## set default uncaught exception handler");
setGlobalUncaughtExceptionHandler();
logger.info("## load canal configurations");
String conf = System.getProperty("canal.conf", "classpath:canal.properties"); String conf = System.getProperty("canal.conf", "classpath:canal.properties");
Properties properties = new Properties(); Properties properties = new Properties();
if (conf.startsWith(CLASSPATH_URL_PREFIX)) { if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
@@ -41,8 +44,7 @@ public class CanalLauncher {
logger.info("## stop the canal server"); logger.info("## stop the canal server");
controller.stop(); controller.stop();
} catch (Throwable e) { } catch (Throwable e) {
logger.warn("##something goes wrong when stopping canal Server:\n{}", logger.warn("##something goes wrong when stopping canal Server:", e);
ExceptionUtils.getFullStackTrace(e));
} finally { } finally {
logger.info("## canal server is down."); logger.info("## canal server is down.");
} }
@@ -50,9 +52,17 @@ public class CanalLauncher {
}); });
} catch (Throwable e) { } catch (Throwable e) {
logger.error("## Something goes wrong when starting up the canal Server:\n{}", logger.error("## Something goes wrong when starting up the canal Server:", e);
ExceptionUtils.getFullStackTrace(e));
System.exit(0); System.exit(0);
} }
} }
private static void setGlobalUncaughtExceptionHandler() {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
logger.error("UnCaughtException", e);
}
});
}
} }

View File

@@ -13,7 +13,6 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@@ -49,6 +48,8 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1,
new NamedThreadFactory("canal-instance-scan")); new NamedThreadFactory("canal-instance-scan"));
private volatile boolean isFirst = true;
public void start() { public void start() {
super.start(); super.start();
Assert.notNull(rootConf, "root conf dir is null!"); Assert.notNull(rootConf, "root conf dir is null!");
@@ -58,6 +59,9 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
public void run() { public void run() {
try { try {
scan(); scan();
if (isFirst) {
isFirst = false;
}
} catch (Throwable e) { } catch (Throwable e) {
logger.error("scan failed", e); logger.error("scan failed", e);
} }
@@ -122,13 +126,18 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
if (!actions.containsKey(destination) && instanceConfigs.length > 0) { if (!actions.containsKey(destination) && instanceConfigs.length > 0) {
// 存在合法的instance.properties,并且第一次添加时,进行启动操作 // 存在合法的instance.properties,并且第一次添加时,进行启动操作
notifyStart(instanceDir, destination); notifyStart(instanceDir, destination, instanceConfigs);
} else if (actions.containsKey(destination)) { } else if (actions.containsKey(destination)) {
// 历史已经启动过 // 历史已经启动过
if (instanceConfigs.length == 0) { // 如果不存在合法的instance.properties if (instanceConfigs.length == 0) { // 如果不存在合法的instance.properties
notifyStop(destination); notifyStop(destination);
} else { } else {
InstanceConfigFiles lastFile = lastFiles.get(destination); InstanceConfigFiles lastFile = lastFiles.get(destination);
// 历史启动过 所以配置文件信息必然存在
if (!isFirst && CollectionUtils.isEmpty(lastFile.getInstanceFiles())) {
logger.error("[{}] is started, but not found instance file info.", destination);
}
boolean hasChanged = judgeFileChanged(instanceConfigs, lastFile.getInstanceFiles()); boolean hasChanged = judgeFileChanged(instanceConfigs, lastFile.getInstanceFiles());
// 通知变化 // 通知变化
if (hasChanged) { if (hasChanged) {
@@ -161,13 +170,22 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
} }
} }
private void notifyStart(File instanceDir, String destination) { private void notifyStart(File instanceDir, String destination, File[] instanceConfigs) {
try { try {
defaultAction.start(destination); defaultAction.start(destination);
actions.put(destination, defaultAction); actions.put(destination, defaultAction);
// 启动成功后记录配置文件信息
InstanceConfigFiles lastFile = lastFiles.get(destination);
List<FileInfo> newFileInfo = new ArrayList<FileInfo>();
for (File instanceConfig : instanceConfigs) {
newFileInfo.add(new FileInfo(instanceConfig.getName(), instanceConfig.lastModified()));
}
lastFile.setInstanceFiles(newFileInfo);
logger.info("auto notify start {} successful.", destination); logger.info("auto notify start {} successful.", destination);
} catch (Throwable e) { } catch (Throwable e) {
logger.error("scan add found[{}] but start failed", destination, ExceptionUtils.getFullStackTrace(e)); logger.error(String.format("scan add found[%s] but start failed", destination), e);
} }
} }
@@ -175,9 +193,10 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
InstanceAction action = actions.remove(destination); InstanceAction action = actions.remove(destination);
try { try {
action.stop(destination); action.stop(destination);
lastFiles.remove(destination);
logger.info("auto notify stop {} successful.", destination); logger.info("auto notify stop {} successful.", destination);
} catch (Throwable e) { } catch (Throwable e) {
logger.error("scan delete found[{}] but stop failed", destination, ExceptionUtils.getFullStackTrace(e)); logger.error(String.format("scan delete found[%s] but stop failed", destination), e);
actions.put(destination, action);// 再重新加回去,下一次scan时再执行删除 actions.put(destination, action);// 再重新加回去,下一次scan时再执行删除
} }
} }
@@ -189,9 +208,7 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
action.reload(destination); action.reload(destination);
logger.info("auto notify reload {} successful.", destination); logger.info("auto notify reload {} successful.", destination);
} catch (Throwable e) { } catch (Throwable e) {
logger.error("scan reload found[{}] but reload failed", logger.error(String.format("scan reload found[%s] but reload failed", destination), e);
destination,
ExceptionUtils.getFullStackTrace(e));
} }
} }
} }
@@ -294,5 +311,4 @@ public class SpringInstanceConfigMonitor extends AbstractCanalLifeCycle implemen
} }
} }
} }

View File

@@ -36,10 +36,12 @@ canal.instance.network.sendBufferSize = 16384
canal.instance.network.soTimeout = 30 canal.instance.network.soTimeout = 30
# binlog filter config # binlog filter config
canal.instance.filter.druid.ddl = true
canal.instance.filter.query.dcl = false canal.instance.filter.query.dcl = false
canal.instance.filter.query.dml = false canal.instance.filter.query.dml = false
canal.instance.filter.query.ddl = false canal.instance.filter.query.ddl = false
canal.instance.filter.table.error = false canal.instance.filter.table.error = false
canal.instance.filter.rows = false
# binlog format/image check # binlog format/image check
canal.instance.binlog.format = ROW,STATEMENT,MIXED canal.instance.binlog.format = ROW,STATEMENT,MIXED
@@ -58,9 +60,13 @@ canal.conf.dir = ../conf
canal.auto.scan = true canal.auto.scan = true
canal.auto.scan.interval = 5 canal.auto.scan.interval = 5
canal.instance.tsdb.spring.xml=classpath:spring/tsdb/h2-tsdb.xml
#canal.instance.tsdb.spring.xml=classpath:spring/tsdb/mysql-tsdb.xml
canal.instance.global.mode = spring canal.instance.global.mode = spring
canal.instance.global.lazy = false canal.instance.global.lazy = false
#canal.instance.global.manager.address = 127.0.0.1:1099 #canal.instance.global.manager.address = 127.0.0.1:1099
#canal.instance.global.spring.xml = classpath:spring/local-instance.xml
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml #canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
canal.instance.global.spring.xml = classpath:spring/file-instance.xml canal.instance.global.spring.xml = classpath:spring/file-instance.xml
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml #canal.instance.global.spring.xml = classpath:spring/default-instance.xml

View File

@@ -1,27 +1,33 @@
################################################# #################################################
## mysql serverId ## mysql serverId
canal.instance.mysql.slaveId = 1234 canal.instance.mysql.slaveId=0
# position info # position info
canal.instance.master.address=127.0.0.1:3306 canal.instance.master.address=127.0.0.1:3306
canal.instance.master.journal.name= canal.instance.master.journal.name=
canal.instance.master.position= canal.instance.master.position=
canal.instance.master.timestamp= canal.instance.master.timestamp=
# table meta tsdb info
canal.instance.tsdb.enable=true
canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:}
canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
canal.instance.tsdb.dbUsername=canal
canal.instance.tsdb.dbPassword=canal
#canal.instance.standby.address = #canal.instance.standby.address =
#canal.instance.standby.journal.name = #canal.instance.standby.journal.name =
#canal.instance.standby.position = #canal.instance.standby.position =
#canal.instance.standby.timestamp = #canal.instance.standby.timestamp =
# username/password # username/password
canal.instance.dbUsername=canal canal.instance.dbUsername=canal
canal.instance.dbPassword=canal canal.instance.dbPassword=canal
canal.instance.defaultDatabaseName = canal.instance.defaultDatabaseName=test
canal.instance.connectionCharset=UTF-8 canal.instance.connectionCharset=UTF-8
# table regex # table regex
canal.instance.filter.regex=.*\\..* canal.instance.filter.regex=.*\\..*
# table black regex # table black regex
canal.instance.filter.black.regex= canal.instance.filter.black.regex=
################################################# #################################################

View File

@@ -0,0 +1,32 @@
#################################################
# rds openapi binlog
canal.instance.rds.open.url=https://rds.aliyuncs.com/
canal.instance.rds.open.accesskey=
canal.instance.rds.open.secretkey=
canal.instance.rds.instanceId=
canal.instance.rds.startTime=
canal.instance.rds.endTime=
# local binlog dir
canal.instance.parser.directory=${canal.file.data.dir:../conf}/${canal.instance.destination:}/binlog
# position info
canal.instance.master.address=127.0.0.1:3306
# table meta tsdb info
canal.instance.tsdb.enable=true
canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:}
canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
#canal.instance.tsdb.dbUsername=canal
#canal.instance.tsdb.dbPassword=canal
# username/password
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal
canal.instance.defaultDatabaseName=test
canal.instance.connectionCharset=UTF-8
# table regex
canal.instance.filter.regex=test\\..*
# table black regex
canal.instance.filter.black.regex=
#################################################

View File

@@ -75,9 +75,7 @@
</logger> </logger>
<root level="WARN"> <root level="WARN">
<!--
<appender-ref ref="STDOUT"/> <appender-ref ref="STDOUT"/>
-->
<appender-ref ref="CANAL-ROOT" /> <appender-ref ref="CANAL-ROOT" />
</root> </root>
</configuration> </configuration>

View File

@@ -85,7 +85,7 @@
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser"> <bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
<property name="destination" value="${canal.instance.destination}" /> <property name="destination" value="${canal.instance.destination}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" /> <property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
<!-- 心跳配置 --> <!-- 心跳配置 -->
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" /> <property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
<property name="detectingSQL" value="${canal.instance.detecting.sql}" /> <property name="detectingSQL" value="${canal.instance.detecting.sql}" />
@@ -128,14 +128,14 @@
<!-- 解析位点记录 --> <!-- 解析位点记录 -->
<property name="logPositionManager"> <property name="logPositionManager">
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager"> <bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
<property name="primary"> <constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" /> <bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
</property> </constructor-arg>
<property name="failback"> <constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager"> <bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
<property name="metaManager" ref="metaManager" /> <constructor-arg ref="metaManager"/>
</bean> </bean>
</property> </constructor-arg>
</bean> </bean>
</property> </property>
@@ -178,8 +178,14 @@
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" /> <property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" /> <property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" /> <property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" /> <property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" /> <property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" /> <property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
<!--表结构相关-->
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
</bean> </bean>
</beans> </beans>

View File

@@ -71,7 +71,7 @@
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser"> <bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
<property name="destination" value="${canal.instance.destination}" /> <property name="destination" value="${canal.instance.destination}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" /> <property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
<!-- 心跳配置 --> <!-- 心跳配置 -->
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" /> <property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
<property name="detectingSQL" value="${canal.instance.detecting.sql}" /> <property name="detectingSQL" value="${canal.instance.detecting.sql}" />
@@ -113,14 +113,14 @@
<!-- 解析位点记录 --> <!-- 解析位点记录 -->
<property name="logPositionManager"> <property name="logPositionManager">
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager"> <bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
<property name="primary"> <constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" /> <bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
</property> </constructor-arg>
<property name="failback"> <constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager"> <bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
<property name="metaManager" ref="metaManager" /> <constructor-arg ref="metaManager"/>
</bean> </bean>
</property> </constructor-arg>
</bean> </bean>
</property> </property>
@@ -163,8 +163,14 @@
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" /> <property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" /> <property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" /> <property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" /> <property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" /> <property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" /> <property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
<!--表结构相关-->
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
</bean> </bean>
</beans> </beans>

View File

@@ -77,7 +77,7 @@
<bean id="eventParser1" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser"> <bean id="eventParser1" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
<property name="destination" value="${canal.instance.destination}" /> <property name="destination" value="${canal.instance.destination}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" /> <property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
<!-- 心跳配置 --> <!-- 心跳配置 -->
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" /> <property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
<property name="detectingSQL" value="${canal.instance.detecting.sql}" /> <property name="detectingSQL" value="${canal.instance.detecting.sql}" />
@@ -160,6 +160,7 @@
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" /> <property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" /> <property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" /> <property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" /> <property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" /> <property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" /> <property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
@@ -167,7 +168,7 @@
<bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser"> <bean id="eventParser2" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
<property name="destination" value="${canal.instance.destination}" /> <property name="destination" value="${canal.instance.destination}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" /> <property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
<!-- 心跳配置 --> <!-- 心跳配置 -->
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" /> <property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
<property name="detectingSQL" value="${canal.instance.detecting.sql}" /> <property name="detectingSQL" value="${canal.instance.detecting.sql}" />
@@ -250,6 +251,8 @@
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" /> <property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" /> <property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" /> <property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" /> <property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" /> <property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" /> <property name="supportBinlogImages" value="${canal.instance.binlog.image}" />

View File

@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName">
<!-- properties -->
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
<property name="ignoreResourceNotFound" value="true" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
<property name="locationNames">
<list>
<value>classpath:canal.properties</value>
<value>classpath:${canal.instance.destination:}/instance.properties</value>
</list>
</property>
</bean>
<bean id="socketAddressEditor" class="com.alibaba.otter.canal.instance.spring.support.SocketAddressEditor" />
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="socketAddressEditor" />
</list>
</property>
</bean>
<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
<property name="destination" value="${canal.instance.destination}" />
<property name="eventParser">
<ref local="eventParser" />
</property>
<property name="eventSink">
<ref local="eventSink" />
</property>
<property name="eventStore">
<ref local="eventStore" />
</property>
<property name="metaManager">
<ref local="metaManager" />
</property>
<property name="alarmHandler">
<ref local="alarmHandler" />
</property>
</bean>
<!-- 报警处理类 -->
<bean id="alarmHandler" class="com.alibaba.otter.canal.common.alarm.LogAlarmHandler" />
<bean id="metaManager" class="com.alibaba.otter.canal.meta.FileMixedMetaManager">
<property name="dataDir" value="${canal.file.data.dir:../conf}" />
<property name="period" value="${canal.file.flush.period:1000}" />
</bean>
<bean id="eventStore" class="com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer">
<property name="bufferSize" value="${canal.instance.memory.buffer.size:16384}" />
<property name="bufferMemUnit" value="${canal.instance.memory.buffer.memunit:1024}" />
<property name="batchMode" value="${canal.instance.memory.batch.mode:MEMSIZE}" />
<property name="ddlIsolation" value="${canal.instance.get.ddl.isolation:false}" />
</bean>
<bean id="eventSink" class="com.alibaba.otter.canal.sink.entry.EntryEventSink">
<property name="eventStore" ref="eventStore" />
</bean>
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsLocalBinlogEventParser">
<property name="destination" value="${canal.instance.destination}" />
<property name="alarmHandler" ref="alarmHandler" />
<!-- 解析过滤处理 -->
<property name="eventFilter">
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
<constructor-arg index="0" value="${canal.instance.filter.regex:.*\..*}" />
</bean>
</property>
<property name="eventBlackFilter">
<bean class="com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter" >
<constructor-arg index="0" value="${canal.instance.filter.black.regex:}" />
<constructor-arg index="1" value="false" />
</bean>
</property>
<!-- 最大事务解析大小,超过该大小后事务将被切分为多个事务投递 -->
<property name="transactionSize" value="${canal.instance.transaction.size:1024}" />
<!-- 解析编码 -->
<property name="connectionCharset" value="${canal.instance.connectionCharset:UTF-8}" />
<!-- 解析位点记录 -->
<property name="logPositionManager">
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
<constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
</constructor-arg>
<constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
<constructor-arg ref="metaManager"/>
</bean>
</constructor-arg>
</bean>
</property>
<!-- 解析数据库信息 -->
<property name="masterInfo">
<bean class="com.alibaba.otter.canal.parse.support.AuthenticationInfo">
<property name="address" value="${canal.instance.master.address}" />
<property name="username" value="${canal.instance.dbUsername:retl}" />
<property name="password" value="${canal.instance.dbPassword:retl}" />
<property name="defaultDatabaseName" value="${canal.instance.defaultDatabaseName:retl}" />
</bean>
</property>
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
<property name="needWait" value="${canal.instance.parser.needWait:false}"/>
<property name="directory" value="${canal.instance.parser.directory:}"/>
<!-- rds相关 -->
<property name="url" value="${canal.instance.rds.open.url:}"/>
<property name="accesskey" value="${canal.instance.rds.open.accesskey:}"/>
<property name="secretkey" value="${canal.instance.rds.open.secretkey:}"/>
<property name="instanceId" value="${canal.instance.rds.instanceId:}"/>
<property name="startTime" value="${canal.instance.rds.startTime:}"/>
<property name="endTime" value="${canal.instance.rds.endTime:}"/>
<!--表结构相关-->
<property name="enableTsdb" value="${canal.instance.tsdb.enable:true}"/>
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
</bean>
</beans>

View File

@@ -68,7 +68,7 @@
<bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser"> <bean id="eventParser" class="com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser">
<property name="destination" value="${canal.instance.destination}" /> <property name="destination" value="${canal.instance.destination}" />
<property name="slaveId" value="${canal.instance.mysql.slaveId:1234}" /> <property name="slaveId" value="${canal.instance.mysql.slaveId:0}" />
<!-- 心跳配置 --> <!-- 心跳配置 -->
<property name="detectingEnable" value="${canal.instance.detecting.enable:false}" /> <property name="detectingEnable" value="${canal.instance.detecting.enable:false}" />
<property name="detectingSQL" value="${canal.instance.detecting.sql}" /> <property name="detectingSQL" value="${canal.instance.detecting.sql}" />
@@ -151,8 +151,14 @@
<property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" /> <property name="filterQueryDml" value="${canal.instance.filter.query.dml:false}" />
<property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" /> <property name="filterQueryDcl" value="${canal.instance.filter.query.dcl:false}" />
<property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" /> <property name="filterQueryDdl" value="${canal.instance.filter.query.ddl:false}" />
<property name="useDruidDdlFilter" value="${canal.instance.filter.druid.ddl:true}" />
<property name="filterRows" value="${canal.instance.filter.rows:false}" />
<property name="filterTableError" value="${canal.instance.filter.table.error:false}" /> <property name="filterTableError" value="${canal.instance.filter.table.error:false}" />
<property name="supportBinlogFormats" value="${canal.instance.binlog.format}" /> <property name="supportBinlogFormats" value="${canal.instance.binlog.format}" />
<property name="supportBinlogImages" value="${canal.instance.binlog.image}" /> <property name="supportBinlogImages" value="${canal.instance.binlog.image}" />
<!--表结构相关-->
<property name="enableTsdb" value="${canal.instance.tsdb.enable:false}"/>
<property name="tsdbSpringXml" value="${canal.instance.tsdb.spring.xml:}"/>
</bean> </bean>
</beans> </beans>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName">
<!-- properties -->
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
<property name="ignoreResourceNotFound" value="true" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
<property name="locationNames">
<list>
<value>classpath:canal.properties</value>
<value>classpath:${canal.instance.destination:}/instance.properties</value>
</list>
</property>
</bean>
<!-- 基于db的实现 -->
<bean id="tableMetaTSDB" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta">
<property name="metaHistoryDAO" ref="metaHistoryDAO"/>
<property name="metaSnapshotDAO" ref="metaSnapshotDAO"/>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="${canal.instance.tsdb.url:}" />
<property name="username" value="${canal.instance.tsdb.dbUsername:canal}" />
<property name="password" value="${canal.instance.tsdb.dbPassword:canal}" />
<property name="maxActive" value="30" />
<property name="initialSize" value="0" />
<property name="minIdle" value="1" />
<property name="maxWait" value="10000" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="useUnfairLock" value="true" />
</bean>
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:spring/tsdb/sql-map/sqlmap-config.xml"/>
</bean>
<bean id="metaHistoryDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDAO">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
<bean id="metaSnapshotDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDAO">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
</beans>

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName">
<!-- properties -->
<bean class="com.alibaba.otter.canal.instance.spring.support.PropertyPlaceholderConfigurer" lazy-init="false">
<property name="ignoreResourceNotFound" value="true" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/><!-- 允许system覆盖 -->
<property name="locationNames">
<list>
<value>classpath:canal.properties</value>
<value>classpath:${canal.instance.destination:}/instance.properties</value>
</list>
</property>
</bean>
<!-- 基于db的实现 -->
<bean id="tableMetaTSDB" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta">
<property name="metaHistoryDAO" ref="metaHistoryDAO"/>
<property name="metaSnapshotDAO" ref="metaSnapshotDAO"/>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="${canal.instance.tsdb.url:}" />
<property name="username" value="${canal.instance.tsdb.dbUsername:canal}" />
<property name="password" value="${canal.instance.tsdb.dbPassword:canal}" />
<property name="maxActive" value="30" />
<property name="initialSize" value="0" />
<property name="minIdle" value="1" />
<property name="maxWait" value="10000" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 1" />
<property name="exceptionSorterClassName" value="com.alibaba.druid.pool.vendor.MySqlExceptionSorter" />
<property name="validConnectionCheckerClassName" value="com.alibaba.druid.pool.vendor.MySqlValidConnectionChecker" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="useUnfairLock" value="true" />
</bean>
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:spring/tsdb/sql-map/sqlmap-config.xml"/>
</bean>
<bean id="metaHistoryDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDAO">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
<bean id="metaSnapshotDAO" class="com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDAO">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
</beans>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -0,0 +1,39 @@
CREATE TABLE IF NOT EXISTS `meta_snapshot` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`destination` varchar(128) DEFAULT NULL COMMENT '通道名称',
`binlog_file` varchar(64) DEFAULT NULL COMMENT 'binlog文件名',
`binlog_offest` bigint(20) DEFAULT NULL COMMENT 'binlog偏移量',
`binlog_master_id` varchar(64) DEFAULT NULL COMMENT 'binlog节点id',
`binlog_timestamp` bigint(20) DEFAULT NULL COMMENT 'binlog应用的时间戳',
`data` longtext DEFAULT NULL COMMENT '表结构数据',
`extra` text DEFAULT NULL COMMENT '额外的扩展信息',
PRIMARY KEY (`id`),
UNIQUE KEY binlog_file_offest(`destination`,`binlog_master_id`,`binlog_file`,`binlog_offest`),
KEY `destination` (`destination`),
KEY `destination_timestamp` (`destination`,`binlog_timestamp`),
KEY `gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='表结构记录表快照表';
CREATE TABLE IF NOT EXISTS `meta_history` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`destination` varchar(128) DEFAULT NULL COMMENT '通道名称',
`binlog_file` varchar(64) DEFAULT NULL COMMENT 'binlog文件名',
`binlog_offest` bigint(20) DEFAULT NULL COMMENT 'binlog偏移量',
`binlog_master_id` varchar(64) DEFAULT NULL COMMENT 'binlog节点id',
`binlog_timestamp` bigint(20) DEFAULT NULL COMMENT 'binlog应用的时间戳',
`use_schema` varchar(1024) DEFAULT NULL COMMENT '执行sql时对应的schema',
`schema` varchar(1024) DEFAULT NULL COMMENT '对应的schema',
`table` varchar(1024) DEFAULT NULL COMMENT '对应的table',
`sql` longtext DEFAULT NULL COMMENT '执行的sql',
`type` varchar(256) DEFAULT NULL COMMENT 'sql类型',
`extra` text DEFAULT NULL COMMENT '额外的扩展信息',
PRIMARY KEY (`id`),
UNIQUE KEY binlog_file_offest(`destination`,`binlog_master_id`,`binlog_file`,`binlog_offest`),
KEY `destination` (`destination`),
KEY `destination_timestamp` (`destination`,`binlog_timestamp`),
KEY `gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='表结构变化明细表';

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.20</version> <version>1.0.25-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
@@ -33,10 +33,6 @@
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.jboss.netty</groupId>
<artifactId>netty</artifactId>
</dependency>
<!-- test dependency --> <!-- test dependency -->
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>

View File

@@ -2,19 +2,19 @@ package com.alibaba.otter.canal.parse.driver.mysql;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.ClientAuthenticationPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.client.ClientAuthenticationPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.QuitCommandPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ErrorPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ErrorPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.HandshakeInitializationPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.HandshakeInitializationPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.Reply323Packet; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.Reply323Packet;
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannelPool;
import com.alibaba.otter.canal.parse.driver.mysql.utils.MySQLPasswordEncrypter; import com.alibaba.otter.canal.parse.driver.mysql.utils.MySQLPasswordEncrypter;
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager; import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
@@ -34,17 +34,20 @@ public class MysqlConnector {
private byte charsetNumber = 33; private byte charsetNumber = 33;
private String defaultSchema = "retl"; private String defaultSchema = "retl";
private int soTimeout = 30 * 1000; private int soTimeout = 30 * 1000;
private int connTimeout = 5 * 1000;
private int receiveBufferSize = 16 * 1024; private int receiveBufferSize = 16 * 1024;
private int sendBufferSize = 16 * 1024; private int sendBufferSize = 16 * 1024;
private SocketChannel channel; private SocketChannel channel;
private volatile boolean dumping = false;
// mysql connectinnId
private long connectionId = -1;
private AtomicBoolean connected = new AtomicBoolean(false); private AtomicBoolean connected = new AtomicBoolean(false);
public MysqlConnector(){ public MysqlConnector(){
} }
public MysqlConnector(InetSocketAddress address, String username, String password){ public MysqlConnector(InetSocketAddress address, String username, String password){
this.address = address; this.address = address;
this.username = username; this.username = username;
this.password = password; this.password = password;
@@ -61,14 +64,12 @@ public class MysqlConnector {
public void connect() throws IOException { public void connect() throws IOException {
if (connected.compareAndSet(false, true)) { if (connected.compareAndSet(false, true)) {
try { try {
channel = SocketChannel.open(); channel = SocketChannelPool.open(address);
configChannel(channel);
logger.info("connect MysqlConnection to {}...", address); logger.info("connect MysqlConnection to {}...", address);
channel.connect(address);
negotiate(channel); negotiate(channel);
} catch (Exception e) { } catch (Exception e) {
disconnect(); disconnect();
throw new IOException("connect " + this.address + " failure:" + ExceptionUtils.getStackTrace(e)); throw new IOException("connect " + this.address + " failure", e);
} }
} else { } else {
logger.error("the channel can't be connected twice."); logger.error("the channel can't be connected twice.");
@@ -86,10 +87,28 @@ public class MysqlConnector {
if (channel != null) { if (channel != null) {
channel.close(); channel.close();
} }
logger.info("disConnect MysqlConnection to {}...", address); logger.info("disConnect MysqlConnection to {}...", address);
} catch (Exception e) { } catch (Exception e) {
throw new IOException("disconnect " + this.address + " failure:" + ExceptionUtils.getStackTrace(e)); throw new IOException("disconnect " + this.address + " failure", e);
}
// 执行一次quit
if (dumping && connectionId >= 0) {
MysqlConnector connector = null;
try {
connector = this.fork();
connector.connect();
MysqlUpdateExecutor executor = new MysqlUpdateExecutor(connector);
executor.update("KILL CONNECTION " + connectionId);
} catch (Exception e) {
throw new IOException("KILL DUMP " + connectionId + " failure", e);
} finally {
if (connector != null) {
connector.disconnect();
}
}
dumping = false;
} }
} else { } else {
logger.info("the channel {} is not connected", this.address); logger.info("the channel {} is not connected", this.address);
@@ -110,18 +129,18 @@ public class MysqlConnector {
connector.setReceiveBufferSize(getReceiveBufferSize()); connector.setReceiveBufferSize(getReceiveBufferSize());
connector.setSendBufferSize(getSendBufferSize()); connector.setSendBufferSize(getSendBufferSize());
connector.setSoTimeout(getSoTimeout()); connector.setSoTimeout(getSoTimeout());
connector.setConnTimeout(connTimeout);
return connector; return connector;
} }
// ====================== help method ==================== public void quit() throws IOException {
QuitCommandPacket quit = new QuitCommandPacket();
byte[] cmdBody = quit.toBytes();
private void configChannel(SocketChannel channel) throws IOException { HeaderPacket quitHeader = new HeaderPacket();
channel.socket().setKeepAlive(true); quitHeader.setPacketBodyLength(cmdBody.length);
channel.socket().setReuseAddress(true); quitHeader.setPacketSequenceNumber((byte) 0x00);
channel.socket().setSoTimeout(soTimeout); PacketManager.writePkg(channel, quitHeader.toBytes(), cmdBody);
channel.socket().setTcpNoDelay(true);
channel.socket().setReceiveBufferSize(receiveBufferSize);
channel.socket().setSendBufferSize(sendBufferSize);
} }
private void negotiate(SocketChannel channel) throws IOException { private void negotiate(SocketChannel channel) throws IOException {
@@ -140,6 +159,7 @@ public class MysqlConnector {
} }
HandshakeInitializationPacket handshakePacket = new HandshakeInitializationPacket(); HandshakeInitializationPacket handshakePacket = new HandshakeInitializationPacket();
handshakePacket.fromBytes(body); handshakePacket.fromBytes(body);
connectionId = handshakePacket.threadId; // 记录一下connection
logger.info("handshake initialization packet received, prepare the client authentication packet to send"); logger.info("handshake initialization packet received, prepare the client authentication packet to send");
@@ -157,8 +177,7 @@ public class MysqlConnector {
h.setPacketBodyLength(clientAuthPkgBody.length); h.setPacketBodyLength(clientAuthPkgBody.length);
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1)); h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
PacketManager.write(channel, PacketManager.writePkg(channel, h.toBytes(), clientAuthPkgBody);
new ByteBuffer[] { ByteBuffer.wrap(h.toBytes()), ByteBuffer.wrap(clientAuthPkgBody) });
logger.info("client authentication packet is sent out."); logger.info("client authentication packet is sent out.");
// check auth result // check auth result
@@ -194,7 +213,7 @@ public class MysqlConnector {
h323.setPacketBodyLength(b323Body.length); h323.setPacketBodyLength(b323Body.length);
h323.setPacketSequenceNumber((byte) (packetSequenceNumber + 1)); h323.setPacketSequenceNumber((byte) (packetSequenceNumber + 1));
PacketManager.write(channel, new ByteBuffer[] { ByteBuffer.wrap(h323.toBytes()), ByteBuffer.wrap(b323Body) }); PacketManager.writePkg(channel, h323.toBytes(), b323Body);
logger.info("client 323 authentication packet is sent out."); logger.info("client 323 authentication packet is sent out.");
// check auth result // check auth result
HeaderPacket header = PacketManager.readHeader(channel, 4); HeaderPacket header = PacketManager.readHeader(channel, 4);
@@ -291,4 +310,27 @@ public class MysqlConnector {
this.password = password; this.password = password;
} }
public long getConnectionId() {
return connectionId;
}
public void setConnectionId(long connectionId) {
this.connectionId = connectionId;
}
public boolean isDumping() {
return dumping;
}
public void setDumping(boolean dumping) {
this.dumping = dumping;
}
public int getConnTimeout() {
return connTimeout;
}
public void setConnTimeout(int connTimeout) {
this.connTimeout = connTimeout;
}
} }

View File

@@ -1,17 +1,18 @@
package com.alibaba.otter.canal.parse.driver.mysql; package com.alibaba.otter.canal.parse.driver.mysql;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.SocketChannel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.client.QueryCommandPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.client.QueryCommandPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.EOFPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ErrorPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ErrorPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetHeaderPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetHeaderPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.RowDataPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.RowDataPacket;
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager; import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager;
/** /**
@@ -24,9 +25,9 @@ public class MysqlQueryExecutor {
private SocketChannel channel; private SocketChannel channel;
public MysqlQueryExecutor(MysqlConnector connector){ public MysqlQueryExecutor(MysqlConnector connector) throws IOException{
if (!connector.isConnected()) { if (!connector.isConnected()) {
throw new RuntimeException("should execute connector.connect() first"); throw new IOException("should execute connector.connect() first");
} }
this.channel = connector.getChannel(); this.channel = connector.getChannel();
@@ -51,7 +52,7 @@ public class MysqlQueryExecutor {
QueryCommandPacket cmd = new QueryCommandPacket(); QueryCommandPacket cmd = new QueryCommandPacket();
cmd.setQueryString(queryString); cmd.setQueryString(queryString);
byte[] bodyBytes = cmd.toBytes(); byte[] bodyBytes = cmd.toBytes();
PacketManager.write(channel, bodyBytes); PacketManager.writeBody(channel, bodyBytes);
byte[] body = readNextPacket(); byte[] body = readNextPacket();
if (body[0] < 0) { if (body[0] < 0) {
@@ -88,16 +89,70 @@ public class MysqlQueryExecutor {
for (RowDataPacket r : rowData) { for (RowDataPacket r : rowData) {
resultSet.getFieldValues().addAll(r.getColumns()); resultSet.getFieldValues().addAll(r.getColumns());
} }
resultSet.setSourceAddress(channel.socket().getRemoteSocketAddress()); resultSet.setSourceAddress(channel.getRemoteSocketAddress());
return resultSet; return resultSet;
} }
private void readEofPacket() throws IOException { public List<ResultSetPacket> queryMulti(String queryString) throws IOException {
QueryCommandPacket cmd = new QueryCommandPacket();
cmd.setQueryString(queryString);
byte[] bodyBytes = cmd.toBytes();
PacketManager.writeBody(channel, bodyBytes);
List<ResultSetPacket> resultSets = new ArrayList<ResultSetPacket>();
boolean moreResult = true;
while (moreResult) {
byte[] body = readNextPacket();
if (body[0] < 0) {
ErrorPacket packet = new ErrorPacket();
packet.fromBytes(body);
throw new IOException(packet + "\n with command: " + queryString);
}
ResultSetHeaderPacket rsHeader = new ResultSetHeaderPacket();
rsHeader.fromBytes(body);
List<FieldPacket> fields = new ArrayList<FieldPacket>();
for (int i = 0; i < rsHeader.getColumnCount(); i++) {
FieldPacket fp = new FieldPacket();
fp.fromBytes(readNextPacket());
fields.add(fp);
}
moreResult = readEofPacket();
List<RowDataPacket> rowData = new ArrayList<RowDataPacket>();
while (true) {
body = readNextPacket();
if (body[0] == -2) {
break;
}
RowDataPacket rowDataPacket = new RowDataPacket();
rowDataPacket.fromBytes(body);
rowData.add(rowDataPacket);
}
ResultSetPacket resultSet = new ResultSetPacket();
resultSet.getFieldDescriptors().addAll(fields);
for (RowDataPacket r : rowData) {
resultSet.getFieldValues().addAll(r.getColumns());
}
resultSet.setSourceAddress(channel.getRemoteSocketAddress());
resultSets.add(resultSet);
}
return resultSets;
}
private boolean readEofPacket() throws IOException {
byte[] eofBody = readNextPacket(); byte[] eofBody = readNextPacket();
EOFPacket packet = new EOFPacket();
packet.fromBytes(eofBody);
if (eofBody[0] != -2) { if (eofBody[0] != -2) {
throw new IOException("EOF Packet is expected, but packet with field_count=" + eofBody[0] + " is found."); throw new IOException("EOF Packet is expected, but packet with field_count=" + eofBody[0] + " is found.");
} }
return (packet.statusFlag & 0x0008) != 0;
} }
protected byte[] readNextPacket() throws IOException { protected byte[] readNextPacket() throws IOException {

View File

@@ -1,7 +1,6 @@
package com.alibaba.otter.canal.parse.driver.mysql; package com.alibaba.otter.canal.parse.driver.mysql;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.SocketChannel;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -21,28 +20,29 @@ public class MysqlUpdateExecutor {
private static final Logger logger = LoggerFactory.getLogger(MysqlUpdateExecutor.class); private static final Logger logger = LoggerFactory.getLogger(MysqlUpdateExecutor.class);
private SocketChannel channel; private MysqlConnector connector;
public MysqlUpdateExecutor(MysqlConnector connector){ public MysqlUpdateExecutor(MysqlConnector connector) throws IOException{
if (!connector.isConnected()) { if (!connector.isConnected()) {
throw new RuntimeException("should execute connector.connect() first"); throw new IOException("should execute connector.connect() first");
} }
this.channel = connector.getChannel(); this.connector = connector;
} }
public MysqlUpdateExecutor(SocketChannel ch){ /*
this.channel = ch; * public MysqlUpdateExecutor(SocketChannel ch){ this.channel = ch; }
} */
public OKPacket update(String updateString) throws IOException { public OKPacket update(String updateString) throws IOException {
QueryCommandPacket cmd = new QueryCommandPacket(); QueryCommandPacket cmd = new QueryCommandPacket();
cmd.setQueryString(updateString); cmd.setQueryString(updateString);
byte[] bodyBytes = cmd.toBytes(); byte[] bodyBytes = cmd.toBytes();
PacketManager.write(channel, bodyBytes); PacketManager.writeBody(connector.getChannel(), bodyBytes);
logger.debug("read update result..."); logger.debug("read update result...");
byte[] body = PacketManager.readBytes(channel, PacketManager.readHeader(channel, 4).getPacketBodyLength()); byte[] body = PacketManager.readBytes(connector.getChannel(),
PacketManager.readHeader(connector.getChannel(), 4).getPacketBodyLength());
if (body[0] < 0) { if (body[0] < 0) {
ErrorPacket packet = new ErrorPacket(); ErrorPacket packet = new ErrorPacket();
packet.fromBytes(body); packet.fromBytes(body);

View File

@@ -47,8 +47,9 @@ public class ClientAuthenticationPacket extends PacketWithHeaderPacket {
/** /**
* CLIENT_LONG_PASSWORD CLIENT_LONG_FLAG CLIENT_PROTOCOL_41 * CLIENT_LONG_PASSWORD CLIENT_LONG_FLAG CLIENT_PROTOCOL_41
* CLIENT_INTERACTIVE CLIENT_TRANSACTIONS CLIENT_SECURE_CONNECTION * CLIENT_INTERACTIVE CLIENT_TRANSACTIONS CLIENT_SECURE_CONNECTION
* CLIENT_MULTI_STATEMENTS;
*/ */
ByteHelper.writeUnsignedIntLittleEndian(1 | 4 | 512 | 8192 | 32768, out); // remove ByteHelper.writeUnsignedIntLittleEndian(1 | 4 | 512 | 8192 | 32768 | 0x00010000, out); // remove
// client_interactive // client_interactive
// feature // feature

View File

@@ -0,0 +1,35 @@
package com.alibaba.otter.canal.parse.driver.mysql.packets.client;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import com.alibaba.otter.canal.parse.driver.mysql.packets.CommandPacket;
/**
* quit cmd
*
* @author agapple 2016年3月1日 下午8:33:02
* @since 1.0.22
*/
public class QuitCommandPacket extends CommandPacket {
public static final byte[] QUIT = new byte[] { 1, 0, 0, 0, 1 };
public QuitCommandPacket(){
setCommand((byte) 0x01);
}
@Override
public void fromBytes(byte[] data) throws IOException {
}
@Override
public byte[] toBytes() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(getCommand());
out.write(QUIT);
return out.toByteArray();
}
}

View File

@@ -74,6 +74,8 @@ public class FieldPacket extends PacketWithHeaderPacket {
this.decimals = data[index]; this.decimals = data[index];
index++; index++;
// //
index += 2;// skip filter
//
if (index < data.length) { if (index < data.length) {
reader.setIndex(index); reader.setIndex(index);
this.definition = reader.readLengthCodedString(data); this.definition = reader.readLengthCodedString(data);

View File

@@ -0,0 +1,85 @@
package com.alibaba.otter.canal.parse.driver.mysql.socket;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import java.io.IOException;
import java.net.SocketAddress;
/**
* 封装netty的通信channel和数据接收缓存,实现读、写、连接校验的功能。 2016-12-28
*
* @author luoyaogui
*/
public class SocketChannel {
private Channel channel = null;
private Object lock = new Object();
private ByteBuf cache = PooledByteBufAllocator.DEFAULT.directBuffer(1024 * 1024); // 缓存大小
public Channel getChannel() {
return channel;
}
public void setChannel(Channel channel) {
this.channel = channel;
}
public void writeCache(ByteBuf buf) {
synchronized (lock) {
cache.discardReadBytes();// 回收内存
cache.writeBytes(buf);
}
}
public void writeChannel(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 {
do {
if (readSize > cache.readableBytes()) {
if (null == channel) {
throw new java.nio.channels.ClosedByInterruptException();
}
synchronized (this) {
try {
wait(100);
} catch (InterruptedException e) {
throw new java.nio.channels.ClosedByInterruptException();
}
}
} else {
byte[] back = new byte[readSize];
synchronized (lock) {
cache.readBytes(back);
}
return back;
}
} while (true);
}
public boolean isConnected() {
return channel != null ? true : false;
}
public SocketAddress getRemoteSocketAddress() {
return channel != null ? channel.remoteAddress() : null;
}
public void close() {
if (channel != null) {
channel.close();
}
channel = null;
cache.discardReadBytes();// 回收已占用的内存
cache.release();// 释放整个内存
cache = null;
}
}

View File

@@ -0,0 +1,103 @@
package com.alibaba.otter.canal.parse.driver.mysql.socket;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.AdaptiveRecvByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.ReferenceCountUtil;
import java.io.IOException;
import java.net.SocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.otter.canal.common.utils.BooleanMutex;
/**
* @author luoyaogui 实现channel的管理(监听连接、读数据、回收) 2016-12-28
*/
@SuppressWarnings({ "rawtypes", "deprecation" })
public abstract class SocketChannelPool {
private static EventLoopGroup group = new NioEventLoopGroup(); // 非阻塞IO线程组
private static Bootstrap boot = new Bootstrap(); // 主
private static Map<Channel, SocketChannel> chManager = new ConcurrentHashMap<Channel, SocketChannel>();
static {
boot.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_RCVBUF, 32 * 1024)
.option(ChannelOption.SO_SNDBUF, 32 * 1024)
.option(ChannelOption.TCP_NODELAY, true)
// 如果是延时敏感型应用,建议关闭Nagle算法
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
//
.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel arg0) throws Exception {
arg0.pipeline().addLast(new BusinessHandler());// 命令过滤和handler添加管理
}
});
}
public static SocketChannel open(SocketAddress address) throws Exception {
final SocketChannel socket = new SocketChannel();
final BooleanMutex mutex = new BooleanMutex(false);
boot.connect(address).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture arg0) throws Exception {
if (arg0.isSuccess()) {
socket.setChannel(arg0.channel());
}
mutex.set(true);
}
});
// wait for complete
mutex.get();
if (null == socket.getChannel()) {
throw new IOException("can't create socket!");
}
chManager.put(socket.getChannel(), socket);
return socket;
}
public static class BusinessHandler extends ChannelInboundHandlerAdapter {
private SocketChannel socket = null;
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
socket.setChannel(null);
chManager.remove(ctx.channel());// 移除
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (null == socket) socket = chManager.get(ctx.channel());
if (socket != null) {
socket.writeCache((ByteBuf) msg);
}
ReferenceCountUtil.release(msg);// 添加防止内存泄漏的
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
}

View File

@@ -1,69 +1,34 @@
package com.alibaba.otter.canal.parse.driver.mysql.utils; package com.alibaba.otter.canal.parse.driver.mysql.utils;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket;
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
public abstract class PacketManager { public abstract class PacketManager {
public static HeaderPacket readHeader(SocketChannel ch, int len) throws IOException { public static HeaderPacket readHeader(SocketChannel ch, int len) throws IOException {
HeaderPacket header = new HeaderPacket(); HeaderPacket header = new HeaderPacket();
header.fromBytes(readBytesAsBuffer(ch, len).array()); header.fromBytes(ch.read(len));
return header; return header;
} }
public static ByteBuffer readBytesAsBuffer(SocketChannel ch, int len) throws IOException {
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 byte[] readBytes(SocketChannel ch, int len) throws IOException { public static byte[] readBytes(SocketChannel ch, int len) throws IOException {
return readBytesAsBuffer(ch, len).array(); return ch.read(len);
} }
/** public static void writePkg(SocketChannel ch, byte[]... srcs) throws IOException {
* Since We r using blocking IO, so we will just write once and assert the ch.writeChannel(srcs);
* 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();
} }
ch.write(srcs); public static void writeBody(SocketChannel ch, byte[] body) throws IOException {
// https://github.com/alibaba/canal/issues/24 writeBody0(ch, body, (byte) 0);
// 部分windows用户会出现size != total的情况,jdk为java7/openjdk,估计和java版本有关,暂时不做检查
// long size = ch.write(srcs);
// if (size != total) {
// throw new IOException("unexpected blocking io behavior");
// }
} }
public static void write(SocketChannel ch, byte[] body) throws IOException { public static void writeBody0(SocketChannel ch, byte[] body, byte packetSeqNumber) throws IOException {
write(ch, body, (byte) 0);
}
public static void write(SocketChannel ch, byte[] body, byte packetSeqNumber) throws IOException {
HeaderPacket header = new HeaderPacket(); HeaderPacket header = new HeaderPacket();
header.setPacketBodyLength(body.length); header.setPacketBodyLength(body.length);
header.setPacketSequenceNumber(packetSeqNumber); header.setPacketSequenceNumber(packetSeqNumber);
write(ch, new ByteBuffer[] { ByteBuffer.wrap(header.toBytes()), ByteBuffer.wrap(body) }); ch.writeChannel(header.toBytes(), body);
} }
} }

View File

@@ -4,7 +4,6 @@ import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.20</version> <version>1.0.25-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

90
example/src/main/bin/init.sh Executable file
View File

@@ -0,0 +1,90 @@
#! /bin/bash
current_path=`pwd`
case "`uname`" in
Linux)
bin_abs_path=$(readlink -f $(dirname $0))
;;
*)
bin_abs_path=`cd $(dirname $0); pwd`
;;
esac
base=${bin_abs_path}/..
# default values
PORT=3306
MYSQL_IP="0"
# parse arguments
while getopts "h:p:" opt; do
case $opt in
p)
if [[ $OPTARG -gt 0 ]]; then
PORT=$OPTARG
else
echo "Invalid -p option: -$OPTARG, should be positive integer >&2"
exit 1
fi
;;
h)
MYSQL_IP=$OPTARG
;;
\?)
printUsage
exit 1
;;
esac
done
function printUsage
{
cat << __Usage
Initialise the instance config property file automatically. This tool will connect to Mysql master server and get the binlog file and position then update the instance config property file for you.
Usage: $0 -p [port] -h [hostname]
Options:
-p: mysql port, default to 3306
-h: mysql host ip.
For example:
$0 -p 3306 -h 172.17.0.2
Enjoy!!!
__Usage
}
if [[ $PORT -lt 0 ]]; then
echo "invalid port argument $PORT"
printUsage
exit 1
fi
# check if mysql ip is specified
if [[ $MYSQL_IP = "0" ]]; then
echo '-h mysql host ip argument is required.'
printUsage
exit 1
fi
MYSQL_ADDR="$MYSQL_IP:$PORT"
MASTER_STATUS=`mysql -ucanal -pcanal -h$MYSQL_IP -P$PORT -e 'show master status' 2>/dev/null | head -n 2 | tail -n 1`
LOG_FILE=`echo "$MASTER_STATUS" | cut -f 1`
LOG_POS=`echo "$MASTER_STATUS" | cut -f 2`
INSTANCE_CONF_FILE=$base/conf/example/instance.properties
if [ ! -f $INSTANCE_CONF_FILE ]; then
echo "Can not find instance config file: $INSTANCE_CONF_FILE"
exit 1
fi
# replace the binlog file and position
sed -i.bak -e "s/canal\.instance\.master\.address = .*/canal\.instance\.master\.address = $MYSQL_ADDR/" -e "s/canal\.instance\.master\.journal\.name = .*/canal\.instance\.master\.journal\.name = $LOG_FILE/" -e "s/canal\.instance\.master\.position = .*/canal\.instance\.master\.position = $LOG_POS/" $INSTANCE_CONF_FILE
cat $INSTANCE_CONF_FILE
echo
echo 'Above is your instance config file, check if the settings are correct'
echo

View File

@@ -1,7 +1,5 @@
package com.alibaba.otter.canal.example; package com.alibaba.otter.canal.example;
import org.apache.commons.lang.exception.ExceptionUtils;
import com.alibaba.otter.canal.client.CanalConnector; import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors; import com.alibaba.otter.canal.client.CanalConnectors;
@@ -41,7 +39,7 @@ public class ClusterCanalClientTest extends AbstractCanalClientTest {
logger.info("## stop the canal client"); logger.info("## stop the canal client");
clientTest.stop(); clientTest.stop();
} catch (Throwable e) { } catch (Throwable e) {
logger.warn("##something goes wrong when stopping canal:\n{}", ExceptionUtils.getFullStackTrace(e)); logger.warn("##something goes wrong when stopping canal:", e);
} finally { } finally {
logger.info("## canal client is down."); logger.info("## canal client is down.");
} }

View File

@@ -2,8 +2,6 @@ package com.alibaba.otter.canal.example;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import org.apache.commons.lang.exception.ExceptionUtils;
import com.alibaba.otter.canal.client.CanalConnector; import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors; import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.common.utils.AddressUtils; import com.alibaba.otter.canal.common.utils.AddressUtils;
@@ -23,8 +21,11 @@ public class SimpleCanalClientTest extends AbstractCanalClientTest {
public static void main(String args[]) { public static void main(String args[]) {
// 根据ip,直接创建链接,无HA的功能 // 根据ip,直接创建链接,无HA的功能
String destination = "example"; String destination = "example";
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(AddressUtils.getHostIp(), String ip = AddressUtils.getHostIp();
11111), destination, "", ""); CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(ip, 11111),
destination,
"",
"");
final SimpleCanalClientTest clientTest = new SimpleCanalClientTest(destination); final SimpleCanalClientTest clientTest = new SimpleCanalClientTest(destination);
clientTest.setConnector(connector); clientTest.setConnector(connector);
@@ -36,7 +37,7 @@ public class SimpleCanalClientTest extends AbstractCanalClientTest {
logger.info("## stop the canal client"); logger.info("## stop the canal client");
clientTest.stop(); clientTest.stop();
} catch (Throwable e) { } catch (Throwable e) {
logger.warn("##something goes wrong when stopping canal:\n{}", ExceptionUtils.getFullStackTrace(e)); logger.warn("##something goes wrong when stopping canal:", e);
} finally { } finally {
logger.info("## canal client is down."); logger.info("## canal client is down.");
} }

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.20</version> <version>1.0.25-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -1,7 +1,6 @@
package com.alibaba.otter.canal.filter; package com.alibaba.otter.canal.filter;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import com.alibaba.otter.canal.filter.aviater.AviaterELFilter; import com.alibaba.otter.canal.filter.aviater.AviaterELFilter;

View File

@@ -5,9 +5,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Assert;
import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.RandomStringUtils;
import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter; import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.20</version> <version>1.0.25-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<artifactId>canal.instance.core</artifactId> <artifactId>canal.instance.core</artifactId>

View File

@@ -0,0 +1,244 @@
package com.alibaba.otter.canal.instance.core;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
import com.alibaba.otter.canal.common.alarm.CanalAlarmHandler;
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
import com.alibaba.otter.canal.meta.CanalMetaManager;
import com.alibaba.otter.canal.parse.CanalEventParser;
import com.alibaba.otter.canal.parse.ha.CanalHAController;
import com.alibaba.otter.canal.parse.ha.HeartBeatHAController;
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
import com.alibaba.otter.canal.parse.inbound.group.GroupEventParser;
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.ClientIdentity;
import com.alibaba.otter.canal.sink.CanalEventSink;
import com.alibaba.otter.canal.store.CanalEventStore;
import com.alibaba.otter.canal.store.model.Event;
/**
* Created with Intellig IDEA. Author: yinxiu Date: 2016-01-07 Time: 22:26
*/
public class AbstractCanalInstance extends AbstractCanalLifeCycle implements CanalInstance {
private static final Logger logger = LoggerFactory.getLogger(AbstractCanalInstance.class);
protected Long canalId; // 和manager交互唯一标示
protected String destination; // 队列名字
protected CanalEventStore<Event> eventStore; // 有序队列
protected CanalEventParser eventParser; // 解析对应的数据信息
protected CanalEventSink<List<CanalEntry.Entry>> eventSink; // 链接parse和store的桥接器
protected CanalMetaManager metaManager; // 消费信息管理器
protected CanalAlarmHandler alarmHandler; // alarm报警机制
@Override
public boolean subscribeChange(ClientIdentity identity) {
if (StringUtils.isNotEmpty(identity.getFilter())) {
logger.info("subscribe filter change to " + identity.getFilter());
AviaterRegexFilter aviaterFilter = new AviaterRegexFilter(identity.getFilter());
boolean isGroup = (eventParser instanceof GroupEventParser);
if (isGroup) {
// 处理group的模式
List<CanalEventParser> eventParsers = ((GroupEventParser) eventParser).getEventParsers();
for (CanalEventParser singleEventParser : eventParsers) {// 需要遍历启动
((AbstractEventParser) singleEventParser).setEventFilter(aviaterFilter);
}
} else {
((AbstractEventParser) eventParser).setEventFilter(aviaterFilter);
}
}
// filter的处理规则
// a. parser处理数据过滤处理
// b. sink处理数据的路由&分发,一份parse数据经过sink后可以分发为多份,每份的数据可以根据自己的过滤规则不同而有不同的数据
// 后续内存版的一对多分发,可以考虑
return true;
}
@Override
public void start() {
super.start();
if (!metaManager.isStart()) {
metaManager.start();
}
if (!alarmHandler.isStart()) {
alarmHandler.start();
}
if (!eventStore.isStart()) {
eventStore.start();
}
if (!eventSink.isStart()) {
eventSink.start();
}
if (!eventParser.isStart()) {
beforeStartEventParser(eventParser);
eventParser.start();
afterStartEventParser(eventParser);
}
logger.info("start successful....");
}
@Override
public void stop() {
super.stop();
logger.info("stop CannalInstance for {}-{} ", new Object[] { canalId, destination });
if (eventParser.isStart()) {
beforeStopEventParser(eventParser);
eventParser.stop();
afterStopEventParser(eventParser);
}
if (eventSink.isStart()) {
eventSink.stop();
}
if (eventStore.isStart()) {
eventStore.stop();
}
if (metaManager.isStart()) {
metaManager.stop();
}
if (alarmHandler.isStart()) {
alarmHandler.stop();
}
logger.info("stop successful....");
}
protected void beforeStartEventParser(CanalEventParser eventParser) {
boolean isGroup = (eventParser instanceof GroupEventParser);
if (isGroup) {
// 处理group的模式
List<CanalEventParser> eventParsers = ((GroupEventParser) eventParser).getEventParsers();
for (CanalEventParser singleEventParser : eventParsers) {// 需要遍历启动
startEventParserInternal(singleEventParser, true);
}
} else {
startEventParserInternal(eventParser, false);
}
}
// around event parser, default impl
protected void afterStartEventParser(CanalEventParser eventParser) {
// 读取一下历史订阅的filter信息
List<ClientIdentity> clientIdentitys = metaManager.listAllSubscribeInfo(destination);
for (ClientIdentity clientIdentity : clientIdentitys) {
subscribeChange(clientIdentity);
}
}
// around event parser
protected void beforeStopEventParser(CanalEventParser eventParser) {
// noop
}
protected void afterStopEventParser(CanalEventParser eventParser) {
boolean isGroup = (eventParser instanceof GroupEventParser);
if (isGroup) {
// 处理group的模式
List<CanalEventParser> eventParsers = ((GroupEventParser) eventParser).getEventParsers();
for (CanalEventParser singleEventParser : eventParsers) {// 需要遍历启动
stopEventParserInternal(singleEventParser);
}
} else {
stopEventParserInternal(eventParser);
}
}
/**
* 初始化单个eventParser,不需要考虑group
*/
protected void startEventParserInternal(CanalEventParser eventParser, boolean isGroup) {
if (eventParser instanceof AbstractEventParser) {
AbstractEventParser abstractEventParser = (AbstractEventParser) eventParser;
// 首先启动log position管理器
CanalLogPositionManager logPositionManager = abstractEventParser.getLogPositionManager();
if (!logPositionManager.isStart()) {
logPositionManager.start();
}
}
if (eventParser instanceof MysqlEventParser) {
MysqlEventParser mysqlEventParser = (MysqlEventParser) eventParser;
CanalHAController haController = mysqlEventParser.getHaController();
if (haController instanceof HeartBeatHAController) {
((HeartBeatHAController) haController).setCanalHASwitchable(mysqlEventParser);
}
if (!haController.isStart()) {
haController.start();
}
}
}
protected void stopEventParserInternal(CanalEventParser eventParser) {
if (eventParser instanceof AbstractEventParser) {
AbstractEventParser abstractEventParser = (AbstractEventParser) eventParser;
// 首先启动log position管理器
CanalLogPositionManager logPositionManager = abstractEventParser.getLogPositionManager();
if (logPositionManager.isStart()) {
logPositionManager.stop();
}
}
if (eventParser instanceof MysqlEventParser) {
MysqlEventParser mysqlEventParser = (MysqlEventParser) eventParser;
CanalHAController haController = mysqlEventParser.getHaController();
if (haController.isStart()) {
haController.stop();
}
}
}
// ==================getter==================================
@Override
public String getDestination() {
return destination;
}
@Override
public CanalEventParser getEventParser() {
return eventParser;
}
@Override
public CanalEventSink getEventSink() {
return eventSink;
}
@Override
public CanalEventStore getEventStore() {
return eventStore;
}
@Override
public CanalMetaManager getMetaManager() {
return metaManager;
}
@Override
public CanalAlarmHandler getAlarmHandler() {
return alarmHandler;
}
}

View File

@@ -1,105 +0,0 @@
package com.alibaba.otter.canal.instance.core;
import java.util.List;
import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
import com.alibaba.otter.canal.parse.CanalEventParser;
import com.alibaba.otter.canal.parse.ha.CanalHAController;
import com.alibaba.otter.canal.parse.ha.HeartBeatHAController;
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
import com.alibaba.otter.canal.parse.inbound.group.GroupEventParser;
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
/**
* @author zebin.xuzb 2012-10-17 下午3:12:34
* @version 1.0.0
*/
public abstract class CanalInstanceSupport extends AbstractCanalLifeCycle {
protected void beforeStartEventParser(CanalEventParser eventParser) {
boolean isGroup = (eventParser instanceof GroupEventParser);
if (isGroup) {
// 处理group的模式
List<CanalEventParser> eventParsers = ((GroupEventParser) eventParser).getEventParsers();
for (CanalEventParser singleEventParser : eventParsers) {// 需要遍历启动
startEventParserInternal(singleEventParser, true);
}
} else {
startEventParserInternal(eventParser, false);
}
}
// around event parser
protected void afterStartEventParser(CanalEventParser eventParser) {
// noop
}
// around event parser
protected void beforeStopEventParser(CanalEventParser eventParser) {
// noop
}
protected void afterStopEventParser(CanalEventParser eventParser) {
boolean isGroup = (eventParser instanceof GroupEventParser);
if (isGroup) {
// 处理group的模式
List<CanalEventParser> eventParsers = ((GroupEventParser) eventParser).getEventParsers();
for (CanalEventParser singleEventParser : eventParsers) {// 需要遍历启动
stopEventParserInternal(singleEventParser);
}
} else {
stopEventParserInternal(eventParser);
}
}
/**
* 初始化单个eventParser,不需要考虑group
*/
protected void startEventParserInternal(CanalEventParser eventParser, boolean isGroup) {
if (eventParser instanceof AbstractEventParser) {
AbstractEventParser abstractEventParser = (AbstractEventParser) eventParser;
// 首先启动log position管理器
CanalLogPositionManager logPositionManager = abstractEventParser.getLogPositionManager();
if (!logPositionManager.isStart()) {
logPositionManager.start();
}
}
if (eventParser instanceof MysqlEventParser) {
MysqlEventParser mysqlEventParser = (MysqlEventParser) eventParser;
CanalHAController haController = mysqlEventParser.getHaController();
if (haController instanceof HeartBeatHAController) {
((HeartBeatHAController) haController).setCanalHASwitchable(mysqlEventParser);
}
if (!haController.isStart()) {
haController.start();
}
}
}
protected void stopEventParserInternal(CanalEventParser eventParser) {
if (eventParser instanceof AbstractEventParser) {
AbstractEventParser abstractEventParser = (AbstractEventParser) eventParser;
// 首先启动log position管理器
CanalLogPositionManager logPositionManager = abstractEventParser.getLogPositionManager();
if (logPositionManager.isStart()) {
logPositionManager.stop();
}
}
if (eventParser instanceof MysqlEventParser) {
MysqlEventParser mysqlEventParser = (MysqlEventParser) eventParser;
CanalHAController haController = mysqlEventParser.getHaController();
if (haController.isStart()) {
haController.stop();
}
}
}
}

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.20</version> <version>1.0.25-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -17,8 +17,7 @@ import com.alibaba.otter.canal.common.alarm.LogAlarmHandler;
import com.alibaba.otter.canal.common.utils.JsonUtils; import com.alibaba.otter.canal.common.utils.JsonUtils;
import com.alibaba.otter.canal.common.zookeeper.ZkClientx; import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter; import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
import com.alibaba.otter.canal.instance.core.CanalInstance; import com.alibaba.otter.canal.instance.core.AbstractCanalInstance;
import com.alibaba.otter.canal.instance.core.CanalInstanceSupport;
import com.alibaba.otter.canal.instance.manager.model.Canal; import com.alibaba.otter.canal.instance.manager.model.Canal;
import com.alibaba.otter.canal.instance.manager.model.CanalParameter; import com.alibaba.otter.canal.instance.manager.model.CanalParameter;
import com.alibaba.otter.canal.instance.manager.model.CanalParameter.DataSourcing; import com.alibaba.otter.canal.instance.manager.model.CanalParameter.DataSourcing;
@@ -28,7 +27,6 @@ import com.alibaba.otter.canal.instance.manager.model.CanalParameter.MetaMode;
import com.alibaba.otter.canal.instance.manager.model.CanalParameter.SourcingType; import com.alibaba.otter.canal.instance.manager.model.CanalParameter.SourcingType;
import com.alibaba.otter.canal.instance.manager.model.CanalParameter.StorageMode; import com.alibaba.otter.canal.instance.manager.model.CanalParameter.StorageMode;
import com.alibaba.otter.canal.instance.manager.model.CanalParameter.StorageScavengeMode; import com.alibaba.otter.canal.instance.manager.model.CanalParameter.StorageScavengeMode;
import com.alibaba.otter.canal.meta.CanalMetaManager;
import com.alibaba.otter.canal.meta.MemoryMetaManager; import com.alibaba.otter.canal.meta.MemoryMetaManager;
import com.alibaba.otter.canal.meta.PeriodMixedMetaManager; import com.alibaba.otter.canal.meta.PeriodMixedMetaManager;
import com.alibaba.otter.canal.meta.ZooKeeperMetaManager; import com.alibaba.otter.canal.meta.ZooKeeperMetaManager;
@@ -46,17 +44,12 @@ import com.alibaba.otter.canal.parse.index.MetaLogPositionManager;
import com.alibaba.otter.canal.parse.index.PeriodMixedLogPositionManager; import com.alibaba.otter.canal.parse.index.PeriodMixedLogPositionManager;
import com.alibaba.otter.canal.parse.index.ZooKeeperLogPositionManager; import com.alibaba.otter.canal.parse.index.ZooKeeperLogPositionManager;
import com.alibaba.otter.canal.parse.support.AuthenticationInfo; import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
import com.alibaba.otter.canal.protocol.ClientIdentity;
import com.alibaba.otter.canal.protocol.position.EntryPosition; import com.alibaba.otter.canal.protocol.position.EntryPosition;
import com.alibaba.otter.canal.sink.CanalEventSink;
import com.alibaba.otter.canal.sink.entry.EntryEventSink; import com.alibaba.otter.canal.sink.entry.EntryEventSink;
import com.alibaba.otter.canal.sink.entry.group.GroupEventSink; import com.alibaba.otter.canal.sink.entry.group.GroupEventSink;
import com.alibaba.otter.canal.store.AbstractCanalStoreScavenge; import com.alibaba.otter.canal.store.AbstractCanalStoreScavenge;
import com.alibaba.otter.canal.store.CanalEventStore;
import com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer; import com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer;
import com.alibaba.otter.canal.store.model.BatchMode; import com.alibaba.otter.canal.store.model.BatchMode;
import com.alibaba.otter.canal.store.model.Event;
/** /**
* 单个canal实例,比如一个destination会独立一个实例 * 单个canal实例,比如一个destination会独立一个实例
@@ -64,24 +57,11 @@ import com.alibaba.otter.canal.store.model.Event;
* @author jianghang 2012-7-11 下午09:26:51 * @author jianghang 2012-7-11 下午09:26:51
* @version 1.0.0 * @version 1.0.0
*/ */
public class CanalInstanceWithManager extends CanalInstanceSupport implements CanalInstance { public class CanalInstanceWithManager extends AbstractCanalInstance {
private static final Logger logger = LoggerFactory.getLogger(CanalInstanceWithManager.class); private static final Logger logger = LoggerFactory.getLogger(CanalInstanceWithManager.class);
protected Long canalId; // 和manager交互唯一标示
protected String destination; // 队列名字
protected String filter; // 过滤表达式 protected String filter; // 过滤表达式
protected CanalParameter parameters; // 对应参数 protected CanalParameter parameters; // 对应参数
protected CanalMetaManager metaManager; // 消费信息管理器
protected CanalEventStore<Event> eventStore; // 有序队列
protected CanalEventParser eventParser; // 解析对应的数据信息
protected CanalEventSink<List<Entry>> eventSink; // 链接parse和store的桥接器
protected CanalAlarmHandler alarmHandler; // alarm报警机制
protected ZkClientx zkClientx;
public CanalInstanceWithManager(Canal canal){
this(canal, null);
}
public CanalInstanceWithManager(Canal canal, String filter){ public CanalInstanceWithManager(Canal canal, String filter){
this.parameters = canal.getCanalParameter(); this.parameters = canal.getCanalParameter();
@@ -89,7 +69,7 @@ public class CanalInstanceWithManager extends CanalInstanceSupport implements Ca
this.destination = canal.getName(); this.destination = canal.getName();
this.filter = filter; this.filter = filter;
logger.info("init CannalInstance for {}-{} with parameters:{}", canalId, destination, parameters); logger.info("init CanalInstance for {}-{} with parameters:{}", canalId, destination, parameters);
// 初始化报警机制 // 初始化报警机制
initAlarmHandler(); initAlarmHandler();
// 初始化metaManager // 初始化metaManager
@@ -101,7 +81,7 @@ public class CanalInstanceWithManager extends CanalInstanceSupport implements Ca
// 初始化eventParser; // 初始化eventParser;
initEventParser(); initEventParser();
// 基础工具,需要提前start,会有先订阅再根据filter条件启动paser的需求 // 基础工具,需要提前start,会有先订阅再根据filter条件启动parse的需求
if (!alarmHandler.isStart()) { if (!alarmHandler.isStart()) {
alarmHandler.start(); alarmHandler.start();
} }
@@ -113,98 +93,9 @@ public class CanalInstanceWithManager extends CanalInstanceSupport implements Ca
} }
public void start() { public void start() {
super.start();
// 初始化metaManager // 初始化metaManager
logger.info("start CannalInstance for {}-{} with parameters:{}", canalId, destination, parameters); logger.info("start CannalInstance for {}-{} with parameters:{}", canalId, destination, parameters);
super.start();
if (!metaManager.isStart()) {
metaManager.start();
}
if (!alarmHandler.isStart()) {
alarmHandler.start();
}
if (!eventStore.isStart()) {
eventStore.start();
}
if (!eventSink.isStart()) {
eventSink.start();
}
if (!eventParser.isStart()) {
beforeStartEventParser(eventParser);
eventParser.start();
}
logger.info("start successful....");
}
public void stop() {
logger.info("stop CannalInstance for {}-{} ", new Object[] { canalId, destination });
if (eventParser.isStart()) {
eventParser.stop();
afterStopEventParser(eventParser);
}
if (eventSink.isStart()) {
eventSink.stop();
}
if (eventStore.isStart()) {
eventStore.stop();
}
if (metaManager.isStart()) {
metaManager.stop();
}
if (alarmHandler.isStart()) {
alarmHandler.stop();
}
// if (zkClientx != null) {
// zkClientx.close();
// }
super.stop();
logger.info("stop successful....");
}
public boolean subscribeChange(ClientIdentity identity) {
if (StringUtils.isNotEmpty(identity.getFilter())) {
AviaterRegexFilter aviaterFilter = new AviaterRegexFilter(identity.getFilter());
boolean isGroup = (eventParser instanceof GroupEventParser);
if (isGroup) {
// 处理group的模式
List<CanalEventParser> eventParsers = ((GroupEventParser) eventParser).getEventParsers();
for (CanalEventParser singleEventParser : eventParsers) {// 需要遍历启动
((AbstractEventParser) singleEventParser).setEventFilter(aviaterFilter);
}
} else {
((AbstractEventParser) eventParser).setEventFilter(aviaterFilter);
}
}
// filter的处理规则
// a. parser处理数据过滤处理
// b. sink处理数据的路由&分发,一份parse数据经过sink后可以分发为多份,每份的数据可以根据自己的过滤规则不同而有不同的数据
// 后续内存版的一对多分发,可以考虑
return true;
}
protected void afterStartEventParser(CanalEventParser eventParser) {
super.afterStartEventParser(eventParser);
// 读取一下历史订阅的filter信息
List<ClientIdentity> clientIdentitys = metaManager.listAllSubscribeInfo(destination);
for (ClientIdentity clientIdentity : clientIdentitys) {
subscribeChange(clientIdentity);
}
} }
protected void initAlarmHandler() { protected void initAlarmHandler() {
@@ -455,29 +346,24 @@ public class CanalInstanceWithManager extends CanalInstanceSupport implements Ca
protected CanalLogPositionManager initLogPositionManager() { protected CanalLogPositionManager initLogPositionManager() {
logger.info("init logPositionPersistManager begin..."); logger.info("init logPositionPersistManager begin...");
IndexMode indexMode = parameters.getIndexMode(); IndexMode indexMode = parameters.getIndexMode();
CanalLogPositionManager logPositionManager = null; CanalLogPositionManager logPositionManager;
if (indexMode.isMemory()) { if (indexMode.isMemory()) {
logPositionManager = new MemoryLogPositionManager(); logPositionManager = new MemoryLogPositionManager();
} else if (indexMode.isZookeeper()) { } else if (indexMode.isZookeeper()) {
logPositionManager = new ZooKeeperLogPositionManager(); logPositionManager = new ZooKeeperLogPositionManager(getZkclientx());
((ZooKeeperLogPositionManager) logPositionManager).setZkClientx(getZkclientx());
} else if (indexMode.isMixed()) { } else if (indexMode.isMixed()) {
logPositionManager = new PeriodMixedLogPositionManager(); MemoryLogPositionManager memoryLogPositionManager = new MemoryLogPositionManager();
ZooKeeperLogPositionManager zooKeeperLogPositionManager = new ZooKeeperLogPositionManager(getZkclientx());
ZooKeeperLogPositionManager zooKeeperLogPositionManager = new ZooKeeperLogPositionManager(); logPositionManager = new PeriodMixedLogPositionManager(memoryLogPositionManager,
zooKeeperLogPositionManager.setZkClientx(getZkclientx()); zooKeeperLogPositionManager,
((PeriodMixedLogPositionManager) logPositionManager).setZooKeeperLogPositionManager(zooKeeperLogPositionManager); 1000L);
} else if (indexMode.isMeta()) { } else if (indexMode.isMeta()) {
logPositionManager = new MetaLogPositionManager(); logPositionManager = new MetaLogPositionManager(metaManager);
((MetaLogPositionManager) logPositionManager).setMetaManager(metaManager);
} else if (indexMode.isMemoryMetaFailback()) { } else if (indexMode.isMemoryMetaFailback()) {
MemoryLogPositionManager primaryLogPositionManager = new MemoryLogPositionManager(); MemoryLogPositionManager primary = new MemoryLogPositionManager();
MetaLogPositionManager failbackLogPositionManager = new MetaLogPositionManager(); MetaLogPositionManager secondary = new MetaLogPositionManager(metaManager);
failbackLogPositionManager.setMetaManager(metaManager);
logPositionManager = new FailbackLogPositionManager(); logPositionManager = new FailbackLogPositionManager(primary, secondary);
((FailbackLogPositionManager) logPositionManager).setPrimary(primaryLogPositionManager);
((FailbackLogPositionManager) logPositionManager).setFailback(failbackLogPositionManager);
} else { } else {
throw new CanalException("unsupport indexMode for " + indexMode); throw new CanalException("unsupport indexMode for " + indexMode);
} }
@@ -515,32 +401,6 @@ public class CanalInstanceWithManager extends CanalInstanceSupport implements Ca
return ZkClientx.getZkClient(StringUtils.join(zkClusters, ";")); return ZkClientx.getZkClient(StringUtils.join(zkClusters, ";"));
} }
// =====================================
public String getDestination() {
return destination;
}
public CanalMetaManager getMetaManager() {
return metaManager;
}
public CanalEventStore<Event> getEventStore() {
return eventStore;
}
public CanalEventParser getEventParser() {
return eventParser;
}
public CanalEventSink<List<Entry>> getEventSink() {
return eventSink;
}
public CanalAlarmHandler getAlarmHandler() {
return alarmHandler;
}
public void setAlarmHandler(CanalAlarmHandler alarmHandler) { public void setAlarmHandler(CanalAlarmHandler alarmHandler) {
this.alarmHandler = alarmHandler; this.alarmHandler = alarmHandler;
} }

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.20</version> <version>1.0.25-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.20</version> <version>1.0.25-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -2,20 +2,14 @@ package com.alibaba.otter.canal.instance.spring;
import java.util.List; import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.common.alarm.CanalAlarmHandler; import com.alibaba.otter.canal.common.alarm.CanalAlarmHandler;
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter; import com.alibaba.otter.canal.instance.core.AbstractCanalInstance;
import com.alibaba.otter.canal.instance.core.CanalInstance;
import com.alibaba.otter.canal.instance.core.CanalInstanceSupport;
import com.alibaba.otter.canal.meta.CanalMetaManager; import com.alibaba.otter.canal.meta.CanalMetaManager;
import com.alibaba.otter.canal.parse.CanalEventParser; import com.alibaba.otter.canal.parse.CanalEventParser;
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
import com.alibaba.otter.canal.parse.inbound.group.GroupEventParser;
import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.ClientIdentity;
import com.alibaba.otter.canal.sink.CanalEventSink; import com.alibaba.otter.canal.sink.CanalEventSink;
import com.alibaba.otter.canal.store.CanalEventStore; import com.alibaba.otter.canal.store.CanalEventStore;
import com.alibaba.otter.canal.store.model.Event; import com.alibaba.otter.canal.store.model.Event;
@@ -27,130 +21,13 @@ import com.alibaba.otter.canal.store.model.Event;
* @author zebin.xuzb * @author zebin.xuzb
* @version 1.0.0 * @version 1.0.0
*/ */
public class CanalInstanceWithSpring extends CanalInstanceSupport implements CanalInstance { public class CanalInstanceWithSpring extends AbstractCanalInstance {
private static final Logger logger = LoggerFactory.getLogger(CanalInstanceWithSpring.class); private static final Logger logger = LoggerFactory.getLogger(CanalInstanceWithSpring.class);
private String destination;
private CanalEventParser eventParser;
private CanalEventSink<List<CanalEntry.Entry>> eventSink;
private CanalEventStore<Event> eventStore;
private CanalMetaManager metaManager;
private CanalAlarmHandler alarmHandler;
public String getDestination() {
return this.destination;
}
public CanalEventParser getEventParser() {
return this.eventParser;
}
public CanalEventSink<List<CanalEntry.Entry>> getEventSink() {
return this.eventSink;
}
public CanalEventStore<Event> getEventStore() {
return this.eventStore;
}
public CanalMetaManager getMetaManager() {
return this.metaManager;
}
public CanalAlarmHandler getAlarmHandler() {
return alarmHandler;
}
public boolean subscribeChange(ClientIdentity identity) {
if (StringUtils.isNotEmpty(identity.getFilter())) {
logger.info("subscribe filter change to " + identity.getFilter());
AviaterRegexFilter aviaterFilter = new AviaterRegexFilter(identity.getFilter());
boolean isGroup = (eventParser instanceof GroupEventParser);
if (isGroup) {
// 处理group的模式
List<CanalEventParser> eventParsers = ((GroupEventParser) eventParser).getEventParsers();
for (CanalEventParser singleEventParser : eventParsers) {// 需要遍历启动
((AbstractEventParser) singleEventParser).setEventFilter(aviaterFilter);
}
} else {
((AbstractEventParser) eventParser).setEventFilter(aviaterFilter);
}
}
// filter的处理规则
// a. parser处理数据过滤处理
// b. sink处理数据的路由&分发,一份parse数据经过sink后可以分发为多份,每份的数据可以根据自己的过滤规则不同而有不同的数据
// 后续内存版的一对多分发,可以考虑
return true;
}
protected void afterStartEventParser(CanalEventParser eventParser) {
super.afterStartEventParser(eventParser);
// 读取一下历史订阅的filter信息
List<ClientIdentity> clientIdentitys = metaManager.listAllSubscribeInfo(destination);
for (ClientIdentity clientIdentity : clientIdentitys) {
subscribeChange(clientIdentity);
}
}
public void start() { public void start() {
super.start();
logger.info("start CannalInstance for {}-{} ", new Object[] { 1, destination }); logger.info("start CannalInstance for {}-{} ", new Object[] { 1, destination });
if (!metaManager.isStart()) { super.start();
metaManager.start();
}
if (!eventStore.isStart()) {
eventStore.start();
}
if (!eventSink.isStart()) {
eventSink.start();
}
if (!eventParser.isStart()) {
beforeStartEventParser(eventParser);
eventParser.start();
afterStartEventParser(eventParser);
}
logger.info("start successful....");
}
public void stop() {
logger.info("stop CannalInstance for {}-{} ", new Object[] { 1, destination });
if (eventParser.isStart()) {
beforeStopEventParser(eventParser);
eventParser.stop();
afterStopEventParser(eventParser);
}
if (eventSink.isStart()) {
eventSink.stop();
}
if (eventStore.isStart()) {
eventStore.stop();
}
if (metaManager.isStart()) {
metaManager.stop();
}
if (alarmHandler.isStart()) {
alarmHandler.stop();
}
// if (zkClientx != null) {
// zkClientx.close();
// }
super.stop();
logger.info("stop successful....");
} }
// ======== setter ======== // ======== setter ========

View File

@@ -120,14 +120,14 @@
<!-- 解析位点记录 --> <!-- 解析位点记录 -->
<property name="logPositionManager"> <property name="logPositionManager">
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager"> <bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
<property name="primary"> <constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" /> <bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
</property> </constructor-arg>
<property name="failback"> <constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager"> <bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
<property name="metaManager" ref="metaManager" /> <constructor-arg ref="metaManager"/>
</bean> </bean>
</property> </constructor-arg>
</bean> </bean>
</property> </property>

View File

@@ -106,14 +106,16 @@
<!-- 解析位点记录 --> <!-- 解析位点记录 -->
<property name="logPositionManager"> <property name="logPositionManager">
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager"> <bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
<property name="primary"> <constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" /> <bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
</property> </constructor-arg>
<property name="failback"> <constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager"> <bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
<property name="metaManager" ref="metaManager" /> <constructor-arg>
<ref bean="metaManager"/>
</constructor-arg>
</bean> </bean>
</property> </constructor-arg>
</bean> </bean>
</property> </property>

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.20</version> <version>1.0.25-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>

View File

@@ -367,7 +367,7 @@ public class FileMixedMetaManager extends MemoryMetaManager implements CanalMeta
this.dataDir = new File(dataDir); this.dataDir = new File(dataDir);
} }
public void setDataDir(File dataDir) { public void setDataDirByFile(File dataDir) {
this.dataDir = dataDir; this.dataDir = dataDir;
} }

View File

@@ -142,8 +142,8 @@ public class PeriodMixedMetaManager extends MemoryMetaManager implements CanalMe
} }
public void updateCursor(ClientIdentity clientIdentity, Position position) throws CanalMetaManagerException { public void updateCursor(ClientIdentity clientIdentity, Position position) throws CanalMetaManagerException {
updateCursorTasks.add(clientIdentity);// 添加到任务队列中进行触发
super.updateCursor(clientIdentity, position); super.updateCursor(clientIdentity, position);
updateCursorTasks.add(clientIdentity);// 添加到任务队列中进行触发
} }
public Position getCursor(ClientIdentity clientIdentity) throws CanalMetaManagerException { public Position getCursor(ClientIdentity clientIdentity) throws CanalMetaManagerException {

View File

@@ -5,9 +5,8 @@ import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.junit.Assert;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -32,7 +31,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
@Test @Test
public void testSubscribeAll() { public void testSubscribeAll() {
FileMixedMetaManager metaManager = new FileMixedMetaManager(); FileMixedMetaManager metaManager = new FileMixedMetaManager();
metaManager.setDataDir(dataDir); metaManager.setDataDirByFile(dataDir);
metaManager.setPeriod(100); metaManager.setPeriod(100);
metaManager.start(); metaManager.start();
@@ -41,7 +40,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
sleep(2000L); sleep(2000L);
// 重新构建一次,能获得上一次zk上的记录 // 重新构建一次,能获得上一次zk上的记录
FileMixedMetaManager metaManager2 = new FileMixedMetaManager(); FileMixedMetaManager metaManager2 = new FileMixedMetaManager();
metaManager2.setDataDir(dataDir); metaManager2.setDataDirByFile(dataDir);
metaManager2.setPeriod(100); metaManager2.setPeriod(100);
metaManager2.start(); metaManager2.start();
@@ -53,7 +52,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
@Test @Test
public void testBatchAll() { public void testBatchAll() {
FileMixedMetaManager metaManager = new FileMixedMetaManager(); FileMixedMetaManager metaManager = new FileMixedMetaManager();
metaManager.setDataDir(dataDir); metaManager.setDataDirByFile(dataDir);
metaManager.setPeriod(100); metaManager.setPeriod(100);
metaManager.start(); metaManager.start();
@@ -68,7 +67,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
@Test @Test
public void testCursorAll() { public void testCursorAll() {
FileMixedMetaManager metaManager = new FileMixedMetaManager(); FileMixedMetaManager metaManager = new FileMixedMetaManager();
metaManager.setDataDir(dataDir); metaManager.setDataDirByFile(dataDir);
metaManager.setPeriod(100); metaManager.setPeriod(100);
metaManager.start(); metaManager.start();
@@ -77,7 +76,7 @@ public class FileMixedMetaManagerTest extends AbstractMetaManagerTest {
sleep(1000L); sleep(1000L);
// 重新构建一次,能获得上一次zk上的记录 // 重新构建一次,能获得上一次zk上的记录
FileMixedMetaManager metaManager2 = new FileMixedMetaManager(); FileMixedMetaManager metaManager2 = new FileMixedMetaManager();
metaManager2.setDataDir(dataDir); metaManager2.setDataDirByFile(dataDir);
metaManager2.setPeriod(100); metaManager2.setPeriod(100);
metaManager2.start(); metaManager2.start();

View File

@@ -3,7 +3,6 @@ package com.alibaba.otter.canal.meta;
import java.util.Map; import java.util.Map;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import com.alibaba.otter.canal.protocol.position.PositionRange; import com.alibaba.otter.canal.protocol.position.PositionRange;

View File

@@ -3,9 +3,8 @@ package com.alibaba.otter.canal.meta;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.junit.Assert;
import org.junit.After; import org.junit.After;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

View File

@@ -3,9 +3,8 @@ package com.alibaba.otter.canal.meta;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.junit.Assert;
import org.junit.After; import org.junit.After;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

View File

@@ -2,9 +2,8 @@ package com.alibaba.otter.canal.meta;
import java.util.Map; import java.util.Map;
import org.junit.Assert;
import org.junit.After; import org.junit.After;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

View File

@@ -1,9 +1,10 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>com.alibaba.otter</groupId> <groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId> <artifactId>canal</artifactId>
<version>1.0.20</version> <version>1.0.25-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<artifactId>canal.parse</artifactId> <artifactId>canal.parse</artifactId>
@@ -45,11 +46,41 @@
<artifactId>canal.parse.driver</artifactId> <artifactId>canal.parse.driver</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.ibatis</groupId>
<artifactId>ibatis-sqlmap</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
</dependency>
<!-- test dependency --> <!-- test dependency -->
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -22,7 +22,6 @@ import com.alibaba.otter.canal.parse.CanalEventParser;
import com.alibaba.otter.canal.parse.exception.CanalParseException; import com.alibaba.otter.canal.parse.exception.CanalParseException;
import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException; import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer.TransactionFlushCallback; import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer.TransactionFlushCallback;
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager; import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
import com.alibaba.otter.canal.parse.support.AuthenticationInfo; import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.CanalEntry;
@@ -97,6 +96,10 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
protected void preDump(ErosaConnection connection) { protected void preDump(ErosaConnection connection) {
} }
protected boolean processTableMeta(EntryPosition position) {
return true;
}
protected void afterDump(ErosaConnection connection) { protected void afterDump(ErosaConnection connection) {
} }
@@ -120,7 +123,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
throw new CanalParseException("consume failed!"); throw new CanalParseException("consume failed!");
} }
LogPosition position = buildLastTranasctionPosition(transaction); LogPosition position = buildLastTransactionPosition(transaction);
if (position != null) { // 可能position为空 if (position != null) { // 可能position为空
logPositionManager.persistLogPosition(AbstractEventParser.this.destination, position); logPositionManager.persistLogPosition(AbstractEventParser.this.destination, position);
} }
@@ -146,7 +149,6 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
ErosaConnection erosaConnection = null; ErosaConnection erosaConnection = null;
while (running) { while (running) {
try { try {
// 开始执行replication // 开始执行replication
// 1. 构造Erosa连接 // 1. 构造Erosa连接
erosaConnection = buildErosaConnection(); erosaConnection = buildErosaConnection();
@@ -159,10 +161,16 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
erosaConnection.connect();// 链接 erosaConnection.connect();// 链接
// 4. 获取最后的位置信息 // 4. 获取最后的位置信息
final EntryPosition startPosition = findStartPosition(erosaConnection); EntryPosition position = findStartPosition(erosaConnection);
final EntryPosition startPosition = position;
if (startPosition == null) { if (startPosition == null) {
throw new CanalParseException("can't find start position for " + destination); throw new CanalParseException("can't find start position for " + destination);
} }
if (!processTableMeta(startPosition)) {
throw new CanalParseException("can't find init table meta for " + destination
+ " with position : " + startPosition);
}
logger.info("find start position : {}", startPosition.toString()); logger.info("find start position : {}", startPosition.toString());
// 重新链接,因为在找position过程中可能有状态,需要断开后重建 // 重新链接,因为在找position过程中可能有状态,需要断开后重建
erosaConnection.reconnect(); erosaConnection.reconnect();
@@ -173,7 +181,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
public boolean sink(EVENT event) { public boolean sink(EVENT event) {
try { try {
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event); CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, false);
if (!running) { if (!running) {
return false; return false;
@@ -190,9 +198,12 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
return running; return running;
} catch (TableIdNotFoundException e) { } catch (TableIdNotFoundException e) {
throw e; throw e;
} catch (Exception e) { } catch (Throwable e) {
if (e.getCause() instanceof TableIdNotFoundException) {
throw (TableIdNotFoundException) e.getCause();
}
// 记录一下,出错的位点信息 // 记录一下,出错的位点信息
processError(e, processSinkError(e,
this.lastPosition, this.lastPosition,
startPosition.getJournalName(), startPosition.getJournalName(),
startPosition.getPosition()); startPosition.getPosition());
@@ -219,6 +230,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
logger.error(String.format("dump address %s has an error, retrying. caused by ", logger.error(String.format("dump address %s has an error, retrying. caused by ",
runningInfo.getAddress().toString()), e); runningInfo.getAddress().toString()), e);
} catch (Throwable e) { } catch (Throwable e) {
processDumpError(e);
exception = e; exception = e;
if (!running) { if (!running) {
if (!(e instanceof java.nio.channels.ClosedByInterruptException || e.getCause() instanceof java.nio.channels.ClosedByInterruptException)) { if (!(e instanceof java.nio.channels.ClosedByInterruptException || e.getCause() instanceof java.nio.channels.ClosedByInterruptException)) {
@@ -231,6 +243,8 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
sendAlarm(destination, ExceptionUtils.getFullStackTrace(e)); sendAlarm(destination, ExceptionUtils.getFullStackTrace(e));
} }
} finally { } finally {
// 重新置为中断状态
Thread.interrupted();
// 关闭一下链接 // 关闭一下链接
afterDump(erosaConnection); afterDump(erosaConnection);
try { try {
@@ -269,7 +283,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
parseThread.setUncaughtExceptionHandler(handler); parseThread.setUncaughtExceptionHandler(handler);
parseThread.setName(String.format("destination = %s , address = %s , EventParser", parseThread.setName(String.format("destination = %s , address = %s , EventParser",
destination, destination,
runningInfo == null ? null : runningInfo.getAddress().toString())); runningInfo == null ? null : runningInfo.getAddress()));
parseThread.start(); parseThread.start();
} }
@@ -314,14 +328,13 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
return result; return result;
} }
protected CanalEntry.Entry parseAndProfilingIfNecessary(EVENT bod) throws Exception { protected CanalEntry.Entry parseAndProfilingIfNecessary(EVENT bod, boolean isSeek) throws Exception {
long startTs = -1; long startTs = -1;
boolean enabled = getProfilingEnabled(); boolean enabled = getProfilingEnabled();
if (enabled) { if (enabled) {
startTs = System.currentTimeMillis(); startTs = System.currentTimeMillis();
} }
CanalEntry.Entry event = binlogParser.parse(bod); CanalEntry.Entry event = binlogParser.parse(bod, isSeek);
if (enabled) { if (enabled) {
this.parsingInterval = System.currentTimeMillis() - startTs; this.parsingInterval = System.currentTimeMillis() - startTs;
} }
@@ -336,20 +349,11 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
return profilingEnabled.get(); return profilingEnabled.get();
} }
protected LogPosition buildLastTranasctionPosition(List<CanalEntry.Entry> entries) { // 初始化一下 protected LogPosition buildLastTransactionPosition(List<CanalEntry.Entry> entries) { // 初始化一下
for (int i = entries.size() - 1; i > 0; i--) { for (int i = entries.size() - 1; i > 0; i--) {
CanalEntry.Entry entry = entries.get(i); CanalEntry.Entry entry = entries.get(i);
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {// 尽量记录一个事务做为position if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {// 尽量记录一个事务做为position
LogPosition logPosition = new LogPosition(); return buildLastPosition(entry);
EntryPosition position = new EntryPosition();
position.setJournalName(entry.getHeader().getLogfileName());
position.setPosition(entry.getHeader().getLogfileOffset());
position.setTimestamp(entry.getHeader().getExecuteTime());
logPosition.setPostion(position);
LogIdentity identity = new LogIdentity(runningInfo.getAddress(), -1L);
logPosition.setIdentity(identity);
return logPosition;
} }
} }
@@ -362,6 +366,8 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
position.setJournalName(entry.getHeader().getLogfileName()); position.setJournalName(entry.getHeader().getLogfileName());
position.setPosition(entry.getHeader().getLogfileOffset()); position.setPosition(entry.getHeader().getLogfileOffset());
position.setTimestamp(entry.getHeader().getExecuteTime()); position.setTimestamp(entry.getHeader().getExecuteTime());
// add serverId at 2016-06-28
position.setServerId(entry.getHeader().getServerId());
logPosition.setPostion(position); logPosition.setPostion(position);
LogIdentity identity = new LogIdentity(runningInfo.getAddress(), -1L); LogIdentity identity = new LogIdentity(runningInfo.getAddress(), -1L);
@@ -369,7 +375,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
return logPosition; return logPosition;
} }
protected void processError(Exception e, LogPosition lastPosition, String startBinlogFile, long startPosition) { protected void processSinkError(Throwable e, LogPosition lastPosition, String startBinlogFile, long startPosition) {
if (lastPosition != null) { if (lastPosition != null) {
logger.warn(String.format("ERROR ## parse this event has an error , last position : [%s]", logger.warn(String.format("ERROR ## parse this event has an error , last position : [%s]",
lastPosition.getPostion()), lastPosition.getPostion()),
@@ -381,13 +387,21 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
} }
} }
protected void processDumpError(Throwable e) {
// do nothing
}
protected void startHeartBeat(ErosaConnection connection) { protected void startHeartBeat(ErosaConnection connection) {
lastEntryTime = 0L; // 初始化 lastEntryTime = 0L; // 初始化
if (timer == null) {// lazy初始化一下 if (timer == null) {// lazy初始化一下
String name = String.format("destination = %s , address = %s , HeartBeatTimeTask", String name = String.format("destination = %s , address = %s , HeartBeatTimeTask",
destination, destination,
runningInfo == null ? null : runningInfo.getAddress().toString()); runningInfo == null ? null : runningInfo.getAddress().toString());
synchronized (MysqlEventParser.class) { synchronized (AbstractEventParser.class) {
// synchronized (MysqlEventParser.class) {
// why use MysqlEventParser.class, u know, MysqlEventParser is
// the child class 4 AbstractEventParser,
// do this is ...
if (timer == null) { if (timer == null) {
timer = new Timer(name, true); timer = new Timer(name, true);
} }
@@ -424,7 +438,7 @@ public abstract class AbstractEventParser<EVENT> extends AbstractCanalLifeCycle
} }
} catch (Throwable e) { } catch (Throwable e) {
logger.warn("heartBeat run failed " + ExceptionUtils.getStackTrace(e)); logger.warn("heartBeat run failed ", e);
} }
} }

View File

@@ -11,7 +11,7 @@ import com.alibaba.otter.canal.protocol.CanalEntry;
*/ */
public interface BinlogParser<T> extends CanalLifeCycle { public interface BinlogParser<T> extends CanalLifeCycle {
CanalEntry.Entry parse(T event) throws CanalParseException; CanalEntry.Entry parse(T event, boolean isSeek) throws CanalParseException;
void reset(); void reset();
} }

View File

@@ -15,8 +15,6 @@ public interface ErosaConnection {
public void disconnect() throws IOException; public void disconnect() throws IOException;
public boolean isConnected();
/** /**
* 用于快速数据查找,和dump的区别在于,seek会只给出部分的数据 * 用于快速数据查找,和dump的区别在于,seek会只给出部分的数据
*/ */

View File

@@ -1,10 +1,10 @@
package com.alibaba.otter.canal.parse.inbound; package com.alibaba.otter.canal.parse.inbound;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.commons.lang.StringUtils;
import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent; import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
import org.apache.commons.lang.StringUtils;
/** /**
* 描述数据meta对象,mysql binlog中对应的{@linkplain TableMapLogEvent}包含的信息不全 * 描述数据meta对象,mysql binlog中对应的{@linkplain TableMapLogEvent}包含的信息不全
@@ -20,36 +20,111 @@ import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
*/ */
public class TableMeta { public class TableMeta {
private String fullName; // schema.table private String schema;
private List<FieldMeta> fileds; private String table;
private List<FieldMeta> fields = new ArrayList<TableMeta.FieldMeta>();
private String ddl; // 表结构的DDL语句
public TableMeta(String fullName, List<FieldMeta> fileds){ public TableMeta(){
this.fullName = fullName;
this.fileds = fileds; }
public TableMeta(String schema, String table, List<FieldMeta> fields){
this.schema = schema;
this.table = table;
this.fields = fields;
} }
public String getFullName() { public String getFullName() {
return fullName; return schema + "." + table;
} }
public void setFullName(String fullName) { public String getSchema() {
this.fullName = fullName; return schema;
} }
public List<FieldMeta> getFileds() { public void setSchema(String schema) {
return fileds; this.schema = schema;
} }
public void setFileds(List<FieldMeta> fileds) { public String getTable() {
this.fileds = fileds; return table;
}
public void setTable(String table) {
this.table = table;
}
public List<FieldMeta> getFields() {
return fields;
}
public void setFields(List<FieldMeta> fileds) {
this.fields = fileds;
}
public FieldMeta getFieldMetaByName(String name) {
for (FieldMeta meta : fields) {
if (meta.getColumnName().equalsIgnoreCase(name)) {
return meta;
}
}
throw new RuntimeException("unknow column : " + name);
}
public List<FieldMeta> getPrimaryFields() {
List<FieldMeta> primarys = new ArrayList<TableMeta.FieldMeta>();
for (FieldMeta meta : fields) {
if (meta.isKey()) {
primarys.add(meta);
}
}
return primarys;
}
public String getDdl() {
return ddl;
}
public void setDdl(String ddl) {
this.ddl = ddl;
}
public void addFieldMeta(FieldMeta fieldMeta) {
this.fields.add(fieldMeta);
}
@Override
public String toString() {
StringBuilder data = new StringBuilder();
data.append("TableMeta [schema=" + schema + ", table=" + table + ", fileds=");
for (FieldMeta field : fields) {
data.append("\n\t").append(field.toString());
}
data.append("\n]");
return data.toString();
} }
public static class FieldMeta { public static class FieldMeta {
public FieldMeta(){
}
public FieldMeta(String columnName, String columnType, boolean nullable, boolean key, String defaultValue){
this.columnName = columnName;
this.columnType = columnType;
this.nullable = nullable;
this.key = key;
this.defaultValue = defaultValue;
}
private String columnName; private String columnName;
private String columnType; private String columnType;
private String isNullable; private boolean nullable;
private String iskey; private boolean key;
private String defaultValue; private String defaultValue;
private String extra; private String extra;
@@ -69,20 +144,8 @@ public class TableMeta {
this.columnType = columnType; this.columnType = columnType;
} }
public String getIsNullable() { public void setNullable(boolean nullable) {
return isNullable; this.nullable = nullable;
}
public void setIsNullable(String isNullable) {
this.isNullable = isNullable;
}
public String getIskey() {
return iskey;
}
public void setIskey(String iskey) {
this.iskey = iskey;
} }
public String getDefaultValue() { public String getDefaultValue() {
@@ -93,6 +156,22 @@ public class TableMeta {
this.defaultValue = defaultValue; this.defaultValue = defaultValue;
} }
public boolean isUnsigned() {
return StringUtils.containsIgnoreCase(columnType, "unsigned");
}
public boolean isNullable() {
return nullable;
}
public boolean isKey() {
return key;
}
public void setKey(boolean key) {
this.key = key;
}
public String getExtra() { public String getExtra() {
return extra; return extra;
} }
@@ -101,22 +180,10 @@ public class TableMeta {
this.extra = extra; this.extra = extra;
} }
public boolean isUnsigned() {
return StringUtils.containsIgnoreCase(columnType, "unsigned");
}
public boolean isKey() {
return StringUtils.equalsIgnoreCase(iskey, "PRI");
}
public boolean isNullable() {
return StringUtils.equalsIgnoreCase(isNullable, "YES");
}
public String toString() { public String toString() {
return "FieldMeta [columnName=" + columnName + ", columnType=" + columnType + ", defaultValue=" return "FieldMeta [columnName=" + columnName + ", columnType=" + columnType + ", defaultValue="
+ defaultValue + ", extra=" + extra + ", isNullable=" + isNullable + ", iskey=" + iskey + "]"; + defaultValue + ", nullable=" + nullable + ", key=" + key + "]";
}
} }
} }
}

View File

@@ -7,22 +7,31 @@ import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.filter.CanalEventFilter; import com.alibaba.otter.canal.filter.CanalEventFilter;
import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter; import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
import com.alibaba.otter.canal.parse.exception.CanalParseException;
import com.alibaba.otter.canal.parse.inbound.AbstractEventParser; import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
import com.alibaba.otter.canal.parse.inbound.BinlogParser; import com.alibaba.otter.canal.parse.inbound.BinlogParser;
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert; import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDB;
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDBBuilder;
import com.alibaba.otter.canal.protocol.position.EntryPosition;
public abstract class AbstractMysqlEventParser extends AbstractEventParser { public abstract class AbstractMysqlEventParser extends AbstractEventParser {
protected final Logger logger = LoggerFactory.getLogger(this.getClass()); protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected static final long BINLOG_START_OFFEST = 4L; protected static final long BINLOG_START_OFFEST = 4L;
protected boolean enableTsdb = false;
protected String tsdbSpringXml;
protected TableMetaTSDB tableMetaTSDB;
// 编码信息 // 编码信息
protected byte connectionCharsetNumber = (byte) 33; protected byte connectionCharsetNumber = (byte) 33;
protected Charset connectionCharset = Charset.forName("UTF-8"); protected Charset connectionCharset = Charset.forName("UTF-8");
protected boolean filterQueryDcl = false; protected boolean filterQueryDcl = false;
protected boolean filterQueryDml = false; protected boolean filterQueryDml = false;
protected boolean filterQueryDdl = false; protected boolean filterQueryDdl = false;
protected boolean filterRows = false;
protected boolean filterTableError = false; protected boolean filterTableError = false;
protected boolean useDruidDdlFilter = true;
protected BinlogParser buildParser() { protected BinlogParser buildParser() {
LogEventConvert convert = new LogEventConvert(); LogEventConvert convert = new LogEventConvert();
@@ -38,6 +47,7 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
convert.setFilterQueryDcl(filterQueryDcl); convert.setFilterQueryDcl(filterQueryDcl);
convert.setFilterQueryDml(filterQueryDml); convert.setFilterQueryDml(filterQueryDml);
convert.setFilterQueryDdl(filterQueryDdl); convert.setFilterQueryDdl(filterQueryDdl);
convert.setFilterRows(filterRows);
convert.setFilterTableError(filterTableError); convert.setFilterTableError(filterTableError);
return convert; return convert;
} }
@@ -51,6 +61,40 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
} }
} }
/**
* 回滚到指定位点
*
* @param position
* @return
*/
protected boolean processTableMeta(EntryPosition position) {
if (tableMetaTSDB != null) {
return tableMetaTSDB.rollback(position);
}
return true;
}
public void start() throws CanalParseException {
if (enableTsdb) {
if (tableMetaTSDB == null) {
// 初始化
tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml);
}
}
super.start();
}
public void stop() throws CanalParseException {
if (enableTsdb) {
TableMetaTSDBBuilder.destory(destination);
tableMetaTSDB = null;
}
super.stop();
}
public void setEventBlackFilter(CanalEventFilter eventBlackFilter) { public void setEventBlackFilter(CanalEventFilter eventBlackFilter) {
super.setEventBlackFilter(eventBlackFilter); super.setEventBlackFilter(eventBlackFilter);
@@ -87,8 +131,39 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
this.filterQueryDdl = filterQueryDdl; this.filterQueryDdl = filterQueryDdl;
} }
public void setFilterRows(boolean filterRows) {
this.filterRows = filterRows;
}
public void setFilterTableError(boolean filterTableError) { public void setFilterTableError(boolean filterTableError) {
this.filterTableError = filterTableError; this.filterTableError = filterTableError;
} }
public boolean isUseDruidDdlFilter() {
return useDruidDdlFilter;
}
public void setUseDruidDdlFilter(boolean useDruidDdlFilter) {
this.useDruidDdlFilter = useDruidDdlFilter;
}
public void setEnableTsdb(boolean enableTsdb) {
this.enableTsdb = enableTsdb;
if (this.enableTsdb) {
if (tableMetaTSDB == null) {
// 初始化
tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml);
}
}
}
public void setTsdbSpringXml(String tsdbSpringXml) {
this.tsdbSpringXml = tsdbSpringXml;
if (tableMetaTSDB == null) {
// 初始化
tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml);
}
}
} }

View File

@@ -18,6 +18,7 @@ import com.taobao.tddl.dbsync.binlog.LogDecoder;
import com.taobao.tddl.dbsync.binlog.LogEvent; import com.taobao.tddl.dbsync.binlog.LogEvent;
import com.taobao.tddl.dbsync.binlog.LogPosition; import com.taobao.tddl.dbsync.binlog.LogPosition;
import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent; import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent;
import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent;
/** /**
* local bin log connection (not real connection) * local bin log connection (not real connection)
@@ -83,7 +84,10 @@ public class LocalBinLogConnection implements ErosaConnection {
context.setLogPosition(new LogPosition(binlogfilename, binlogPosition)); context.setLogPosition(new LogPosition(binlogfilename, binlogPosition));
while (running) { while (running) {
boolean needContinue = true; boolean needContinue = true;
LogEvent event; LogEvent event = null;
// 处理一下binlog文件名
event = new RotateLogEvent(context.getLogPosition().getFileName(), 4);
func.sink(event);
while (fetcher.fetch()) { while (fetcher.fetch()) {
event = decoder.decode(fetcher, context); event = decoder.decode(fetcher, context);
if (event == null) { if (event == null) {
@@ -94,14 +98,6 @@ public class LocalBinLogConnection implements ErosaConnection {
needContinue = false; needContinue = false;
break; break;
} }
// do {
// event = decoder.decode(fetcher, context);
// if (event != null && !func.sink(event)) {
// needContinue = false;
// break;
// }
// } while (event != null);
} }
if (needContinue) {// 读取下一个 if (needContinue) {// 读取下一个
@@ -119,7 +115,6 @@ public class LocalBinLogConnection implements ErosaConnection {
} }
current = nextFile; current = nextFile;
fetcher.open(current); fetcher.open(current);
context.setLogPosition(new LogPosition(nextFile.getName())); context.setLogPosition(new LogPosition(nextFile.getName()));
} else { } else {
@@ -145,6 +140,7 @@ public class LocalBinLogConnection implements ErosaConnection {
FileLogFetcher fetcher = new FileLogFetcher(bufferSize); FileLogFetcher fetcher = new FileLogFetcher(bufferSize);
LogDecoder decoder = new LogDecoder(); LogDecoder decoder = new LogDecoder();
decoder.handle(LogEvent.FORMAT_DESCRIPTION_EVENT);
decoder.handle(LogEvent.QUERY_EVENT); decoder.handle(LogEvent.QUERY_EVENT);
decoder.handle(LogEvent.XID_EVENT); decoder.handle(LogEvent.XID_EVENT);
LogContext context = new LogContext(); LogContext context = new LogContext();
@@ -158,13 +154,11 @@ public class LocalBinLogConnection implements ErosaConnection {
binlogFilename = lastXidLogFilename; binlogFilename = lastXidLogFilename;
binlogFileOffset = lastXidLogFileOffset; binlogFileOffset = lastXidLogFileOffset;
L: while (fetcher.fetch()) { while (fetcher.fetch()) {
LogEvent event; LogEvent event = decoder.decode(fetcher, context);
do {
event = decoder.decode(fetcher, context);
if (event != null) { if (event != null) {
if (event.getWhen() > timestampSeconds) { if (event.getWhen() > timestampSeconds) {
break L; break;
} }
needContinue = false; needContinue = false;
@@ -181,7 +175,6 @@ public class LocalBinLogConnection implements ErosaConnection {
lastXidLogFileOffset = event.getLogPos(); lastXidLogFileOffset = event.getLogPos();
} }
} }
} while (event != null);
} }
if (needContinue) {// 读取下一个 if (needContinue) {// 读取下一个

View File

@@ -1,10 +1,15 @@
package com.alibaba.otter.canal.parse.inbound.mysql; package com.alibaba.otter.canal.parse.inbound.mysql;
import java.io.IOException;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import com.alibaba.otter.canal.parse.CanalEventParser; import com.alibaba.otter.canal.parse.CanalEventParser;
import com.alibaba.otter.canal.parse.exception.CanalParseException; import com.alibaba.otter.canal.parse.exception.CanalParseException;
import com.alibaba.otter.canal.parse.inbound.ErosaConnection; import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache;
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta;
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager; import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
import com.alibaba.otter.canal.parse.support.AuthenticationInfo; import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
import com.alibaba.otter.canal.protocol.position.EntryPosition; import com.alibaba.otter.canal.protocol.position.EntryPosition;
@@ -19,12 +24,14 @@ import com.alibaba.otter.canal.protocol.position.LogPosition;
public class LocalBinlogEventParser extends AbstractMysqlEventParser implements CanalEventParser { public class LocalBinlogEventParser extends AbstractMysqlEventParser implements CanalEventParser {
// 数据库信息 // 数据库信息
private AuthenticationInfo masterInfo; protected AuthenticationInfo masterInfo;
private EntryPosition masterPosition; // binlog信息 protected EntryPosition masterPosition; // binlog信息
protected MysqlConnection metaConnection; // 查询meta信息的链接
protected TableMetaCache tableMetaCache; // 对应meta
private String directory; protected String directory;
private boolean needWait = false; protected boolean needWait = false;
private int bufferSize = 16 * 1024; protected int bufferSize = 16 * 1024;
public LocalBinlogEventParser(){ public LocalBinlogEventParser(){
// this.runningInfo = new AuthenticationInfo(); // this.runningInfo = new AuthenticationInfo();
@@ -35,6 +42,37 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
return buildLocalBinLogConnection(); return buildLocalBinLogConnection();
} }
@Override
protected void preDump(ErosaConnection connection) {
metaConnection = buildMysqlConnection();
try {
metaConnection.connect();
} catch (IOException e) {
throw new CanalParseException(e);
}
if (tableMetaTSDB != null && tableMetaTSDB instanceof DatabaseTableMeta) {
((DatabaseTableMeta) tableMetaTSDB).setConnection(metaConnection);
((DatabaseTableMeta) tableMetaTSDB).setFilter(eventFilter);
((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter);
}
tableMetaCache = new TableMetaCache(metaConnection, tableMetaTSDB);
((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache);
}
@Override
protected void afterDump(ErosaConnection connection) {
if (metaConnection != null) {
try {
metaConnection.disconnect();
} catch (IOException e) {
logger.error("ERROR # disconnect meta connection for address:{}", metaConnection.getConnector()
.getAddress(), e);
}
}
}
public void start() throws CanalParseException { public void start() throws CanalParseException {
if (runningInfo == null) { // 第一次链接主库 if (runningInfo == null) { // 第一次链接主库
runningInfo = masterInfo; runningInfo = masterInfo;
@@ -43,6 +81,24 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
super.start(); super.start();
} }
@Override
public void stop() {
if (metaConnection != null) {
try {
metaConnection.disconnect();
} catch (IOException e) {
logger.error("ERROR # disconnect meta connection for address:{}", metaConnection.getConnector()
.getAddress(), e);
}
}
if (tableMetaCache != null) {
tableMetaCache.clearTableMeta();
}
super.stop();
}
private ErosaConnection buildLocalBinLogConnection() { private ErosaConnection buildLocalBinLogConnection() {
LocalBinLogConnection connection = new LocalBinLogConnection(); LocalBinLogConnection connection = new LocalBinLogConnection();
@@ -53,6 +109,19 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements
return connection; return connection;
} }
private MysqlConnection buildMysqlConnection() {
MysqlConnection connection = new MysqlConnection(runningInfo.getAddress(),
runningInfo.getUsername(),
runningInfo.getPassword(),
connectionCharsetNumber,
runningInfo.getDefaultDatabaseName());
connection.getConnector().setReceiveBufferSize(64 * 1024);
connection.getConnector().setSendBufferSize(64 * 1024);
connection.getConnector().setSoTimeout(30 * 1000);
connection.setCharset(connectionCharset);
return connection;
}
@Override @Override
protected EntryPosition findStartPosition(ErosaConnection connection) { protected EntryPosition findStartPosition(ErosaConnection connection) {
// 处理逻辑 // 处理逻辑

View File

@@ -2,11 +2,10 @@ package com.alibaba.otter.canal.parse.inbound.mysql;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.List; import java.util.List;
import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -21,6 +20,7 @@ import com.alibaba.otter.canal.parse.exception.CanalParseException;
import com.alibaba.otter.canal.parse.inbound.ErosaConnection; import com.alibaba.otter.canal.parse.inbound.ErosaConnection;
import com.alibaba.otter.canal.parse.inbound.SinkFunction; import com.alibaba.otter.canal.parse.inbound.SinkFunction;
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher; import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher;
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
import com.taobao.tddl.dbsync.binlog.LogContext; import com.taobao.tddl.dbsync.binlog.LogContext;
import com.taobao.tddl.dbsync.binlog.LogDecoder; import com.taobao.tddl.dbsync.binlog.LogDecoder;
import com.taobao.tddl.dbsync.binlog.LogEvent; import com.taobao.tddl.dbsync.binlog.LogEvent;
@@ -35,17 +35,36 @@ public class MysqlConnection implements ErosaConnection {
private BinlogFormat binlogFormat; private BinlogFormat binlogFormat;
private BinlogImage binlogImage; private BinlogImage binlogImage;
// tsdb releated
private AuthenticationInfo authInfo;
protected int connTimeout = 5 * 1000; // 5秒
protected int soTimeout = 60 * 60 * 1000; // 1小时
public MysqlConnection(){ public MysqlConnection(){
} }
public MysqlConnection(InetSocketAddress address, String username, String password){ public MysqlConnection(InetSocketAddress address, String username, String password){
authInfo = new AuthenticationInfo();
authInfo.setAddress(address);
authInfo.setUsername(username);
authInfo.setPassword(password);
connector = new MysqlConnector(address, username, password); connector = new MysqlConnector(address, username, password);
// 将connection里面的参数透传下
connector.setSoTimeout(soTimeout);
connector.setConnTimeout(connTimeout);
} }
public MysqlConnection(InetSocketAddress address, String username, String password, byte charsetNumber, public MysqlConnection(InetSocketAddress address, String username, String password, byte charsetNumber,
String defaultSchema){ String defaultSchema){
authInfo = new AuthenticationInfo();
authInfo.setAddress(address);
authInfo.setUsername(username);
authInfo.setPassword(password);
authInfo.setDefaultDatabaseName(defaultSchema);
connector = new MysqlConnector(address, username, password, charsetNumber, defaultSchema); connector = new MysqlConnector(address, username, password, charsetNumber, defaultSchema);
// 将connection里面的参数透传下
connector.setSoTimeout(soTimeout);
connector.setConnTimeout(connTimeout);
} }
public void connect() throws IOException { public void connect() throws IOException {
@@ -69,6 +88,11 @@ public class MysqlConnection implements ErosaConnection {
return exector.query(cmd); return exector.query(cmd);
} }
public List<ResultSetPacket> queryMulti(String cmd) throws IOException {
MysqlQueryExecutor exector = new MysqlQueryExecutor(connector);
return exector.queryMulti(cmd);
}
public void update(String cmd) throws IOException { public void update(String cmd) throws IOException {
MysqlUpdateExecutor exector = new MysqlUpdateExecutor(connector); MysqlUpdateExecutor exector = new MysqlUpdateExecutor(connector);
exector.update(cmd); exector.update(cmd);
@@ -132,6 +156,7 @@ public class MysqlConnection implements ErosaConnection {
BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket(); BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket();
binlogDumpCmd.binlogFileName = binlogfilename; binlogDumpCmd.binlogFileName = binlogfilename;
binlogDumpCmd.binlogPosition = binlogPosition; binlogDumpCmd.binlogPosition = binlogPosition;
// binlogDumpCmd.slaveServerId = this.slaveId;
binlogDumpCmd.slaveServerId = this.slaveId; binlogDumpCmd.slaveServerId = this.slaveId;
byte[] cmdBody = binlogDumpCmd.toBytes(); byte[] cmdBody = binlogDumpCmd.toBytes();
@@ -139,8 +164,8 @@ public class MysqlConnection implements ErosaConnection {
HeaderPacket binlogDumpHeader = new HeaderPacket(); HeaderPacket binlogDumpHeader = new HeaderPacket();
binlogDumpHeader.setPacketBodyLength(cmdBody.length); binlogDumpHeader.setPacketBodyLength(cmdBody.length);
binlogDumpHeader.setPacketSequenceNumber((byte) 0x00); binlogDumpHeader.setPacketSequenceNumber((byte) 0x00);
PacketManager.write(connector.getChannel(), new ByteBuffer[] { ByteBuffer.wrap(binlogDumpHeader.toBytes()), PacketManager.writePkg(connector.getChannel(), binlogDumpHeader.toBytes(), cmdBody);
ByteBuffer.wrap(cmdBody) }); connector.setDumping(true);
} }
public MysqlConnection fork() { public MysqlConnection fork() {
@@ -148,6 +173,8 @@ public class MysqlConnection implements ErosaConnection {
connection.setCharset(getCharset()); connection.setCharset(getCharset());
connection.setSlaveId(getSlaveId()); connection.setSlaveId(getSlaveId());
connection.setConnector(connector.fork()); connection.setConnector(connector.fork());
// set authInfo
connection.setAuthInfo(authInfo);
return connection; return connection;
} }
@@ -168,25 +195,25 @@ public class MysqlConnection implements ErosaConnection {
try { try {
update("set wait_timeout=9999999"); update("set wait_timeout=9999999");
} catch (Exception e) { } catch (Exception e) {
logger.warn(ExceptionUtils.getFullStackTrace(e)); logger.warn("update wait_timeout failed", e);
} }
try { try {
update("set net_write_timeout=1800"); update("set net_write_timeout=1800");
} catch (Exception e) { } catch (Exception e) {
logger.warn(ExceptionUtils.getFullStackTrace(e)); logger.warn("update net_write_timeout failed", e);
} }
try { try {
update("set net_read_timeout=1800"); update("set net_read_timeout=1800");
} catch (Exception e) { } catch (Exception e) {
logger.warn(ExceptionUtils.getFullStackTrace(e)); logger.warn("update net_read_timeout failed", e);
} }
try { try {
// 设置服务端返回结果时不做编码转化,直接按照数据库的二进制编码进行发送,由客户端自己根据需求进行编码转化 // 设置服务端返回结果时不做编码转化,直接按照数据库的二进制编码进行发送,由客户端自己根据需求进行编码转化
update("set names 'binary'"); update("set names 'binary'");
} catch (Exception e) { } catch (Exception e) {
logger.warn(ExceptionUtils.getFullStackTrace(e)); logger.warn("update names failed", e);
} }
try { try {
@@ -196,14 +223,24 @@ public class MysqlConnection implements ErosaConnection {
// 但也不能乱设置,需要和mysql server的checksum配置一致,不然RotateLogEvent会出现乱码 // 但也不能乱设置,需要和mysql server的checksum配置一致,不然RotateLogEvent会出现乱码
update("set @master_binlog_checksum= '@@global.binlog_checksum'"); update("set @master_binlog_checksum= '@@global.binlog_checksum'");
} catch (Exception e) { } catch (Exception e) {
logger.warn(ExceptionUtils.getFullStackTrace(e)); logger.warn("update master_binlog_checksum failed", e);
}
try {
// 参考:https://github.com/alibaba/canal/issues/284
// mysql5.6需要设置slave_uuid避免被server kill链接
update("set @slave_uuid=uuid()");
} catch (Exception e) {
if (!StringUtils.contains(e.getMessage(), "Unknown system variable")) {
logger.warn("update slave_uuid failed", e);
}
} }
try { try {
// mariadb针对特殊的类型,需要设置session变量 // mariadb针对特殊的类型,需要设置session变量
update("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'"); update("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
} catch (Exception e) { } catch (Exception e) {
logger.warn(ExceptionUtils.getFullStackTrace(e)); logger.warn("update mariadb_slave_capability failed", e);
} }
} }
@@ -373,4 +410,23 @@ public class MysqlConnection implements ErosaConnection {
return binlogImage; return binlogImage;
} }
public InetSocketAddress getAddress() {
return authInfo.getAddress();
}
public void setConnTimeout(int connTimeout) {
this.connTimeout = connTimeout;
}
public void setSoTimeout(int soTimeout) {
this.soTimeout = soTimeout;
}
public AuthenticationInfo getAuthInfo() {
return authInfo;
}
public void setAuthInfo(AuthenticationInfo authInfo) {
this.authInfo = authInfo;
}
} }

View File

@@ -1,7 +1,9 @@
package com.alibaba.otter.canal.parse.inbound.mysql; package com.alibaba.otter.canal.parse.inbound.mysql;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -10,9 +12,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import com.alibaba.otter.canal.common.utils.JsonUtils;
import com.alibaba.otter.canal.parse.CanalEventParser; import com.alibaba.otter.canal.parse.CanalEventParser;
import com.alibaba.otter.canal.parse.CanalHASwitchable; import com.alibaba.otter.canal.parse.CanalHASwitchable;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket;
@@ -26,9 +28,9 @@ import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogFormat;
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogImage; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogImage;
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert; import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert;
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache; import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache;
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DatabaseTableMeta;
import com.alibaba.otter.canal.parse.support.AuthenticationInfo; import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
import com.alibaba.otter.canal.protocol.position.EntryPosition; import com.alibaba.otter.canal.protocol.position.EntryPosition;
import com.alibaba.otter.canal.protocol.position.LogPosition; import com.alibaba.otter.canal.protocol.position.LogPosition;
import com.taobao.tddl.dbsync.binlog.LogEvent; import com.taobao.tddl.dbsync.binlog.LogEvent;
@@ -67,7 +69,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验 private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验
private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验 private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验
// 心跳检查 // update by yishun.chen,特殊异常处理参数
private int dumpErrorCount = 0; // binlogDump失败异常计数
private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值
protected ErosaConnection buildErosaConnection() { protected ErosaConnection buildErosaConnection() {
return buildMysqlConnection(this.runningInfo); return buildMysqlConnection(this.runningInfo);
@@ -92,6 +96,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
for (BinlogFormat supportFormat : supportBinlogFormats) { for (BinlogFormat supportFormat : supportBinlogFormats) {
if (supportFormat != null && format == supportFormat) { if (supportFormat != null && format == supportFormat) {
found = true; found = true;
break;
} }
} }
@@ -106,6 +111,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
for (BinlogImage supportImage : supportBinlogImages) { for (BinlogImage supportImage : supportBinlogImages) {
if (supportImage != null && image == supportImage) { if (supportImage != null && image == supportImage) {
found = true; found = true;
break;
} }
} }
@@ -114,7 +120,13 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
} }
} }
tableMetaCache = new TableMetaCache(metaConnection); if (tableMetaTSDB != null && tableMetaTSDB instanceof DatabaseTableMeta) {
((DatabaseTableMeta) tableMetaTSDB).setConnection(metaConnection);
((DatabaseTableMeta) tableMetaTSDB).setFilter(eventFilter);
((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter);
}
tableMetaCache = new TableMetaCache(metaConnection, tableMetaTSDB);
((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache); ((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache);
} }
} }
@@ -176,10 +188,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
} }
protected void stopHeartBeat() { protected void stopHeartBeat() {
TimerTask heartBeatTimerTask = this.heartBeatTimerTask;
super.stopHeartBeat(); super.stopHeartBeat();
if (heartBeatTimerTask != null && heartBeatTimerTask instanceof MysqlDetectingTimeTask) {
if (heartBeatTimerTask != null) {
MysqlConnection mysqlConnection = ((MysqlDetectingTimeTask) heartBeatTimerTask).getMysqlConnection(); MysqlConnection mysqlConnection = ((MysqlDetectingTimeTask) heartBeatTimerTask).getMysqlConnection();
try { try {
mysqlConnection.disconnect(); mysqlConnection.disconnect();
@@ -234,19 +245,19 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
((HeartBeatCallback) haController).onFailed(e); ((HeartBeatCallback) haController).onFailed(e);
} }
reconnect = true; reconnect = true;
logger.warn("connect failed by " + ExceptionUtils.getStackTrace(e)); logger.warn("connect failed by ", e);
} catch (IOException e) { } catch (IOException e) {
if (haController != null && haController instanceof HeartBeatCallback) { if (haController != null && haController instanceof HeartBeatCallback) {
((HeartBeatCallback) haController).onFailed(e); ((HeartBeatCallback) haController).onFailed(e);
} }
reconnect = true; reconnect = true;
logger.warn("connect failed by " + ExceptionUtils.getStackTrace(e)); logger.warn("connect failed by ", e);
} catch (Throwable e) { } catch (Throwable e) {
if (haController != null && haController instanceof HeartBeatCallback) { if (haController != null && haController instanceof HeartBeatCallback) {
((HeartBeatCallback) haController).onFailed(e); ((HeartBeatCallback) haController).onFailed(e);
} }
reconnect = true; reconnect = true;
logger.warn("connect failed by " + ExceptionUtils.getStackTrace(e)); logger.warn("connect failed by ", e);
} }
} }
@@ -304,10 +315,35 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
connection.getConnector().setSendBufferSize(sendBufferSize); connection.getConnector().setSendBufferSize(sendBufferSize);
connection.getConnector().setSoTimeout(defaultConnectionTimeoutInSeconds * 1000); connection.getConnector().setSoTimeout(defaultConnectionTimeoutInSeconds * 1000);
connection.setCharset(connectionCharset); connection.setCharset(connectionCharset);
// 随机生成slaveId
if (this.slaveId <= 0) {
this.slaveId = generateUniqueServerId();
}
connection.setSlaveId(this.slaveId); connection.setSlaveId(this.slaveId);
return connection; return connection;
} }
private final long generateUniqueServerId() {
try {
// a=`echo $masterip|cut -d\. -f1`
// b=`echo $masterip|cut -d\. -f2`
// c=`echo $masterip|cut -d\. -f3`
// d=`echo $masterip|cut -d\. -f4`
// #server_id=`expr $a \* 256 \* 256 \* 256 + $b \* 256 \* 256 + $c
// \* 256 + $d `
// #server_id=$b$c$d
// server_id=`expr $b \* 256 \* 256 + $c \* 256 + $d `
InetAddress localHost = InetAddress.getLocalHost();
byte[] addr = localHost.getAddress();
int salt = (destination != null) ? destination.hashCode() : 0;
return ((0x7f & salt) << 24) + ((0xff & (int) addr[1]) << 16) // NL
+ ((0xff & (int) addr[2]) << 8) // NL
+ (0xff & (int) addr[3]);
} catch (UnknownHostException e) {
throw new CanalParseException("Unknown host", e);
}
}
protected EntryPosition findStartPosition(ErosaConnection connection) throws IOException { protected EntryPosition findStartPosition(ErosaConnection connection) throws IOException {
EntryPosition startPosition = findStartPositionInternal(connection); EntryPosition startPosition = findStartPositionInternal(connection);
if (needTransactionPosition.get()) { if (needTransactionPosition.get()) {
@@ -324,6 +360,40 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
return startPosition; return startPosition;
} }
protected EntryPosition findEndPosition(ErosaConnection connection) throws IOException {
MysqlConnection mysqlConnection = (MysqlConnection) connection;
EntryPosition endPosition = findEndPosition(mysqlConnection);
return endPosition;
}
protected EntryPosition findEndPositionWithMasterIdAndTimestamp(MysqlConnection connection) {
MysqlConnection mysqlConnection = (MysqlConnection) connection;
final EntryPosition endPosition = findEndPosition(mysqlConnection);
if (tableMetaTSDB != null) {
long startTimestamp = System.currentTimeMillis();
return findAsPerTimestampInSpecificLogFile(mysqlConnection,
startTimestamp,
endPosition,
endPosition.getJournalName());
} else {
return endPosition;
}
}
protected EntryPosition findPositionWithMasterIdAndTimestamp(MysqlConnection connection, EntryPosition fixedPosition) {
MysqlConnection mysqlConnection = (MysqlConnection) connection;
if (tableMetaTSDB != null && (fixedPosition.getTimestamp() == null || fixedPosition.getTimestamp() <= 0)) {
// 使用一个未来极大的时间,基于位点进行定位
long startTimestamp = System.currentTimeMillis() + 102L * 365 * 24 * 3600 * 1000; // 当前时间的未来102年
return findAsPerTimestampInSpecificLogFile(mysqlConnection,
startTimestamp,
fixedPosition,
fixedPosition.getJournalName());
} else {
return fixedPosition;
}
}
protected EntryPosition findStartPositionInternal(ErosaConnection connection) { protected EntryPosition findStartPositionInternal(ErosaConnection connection) {
MysqlConnection mysqlConnection = (MysqlConnection) connection; MysqlConnection mysqlConnection = (MysqlConnection) connection;
LogPosition logPosition = logPositionManager.getLatestIndexBy(destination); LogPosition logPosition = logPositionManager.getLatestIndexBy(destination);
@@ -337,7 +407,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
} }
if (entryPosition == null) { if (entryPosition == null) {
entryPosition = findEndPosition(mysqlConnection); // 默认从当前最后一个位置进行消费 entryPosition = findEndPositionWithMasterIdAndTimestamp(mysqlConnection); // 默认从当前最后一个位置进行消费
} }
// 判断一下是否需要按时间订阅 // 判断一下是否需要按时间订阅
@@ -349,13 +419,15 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
return findByStartTimeStamp(mysqlConnection, entryPosition.getTimestamp()); return findByStartTimeStamp(mysqlConnection, entryPosition.getTimestamp());
} else { } else {
logger.warn("prepare to find start position just show master status"); logger.warn("prepare to find start position just show master status");
return findEndPosition(mysqlConnection); // 默认从当前最后一个位置进行消费 return findEndPositionWithMasterIdAndTimestamp(mysqlConnection); // 默认从当前最后一个位置进行消费
} }
} else { } else {
if (entryPosition.getPosition() != null && entryPosition.getPosition() > 0L) { if (entryPosition.getPosition() != null && entryPosition.getPosition() > 0L) {
// 如果指定binlogName + offest,直接返回 // 如果指定binlogName + offest,直接返回
entryPosition = findPositionWithMasterIdAndTimestamp(mysqlConnection, entryPosition);
logger.warn("prepare to find start position {}:{}:{}", logger.warn("prepare to find start position {}:{}:{}",
new Object[] { entryPosition.getJournalName(), entryPosition.getPosition(), "" }); new Object[] { entryPosition.getJournalName(), entryPosition.getPosition(),
entryPosition.getTimestamp() });
return entryPosition; return entryPosition;
} else { } else {
EntryPosition specificLogFilePosition = null; EntryPosition specificLogFilePosition = null;
@@ -384,7 +456,27 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
} }
} else { } else {
if (logPosition.getIdentity().getSourceAddress().equals(mysqlConnection.getConnector().getAddress())) { if (logPosition.getIdentity().getSourceAddress().equals(mysqlConnection.getConnector().getAddress())) {
logger.warn("prepare to find start position just last position"); if (dumpErrorCountThreshold >= 0 && dumpErrorCount > dumpErrorCountThreshold) {
// binlog定位位点失败,可能有两个原因:
// 1. binlog位点被删除
// 2.vip模式的mysql,发生了主备切换,判断一下serverId是否变化,针对这种模式可以发起一次基于时间戳查找合适的binlog位点
boolean case2 = (standbyInfo == null || standbyInfo.getAddress() == null)
&& logPosition.getPostion().getServerId() != null
&& !logPosition.getPostion().getServerId().equals(findServerId(mysqlConnection));
if (case2) {
long timestamp = logPosition.getPostion().getTimestamp();
long newStartTimestamp = timestamp - fallbackIntervalInSeconds * 1000;
logger.warn("prepare to find start position by last position {}:{}:{}", new Object[] { "", "",
logPosition.getPostion().getTimestamp() });
EntryPosition findPosition = findByStartTimeStamp(mysqlConnection, newStartTimestamp);
// 重新置为一下
dumpErrorCount = 0;
return findPosition;
}
}
// 其余情况
logger.warn("prepare to find start position just last position\n {}",
JsonUtils.marshalToString(logPosition));
return logPosition.getPostion(); return logPosition.getPostion();
} else { } else {
// 针对切换的情况,考虑回退时间 // 针对切换的情况,考虑回退时间
@@ -409,7 +501,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
public boolean sink(LogEvent event) { public boolean sink(LogEvent event) {
try { try {
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event); CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, true);
if (entry == null) { if (entry == null) {
return true; return true;
} }
@@ -426,7 +518,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
} }
} catch (Exception e) { } catch (Exception e) {
// 上一次记录的poistion可能为一条update/insert/delete变更事件,直接进行dump的话,会缺少tableMap事件,导致tableId未进行解析 // 上一次记录的poistion可能为一条update/insert/delete变更事件,直接进行dump的话,会缺少tableMap事件,导致tableId未进行解析
processError(e, lastPosition, entryPosition.getJournalName(), entryPosition.getPosition()); processSinkError(e, lastPosition, entryPosition.getJournalName(), entryPosition.getPosition());
reDump.set(true); reDump.set(true);
return false; return false;
} }
@@ -442,7 +534,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
public boolean sink(LogEvent event) { public boolean sink(LogEvent event) {
try { try {
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event); CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, true);
if (entry == null) { if (entry == null) {
return true; return true;
} }
@@ -460,7 +552,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
lastPosition = buildLastPosition(entry); lastPosition = buildLastPosition(entry);
} catch (Exception e) { } catch (Exception e) {
processError(e, lastPosition, entryPosition.getJournalName(), entryPosition.getPosition()); processSinkError(e, lastPosition, entryPosition.getJournalName(), entryPosition.getPosition());
return false; return false;
} }
@@ -520,9 +612,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
return entryPosition; return entryPosition;
} }
} catch (Exception e) { } catch (Exception e) {
logger.warn("the binlogfile:{} doesn't exist, to continue to search the next binlogfile , caused by {}", logger.warn(String.format("the binlogfile:%s doesn't exist, to continue to search the next binlogfile , caused by",
startSearchBinlogFile, startSearchBinlogFile),
ExceptionUtils.getFullStackTrace(e)); e);
int binlogSeqNum = Integer.parseInt(startSearchBinlogFile.substring(startSearchBinlogFile.indexOf(".") + 1)); int binlogSeqNum = Integer.parseInt(startSearchBinlogFile.substring(startSearchBinlogFile.indexOf(".") + 1));
if (binlogSeqNum <= 1) { if (binlogSeqNum <= 1) {
logger.warn("Didn't find the corresponding binlog files"); logger.warn("Didn't find the corresponding binlog files");
@@ -540,6 +632,22 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
return null; return null;
} }
/**
* 查询当前db的serverId信息
*/
private Long findServerId(MysqlConnection mysqlConnection) {
try {
ResultSetPacket packet = mysqlConnection.query("show variables like 'server_id'");
List<String> fields = packet.getFieldValues();
if (CollectionUtils.isEmpty(fields)) {
throw new CanalParseException("command : show variables like 'server_id' has an error! pls check. you need (at least one of) the SUPER,REPLICATION CLIENT privilege(s) for this operation");
}
return Long.valueOf(fields.get(1));
} catch (IOException e) {
throw new CanalParseException("command : show variables like 'server_id' has an error!", e);
}
}
/** /**
* 查询当前的binlog位置 * 查询当前的binlog位置
*/ */
@@ -638,7 +746,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
public boolean sink(LogEvent event) { public boolean sink(LogEvent event) {
EntryPosition entryPosition = null; EntryPosition entryPosition = null;
try { try {
CanalEntry.Entry entry = parseAndProfilingIfNecessary(event); CanalEntry.Entry entry = parseAndProfilingIfNecessary(event, true);
if (entry == null) { if (entry == null) {
return true; return true;
} }
@@ -646,6 +754,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
String logfilename = entry.getHeader().getLogfileName(); String logfilename = entry.getHeader().getLogfileName();
Long logfileoffset = entry.getHeader().getLogfileOffset(); Long logfileoffset = entry.getHeader().getLogfileOffset();
Long logposTimestamp = entry.getHeader().getExecuteTime(); Long logposTimestamp = entry.getHeader().getExecuteTime();
Long serverId = entry.getHeader().getServerId();
if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType()) if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())
|| CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) { || CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
@@ -658,7 +767,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
} }
if (StringUtils.equals(endPosition.getJournalName(), logfilename) if (StringUtils.equals(endPosition.getJournalName(), logfilename)
&& endPosition.getPosition() <= (logfileoffset + event.getEventLen())) { && endPosition.getPosition() <= logfileoffset) {
return false; return false;
} }
@@ -666,23 +775,21 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
// position = current + // position = current +
// data.length,代表该事务的下一条offest,避免多余的事务重复 // data.length,代表该事务的下一条offest,避免多余的事务重复
if (CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) { if (CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) {
entryPosition = new EntryPosition(logfilename, entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp, serverId);
logfileoffset + event.getEventLen(),
logposTimestamp);
logger.debug("set {} to be pending start position before finding another proper one...", logger.debug("set {} to be pending start position before finding another proper one...",
entryPosition); entryPosition);
logPosition.setPostion(entryPosition); logPosition.setPostion(entryPosition);
} else if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())) { } else if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType())) {
// 当前事务开始位点 // 当前事务开始位点
entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp); entryPosition = new EntryPosition(logfilename, logfileoffset, logposTimestamp, serverId);
logger.debug("set {} to be pending start position before finding another proper one...", logger.debug("set {} to be pending start position before finding another proper one...",
entryPosition); entryPosition);
logPosition.setPostion(entryPosition); logPosition.setPostion(entryPosition);
} }
lastPosition = buildLastPosition(entry); lastPosition = buildLastPosition(entry);
} catch (Exception e) { } catch (Throwable e) {
processError(e, lastPosition, searchBinlogFile, 4L); processSinkError(e, lastPosition, searchBinlogFile, 4L);
} }
return running; return running;
@@ -700,21 +807,17 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
} }
} }
protected Entry parseAndProfilingIfNecessary(LogEvent bod) throws Exception { @Override
long startTs = -1; protected void processDumpError(Throwable e) {
boolean enabled = getProfilingEnabled(); if (e instanceof IOException) {
if (enabled) { String message = e.getMessage();
startTs = System.currentTimeMillis(); if (StringUtils.contains(message, "errno = 1236")) {
// 1236 errorCode代表ER_MASTER_FATAL_ERROR_READING_BINLOG
dumpErrorCount++;
} }
CanalEntry.Entry event = binlogParser.parse(bod);
if (enabled) {
this.parsingInterval = System.currentTimeMillis() - startTs;
} }
if (parsedEventCount.incrementAndGet() < 0) { super.processDumpError(e);
parsedEventCount.set(0);
}
return event;
} }
public void setSupportBinlogFormats(String formatStrs) { public void setSupportBinlogFormats(String formatStrs) {
@@ -800,4 +903,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
public void setHaController(CanalHAController haController) { public void setHaController(CanalHAController haController) {
this.haController = haController; this.haController = haController;
} }
public void setDumpErrorCountThreshold(int dumpErrorCountThreshold) {
this.dumpErrorCountThreshold = dumpErrorCountThreshold;
}
} }

View File

@@ -3,13 +3,12 @@ package com.alibaba.otter.canal.parse.inbound.mysql.dbsync;
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedByInterruptException; import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.SocketChannel;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel;
import com.taobao.tddl.dbsync.binlog.LogFetcher; import com.taobao.tddl.dbsync.binlog.LogFetcher;
/** /**
@@ -149,20 +148,8 @@ public class DirectLogFetcher extends LogFetcher {
private final boolean fetch0(final int off, final int len) throws IOException { private final boolean fetch0(final int off, final int len) throws IOException {
ensureCapacity(off + len); ensureCapacity(off + len);
ByteBuffer buffer = ByteBuffer.wrap(this.buffer, off, len); byte[] read = channel.read(len);
while (buffer.hasRemaining()) { System.arraycopy(read, 0, this.buffer, off, len);
int readNum = channel.read(buffer);
if (readNum == -1) {
throw new IOException("Unexpected End Stream");
}
}
// for (int count, n = 0; n < len; n += count) {
// if (0 > (count = input.read(buffer, off + n, len - n))) {
// // Reached end of input stream
// return false;
// }
// }
if (limit < off + len) limit = off + len; if (limit < off + len) limit = off + len;
return true; return true;

View File

@@ -6,6 +6,7 @@ import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.sql.Types; import java.sql.Types;
import java.util.Arrays;
import java.util.BitSet; import java.util.BitSet;
import java.util.List; import java.util.List;
@@ -21,7 +22,9 @@ import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
import com.alibaba.otter.canal.parse.inbound.BinlogParser; import com.alibaba.otter.canal.parse.inbound.BinlogParser;
import com.alibaba.otter.canal.parse.inbound.TableMeta; import com.alibaba.otter.canal.parse.inbound.TableMeta;
import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta; import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta;
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.SimpleDdlParser.DdlResult; import com.alibaba.otter.canal.parse.inbound.mysql.ddl.DdlResult;
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.DruidDdlParser;
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.SimpleDdlParser;
import com.alibaba.otter.canal.protocol.CanalEntry.Column; import com.alibaba.otter.canal.protocol.CanalEntry.Column;
import com.alibaba.otter.canal.protocol.CanalEntry.Entry; import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType; import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
@@ -33,6 +36,7 @@ import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
import com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin; import com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin;
import com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd; import com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd;
import com.alibaba.otter.canal.protocol.CanalEntry.Type; import com.alibaba.otter.canal.protocol.CanalEntry.Type;
import com.alibaba.otter.canal.protocol.position.EntryPosition;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import com.taobao.tddl.dbsync.binlog.LogEvent; import com.taobao.tddl.dbsync.binlog.LogEvent;
import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent; import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent;
@@ -84,8 +88,12 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
private boolean filterQueryDdl = false; private boolean filterQueryDdl = false;
// 是否跳过table相关的解析异常,比如表不存在或者列数量不匹配,issue 92 // 是否跳过table相关的解析异常,比如表不存在或者列数量不匹配,issue 92
private boolean filterTableError = false; private boolean filterTableError = false;
// 新增rows过滤,用于仅订阅除rows以外的数据
private boolean filterRows = false;
private boolean useDruidDdlFilter = true;
public Entry parse(LogEvent logEvent) throws CanalParseException { @Override
public Entry parse(LogEvent logEvent, boolean isSeek) throws CanalParseException {
if (logEvent == null || logEvent instanceof UnknownLogEvent) { if (logEvent == null || logEvent instanceof UnknownLogEvent) {
return null; return null;
} }
@@ -96,7 +104,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
binlogFileName = ((RotateLogEvent) logEvent).getFilename(); binlogFileName = ((RotateLogEvent) logEvent).getFilename();
break; break;
case LogEvent.QUERY_EVENT: case LogEvent.QUERY_EVENT:
return parseQueryEvent((QueryLogEvent) logEvent); return parseQueryEvent((QueryLogEvent) logEvent, isSeek);
case LogEvent.XID_EVENT: case LogEvent.XID_EVENT:
return parseXidEvent((XidLogEvent) logEvent); return parseXidEvent((XidLogEvent) logEvent);
case LogEvent.TABLE_MAP_EVENT: case LogEvent.TABLE_MAP_EVENT:
@@ -135,7 +143,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
} }
} }
private Entry parseQueryEvent(QueryLogEvent event) { private Entry parseQueryEvent(QueryLogEvent event, boolean isSeek) {
String queryString = event.getQuery(); String queryString = event.getQuery();
if (StringUtils.endsWithIgnoreCase(queryString, BEGIN)) { if (StringUtils.endsWithIgnoreCase(queryString, BEGIN)) {
TransactionBegin transactionBegin = createTransactionBegin(event.getSessionId()); TransactionBegin transactionBegin = createTransactionBegin(event.getSessionId());
@@ -146,16 +154,81 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
Header header = createHeader(binlogFileName, event.getHeader(), "", "", null); Header header = createHeader(binlogFileName, event.getHeader(), "", "", null);
return createEntry(header, EntryType.TRANSACTIONEND, transactionEnd.toByteString()); return createEntry(header, EntryType.TRANSACTIONEND, transactionEnd.toByteString());
} else { } else {
// DDL语句处理 boolean notFilter = false;
EventType type = EventType.QUERY;
String tableName = null;
String schemaName = null;
if (useDruidDdlFilter) {
List<DdlResult> results = DruidDdlParser.parse(queryString, event.getDbName());
for (DdlResult result : results) {
if (!processFilter(queryString, result)) {
// 只要有一个数据不进行过滤
notFilter = true;
}
}
if (results.size() > 0) {
// 如果针对多行的DDL,只能取第一条
type = results.get(0).getType();
schemaName = results.get(0).getSchemaName();
tableName = results.get(0).getTableName();
}
} else {
DdlResult result = SimpleDdlParser.parse(queryString, event.getDbName()); DdlResult result = SimpleDdlParser.parse(queryString, event.getDbName());
if (!processFilter(queryString, result)) {
String schemaName = event.getDbName(); notFilter = true;
if (StringUtils.isNotEmpty(result.getSchemaName())) {
schemaName = result.getSchemaName();
} }
type = result.getType();
schemaName = result.getSchemaName();
tableName = result.getTableName();
}
if (!notFilter) {
// 如果是过滤的数据就不处理了
return null;
}
if (!isSeek) {
// 使用新的表结构元数据管理方式
EntryPosition position = createPosition(event.getHeader());
tableMetaCache.apply(position, event.getDbName(), queryString, null);
}
Header header = createHeader(binlogFileName, event.getHeader(), schemaName, tableName, type);
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
if (type != EventType.QUERY) {
rowChangeBuider.setIsDdl(true);
}
rowChangeBuider.setSql(queryString);
if (StringUtils.isNotEmpty(event.getDbName())) {// 可能为空
rowChangeBuider.setDdlSchemaName(event.getDbName());
}
rowChangeBuider.setEventType(type);
return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
}
}
private boolean processFilter(String queryString, DdlResult result) {
String schemaName = result.getSchemaName();
String tableName = result.getTableName(); String tableName = result.getTableName();
EventType type = EventType.QUERY; // fixed issue https://github.com/alibaba/canal/issues/58
// 更新下table meta cache
if (tableMetaCache != null
&& (result.getType() == EventType.ALTER || result.getType() == EventType.ERASE || result.getType() == EventType.RENAME)) {
// 对外返回,保证兼容,还是返回QUERY类型,这里暂不解析tableName,所以无法支持过滤
for (DdlResult renameResult = result; renameResult != null; renameResult = renameResult.getRenameTableResult()) {
String schemaName0 = renameResult.getSchemaName();
String tableName0 = renameResult.getTableName();
if (StringUtils.isNotEmpty(tableName0)) {
// 如果解析到了正确的表信息,则根据全名进行清除
tableMetaCache.clearTableMeta(schemaName0, tableName0);
} else {
// 如果无法解析正确的表信息,则根据schema进行清除
tableMetaCache.clearTableMetaWithSchemaName(schemaName0);
}
}
}
// fixed issue https://github.com/alibaba/canal/issues/58 // fixed issue https://github.com/alibaba/canal/issues/58
if (result.getType() == EventType.ALTER || result.getType() == EventType.ERASE if (result.getType() == EventType.ALTER || result.getType() == EventType.ERASE
|| result.getType() == EventType.CREATE || result.getType() == EventType.TRUNCATE || result.getType() == EventType.CREATE || result.getType() == EventType.TRUNCATE
@@ -163,10 +236,9 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|| result.getType() == EventType.DINDEX) { // 针对DDL类型 || result.getType() == EventType.DINDEX) { // 针对DDL类型
if (filterQueryDdl) { if (filterQueryDdl) {
return null; return true;
} }
type = result.getType();
if (StringUtils.isEmpty(tableName) if (StringUtils.isEmpty(tableName)
|| (result.getType() == EventType.RENAME && StringUtils.isEmpty(result.getOriTableName()))) { || (result.getType() == EventType.RENAME && StringUtils.isEmpty(result.getOriTableName()))) {
// 如果解析不出tableName,记录一下日志,方便bugfix,目前直接抛出异常,中断解析 // 如果解析不出tableName,记录一下日志,方便bugfix,目前直接抛出异常,中断解析
@@ -181,11 +253,11 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
// rename校验只要源和目标满足一个就进行操作 // rename校验只要源和目标满足一个就进行操作
if (nameFilter != null if (nameFilter != null
&& !nameFilter.filter(result.getOriSchemaName() + "." + result.getOriTableName())) { && !nameFilter.filter(result.getOriSchemaName() + "." + result.getOriTableName())) {
return null; return true;
} }
} else { } else {
// 其他情况返回null // 其他情况返回null
return null; return true;
} }
} }
@@ -194,11 +266,11 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
// rename校验只要源和目标满足一个就进行操作 // rename校验只要源和目标满足一个就进行操作
if (nameBlackFilter != null if (nameBlackFilter != null
&& nameBlackFilter.filter(result.getOriSchemaName() + "." + result.getOriTableName())) { && nameBlackFilter.filter(result.getOriSchemaName() + "." + result.getOriTableName())) {
return null; return true;
} }
} else { } else {
// 其他情况返回null // 其他情况返回null
return null; return true;
} }
} }
} }
@@ -206,36 +278,13 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|| result.getType() == EventType.DELETE) { || result.getType() == EventType.DELETE) {
// 对外返回,保证兼容,还是返回QUERY类型,这里暂不解析tableName,所以无法支持过滤 // 对外返回,保证兼容,还是返回QUERY类型,这里暂不解析tableName,所以无法支持过滤
if (filterQueryDml) { if (filterQueryDml) {
return null; return true;
} }
} else if (filterQueryDcl) { } else if (filterQueryDcl) {
return null; return true;
} }
// 更新下table meta cache return false;
if (tableMetaCache != null
&& (result.getType() == EventType.ALTER || result.getType() == EventType.ERASE || result.getType() == EventType.RENAME)) {
if (StringUtils.isNotEmpty(tableName)) {
// 如果解析到了正确的表信息,则根据全名进行清除
tableMetaCache.clearTableMeta(schemaName, tableName);
} else {
// 如果无法解析正确的表信息,则根据schema进行清除
tableMetaCache.clearTableMetaWithSchemaName(schemaName);
}
}
Header header = createHeader(binlogFileName, event.getHeader(), schemaName, tableName, type);
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
if (result.getType() != EventType.QUERY) {
rowChangeBuider.setIsDdl(true);
}
rowChangeBuider.setSql(queryString);
if (StringUtils.isNotEmpty(event.getDbName())) {// 可能为空
rowChangeBuider.setDdlSchemaName(event.getDbName());
}
rowChangeBuider.setEventType(result.getType());
return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
}
} }
private Entry parseRowsQueryEvent(RowsQueryLogEvent event) { private Entry parseRowsQueryEvent(RowsQueryLogEvent event) {
@@ -246,7 +295,15 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
String queryString = null; String queryString = null;
try { try {
queryString = new String(event.getRowsQuery().getBytes(ISO_8859_1), charset.name()); queryString = new String(event.getRowsQuery().getBytes(ISO_8859_1), charset.name());
return buildQueryEntry(queryString, event.getHeader()); String tableName = null;
if (useDruidDdlFilter) {
List<DdlResult> results = DruidDdlParser.parse(queryString, null);
if (results.size() > 0) {
tableName = results.get(0).getTableName();
}
}
return buildQueryEntry(queryString, event.getHeader(), tableName);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new CanalParseException(e); throw new CanalParseException(e);
} }
@@ -297,6 +354,9 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
} }
private Entry parseRowsEvent(RowsLogEvent event) { private Entry parseRowsEvent(RowsLogEvent event) {
if (filterRows) {
return null;
}
try { try {
TableMapLogEvent table = event.getTable(); TableMapLogEvent table = event.getTable();
if (table == null) { if (table == null) {
@@ -304,6 +364,9 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
throw new TableIdNotFoundException("not found tableId:" + event.getTableId()); throw new TableIdNotFoundException("not found tableId:" + event.getTableId());
} }
boolean isRDSHeartBeat = tableMetaCache.isOnRDS()
&& isRDSHeartBeat(table.getDbName(), table.getTableName());
String fullname = table.getDbName() + "." + table.getTableName(); String fullname = table.getDbName() + "." + table.getTableName();
// check name filter // check name filter
if (nameFilter != null && !nameFilter.filter(fullname)) { if (nameFilter != null && !nameFilter.filter(fullname)) {
@@ -313,6 +376,19 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
return null; return null;
} }
// if (isHeartBeat || isRDSHeartBeat) {
// // 忽略rds模式的mysql.ha_health_check心跳数据
// return null;
// }
TableMeta tableMeta = null;
if (isRDSHeartBeat) {
// 处理rds模式的mysql.ha_health_check心跳数据
// 主要RDS的心跳表基本无权限,需要mock一个tableMeta
FieldMeta idMeta = new FieldMeta("id", "bigint(20)", true, false, "0");
FieldMeta typeMeta = new FieldMeta("type", "char(1)", false, true, "0");
tableMeta = new TableMeta(table.getDbName(), table.getTableName(), Arrays.asList(idMeta, typeMeta));
}
EventType eventType = null; EventType eventType = null;
int type = event.getHeader().getType(); int type = event.getHeader().getType();
if (LogEvent.WRITE_ROWS_EVENT_V1 == type || LogEvent.WRITE_ROWS_EVENT == type) { if (LogEvent.WRITE_ROWS_EVENT_V1 == type || LogEvent.WRITE_ROWS_EVENT == type) {
@@ -330,6 +406,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
table.getDbName(), table.getDbName(),
table.getTableName(), table.getTableName(),
eventType); eventType);
EntryPosition position = createPosition(event.getHeader());
RowChange.Builder rowChangeBuider = RowChange.newBuilder(); RowChange.Builder rowChangeBuider = RowChange.newBuilder();
rowChangeBuider.setTableId(event.getTableId()); rowChangeBuider.setTableId(event.getTableId());
rowChangeBuider.setIsDdl(false); rowChangeBuider.setIsDdl(false);
@@ -337,11 +414,11 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
rowChangeBuider.setEventType(eventType); rowChangeBuider.setEventType(eventType);
RowsLogBuffer buffer = event.getRowsBuf(charset.name()); RowsLogBuffer buffer = event.getRowsBuf(charset.name());
BitSet columns = event.getColumns(); BitSet columns = event.getColumns();
BitSet changeColumns = event.getColumns(); BitSet changeColumns = event.getChangeColumns();
boolean tableError = false; boolean tableError = false;
TableMeta tableMeta = null; if (tableMetaCache != null && tableMeta == null) {// 入错存在table meta
if (tableMetaCache != null) {// 入错存在table meta cache // cache
tableMeta = getTableMeta(table.getDbName(), table.getTableName(), true); tableMeta = getTableMeta(table.getDbName(), table.getTableName(), true, position);
if (tableMeta == null) { if (tableMeta == null) {
tableError = true; tableError = true;
if (!filterTableError) { if (!filterTableError) {
@@ -367,7 +444,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
break; break;
} }
tableError |= parseOneRow(rowDataBuilder, event, buffer, event.getChangeColumns(), true, tableMeta); tableError |= parseOneRow(rowDataBuilder, event, buffer, changeColumns, true, tableMeta);
} }
rowChangeBuider.addRowDatas(rowDataBuilder.build()); rowChangeBuider.addRowDatas(rowDataBuilder.build());
@@ -387,21 +464,42 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
} }
} }
private EntryPosition createPosition(LogHeader logHeader) {
return new EntryPosition(binlogFileName,
logHeader.getLogPos(),
logHeader.getWhen() * 1000L,
logHeader.getServerId()); // 记录到秒
}
private boolean parseOneRow(RowData.Builder rowDataBuilder, RowsLogEvent event, RowsLogBuffer buffer, BitSet cols, private boolean parseOneRow(RowData.Builder rowDataBuilder, RowsLogEvent event, RowsLogBuffer buffer, BitSet cols,
boolean isAfter, TableMeta tableMeta) throws UnsupportedEncodingException { boolean isAfter, TableMeta tableMeta) throws UnsupportedEncodingException {
final int columnCnt = event.getTable().getColumnCnt(); int columnCnt = event.getTable().getColumnCnt();
final ColumnInfo[] columnInfo = event.getTable().getColumnInfo(); ColumnInfo[] columnInfo = event.getTable().getColumnInfo();
boolean tableError = false; boolean tableError = false;
// check table fileds count,只能处理加字段 // check table fileds count,只能处理加字段
if (tableMeta != null && columnInfo.length > tableMeta.getFileds().size()) { boolean existRDSNoPrimaryKey = false;
if (tableMeta != null && columnInfo.length > tableMeta.getFields().size()) {
if (tableMetaCache.isOnRDS()) {
// 特殊处理下RDS的场景
List<FieldMeta> primaryKeys = tableMeta.getPrimaryFields();
if (primaryKeys == null || primaryKeys.isEmpty()) {
if (columnInfo.length == tableMeta.getFields().size() + 1
&& columnInfo[columnInfo.length - 1].type == LogEvent.MYSQL_TYPE_LONGLONG) {
existRDSNoPrimaryKey = true;
}
}
}
EntryPosition position = createPosition(event.getHeader());
if (!existRDSNoPrimaryKey) {
// online ddl增加字段操作步骤: // online ddl增加字段操作步骤:
// 1. 新增一张临时表,将需要做ddl表的数据全量导入 // 1. 新增一张临时表,将需要做ddl表的数据全量导入
// 2. 在老表上建立I/U/D的trigger,增量的将数据插入到临时表 // 2. 在老表上建立I/U/D的trigger,增量的将数据插入到临时表
// 3. 锁住应用请求,将临时表rename为老表的名字,完成增加字段的操作 // 3. 锁住应用请求,将临时表rename为老表的名字,完成增加字段的操作
// 尝试做一次reload,可能因为ddl没有正确解析,或者使用了类似online ddl的操作 // 尝试做一次reload,可能因为ddl没有正确解析,或者使用了类似online ddl的操作
// 因为online ddl没有对应表名的alter语法,所以不会有clear cache的操作 // 因为online ddl没有对应表名的alter语法,所以不会有clear cache的操作
tableMeta = getTableMeta(event.getTable().getDbName(), event.getTable().getTableName(), false);// 强制重新获取一次 tableMeta = getTableMeta(event.getTable().getDbName(), event.getTable().getTableName(), false, position);// 强制重新获取一次
if (tableMeta == null) { if (tableMeta == null) {
tableError = true; tableError = true;
if (!filterTableError) { if (!filterTableError) {
@@ -411,13 +509,17 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
} }
// 在做一次判断 // 在做一次判断
if (tableMeta != null && columnInfo.length > tableMeta.getFileds().size()) { if (tableMeta != null && columnInfo.length > tableMeta.getFields().size()) {
tableError = true; tableError = true;
if (!filterTableError) { if (!filterTableError) {
throw new CanalParseException("column size is not match for table:" + tableMeta.getFullName() + "," throw new CanalParseException("column size is not match for table:" + tableMeta.getFullName()
+ columnInfo.length + " vs " + tableMeta.getFileds().size()); + "," + columnInfo.length + " vs " + tableMeta.getFields().size());
} }
} }
} else {
logger.warn("[" + event.getTable().getDbName() + "." + event.getTable().getTableName()
+ "] is no primary key , skip alibaba_rds_row_id column");
}
} }
for (int i = 0; i < columnCnt; i++) { for (int i = 0; i < columnCnt; i++) {
@@ -427,12 +529,18 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
continue; continue;
} }
if (existRDSNoPrimaryKey && i == columnCnt - 1 && info.type == LogEvent.MYSQL_TYPE_LONGLONG) {
// 不解析最后一列
buffer.nextValue(info.type, info.meta, false);
continue;
}
Column.Builder columnBuilder = Column.newBuilder(); Column.Builder columnBuilder = Column.newBuilder();
FieldMeta fieldMeta = null; FieldMeta fieldMeta = null;
if (tableMeta != null && !tableError) { if (tableMeta != null && !tableError) {
// 处理file meta // 处理file meta
fieldMeta = tableMeta.getFileds().get(i); fieldMeta = tableMeta.getFields().get(i);
columnBuilder.setName(fieldMeta.getColumnName()); columnBuilder.setName(fieldMeta.getColumnName());
columnBuilder.setIsKey(fieldMeta.isKey()); columnBuilder.setIsKey(fieldMeta.isKey());
// 增加mysql type类型,issue 73 // 增加mysql type类型,issue 73
@@ -452,6 +560,10 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
} }
} }
buffer.nextValue(info.type, info.meta, isBinary); buffer.nextValue(info.type, info.meta, isBinary);
if (existRDSNoPrimaryKey && i == columnCnt - 1 && info.type == LogEvent.MYSQL_TYPE_LONGLONG) {
// 不解析最后一列
continue;
}
int javaType = buffer.getJavaType(); int javaType = buffer.getJavaType();
if (buffer.isNull()) { if (buffer.isNull()) {
@@ -569,6 +681,14 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
} }
private Entry buildQueryEntry(String queryString, LogHeader logHeader, String tableName) {
Header header = createHeader(binlogFileName, logHeader, "", tableName, EventType.QUERY);
RowChange.Builder rowChangeBuider = RowChange.newBuilder();
rowChangeBuider.setSql(queryString);
rowChangeBuider.setEventType(EventType.QUERY);
return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString());
}
private Entry buildQueryEntry(String queryString, LogHeader logHeader) { private Entry buildQueryEntry(String queryString, LogHeader logHeader) {
Header header = createHeader(binlogFileName, logHeader, "", "", EventType.QUERY); Header header = createHeader(binlogFileName, logHeader, "", "", EventType.QUERY);
RowChange.Builder rowChangeBuider = RowChange.newBuilder(); RowChange.Builder rowChangeBuider = RowChange.newBuilder();
@@ -615,8 +735,9 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
if (column.getIsNull() && newValue == null) { if (column.getIsNull() && newValue == null) {
// 如果全是null // 如果全是null
return false; return false;
} else if (newValue != null && column.getValue().equals(newValue)) { } else if (newValue != null && (!column.getIsNull() && column.getValue().equals(newValue))) {
// 如果不围null,并且相等 // fixed issue #135, old column is Null
// 如果不为null,并且相等
return false; return false;
} }
} }
@@ -626,9 +747,9 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
return true; return true;
} }
private TableMeta getTableMeta(String dbName, String tbName, boolean useCache) { private TableMeta getTableMeta(String dbName, String tbName, boolean useCache, EntryPosition position) {
try { try {
return tableMetaCache.getTableMeta(dbName, tbName, useCache); return tableMetaCache.getTableMeta(dbName, tbName, useCache, position);
} catch (Exception e) { } catch (Exception e) {
String message = ExceptionUtils.getRootCauseMessage(e); String message = ExceptionUtils.getRootCauseMessage(e);
if (filterTableError) { if (filterTableError) {
@@ -646,6 +767,10 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
|| "TEXT".equalsIgnoreCase(columnType) || "TINYTEXT".equalsIgnoreCase(columnType); || "TEXT".equalsIgnoreCase(columnType) || "TINYTEXT".equalsIgnoreCase(columnType);
} }
private boolean isRDSHeartBeat(String schema, String table) {
return "mysql".equalsIgnoreCase(schema) && "ha_health_check".equalsIgnoreCase(table);
}
public static TransactionBegin createTransactionBegin(long threadId) { public static TransactionBegin createTransactionBegin(long threadId) {
TransactionBegin.Builder beginBuilder = TransactionBegin.newBuilder(); TransactionBegin.Builder beginBuilder = TransactionBegin.newBuilder();
beginBuilder.setThreadId(threadId); beginBuilder.setThreadId(threadId);
@@ -705,4 +830,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
this.filterTableError = filterTableError; this.filterTableError = filterTableError;
} }
public void setFilterRows(boolean filterRows) {
this.filterRows = filterRows;
}
} }

View File

@@ -14,8 +14,11 @@ import com.alibaba.otter.canal.parse.exception.CanalParseException;
import com.alibaba.otter.canal.parse.inbound.TableMeta; import com.alibaba.otter.canal.parse.inbound.TableMeta;
import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta; import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta;
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection;
import com.google.common.base.Function; import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDB;
import com.google.common.collect.MigrateMap; import com.alibaba.otter.canal.protocol.position.EntryPosition;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
/** /**
* 处理table meta解析和缓存 * 处理table meta解析和缓存
@@ -32,22 +35,28 @@ public class TableMetaCache {
public static final String COLUMN_DEFAULT = "COLUMN_DEFAULT"; public static final String COLUMN_DEFAULT = "COLUMN_DEFAULT";
public static final String EXTRA = "EXTRA"; public static final String EXTRA = "EXTRA";
private MysqlConnection connection; private MysqlConnection connection;
private boolean isOnRDS = false;
private TableMetaTSDB tableMetaTSDB;
// 第一层tableId,第二层schema.table,解决tableId重复,对应多张表 // 第一层tableId,第二层schema.table,解决tableId重复,对应多张表
private Map<String, TableMeta> tableMetaCache; private LoadingCache<String, TableMeta> tableMetaDB;
public TableMetaCache(MysqlConnection con){ public TableMetaCache(MysqlConnection con, TableMetaTSDB tableMetaTSDB){
this.connection = con; this.connection = con;
tableMetaCache = MigrateMap.makeComputingMap(new Function<String, TableMeta>() { this.tableMetaTSDB = tableMetaTSDB;
// 如果持久存储的表结构为空,从db里面获取下
if (tableMetaTSDB == null) {
this.tableMetaDB = CacheBuilder.newBuilder().build(new CacheLoader<String, TableMeta>() {
public TableMeta apply(String name) { @Override
public TableMeta load(String name) throws Exception {
try { try {
return getTableMeta0(name); return getTableMetaByDB(name);
} catch (IOException e) { } catch (CanalParseException e) {
// 尝试做一次retry操作 // 尝试做一次retry操作
try { try {
connection.reconnect(); connection.reconnect();
return getTableMeta0(name); return getTableMetaByDB(name);
} catch (IOException e1) { } catch (IOException e1) {
throw new CanalParseException("fetch failed by table meta:" + name, e1); throw new CanalParseException("fetch failed by table meta:" + name, e1);
} }
@@ -55,50 +64,26 @@ public class TableMetaCache {
} }
}); });
} }
public TableMeta getTableMeta(String schema, String table) { try {
return getTableMeta(schema, table, true); ResultSetPacket packet = connection.query("show global variables like 'rds\\_%'");
if (packet.getFieldValues().size() > 0) {
isOnRDS = true;
} }
} catch (IOException e) {
public TableMeta getTableMeta(String schema, String table, boolean useCache) {
if (!useCache) {
tableMetaCache.remove(getFullName(schema, table));
}
return tableMetaCache.get(getFullName(schema, table));
}
public void clearTableMeta(String schema, String table) {
tableMetaCache.remove(getFullName(schema, table));
}
public void clearTableMetaWithSchemaName(String schema) {
// Set<String> removeNames = new HashSet<String>(); //
// 存一份临时变量,避免在遍历的时候进行删除
for (String name : tableMetaCache.keySet()) {
if (StringUtils.startsWithIgnoreCase(name, schema + ".")) {
// removeNames.add(name);
tableMetaCache.remove(name);
} }
} }
// for (String name : removeNames) { private TableMeta getTableMetaByDB(String fullname) throws IOException {
// tables.remove(name);
// }
}
public void clearTableMeta() {
tableMetaCache.clear();
}
private TableMeta getTableMeta0(String fullname) throws IOException {
ResultSetPacket packet = connection.query("desc " + fullname); ResultSetPacket packet = connection.query("desc " + fullname);
return new TableMeta(fullname, parserTableMeta(packet)); String[] names = StringUtils.split(fullname, "`.`");
String schema = names[0];
String table = names[1].substring(0, names[1].length());
return new TableMeta(schema, table, parserTableMeta(packet));
} }
private List<FieldMeta> parserTableMeta(ResultSetPacket packet) { public static List<FieldMeta> parserTableMeta(ResultSetPacket packet) {
Map<String, Integer> nameMaps = new HashMap<String, Integer>(6, 1f); Map<String, Integer> nameMaps = new HashMap<String, Integer>(6, 1f);
int index = 0; int index = 0;
@@ -114,8 +99,10 @@ public class TableMetaCache {
// 做一个优化,使用String.intern(),共享String对象,减少内存使用 // 做一个优化,使用String.intern(),共享String对象,减少内存使用
meta.setColumnName(packet.getFieldValues().get(nameMaps.get(COLUMN_NAME) + i * size).intern()); meta.setColumnName(packet.getFieldValues().get(nameMaps.get(COLUMN_NAME) + i * size).intern());
meta.setColumnType(packet.getFieldValues().get(nameMaps.get(COLUMN_TYPE) + i * size)); meta.setColumnType(packet.getFieldValues().get(nameMaps.get(COLUMN_TYPE) + i * size));
meta.setIsNullable(packet.getFieldValues().get(nameMaps.get(IS_NULLABLE) + i * size)); meta.setNullable(StringUtils.equalsIgnoreCase(packet.getFieldValues().get(nameMaps.get(IS_NULLABLE) + i
meta.setIskey(packet.getFieldValues().get(nameMaps.get(COLUMN_KEY) + i * size)); * size),
"YES"));
meta.setKey("PRI".equalsIgnoreCase(packet.getFieldValues().get(nameMaps.get(COLUMN_KEY) + i * size)));
meta.setDefaultValue(packet.getFieldValues().get(nameMaps.get(COLUMN_DEFAULT) + i * size)); meta.setDefaultValue(packet.getFieldValues().get(nameMaps.get(COLUMN_DEFAULT) + i * size));
meta.setExtra(packet.getFieldValues().get(nameMaps.get(EXTRA) + i * size)); meta.setExtra(packet.getFieldValues().get(nameMaps.get(EXTRA) + i * size));
@@ -125,6 +112,98 @@ public class TableMetaCache {
return result; return result;
} }
public TableMeta getTableMeta(String schema, String table) {
return getTableMeta(schema, table, true);
}
public TableMeta getTableMeta(String schema, String table, boolean useCache) {
if (!useCache) {
tableMetaDB.invalidate(getFullName(schema, table));
}
return tableMetaDB.getUnchecked(getFullName(schema, table));
}
public TableMeta getTableMeta(String schema, String table, EntryPosition position) {
return getTableMeta(schema, table, true, position);
}
public TableMeta getTableMeta(String schema, String table, boolean useCache, EntryPosition position) {
TableMeta tableMeta = null;
if (tableMetaTSDB != null) {
tableMeta = tableMetaTSDB.find(schema, table);
if (tableMeta == null) {
// 因为条件变化,可能第一次的tableMeta没取到,需要从db获取一次,并记录到snapshot中
String fullName = getFullName(schema, table);
try {
ResultSetPacket packet = connection.query("show create table " + fullName);
String createDDL = null;
if (packet.getFieldValues().size() > 0) {
createDDL = packet.getFieldValues().get(1);
}
// 强制覆盖掉内存值
tableMetaTSDB.apply(position, schema, createDDL, "first");
tableMeta = tableMetaTSDB.find(schema, table);
} catch (IOException e) {
throw new CanalParseException("fetch failed by table meta:" + fullName, e);
}
}
return tableMeta;
} else {
if (!useCache) {
tableMetaDB.invalidate(getFullName(schema, table));
}
return tableMetaDB.getUnchecked(getFullName(schema, table));
}
}
public void clearTableMeta(String schema, String table) {
if (tableMetaTSDB != null) {
// tsdb不需要做,会基于ddl sql自动清理
} else {
tableMetaDB.invalidate(getFullName(schema, table));
}
}
public void clearTableMetaWithSchemaName(String schema) {
if (tableMetaTSDB != null) {
// tsdb不需要做,会基于ddl sql自动清理
} else {
for (String name : tableMetaDB.asMap().keySet()) {
if (StringUtils.startsWithIgnoreCase(name, schema + ".")) {
// removeNames.add(name);
tableMetaDB.invalidate(name);
}
}
}
}
public void clearTableMeta() {
if (tableMetaTSDB != null) {
// tsdb不需要做,会基于ddl sql自动清理
} else {
tableMetaDB.invalidateAll();
}
}
/**
* 更新一下本地的表结构内存
*
* @param position
* @param schema
* @param ddl
* @return
*/
public boolean apply(EntryPosition position, String schema, String ddl, String extra) {
if (tableMetaTSDB != null) {
return tableMetaTSDB.apply(position, schema, ddl, extra);
} else {
// ignore
return true;
}
}
private String getFullName(String schema, String table) { private String getFullName(String schema, String table) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
return builder.append('`') return builder.append('`')
@@ -136,4 +215,13 @@ public class TableMetaCache {
.append('`') .append('`')
.toString(); .toString();
} }
public boolean isOnRDS() {
return isOnRDS;
}
public void setOnRDS(boolean isOnRDS) {
this.isOnRDS = isOnRDS;
}
} }

View File

@@ -0,0 +1,115 @@
package com.alibaba.otter.canal.parse.inbound.mysql.ddl;
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
/**
* @author agapple 2017年8月1日 下午7:30:42
* @since 3.2.5
*/
public class DdlResult {
private String schemaName;
private String tableName;
private String oriSchemaName; // rename ddl中的源表
private String oriTableName; // rename ddl中的目标表
private EventType type;
private DdlResult renameTableResult; // 多个rename table的存储
/*
* RENAME TABLE tbl_name TO new_tbl_name [, tbl_name2 TO new_tbl_name2] ...
*/
public DdlResult(){
}
public DdlResult(String schemaName){
this.schemaName = schemaName;
}
public DdlResult(String schemaName, String tableName){
this.schemaName = schemaName;
this.tableName = tableName;
}
public DdlResult(String schemaName, String tableName, String oriSchemaName, String oriTableName){
this.schemaName = schemaName;
this.tableName = tableName;
this.oriSchemaName = oriSchemaName;
this.oriTableName = oriTableName;
}
public String getSchemaName() {
return schemaName;
}
public void setSchemaName(String schemaName) {
this.schemaName = schemaName;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public EventType getType() {
return type;
}
public void setType(EventType type) {
this.type = type;
}
public String getOriSchemaName() {
return oriSchemaName;
}
public void setOriSchemaName(String oriSchemaName) {
this.oriSchemaName = oriSchemaName;
}
public String getOriTableName() {
return oriTableName;
}
public void setOriTableName(String oriTableName) {
this.oriTableName = oriTableName;
}
public DdlResult getRenameTableResult() {
return renameTableResult;
}
public void setRenameTableResult(DdlResult renameTableResult) {
this.renameTableResult = renameTableResult;
}
@Override
public DdlResult clone() {
DdlResult result = new DdlResult();
result.setOriSchemaName(oriSchemaName);
result.setOriTableName(oriTableName);
result.setSchemaName(schemaName);
result.setTableName(tableName);
// result.setType(type);
return result;
}
@Override
public String toString() {
DdlResult ddlResult = this;
StringBuffer sb = new StringBuffer();
do {
sb.append(String.format("DdlResult [schemaName=%s , tableName=%s , oriSchemaName=%s , oriTableName=%s , type=%s ];",
ddlResult.schemaName,
ddlResult.tableName,
ddlResult.oriSchemaName,
ddlResult.oriTableName,
ddlResult.type));
ddlResult = ddlResult.renameTableResult;
} while (ddlResult != null);
return sb.toString();
}
}

View File

@@ -0,0 +1,201 @@
package com.alibaba.otter.canal.parse.inbound.mysql.ddl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddConstraint;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddIndex;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableDropConstraint;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableDropIndex;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableDropKey;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableItem;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableRename;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableStatement;
import com.alibaba.druid.sql.ast.statement.SQLConstraint;
import com.alibaba.druid.sql.ast.statement.SQLCreateIndexStatement;
import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement;
import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement;
import com.alibaba.druid.sql.ast.statement.SQLDropIndexStatement;
import com.alibaba.druid.sql.ast.statement.SQLDropTableStatement;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLInsertStatement;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.ast.statement.SQLTruncateStatement;
import com.alibaba.druid.sql.ast.statement.SQLUnique;
import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement.Item;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.util.JdbcConstants;
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
/**
* @author agapple 2017年7月27日 下午4:05:34
* @since 1.0.25
*/
public class DruidDdlParser {
public static List<DdlResult> parse(String queryString, String schmeaName) {
List<SQLStatement> stmtList = null;
try {
stmtList = SQLUtils.parseStatements(queryString, JdbcConstants.MYSQL, false);
} catch (ParserException e) {
// 可能存在一些SQL是不支持的,比如存储过程
DdlResult ddlResult = new DdlResult();
ddlResult.setType(EventType.QUERY);
return Arrays.asList(ddlResult);
}
List<DdlResult> ddlResults = new ArrayList<DdlResult>();
for (SQLStatement statement : stmtList) {
if (statement instanceof SQLCreateTableStatement) {
DdlResult ddlResult = new DdlResult();
SQLCreateTableStatement createTable = (SQLCreateTableStatement) statement;
processName(ddlResult, schmeaName, createTable.getName(), false);
ddlResult.setType(EventType.CREATE);
ddlResults.add(ddlResult);
} else if (statement instanceof SQLAlterTableStatement) {
SQLAlterTableStatement alterTable = (SQLAlterTableStatement) statement;
for (SQLAlterTableItem item : alterTable.getItems()) {
if (item instanceof SQLAlterTableRename) {
DdlResult ddlResult = new DdlResult();
processName(ddlResult, schmeaName, alterTable.getName(), true);
processName(ddlResult, schmeaName, ((SQLAlterTableRename) item).getToName(), false);
ddlResults.add(ddlResult);
} else if (item instanceof SQLAlterTableAddIndex) {
DdlResult ddlResult = new DdlResult();
processName(ddlResult, schmeaName, alterTable.getName(), false);
ddlResult.setType(EventType.CINDEX);
ddlResults.add(ddlResult);
} else if (item instanceof SQLAlterTableDropIndex || item instanceof SQLAlterTableDropKey) {
DdlResult ddlResult = new DdlResult();
processName(ddlResult, schmeaName, alterTable.getName(), false);
ddlResult.setType(EventType.DINDEX);
ddlResults.add(ddlResult);
} else if (item instanceof SQLAlterTableAddConstraint) {
DdlResult ddlResult = new DdlResult();
processName(ddlResult, schmeaName, alterTable.getName(), false);
SQLConstraint constraint = ((SQLAlterTableAddConstraint) item).getConstraint();
if (constraint instanceof SQLUnique) {
ddlResult.setType(EventType.CINDEX);
ddlResults.add(ddlResult);
}
} else if (item instanceof SQLAlterTableDropConstraint) {
DdlResult ddlResult = new DdlResult();
processName(ddlResult, schmeaName, alterTable.getName(), false);
ddlResult.setType(EventType.DINDEX);
ddlResults.add(ddlResult);
} else {
DdlResult ddlResult = new DdlResult();
processName(ddlResult, schmeaName, alterTable.getName(), false);
ddlResult.setType(EventType.ALTER);
ddlResults.add(ddlResult);
}
}
} else if (statement instanceof SQLDropTableStatement) {
SQLDropTableStatement dropTable = (SQLDropTableStatement) statement;
for (SQLExprTableSource tableSource : dropTable.getTableSources()) {
DdlResult ddlResult = new DdlResult();
processName(ddlResult, schmeaName, tableSource.getExpr(), false);
ddlResult.setType(EventType.ERASE);
ddlResults.add(ddlResult);
}
} else if (statement instanceof SQLCreateIndexStatement) {
SQLCreateIndexStatement createIndex = (SQLCreateIndexStatement) statement;
SQLTableSource tableSource = createIndex.getTable();
DdlResult ddlResult = new DdlResult();
processName(ddlResult, schmeaName, ((SQLExprTableSource) tableSource).getExpr(), false);
ddlResult.setType(EventType.CINDEX);
ddlResults.add(ddlResult);
} else if (statement instanceof SQLDropIndexStatement) {
SQLDropIndexStatement dropIndex = (SQLDropIndexStatement) statement;
SQLExprTableSource tableSource = dropIndex.getTableName();
DdlResult ddlResult = new DdlResult();
processName(ddlResult, schmeaName, tableSource.getExpr(), false);
ddlResult.setType(EventType.DINDEX);
ddlResults.add(ddlResult);
} else if (statement instanceof SQLTruncateStatement) {
SQLTruncateStatement truncate = (SQLTruncateStatement) statement;
for (SQLExprTableSource tableSource : truncate.getTableSources()) {
DdlResult ddlResult = new DdlResult();
processName(ddlResult, schmeaName, tableSource.getExpr(), false);
ddlResult.setType(EventType.TRUNCATE);
ddlResults.add(ddlResult);
}
} else if (statement instanceof MySqlRenameTableStatement) {
MySqlRenameTableStatement rename = (MySqlRenameTableStatement) statement;
for (Item item : rename.getItems()) {
DdlResult ddlResult = new DdlResult();
processName(ddlResult, schmeaName, item.getName(), true);
processName(ddlResult, schmeaName, item.getTo(), false);
ddlResult.setType(EventType.RENAME);
ddlResults.add(ddlResult);
}
} else if (statement instanceof SQLInsertStatement) {
DdlResult ddlResult = new DdlResult();
SQLInsertStatement insert = (SQLInsertStatement) statement;
processName(ddlResult, schmeaName, insert.getTableName(), false);
ddlResult.setType(EventType.INSERT);
ddlResults.add(ddlResult);
} else if (statement instanceof SQLUpdateStatement) {
DdlResult ddlResult = new DdlResult();
SQLUpdateStatement update = (SQLUpdateStatement) statement;
// 拿到的表名可能为null,比如update a,b set a.id=x
processName(ddlResult, schmeaName, update.getTableName(), false);
ddlResult.setType(EventType.UPDATE);
ddlResults.add(ddlResult);
} else if (statement instanceof SQLDeleteStatement) {
DdlResult ddlResult = new DdlResult();
SQLDeleteStatement delete = (SQLDeleteStatement) statement;
// 拿到的表名可能为null,比如delete a,b from a where a.id = b.id
processName(ddlResult, schmeaName, delete.getTableName(), false);
ddlResult.setType(EventType.DELETE);
ddlResults.add(ddlResult);
}
}
return ddlResults;
}
private static void processName(DdlResult ddlResult, String schema, SQLExpr sqlName, boolean isOri) {
if (sqlName == null) {
return;
}
String table = null;
if (sqlName instanceof SQLPropertyExpr) {
SQLIdentifierExpr owner = (SQLIdentifierExpr) ((SQLPropertyExpr) sqlName).getOwner();
schema = unescapeName(owner.getName());
table = unescapeName(((SQLPropertyExpr) sqlName).getName());
} else if (sqlName instanceof SQLIdentifierExpr) {
table = unescapeName(((SQLIdentifierExpr) sqlName).getName());
}
if (isOri) {
ddlResult.setOriSchemaName(schema);
ddlResult.setOriTableName(table);
} else {
ddlResult.setSchemaName(schema);
ddlResult.setTableName(table);
}
}
public static String unescapeName(String name) {
if (name.length() > 2) {
char c0 = name.charAt(0);
char x0 = name.charAt(name.length() - 1);
if ((c0 == '"' && x0 == '"') || (c0 == '`' && x0 == '`')) {
return name.substring(1, name.length() - 1);
}
}
return name;
}
}

View File

@@ -1,4 +1,4 @@
package com.alibaba.otter.canal.parse.inbound.mysql.dbsync; package com.alibaba.otter.canal.parse.inbound.mysql.ddl;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.oro.text.regex.Perl5Matcher; import org.apache.oro.text.regex.Perl5Matcher;
@@ -31,7 +31,9 @@ public class SimpleDdlParser {
public static final String INSERT_PATTERN = "^\\s*(INSERT|MERGE|REPLACE)(.*)$"; public static final String INSERT_PATTERN = "^\\s*(INSERT|MERGE|REPLACE)(.*)$";
public static final String UPDATE_PATTERN = "^\\s*UPDATE(.*)$"; public static final String UPDATE_PATTERN = "^\\s*UPDATE(.*)$";
public static final String DELETE_PATTERN = "^\\s*DELETE(.*)$"; public static final String DELETE_PATTERN = "^\\s*DELETE(.*)$";
public static final String RENAME_PATTERN = "^\\s*RENAME\\s*TABLE\\s*(.*?)\\s*TO\\s*(.*?)$"; public static final String RENAME_PATTERN = "^\\s*RENAME\\s+TABLE\\s+(.+?)\\s+TO\\s+(.+?)$";
public static final String RENAME_REMNANT_PATTERN = "^\\s*(.+?)\\s+TO\\s+(.+?)$";
/** /**
* <pre> * <pre>
* CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name * CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name
@@ -74,6 +76,18 @@ public class SimpleDdlParser {
result = parseRename(queryString, schmeaName, RENAME_PATTERN); result = parseRename(queryString, schmeaName, RENAME_PATTERN);
if (result != null) { if (result != null) {
result.setType(EventType.RENAME); result.setType(EventType.RENAME);
String[] renameStrings = queryString.split(",");
if (renameStrings.length > 1) {
DdlResult lastResult = result;
for (int i = 1; i < renameStrings.length; i++) {
DdlResult ddlResult = parseRename(renameStrings[i], schmeaName, RENAME_REMNANT_PATTERN);
ddlResult.setType(EventType.RENAME);
lastResult.setRenameTableResult(ddlResult);
lastResult = ddlResult;
}
}
return result; return result;
} }
@@ -204,79 +218,4 @@ public class SimpleDdlParser {
sql = sb.toString(); sql = sb.toString();
} }
} }
public static class DdlResult {
private String schemaName;
private String tableName;
private String oriSchemaName; // rename ddl中的源表
private String oriTableName; // rename ddl中的目标表
private EventType type;
public DdlResult(){
}
public DdlResult(String schemaName){
this.schemaName = schemaName;
}
public DdlResult(String schemaName, String tableName){
this.schemaName = schemaName;
this.tableName = tableName;
}
public DdlResult(String schemaName, String tableName, String oriSchemaName, String oriTableName){
this.schemaName = schemaName;
this.tableName = tableName;
this.oriSchemaName = oriSchemaName;
this.oriTableName = oriTableName;
}
public String getSchemaName() {
return schemaName;
}
public void setSchemaName(String schemaName) {
this.schemaName = schemaName;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public EventType getType() {
return type;
}
public void setType(EventType type) {
this.type = type;
}
public String getOriSchemaName() {
return oriSchemaName;
}
public void setOriSchemaName(String oriSchemaName) {
this.oriSchemaName = oriSchemaName;
}
public String getOriTableName() {
return oriTableName;
}
public void setOriTableName(String oriTableName) {
this.oriTableName = oriTableName;
}
@Override
public String toString() {
return "DdlResult [schemaName=" + schemaName + ", tableName=" + tableName + ", oriSchemaName="
+ oriSchemaName + ", oriTableName=" + oriTableName + ", type=" + type + "]";
}
}
} }

View File

@@ -9,6 +9,8 @@ import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.IOFileFilter; import org.apache.commons.io.filefilter.IOFileFilter;
@@ -188,7 +190,9 @@ public class BinLogFileQueue {
files.addAll(FileUtils.listFiles(directory, new IOFileFilter() { files.addAll(FileUtils.listFiles(directory, new IOFileFilter() {
public boolean accept(File file) { public boolean accept(File file) {
return file.getName().startsWith(baseName); Pattern pattern = Pattern.compile("\\d+$");
Matcher matcher = pattern.matcher(file.getName());
return file.getName().startsWith(baseName) && matcher.find();
} }
public boolean accept(File dir, String name) { public boolean accept(File dir, String name) {

View File

@@ -0,0 +1,368 @@
package com.alibaba.otter.canal.parse.inbound.mysql.rds;
import static org.apache.http.client.config.RequestConfig.custom;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.net.ssl.SSLContext;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
public class HttpHelper {
protected static final Logger logger = LoggerFactory.getLogger(HttpHelper.class);
public static byte[] getBytes(String url, int timeout) throws Exception {
long start = System.currentTimeMillis();
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setMaxConnPerRoute(50);
builder.setMaxConnTotal(100);
CloseableHttpClient httpclient = builder.build();
URI uri = new URIBuilder(url).build();
RequestConfig config = custom().setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout)
.build();
HttpGet httpGet = new HttpGet(uri);
HttpClientContext context = HttpClientContext.create();
context.setRequestConfig(config);
CloseableHttpResponse response = httpclient.execute(httpGet, context);
try {
int statusCode = response.getStatusLine().getStatusCode();
long end = System.currentTimeMillis();
long cost = end - start;
if (logger.isWarnEnabled()) {
logger.warn("post " + url + ", cost : " + cost);
}
if (statusCode == HttpStatus.SC_OK) {
return EntityUtils.toByteArray(response.getEntity());
} else {
String errorMsg = EntityUtils.toString(response.getEntity());
throw new RuntimeException("requestGet remote error, url=" + uri.toString() + ", code=" + statusCode
+ ", error msg=" + errorMsg);
}
} finally {
response.close();
httpGet.releaseConnection();
}
}
public static String get(String url, int timeout) {
// logger.info("get url is :" + url);
// 支持采用https协议,忽略证书
url = url.trim();
if (url.startsWith("https")) {
return getIgnoreCerf(url, null, null, timeout);
}
long start = System.currentTimeMillis();
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setMaxConnPerRoute(50);
builder.setMaxConnTotal(100);
CloseableHttpClient httpclient = builder.build();
CloseableHttpResponse response = null;
HttpGet httpGet = null;
try {
URI uri = new URIBuilder(url).build();
RequestConfig config = custom().setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout)
.build();
httpGet = new HttpGet(uri);
HttpClientContext context = HttpClientContext.create();
context.setRequestConfig(config);
response = httpclient.execute(httpGet, context);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
return EntityUtils.toString(response.getEntity());
} else {
String errorMsg = EntityUtils.toString(response.getEntity());
throw new RuntimeException("requestGet remote error, url=" + uri.toString() + ", code=" + statusCode
+ ", error msg=" + errorMsg);
}
} catch (Throwable t) {
long end = System.currentTimeMillis();
long cost = end - start;
String curlRequest = getCurlRequest(url, null, null, cost);
throw new RuntimeException("requestGet remote error, request : " + curlRequest, t);
} finally {
long end = System.currentTimeMillis();
long cost = end - start;
printCurlRequest(url, null, null, cost);
if (response != null) {
try {
response.close();
} catch (IOException e) {
}
}
httpGet.releaseConnection();
}
}
private static String getIgnoreCerf(String url, CookieStore cookieStore, Map<String, String> params, int timeout) {
long start = System.currentTimeMillis();
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setMaxConnPerRoute(50);
builder.setMaxConnTotal(100);
HttpGet httpGet = null;
CloseableHttpResponse response = null;
try {
// 创建支持忽略证书的https
final SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
return true;
}
}).build();
CloseableHttpClient httpClient = HttpClientBuilder.create()
.setSSLContext(sslContext)
.setConnectionManager(new PoolingHttpClientConnectionManager(RegistryBuilder.<ConnectionSocketFactory> create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE))
.build()))
.build();
// ---------------- 创建支持https 的client成功---------
URI uri = new URIBuilder(url).build();
RequestConfig config = custom().setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout)
.build();
httpGet = new HttpGet(uri);
HttpClientContext context = HttpClientContext.create();
context.setRequestConfig(config);
response = httpClient.execute(httpGet, context);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
return EntityUtils.toString(response.getEntity());
} else {
String errorMsg = EntityUtils.toString(response.getEntity());
throw new RuntimeException("requestGet remote error, url=" + uri.toString() + ", code=" + statusCode
+ ", error msg=" + errorMsg);
}
} catch (Throwable t) {
long end = System.currentTimeMillis();
long cost = end - start;
String curlRequest = getCurlRequest(url, cookieStore, params, cost);
throw new RuntimeException("requestPost(Https) remote error, request : " + curlRequest, t);
} finally {
long end = System.currentTimeMillis();
long cost = end - start;
printCurlRequest(url, null, null, cost);
if (response != null) {
try {
response.close();
} catch (IOException e) {
}
}
if (httpGet != null) {
httpGet.releaseConnection();
}
}
}
private static String postIgnoreCerf(String url, CookieStore cookieStore, Map<String, String> params, int timeout) {
long start = System.currentTimeMillis();
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setMaxConnPerRoute(50);
builder.setMaxConnTotal(100);
HttpPost httpPost = null;
CloseableHttpResponse response = null;
try {
// 创建支持忽略证书的https
final SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
return true;
}
}).build();
CloseableHttpClient httpClient = HttpClientBuilder.create()
.setSSLContext(sslContext)
.setConnectionManager(new PoolingHttpClientConnectionManager(RegistryBuilder.<ConnectionSocketFactory> create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE))
.build()))
.build();
// ---------------- 创建支持https 的client成功---------
URI uri = new URIBuilder(url).build();
RequestConfig config = custom().setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout)
.build();
httpPost = new HttpPost(uri);
List<NameValuePair> parameters = Lists.newArrayList();
for (String key : params.keySet()) {
NameValuePair nameValuePair = new BasicNameValuePair(key, params.get(key));
parameters.add(nameValuePair);
}
httpPost.setEntity(new UrlEncodedFormEntity(parameters, Charset.forName("UTF-8")));
HttpClientContext context = HttpClientContext.create();
context.setRequestConfig(config);
context.setCookieStore(cookieStore);
response = httpClient.execute(httpPost, context);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
long end = System.currentTimeMillis();
long cost = end - start;
printCurlRequest(url, cookieStore, params, cost);
return EntityUtils.toString(response.getEntity());
} else {
long end = System.currentTimeMillis();
long cost = end - start;
String curlRequest = getCurlRequest(url, cookieStore, params, cost);
throw new RuntimeException("requestPost(Https) remote error, request : " + curlRequest
+ ", statusCode=" + statusCode + "");
}
} catch (Throwable t) {
long end = System.currentTimeMillis();
long cost = end - start;
String curlRequest = getCurlRequest(url, cookieStore, params, cost);
throw new RuntimeException("requestPost(Https) remote error, request : " + curlRequest, t);
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
}
}
if (httpPost != null) {
httpPost.releaseConnection();
}
}
}
public static String post(String url, CookieStore cookieStore, Map<String, String> params, int timeout) {
url = url.trim();
// 支持采用https协议,忽略证书
if (url.startsWith("https")) {
return postIgnoreCerf(url, cookieStore, params, timeout);
}
long start = System.currentTimeMillis();
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setMaxConnPerRoute(50);
builder.setMaxConnTotal(100);
HttpPost httpPost = null;
CloseableHttpResponse response = null;
try {
CloseableHttpClient httpclient = builder.build();
URI uri = new URIBuilder(url).build();
RequestConfig config = custom().setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout)
.build();
httpPost = new HttpPost(uri);
List<NameValuePair> parameters = Lists.newArrayList();
for (String key : params.keySet()) {
NameValuePair nameValuePair = new BasicNameValuePair(key, params.get(key));
parameters.add(nameValuePair);
}
httpPost.setEntity(new UrlEncodedFormEntity(parameters, Charset.forName("UTF-8")));
HttpClientContext context = HttpClientContext.create();
context.setRequestConfig(config);
context.setCookieStore(cookieStore);
response = httpclient.execute(httpPost, context);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
long end = System.currentTimeMillis();
long cost = end - start;
printCurlRequest(url, cookieStore, params, cost);
return EntityUtils.toString(response.getEntity());
} else {
long end = System.currentTimeMillis();
long cost = end - start;
String curlRequest = getCurlRequest(url, cookieStore, params, cost);
throw new RuntimeException("requestPost remote error, request : " + curlRequest + ", statusCode="
+ statusCode + ";" + EntityUtils.toString(response.getEntity()));
}
} catch (Throwable t) {
long end = System.currentTimeMillis();
long cost = end - start;
String curlRequest = getCurlRequest(url, cookieStore, params, cost);
throw new RuntimeException("requestPost remote error, request : " + curlRequest, t);
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
}
}
if (httpPost != null) {
httpPost.releaseConnection();
}
}
}
public static void printCurlRequest(String url, CookieStore cookieStore, Map<String, String> params, long cost) {
logger.warn(getCurlRequest(url, cookieStore, params, cost));
}
private static String getCurlRequest(String url, CookieStore cookieStore, Map<String, String> params, long cost) {
if (params == null) {
return "curl '" + url + "'\ncost : " + cost;
} else {
StringBuilder paramsStr = new StringBuilder();
Iterator<Map.Entry<String, String>> iterator = params.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
paramsStr.append(entry.getKey() + "=" + entry.getValue());
if (iterator.hasNext()) {
paramsStr.append("&");
}
}
if (cookieStore == null) {
return "curl '" + url + "' -d '" + paramsStr.toString() + "'\ncost : " + cost;
} else {
StringBuilder cookieStr = new StringBuilder();
List<Cookie> cookies = cookieStore.getCookies();
Iterator<Cookie> iteratorCookie = cookies.iterator();
while (iteratorCookie.hasNext()) {
Cookie cookie = iteratorCookie.next();
cookieStr.append(cookie.getName() + "=" + cookie.getValue());
if (iteratorCookie.hasNext()) {
cookieStr.append(";");
}
}
return "curl '" + url + "' -b '" + cookieStr + "' -d '" + paramsStr.toString() + "'\ncost : " + cost;
}
}
}
}

View File

@@ -0,0 +1,334 @@
package com.alibaba.otter.canal.parse.inbound.mysql.rds;
import io.netty.handler.codec.http.HttpResponseStatus;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.UUID;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
/**
* @author agapple 2017年10月14日 下午1:53:52
* @since 1.0.25
*/
public class RdsBinlogOpenApi {
protected static final Logger logger = LoggerFactory.getLogger(RdsBinlogOpenApi.class);
private static final String ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
private static final int TIMEOUT = 10000;
private static final String ENCODING = "UTF-8";
private static final String MAC_NAME = "HmacSHA1";
private static final String API_VERSION = "2014-08-15";
private static final String SIGNATURE_VERSION = "1.0";
public static void downloadBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime,
Date endTime, File destDir) throws Throwable {
int pageSize = 100;
int pageNumber = 0;
int pageRecordCount = 1;
String hostInstanceID = null;
while (pageRecordCount > 0 && pageRecordCount <= pageSize) {
pageNumber += 1;
String result = describeBinlogFiles(url, ak, sk, dbInstanceId, startTime, endTime, pageSize, pageNumber);
JSONObject jsobObj = JSON.parseObject(result);
pageRecordCount = jsobObj.getInteger("PageRecordCount");
if (pageRecordCount > 0) {
FileUtils.forceMkdir(destDir);
File hostIdFile = new File(destDir, "hostId");
if (hostIdFile.exists()) {
List<String> lines = IOUtils.readLines(new FileInputStream(hostIdFile));
hostInstanceID = StringUtils.join(lines, "\n");
}
String itemStr = jsobObj.getString("Items");
JSONObject binLogFileObj = JSONObject.parseObject(itemStr);
JSONArray items = binLogFileObj.getJSONArray("BinLogFile");
if (items == null || items.isEmpty()) {
continue;
}
for (int i = 0; i < items.size(); i++) {
JSONObject item = (JSONObject) items.get(i);
String oneHostInstanceID = item.getString("HostInstanceID");
if (hostInstanceID == null) {
hostInstanceID = oneHostInstanceID;
FileOutputStream hostIdFileOut = null;
try {
hostIdFileOut = new FileOutputStream(hostIdFile);
hostIdFileOut.write(oneHostInstanceID.getBytes());
hostIdFileOut.flush();
} finally {
IOUtils.closeQuietly(hostIdFileOut);
}
}
if (hostInstanceID.equals(oneHostInstanceID)) { // 只选择一个host下载
String downloadLink = item.getString("DownloadLink");
String fileName = StringUtils.substringBetween(downloadLink, "mysql-bin.", ".tar");
if (StringUtils.isNotEmpty(fileName)) {
File currentFile = new File(destDir, "mysql-bin." + fileName);
if (currentFile.isFile() && currentFile.exists()) {
// 检查一下文件是否存在,存在就就没必要下载了
continue;
}
}
HttpGet httpGet = new HttpGet(downloadLink);
CloseableHttpClient httpClient = HttpClientBuilder.create()
.setMaxConnPerRoute(50)
.setMaxConnTotal(100)
.build();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(TIMEOUT)
.setConnectionRequestTimeout(TIMEOUT)
.setSocketTimeout(TIMEOUT)
.build();
httpGet.setConfig(requestConfig);
HttpResponse response = httpClient.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpResponseStatus.OK.code()) {
throw new RuntimeException("download failed , url:" + downloadLink + " , statusCode:"
+ statusCode);
}
saveFile(destDir, response);
}
}
}
}
}
private static void saveFile(File parentFile, HttpResponse response) throws IOException {
InputStream is = response.getEntity().getContent();
long totalSize = Long.parseLong(response.getFirstHeader("Content-Length").getValue());
String fileName = response.getFirstHeader("Content-Disposition").getValue();
fileName = StringUtils.substringAfter(fileName, "filename=");
boolean isTar = StringUtils.endsWith(fileName, ".tar");
FileUtils.forceMkdir(parentFile);
FileOutputStream fos = null;
try {
if (isTar) {
TarArchiveInputStream tais = new TarArchiveInputStream(is);
TarArchiveEntry tarArchiveEntry = null;
while ((tarArchiveEntry = tais.getNextTarEntry()) != null) {
String name = tarArchiveEntry.getName();
File tarFile = new File(parentFile, name);
logger.info("start to download file " + tarFile.getName());
BufferedOutputStream bos = null;
try {
bos = new BufferedOutputStream(new FileOutputStream(tarFile));
int read = -1;
byte[] buffer = new byte[1024];
while ((read = tais.read(buffer)) != -1) {
bos.write(buffer, 0, read);
}
logger.info("download file " + tarFile.getName() + " end!");
} finally {
IOUtils.closeQuietly(bos);
}
}
tais.close();
} else {
File file = new File(parentFile, fileName);
if (!file.isFile()) {
file.createNewFile();
}
try {
fos = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int len;
long copySize = 0;
long nextPrintProgress = 0;
logger.info("start to download file " + file.getName());
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
copySize += len;
long progress = copySize * 100 / totalSize;
if (progress >= nextPrintProgress) {
logger.info("download " + file.getName() + " progress : " + progress
+ "% , download size : " + copySize + ", total size : " + totalSize);
nextPrintProgress += 10;
}
}
logger.info("download file " + file.getName() + " end!");
fos.flush();
} finally {
IOUtils.closeQuietly(fos);
}
}
} finally {
IOUtils.closeQuietly(fos);
}
}
public static String describeBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime,
Date endTime, int pageSize, int pageNumber) throws Exception {
Map<String, String> paramMap = new HashMap<String, String>();
paramMap.put("Action", "DescribeBinlogFiles");
paramMap.put("DBInstanceId", dbInstanceId); // rds实例id
paramMap.put("StartTime", formatIso8601Date(startTime));
paramMap.put("EndTime", formatIso8601Date(endTime));
paramMap.put("PageSize", String.valueOf(pageSize));
paramMap.put("PageNumber", String.valueOf(pageNumber));
return doRequest(url, paramMap, ak, sk);
}
private static String doRequest(String domin, Map<String, String> param, String ak, String sk) throws Exception {
param.put("AccessKeyId", ak);
param.put("SignatureMethod", "HMAC-SHA1");
param.put("SignatureVersion", SIGNATURE_VERSION);
param.put("Version", API_VERSION);
param.put("SignatureNonce", UUID.randomUUID().toString());
param.put("Format", "JSON");
param.put("Timestamp", formatIso8601Date(new Date()));
String signStr = generate("POST", param, sk);
param.put("Signature", signStr);
String request = concatQueryString(param);
String url = domin + "?" + request;
String result = HttpHelper.post(url, null, Collections.EMPTY_MAP, TIMEOUT);
return result;
}
public static String concatQueryString(Map<String, String> parameters) throws UnsupportedEncodingException {
if (null == parameters) {
return null;
}
StringBuilder urlBuilder = new StringBuilder("");
for (Map.Entry<String, String> entry : parameters.entrySet()) {
String key = entry.getKey();
String val = entry.getValue();
urlBuilder.append(encode(key));
if (val != null) {
urlBuilder.append("=").append(encode(val));
}
urlBuilder.append("&");
}
int strIndex = urlBuilder.length();
if (parameters.size() > 0) {
urlBuilder.deleteCharAt(strIndex - 1);
}
return urlBuilder.toString();
}
public static String encode(String value) throws UnsupportedEncodingException {
return URLEncoder.encode(value, "UTF-8");
}
private static String formatIso8601Date(Date date) {
SimpleDateFormat df = new SimpleDateFormat(ISO8601_DATE_FORMAT);
df.setTimeZone(TimeZone.getTimeZone("GMT"));
return df.format(date);
}
/**
* 使用 HMAC-SHA1 签名方法对对encryptText进行签名
*
* @param encryptText 被签名的字符串
* @param encryptKey 密钥
* @return
* @throws Exception
*/
public static byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception {
byte[] data = encryptKey.getBytes(ENCODING);
// 根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称
SecretKey secretKey = new SecretKeySpec(data, MAC_NAME);
// 生成一个指定 Mac 算法 的 Mac 对象
Mac mac = Mac.getInstance(MAC_NAME);
// 用给定密钥初始化 Mac 对象
mac.init(secretKey);
byte[] text = encryptText.getBytes(ENCODING);
// 完成 Mac 操作
return mac.doFinal(text);
}
private static String base64(byte input[]) throws UnsupportedEncodingException {
return new String(Base64.encodeBase64(input), ENCODING);
}
/** 对参数名称和参数值进行URL编码 **/
public static String generate(String method, Map<String, String> parameter, String accessKeySecret)
throws Exception {
String signString = generateSignString(method, parameter);
byte[] signBytes = HmacSHA1Encrypt(signString, accessKeySecret + "&");
String signature = base64(signBytes);
if ("POST".equals(method)) {
return signature;
}
return URLEncoder.encode(signature, "UTF-8");
}
private static String generateQueryString(TreeMap<String, String> treeMap) {
StringBuilder canonicalizedQueryString = new StringBuilder();
boolean first = true;
for (String key : treeMap.navigableKeySet()) {
String value = treeMap.get(key);
if (!first) {
canonicalizedQueryString.append("&");
}
first = false;
canonicalizedQueryString.append(percentEncode(key)).append("=").append(percentEncode(value));
}
return canonicalizedQueryString.toString();
}
public static String generateSignString(String httpMethod, Map<String, String> parameter) throws IOException {
TreeMap<String, String> sortParameter = new TreeMap<String, String>();
sortParameter.putAll(parameter);
String canonicalizedQueryString = generateQueryString(sortParameter);
if (null == httpMethod) {
throw new RuntimeException("httpMethod can not be empty");
}
/** 构造待签名的字符串* */
StringBuilder stringToSign = new StringBuilder();
stringToSign.append(httpMethod).append("&");
stringToSign.append(percentEncode("/")).append("&");
stringToSign.append(percentEncode(canonicalizedQueryString));
return stringToSign.toString();
}
public static String percentEncode(String value) {
try {
return value == null ? null : URLEncoder.encode(value, ENCODING)
.replaceAll("\\+", "%20")
.replaceAll("\\*", "%2A")
.replaceAll("%7E", "~");
} catch (Exception e) {
}
return "";
}
}

View File

@@ -0,0 +1,111 @@
package com.alibaba.otter.canal.parse.inbound.mysql.rds;
import java.io.File;
import java.util.Date;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.Assert;
import com.alibaba.otter.canal.parse.CanalEventParser;
import com.alibaba.otter.canal.parse.exception.CanalParseException;
import com.alibaba.otter.canal.parse.inbound.mysql.LocalBinlogEventParser;
import com.alibaba.otter.canal.protocol.position.EntryPosition;
/**
* 基于rds binlog备份文件的复制
*
* @author agapple 2017年10月15日 下午1:27:36
* @since 1.0.25
*/
public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements CanalEventParser {
private String url = "https://rds.aliyuncs.com/"; // openapi地址
private String accesskey; // 云账号的ak
private String secretkey; // 云账号sk
private String instanceId; // rds实例id
private Long startTime;
private Long endTime;
public RdsLocalBinlogEventParser(){
}
public void start() throws CanalParseException {
try {
Assert.notNull(startTime);
Assert.notNull(accesskey);
Assert.notNull(secretkey);
Assert.notNull(instanceId);
Assert.notNull(url);
if (endTime == null) {
endTime = System.currentTimeMillis();
}
RdsBinlogOpenApi.downloadBinlogFiles(url,
accesskey,
secretkey,
instanceId,
new Date(startTime),
new Date(endTime),
new File(directory));
// 更新一下时间戳
masterPosition = new EntryPosition(startTime);
} catch (Throwable e) {
logger.error("download binlog failed", e);
throw new CanalParseException(e);
}
super.start();
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
if (StringUtils.isNotEmpty(url)) {
this.url = url;
}
}
public String getAccesskey() {
return accesskey;
}
public void setAccesskey(String accesskey) {
this.accesskey = accesskey;
}
public String getSecretkey() {
return secretkey;
}
public void setSecretkey(String secretkey) {
this.secretkey = secretkey;
}
public String getInstanceId() {
return instanceId;
}
public void setInstanceId(String instanceId) {
this.instanceId = instanceId;
}
public Long getStartTime() {
return startTime;
}
public void setStartTime(Long startTime) {
this.startTime = startTime;
}
public Long getEndTime() {
return endTime;
}
public void setEndTime(Long endTime) {
this.endTime = endTime;
}
}

View File

@@ -0,0 +1,492 @@
package com.alibaba.otter.canal.parse.inbound.mysql.tsdb;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import com.alibaba.druid.sql.repository.Schema;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.otter.canal.filter.CanalEventFilter;
import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket;
import com.alibaba.otter.canal.parse.exception.CanalParseException;
import com.alibaba.otter.canal.parse.inbound.TableMeta;
import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta;
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection;
import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache;
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.DdlResult;
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.DruidDdlParser;
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDAO;
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDO;
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDAO;
import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDO;
import com.alibaba.otter.canal.protocol.position.EntryPosition;
/**
* 基于db远程管理 see internal class: CanalTableMeta , ConsoleTableMetaTSDB
*
* @author agapple 2017年7月27日 下午10:47:55
* @since 3.2.5
*/
public class DatabaseTableMeta implements TableMetaTSDB {
private static Logger logger = LoggerFactory.getLogger(DatabaseTableMeta.class);
private static Pattern pattern = Pattern.compile("Duplicate entry '.*' for key '*'");
private static Pattern h2Pattern = Pattern.compile("Unique index or primary key violation");
private static final EntryPosition INIT_POSITION = new EntryPosition("0", 0L, -2L, -1L);
private String destination;
private MemoryTableMeta memoryTableMeta;
private MysqlConnection connection; // 查询meta信息的链接
private CanalEventFilter filter;
private CanalEventFilter blackFilter;
private EntryPosition lastPosition;
private ScheduledExecutorService scheduler;
private MetaHistoryDAO metaHistoryDAO;
private MetaSnapshotDAO metaSnapshotDAO;
public DatabaseTableMeta(){
}
@Override
public boolean init(final String destination) {
this.destination = destination;
this.memoryTableMeta = new MemoryTableMeta();
this.scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "[scheduler-table-meta-snapshot]");
}
});
// 24小时生成一份snapshot
scheduler.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
MDC.put("destination", destination);
applySnapshotToDB(lastPosition, false);
} catch (Throwable e) {
logger.error("scheudle applySnapshotToDB faield", e);
}
}
}, 24, 24, TimeUnit.HOURS);
return true;
}
@Override
public TableMeta find(String schema, String table) {
synchronized (memoryTableMeta) {
return memoryTableMeta.find(schema, table);
}
}
@Override
public boolean apply(EntryPosition position, String schema, String ddl, String extra) {
// 首先记录到内存结构
synchronized (memoryTableMeta) {
if (memoryTableMeta.apply(position, schema, ddl, extra)) {
this.lastPosition = position;
// 同步每次变更给远程做历史记录
return applyHistoryToDB(position, schema, ddl, extra);
} else {
throw new RuntimeException("apply to memory is failed");
}
}
}
@Override
public boolean rollback(EntryPosition position) {
// 每次rollback需要重新构建一次memory data
this.memoryTableMeta = new MemoryTableMeta();
boolean flag = false;
EntryPosition snapshotPosition = buildMemFromSnapshot(position);
if (snapshotPosition != null) {
applyHistoryOnMemory(snapshotPosition, position);
flag = true;
}
if (!flag) {
// 如果没有任何数据,则为初始化状态,全量dump一份关注的表
if (dumpTableMeta(connection, filter)) {
// 记录一下snapshot结果,方便快速恢复
flag = applySnapshotToDB(INIT_POSITION, true);
}
}
return flag;
}
@Override
public Map<String, String> snapshot() {
return memoryTableMeta.snapshot();
}
/**
* 初始化的时候dump一下表结构
*/
private boolean dumpTableMeta(MysqlConnection connection, final CanalEventFilter filter) {
try {
ResultSetPacket packet = connection.query("show databases");
List<String> schemas = new ArrayList<String>();
for (String schema : packet.getFieldValues()) {
schemas.add(schema);
}
for (String schema : schemas) {
packet = connection.query("show tables from `" + schema + "`");
List<String> tables = new ArrayList<String>();
for (String table : packet.getFieldValues()) {
String fullName = schema + "." + table;
if (blackFilter == null || !blackFilter.filter(fullName)) {
if (filter == null || filter.filter(fullName)) {
tables.add(table);
}
}
}
if (tables.isEmpty()) {
continue;
}
StringBuilder sql = new StringBuilder();
for (String table : tables) {
sql.append("show create table `" + schema + "`.`" + table + "`;");
}
List<ResultSetPacket> packets = connection.queryMulti(sql.toString());
for (ResultSetPacket onePacket : packets) {
if (onePacket.getFieldValues().size() > 1) {
String oneTableCreateSql = onePacket.getFieldValues().get(1);
memoryTableMeta.apply(INIT_POSITION, schema, oneTableCreateSql, null);
}
}
}
return true;
} catch (IOException e) {
throw new CanalParseException(e);
}
}
private boolean applyHistoryToDB(EntryPosition position, String schema, String ddl, String extra) {
Map<String, String> content = new HashMap<String, String>();
content.put("destination", destination);
content.put("binlogFile", position.getJournalName());
content.put("binlogOffest", String.valueOf(position.getPosition()));
content.put("binlogMasterId", String.valueOf(position.getServerId()));
content.put("binlogTimestamp", String.valueOf(position.getTimestamp()));
content.put("useSchema", schema);
if (content.isEmpty()) {
throw new RuntimeException("apply failed caused by content is empty in applyHistoryToDB");
}
// 待补充
List<DdlResult> ddlResults = DruidDdlParser.parse(ddl, schema);
if (ddlResults.size() > 0) {
DdlResult ddlResult = ddlResults.get(0);
content.put("sqlSchema", ddlResult.getSchemaName());
content.put("sqlTable", ddlResult.getTableName());
content.put("sqlType", ddlResult.getType().name());
content.put("sqlText", ddl);
content.put("extra", extra);
}
MetaHistoryDO metaDO = new MetaHistoryDO();
try {
BeanUtils.populate(metaDO, content);
// 会建立唯一约束,解决:
// 1. 重复的binlog file+offest
// 2. 重复的masterId+timestamp
metaHistoryDAO.insert(metaDO);
} catch (Throwable e) {
if (isUkDuplicateException(e)) {
// 忽略掉重复的位点
logger.warn("dup apply for sql : " + ddl);
} else {
throw new CanalParseException("apply history to db failed caused by : " + e.getMessage(), e);
}
}
return true;
}
/**
* 发布数据到console上
*/
private boolean applySnapshotToDB(EntryPosition position, boolean init) {
// 获取一份快照
MemoryTableMeta tmpMemoryTableMeta = new MemoryTableMeta();
Map<String, String> schemaDdls = null;
synchronized (memoryTableMeta) {
if (!init && position == null) {
// 如果是持续构建,则识别一下是否有DDL变更过,如果没有就忽略了
return false;
}
schemaDdls = memoryTableMeta.snapshot();
for (Map.Entry<String, String> entry : schemaDdls.entrySet()) {
tmpMemoryTableMeta.apply(position, entry.getKey(), entry.getValue(), null);
}
}
// 基于临时内存对象进行对比
boolean compareAll = true;
for (Schema schema : tmpMemoryTableMeta.getRepository().getSchemas()) {
for (String table : schema.showTables()) {
if (!compareTableMetaDbAndMemory(connection, schema.getName(), table)) {
compareAll = false;
}
}
}
if (compareAll) {
Map<String, String> content = new HashMap<String, String>();
content.put("destination", destination);
content.put("binlogFile", position.getJournalName());
content.put("binlogOffest", String.valueOf(position.getPosition()));
content.put("binlogMasterId", String.valueOf(position.getServerId()));
content.put("binlogTimestamp", String.valueOf(position.getTimestamp()));
content.put("data", JSON.toJSONString(schemaDdls));
if (content.isEmpty()) {
throw new RuntimeException("apply failed caused by content is empty in applySnapshotToDB");
}
MetaSnapshotDO snapshotDO = new MetaSnapshotDO();
try {
BeanUtils.populate(snapshotDO, content);
metaSnapshotDAO.insert(snapshotDO);
} catch (Throwable e) {
if (isUkDuplicateException(e)) {
// 忽略掉重复的位点
logger.info("dup apply snapshot use position : " + position + " , just ignore");
} else {
throw new CanalParseException("apply failed caused by : " + e.getMessage(), e);
}
}
return true;
} else {
logger.error("compare failed , check log");
}
return false;
}
private boolean compareTableMetaDbAndMemory(MysqlConnection connection, final String schema, final String table) {
TableMeta tableMetaFromMem = memoryTableMeta.find(schema, table);
TableMeta tableMetaFromDB = new TableMeta();
tableMetaFromDB.setSchema(schema);
tableMetaFromDB.setTable(table);
try {
ResultSetPacket packet = connection.query("desc " + getFullName(schema, table));
tableMetaFromDB.setFields(TableMetaCache.parserTableMeta(packet));
} catch (IOException e) {
if (e.getMessage().contains("errorNumber=1146")) {
logger.error("table not exist in db , pls check :" + getFullName(schema, table) + " , mem : "
+ tableMetaFromMem);
return false;
}
throw new CanalParseException(e);
}
boolean result = compareTableMeta(tableMetaFromMem, tableMetaFromDB);
if (!result) {
String createDDL = null;
try {
ResultSetPacket packet = connection.query("show create table " + getFullName(schema, table));
if (packet.getFieldValues().size() > 1) {
createDDL = packet.getFieldValues().get(1);
}
} catch (IOException e) {
// ignore
}
logger.error("pls submit github issue, show create table ddl:" + createDDL + " , compare failed . \n db : "
+ tableMetaFromDB + " \n mem : " + tableMetaFromMem);
}
return result;
}
private EntryPosition buildMemFromSnapshot(EntryPosition position) {
try {
MetaSnapshotDO snapshotDO = metaSnapshotDAO.findByTimestamp(destination, position.getTimestamp());
if (snapshotDO == null) {
return null;
}
String binlogFile = snapshotDO.getBinlogFile();
Long binlogOffest = snapshotDO.getBinlogOffest();
String binlogMasterId = snapshotDO.getBinlogMasterId();
Long binlogTimestamp = snapshotDO.getBinlogTimestamp();
EntryPosition snapshotPosition = new EntryPosition(binlogFile,
binlogOffest == null ? 0l : binlogOffest,
binlogTimestamp == null ? 0l : binlogTimestamp,
Long.valueOf(binlogMasterId == null ? "-2" : binlogMasterId));
// data存储为Map<String,String>,每个分库一套建表
String sqlData = snapshotDO.getData();
JSONObject jsonObj = JSON.parseObject(sqlData);
for (Map.Entry entry : jsonObj.entrySet()) {
// 记录到内存
if (!memoryTableMeta.apply(snapshotPosition,
ObjectUtils.toString(entry.getKey()),
ObjectUtils.toString(entry.getValue()),
null)) {
return null;
}
}
return snapshotPosition;
} catch (Throwable e) {
throw new CanalParseException("apply failed caused by : " + e.getMessage(), e);
}
}
private boolean applyHistoryOnMemory(EntryPosition position, EntryPosition rollbackPosition) {
try {
List<MetaHistoryDO> metaHistoryDOList = metaHistoryDAO.findByTimestamp(destination,
position.getTimestamp(),
rollbackPosition.getTimestamp());
if (metaHistoryDOList == null) {
return true;
}
for (MetaHistoryDO metaHistoryDO : metaHistoryDOList) {
String binlogFile = metaHistoryDO.getBinlogFile();
Long binlogOffest = metaHistoryDO.getBinlogOffest();
String binlogMasterId = metaHistoryDO.getBinlogMasterId();
Long binlogTimestamp = metaHistoryDO.getBinlogTimestamp();
String useSchema = metaHistoryDO.getUseSchema();
String sqlData = metaHistoryDO.getSqlText();
EntryPosition snapshotPosition = new EntryPosition(binlogFile,
binlogOffest == null ? 0L : binlogOffest,
binlogTimestamp == null ? 0L : binlogTimestamp,
Long.valueOf(binlogMasterId == null ? "-2" : binlogMasterId));
// 如果是同一秒内,对比一下history的位点,如果比期望的位点要大,忽略之
if (snapshotPosition.getTimestamp() > rollbackPosition.getTimestamp()) {
continue;
} else if (rollbackPosition.getServerId() == snapshotPosition.getServerId()
&& snapshotPosition.compareTo(rollbackPosition) > 0) {
continue;
}
// 记录到内存
if (!memoryTableMeta.apply(snapshotPosition, useSchema, sqlData, null)) {
return false;
}
}
return metaHistoryDOList.size() > 0;
} catch (Throwable e) {
throw new CanalParseException("apply failed", e);
}
}
private String getFullName(String schema, String table) {
StringBuilder builder = new StringBuilder();
return builder.append('`')
.append(schema)
.append('`')
.append('.')
.append('`')
.append(table)
.append('`')
.toString();
}
private boolean compareTableMeta(TableMeta source, TableMeta target) {
if (!StringUtils.equalsIgnoreCase(source.getSchema(), target.getSchema())) {
return false;
}
if (!StringUtils.equalsIgnoreCase(source.getTable(), target.getTable())) {
return false;
}
List<FieldMeta> sourceFields = source.getFields();
List<FieldMeta> targetFields = target.getFields();
if (sourceFields.size() != targetFields.size()) {
return false;
}
for (int i = 0; i < sourceFields.size(); i++) {
FieldMeta sourceField = sourceFields.get(i);
FieldMeta targetField = targetFields.get(i);
if (!StringUtils.equalsIgnoreCase(sourceField.getColumnName(), targetField.getColumnName())) {
return false;
}
if (!StringUtils.equalsIgnoreCase(sourceField.getColumnType(), targetField.getColumnType())) {
return false;
}
if (!StringUtils.equalsIgnoreCase(sourceField.getDefaultValue(), targetField.getDefaultValue())) {
return false;
}
if (sourceField.isNullable() != targetField.isNullable()) {
return false;
}
if (sourceField.isKey() != targetField.isKey()) {
return false;
}
}
return true;
}
public void setConnection(MysqlConnection connection) {
this.connection = connection;
}
public void setFilter(CanalEventFilter filter) {
this.filter = filter;
}
public MetaHistoryDAO getMetaHistoryDAO() {
return metaHistoryDAO;
}
public void setMetaHistoryDAO(MetaHistoryDAO metaHistoryDAO) {
this.metaHistoryDAO = metaHistoryDAO;
}
public MetaSnapshotDAO getMetaSnapshotDAO() {
return metaSnapshotDAO;
}
public void setMetaSnapshotDAO(MetaSnapshotDAO metaSnapshotDAO) {
this.metaSnapshotDAO = metaSnapshotDAO;
}
public void setBlackFilter(CanalEventFilter blackFilter) {
this.blackFilter = blackFilter;
}
public MysqlConnection getConnection() {
return connection;
}
public boolean isUkDuplicateException(Throwable t) {
if (pattern.matcher(t.getMessage()).find() || h2Pattern.matcher(t.getMessage()).find()) {
// 违反外键约束时也抛出这种异常,所以这里还要判断包含字符串Duplicate entry
return true;
}
return false;
}
}

View File

@@ -0,0 +1,240 @@
package com.alibaba.otter.canal.parse.inbound.mysql.tsdb;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.druid.sql.ast.SQLDataType;
import com.alibaba.druid.sql.ast.SQLDataTypeImpl;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLNullExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.ast.statement.SQLColumnConstraint;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import com.alibaba.druid.sql.ast.statement.SQLColumnPrimaryKey;
import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement;
import com.alibaba.druid.sql.ast.statement.SQLNotNullConstraint;
import com.alibaba.druid.sql.ast.statement.SQLNullConstraint;
import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem;
import com.alibaba.druid.sql.ast.statement.SQLTableElement;
import com.alibaba.druid.sql.dialect.mysql.ast.MySqlPrimaryKey;
import com.alibaba.druid.sql.repository.Schema;
import com.alibaba.druid.sql.repository.SchemaObject;
import com.alibaba.druid.sql.repository.SchemaRepository;
import com.alibaba.druid.util.JdbcConstants;
import com.alibaba.otter.canal.parse.inbound.TableMeta;
import com.alibaba.otter.canal.parse.inbound.TableMeta.FieldMeta;
import com.alibaba.otter.canal.parse.inbound.mysql.ddl.DruidDdlParser;
import com.alibaba.otter.canal.protocol.position.EntryPosition;
/**
* 基于DDL维护的内存表结构
*
* @author agapple 2017年7月27日 下午4:19:40
* @since 3.2.5
*/
public class MemoryTableMeta implements TableMetaTSDB {
private Logger logger = LoggerFactory.getLogger(MemoryTableMeta.class);
private Map<List<String>, TableMeta> tableMetas = new ConcurrentHashMap<List<String>, TableMeta>();
private SchemaRepository repository = new SchemaRepository(JdbcConstants.MYSQL);
public MemoryTableMeta(){
}
@Override
public boolean init(String destination) {
return true;
}
public boolean apply(EntryPosition position, String schema, String ddl, String extra) {
tableMetas.clear();
synchronized (this) {
if (StringUtils.isNotEmpty(schema)) {
repository.setDefaultSchema(schema);
}
try {
repository.console(ddl);
} catch (Throwable e) {
logger.warn("parse faield : " + ddl, e);
}
}
// TableMeta meta = find("tddl5_00", "ab");
// if (meta != null) {
// repository.setDefaultSchema("tddl5_00");
// System.out.println(repository.console("show create table tddl5_00.ab"));
// System.out.println(repository.console("show columns from tddl5_00.ab"));
// }
return true;
}
@Override
public TableMeta find(String schema, String table) {
List<String> keys = Arrays.asList(schema, table);
TableMeta tableMeta = tableMetas.get(keys);
if (tableMeta == null) {
synchronized (this) {
tableMeta = tableMetas.get(keys);
if (tableMeta == null) {
Schema schemaRep = repository.findSchema(schema);
if (schema == null) {
return null;
}
SchemaObject data = schemaRep.findTable(table);
if (data == null) {
return null;
}
SQLStatement statement = data.getStatement();
if (statement == null) {
return null;
}
if (statement instanceof SQLCreateTableStatement) {
tableMeta = parse((SQLCreateTableStatement) statement);
}
if (tableMeta != null) {
if (table != null) {
tableMeta.setTable(table);
}
if (schema != null) {
tableMeta.setSchema(schema);
}
tableMetas.put(keys, tableMeta);
}
}
}
}
return tableMeta;
}
@Override
public boolean rollback(EntryPosition position) {
throw new RuntimeException("not support for memory");
}
public Map<String, String> snapshot() {
Map<String, String> schemaDdls = new HashMap<String, String>();
for (Schema schema : repository.getSchemas()) {
StringBuffer data = new StringBuffer(4 * 1024);
for (String table : schema.showTables()) {
SchemaObject schemaObject = schema.findTable(table);
schemaObject.getStatement().output(data);
data.append("; \n");
}
schemaDdls.put(schema.getName(), data.toString());
}
return schemaDdls;
}
private TableMeta parse(SQLCreateTableStatement statement) {
int size = statement.getTableElementList().size();
if (size > 0) {
TableMeta tableMeta = new TableMeta();
for (int i = 0; i < size; ++i) {
SQLTableElement element = statement.getTableElementList().get(i);
processTableElement(element, tableMeta);
}
return tableMeta;
}
return null;
}
private void processTableElement(SQLTableElement element, TableMeta tableMeta) {
if (element instanceof SQLColumnDefinition) {
FieldMeta fieldMeta = new FieldMeta();
SQLColumnDefinition column = (SQLColumnDefinition) element;
String name = getSqlName(column.getName());
// String charset = getSqlName(column.getCharsetExpr());
SQLDataType dataType = column.getDataType();
String dataTypStr = dataType.getName();
if (dataType.getArguments().size() > 0) {
dataTypStr += "(";
for (int i = 0; i < column.getDataType().getArguments().size(); i++) {
if (i != 0) {
dataTypStr += ",";
}
SQLExpr arg = column.getDataType().getArguments().get(i);
dataTypStr += arg.toString();
}
dataTypStr += ")";
}
if (dataType instanceof SQLDataTypeImpl) {
SQLDataTypeImpl dataTypeImpl = (SQLDataTypeImpl) dataType;
if (dataTypeImpl.isUnsigned()) {
dataTypStr += " unsigned";
}
if (dataTypeImpl.isZerofill()) {
dataTypStr += " zerofill";
}
}
if (column.getDefaultExpr() == null || column.getDefaultExpr() instanceof SQLNullExpr) {
fieldMeta.setDefaultValue(null);
} else {
fieldMeta.setDefaultValue(getSqlName(column.getDefaultExpr()));
}
fieldMeta.setColumnName(name);
fieldMeta.setColumnType(dataTypStr);
fieldMeta.setNullable(true);
List<SQLColumnConstraint> constraints = column.getConstraints();
for (SQLColumnConstraint constraint : constraints) {
if (constraint instanceof SQLNotNullConstraint) {
fieldMeta.setNullable(false);
} else if (constraint instanceof SQLNullConstraint) {
fieldMeta.setNullable(true);
} else if (constraint instanceof SQLColumnPrimaryKey) {
fieldMeta.setKey(true);
}
}
tableMeta.addFieldMeta(fieldMeta);
} else if (element instanceof MySqlPrimaryKey) {
MySqlPrimaryKey column = (MySqlPrimaryKey) element;
List<SQLSelectOrderByItem> pks = column.getColumns();
for (SQLSelectOrderByItem pk : pks) {
String name = getSqlName(pk.getExpr());
FieldMeta field = tableMeta.getFieldMetaByName(name);
field.setKey(true);
}
}
}
private String getSqlName(SQLExpr sqlName) {
if (sqlName == null) {
return null;
}
if (sqlName instanceof SQLPropertyExpr) {
SQLIdentifierExpr owner = (SQLIdentifierExpr) ((SQLPropertyExpr) sqlName).getOwner();
return DruidDdlParser.unescapeName(owner.getName()) + "."
+ DruidDdlParser.unescapeName(((SQLPropertyExpr) sqlName).getName());
} else if (sqlName instanceof SQLIdentifierExpr) {
return DruidDdlParser.unescapeName(((SQLIdentifierExpr) sqlName).getName());
} else if (sqlName instanceof SQLCharExpr) {
return ((SQLCharExpr) sqlName).getText();
} else {
return sqlName.toString();
}
}
public SchemaRepository getRepository() {
return repository;
}
}

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