【建議收藏】超詳細(xì)的Canal入門,看這篇就夠了?。?!

概述

canal是阿里巴巴旗下的一款開源項(xiàng)目,純Java開發(fā)。基于數(shù)據(jù)庫增量日志解析,提供增量數(shù)據(jù)訂閱&消費(fèi),目前主要支持了MySQL(也支持mariaDB)。

背景

早期,阿里巴巴B2B公司因?yàn)榇嬖诤贾莺兔绹p機(jī)房部署,存在跨機(jī)房同步的業(yè)務(wù)需求。不過早期的數(shù)據(jù)庫同步業(yè)務(wù),主要是基于trigger的方式獲取增量變更,不過從2010年開始,阿里系公司開始逐步的嘗試基于數(shù)據(jù)庫的日志解析,獲取增量變更進(jìn)行同步,由此衍生出了增量訂閱&消費(fèi)的業(yè)務(wù),從此開啟了一段新紀(jì)元。ps. 目前內(nèi)部使用的同步,已經(jīng)支持mysql5.x和oracle部分版本的日志解析

基于日志增量訂閱&消費(fèi)支持的業(yè)務(wù):

  1. 數(shù)據(jù)庫鏡像
  2. 數(shù)據(jù)庫實(shí)時(shí)備份
  3. 多級(jí)索引 (賣家和買家各自分庫索引)
  4. search build
  5. 業(yè)務(wù)cache刷新
  6. 價(jià)格變化等重要業(yè)務(wù)消息

當(dāng)前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x

工作原理

Mysql的BinLog

它記錄了所有的DDL和DML(除了數(shù)據(jù)查詢語句)語句,以事件形式記錄,還包含語句所執(zhí)行的消耗的時(shí)間。主要用來備份和數(shù)據(jù)同步。

binlog 有三種模式:STATEMENT、ROW、MIXED

  1. STATEMENT 記錄的是執(zhí)行的sql語句
  2. ROW 記錄的是真實(shí)的行數(shù)據(jù)記錄
  3. MIXED 記錄的是1+2,優(yōu)先按照1的模式記錄
舉例說明

舉例來說,下面的sql

COPYupdate user set age=20

對(duì)應(yīng)STATEMENT模式只有一條記錄,對(duì)應(yīng)ROW模式則有可能有成千上萬條記錄(取決數(shù)據(jù)庫中的記錄數(shù))。

MySQL主備復(fù)制原理

img
  1. Slave 上面的IO線程連接上 Master,并請(qǐng)求從指定日志文件的指定位置(或者從最開始的日志)之后的日志內(nèi)容;
  2. Master 接收到來自 Slave 的 IO 線程的請(qǐng)求后,通過負(fù)責(zé)復(fù)制的 IO 線程根據(jù)請(qǐng)求信息讀取指定日志指定位置之后的日志信息,返回給 Slave 端的 IO 線程。返回信息中除了日志所包含的信息之外,還包括本次返回的信息在 Master 端的 Binary Log 文件的名稱以及在 Binary Log 中的位置;
  3. Slave 的 IO 線程接收到信息后,將接收到的日志內(nèi)容依次寫入到 Slave 端的Relay Log文件(mysql-relay-bin.xxxxxx)的最末端,并將讀取到的Master端的bin-log的文件名和位置記錄到master- info文件中,以便在下一次讀取的時(shí)候能夠清楚的高速M(fèi)aster“我需要從某個(gè)bin-log的哪個(gè)位置開始往后的日志內(nèi)容,請(qǐng)發(fā)給我”
  4. Slave 的 SQL 線程檢測到 Relay Log 中新增加了內(nèi)容后,會(huì)馬上解析該 Log 文件中的內(nèi)容成為在 Master 端真實(shí)執(zhí)行時(shí)候的那些可執(zhí)行的 Query 語句,并在自身執(zhí)行這些 Query。這樣,實(shí)際上就是在 Master 端和 Slave 端執(zhí)行了同樣的 Query,所以兩端的數(shù)據(jù)是完全一樣的。
    當(dāng)然這個(gè)過程本質(zhì)上還是存在一定的延遲的。

