一口氣搞定系列-Canal組件

1,Canal 組件部署

  1. 安裝配置MySQL
    1.1 安裝 mysql囊陡,
    1.2 配置 mysql binlog使用ROW模式
    1.3 在MySQL添加對應(yīng)的canal用戶
    1.4 檢查canal用戶生效
  2. 下載canal并配置
    2.1 下載canal(1.1.4)
    2.2 配置 canal(1.1.4)
    2.3 啟動canal (需要JDK>=1.6.25)

(1) 配置MySQL

(1.1) 安裝MySQL

(1.2) 修改MySQL配置文件

canal的原理是基于mysql binlog技術(shù)纽乱,所以需要開啟mysql的binlog寫入功能蛾绎,并且配置binlog模式為row.

[mysqld]  
log-bin=mysql-bin  # 開啟 binlog
binlog-format=ROW  # 選擇 ROW 模式
server_id=1        # 配置 MySQL replaction ,不能和 canal 的 slaveId 重復(fù)

(1.3) MySQL添加canal用戶并授權(quán)

canal的原理是模擬自己為mysql slave迫淹,所以需要mysql slave的相關(guān)權(quán)限

CREATE USER canal IDENTIFIED BY 'canal';    

GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%'; 

FLUSH PRIVILEGES;

(1.4) 校驗(yàn)用戶對應(yīng)權(quán)限

  1. show master status ;
    如果正常顯示binlog秘通,則沒問題,如果提示 Access denied; you need (at least one of) the SUPER, REPLICATION CLIENT privilege(s) for this operation 敛熬,則沒有對應(yīng) REPLICATION CLIENT 權(quán)限

  2. show slave status ;
    如果提示 Access denied; you need (at least one of) the REPLICATION SLAVE privilege(s) for this operation ,則沒有對應(yīng) REPLICATION SLAVE 權(quán)限

(2) 下載并啟動canal

執(zhí)行 ./bin/startup.sh 即可啟動

(2.1) 下載canal

https://github.com/alibaba/canal/releases 選擇合適的版本
下載 wget https://github.com/alibaba/canal/releases/download/canal-1.1.14/canal.deployer-1.1.14.tar.gz

(2.2) 修改配置

修改 conf/example/instance.properties
以下只列出比較重要的配置
詳細(xì)的參數(shù)的說明可以參考這個(gè)網(wǎng)址

## mysql serverId  不能重復(fù)
canal.instance.mysql.slaveId = 1234
#position info第股,需要改成自己的數(shù)據(jù)庫信息
canal.instance.master.address = 127.0.0.1:3306 
#username/password应民,需要改成自己的數(shù)據(jù)庫信息
canal.instance.dbUsername = canal  
canal.instance.dbPassword = canal
canal.instance.defaultDatabaseName = database_wkq
canal.instance.connectionCharset = UTF-8
#table regex  需要監(jiān)控的表 通過,分隔  也可以使用正則 .*\\..*
canal.instance.filter.regex = table_wkq,table_2,table_3
# table black regex
canal.instance.filter.black.regex =

(2.3) 啟動canal

通過 sh bin/startup.sh 或者 ./bin/startup.sh 啟動

啟動后通過 jps -l 命令 可以看到 com.alibaba.otter.canal.deployer.CanalLauncher

canal啟動時(shí)canal.log

canal.deployer-1.0.24/logs/canal/canal.log

2018-07-23 20:27:46.449 [main] INFO  com.alibaba.otter.canal.deployer.CanalLauncher - ## start the canal server.
2018-07-23 20:27:46.625 [main] INFO  com.alibaba.otter.canal.deployer.CanalController - ## start the canal server[10.0.62.130:11111]
2018-07-23 20:27:47.576 [main] INFO  com.alibaba.otter.canal.deployer.CanalLauncher - ## the canal server is running now ......
2018-07-23 20:27:47.721 [canal-instance-scan-0] INFO  c.a.o.canal.deployer.monitor.SpringInstanceConfigMonitor - auto notify start xxx1 successful.
2018-07-23 20:27:47.802 [canal-instance-scan-0] INFO  c.a.o.canal.deployer.monitor.SpringInstanceConfigMonitor - auto notify start xxx2 successful.
2018-07-23 20:27:47.862 [canal-instance-scan-0] INFO  c.a.o.canal.deployer.monitor.SpringInstanceConfigMonitor - auto notify start xxx3 successful.
2018-07-23 20:27:47.921 [canal-instance-scan-0] INFO  c.a.o.canal.deployer.monitor.SpringInstanceConfigMonitor - auto notify start xxx4 successful.
2018-07-23 20:27:47.987 [canal-instance-scan-0] INFO  c.a.o.canal.deployer.monitor.SpringInstanceConfigMonitor - auto notify start xxx5 successful.
2018-07-23 20:27:48.044 [canal-instance-scan-0] INFO  c.a.o.canal.deployer.monitor.SpringInstanceConfigMonitor - auto notify start xxx6 successful.
2018-07-23 20:27:48.094 [canal-instance-scan-0] INFO  c.a.o.canal.deployer.monitor.SpringInstanceConfigMonitor - auto notify start xxx7 successful.

