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