Oh! Binlog還能這樣用之Canal篇


背景

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

當你使用了redis或者其餘中間件作緩存的時候,常常發現緩存和數據庫的數據不一致,只能經過定時任務或者緩存過時的方式去作一些限制。當你使用了ES作搜索工具,使用雙寫的那一套方法,還在爲ES和數據庫不是一個事務而擔心。當你須要遷移數據的時候,也還在使用雙寫的方法,若是是同一個數據庫的還好,若是是不一樣數據庫就不能保證事務,那麼數據一致性也是個問題,就會寫不少的修復Job和檢查Job。mysql

這些問題相信在不少同窗的業務當中應該都遇到過,也可能由於這些問題經常增長了不少的工做量或者致使一些數據不一致的故障。那麼咱們怎麼才能比較簡單的解決這些問題呢?git

咱們想想這個問題的本質是什麼呢?就是須要保證咱們的數據不論在redis仍是在es都要和咱們的mysql一致,本質上是數據的複製。一想到數據的複製,熟悉Mysql的朋友就會說到:Mysql的主備不也是數據複製嗎?若是咱們模仿Mysql的主備複製,那咱們數據同步那麼就會很容易了。github

Mysql主從

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

Stpe 1: 做爲master的mysql須要在每一個事務更新數據完成以前,將該操做記錄串行地寫入到binlog文件中,存儲在本地磁盤中。sql

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中有三種格式,分別是:StatementRowMixed三種,能夠經過show variables like 'binlog_format'進行查看當前數據庫的binlog格式,以下圖所示就是一個Row格式的binlog:

Statement

Statement也就是語句類型,他會記錄每一條修改數據的Sql到binlog中。

優勢:空間佔比是最小的,不會記錄沒有修改的字段。相比其餘模式減小了不少的日誌亮,提升I/O性能。缺點:異構系統不方便使用,好比redis緩存複製的時候,很難模擬mysql的從操做,須要數據重查一次。而且slave也會有問題,好比使用一些UUID函數,slave重放的時候並不能保證兩邊是一致的。咱們能夠查看下Statement的日誌內容究竟是什麼?咱們這裏能夠輸入命令:show master status;查看咱們當前master正在使用的binlog,以下圖:

而後再使用命令show binlog events in 'mysql-bin.000003', 查看這個日誌中的內容是什麼:咱們能夠發現咱們全部的操做都會在一個完整的事務中進行,若是事務沒有提交是不會出如今咱們的binlog當中的,這個你們能夠下來進行實驗一下,咱們在數據庫中的更新原始sql都會被徹底的記錄下來。

Row

Row模式和Statement不一樣,他會記錄每一行被修改後的全部的數據:

優勢:異構系統也能比較方便的同步數據,而且不會出現UUID函數的那種問題,不管什麼狀況都能被複制。缺點:數據量比較多,好比update語句,他還會記錄更新前的每個字段和更新後的每個字段。形成日誌量比較大,對I/O有必定的影響。

一樣的咱們也查看一下其中的內容:

show binlog events in 'mysql-bin.000004'命令中,咱們發如今事務中是查看不了咱們具體的數據的,這個時候就須要咱們工具幫忙了mysqlbinlog,他也在mysql的bin目錄下咱們直接調用就行了,輸入命令/usr/local/mysql/bin/mysqlbinlog --base64-output=decode-rows -v mysql-bin.000004,咱們能夠看見:

這裏展現的是一個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項目。

canal大致原理也是模仿mysql的slave,從master上不斷的去拉取binlog,而後將binlog能夠投放到不一樣的地方,好比咱們常見的消息隊列:kafka,rocketmq等等。固然在阿里雲的付費dts上面也是能夠直接同步到redis,es或者其餘的一些存儲介質當中。

canal的簡單使用能夠查看quickStart:https://github.com/alibaba/canal/wiki/QuickStart ,這裏不作過多的介紹。接下來主要是更多的介紹canal的總體架構,以及實現的原理等等。

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中很是重要的組件,他們之間的關係以下:

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。

Step4: 獲取這一次須要消費的位置,若是有存儲上一次的就從上一次中獲取,若是沒有的話須要經過show master status命令中獲取到的最新的Position進行消費。

Step5: 進行dump操做,模擬slave發送註冊slave請求,以及dump binlog請求,而後用一個死循環不斷的從binlog中拉取數據:

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


EventSink

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

過濾:

過濾空的事務過濾心跳自定義過濾

記錄,這裏使用了Prometheus,來進行數據的統計上報。合併,如今有不少分庫分表的業務須要,他們的數據來源都是從不一樣的Parser中來的,可是最後都須要彙總到同一個EventStore中。在這個場景須要注意的咱們能夠須要注意的是會作時間歸併控制,也就是儘可能讓每一個分庫的數據彙總後都是遞增的方式提交,避免出現某個分庫的數據比其餘的領先或者落後不少。


EventStore

咱們先看看EventStore中提供的接口:能夠看見EventStore其實就是一個簡單的存儲,在canal中提供了MemoryEventStoreWithBuffer,在內存中進行中轉的數據,其中的原理是經過RingBuffer(無鎖,高性能隊列)實現的,有關於RingBuffer的信息能夠參考我以前的文章你應該知道的Disruptor,在3.1中有對RingBuffer進行詳細講解。

而後CanalMq經過EventStore不斷的獲取數據,來進行數據發送。

小結

在Canal裏面其實還有一些其餘的優化,好比對於修改表結構以後的優化,gtid的一些優化等等,你們有興趣的能夠下來自行閱讀,這裏就不擴展講開了。

總結

這篇文章主要給你們講了binlog的一些知識以及Canal的一些科普,但願你們之後在作一些異構系統數據的同步的時候,能夠多多使用binlog,用更簡單的技術,作更可靠的事。

若是你們以爲這篇文章對你有幫助,你的關注和轉發是對我最大的支持,O(∩_∩)O:

本文分享自微信公衆號 - 咖啡拿鐵(close_3092860495)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索