mysql的binlog文件長這個(gè)樣子。

COPYmysql-bin.003831
mysql-bin.003840  
mysql-bin.003849  
mysql-bin.003858 

啟用Binlog注意以下幾點(diǎn):

  1. Master主庫一般會(huì)有多臺(tái)Slave訂閱,且Master主庫要支持業(yè)務(wù)系統(tǒng)實(shí)時(shí)變更操作,服務(wù)器資源會(huì)有瓶頸;
  2. 需要同步的數(shù)據(jù)表一定要有主鍵;

canal能夠同步數(shù)據(jù)的原理

理解了mysql的主從同步的機(jī)制再來看canal就比較清晰了,canal主要是聽過偽裝成mysql從server來向主server拉取數(shù)據(jù)。

img
  1. canal模擬mysql slave的交互協(xié)議,偽裝自己為mysql slave,向mysql master發(fā)送dump協(xié)議
  2. mysql master收到dump請(qǐng)求,開始推送binary log給slave(也就是canal)
  3. canal解析binary log對(duì)象(原始為byte流)

Canal架構(gòu)

canal的設(shè)計(jì)理念

canal的組件化設(shè)計(jì)非常好,有點(diǎn)類似于tomcat的設(shè)計(jì)。使用組合設(shè)計(jì),依賴倒置,面向接口的設(shè)計(jì)。

img

canal的組件

  1. canal server 這個(gè)代表了我們部署的一個(gè)canal 應(yīng)用
  2. canal instance 這個(gè)代表了一個(gè)canal server中的多個(gè) mysql instance ,從這一點(diǎn)說明一個(gè)canal server可以搜集多個(gè)庫的數(shù)據(jù),在canal中叫 destionation。

每個(gè)canal instance 有多個(gè)組件構(gòu)成。在conf/spring/default-instance.xml中配置了這些組件。他其實(shí)是使用了spring的容器來進(jìn)行這些組件管理的。

instance 包含的組件

這里是一個(gè)cannalInstance工作所包含的大組件。截取自 conf/spring/default-instance.xml

COPY<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
    <property name="destination" value="${canal.instance.destination}" />
    <property name="eventParser">
        <ref local="eventParser" />
    </property>
    <property name="eventSink">
        <ref local="eventSink" />
    </property>
    <property name="eventStore">
        <ref local="eventStore" />
    </property>
    <property name="metaManager">
        <ref local="metaManager" />
    </property>
    <property name="alarmHandler">
        <ref local="alarmHandler" />
    </property>
</bean>  
EventParser設(shè)計(jì)

eventParser 最基本的組件,類似于mysql從庫的dump線程,負(fù)責(zé)從master中獲取bin_log

img

整個(gè)parser過程大致可分為幾步:

  1. Connection獲取上一次解析成功的位置 (如果第一次啟動(dòng),則獲取初始指定的位置或者是當(dāng)前數(shù)據(jù)庫的binlog位點(diǎn))
  2. Connection建立鏈接,發(fā)送BINLOG_DUMP指令
    // 0. write command number
    // 1. write 4 bytes bin-log position to start at
    // 2. write 2 bytes bin-log flags
    // 3. write 4 bytes server id of the slave
    // 4. write bin-log file name
  3. Mysql開始推送Binaly Log
  4. 接收到的Binaly Log的通過Binlog parser進(jìn)行協(xié)議解析,補(bǔ)充一些特定信息
    // 補(bǔ)充字段名字,字段類型,主鍵信息,unsigned類型處理
  5. 傳遞給EventSink模塊進(jìn)行數(shù)據(jù)存儲(chǔ),是一個(gè)阻塞操作,直到存儲(chǔ)成功
  6. 存儲(chǔ)成功后,定時(shí)記錄Binaly Log位置
EventSink設(shè)計(jì)

eventSink 數(shù)據(jù)的歸集,使用設(shè)置的filter對(duì)bin log進(jìn)行過濾,工作的過程如下。

說明:

數(shù)據(jù)過濾:支持通配符的過濾模式,表名,字段內(nèi)容等

