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 的使用和優化

 

 

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