天天看點

【源碼】canal和otter的高可靠性分析

一般來說,我們對于資料庫最主要的要求就是:資料不丢。不管是主從複制,還是使用類似otter+canal這樣的資料庫同步方案,我們最基本的需求是,在資料不丢失的前提下,盡可能的保證系統的高可用,也就是在某個節點挂掉,或者資料庫發生主從切換等情況下,我們的資料同步系統依然能夠發揮它的作用--資料同步。本文讨論的場景是資料庫發生主從切換,本文将從源碼的角度,來看看otter和canal是如何保證高可用和高可靠的。

通過閱讀文檔和源碼,我們可以知道,對于一個canal server,基礎的架構包括以下幾個部分:MetaManager、EventParser、EventSink和EventStore。其中EventParser的作用就是發送dump指令,從mysql資料庫擷取binlog檔案。發送dump指令,可以指定時間戳或者position,從指定的時間或者位置開始dump。我們來看看過程:

首先是CanalServer啟動。otter預設使用的是内置版的canal server,是以我們主要看CanalServerWithEmbedded這個類。來看下他的啟動過程:

我們看下執行個體啟動那一行,跟到AbstractCanalInstance類中

我們主要看下eventParser.start()方法裡面的内容。我們主要關注的是EventParser使如何在主從切換的條件下,進行dump節點的确定的。我們跟蹤到AbstractEventParser類中的start()方法,重點看下

這塊有兩個實作,但是canal目前使用的是MysqlEventParser,也就是基于Mysql的Binlog檔案來進行資料同步。我們看下代碼:

對于第一行findStartPositionInternal(connection),我們重點關注的情況是資料庫連接配接位址發生變化,也就是進行了主從切換的情況。

我們分析下case2這個條件,其實就是表示的就是配置了主從切換,而且發生了serverId變化的情況,在這種情況下,首先需要擷取到事件發生的時間戳,然後将這個事件發生的時間減去60s,也就是向前推一分鐘之後,在新的binlog檔案中根據新的時間戳來找到當時對應的事件。

這塊根據時間戳來尋找事件的過程比較簡單,首先根據binglog-index檔案找到所有的binlog檔案名,然後周遊binlog檔案的頭,找到binlog檔案的寫入時間,與新的時間戳進行對比,定位到binlog檔案。定位到檔案後,直接根據時間戳來進行周遊,找到新的時間戳之前發生的那個事務起始位置。

這塊的邏輯如下:

發送dump指令,起始位置為4L,也就是跳過了binlog的第一個标志事件。

canal收到binlog,開始進行對binlog檔案進行解析。

主要我們看的是事務開始和事務送出的事件,判斷事務開始或結束的時間,是否小于我們要找的時間戳,如果大于等于,直接周遊下一個事件。

傳入了一個endPosition,防止無限掃描。

雖說是從頭開始掃描的,但是要想跳出周遊,需要滿足一定的條件。在跳出周遊之前,最後一次設定的logPosition才是我們要招的logPosition。

如果是一個事務送出的事件,我們要找的position就是這個事件的position+event.length。如果是事務開始,position就是目前事件的position。其他的事件都忽略。

至此,我們已經找到了我們想要的binlog檔案名和對應的事務開始position,我們繼續下面的步驟即可。

這塊内容的主要思想如下:

維護一個類似于Disruptor的RingBuffer,同時維護三個序列,put/get/ack。

EventSink之後的資料,調用put接口,将資料放入環形隊列中。

Canal client擷取資料,調用get方法。

異步調用ack方法,清除ack之前的資料。

值得注意的是,這塊get和ack采用了流式API的模式,get和ack異步進行,可以先get,然後異步調用ack。

ack是有序的,不允許跳躍式的送出。

至此,我們基本上知道了canal是如何在發生資料庫主從切換時保證高可用和高可靠的,我們可能還有疑惑:為什麼要回退60s,來解析binlog,這樣不會導緻資料重複嗎?還有一些自增的update語句(不具備幂等性),不會産生資料錯誤嗎?要想回答這些問題,就需要我們了解Binlog的Row模式了。

Mysql Binlog的Row模式記錄的,是資料庫中每一行的資料變化,而不僅僅是sql語句。比如我們對資料庫中的多行,使用一條sql語句進行了修改。在這種情況下,如果Binlog模式為Statement,隻會記錄一條sql語句。而Row模式下,會對每一行的資料變化進行記錄,以及變化前後每個字段的值。這也就是為什麼Row模式的binlog檔案如此之大的原因。

對于一些不具備幂等性的sql語句,采用Row語句進行Binlog解析時,也是可以通過重複執行,來保證我們資料的最終一緻性的。這也就解釋了,為什麼要回退60s來進行Binlog位點定位、解析的問題。考慮到Mysql主從的資料複制的延遲性(60s,一般來說的延遲沒有這麼久),我們可以在主節點挂掉的情況下,回退60s到從節點上繼續進行binlog的解析。

當然,也需要考慮一些極端的情況,也就是主從複制确實超過了60s的延遲,在這種情況下,就需要otter登場了。基本思路是:反查資料庫同步 (以資料庫最新版本同步,解決交替性,比如設定一緻性反查資料庫延遲閥值為60秒,即當同步過程中發現資料延遲超過了60秒,就會基于PK反查一次資料庫,拿到目前最新值進行同步,減少交替性的問題)。