(2.2) canal正常啟動時(shí)instance對應(yīng)的日志

canal.deployer-1.0.24/logs/example/example.log
2018-07-23 20:27:47.429 [canal-instance-scan-0] INFO  c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [canal.properties]
2018-07-23 20:27:47.436 [canal-instance-scan-0] INFO  c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [xxx/instance.properties]
2018-07-23 20:27:47.444 [canal-instance-scan-0] WARN  org.springframework.beans.TypeConverterDelegate - PropertyEditor [com.sun.beans.editors.EnumEditor] found through deprecated global PropertyEditorManager fallback - consider using a more isolated form of registration, e.g. on the BeanWrapper/BeanFactory!
2018-07-23 20:27:47.451 [canal-instance-scan-0] INFO  c.a.otter.canal.instance.spring.CanalInstanceWithSpring - start CannalInstance for 1-xxx 
2018-07-23 20:27:47.453 [canal-instance-scan-0] INFO  c.a.otter.canal.instance.core.AbstractCanalInstance - start successful....
2018-07-23 20:27:47.666 [destination = xxx , address = /127.0.0.1:3306 , EventParser] WARN  c.a.otter.canal.parse.inbound.mysql.MysqlEventParser - prepare to find start position just show master status

(3)停止canal

sh stop.sh` 或 `./bin/stop.sh
2018-07-23 21:45:08.241 [Thread-5] INFO  com.alibaba.otter.canal.deployer.CanalLauncher - ## stop the canal server
2018-07-23 21:45:08.296 [Thread-5] INFO  com.alibaba.otter.canal.deployer.CanalController - ## stop the canal server[10.0.62.130:11111]
2018-07-23 21:45:08.296 [Thread-5] INFO  com.alibaba.otter.canal.deployer.CanalLauncher - ## canal server is down.

(4) 程序中使用

以下代碼僅作為示例

<dependency>
    <groupId>com.alibaba.otter</groupId>
    <artifactId>canal.client</artifactId>
    <version>1.1.4</version>
</dependency>
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 lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * CanalTest
 *
 * @author: weikeqin.cn@gmail.com
 * @date: 2020-05-30 08:26
 **/
@Slf4j
public class CanalTest {

    /**
     * @param args
     */
    public static void main(String args[]) {

        String canalHost = "127.0.0.1";
        int canalPort = 11111;
        String destination = "example";
        InetSocketAddress address = new InetSocketAddress(canalHost, canalPort);

        // 創(chuàng)建鏈接
        CanalConnector connector = CanalConnectors.newSingleConnector(address, destination, "", "");
        // connector = CanalConnectors.newClusterConnector(addresses, destination, "", "");

        int batchSize = 1000;
        int emptyCount = 0;
        try {
            // 鏈接對應(yīng)的canal server
            connector.connect();
            // 客戶端訂閱,不提交客戶端filter夕吻,以服務(wù)端的filter為準(zhǔn)
            connector.subscribe();
            // 回滾到未進(jìn)行ack的地方诲锹,下次fetch的時(shí)候,可以從最后一個(gè)沒有 ack 的地方開始拿
            connector.rollback();

            int totalEmptyCount = 12000000;

            // 退出條件 一般是 while true
            while (emptyCount < totalEmptyCount) {
                // 獲取指定數(shù)量的數(shù)據(jù)
                Message message = connector.getWithoutAck(batchSize);
                long batchId = message.getId();
                int size = message.getEntries().size();

                if (batchId == -1 || size == 0) {

                    emptyCount++;
                    log.info("empty count : " + emptyCount);

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        log.info("", e);
                    }

                } else {
                    emptyCount = 0;
                    log.info("message[batchId={},size={}] ", batchId, size);

                    // 消費(fèi)
                    consumeMsg(message.getEntries());
                }

                // 提交確認(rèn)
                connector.ack(batchId);
                // 處理失敗, 回滾數(shù)據(jù)
                // connector.rollback(batchId);
            }

            log.info("empty too many times, exit");
        } finally {
            // 釋放鏈接
            connector.disconnect();
        }
    }

    /**
     * 消費(fèi)消息
     *
     * @param entries
     */
    private static void consumeMsg(List<CanalEntry.Entry> entries) {

        // 這里只打印
        printEntry(entries);
        // TODO 其它操作

    }

    /**
     * @param entrys
     */
    private static void printEntry(List<CanalEntry.Entry> entrys) {

        for (CanalEntry.Entry entry : entrys) {

            if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN || entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
                continue;
            }

            CanalEntry.RowChange rowChage = null;
            try {
                rowChage = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
                throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(), e);
            }

            CanalEntry.EventType eventType = rowChage.getEventType();
            log.info(String.format("================  binlog[%s:%s] , name[%s,%s] , eventType : %s",
                    entry.getHeader().getLogfileName(),
                    entry.getHeader().getLogfileOffset(),
                    entry.getHeader().getSchemaName(),
                    entry.getHeader().getTableName(),
                    eventType)
            );

            for (CanalEntry.RowData rowData : rowChage.getRowDatasList()) {

                if (eventType == CanalEntry.EventType.DELETE) {
                    printColumn(rowData.getBeforeColumnsList());
                } else if (eventType == CanalEntry.EventType.INSERT) {
                    printColumn(rowData.getAfterColumnsList());
                } else {
                    log.info("------- before");
                    printColumn(rowData.getBeforeColumnsList());
                    log.info("------- after");
                    printColumn(rowData.getAfterColumnsList());
                }
            }
        }
    }

    /**
     * @param columns
     */
    private static void printColumn(List<CanalEntry.Column> columns) {
        Map<String, String> map = new HashMap<>();
        for (CanalEntry.Column column : columns) {
            map.put(column.getName(), column.getValue());
            //log.info(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
        }
        log.info("{}", map);
    }


}

