Canal 原始碼走讀

莫那·魯道發表於2019-02-26

前言

canal 是什麼? 引用一下官方回答:

阿里巴巴mysql資料庫binlog的增量訂閱&消費元件

canal 能做什麼?

基於日誌增量訂閱&消費支援的業務:

  1. 資料庫映象
  2. 資料庫實時備份
  3. 多級索引 (賣家和買家各自分庫索引)
  4. search build
  5. 業務cache重新整理
  6. 價格變化等重要業務訊息

比如 LZ 目前就使用 canal 實現資料實時複製,搜尋引擎資料構建等功能。既然要使用,就好好的研究一下。

時間有限,一起來簡單看看。

軟體架構

關於 canla 的工作原理,我就不展開了,有興趣的可以看看官方文件,或者這個 ppt : docs.google.com/presentatio…

說白了, canal 就是偽裝成 mysql 的 slave,dump binlog,解析 binlog,然後傳遞給應用程式,總體還是蠻簡單的。

好,我們來看看 canal 的程式碼架構。

Canal 原始碼走讀

我們看到,canal server 內部由幾個模組組成, 最外部的是 Server,該 Server 接收 Canal Client 請求,並返回 Client 資料。一個 Server 就是一個 JVM。每個 Server 內部由多個 CanalInstance,每個 CanalInstance 其實就是我們設定的 destination,通常是一個資料庫。

每個 CanalInstance 內部由 5 個模組,分別是 parser 解析,sink 過濾,store 儲存,metaManager 後設資料管理,Alarm 報警。

這 5 個模組是幹嘛的呢?

簡單說一下:

當 Canal Server 啟動後,會根據配置啟動 N 個 CanalInstance, 每個 CanalInstance 都會使用 socket 連線 mysql,dump binlog,然後將資料交給 parser 解析,sink 過濾,store 儲存,當 client 連線時,會從 zk 上讀取該 client 的資訊,而 metaManager 後設資料管理就是管理 zk(當然有多種實現,比如儲存在檔案中) 資訊的,如果發生錯誤了,就呼叫 Alarm 傳送報警資訊(你可以接入自己公司的監控系統),目前是列印日誌。

Canal 啟動流程

canal 程式碼量目前有 6 萬多行,去除 2 個 ProtocolBuffer 生成類大概 1.7 萬行,也還有 4.3 萬行,程式碼還是不少的。

啟動過程也比較繞。這裡我簡單畫了一個流程圖:

Canal 原始碼走讀

解釋一下這個圖:

canal 指令碼從 CanalLauncher main 方法啟動,然後呼叫 CanalController 的 start 方法,CanalController 呼叫 InstanceConfigMonitor 的 start 方法,最後呼叫 canal 關鍵元件 CanalServerWithEmbedded 的 start 方法。

在 Canal 內部, 有 CanalServerWithEmbedded 和 CanalServerWithNetty,前者是沒有 Server 埠的,是一個無埠的代理。後者是基於 Netty 實現的伺服器,在 channelRead 方法中,會呼叫 CanalServerWithEmbedded 的相關方法。

CanalServerWithEmbedded 是單例的, 內部會有多個 CanalInstance, 他有多個實現,獨立版本中使用的是 CanalInstanceWithSpring 版本,基於 Spring 管理元件的生命週期。

每個 CanalInstance 內部有 5 個元件,也就是上面說的幾個元件,他們會分別啟動。

其中,比較關鍵的是 parser,sink,store。

CanalEventParser 啟動後,會啟動一個叫做 parseThread 執行緒,不停的迴圈。主要是:構造與 mysql 的連線,然後啟動心跳執行緒,然後開始 dump binlog。

dump 出來的 binlog 通過 disruptor 無鎖佇列釋出,內部由 3 個消費者按照順序消費 binlog,處理完之後,交給了 sink 模組。

然後是 sink,這個比較簡單,就不說了。sink 處理完之後,交給了 store 模組。

store 模式是一個類似 RingBuffer 的迴圈陣列,儲存著從 mysql dump 出來的資料,client 也是從這裡獲取資料的。該陣列維護著 3 個指標,get,put, ack。

這裡比較奇怪的是,為什麼不使用責任鏈模式夠組裝元件?

Canal 資料流向

看了啟動流程,再來看看 canal 內部執行的資料流向是什麼樣子的。我這裡簡單畫了一個圖。

Canal 原始碼走讀

獨立版本的 Canal 使用 Netty 暴露埠,使用自己構造的 SessionHandler 處理 TCP 請求,SessionHandler 將請求交給 CanalServerWithEmbedded 來處理。

我們看 CanalServerWithEmbedded 的一些方法,例如 subscribe,get,ack 等,都是和 client 對應的方法,也就是說,CanalServerWithEmbedded 是和 client 打交道的一個類。

CanalServerWithEmbedded 內部管理所有的 CanalInstance,通過 Client 的資訊,找到 Client 訂閱的 CanalInstance,然後呼叫 CanalInstance 內部的 Store 模組,也就是那個 RingBuffer 的 get 方法,獲取 RingBuffer 的資料。

從 Myslq 的角度看,MysqlConnection 從 Myslq dump 資料,交給 parser 解析,parser 解析完,交給 sink,sink 處理完,交給 store 儲存,等待 client 前來獲取。

看完了資料流向,如果對哪裡有什麼疑問,就可以看看哪個模組對應的程式碼是什麼,直接看是看就好了。

總結

花了點時間看了看 Canal 的程式碼,總體上還是非常好的,只是有些地方有點疑問,例如 parser,sink,store 為什麼不使用過濾器模式。

Client 和 CanalServerWithEmbedded 為什麼不使用 RPC 的方式互動,這樣更簡單明瞭。

程式碼裡回撥方法太多太長,影響閱讀。

但總體瑕不掩瑜,值得一讀。

相關文章