Oh! Binlog還能這樣用之Canal

語言: CN / TW / HK

背景

不知道是否你還在為下面的問題而困擾:

  • 當你使用了redis或者其他中介軟體做快取的時候,經常發現快取和資料庫的資料不一致,只能通過定時任務或者快取過期的方式去做一些限制。
  • 當你使用了ES做搜尋工具,使用雙寫的那一套方法,還在為ES和資料庫不是一個事務而擔憂。
  • 當你需要遷移資料的時候,也還在使用雙寫的方法,如果是同一個資料庫的還好,如果是不同資料庫就不能保證事務,那麼資料一致性也是個問題,就會寫很多的修復Job和檢查Job。

這些問題相信在很多同學的業務當中應該都遇到過,也可能因為這些問題常常增加了很多的工作量或者導致一些資料不一致的故障。那麼我們怎麼才能比較簡單的解決這些問題呢?

我們想一想這個問題的本質是什麼呢?就是需要保證我們的資料不論在redis還是在es都要和我們的mysql一致,本質上是資料的複製。一想到資料的複製,熟悉Mysql的朋友就會說到:Mysql的主備不也是資料複製嗎?如果我們模仿Mysql的主備複製,那我們資料同步那麼就會很容易了。

Mysql主從

既然我們可以模仿Mysql的主從複製來完成我們的需求,那麼我們需要先了解一下mysql主從的原理,如下圖所示:

Oh! Binlog還能這樣用之Canal

 

  • Stpe 1: 作為master的mysql需要在每個事務更新資料完成之前,將該操作記錄序列地寫入到binlog檔案中,儲存在本地磁碟中。
  • Step 2: 在我們的salve伺服器中開啟一個I/O Thread,它會不斷的從binlog中讀取如果讀取。如果進度已經跟上了master,就進入睡眠狀態並等待master產生新的事件。所有讀取的資料都會寫到Relay log(中繼日誌)中。
  • Step 3:SQL Thread會讀取中繼日誌,並順序執行該日誌中的SQL事件,從而與主資料庫中的資料保持一致。

在主從複製中過程中,其中最為重要的就是binlog,從庫會根據binlog的資訊從而來複製出一份主庫的資料。

如果我們能在業務程式碼中拿到binlog,通過binlog的資料,複製到redis或者es中,那我們就完全不用擔心資料的一致性的問題了。

binlog

binlog(Binary Log)顧名思義就是Mysql中二進位制的日誌,記錄了Mysql對資料庫執行更改的所有操作。binlog也是server層產生的日誌和我們的儲存引擎沒有關係,不論你使用哪種儲存引擎,都可以使用我們的binlog。

binlog格式

在binlog中有三種格式,分別是:Statement,Row, Mixed三種,可以通過show variables like 'binlog_format'進行檢視當前資料庫的binlog格式,如下圖所示就是一個Row格式的binlog:

Oh! Binlog還能這樣用之Canal

 

Statement

Statement也就是語句型別,他會記錄每一條修改資料的Sql到binlog中。

  • 優點:空間佔比是最小的,不會記錄沒有修改的欄位。相比其他模式減少了很多的日誌量,提高I/O效能。
  • 缺點:異構系統不方便使用,比如redis快取複製的時候,很難模擬mysql的從操作,需要資料重查一次。並且slave也會有問題,比如使用一些UUID函式,slave重放的時候並不能保證兩邊是一致的。

我們可以檢視下Statement的日誌內容到底是什麼?我們這裡可以輸入命令:show master status;檢視我們當前master正在使用的binlog,如下圖:

Oh! Binlog還能這樣用之Canal

 

然後再使用命令show binlog events in 'mysql-bin.000003', 檢視這個日誌中的內容是什麼:

Oh! Binlog還能這樣用之Canal

 

我們可以發現我們所有的操作都會在一個完整的事務中進行,如果事務沒有提交是不會出現在我們的binlog當中的,這個大家可以下來進行實驗一下,我們在資料庫中的更新原始sql都會被完全的記錄下來。

Row

Row模式和Statement不同,他會記錄每一行被修改後的所有的資料:

  • 優點:異構系統也能比較方便的同步資料,並且不會出現UUID函式的那種問題,無論什麼情況都能被複制。
  • 缺點:資料量比較多,比如update語句,他還會記錄更新前的每一個欄位和更新後的每一個欄位。造成日誌量比較大,對I/O有一定的影響。

同樣的我們也檢視一下其中的內容:

Oh! Binlog還能這樣用之Canal

 

在show binlog events in 'mysql-bin.000004'命令中,我們發現在事務中是檢視不了我們具體的資料的,這個時候就需要我們工具幫忙了mysqlbinlog,他也在mysql的bin目錄下我們直接呼叫就好了,輸入命令/usr/local/mysql/bin/mysqlbinlog --base64-output=decode-rows -v mysql-bin.000004,我們可以看見:

Oh! Binlog還能這樣用之Canal

 

這裡展示的是一個update語句,他不僅顯示了原始值,也展示了修改後的值。

這裡要注意的是binlog_row_image用於決定row是否會記錄原始值,預設是FULL代表會記錄,也就是我們上面的這種情況,還有個引數是minimal,代表只記錄更新後的值。

Mixed

在mixed模式下,MySQL預設仍然採用statement格式進行記錄,但是一旦它判斷可能會有資料不一致的情況(UUID函式)發生,則會採用row格式來記錄。

我們目前預設使用的是Row模式,在Row模式下可以比較方便的將資料異構,其實Row模式對I/O影響在業務當中來說感知並不是特別明顯。