(5) canal復(fù)制原理

復(fù)制如何工作涉馅,整體上來說归园,復(fù)制有3個(gè)步驟:
(1) master將改變記錄到二進(jìn)制日志(binary log)中(這些記錄叫做二進(jìn)制日志事件,binary log events)稚矿;
(2) slave將master的binary log events復(fù)制到它的中繼日志(relay log)中庸诱;
(3) slave讀取中繼日志中的事件,將其重放到備庫數(shù)據(jù)之上晤揣。

下圖描述了復(fù)制的過程:
[圖片上傳失敗...(image-b777c3-1662962030216)]

(6) 遇到的問題

(6.1) Error When doing Client Authentication:ErrorPacket

Caused by: java.io.IOException: connect /127.0.0.1:3306 failure:java.io.IOException: Error When doing Client Authentication:ErrorPacket [errorNumber=1045, fieldCount=-1, message=Access denied for user 'canal'@'localhost' (using password: YES), sqlState=28000, sqlStateMarker=#]
    at com.alibaba.otter.canal.parse.driver.mysql.MysqlConnector.negotiate(MysqlConnector.java:208)
    at com.alibaba.otter.canal.parse.driver.mysql.MysqlConnector.connect(MysqlConnector.java:71)
    at com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.connect(MysqlConnection.java:56)
    at com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser.preDump(MysqlEventParser.java:86)
    at com.alibaba.otter.canal.parse.inbound.AbstractEventParser$3.run(AbstractEventParser.java:157)
    at java.lang.Thread.run(Thread.java:748)

原因 用戶名密碼不正確

(6.2) Access denied; you need (at least one of) the SUPER, REPLICATION CLIENT privilege(s) for this operation

ERROR com.alibaba.otter.canal.common.alarm.LogAlarmHandler - destination:xx
[com.alibaba.otter.canal.parse.exception.CanalParseException: command : 'show master status' has an error!
Caused by: java.io.IOException: ErrorPacket [errorNumber=1227, fieldCount=-1, message=Access denied; you need (at least one of) the SUPER, REPLICATION CLIENT privilege(s) for this operation, sqlState=42000, sqlStateMarker=#]
 with command: show master status

用canal賬戶登錄后發(fā)現(xiàn)可以查看對應(yīng)數(shù)據(jù)庫對應(yīng)表的數(shù)據(jù)桥爽,但是 show master status 提示 Access denied; you need (at least one of) the SUPER, REPLICATION CLIENT privilege(s) for this operation

1、instance.properties配置文件里配置的用戶沒有REPLICATION權(quán)限
2昧识、canal instance.properties 配置錯(cuò)誤
3钠四、配置文件里用戶名密碼不正確
4、MySQL對應(yīng)用戶不存在
5跪楞、MySQL配置不對

給canal用戶對應(yīng)的replication權(quán)限
grant replication client on *.* to 'canal'@'%';
flush privileges

(6.3) Access denied; you need (at least one of) the REPLICATION SLAVE privilege(s) for this operation

[destination = xxx , address = /127.0.0.1:3306 , EventParser] ERROR com.alibaba.otter.canal.common.alarm.LogAlarmHandler - destination:xx[java.io.IOException: Received error packet: errno = 1227, sqlstate = 42000 errmsg = Access denied; you need (at least one of) the REPLICATION SLAVE privilege(s) for this operation
    at com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher.fetch(DirectLogFetcher.java:95)

Access denied 沒權(quán)限 需要給對應(yīng)賬戶授權(quán)
REPLICATION SLAVE 常用于建立復(fù)制時(shí)所需要用到的用戶權(quán)限缀去,也就是slave server必須被master server授權(quán)具有該權(quán)限的用戶,才能通過該用戶復(fù)制甸祭。
并且”SHOW SLAVE HOSTS”這條命令和REPLICATION SLAVE權(quán)限有關(guān)缕碎,否則執(zhí)行時(shí)會報(bào)錯(cuò):

REPLICATION CLIENT 不可用于建立復(fù)制,有該權(quán)限時(shí)淋叶,只是多了可以使用如”SHOW SLAVE STATUS”阎曹、”SHOW MASTER STATUS”等命令伪阶。
在5.6.6版本以后,也可以使用”SHOW BINARY LOGS”处嫌。

GRANT REPLICATION SLAVE ON *.* TO 'canal'@'%'`
`flush privileges

(6.4) canal用了UseConcMarkSweepGC不能用JDK14

Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option PermSize; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option MaxPermSize; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option UseConcMarkSweepGC; support was removed in 14.0
Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option CMSParallelRemarkEnabled; support was removed in 14.0
Unrecognized VM option 'UseCMSCompactAtFullCollection'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

修改 bin/start.sh 文件栅贴,修改對應(yīng)的JAVA路徑

## set java path
if [ -z "$JAVA" ] ; then
  #JAVA=$(which java)
  JAVA="/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java"
fi

(6.5) com.alibaba.otter.canal.protocol.exception.CanalClientException: java.net.ConnectException: Connection refused

Exception in thread "main" com.alibaba.otter.canal.protocol.exception.CanalClientException: java.net.ConnectException: Connection refused
    at com.alibaba.otter.canal.client.impl.SimpleCanalConnector.doConnect(SimpleCanalConnector.java:198)
    at com.alibaba.otter.canal.client.impl.SimpleCanalConnector.connect(SimpleCanalConnector.java:115)

canal沒啟動 或者 canal掛了
配置被刪了,檢查對應(yīng) destinationinstance.properties
instance.properties 沒配置

(7) canal-admin后臺管理

canal-admin設(shè)計(jì)上是為canal提供整體配置管理熏迹、節(jié)點(diǎn)運(yùn)維等面向運(yùn)維的功能檐薯,提供相對友好的WebUI操作界面,方便更多用戶快速和安全的操作
簡單來說注暗,canal-admin是一個(gè)后臺維護(hù)系統(tǒng)坛缕,簡化了配置canal的工作,提高了效率捆昏,終于不用到服務(wù)器上一個(gè)一個(gè)配了

訪問地址 http://127.0.0.1:8089/

(7.1) canal-admin的核心模型主要有

instance赚楚,對應(yīng)canal-server里的instance,一個(gè)最小的訂閱mysql的隊(duì)列
server骗卜,對應(yīng)canal-server宠页,一個(gè)server里可以包含多個(gè)instance
集群,對應(yīng)一組canal-server寇仓,組合在一起面向高可用HA的運(yùn)維

References

[1] canal/wiki
[2] canal-AdminGuide
[3] ClientExample
[4] Canal-Admin-QuickStart
[5] Canal-Admin-Guide
[6] canal配置使用
[7] Mysql 普通賬戶授權(quán)replication client后登錄失敗問題
[8] REPLICATION SLAVE 與 REPLICATION CLIENT 權(quán)限
[9] 對replication slave,replication client的一點(diǎn)說明
[10] MySQL 5.6 Reference Manual – 6.2.1 Privileges Provided by MySQL
[11] SimpleCanalClientTest
[12] ClusterCanalClientTest

2,原理簡介

  • canal 模擬 MySQL slave 的交互協(xié)議举户,偽裝自己為 MySQL slave ,向 MySQL master 發(fā)送dump 協(xié)議
  • MySQL master 收到 dump 請求遍烦,開始推送 binary log 給 slave (即 canal )
  • canal 解析 binary log 對象(原始為 byte 流)

(1)數(shù)據(jù)庫主從同步

  • Mysql Master 首先將數(shù)據(jù)寫入二進(jìn)制文件(binary log)俭嘁,可以通過 show binlog events 進(jìn)行查看
  • Mysql Slave 將Mysql 的 binary log events 拷貝到中間日志(relay log)
  • Mysql Slave 重放 relay log中事件,將數(shù)據(jù)變更反映他自己的數(shù)據(jù)
原理.png

(2)同步一致性問題

「MySQL的復(fù)制分為:異步復(fù)制服猪、半同步復(fù)制供填、全同步復(fù)制÷Γ」

1),異步復(fù)制

MySQL默認(rèn)的復(fù)制即是異步復(fù)制捕虽,主庫在執(zhí)行完客戶端提交的事務(wù)后會立即將結(jié)果返給給客戶端,并不關(guān)心從庫是否已經(jīng)接收并處理坡脐,這樣就會有一個(gè)問題泄私,「主如果crash掉了,此時(shí)主上已經(jīng)提交的事務(wù)可能并沒有傳到從庫上备闲,如果此時(shí)晌端,強(qiáng)行將從提升為主,可能導(dǎo)致新主上的數(shù)據(jù)不完整恬砂∵志溃」

2),全同步復(fù)制

指當(dāng)主庫執(zhí)行完一個(gè)事務(wù),所有的從庫都執(zhí)行了該事務(wù)才返回給客戶端泻骤。「因?yàn)樾枰却袕膸靾?zhí)行完該事務(wù)才能返回」漆羔,所以全同步復(fù)制的性能必然會收到嚴(yán)重的影響梧奢。