數(shù)據(jù)路由/分發(fā):解決1:n (1個(gè)parser對(duì)應(yīng)多個(gè)store的模式)

數(shù)據(jù)歸并:解決n:1 (多個(gè)parser對(duì)應(yīng)1個(gè)store)

數(shù)據(jù)加工:在進(jìn)入store之前進(jìn)行額外的處理,比如join

數(shù)據(jù)1:n業(yè)務(wù)

為了合理的利用數(shù)據(jù)庫資源, 一般常見的業(yè)務(wù)都是按照schema進(jìn)行隔離,然后在mysql上層或者dao這一層面上,進(jìn)行一個(gè)數(shù)據(jù)源路由,屏蔽數(shù)據(jù)庫物理位置對(duì)開發(fā)的影響,阿里系主要是通過cobar/tddl來解決數(shù)據(jù)源路由問題。

所以,一般一個(gè)數(shù)據(jù)庫實(shí)例上,會(huì)部署多個(gè)schema,每個(gè)schema會(huì)有由1個(gè)或者多個(gè)業(yè)務(wù)方關(guān)注

數(shù)據(jù)n:1業(yè)務(wù)

同樣,當(dāng)一個(gè)業(yè)務(wù)的數(shù)據(jù)規(guī)模達(dá)到一定的量級(jí)后,必然會(huì)涉及到水平拆分和垂直拆分的問題,針對(duì)這些拆分的數(shù)據(jù)需要處理時(shí),就需要鏈接多個(gè)store進(jìn)行處理,消費(fèi)的位點(diǎn)就會(huì)變成多份,而且數(shù)據(jù)消費(fèi)的進(jìn)度無法得到盡可能有序的保證。

所以,在一定業(yè)務(wù)場景下,需要將拆分后的增量數(shù)據(jù)進(jìn)行歸并處理,比如按照時(shí)間戳/全局id進(jìn)行排序歸并.

EventStore設(shè)計(jì)

eventStore 用來存儲(chǔ)filter過濾后的數(shù)據(jù),canal目前的數(shù)據(jù)只在這里存儲(chǔ),工作流程如下

  • 目前僅實(shí)現(xiàn)了Memory內(nèi)存模式,后續(xù)計(jì)劃增加本地file存儲(chǔ),mixed混合模式
  • 借鑒了Disruptor的RingBuffer的實(shí)現(xiàn)思路

定義了3個(gè)cursor

  • Put : Sink模塊進(jìn)行數(shù)據(jù)存儲(chǔ)的最后一次寫入位置
  • Get : 數(shù)據(jù)訂閱獲取的最后一次提取位置
  • Ack : 數(shù)據(jù)消費(fèi)成功的最后一次消費(fèi)位置

借鑒Disruptor的RingBuffer的實(shí)現(xiàn),將RingBuffer拉直來看:

實(shí)現(xiàn)說明:

  • Put/Get/Ack cursor用于遞增,采用long型存儲(chǔ)
  • buffer的get操作,通過取余或者與操作。(與操作: cusor & (size - 1) , size需要為2的指數(shù),效率比較高)
metaManager

metaManager 用來存儲(chǔ)一些原數(shù)據(jù),比如消費(fèi)到的游標(biāo),當(dāng)前活動(dòng)的server等信息

alarmHandler

alarmHandler 報(bào)警,這個(gè)一般情況下就是錯(cuò)誤日志,理論上應(yīng)該是可以定制成郵件等形式,但是目前不支持

各個(gè)組件目前支持的類型

canal采用了spring bean container的方式來組裝一個(gè)canal instance ,目的是為了能夠更加靈活。

canal通過這些組件的選取可以達(dá)到不同使用場景的效果,比如單機(jī)的話,一般使用file來存儲(chǔ)metadata就行了,HA的話一般使用zookeeper來存儲(chǔ)metadata。

eventParser

eventParser 目前只有三種

  • MysqlEventParser 用于解析mysql的日志
  • GroupEventParser 多個(gè)eventParser的集合,理論上是對(duì)應(yīng)了分表的情況,可以通過這個(gè)合并到一起
  • RdsLocalBinlogEventParser 基于rds的binlog 的復(fù)制
