canal+zookeeper+mysql高可用配置

一、部署環境
1.基礎環境:

軟件 版本 作用
Linux Centos7.1,8g
Jdk 1.8.0_151
canal 1.1.1 canal server端,與mysql和zookeeper交互
Zookeeper 3.4.5 作爲canal server端和client的一個代理者,canal1.1.1中依賴的zookeeper的版本爲3.4.5

2.機器環境:canal client服務器2臺,canal server服務器2臺(資源有限,zookeeper和canal server 部署在一起)

分類 IP 安裝軟件
canal-server X.X.X.50:2100 X.X.X.54:2100 canal-server
zookeeper X.X.X.50:2181 X.X.X.54:2181 zookeeper(正常的zookeeper服務要求安裝奇數個,因爲zookeper的leader選舉,要求可用節點數量 > 總節點數量/2。由於資源有限,本文僅啓動了2個的zookeeper服務,這樣其實對高可用產生了影響,因爲如果zookeeper中的任意一臺服務掛掉,也會造成整個canal服務的不可用 )
canal-client X.X.X.X:8999 X.X.X.X:8999 業務模塊作爲canal的client端)

二、安裝canal-server端

  1. 軟件下載
    鏈接:canal.deployer-1.1.1.tar.gz
  2. 解壓文件到指定目錄
    /usr/local/etc
  3. 修改canal.properties配置文件(系統配置文件)
canal.id= 2101 #每個canal server實例的唯一標識,暫無實際意義,默認:1
canal.ip= #canal server綁定的本地IP信息,如果不配置,默認選擇一個本機IP進行啓動服務,默認:無
canal.port= 2100 #canal server提供socket服務的端口,默認:11111
canal.zkServers= X.X.X.X:2181,X.X.X.X:2181 #canal server鏈接zookeeper集羣的鏈接信息
# flush data to zk
canal.zookeeper.flush.period = 1000 #canal持久化數據到zookeeper上的更新頻率,單位毫秒
canal.withoutNetty = false
# tcp, kafka, RocketMQ
canal.serverMode = tcp    #canal server端的模式,可選
# flush meta cursor/parse position to file
canal.file.data.dir = ${canal.conf.dir}
# binlog filter config
canal.instance.filter.druid.ddl = true 
canal.instance.filter.query.dcl = true #是否忽略DCL的query語句,比如grant/create user等,默認false
canal.instance.filter.query.dml = true #是否忽略DML的query語句,比如insert/update/delete table.(mysql5.6ROW模式可以包含statement模式的query記錄),默認false
canal.instance.filter.query.ddl = true #是否忽略DDL的query語句,比如create table/alater table/drop table/rename table/create index/drop index. (目前支持的ddl類型主要爲table級別的操作,create databases/trigger/procedure暫時劃分爲dcl類型),默認false

  1. 修改instance.properties配置文件(instance級別的配置文件)
 canal.instance.mysql.slaveId=0 #mysql集羣配置中的serverId概念,需要保證和當前mysql集羣中id唯一,默認:1234
# position info
canal.instance.master.address=X.X.X.X:3306 #mysql主庫鏈接地址,默認:127.0.0.1:3306
canal.instance.master.journal.name= #mysql主庫鏈接時起始的binlog文件,默認:無
canal.instance.master.position= #mysql主庫鏈接時起始的binlog偏移量,默認:無	
canal.instance.master.timestamp= #mysql主庫鏈接時起始的binlog的時間戳,默認:無
# username/password
canal.instance.dbUsername=canal #mysql數據庫帳號	
canal.instance.dbPassword=canal #mysql數據庫密碼	
canal.instance.connectionCharset = UTF-8 #數據解析編碼
canal.instance.defaultDatabaseName =kuaiche #mysql鏈接時默認schema	
# table regex
canal.instance.filter.regex=kuaiche.bill_transport
# mysql 數據解析關注的表,Perl正則表達式.多個正則之間以逗號(,)分隔,轉義符需要雙斜槓(\\) 
# 常見例子:
# 1. 所有表:.* or .*\\..*
# 2. canal schema下所有表: canal\\..*
# 3. canal下的以canal打頭的表:canal\\.canal.*
# 4. canal schema下的一張表:canal.test1
# 5. 多個規則組合使用:canal\\..*,mysql.test1,mysql.test2 (逗號分隔)
# table black regex
canal.instance.filter.black.regex= # 過濾黑名單:
  1. 啓動命令
    sh bin/startup.sh

  1. 停止命令
    sh bin/stop.sh

  1. 查看啓動日誌
    canal/logs/canal/canal.log
2018-12-05 17:58:26.174 [main] INFO  com.alibaba.otter.canal.deployer.CanalLauncher - ## set default uncaught exception handler
2018-12-05 17:58:26.216 [main] INFO  com.alibaba.otter.canal.deployer.CanalLauncher - ## load canal configurations
2018-12-05 17:58:26.226 [main] INFO  com.alibaba.otter.canal.deployer.CanalLauncher - ## start the canal server.
2018-12-05 17:58:26.636 [main] INFO  com.alibaba.otter.canal.deployer.CanalController - ## start the canal server[X.X.X.X:X]
2018-12-05 17:58:26.832 [main] INFO  com.alibaba.otter.canal.deployer.CanalLauncher - ## the canal server is running now ......

