探究 Canal EventParser 的設計與實現奧妙

本文將從三個方面深度剖析 EventParser 組件。

  • 從官方文檔看 EventParser 的設計思想
  • 從 EventParser 初始化了解內部的是可配置項
  • 從 EventParser 的啓動窺探其工作實現原理

溫馨提示:本篇篇幅較長,如果耐心閱讀一定會有不錯的收穫,爲了提高閱讀體驗,本文所有源碼都是通過截圖方式,大家可以重點閱讀對應的文字說明,並在文末進行了總結。

1、官方文檔看 EventParser

首先我們先從官方文檔來看 EventParser 的整體設計,其架構設計圖如下所示:
在這裏插入圖片描述
上述圖羅列出了 EventParser 的整體工作流程圖,其關鍵步驟如下:

  1. 從 Log Position 管理器中獲取上一次解析的日誌位點。
  2. 向 Mysql Master 節點發送 BINLOG_DUMP 請求。
  3. Mysql Master 節點從 Slave 端傳入的日誌位點開始向從節點推送 binlog 日誌。
  4. Slave 接收 binlog 日誌,調用 BinlogParser 解析 binlog日誌。
  5. 將解析後的結構化數據傳入到 EventSink 組件。
  6. 定時記錄解析 binlog 的日誌,以便重啓後繼續進行增量訂閱。
  7. 上圖中還羅列一個HA 特性,即需要同步的 Master 如果宕機,可以從它的其他從節點繼續同步 binlog 日誌,避免單點故障。

官方文檔有助於理解 EventParser 組件的實現原理,但關於如何使用 EventParser 的篇幅較少,故接下來將從源碼的角度來反推 EventParser 的特性以及詳細的工作實現原理,以便指導我們如何更好的使用 EventParser。

2、源碼剖析 EventParser 初始化

從上篇文章我們即可得知,EventParser 組件是 Canal Instance 的四大核心組件之一,那本節的故事就從 CanalInstanceWithManager 的 initEventParser 方法開始。

CanalInstanceWithManager#initEventParser
在這裏插入圖片描述
Step1:獲取數據庫的連接信息,上面的代碼就是集合的基本操作,但從上面的代碼可以窺探如何配置數據庫相關的地址信息。
配置 canal instance 中 數據庫的地址,用戶名密碼有如下幾種方式(CanalParamter):

  • 單庫場景配置方式一:CanalParmeter 中提供了 masterAddress、masterUsername、masterPassword、standbyAddress、standbyUsername、standbyPassword 6 個屬性分別用來指定主庫與從庫的信息,配置了從庫的目的是提供 HA 機制。
  • 單庫場景配置方式二:CanalParamter 提供的 List dbAddresses 方式進行配置,該集合的第一個元素爲主庫地址、第二個元素爲從庫地址,其數據庫用戶名通過 dbUsername、dbPassword 來配置。
  • 多庫場景:CanalParmeter 提供了 List< List< DataSourcing>> groupDbAddresses 屬性用來設置 mysql 組,例如 MySQL 分庫分表。groupDbAddresses 的第一個元素爲主庫的地址列表,第二個元素爲從庫的地址列表。

溫馨提示:這裏的用戶名與密碼是在對應服務器用於進行 binlog 日誌同步的賬號信息。

關於多庫場景的配置,再詳細舉例如下:
在這裏插入圖片描述
其對應的初始化代碼如下:
在這裏插入圖片描述
CanalInstanceWithManager#initEventParser
在這裏插入圖片描述
Step2:根據配置的 MySQL 構建 EventParser 實例。這裏有如下幾個關鍵點:

  • 如果配置的 MySQL 地址是組方式則會創建 GroupEventParser,其內部會維護一個 EventParser 列表。
  • 通過調用 doInitEventParser 方法創建 EventParser 實例。

接下來我們將重點查看 doInitEventParser 的實現細節。
CanalInstanceWithManager#doInitEventParser
在這裏插入圖片描述
Step3:從這裏可以看出 Canal 目前並不支持 Oracle 數據,只支持 MySQL 與 本地 binlog 文件(直接根據 binlog 日誌文件解析)。

溫馨提示:接下來將重點探討基於 MySQL binlog 日誌,並且會忽略與阿里雲相關的 RDS 、tsdb 等數據庫輔助支持,只關係與開源 MySQL 相關的處理邏輯。