eventSink

eventSink 目前只有EntryEventSink 就是基于mysql的binlog數(shù)據(jù)對(duì)象的處理操作

eventStore

eventStore 目前只有一種 MemoryEventStoreWithBuffer,內(nèi)部使用了一個(gè)ringbuffer 也就是說canal解析的數(shù)據(jù)都是存在內(nèi)存中的,并沒有到zookeeper當(dāng)中。

metaManager

metaManager 這個(gè)比較多,其實(shí)根據(jù)元數(shù)據(jù)存放的位置可以分為三大類,memory,file,zookeeper

Canal-HA機(jī)制

canal是支持HA的,其實(shí)現(xiàn)機(jī)制也是依賴zookeeper來實(shí)現(xiàn)的,用到的特性有watcher和EPHEMERAL節(jié)點(diǎn)(和session生命周期綁定),與HDFS的HA類似。

canal的ha分為兩部分,canal server和canal client分別有對(duì)應(yīng)的ha實(shí)現(xiàn)

  • canal server: 為了減少對(duì)mysql dump的請(qǐng)求,不同server上的instance(不同server上的相同instance)要求同一時(shí)間只能有一個(gè)處于running,其他的處于standby狀態(tài)(standby是instance的狀態(tài))。
  • canal client: 為了保證有序性,一份instance同一時(shí)間只能由一個(gè)canal client進(jìn)行g(shù)et/ack/rollback操作,否則客戶端接收無法保證有序。

server ha的架構(gòu)圖如下

大致步驟:

  1. canal server要啟動(dòng)某個(gè)canal instance時(shí)都先向zookeeper_進(jìn)行一次嘗試啟動(dòng)判斷_(實(shí)現(xiàn):創(chuàng)建EPHEMERAL節(jié)點(diǎn),誰創(chuàng)建成功就允許誰啟動(dòng))
  2. 創(chuàng)建zookeeper節(jié)點(diǎn)成功后,對(duì)應(yīng)的canal server就啟動(dòng)對(duì)應(yīng)的canal instance,沒有創(chuàng)建成功的canal instance就會(huì)處于standby狀態(tài)
  3. 一旦zookeeper發(fā)現(xiàn)canal server A創(chuàng)建的instance節(jié)點(diǎn)消失后,立即通知其他的canal server再次進(jìn)行步驟1的操作,重新選出一個(gè)canal server啟動(dòng)instance。
  4. canal client每次進(jìn)行connect時(shí),會(huì)首先向zookeeper詢問當(dāng)前是誰啟動(dòng)了canal instance,然后和其建立鏈接,一旦鏈接不可用,會(huì)重新嘗試connect。

Canal Client的方式和canal server方式類似,也是利用zookeeper的搶占EPHEMERAL節(jié)點(diǎn)的方式進(jìn)行控制.

canal的工作過程

dump日志

啟動(dòng)時(shí)去MySQL 進(jìn)行dump操作的binlog 位置確定

工作的過程。在啟動(dòng)一個(gè)canal instance 的時(shí)候,首先啟動(dòng)一個(gè)eventParser 線程來進(jìn)行數(shù)據(jù)的dump 當(dāng)他去master拉取binlog的時(shí)候需要binlog的位置,這個(gè)位置的確定是按照如下的順序來確定的(這個(gè)地方講述的是HA模式哈)。

  1. 在啟動(dòng)的時(shí)候判斷是否使用zookeeper,如果是zookeeper,看能否拿到 cursor (也就是binlog的信息),如果能夠拿到,把這個(gè)信息存到內(nèi)存中(MemoryLogPositionManager),然后拿這個(gè)信息去mysql中dump binlog
  2. 通過1拿不到的話(一般是zookeeper當(dāng)中每一,比如第一次搭建的時(shí)候,或者因?yàn)槟承┰騴k中的數(shù)據(jù)被刪除了),就去配置文件配置當(dāng)中的去拿,把這個(gè)信息存到內(nèi)存中(MemoryLogPositionManager),然后拿這個(gè)信息去mysql中dump binlog
  3. 通過2依然沒有拿到的話,就去mysql 中執(zhí)行一個(gè)sql show master status 這個(gè)語句會(huì)顯示當(dāng)前mysql binlog最后位置的信息,也就是剛寫入的binlog所在的位置信息。把這個(gè)信息存到內(nèi)存中(MemoryLogPositionManager),然后拿這個(gè)信息去mysql中dump binlog。