3),半同步復(fù)制

是介于全同步復(fù)制與全異步復(fù)制之間的一種,「主庫只需要等待至少一個(gè)從庫節(jié)點(diǎn)收到」并且 Flush Binlog 到 Relay Log 文件即可演痒,主庫不需要等待所有從庫給主庫反饋亲轨。同時(shí),「這里只是一個(gè)收到的反饋鸟顺,而不是已經(jīng)完全完成并且提交的反饋」惦蚊,如此,節(jié)省了很多時(shí)間讯嫂。

(3)canal.admin蹦锋、adapter和deployer的區(qū)別

canal.example

是demo工程

canal-admin

canal-admin設(shè)計(jì)上是為canal提供整體配置管理、節(jié)點(diǎn)運(yùn)維等面向運(yùn)維的功能欧芽,提供相對友好的WebUI操作界面莉掂,方便更多用戶快速和安全的操作

canal-adapter

增加客戶端數(shù)據(jù)落地的適配及啟動功能(支持Sync HBase、Sync ES 等)

canal 1.1.1版本之后, 內(nèi)置增加客戶端數(shù)據(jù)同步功能,

canal-deployer

這個(gè)就相當(dāng)于canal的服務(wù)端渐裸,啟動它才可以在客戶端接收數(shù)據(jù)庫變更信息巫湘。

