Canal 介紹及使用

HuDu發表於2022-09-28

1.1、什麼是 Canal

Canal 是用 Java 開發的基於資料庫增量日誌解析,提供增量資料訂閱&消費的中介軟體。目前。Canal 主要支援了 MySQL 的 Binlog 解析,解析完成後才利用 Canal Client 來處理獲得的相關資料。(資料庫同步需要阿里的Otter中介軟體,基於Canal)。

1.2、MySQL 的 Binlog

1.2.1、什麼是 Binglog

MySQL 的二進位制日誌可以說 MySQL 最重要的日誌了,它記錄了所有的 DDL 和 DML (除了資料查詢語句)語句,以事件形式記錄,還包含語句所執行的消耗的時間,MySQL的二進位制日誌是事務安全型的。

一般來說開啟二進位制日誌大概會有1%的效能損耗。二進位制有兩個最重要的使用場景:

  • 其一: MySQL Replication在Master端開啟Binlog, Master把它的二進位制日誌傳遞給Slaves 來達到Master-Slave資料一致的目的。
  • 其二:自然就是資料恢復了,透過使用MySQL Binlog工具來使恢復資料。

二進位制日誌包括兩類檔案:二進位制日誌索引檔案(檔名字尾為.index)用於記錄所有
的二進位制檔案,二進位制日誌檔案(檔名字尾為.00000*)記錄資料庫所有的DDL和DML(除
了資料查詢語句)語句事件。

1.2.2、Binglog 分類

MySQL Binlog 的格式有三種,分別是STATEMENTMIXEDROW。在配置檔案中可以選擇配
binlog_ format= statement| mixed |row。三種格式的區別:

  1. statement:語句級,binlog會記錄每次一執行寫操作的語句。相對row模式節省空,但是可能會會產生不一致,比如一些函式,例如update t set create_date = now(),如果使用 binlog 日誌,進行恢復,由於執行時間不同,可能產生的資料就不同
    1. 優點:節省空間
    2. 缺點:又可能造成資料不一致
  2. row:行級,binglog 會記錄每次操作後每行記錄的變化
    1. 優點:保持資料的絕對一致。因為不管 sql 是什麼,引用了什麼函式,它只記錄執行後的結果
    2. 缺點:佔用空間較大
  3. mixed:stetement 的升級版,一定程度上解決了一些情況而造成的 statement 模式不一致問題,預設還是 statement,在某些情況下譬如:當函式中包含UUID() 時;包含AUTO_ INCREMENT欄位的表被更新時;執行INSERT DELAYED語句時;用UDF時;會按照 ROW 的方式進行處理。
    1. 優點:節省空間,同時兼顧了一定的一致性。
    2. 缺點:還有些極個別情況依舊會造成不一致,另外 stetement 和 mixed 對於需要 binlog 的監控情況都不方便。

綜上所述,Canal 先做監控分析,選擇 row 格式比較合適。

1.3、Canal 工作原理

  • canal 模擬 MySQL slave 的互動協議,偽裝自己為 MySQL slave ,向 MySQL master 傳送 dump 協議
  • MySQL master 收到 dump 請求,開始推送 binary log 給 slave (即 canal )
  • canal 解析 binary log 物件(原始為 byte 流)

1.4、使用場景

1.4.1、原始場景

阿里 Otter 中介軟體的一部分,Otter 是阿里用於進行非同步資料庫之間同步框架,Canal 是其中一部分

Canal 介紹及使用

1.4.2、常用場景一

更新快取,例如當資料寫入到 MySQL 中時,將增加或修改的資料同步到快取中,使用者每次直接從快取中拿取資料,如果沒獲取到,再從資料庫查詢。

1.4.3、常用場景二

抓取業務表的新增變化資料,用於製作實時統計

這裡以mysql8.0.28canal-1.1.7-alpha-1,為例,由於使用的是 mysql8,如果 canal 版本過低會導致各種 bug。

2.1、建立資料庫

CREATE DATABASE `canal_test` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */ /*!80016 DEFAULT ENCRYPTION='N' */

2.2、建立資料庫表

CREATE TABLE user_info(
  `id` VARCHAR(255),
    `name` VARCHAR(255),
    `sex` VARCHAR(255)
)

2.3、配置檔案開啟 Binglog

$ vim /etc/mysql/my.cnf

[mysqld]
log-bin=mysql-bin # 開啟 binlog
binlog-format=ROW # 選擇 ROW 模式
binglog-do-db=canal_test
server_id=1 # 配置 MySQL replaction 需要定義,不要和 canal 的 slaveId 重複

注意: binglog-do-db 根據自己情況進行修改,指定具體要同步的資料庫,如果不配置,表示所有的資料均開啟 Binglog

2.4、重啟 MySQL 生效

$ sudo systemctl restart mysqld

2.5、建立使用者並賦權

-- 由於預設密碼比較嚴格,降低密碼策略嚴格程度
SHOW VARIABLES LIKE 'validate_password%';
SET GLOBAL validate_password.policy = LOW;
SET GLOBAL validate_password.length = 4;

CREATE USER 'canal'@'%' IDENTIFIED by 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
FLUSH PRIVILEGES;

3.1、下載並解壓

Canal 下載地址
Canal 官方文件地址

$ mkdir canal
$ tar -zxvf canal.deployer-1.1.7-SNAPSHOT.tar.gz -C canal

3.2、修改 canal.properties 檔案

修改canal.properties檔案

canal.serverMode = tcp

說明:這個檔案是 canal 的基本迪用配置,canal 埠號預設就是 11111,修改 canal 的輸出 model,預設tcp,改為輸出到 kafka 等訊息中介軟體。