CanalInstanceWithManager#doInitEventParser
在這裏插入圖片描述
Step3:MySQL 的 binlog 事件解析器實現類爲 MysqlEventParser,這裏我們重點來闡述一下這些參數的含義:

  • destination
    Canal Instance 實例的名稱。
  • connectionCharset
    字符集,解析 binlog 時會將指定的字節數據使用該編碼級進行轉換,默認爲UTF-8。
  • connectionCharsetNumber
    字符集的數字表現形式,UTF8對應的值爲 33,該值在與 MySQL 的交互協議包中需要被用到,這裏 Canal 處理的不是特別好,最好該屬性設置爲只讀,由 connectionCharset 聯動進行設置。
  • defaultConnectionTimeoutInSeconds
    MySQL 默認連接超時時間,因爲 Canal 會僞裝爲 MySQL 服務器的 Slave 節點,需要向 MySQL Master 發送請求,故需要先創建鏈接,這裏就是創建連接的默認超市時間,默認爲 30s。
  • sendBufferSize
    用於網絡通道發送端緩存區,目前在 Canal 中網絡通道的實現類爲 BioSocketChannelPool、NettySocketChannelPool,從代碼的角度來看,目前這個參數並不會生效,即使用操作系統的默認值。
  • receiveBufferSize
    用於忘了通道接收緩存區大小,目前同 sendBufferSize 參數,並不會生效。
  • detectingEnable
    是否開啓心跳檢測,默認爲開啓。
  • detectingSQL
    心跳檢測語句,例如 select 1,show master status 等。
  • detectingIntervalInSeconds
    心跳間隔檢測,默認爲 3s。
  • slaveId
    從服務器的 id,在同一個 MySQL 複製組內不能重複。

CanalInstanceWithManager#doInitEventParser
在這裏插入圖片描述
Step4:如果設置了 CanalPrameter 的 List positions 屬性,則將其解析爲 EntryPosition 實體,我們來看一下如何表徵 binlog 日誌的位點信息。
在這裏插入圖片描述
其主要的核心參數如下:

  • long timestamp
    時間戳,用時間戳來表示位置
  • String journalName
    binlog 日誌的文件名,例如 mysql-bin.000001。
  • Long position
    使用偏移量來表示具體位點。
  • long serverId
    設置 master 的 id。
  • String gtid
    全局事務ID。

溫馨提示:實踐指導,CanalParameter 的 List< String> positions 不支持組模式,只能設置一組,即第一個元素爲主,第二個元素可以爲從節點,該屬性非必填。

CanalInstanceWithManager#doInitEventParser
在這裏插入圖片描述
Step5:繼續設置參數,具體看一下各個參數的含義:

  • fallbackIntervalInSeconds
    如果 MySQL 主節點宕機,Canal 支持切換到其從節點繼續同步 binlog 日誌,但爲了數據的完整性,可以設置一個回退時間,即會造成數據重複下發,但儘量不丟失,該值默認爲 60s。
  • profilingEnabled
    是否開啓性能採集,主要採集的是一批日誌經過 EventSink 組件處理到完成 存入EventStore 的時間消耗。
  • filterTableError
    是否忽略表過濾異常,默認爲 false,表過濾會在後續文章中詳細介紹。
  • parallel
    解析、canal 接入 prometheus 採集監控數據是否支持併發,默認爲 false。
  • isGTIDMode
    是否開啓 gtid 模式。

CanalInstanceWithManager#doInitEventParser
在這裏插入圖片描述
Step6:繼續填充解析器相關參數,其重點實現如下:

  • transactionSize
    Canal 提供了一種機制,嘗試講一個數據庫事務中所有的變更日誌一起進行處理,這個爲處理緩存事務日誌的緩存區長度,默認爲 1024。
  • logPositionManager
    初始化日誌位點管理器,Canal 提供了基於內存、zookeeper、內存與zookeepr混合管理器等日誌位點管理器,這個後續會詳細介紹。
  • AviaterRegexFilter
    提供了基於 aviater 的正則表達式,對 table 名稱進行過濾。
  • blackFilter
    canal 提供了黑名單配置,提供黑名單正則表達式對 table 名稱進行過濾。