3,常見問題排查

(1)表結(jié)構(gòu)元數(shù)據(jù)的存儲問題(配置項(xiàng)里面使用了tsdb也就是時(shí)序數(shù)據(jù)庫的字眼昏鹃,下面就稱為tsdb功能)。

  • 解決方案1:

    environment

    • canal 1.1.1
    • mysql 5.7

    Issue Description

    當(dāng)前測試過修改表結(jié)構(gòu)的情況(注意:h2.mv.db的文件所在路徑為:CANAL_HOME/canal.deployer/conf/example/):
    1.表添加字段:需要重啟canal诀诊,不重啟同步失效洞渤;刪除 h2.mv.db 后再重啟,同步生效属瓣。
    2.表修改字段:不需要重啟载迄,只能同步未修改前的表結(jié)構(gòu)數(shù)據(jù)(返回的還是未修改字段名的字段);刪除 h2.mv.db 后再重啟抡蛙,修改的字段生效护昧。
    3.刪除字段不需要重啟,也不需要刪除 h2.mv.db粗截,即時(shí)生效惋耙。

  • 解決方案2:

    默認(rèn)開啟tsdb功能,也就是會通過h2數(shù)據(jù)庫緩存解析的表結(jié)構(gòu),但是實(shí)際情況下,如果上游變更了表結(jié)構(gòu)至非,h2數(shù)據(jù)庫對應(yīng)的緩存是不會更新的习劫,這個(gè)時(shí)候一般會出現(xiàn)神奇的解析異常届腐,異常的信息一般如下:

    Caused by: com.alibaba.otter.canal.parse.exception.CanalParseException: column size is not match for table:數(shù)據(jù)庫名稱.表名稱,新表結(jié)構(gòu)的字段數(shù)量 vs 緩存表結(jié)構(gòu)的字段數(shù)量;
    

    該異常還會導(dǎo)致一個(gè)可怕的后果:解析線程被阻塞傀顾,也就是binlog事件不會再接收和解析嫉拐。這個(gè)問題筆者也查看過很多Issue漠嵌,大家都認(rèn)為是一個(gè)嚴(yán)重的BUG,目前認(rèn)為比較可行的解決方案是:禁用tsdb功能(真的夠粗暴),也就是canal.instance.tsdb.enable設(shè)置為false。如果不禁用tsdb功能,一旦出現(xiàn)了該問題报腔,必須要先停止Canal服務(wù)翻诉,接著刪除$CANAL_HOME/conf/目標(biāo)數(shù)據(jù)庫實(shí)例標(biāo)識/h2.mv.db文件绅作,然后啟動Canal服務(wù)蛾派。

    因?yàn)檫@個(gè)比較坑的問題,筆者在生產(chǎn)禁用了tsdb功能眯杏,并且添加了DDL語句的處理邏輯,直接打到釘釘預(yù)警上并且@整個(gè)群的人岂贩。

