前言
canal 是什么? 引用一下官方回答:
阿里巴巴mysql數(shù)據(jù)庫binlog的增量訂閱&消費(fèi)組件
canal 能做什么赠橙?
基于日志增量訂閱&消費(fèi)支持的業(yè)務(wù):
- 數(shù)據(jù)庫鏡像
- 數(shù)據(jù)庫實(shí)時(shí)備份
- 多級(jí)索引 (賣家和買家各自分庫索引)
- search build
- 業(yè)務(wù)cache刷新
- 價(jià)格變化等重要業(yè)務(wù)消息
比如 LZ 目前就使用 canal 實(shí)現(xiàn)數(shù)據(jù)實(shí)時(shí)復(fù)制麸拄,搜索引擎數(shù)據(jù)構(gòu)建等功能实柠。既然要使用橱脸,就好好的研究一下抒和。
時(shí)間有限膳沽,一起來簡(jiǎn)單看看汗菜。
軟件架構(gòu)
關(guān)于 canal 的工作原理,我就不展開了挑社,有興趣的可以看看官方文檔陨界,或者這個(gè) ppt : https://docs.google.com/presentation/d/1MkszUPYRDkfVPz9IqOT1LLT5d9tuwde_WC8GZvjaDRg/edit#slide=id.p16
說白了, canal 就是偽裝成 mysql 的 slave痛阻,dump binlog菌瘪,解析 binlog,然后傳遞給應(yīng)用程序,總體還是蠻簡(jiǎn)單的俏扩。
好糜工,我們來看看 canal 的代碼架構(gòu)。
我們看到录淡,canal server 內(nèi)部由幾個(gè)模塊組成捌木, 最外部的是 Server,該 Server 接收 Canal Client 請(qǐng)求嫉戚,并返回 Client 數(shù)據(jù)刨裆。一個(gè) Server 就是一個(gè) JVM。每個(gè) Server 內(nèi)部由多個(gè) CanalInstance彬檀,每個(gè) CanalInstance 其實(shí)就是我們?cè)O(shè)置的 destination帆啃,通常是一個(gè)數(shù)據(jù)庫。
每個(gè) CanalInstance 內(nèi)部由 5 個(gè)模塊凤覆,分別是 parser 解析链瓦,sink 過濾,store 存儲(chǔ)盯桦,metaManager 元數(shù)據(jù)管理慈俯,Alarm 報(bào)警。
這 5 個(gè)模塊是干嘛的呢拥峦?
簡(jiǎn)單說一下:
當(dāng) Canal Server 啟動(dòng)后贴膘,會(huì)根據(jù)配置啟動(dòng) N 個(gè) CanalInstance, 每個(gè) CanalInstance 都會(huì)使用 socket 連接 mysql略号,dump binlog刑峡,然后將數(shù)據(jù)交給 parser 解析,sink 過濾玄柠,store 存儲(chǔ)突梦,當(dāng) client 連接時(shí),會(huì)從 zk 上讀取該 client 的信息羽利,而 metaManager 元數(shù)據(jù)管理就是管理 zk(當(dāng)然有多種實(shí)現(xiàn)宫患,比如存儲(chǔ)在文件中) 信息的,如果發(fā)生錯(cuò)誤了这弧,就調(diào)用 Alarm 發(fā)送報(bào)警信息(你可以接入自己公司的監(jiān)控系統(tǒng))娃闲,目前是打印日志。
Canal 啟動(dòng)流程
canal 代碼量目前有 6 萬多行匾浪,去除 2 個(gè) ProtocolBuffer 生成類大概 1.7 萬行皇帮,也還有 4.3 萬行,代碼還是不少的蛋辈。
啟動(dòng)過程也比較繞属拾。這里我簡(jiǎn)單畫了一個(gè)流程圖:
解釋一下這個(gè)圖:
canal 腳本從 CanalLauncher main 方法啟動(dòng),然后調(diào)用 CanalController 的 start 方法,CanalController 調(diào)用 InstanceConfigMonitor 的 start 方法捌年,最后調(diào)用 canal 關(guān)鍵組件 CanalServerWithEmbedded 的 start 方法瓢娜。
在 Canal 內(nèi)部, 有 CanalServerWithEmbedded 和 CanalServerWithNetty礼预,前者是沒有 Server 端口的眠砾,是一個(gè)無端口的代理。后者是基于 Netty 實(shí)現(xiàn)的服務(wù)器托酸,在 channelRead 方法中褒颈,會(huì)調(diào)用 CanalServerWithEmbedded 的相關(guān)方法。
CanalServerWithEmbedded 是單例的励堡, 內(nèi)部會(huì)有多個(gè) CanalInstance谷丸, 他有多個(gè)實(shí)現(xiàn),獨(dú)立版本中使用的是 CanalInstanceWithSpring 版本应结,基于 Spring 管理組件的生命周期刨疼。
每個(gè) CanalInstance 內(nèi)部有 5 個(gè)組件,也就是上面說的幾個(gè)組件鹅龄,他們會(huì)分別啟動(dòng)揩慕。
其中,比較關(guān)鍵的是 parser扮休,sink迎卤,store。
CanalEventParser 啟動(dòng)后玷坠,會(huì)啟動(dòng)一個(gè)叫做 parseThread 線程蜗搔,不停的循環(huán)。主要是:構(gòu)造與 mysql 的連接八堡,然后啟動(dòng)心跳線程樟凄,然后開始 dump binlog。
dump 出來的 binlog 通過 disruptor 無鎖隊(duì)列發(fā)布兄渺,內(nèi)部由 3 個(gè)消費(fèi)者按照順序消費(fèi) binlog不同,處理完之后,交給了 sink 模塊溶耘。
然后是 sink,這個(gè)比較簡(jiǎn)單服鹅,就不說了凳兵。sink 處理完之后,交給了 store 模塊企软。
store 模式是一個(gè)類似 RingBuffer 的循環(huán)數(shù)組庐扫,存儲(chǔ)著從 mysql dump 出來的數(shù)據(jù),client 也是從這里獲取數(shù)據(jù)的。該數(shù)組維護(hù)著 3 個(gè)指針形庭,get铅辞,put, ack萨醒。
這里比較奇怪的是斟珊,為什么不使用責(zé)任鏈模式夠組裝組件?
Canal 數(shù)據(jù)流向
看了啟動(dòng)流程富纸,再來看看 canal 內(nèi)部運(yùn)行的數(shù)據(jù)流向是什么樣子的囤踩。我這里簡(jiǎn)單畫了一個(gè)圖。
獨(dú)立版本的 Canal 使用 Netty 暴露端口晓褪,使用自己構(gòu)造的 SessionHandler 處理 TCP 請(qǐng)求堵漱,SessionHandler 將請(qǐng)求交給 CanalServerWithEmbedded 來處理。
我們看 CanalServerWithEmbedded 的一些方法涣仿,例如 subscribe勤庐,get,ack 等好港,都是和 client 對(duì)應(yīng)的方法愉镰,也就是說,CanalServerWithEmbedded 是和 client 打交道的一個(gè)類媚狰。
CanalServerWithEmbedded 內(nèi)部管理所有的 CanalInstance岛杀,通過 Client 的信息,找到 Client 訂閱的 CanalInstance崭孤,然后調(diào)用 CanalInstance 內(nèi)部的 Store 模塊类嗤,也就是那個(gè) RingBuffer 的 get 方法,獲取 RingBuffer 的數(shù)據(jù)辨宠。
從 Myslq 的角度看遗锣,MysqlConnection 從 Myslq dump 數(shù)據(jù),交給 parser 解析嗤形,parser 解析完精偿,交給 sink,sink 處理完赋兵,交給 store 保存笔咽,等待 client 前來獲取。
看完了數(shù)據(jù)流向霹期,如果對(duì)哪里有什么疑問叶组,就可以看看哪個(gè)模塊對(duì)應(yīng)的代碼是什么,直接看是看就好了历造。
總結(jié)
花了點(diǎn)時(shí)間看了看 Canal 的代碼甩十,總體上還是非常好的船庇,只是有些地方有點(diǎn)疑問,例如 parser侣监,sink鸭轮,store 為什么不使用過濾器模式。
Client 和 CanalServerWithEmbedded 為什么不使用 RPC 的方式交互橄霉,這樣更簡(jiǎn)單明了窃爷。
代碼里回調(diào)方法太多太長(zhǎng),影響閱讀酪劫。
但總體瑕不掩瑜吞鸭,值得一讀。