CanalInstanceWithManager#doInitEventParser
在這裏插入圖片描述
Step7:如果解析器是 MySQL 解析器,提供了 HA 機制,即如果 MySQL Master 宕機,Canal 還能主動切換到 MYSQL Slave 節點,繼續同步 binlog 日誌。

3、EventParser 工作流程詳解

上面已經詳細介紹了EventParser 的初始化過程,有助於大家對 CanalInstance 相關配置參數的理解,本節將相信介紹 EventParser 的工作流程,其實現代碼入口爲 EventParser 的 start 方法。本文重點將探究 MySQL binlog 日誌的解析,故其實現類爲:MysqlEventParser。

MysqlEventParser 的 start 方法代碼如下:
在這裏插入圖片描述
主要調用的是其父類的 start 方法。接下來對其進行詳細解讀。

AbstractEventParser#start
在這裏插入圖片描述
Step1:創建環形緩存區,其主要的作用是 Canal 在解析 binlog 日誌後,會盡量嘗試將一個數據庫事務所產生的全部變更日誌(一個事務所有變更數據)當成一個整體提交給 EventSink 組件,從而 Canal 的消費方能一次將一個事務的數據全部同步,數據的完整性得到了保證。

溫馨提示:關於環形緩存區的具體實現細節將在下文詳細介紹,這裏先簡單說一下 Canal 目前無法百分之百保證一個事務的數據就一定是一次消費,如果一個事務產生的變更日誌超過了環形緩存區的容量,則會被強制提交消費,一個事務的數據會被分開消費,默認環形緩存區的長度爲 1024.

AbstractEventParser#start
在這裏插入圖片描述
Step2:構建一個 binlog 解析器,該方法在 AbstractEventParser 中爲一個抽象方法,具體的實現在其子類中,其代碼截圖如下:AbstractMysqlEventParser,在 MySQL binlog 解析的實現類爲 LogEventConvert,所處的模塊爲 parse,該部分是整個 Canal EventParser 的核心,將在後續文章中單獨詳細介紹。
在這裏插入圖片描述
AbstractEventParser#start
在這裏插入圖片描述
Step3:啓動一個獨立的線程來負責 binlog 的解析,其線程包含了 Canal Instance 的 destination、address 等信息,方便利用 jstack 去診斷 binlog 解析相關問題。接下來就是解讀該線程的 run 方法,從而探究 binlog 的解析流程。

AbstractEventParser#start
在這裏插入圖片描述
Step31:首先創建一條到需要解析 binlog 日誌的服務器,例如需要同步 192.168.1.166:3306 這個數據庫實例的 binlog 日誌,那 Canal 首先會使用擁有該庫複製權限的賬號去創建一條TCP連接,本文並不會詳細去介紹這裏的實現細節,這裏代表一個領域,即需要知曉 MySQL 通訊協議,通過TCP與MySQL建立連接,並按照 MySQL 通訊協議發送命令,例如 select、dump 等請求,這個後續在學完 Canal 等核心組件後,可能會深入學習該部分的內容,這裏我重點點出其實現的幾個關鍵要點:

  • 首先創建一條TCP連接,連接到 MySQL 服務器,Canal 提供了 BIO 與 Netty 兩種實現方式。
  • TCP 三次握手後成功建立TCP連接後,需要與 MySQL 進行握手,完成協議約定,客戶端登錄校驗等,例如握手實現代碼見:MysqlConnector negotiate。
  • 一言以蔽之,MySqlConnection 的職責就是實現一個 MySQL 客戶端。其效果等同於實現我們常用的 SQL 連接客戶端,關於這方面的編程其實不難,如果大家有志成爲一名數據庫中間件方面的技術人員,只需按照 MySQL 官方文檔中有關通訊協議即可。

AbstractEventParser#start
在這裏插入圖片描述
Step32:發送心跳包,這裏的關鍵實現點如下:

  • 利用 Timer 實現定時調度,心跳包發送間隔通過 detectingIntervalInSeconds 指定。
  • 心跳包主要是構建一個 CanalEntry.Entry,其類型爲EntryType.HEARTBEAT。
    心跳包並不是發送給遠端 MySQL 服務器,而是將 Entry 下發到 EventSink 組件。
  • 該心跳包的用意合作,在這裏先留一個伏筆,後續文章會依依揭曉。