(2)Mysql的數(shù)據(jù)binlog的格式必須是Row模式

Binlog解析錯(cuò)誤:重復(fù)解析/DML解析為QUERY

  • INSERT/UPDATE/DELETE被解析為Query或DDL語句
  • Binlog重復(fù)解析,即一個(gè)操作又有QUERY消息垮耳,又有對應(yīng)的INSERT/UPDATE/DELETE消息。

1)妓湘,Row模式

Binlog日志中僅記錄哪一條記錄被修改了查蓉,修改成什么樣了,會非常清楚的記錄下每一行數(shù)據(jù)修改的細(xì)節(jié)榜贴,「Master修改了哪些行豌研,slave也直接修改對應(yīng)行的數(shù)據(jù)」

問1:INSERT/UPDATE/DELETE被解析為Query或DDL語句?

答1:出現(xiàn)這類情況主要原因?yàn)槭盏降腷inlog就為Query事件唬党,比如:

  1. binlog格式為非row模式鹃共,通過show variables like 'binlog_format'可以查看. 針對statement/mixed模式,DML語句都會是以SQL語句存在
  2. mysql5.6+之后驶拱,在binlog為row模式下霜浴,針對DML語句通過一個(gè)開關(guān)(binlog-rows-query-log-events=true, show variables里也可以看到該變量),記錄DML的原始SQL蓝纲,對應(yīng)binlog事件為RowsQueryLogEvent阴孟,同時(shí)也有對應(yīng)的row記錄. ps. canal可以通過properties設(shè)置來過濾:canal.instance.filter.query.dml = true

2),Statement模式

每一條會修改數(shù)據(jù)的sql都會記錄到master的binlog中晌纫,「slave在復(fù)制的時(shí)候sql進(jìn)程會解析成和原來master端執(zhí)行相同的sql再執(zhí)行∮浪浚」

3),Mix模式

「在 Mixed 模式下锹漱,MySQL 會根據(jù)執(zhí)行的每一條具體的 SQL 語句來區(qū)分對待記錄的日志形式,也就是在 statement 和 row 之間選擇一種慕嚷「珉梗」

(3)Filter失效

配置方式有兩種,一種是使用配置文件的方式喝检;另一種是使用代碼的方式指定嗅辣;
使用代碼的方式優(yōu)先級要大于配置的方式;那么此時(shí)就會出現(xiàn)導(dǎo)致Filter失效蛇耀。

  • 配置文件方式

    我們通常在canal-server端的conf/example/instance.properties文件中進(jìn)行設(shè)置

mysql 數(shù)據(jù)解析關(guān)注的表辩诞,Perl正則表達(dá)式.
多個(gè)正則之間以逗號(,)分隔,轉(zhuǎn)義符需要雙斜杠(\\)
常見例子:
1.  所有表:.*   or  .*\\..*
2.  canal schema下所有表:canal\\..*
3.  canal下的以canal打頭的表:canal\\.canal.*
4.  canal schema下的一張表:canal.test1
5.  多個(gè)規(guī)則組合使用:canal\\..*,mysql.test1,mysql.test2 (逗號分隔)
  • 使用代碼的方式

    也可以在客戶端與canal進(jìn)行連接時(shí)纺涤,用客戶端的connector.subscribe("xxxxxxx");來覆蓋服務(wù)端初始化時(shí)的設(shè)置译暂。

(4)消費(fèi)落后

部署完Canal后,在遇到數(shù)據(jù)庫寫入高峰期撩炊,就遇到了數(shù)據(jù)延遲問題外永。數(shù)據(jù)延遲還是小事,但是一旦延遲到堆滿了內(nèi)存緩沖區(qū)拧咳,不消費(fèi)的話伯顶,新的消息就進(jìn)不來了。

