本文將從三個方面深度剖析 EventParser 組件。
- 從官方文檔看 EventParser 的設計思想
- 從 EventParser 初始化了解內部的是可配置項
- 從 EventParser 的啓動窺探其工作實現原理
溫馨提示:本篇篇幅較長,如果耐心閱讀一定會有不錯的收穫,爲了提高閱讀體驗,本文所有源碼都是通過截圖方式,大家可以重點閱讀對應的文字說明,並在文末進行了總結。
1、官方文檔看 EventParser
首先我們先從官方文檔來看 EventParser 的整體設計,其架構設計圖如下所示:
上述圖羅列出了 EventParser 的整體工作流程圖,其關鍵步驟如下:
- 從 Log Position 管理器中獲取上一次解析的日誌位點。
- 向 Mysql Master 節點發送 BINLOG_DUMP 請求。
- Mysql Master 節點從 Slave 端傳入的日誌位點開始向從節點推送 binlog 日誌。
- Slave 接收 binlog 日誌,調用 BinlogParser 解析 binlog日誌。
- 將解析後的結構化數據傳入到 EventSink 組件。
- 定時記錄解析 binlog 的日誌,以便重啓後繼續進行增量訂閱。
- 上圖中還羅列一個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,看似簡單,但實現起來還是比較困難的,下面這些方面是後續值得我們研究探討的點:
- 環形緩存區的使用與技巧。
- 實現 MySQL 通訊協議,向 MySQL 發送相關SQL語句並解析返回結果\,具體由 MysqlConnection 對象實現。
- 日誌解析位點管理機制。
- 基於GTID、日誌位點偏移量兩種方式定位 binlog 日誌方式。
- 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專欄