canal/logs/example/example.log


三、canal-client端
1.使用框架:spring-boot
2.依賴jar包

<dependency>
    <groupId>com.alibaba.otter</groupId>
    <artifactId>canal.protocol</artifactId>
    <version>1.1.1</version>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>com.alibaba.otter</groupId>
    <artifactId>canal.client</artifactId>
    <version>1.1.1</version>
    <optional>true</optional>
</dependency>
`注意jar包依賴的衝突,canal依賴的zookeeper版本爲3.4.5`

4.canal數據結構
canal的數據傳輸有兩塊,一塊是進行binlog訂閱時,binlog轉換爲我們所定義的Message,第二塊是client與server進行TCP交互時,傳輸的TCP協議。
Entry數據結構

Entry
    Header
        version         [協議的版本號,default = 1]
        logfileName     [binlog文件名]
        logfileOffset   [binlog position]
        serverId        [服務端serverId]
        serverenCode    [變更數據的編碼]
        executeTime     [變更數據的執行時間]
        sourceType      [變更數據的來源,default = MYSQL]
        schemaName      [變更數據的schemaname]
        tableName       [變更數據的tablename]
        eventLength     [每個event的長度]
        eventType       [insert/update/delete類型,default = UPDATE]
        props           [預留擴展]
        gtid            [當前事務的gitd]
    entryType           [事務頭BEGIN/事務尾END/數據ROWDATA/HEARTBEAT/GTIDLOG]
    storeValue          [byte數據,可展開,對應的類型爲RowChange]    
RowChange
    tableId             [tableId,由數據庫產生]
    eventType           [數據變更類型,default = UPDATE]
    isDdl               [標識是否是ddl語句,比如create table/drop table]
    sql                 [ddl/query的sql語句]
    rowDatas            [具體insert/update/delete的變更數據,可爲多條,1個binlog event事件可對應多條變更,比如批處理]
        beforeColumns   [字段信息,增量數據(修改前,刪除前),Column類型的數組]
        afterColumns    [字段信息,增量數據(修改後,新增後),Column類型的數組] 
        props           [預留擴展]
    props               [預留擴展]
    ddlSchemaName       [ddl/query的schemaName,會存在跨庫ddl,需要保留執行ddl的當前schemaName]
Column 
    index               [字段下標]      
    sqlType             [jdbc type]
    name                [字段名稱(忽略大小寫),在mysql中是沒有的]
    isKey               [是否爲主鍵]
    updated             [是否發生過變更]
    isNull              [值是否爲null]
    props               [預留擴展]
    value               [字段值,timestamp,Datetime是一個時間格式的文本]
    length              [對應數據對象原始長度]
    mysqlType           [字段mysql類型]

4.canal功能含義

- 連接,connector.connect() 
- 訂閱,connector.subscribe 
- 獲取數據,connector.getWithoutAck() 
- 業務處理 
- 提交確認,connector.ack() 
- 回滾,connector.rollback() 
- 斷開連接,connector.disconnect()

4.application.yml配置文件信息

canal:
  server:
    url:   #單機配置,local/dev/test環境使用
    port:   #單機配置,local/dev/test環境使用
    destination: example #環境共用參數
    username:
    password:
    subscribe: #需要監控庫的mysql表
      bc_address_library,bc_contact,bc_contact_company,bc_customer,
      bill_entrust,bill_transport,
      consigner_info,fleet_info,
      fleet_vehicle_info,vehicle_info_temp,fleet_vehicle_mapping,
      bc_goods,queue_appoint_query,queue_appoint_record_log,
      queue_appoint_trucker,queue_appoint_vehicle,queue_warehouse_notice,
      queue_warehouse_notice_trucker_mapping,queue_warehouse_notice_vehicle_mapping
    refreshSeconds: 10 #單位:秒
    zkServers: X.X.X.X:2181,X.X.X.X:2181 # zookeeper HA高可用配置,forecast/prod環境使用
    dbname: kuaiche #數據庫名稱

5.構建連接實例

/**
     * 異常等待時間
     */
    private static final long EXCEPTION_SECONDS = 10;
    /**
     * 線程最長休眠時間
     */
    private static final long MAX_SLEEP_SECONDS = 30;

    @Value("${canal.server.zkServers}")
    private String zkServers;

    @Value("${canal.server.subscribe}")
    private String subscribe;

    @Value("${canal.server.destination}")
    private String destination;

    @Value("${canal.server.refreshSeconds}")
    private long refreshSeconds;

    @Value("${canal.server.dbname}")
    private String dbname;

    private final JsonUtility jsonUtility;

    @Autowired
    public CanalClusterClient(JsonUtility jsonUtility) {
        this.jsonUtility = jsonUtility;
    }

    private CanalConnector connector;