多例項配置如果建立多個例項,透過前面canal架構,我們可以知道,一個canal服務中可以有多個instance,conf下的每一個 example 即是一個例項,每個例項下面都有獨立的配置檔案。預設只有一個例項 example,如果需要多個例項處理不同的 MySQL 資料的話,直接複製出多個 example,並對其重新命名,命名和配置檔案中指定的名稱一致,然後修改 canal.properties 中的 canal.destinations=例項1,例項2,例項3

canal.destinations = example

3.3、修改 instance.properties

這裡按一個資料庫為例

$ cd canal/conf/example
$ vim instance.properties

3.3.1、配置 MySQL 伺服器地址

canal.instance.mysql.slaveId=20
canal.instance.master.address=ip:port

# 關閉 tsdb
canal.instance.tsdb.enable=false

3.3.2、配置連線 MySQL 使用者名稱和密碼

# username/password
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal
canal.instance.connectionCharset = UTF-8
# enable druid Decrypt database password
canal.instance.enableDruid=false

3.3.3、 開放埠

# canal admin 埠
$ firewall-cmd --zone=public --add-port=11110/tcp --permanent
# canal 監聽埠
$ firewall-cmd --zone=public --add-port=11111/tcp --permanent
# canal 指標短褲哦
$ firewall-cmd --zone=public --add-port=11112/tcp --permanent
$ firewall-cmd --reload

4.1、拉取映象

# 拉取映象
$ docker pull canal/canal-server:v1.1.6
# 下載指令碼
$ wget https://raw.githubusercontent.com/alibaba/canal/master/docker/run.sh

# 構建一個destination name為test的佇列
sh run.sh -e canal.auto.scan=false
          -e canal.destinations=test
          -e canal.instance.master.address=127.0.0.1:3306
          -e canal.instance.dbUsername=canal
          -e canal.instance.dbPassword=canal
          -e canal.instance.connectionCharset=UTF-8
          -e canal.instance.tsdb.enable=true
          -e canal.instance.gtidon=false

問題說明

canal 啟動時,報錯

Canal 介紹及使用

因為自MySQL 8.0.3開始,身份驗證外掛預設使用caching_sha2_password

問題解決

修改canal使用者對應的身份驗證外掛為 mysql_native_password

mysql> select host,user,plugin from mysql.user ;
mysql> ALTER USER 'canal'@'%' IDENTIFIED WITH mysql_native_password BY 'password';

再次啟動即可。

6.1、引入依賴

<dependencies>
  <dependency>
  <groupId>com.alibaba.otter</groupId>
 <artifactId>canal.client</artifactId>
 <version>1.1.6</version>
  </dependency>
 <dependency>
  <groupId>com.alibaba.otter</groupId>
 <artifactId>canal.protocol</artifactId>
 <version>1.1.6</version>
  </dependency>
</dependencies>

6.2、客戶端程式碼

public class CanalClient {

    private static final Logger log = LoggerFactory.getLogger(CanalClient.class);

    public static void main(String[] args) throws InterruptedException, InvalidProtocolBufferException {
        // 獲取連線
        CanalConnector canalConnector =
                CanalConnectors.newSingleConnector(new InetSocketAddress("192.168.33.81", 11111), "example", "", "");

        // 連線
        canalConnector.connect();

        // 訂閱資料庫,這裡一定要是world.*否則無法獲取到資料
        canalConnector.subscribe("world.*");

         while (true) {
            // 獲取資料,一次拉取 100 條資料
            Message message = canalConnector.get(100);

            List<CanalEntry.Entry> entries = message.getEntries();

            // 判斷集合是否為空,如果為空則等待之後再拉取
            if (entries.isEmpty()) {
                log.warn("當此抓取沒有資料,等待片刻");
                TimeUnit.SECONDS.sleep(5);
            } else {
                // 遍歷 entries
                for (CanalEntry.Entry entry : entries) {
                    // 1.獲取表名
                    String tableName = entry.getHeader().getTableName();

                    // 2.獲取型別
                    CanalEntry.EntryType entryType = entry.getEntryType();

                    // 3.獲取序列化的資料
                    ByteString storeValue = entry.getStoreValue();
                    // 4.判斷 entryType 是否為 ROWDATA 型別
                    if (CanalEntry.EntryType.ROWDATA.equals(entryType)) {
                        // 5.反序列化資料
                        CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(storeValue);

                        //6.獲取當前時間的操作型別
                        CanalEntry.EventType eventType = rowChange.getEventType();

                        //7.獲取行資料集
                        List<CanalEntry.RowData> rowDatesList = rowChange.getRowDatasList();

                        //8.遍歷  rowDatesList 並列印資料集
                        for (CanalEntry.RowData rowData : rowDatesList) {
                            JSONObject beforeDate = new JSONObject();
                            List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
                            for (CanalEntry.Column column : beforeColumnsList) {
                                beforeDate.put(column.getName(), column.getValue());
                            }

                            JSONObject afterDate = new JSONObject();
                            List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();
                            for (CanalEntry.Column column : afterColumnsList) {
                                afterDate.put(column.getName(), column.getValue());
                            }

                            // 資料的列印
                            log.info("Table: {},EventType: {},Before: {},After: {}", tableName, eventType, beforeDate, afterDate);
                        }
                    } else {
                        log.warn("當前操作型別為: {}", entryType);
                    }
                }
            }
         }
    }
}

6.3、測試

6.3.1、新增資料

INSERT INTO user_info VALUES('1002','test2','female'),('1003','test3','male');

效果如下

Canal 介紹及使用

6.3.2、刪除資料

DELETE FROM user_info WHERE id = '1001';

Canal 介紹及使用

6.3.3、修改資料

UPDATE user_info SET name = 'female' WHERE id = '1003';

Canal 介紹及使用

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章