具體的設(shè)計(jì)可以參考這個(gè)網(wǎng)址

解決方案:

「一個(gè)可行的解決辦法是骆膝,將消息拉取后祭衩,寫入消息隊(duì)列(如RabbitMQ/Kafka),用消息隊(duì)列來堆積消息處理阅签,來保證大量消息堆積后不會導(dǎo)致canal卡死掐暮,并且可以支持?jǐn)?shù)據(jù)持久化≌樱」

(5),未配置MySQL-master連接信息

  • 報(bào)錯(cuò)信息
2021-12-12 20:11:04.251 [destination = example , address = null , EventParser] ERROR c.a.o.c.p.inbound.mysql.rds.RdsBinlogEventParserProxy - parse events has an error
com.alibaba.otter.canal.parse.exception.CanalParseException: illegal connection is null
  • 修改配置文件信息路克,追加master的配置

    canal.instance.master.address = 127.0.0.1:3306

  • 校驗(yàn)meta,dat 元數(shù)據(jù)信息

[yunwei@host-192-124-16-73 example]# ls
h2.mv.db  h2.trace.db  instance.properties  meta.dat
[yunwei@host-192-124-16-73 example]# pwd
/app/canal.deployer/conf/example
[yunwei@host-192-124-16-73 example]# cat meta.dat 
{"clientDatas":[{"clientIdentity":{"clientId":1001,"destination":"example","filter":""},"cursor":{"identity":{"slaveId":-1,"sourceAddress":{"address":"192.124.16.90","port":3306}},"postion":{"gtid":"","included":false,"journalName":"mysql-bin.000008","position":4918,"serverId":1,"timestamp":1639312041000}}}],"destination":"example"}
  • 在數(shù)據(jù)庫中進(jìn)行對比查看
-- 查看binlog的日志存儲
mysql> show binary logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000001 |       177 |
| mysql-bin.000002 |      1131 |
| mysql-bin.000003 |       792 |
| mysql-bin.000004 |  45420039 |
| mysql-bin.000005 |      2943 |
| mysql-bin.000006 |       862 |
| mysql-bin.000007 |      9082 |
| mysql-bin.000008 |      4949 |
+------------------+-----------+
8 rows in set (0.00 sec)