try {
            while (true) {
                //初始化連接,或連接失效時,連接canal server,每次獲取數據時都檢查,確保連接有效性
                if(null==connector || !connector.checkValid()){
                    try {
                        connector = CanalConnectors.newClusterConnector(zkServers, destination, "", "");
                        connector.connect();
                        connector.subscribe(subscribe);
                        log.debug(">>>> Connection canal server successful,zkServers:【{}】,subscribe:【{}】 <<<<<",zkServers,subscribe);
                    } catch (Exception e) {
                        log.debug(">>>> Connection canal server failed,zkServers:【{}】,subscribe:【{}】, exception:【{}】...try again after 10s <<<<<",zkServers,subscribe,e.getMessage());
                        Thread.sleep(EXCEPTION_SECONDS);
                        continue;
                    }
                }
                // 獲取指定數量的數據
                Message message = connector.getWithoutAck(batchSize);
                if(null == message){
                    log.debug(">>>> Canal client connect zookeeper server is running, get Message is null! <<<<<");
                    Thread.sleep(EXCEPTION_SECONDS);
                    continue;
                }
                //刷新間隔時間不超過30s
                if(refreshSeconds>MAX_SLEEP_SECONDS){refreshSeconds=EXCEPTION_SECONDS;}
                //獲取同步id
                long batchId = message.getId();
                int size = message.getEntries().size();
                if (batchId == -1 || size == 0) {
                    Thread.sleep(refreshSeconds*1000);
                    refreshSeconds++;
                    if(refreshSeconds<MAX_SLEEP_SECONDS){
                        log.debug(">>>> Canal client connect zookeeper server is running, get Message size is empty, ...try again after {}s! <<<<<",refreshSeconds);

                    }else if(refreshSeconds == MAX_SLEEP_SECONDS){
                        log.debug(">>>> Canal client connect zookeeper server【{}】,subscribe:【{}】is running, get Message size is empty, ...try again after {}s! <<<<<",zkServers,subscribe,refreshSeconds);
                    }
                } else {
                    try {
                        // 異步請求開始時間
                        long asyncBeginTime = System.currentTimeMillis();
                        jsonUtility.printEntry(message.getEntries(),batchId);
                        //結束時間
                        long asyncEndTime = System.currentTimeMillis();
                        log.debug(">>>>Canal clent batchId:{},Async method time-consuming:{}ms<<<<<" , batchId,asyncEndTime-asyncBeginTime);
                    } catch (Exception e) {
                        log.error(">>>> PrintEntry Exception:{} <<<<<" , e);
                    }
                }
                // 提交確認
                connector.ack(batchId);
            }
        } catch (Exception e) {
            log.error(">>>> get message from canal zooleeper server:【{}】,subscribe:【{}】error:【{}】 <<<<<",zkServers,subscribe,e.getMessage());
        }

四、安裝zookeeper
1.安裝詳見另一篇:zookeeper
注意:canal中依賴的zookeeper的版本爲3.4.5,下載時注意選擇對應的版本號


五、HA模式測試
1.前提條件:

`以下服務都已正常啓動`
- canal-server,X.X.X.50:2100X.X.X.54:2100
- canal-client,X.X.X.107:8999X.X.X.85:8999
- zookeeper,X.X.X.50:2181X.X.X.54:2181

2.狀態檢測

  • 連接任一zookeeper客戶端
    ./zkCli.sh -server X.X.X.50:2181
  • 獲取正在同步數據的canal server
    get /otter/canal/destinations/example/running
{"active":true,"address":"X.X.X.50:2100","cid":2101}
cZxid = 0x100028621
ctime = Wed Dec 05 17:56:01 CST 2018
mZxid = 0x100028621
mtime = Wed Dec 05 17:56:01 CST 2018
pZxid = 0x100028621
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x16772fcc6300017
dataLength = 57
numChildren = 0
  • 獲取正在連接的canal client
    get /otter/canal/destinations/example/1001/running
{"active":true,"address":"X.X.X.X:15586","clientId":1001}
cZxid = 0x10002a72c
ctime = Wed Dec 26 17:13:25 CST 2018
mZxid = 0x10002a72e
mtime = Wed Dec 26 17:13:25 CST 2018
pZxid = 0x10002a72c
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x16772fcc6300024
dataLength = 63
numChildren = 0
numChildren = 0

3.HA切換測試

  • a)在zookeeper客戶端使用 get /otter/canal/destinations/example/running命令,獲取到當前正在同步數據的canal server服務的ip地址,正常關閉canal server(會釋放instance的所有資源,包括刪除running節點);
  • b)根據上一步獲取到的服務地址,登錄對應的服務器使用sh bin/stop.sh命令停止該server服務;
  • c)重複步驟a,會看到另一臺canal server成爲正在運行的server端;
  • d)在zookeeper客戶端使用 get /otter/canal/destinations/example/1001/running 命令,獲取到當前正在同步數據的canal client服務的ip地址;
  • f)根據上一步獲取到的服務地址,登錄對應的服務器 kill該canal client的進程;
  • g)重複步驟d,會看到另一臺canal client成爲正在連接的client端;
    在切換期間,可以實時修改mysql數據庫的數據,查看對應的canal客戶端日誌輸出信息。

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