Canal高可用模式介绍
Canal HA一共分为两部分,分别为Canal Server HA 和 Canal Client HA
Canal Server HA
说明
需要特别说明的是,Canal 集群的高可用 不是基于Server级别的,而是基于instance级别的,而一个instance对应的是一个mysql实例。
打个比方
假如一共安装了两个Canal Server,数据库端一共有两个instance 需要连接,如果要实现高可用的话,需要在每个Canal Server都配置上这两个instance ,当两个Canal Server启动的时候这两个Canal Server的各两个instance 都会试图去连接这两个 mysql,如果其中一个连接成功,则会在 zookeeper 对应位置注册节点,而另一个Canal Server的instance因为没有抢占成功,处于standby状态。如果当前instance 在与mysql 建立连接并进行binlog解析时,发送一定次数的连接异常,则将会判断为当前instance失效。zookeeper删除相应节点,再次触发抢占。
部署步骤
修改canal.properties文件
canal.zkServers=xxx:2181
canal.instance.global.spring.xml = classpath:spring/default-instance.xml
canal.instance.mysql.slaveId = 1234 ##另外一台机器改成1235,保证两台机器上的slaveId不重复
修改instance.properties文件
canal.instance.mysql.slaveId = 1234 ##另外一台机器改成1235,需要保证两台机器上的slaveId不重复
Canal Client HA
说明
当Canal Server中的某个instance挂掉之后,其他Canal Server在zookeeper中重新抢占节点。而Canal Client HA的原理就是通过监听zookeeper 中的canal节点,当监听到 关于canal节点有变化的时候,触发回调,重新初始化client连接该新的instance节点。这就是Canal Client的HA。
新增监听节点
同样因为这个特性,在canal server运行的情况下,也可以动态添加新加入的instance节点,canal client同样可以通过zookeeper监听到,直接新起一个线程连接该instance即可。
部署步骤
这个没有什么部署,直接写代码监听zookeeper,在zookeeper中对应连接canal即可。
说说Canal和Zookeeper对应节点的关系
/otter/canal:canal的根目录
/otter/canal/cluster:整个canal server的集群列表
/otter/canal/destinations:destination的根目录
/otter/canal/destinations/example/running:服务端当前正在提供服务的running节点
/otter/canal/destinations/example/cluster:针对某个destination的工作集群列表
/otter/canal/destinations/example/1001/running:客户端当前正在读取的running节点
/otter/canal/destinations/example/1001/cluster:针对某个destination的客户端列表
/otter/canal/destinations/example/1001/cursor:客户端读取的position信息
Canal Server HA搭建
机器准备
mysql: 192.168.207.141:3316 用户:root密码:123456
canal: 192.168.207.141 192.168.207.142
zookeeper: 192.168.207.141:192.168.207.142:192.168.207.143:2181
设置数据库binlog
在数据库服务器上将mysql binlog模式开启,并设置为row模式,因为canal对row模式支持较好,支持从指定的binlog的位置读取信息
在两台canal各自所在的机器上修改canal配置
修改canal.properties
canal.zkServers=192.168.207.141:2181,192.168.207.142:2181,192.168.207.143:2181
修改 instance.properties
canal.instance.mysql.slaveId = 1234 #两台不一样
# position info
canal.instance.master.address = 192.168.207.141:3306
canal.instance.dbUsername = root
canal.instance.dbPassword = 123456
# canal.instance.defaultDatabaseName = canal
canal.instance.connectionCharset = UTF-8
两台做同样的操作 开始启动
./startup.sh
查看日志,是否正常启动
tail -200 /example/example.log
编写客户端程序及启动
package com.mytest.canal;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.protocol.CanalEntry.*;
import com.alibaba.otter.canal.protocol.Message;
import com.google.protobuf.InvalidProtocolBufferException;
import com.mytest.canal.bean.CanalStuMsgBean;
import com.mytest.canal.bean.CanalStuScoreBean;
import com.mytest.canal.bean.StudentMsgBean;
import com.mytest.canal.bean.StuentScoreBean;
import com.mytest.kafka.KafkaProduceUtils;
import com.mytest.utils.DateUtil;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
public class CanalClientTest {
private static KafkaProduceUtils kafkaProduceUtils;
public static StringBuilder builder = new StringBuilder();
public static void main(String args[]) {
// kafkaProduceUtils = new KafkaProduceUtils("my_test_topic");
// 创建链接
CanalConnector connector = CanalConnectors.newClusterConnector("192.168.207.141:2181,192.168.207.142:2181,192.168.207.143:2181", "example", "", "");
int batchSize = 1000;
int emptyCount = 0;
try {
connector.connect();
connector.subscribe(".*\\..*");
connector.rollback();
int totalEmptyCount = 12000;
while (emptyCount < totalEmptyCount) {
Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
long batchId = message.getId();
int size = message.getEntries().size();
if (batchId == -1 || size == 0) {
emptyCount++;
System.out.println("empty count : " + emptyCount);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
} else {
emptyCount = 0;
System.out.printf("message[batchId=%s,size=%s] \n", batchId, size);
}
connector.ack(batchId); // 提交确认
// connector.rollback(batchId); // 处理失败, 回滚数据
}
System.out.println("empty too many times, exit");
} catch (Exception e) {
e.printStackTrace();
} finally {
connector.disconnect();
}
}
操作数据库,查看是否正常输出信息
如正常输出信息,重启正在工作的canal server,这时另外一台canal会立即启动 example instance,提供新的数据服务
可以关注一下公众号,我们一起来讨论下spark,flink,canal 的使用和优化