-- 查看當(dāng)前已經(jīng)消費(fèi)到了什么位置 
mysql> show binlog events in 'mysql-bin.000008';
+------------------+------+----------------+-----------+-------------+---------------------------------------+
| Log_name         | Pos  | Event_type     | Server_id | End_log_pos | Info                                  |
+------------------+------+----------------+-----------+-------------+---------------------------------------+
| mysql-bin.000008 |    4 | Format_desc    |         1 |         123 | Server ver: 5.7.27-log, Binlog ver: 4 |
| mysql-bin.000008 |  123 | Previous_gtids |         1 |         154 |                                       |
| mysql-bin.000008 |  154 | Anonymous_Gtid |         1 |         219 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'  |
| mysql-bin.000008 |  219 | Query          |         1 |         291 | BEGIN                                 |
| mysql-bin.000008 |  291 | Table_map      |         1 |         402 | table_id: 110 (zhsq.t_vehicle_record) |
| mysql-bin.000008 |  402 | Write_rows     |         1 |         808 | table_id: 110 flags: STMT_END_F       |
| mysql-bin.000008 |  808 | Xid            |         1 |         839 | COMMIT /* xid=108 */                  |
| mysql-bin.000008 |  839 | Anonymous_Gtid |         1 |         904 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'  |
| mysql-bin.000008 |  904 | Query          |         1 |         976 | BEGIN                                 |
| mysql-bin.000008 |  976 | Table_map      |         1 |        1087 | table_id: 110 (zhsq.t_vehicle_record) |
| mysql-bin.000008 | 1087 | Write_rows     |         1 |        1493 | table_id: 110 flags: STMT_END_F       |
| mysql-bin.000008 | 1493 | Xid            |         1 |        1524 | COMMIT /* xid=115 */                  |
| mysql-bin.000008 | 1524 | Anonymous_Gtid |         1 |        1589 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'  |
| mysql-bin.000008 | 1589 | Query          |         1 |        1661 | BEGIN                                 |
| mysql-bin.000008 | 1661 | Table_map      |         1 |        1772 | table_id: 110 (zhsq.t_vehicle_record) |
| mysql-bin.000008 | 1772 | Write_rows     |         1 |        2178 | table_id: 110 flags: STMT_END_F       |
| mysql-bin.000008 | 2178 | Xid            |         1 |        2209 | COMMIT /* xid=123 */                  |
| mysql-bin.000008 | 2209 | Anonymous_Gtid |         1 |        2274 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'  |
| mysql-bin.000008 | 2274 | Query          |         1 |        2346 | BEGIN                                 |
| mysql-bin.000008 | 2346 | Table_map      |         1 |        2457 | table_id: 110 (zhsq.t_vehicle_record) |
| mysql-bin.000008 | 2457 | Write_rows     |         1 |        2863 | table_id: 110 flags: STMT_END_F       |
| mysql-bin.000008 | 2863 | Xid            |         1 |        2894 | COMMIT /* xid=158 */                  |
| mysql-bin.000008 | 2894 | Anonymous_Gtid |         1 |        2959 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'  |
| mysql-bin.000008 | 2959 | Query          |         1 |        3031 | BEGIN                                 |
| mysql-bin.000008 | 3031 | Table_map      |         1 |        3142 | table_id: 110 (zhsq.t_vehicle_record) |
| mysql-bin.000008 | 3142 | Write_rows     |         1 |        3548 | table_id: 110 flags: STMT_END_F       |
| mysql-bin.000008 | 3548 | Xid            |         1 |        3579 | COMMIT /* xid=192 */                  |
| mysql-bin.000008 | 3579 | Anonymous_Gtid |         1 |        3644 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'  |
| mysql-bin.000008 | 3644 | Query          |         1 |        3716 | BEGIN                                 |
| mysql-bin.000008 | 3716 | Table_map      |         1 |        3827 | table_id: 110 (zhsq.t_vehicle_record) |
| mysql-bin.000008 | 3827 | Write_rows     |         1 |        4233 | table_id: 110 flags: STMT_END_F       |
| mysql-bin.000008 | 4233 | Xid            |         1 |        4264 | COMMIT /* xid=215 */                  |
| mysql-bin.000008 | 4264 | Anonymous_Gtid |         1 |        4329 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'  |
| mysql-bin.000008 | 4329 | Query          |         1 |        4401 | BEGIN                                 |
| mysql-bin.000008 | 4401 | Table_map      |         1 |        4512 | table_id: 110 (zhsq.t_vehicle_record) |
| mysql-bin.000008 | 4512 | Write_rows     |         1 |        4918 | table_id: 110 flags: STMT_END_F       |
| mysql-bin.000008 | 4918 | Xid            |         1 |        4949 | COMMIT /* xid=234 */                  |
+------------------+------+----------------+-----------+-------------+---------------------------------------+
37 rows in set (0.00 sec)

參考連接

1,監(jiān)聽Mysql表的變化养交,使用Canal

2精算,【開源實(shí)戰(zhàn)】Canal部署常見問題:重復(fù)解析/Filter失效/消費(fèi)落后

3,Canal 1.1.4 避坑指南

4碎连,修改表結(jié)構(gòu)之后是否重啟Canal

5灰羽,Canal筆記

6,配置項(xiàng)詳細(xì)說明

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市谦趣,隨后出現(xiàn)的幾起案子疲吸,更是在濱河造成了極大的恐慌,老刑警劉巖前鹅,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摘悴,死亡現(xiàn)場離奇詭異,居然都是意外死亡舰绘,警方通過查閱死者的電腦和手機(jī)蹂喻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捂寿,“玉大人口四,你說我怎么就攤上這事∏芈” “怎么了蔓彩?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長驳概。 經(jīng)常有香客問我赤嚼,道長,這世上最難降的妖魔是什么顺又? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任更卒,我火速辦了婚禮,結(jié)果婚禮上稚照,老公的妹妹穿的比我還像新娘蹂空。我一直安慰自己,他們只是感情好果录,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布上枕。 她就那樣靜靜地躺著,像睡著了一般弱恒。 火紅的嫁衣襯著肌膚如雪姿骏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天斤彼,我揣著相機(jī)與錄音,去河邊找鬼蘸泻。 笑死琉苇,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的悦施。 我是一名探鬼主播并扇,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼抡诞!你這毒婦竟也來了穷蛹?” 一聲冷哼從身側(cè)響起土陪,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肴熏,沒想到半個(gè)月后鬼雀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蛙吏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年源哩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸦做。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡励烦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泼诱,到底是詐尸還是另有隱情坛掠,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布治筒,位于F島的核電站屉栓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏矢炼。R本人自食惡果不足惜系瓢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望句灌。 院中可真熱鬧夷陋,春花似錦、人聲如沸胰锌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽资昧。三九已至酬土,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間格带,已是汗流浹背撤缴。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叽唱,地道東北人屈呕。 一個(gè)月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像棺亭,于是被迫代替她去往敵國和親虎眨。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容