后面的eventParser的操作就會(huì)以內(nèi)存中(MemoryLogPositionManager)存儲(chǔ)的binlog位置去master進(jìn)行dump操作了。

mysql的show master status 操作

COPYmysql> show master status\G
*************************** 1. row ***************************
             File: mysql-bin.000028
         Position: 635762367
     Binlog_Do_DB:
 Binlog_Ignore_DB:
Executed_Gtid_Set: 18db0532-6a08-11e8-a13e-52540042a113:1-2784514,
318556ef-4e47-11e6-81b6-52540097a9a8:1-30002,
ac5a3780-63ad-11e8-a9ac-52540042a113:1-5,
be44d87c-4f25-11e6-a0a8-525400de9ffd:1-156349782
1 row in set (0.00 sec

歸集(sink)和存儲(chǔ)(store)

數(shù)據(jù)在dump回來之后進(jìn)行的歸集(sink)和存儲(chǔ)(store)

sink操作是可以支撐將多個(gè)eventParser的數(shù)據(jù)進(jìn)行過濾filter

filter使用的是instance.properties中配置的filter,當(dāng)然這個(gè)filter也可以由canal的client端在進(jìn)行subscribe的時(shí)候進(jìn)行設(shè)置。如果在client端進(jìn)行了設(shè)置,那么服務(wù)端配置文件instance.properties的配置都會(huì)失效

sink 之后將過濾后的數(shù)據(jù)存儲(chǔ)到eventStore當(dāng)中去。

目前eventStore的實(shí)現(xiàn)只有一個(gè)MemoryEventStoreWithBuffer,也就是基于內(nèi)存的ringbuffer,使用這個(gè)store有一個(gè)特點(diǎn),這個(gè)ringbuffer是基于內(nèi)存的,大小是有限制的(bufferSize = 16 * 1024 也就是16M),所以,當(dāng)canal的客戶端消費(fèi)比較慢的時(shí)候,ringbuffer中存滿了就會(huì)阻塞sink操作,那么正讀取mysql binlogeventParser線程也會(huì)受阻。
??這種設(shè)計(jì)其實(shí)也是有道理的。 因?yàn)閏anal的操作是pull 模型,不是producer push的模型,所以他沒必要存儲(chǔ)太多數(shù)據(jù),這樣就可以避免了數(shù)據(jù)存儲(chǔ)和持久化管理的一些問題。使數(shù)據(jù)管理的復(fù)雜度大大降低。

上面這些整個(gè)是canal的parser 線程的工作流程,主要對(duì)應(yīng)的就是將數(shù)據(jù)從mysql搞下來,做一些基本的歸集和過濾,然后存儲(chǔ)到內(nèi)存中。

binlog的消費(fèi)者

canal從mysql訂閱了binlog以后主要還是想要給消費(fèi)者使用。那么binlog是在什么時(shí)候被消費(fèi)呢。這就是另一條主線了。就像咱們做一個(gè)toC的系統(tǒng),管理系統(tǒng)是必須的,用戶使用的app或者web又是一套,eventParser 線程就像是管理系統(tǒng),往里面錄入基礎(chǔ)數(shù)據(jù)。canal的client就像是app端一樣,是這些數(shù)據(jù)的消費(fèi)方。
??binlog的主要消費(fèi)者就是canal的client端。使用的協(xié)議是基于tcp的google.protobuf,當(dāng)然tcp的模式是io多路復(fù)用,也就是nio。當(dāng)我們的client發(fā)起請(qǐng)求之后,canal的server端就會(huì)從eventStore中將數(shù)據(jù)傳輸給客戶端。根據(jù)客戶端的ack機(jī)制,將binlog的元數(shù)據(jù)信息定期同步到zookeeper當(dāng)中。

canal的目錄結(jié)構(gòu)

