背景
工作中有很多場景意蛀,比如某些場景為了加快接口響應速度,加入緩存夺饲;實現(xiàn)數(shù)據(jù)持久化的同時奸汇,還要滿足搜索引擎,需要一份數(shù)據(jù)多處存儲等往声。思考:以上場景均為一份數(shù)據(jù)多份存儲擂找,這種情況下如何實現(xiàn)數(shù)據(jù)的同步,且保證數(shù)據(jù)一致性浩销?
無可厚非贯涎,可將數(shù)據(jù)的多寫,嵌入在業(yè)務代碼里慢洋。但有些場景無權限侵入項目代碼塘雳;且這種實現(xiàn)方式存在代碼片段到處分布、事務無法保障等問題且警。由此粉捻,是否可將數(shù)據(jù)變更的訂閱、同步抽象獨立出來斑芜?canal即是這種思想下的產物組件之一肩刃。抽象業(yè)務實現(xiàn)如下圖:
目錄
canal是什么
數(shù)據(jù)庫增量日志解析、訂閱杏头、消費盈包。阿里開源數(shù)據(jù)庫訂閱組件
使用場景
● 數(shù)據(jù)庫鏡像
● 數(shù)據(jù)庫實時備份
● 索引構建和實時維護(拆分異構索引、倒排索引等)
● 業(yè)務 cache 緩存刷新
● 帶業(yè)務邏輯的增量數(shù)據(jù)處理
canal工作原理
canal 模擬 MySQL slave 的交互協(xié)議醇王,偽裝自己為 MySQL slave 呢燥,向 MySQL master 發(fā)送dump 協(xié)議
MySQL master 收到 dump 請求,開始推送 binary log 給 slave (即 canal )
canal 解析 binary log 對象(原始為 byte 流)
canal實戰(zhàn)
1.服務端
1寓娩、開啟mysql的binlog寫入功能叛氨,并且配置binlog模式為row
2呼渣、分配slave角色賬號
以上可聯(lián)系DBA操作
3、搭建高可用zookeeper集群
4寞埠、下載部署包屁置、解壓
wget http://canal4mysql.googlecode.com/files/canal.deployer-1.0.1.tar.gz
5、配置
/opt/soft/canal/canal.deployer-1.0.24/conf/canal.properties
/opt/soft/canal/canal.deployer-1.0.24/conf/huangye-evaluation/instance.properties
也可搭建canal-admin界面化部署
6仁连、啟動
sh /opt/soft/canal/canal.deployer-1.0.24/bin/startup.sh
日志:
tail -f /opt/soft/canal/canal.deployer-1.0.24/logs/canal/canal.log
2.客戶端
3.協(xié)議格式
Entry
Header
logfileName [binlog文件名]
logfileOffset [binlog position]
executeTime [binlog里記錄變更發(fā)生的時間戳]
schemaName [數(shù)據(jù)庫實例]
tableName [表名]
eventType [insert/update/delete類型]
entryType [事務頭BEGIN/事務尾END/數(shù)據(jù)ROWDATA]
storeValue [byte數(shù)據(jù),可展開蓝角,對應的類型為RowChange]
RowChange
isDdl [是否是ddl變更操作,比如create table/drop table]
sql [具體的ddl sql]
rowDatas [具體insert/update/delete的變更數(shù)據(jù)饭冬,可為多條使鹅,1個binlog
event事件可對應多條變更,比如批處理]
beforeColumns [Column類型的數(shù)組]
afterColumns [Column類型的數(shù)組]
Column
index [column序號]
sqlType [jdbc type]
name [column name]
isKey [是否為主鍵]
updated [是否發(fā)生過變更]
isNull [值是否為null]
value [具體的內容昌抠,注意為文本]
canal源碼解讀
1.基礎模塊
● common
公共工具模塊一些util患朱、另外還有canal的server、client啟動關閉相關的生命周期的基礎接口定義炊苫。
● protocol
協(xié)議模塊麦乞,主要是canal-sever和canal-client之間的信息傳輸序列化方式,采用protocobuffer序列化劝评。
● driver
封裝數(shù)據(jù)庫建立連接姐直,獲取并解析二進制流的方法。獲取原始的二進制流蒋畜,因為單純的jdbc無法滿足獲取二進制日志声畏,所以他自己封裝,又因為未來可能支持mix姻成,statement模式所以查庫的邏輯也一起封裝了插龄。
● dbsync
定義了大量的event類對應各種不同格式的二進制日志。
● filter
用于canal-server過濾掉不想關注的某些科展,某個表均牢,某個字段,的二進制日志等等才睹。還可以可以過濾是否處理ddl徘跪、dml等等。
● sink
調用filter進行過濾琅攘,將結果交給store進行緩存
● store
維護一個ringbuffer隊列將解析后的二進制日志結果緩存垮庐,提供給下游消費。為了消息消費可靠坞琴,使用ack方式哨查;為了提高性能,使用ringbuffer剧辐,消息投遞方面是可靠的寒亥,順序的邮府,數(shù)組比鏈表快,不進行垃圾回收
● meta
定義了各種元數(shù)據(jù)的修改溉奕,查詢操作挟纱,但是修改時可能涉及到原子性操作,需要在對應的store腐宋、sink等模塊兒里加鎖等方式操作。
● parser
調用dbsync解析二進制日志檀轨,并且維護二進制日志的位點胸竞,下次請求二進制日志時從對應的位點開始。位點分為本地位點儲存参萄,集群位點儲存卫枝。位點有多種實現(xiàn):canalconfspring下
base:基本的內存存儲位點
default:基于zookeeper位點存儲
file:本地存儲位點
group:為了分庫分表設計的位點存儲,可以支持配置多個數(shù)據(jù)源讹挎,但是微端存儲在內存不建議使用
● instance
調用底層模塊校赤,串起來整個流程。一個server對應多個instance筒溃,每個instance里有自己的parser马篮、store、sink怜奖。在代碼里叫instance浑测,在配置文件instance也叫destination。一個server對應多個instance歪玲,每個instance里有自己的parser迁央、store、sink滥崩。在代碼里叫instance岖圈,在配置文件instance也叫destination。一個instance封裝了一個連接數(shù)據(jù)庫的實例钙皮,一個client的注冊蜂科,zookeeper的交互。
● server
canal-sever的運行實例短条,抽象出2種實現(xiàn)崇摄,基于嵌入式和jetty獨立部署的方式,抽象出一種對接kafka的實現(xiàn)慌烧。
2.可獨立部署模塊
● deployer
模擬主從協(xié)議逐抑,從主庫拉取二進制日志,解析二進制日志屹蚊,并且將解析后的結果存儲在本地隊列厕氨,提供給下游消費进每,以后稱之為canal-server。
● example
從canal-server獲取解析后的二進制日志命斧,進行定制化處理田晚,這個工程里可以放一些定制化消費canal-server消息的代碼,以后稱之為canal-client国葬。
● canal-admin
由于canal-server負責連接mysql贤徒,解析二進制日志,canal-server不進行任何定制化的業(yè)務編碼汇四,僅需簡單配置接奈,因此canal-server的工作更偏向于運維。canal-admin就是用于運維在線部署配置canal-server的通孽。
3.對外支持
● client
是連接client-server的組件包序宦,通過這個包可以連接到客戶端上面的example就是依賴了client這個模塊的一個demo。
● docker
對接docker背苦,用于支持在docker里面部署canal-server。
● prometheus
對接監(jiān)控秕噪,prometheus相當于一個鍵值對的數(shù)據(jù)庫巢价,canal-server每間隔一段時間將prometheus格式的信息通過http端口傳遞給prometheus數(shù)據(jù)庫。通過這些數(shù)據(jù)可以監(jiān)控canal-server的情況固阁。
canal架構
1.架構
說明:
server代表一個canal運行實例壤躲,對應于一個jvm
instance對應于一個數(shù)據(jù)隊列(1個server對應1..n個instance)
2.canal業(yè)務架構
EventParser:數(shù)據(jù)源接入,模擬slave協(xié)議和master進行交互备燃,協(xié)議解析
EventSink:Parser和Store連接器碉克,主要進行數(shù)據(jù)過濾,加工并齐,分發(fā)的工作
EventStore:負責存儲
MemoryMetaManager:增量訂閱和消費信息管理器
3.EventParse
整個parser過程大致可分為以下幾步:
- Connection獲取上一次解析成功的log position(如果是第一次啟動漏麦,則獲取初始指定的位置或者是當前數(shù)據(jù)庫的binlog log position)
- Connection建立連接,向MySQL master發(fā)送BINLOG_DUMP請求
- MySQL開始推送binary Log接收到的binary Log
- 通過BinlogParser進行協(xié)議解析朋蔫,補充一些特定信息测垛。如補充字段名字捏膨、字段類型、主鍵信息、unsigned類型處理等
- 將解析后的數(shù)據(jù)傳入到EventSink組件進行數(shù)據(jù)存儲(這是一個阻塞操作链快,直到存儲成功)
- 定時記錄binary Log位置誉己,以便重啟后繼續(xù)進行增量訂閱
如果需要同步的master宕機,可以從它的其他slave節(jié)點繼續(xù)同步binlog日志域蜗,避免單點故障巨双。
4.Event Sink設計:
EventSink主要作用如下:
數(shù)據(jù)過濾:支持通配符的過濾模式,表名霉祸,字段內容等
數(shù)據(jù)路由/分發(fā):解決1:n(1個parser對應多個store的模式)
數(shù)據(jù)歸并:解決n:1(多個parser對應1個store)
數(shù)據(jù)加工:在進入store之前進行額外的處理筑累,比如join
數(shù)據(jù)1:n業(yè)務
為了合理的利用數(shù)據(jù)庫資源, 一般常見的業(yè)務都是按照schema進行隔離脉执,然后在MySQL上層或者dao這一層面上,進行一個數(shù)據(jù)源路由戒劫,屏蔽數(shù)據(jù)庫物理位置對開發(fā)的影響半夷。所以,一般一個數(shù)據(jù)庫實例上迅细,會部署多個schema巫橄,每個schema會有由1個或者多個業(yè)務方關注。
數(shù)據(jù)n:1業(yè)務
同樣茵典,當一個業(yè)務的數(shù)據(jù)規(guī)模達到一定的量級后湘换,必然會涉及到水平拆分和垂直拆分的問題,針對這些拆分的數(shù)據(jù)需要處理時统阿,就需要鏈接多個store進行處理彩倚,消費的位點就會變成多份,而且數(shù)據(jù)消費的進度無法得到盡可能有序的保證扶平。所以帆离,在一定業(yè)務場景下,需要將拆分后的增量數(shù)據(jù)進行歸并處理结澄,比如按照時間戳/全局id進行排序歸并哥谷。
5.Event Store設計:
支持多種存儲模式,比如Memory內存模式麻献。采用內存環(huán)裝的設計來保存消息们妥,借鑒了Disruptor的RingBuffer的實現(xiàn)思路。
RingBuffer設計
定義了3個cursor:
put:Sink模塊進行數(shù)據(jù)存儲的最后一次寫入位置(同步寫入數(shù)據(jù)的cursor)
get:數(shù)據(jù)訂閱獲取的最后一次提取位置(同步獲取的數(shù)據(jù)的cursor)
ack:數(shù)據(jù)消費成功的最后一次消費位置
借鑒Disruptor的RingBuffer的實現(xiàn)勉吻,將RingBuffer拉直來看:
實現(xiàn)說明:
- put/get/ack cursor用于遞增监婶,采用long型存儲。三者之間的關系為put>=get>=ack
- buffer的get操作齿桃,通過取余或者&操作压储。(&操作:cusor & (size - 1) , size需要為2的指數(shù)鲜漩,效率比較高)
6.Instance設計
instance代表了一個實際運行的數(shù)據(jù)隊列,包括了EventPaser集惋、EventSink孕似、EventStore等組件。抽象了CanalInstanceGenerator刮刑,主要是考慮配置的管理方式:
manager方式:和你自己的內部web console/manager系統(tǒng)進行對接喉祭。(目前主要是公司內部使用)
spring方式:基于spring xml + properties進行定義,構建spring配置雷绢。
7.Server設計
server代表了一個canal運行實例泛烙,為了方便組件化使用,特意抽象了Embeded(嵌入式)/Netty(網絡訪問)的兩種實現(xiàn)翘紊。
8.增量訂閱/消費設計
針對上述的補充說明:
1.可以提供數(shù)據(jù)庫變更前和變更后的字段內容蔽氨,針對binlog中沒有的name、isKey等信息進行補全
2.可以提供ddl的變更語句
9.canal HA機制
Canal的HA實現(xiàn)機制是依賴zookeeper實現(xiàn)的帆疟,主要分為Canal server和Canal client的HA鹉究。
Canal server:為了減少對MySQL dump的請求,不同server上的instance要求同一時間只能有一個處于running狀態(tài)踪宠,其他的處于standby狀態(tài)自赔。
Canal client:為了保證有序性,一份instance同一時間只能由一個Canal client進行get/ack/rollback操作柳琢,否則客戶端接收無法保證有序绍妨。
10.Canal Server HA架構
大致步驟:
- Canal server要啟動某個Canal instance時都先向Zookeeper進行一次嘗試啟動判斷 (實現(xiàn):創(chuàng)建EPHEMERAL節(jié)點,誰創(chuàng)建成功就允許誰啟動)
- 創(chuàng)建Zookeeper節(jié)點成功后柬脸,對應的Canal server就啟動對應的Canal instance他去,沒有創(chuàng)建成功的Canal instance就會處于standby狀態(tài)
- 一旦Zookeeper發(fā)現(xiàn)Canal server A創(chuàng)建的節(jié)點消失后,立即通知其他的Canal server再次進行步驟1的操作倒堕,重新選出一個Canal server啟動instance
- Canal client每次進行connect時孤页,會首先向Zookeeper詢問當前是誰啟動了Canal instance,然后和其建立鏈接涩馆,一旦鏈接不可用行施,會重新嘗試connect
Canal Client的方式和Canal server方式類似,也是利用Zookeeper的搶占EPHEMERAL節(jié)點的方式進行控制魂那。
踩過的坑
1.消費要冪等
canal-server在請求二進制日志時需要得到上次請求到哪個位置即位點蛾号,位點canal又分為了兩種方式持久化。
a涯雅、本地文件位點:默認的存儲位點方式鲜结;將位點記錄在緩存中,定時刷新到本地的meta.log文件中。
b精刷、集群存儲位點:將位點存儲于緩存中拗胜,定時刷到zookeeper的臨時節(jié)點里,多個節(jié)點間可以共享位點
均需注意消費要冪等怒允。
2.拷貝一個已有的server埂软,需要刪掉meta.log和修改tsdb配置
meta.log是本地存儲位點方式記錄的位點信息,因此拷貝一個server做部署時要刪掉舊的位點信息文件纫事,否則server啟動不起來
tsdb文件是canal為了加快解析二進制日志勘畔,將表的元數(shù)據(jù)存儲到了本地,因此拷貝一個server時這個文件還是舊的會導致server能啟動丽惶,但是解析二進制日志失敗炫七。
3.建議不要使用tsdb,默認是開啟的钾唬,而且默認緩存15天
為了提升解析二進制的速度万哪,canal將表結構信息進行了tsdb緩存,因此當表結構發(fā)生變化時緩存的表結構可能導致解析失敗抡秆。禁用掉tsdb緩存:
canal.instance.tsdb.enable = false
4.消費注意多線程操作
canal為了保證消息順序消費奕巍,一個canal-server只能有一個canal-client與之對應。對應到api就是getWithoutAck這些api琅轧,如果多線程操作這些api會引起非順序性消費
5.數(shù)據(jù)庫遷移伍绳,binlog同步中斷找不到位點信息踊挠,server卡住不消費
解決:
1) 確認事故時間點乍桂,修改配置從事故點開始繼續(xù)消費
2) 刪除meta.log文件或者刪除zookeeper記錄位置的臨時節(jié)點(不同版本記錄位置方式不同),從當前時刻的 binlog 開始消費效床,手動補充事故時間段遺漏數(shù)據(jù)
以上兩種方式均需手動重啟
6.binlog模式為row
開啟其他模式睹酌,導致filter規(guī)則失效,或者binlog解析eventType類型出錯剩檀。如Query解析為DML
總結&思考
可使用canal組件的場景憋沿,可否用業(yè)務+MQ中間件,業(yè)務雙寫模式實現(xiàn)沪猴?
業(yè)務+MQ中間件
優(yōu)點:實現(xiàn)簡單辐啄,不需要引入過多組件;維護成本低
缺點:不能保證數(shù)據(jù)實時性运嗜、一致性壶辜、業(yè)務控制事務、相同代碼片段分布各處担租、與業(yè)務耦合
canal
優(yōu)點:可保證數(shù)據(jù)實時性砸民、一致性、解藕
缺點:單獨部署,增加維護成本
具體使用哪種方案岭参,可根據(jù)具體業(yè)務場景選擇合適的方案