Canal

當我們知道binlog是什麼之後,我們就需要怎麼去使用這個binlog。binlog的同步工具常見的有:databus,canal,maxwell,阿里雲dts等等,在這裡我們就不比較他們各自的優劣點了,重點去介紹canal。

canal(github地址:https://github.com/alibaba/canal),譯意為水道/管道/溝渠,主要用途是基於 MySQL 資料庫增量日誌解析,提供增量資料訂閱和消費

早期阿里巴巴因為杭州和美國雙機房部署,存在跨機房同步的業務需求,實現方式主要是基於業務 trigger 獲取增量變更。從 2010 年開始,業務逐步嘗試資料庫日誌解析獲取增量變更進行同步,由此衍生出了大量的資料庫增量訂閱和消費業務。後面在阿里雲中逐漸演化成DTS專案。

Oh! Binlog還能這樣用之Canal

 

canal大體原理也是模仿mysql的slave,從master上不斷的去拉取binlog,然後將binlog可以投放到不同的地方,比如我們常見的訊息佇列:kafka,rocketmq等等。當然在阿里雲的付費dts上面也是可以直接同步到redis,es或者其他的一些儲存介質當中。

canal的簡單使用可以檢視quickStart:https://github.com/alibaba/canal/wiki/QuickStart ,這裡不做過多的介紹。接下來主要是更多的介紹canal的整體架構,以及實現的原理等等。

Canal整體架構

Oh! Binlog還能這樣用之Canal

 

CanalServer:一個Jvm就可以理解成一個CanalServer,如果是叢集模式的Canal的話 那麼就會有多個CanalServer。

CanalInstance: 可以理解為一個作業為一個Instance,比如有一個把A庫的binlog同步到A訊息佇列,B庫的binlog同步到B的訊息佇列,那麼這就是兩個不同的Instance,至於哪個Instance在哪個CanalServer上跑,需要看誰先在ZK搶佔到臨時節點,如果分配得足夠均勻的話,可以在叢集模式下緩解很多壓力。

CanalParser: 用於拉取mysql-binlog,並進行解析。

EventSink: 將解析的資料進行處理加工(過濾,合併等)。

CanalEventStore: 這個有點類似slave中的relay log,用於將日誌進行中繼儲存,但是在canal中目前只支援了在記憶體中儲存,目前不支援落盤儲存。

CanalParser,EventSink,CanalEventStore這三個都是屬於Canal中非常重要的元件,他們之間的關係如下:

Oh! Binlog還能這樣用之Canal

 

CanalParser產生資料讓EventSink進行加工,加工後的資料會儲存在CanalEventStore中,然後MQ從CanalEventStore中不斷的拉取最新資料,然後投遞到MQ。

CanalParser

我們來講講在CanalParser中Canal是如何偽裝成slave去拉資料的,在AbstractEventParser.java這個類中有如下步驟:

  • Step1: 構建一個數據庫連結,並且生成一個slaveId,用於標示自己slave的身份。
  • Step2: 獲取資料庫的元資訊,比如binlogFormat,binRowImage等等。
  • Step3: 通過show variables like 'server_id' 命令,獲取我們需要監聽binlog服務的serverId。
Oh! Binlog還能這樣用之Canal

 

  • Step4: 獲取這一次需要消費的位置,如果有儲存上一次的就從上一次中獲取,如果沒有的話需要通過show master status命令中獲取到的最新的Position進行消費。
Oh! Binlog還能這樣用之Canal

 

  • Step5: 進行dump操作,模擬slave傳送註冊slave請求,以及dump binlog請求,然後用一個死迴圈不斷的從binlog中拉取資料:
Oh! Binlog還能這樣用之Canal

 

  • Step6: 將獲取到的二進位制資料,根據mysql binlog協議轉換成logEntry,方便後續處理。

EventSink

EventSink會將上面獲取到的logEntry來進行加工:

  • 過濾:過濾空的事務過濾心跳自定義過濾
  • 記錄,這裡使用了Prometheus,來進行資料的統計上報。
  • 合併,現在有很多分庫分表的業務需要,他們的資料來源都是從不同的Parser中來的,但是最後都需要彙總到同一個EventStore中。在這個場景需要注意的我們可以需要注意的是會做時間歸併控制,也就是儘量讓每個分庫的資料彙總後都是遞增的方式提交,避免出現某個分庫的資料比其他的領先或者落後很多。

EventStore

我們先看看EventStore中提供的介面

Oh! Binlog還能這樣用之Canal

 

可以看見EventStore其實就是一個簡單的儲存,在canal中提供了MemoryEventStoreWithBuffer,在記憶體中進行中轉的資料,其中的原理是通過RingBuffer(無鎖,高效能佇列)實現的,有關於RingBuffer的資訊可以參考我之前的文章你應該知道的Disruptor,在3.1中有對RingBuffer進行詳細講解。

然後CanalMq通過EventStore不斷的獲取資料,來進行資料傳送。

Oh! Binlog還能這樣用之Canal

 

小結

在Canal裡面其實還有一些其他的優化,比如對於修改表結構之後的優化,gtid的一些優化等等,大家有興趣的可以下來自行閱讀,這裡就不擴充套件講開了。

總結

這篇文章主要給大家講了binlog的一些知識以及Canal的一些科普,希望大家以後在做一些異構系統資料的同步的時候,可以多多使用binlog,用更簡單的技術,做更可靠的事。

最後小編又整理出一份面試寶典 有需要的大哥們只需要新增小編的vx:mxzFAFAFA即可免費獲取!





分享到:
「其他文章」