配置父目錄:
在下面可以看到

COPYcanal
├── bin
│   ├── canal.pid
│   ├── startup.bat
│   ├── startup.sh
│   └── stop.sh
└── conf
    ├── canal.properties
    ├── gamer ---目錄
    ├── ww_social ---目錄
    ├── wother ---目錄
    ├── nihao ---目錄
    ├── liveim ---目錄
    ├── logback.xml
    ├── spring ---目錄
    ├── ym ---目錄
    └── xrm_ppp ---目錄

這里是全部展開的目錄

COPYcanal
├── bin
│   ├── canal.pid
│   ├── startup.bat
│   ├── startup.sh
│   └── stop.sh
└── conf
    ├── canal.properties
    ├── game_center
    │   └── instance.properties
    ├── ww_social
    │   ├── h2.mv.db
    │   ├── h2.trace.db
    │   └── instance.properties
    ├── wwother
    │   ├── h2.mv.db
    │   └── instance.properties
    ├── nihao
    │   ├── h2.mv.db
    │   ├── h2.trace.db
    │   └── instance.properties
    ├── movie
    │   ├── h2.mv.db
    │   └── instance.properties
    ├── logback.xml
    ├── spring
    │   ├── default-instance.xml
    │   ├── file-instance.xml
    │   ├── group-instance.xml
    │   ├── local-instance.xml
    │   ├── memory-instance.xml
    │   └── tsdb
    │       ├── h2-tsdb.xml
    │       ├── mysql-tsdb.xml
    │       ├── sql
    │       └── sql-map
    └── ym
        └── instance.properties

Canal應(yīng)用場景

同步緩存redis/全文搜索ES

canal一個(gè)常見應(yīng)用場景是同步緩存/全文搜索,當(dāng)數(shù)據(jù)庫變更后通過binlog進(jìn)行緩存/ES的增量更新。當(dāng)緩存/ES更新出現(xiàn)問題時(shí),應(yīng)該回退binlog到過去某個(gè)位置進(jìn)行重新同步,并提供全量刷新緩存/ES的方法,如下圖所示。

img

下發(fā)任務(wù)

另一種常見應(yīng)用場景是下發(fā)任務(wù),當(dāng)數(shù)據(jù)變更時(shí)需要通知其他依賴系統(tǒng)。其原理是任務(wù)系統(tǒng)監(jiān)聽數(shù)據(jù)庫變更,然后將變更的數(shù)據(jù)寫入MQ/kafka進(jìn)行任務(wù)下發(fā),比如商品數(shù)據(jù)變更后需要通知商品詳情頁、列表頁、搜索頁等先關(guān)系統(tǒng)。這種方式可以保證數(shù)據(jù)下發(fā)的精確性,通過MQ發(fā)送消息通知變更緩存是無法做到這一點(diǎn)的,而且業(yè)務(wù)系統(tǒng)中不會(huì)散落著各種下發(fā)MQ的代碼,從而實(shí)現(xiàn)了下發(fā)歸集,如下圖所示。

img

數(shù)據(jù)異構(gòu)

在大型網(wǎng)站架構(gòu)中,DB都會(huì)采用分庫分表來解決容量和性能問題,但分庫分表之后帶來的新問題。比如不同維度的查詢或者聚合查詢,此時(shí)就會(huì)非常棘手。一般我們會(huì)通過數(shù)據(jù)異構(gòu)機(jī)制來解決此問題。

所謂的數(shù)據(jù)異構(gòu),那就是將需要join查詢的多表按照某一個(gè)維度又聚合在一個(gè)DB中。讓你去查詢。canal就是實(shí)現(xiàn)數(shù)據(jù)異構(gòu)的手段之一。

img

本文由傳智教育博學(xué)谷狂野架構(gòu)師教研團(tuán)隊(duì)發(fā)布。

如果本文對(duì)您有幫助,歡迎關(guān)注點(diǎn)贊;如果您有任何建議也可留言評(píng)論私信,您的支持是我堅(jiān)持創(chuàng)作的動(dòng)力。

轉(zhuǎn)載請(qǐng)注明出處!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容