Canal HA模式讲解及部署

Canal高可用模式介绍

Canal HA一共分为两部分,分别为Canal Server HACanal 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 的使用和优化

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章