简介
本篇主要介绍canal的HA集群的搭建过程,以及结合自身使用过程的一些经历介绍一些注意事项。
1. 集群的搭建
HA集群主要使用zookeeper来作为一些metadata的存储基地,以便于满足集群信息的共享。下面我们开始介绍集群搭建的步骤。
1.1 机器准备
mysql: 10.29.15.227:3316 用户:user_canal 密码:pass_canal
canal: 10.29.26.172 10.29.18.29
zookeeper: 10.29.17.46:2181,10.29.18.29:2181,10.29.26.172:2181
1.2 在两台canal机器上完成以下配置
1.2.1 修改canal.properties
canal.zkServers=10.20.144.51:2181
canal.instance.global.spring.xml = classpath:spring/default-instance.xml
1.2.2 新建对应的destionation
注意每个destionation 对应的是一个mysql实例
mkdir test_canal_db
修改对应的instance.properties文件(该文件从example/instance.properties拷贝而来)
canal.instance.mysql.slaveId = 1234 ##另外一台机器改成1235,保证slaveId不重复即可
canal.instance.master.address = 10.29.61.119:3315
注意: 两台机器上的instance目录的名字需要保证完全一致,HA模式是依赖于instance name进行管理,同时必须都选择default-instance.xml配置
这个时候去对应的日志目录下面 logs/test_canal_db/test_canal_db.log 只会看到一台成功启动的日志。
然后这个时候可以去zookeeper中去看看对应存储的一些元信息。
[zk: localhost:2181(CONNECTED) 6] get /otter/canal/destinations/test_canal_db/running
{"active":true,"address":"10.29.18.29:11111","cid":1}
可以看到,这个时候对应的是有一个是master
2. 集群原理的解释
2.1 启动的时候决定谁为master
canal在启动的时候会往zookeeper当中抢占式的创建一个临时节点 /otter/canal/destinations/{0}/running
,抢占成功的就是主节点,失败的就是从节点。
2.2 运行过程中的HA切换
同时,canal server在启动的时候会对这个节点进行监听,一旦有变化,就会进行再次的抢占。所以就通过这个实现了server端的HA。
2.3 客户端的HA切换
如果只有server端实现了切换肯定是不够的,客户端也需要仍然能够连到正确的server上。所以客户端也是使用了zookeeper来进行服务发现的。client也监听了zk当中对应节点的信息变化。
3. 实际中运用的一些经验总结
3.1 如何让运行起来的canal(HA模式)恢复到初始状态。
在实际的应用中,有一次,dba因为一些原因要对数据库中的日志进行重做,会停止我们canal连接的数据库,导致我们的canal需要进行stop操作,同时,恢复后的binlog日志也是新的格式。
这个时候的操作应该是:
停止canal的消费端—>
停止canal中的master—>
停止刚刚成为master的另一台机器—>
清除zookeeper中的信息(主要是client的信息,因为这里记录了binlog的位置)。
在DBA将数据库日志重做以后,给出新的binlog信息,这个时候将binlog的信息配置到具体的 instance.properties当中
假如下面的DBA给出的binlog信息
canal.instance.master.journal.name=mysql-bin.000028
canal.instance.master.position=933520284
3.2 canal 连接的数据库是slave,无法获取到master的binlog的情况
在刚开始进行canal的上线配置的时候出现过一些问题。有一个问题的表现是,canal连接的库是对应的server ip 是 10.29.211.78 ,但是mysql是一个集群,我们连的是读库,写库在 10.29.211.43上面,出现的问题一直无法获取到binlog,最开始怀疑是canal配置有问题,但是多个destionations ,其他的就正常,所以怀疑可能是数据库配置的一些问题。后来怀疑可能是主从配置的一些问题。
在canal连的数据库上执行
show variables like ‘log_%’
mysql>
mysql> show variables like 'log_%'\G
*************************** 1. row ***************************
...
*************************** 11. row ***************************
Variable_name: log_slave_updates
Value: OFF
*************************** 26. row ***************************
Variable_name: log_warnings
Value: 0
26 rows in set (0.08 sec)
mysql
这里可以看到对应的有一个参数叫 log_slave_updates
这个参数标识了从库是否在同步完主库的binlog信息后将该信息也写入自己的binlog当中。
默认情况下,slave库在拿到master的binlog以后,会进行解析,将数据写入当前的库当中,但是对应的从master过来的binlog却会被丢弃,所以,读库虽然有主库master写入的数据,却不回有主库对应的binlog,所以canal也就拿不到对应的binlog。
解决方案:
将mysql 从库设置log_slave_updates=true
3.3 canal版本预警
这个坑踩的有点亏,当时canal已经到了v1.0.26,所以我们就选用了v1.0.25 ,结果过了几个月后,这个版本被官方标识为不建议生产使用,因为它爆出了太多bug,所以权当预警吧。
4 对应的canal的配置
这里将实际使用的配置贴出来,不一定是最好的,权作参考罢
vim canal.properties
canal.id= 1
canal.ip=
canal.port= 11111
canal.zkServers=10.29.17.46:2181,10.29.18.29:2181,10.29.26.172:2181
canal.zookeeper.flush.period = 1000
canal.file.flush.period = 1000
canal.instance.memory.buffer.size = 16384
canal.instance.memory.buffer.memunit = 1024
canal.instance.memory.batch.mode = MEMSIZE
canal.instance.detecting.enable = false
canal.instance.detecting.sql = select 1
canal.instance.detecting.interval.time = 3
canal.instance.detecting.retry.threshold = 3
canal.instance.detecting.heartbeatHaEnable = false
canal.instance.transaction.size = 1024
canal.instance.fallbackIntervalInSeconds = 60
canal.instance.network.receiveBufferSize = 16384
canal.instance.network.sendBufferSize = 16384
canal.instance.network.soTimeout = 30
canal.instance.filter.druid.ddl = true
canal.instance.filter.query.dcl = false
canal.instance.filter.query.dml = false
canal.instance.filter.query.ddl = false
canal.instance.filter.table.error = true
canal.instance.filter.rows = false
canal.instance.binlog.format = ROW,STATEMENT,MIXED
canal.instance.binlog.image = FULL,MINIMAL,NOBLOB
canal.instance.get.ddl.isolation = false
canal.destinations=searchapp,ll_social
canal.conf.dir = ../conf
canal.auto.scan = true
canal.auto.scan.interval = 5
canal.instance.global.mode = spring
canal.instance.global.lazy = false
canal.instance.global.spring.xml = classpath:spring/default-instance.xml
这里只解释一下HA模式的特别配置,canal.zkServers 和 canal.instance.global.spring.xml
配置了zookeeper相关的,在canal server启动的时候会尝试连接zookeeper,然后基于zookeeper中的数据来进行启动。spring/default-instance.xml 则是对应的对metadata在zookeeper中的持久化的实现。
对应的数据库的instance.properties配置
canal.instance.mysql.slaveId=2617201
canal.instance.master.address=10.29.15.227:3316
canal.instance.master.journal.name=
canal.instance.master.position=
canal.instance.master.timestamp=
canal.instance.tsdb.enable=false
canal.instance.dbUsername=canal_user
canal.instance.dbPassword=hF5mgndM
canal.instance.defaultDatabaseName=test
canal.instance.connectionCharset=UTF-8
canal.instance.filter.regex=ll_social\\.post,ll_social\\.group,ll_social\\.tag,ll_social\\.social_user,ll_social\\.post_group,ll_social\\.group_special_post,ll_social\\.comment,ll_social\\.v_post,ll_social\\.v_feed_hot,ll_social\\.post_fav_count,ll_social\\.post_reply_count,ll_social\\.user_group,ll_social\\.group_influence
canal.instance.filter.black.regex=
5.canal的HA客户端使用
代码示例如下
String zookeeperHost = ConfigFace.getServerConfig().getString("zookeeper/host");
CanalConnector connector = CanalConnectors.newClusterConnector(zookeeperHost,canalInstanceName, "", "");
try {
connector.connect();
connector.subscribe("user_db.*");//Canal client端进行过滤
// connector.subscribe();//Canal Server端进行过滤
while (true) {
Message message = connector.getWithoutAck(1000); // 获取指定数量的数据
long batchId = message.getId();
int size = message.getEntries().size();
if (batchId == -1 || size == 0) {
Thread.sleep(1000);
} else {
action(message.getEntries());//进行自己的业务处理
}
connector.ack(batchId); // 提交确认
// connector.rollback(batchId); // 处理失败, 回滚数据
}
} catch (Exception e) {
connector.rollback();
log.error("从canal 中获取数据出错!", e);
} finally {
connector.disconnect();
}
可以看到HA的client也是通过zookeeper来获取对应的server端的信息,并且会监听zookeeper对应的节点信息,当running server 发生变化的时候会进行连接重建,所以可以实现HA。