AbstractEventParser#start
在這裏插入圖片描述
Step33:執行發送 dump 命令正式從 MySQL 服務器接收 binlog 日誌之前的準備工作,具體準備工作如下:

  • 首先再創建一條專屬數據庫連接,主要用於查找 MySQL 的一些配置信息,統稱元數據。
  • 向 MySQL 服務器發送 show variables like 'binlog_format‘ 語句查詢服務端配置的 binlog 格式,MySQL 支持 STATEMENT、ROW、MIXED 三種模式。
  • 向 MySQL 服務起發送 show variables like ‘binlog_row_image’ 語句查詢服務器斷配置的 binlog_row_image。

擴展閱讀:binlog_format 我相信大家都不陌生,對 binlog_row_image 見過的估計比較少,那 binlog_row_image 有何作用呢?

binlog_row_image 主要是在 binlog_format 爲 ROW 模式下,控制記錄 binlog 事件的方式,binlog 的作用是記錄數據的變化,例如 update 請求,需要記錄一行記錄變化之前的數據以及變化後的數據,在 binlog event 分別用 before 、after 記錄變化前後的數據,但有一個問題,是隻發生變化的字段的前後值呢,還是記錄一行中所有字段修改前後的值呢?故引入了 binlog_row_image,該值支持如下選項:

  • full:在 before 與 after 中記錄所有字段的值,針對每一個字段,使用 update 來表示該字段是否發生變化,該選項爲默認值。
  • minimal:在 before 與 after 中只記錄發生變化的字段,並且包含能夠唯一識一行數據的值,例如主鍵。
  • noblob:在 before 與 after 中記錄所有的列值,但 BLOB 與 TEXT 類型的字段列除外(如未更改)。

AbstractEventParser#start
在這裏插入圖片描述
Step34:向 MySQL 服務端發送 show variables like ‘server_id’ 語句,查詢服務端配置的 serverId。

AbstractEventParser#start
在這裏插入圖片描述
Step35:通過日誌位點管理器獲取需要同步的位點,後續會詳細展開。

AbstractEventParser#start
在這裏插入圖片描述
Step36:通過向 MySQL 發送 dump 請求,從服務器接收 binlog 日誌,並進行處理,爲了提高性能,Canal 支持該過程進行並行化處理,通過 parallel 屬性設置是否支持併發,從而引入 disruptor 高性能併發框架,詳情後在後續文章中詳細解讀。

AbstractEventParser#start
在這裏插入圖片描述
Step37:通過接收到 MySQL 服務端返回的日誌並解析爲 Canal.Entry 對象,並傳輸到 EventSink 組件。

上述過程反覆執行,持續完成 binlog 日誌的解析,實現數據的同步。

4、總結
本文首先結合官方文檔瞭解了 EventParser,但 Canal 的官方手冊並不特別詳細,故需要我們通過源碼去反推 canal instance 中關於 EventParser 有哪些參數,並且這些參數有何意義,是如何工作的。

衆所周知,EventParser 的主要職責就是與 MySQL 服務器“打交道”,將自己僞裝成 MySQL 服務器的一個從節點,從服務器端接收 binlog 日誌,並將二進制流解碼成 Canal.Entry,看似簡單,但實現起來還是比較困難的,下面這些方面是後續值得我們研究探討的點:

  1. 環形緩存區的使用與技巧。
  2. 實現 MySQL 通訊協議,向 MySQL 發送相關SQL語句並解析返回結果\,具體由 MysqlConnection 對象實現。
  3. 日誌解析位點管理機制。
  4. 基於GTID、日誌位點偏移量兩種方式定位 binlog 日誌方式。
  5. dump 命令的發送、高性能設計( disruptor 框架的引入)

本文由於篇幅的問題,對上述知識點只是點到爲止,後續會按需要進行深入探討。


好了,本文就介紹到這裏了,您的點贊與轉發是對我持續輸出高質量文章最大的鼓勵。

歡迎加筆者微信號(dingwpmz),加羣探討,筆者優質專欄目錄:
1、源碼分析RocketMQ專欄(40篇+)
2、源碼分析Sentinel專欄(12篇+)
3、源碼分析Dubbo專欄(28篇+)
4、源碼分析Mybatis專欄
5、源碼分析Netty專欄(18篇+)
6、源碼分析JUC專欄
7、源碼分析Elasticjob專欄
8、Elasticsearch專欄(20篇+)
9、源碼分析MyCat專欄

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