你真的了解 CDC 吗?
Change Data Capture[1] 簡稱 CDC, 用于異構數據同步,將 database 的數據同步到第三方,這里的 DB 可以是 MySQL, PG, Mongo 等等一切數據源,英文技術圈稱之為 Single Source OF True (SSOT), 目標稱為 Derived Data Systems。常見的使用場景有:
緩存 Cache, 異步的生成,刪除,更新緩存 kv
構建索引,比如將數據同步到 ES 用于全文檢索
大數據分析 Warehouse, 實時同步數據,離線分析等等
上面的架構圖來自 DDIA, 與當年在趕集網時一樣:用戶發貼,數據寫到數據庫 MySQL 中,CDC 服務將自己偽裝成 slave 從庫實時接收 binlog, 解析后更新相應的 memcache 集群
移動互聯網十多年發展,CDC 技術理念己經相當成熟,每種語言都有成熟的輪子。當年趕集網使用非常古老的 tungsten replicator[2], 非常難用,后來遷移到 alibaba 開源的 alibaba canal[3], 大部份 java 公司都基于 canal 做定制,基于 MySQL 數據庫增量日志解析,提供增量數據訂閱和消費的產品中,canal 使用是最廣最成熟
對于 go 技術棧的公司,可以使用 go-mysql[4] 開源項目,創建者是 PingCAP 首架唐劉大佬。非常成熟,使用也最廣,包括他們自己的產品 ticdc[5], 可以同步數據到 MySQL, Puslar, TiDB, Kafka 以及做增量備份
另外這類產品非常考驗運維,穩定的產品一般沒人在意,出故障就會影響數據一致性比如刪除大批數據, 或是 alter table, 造成 CDC 延遲,那么 cache 更新慢,數據刪了但是緩存還有,這種場景需要限速。單點都有高可用問題,需要制定方案,定期演練
原理
以趕集網更新貼子緩存的例子,如果沒有 CDC 服務,程序代碼會耦合非常多的雙寫邏輯,業務自己負責同步數據到緩存,所有涉及 DB 變更的地方都需要寫上這段代碼,當然也可以放到 DAO 里面
相比于應用層同步,CDC 旁路模式做同步,主要有三種旁路原理:
觸發器 trigger 將更新通過觸發器寫到專用表 table, 缺點非常多,性能不好,不能異構寫
增加 time 字段,數據惰性刪除標記 NULL,CDC 服務定期掃該表。缺點實時無法保證,而且對業務有侵入,要求業務適配
解析 log, 當前主流實現,Mongodb 讀取 oplog, MySQL 讀取 binlog
解析 binlog, 增量解析日志可以保證實時性,同時也將邏輯與業務解耦,性能得以保證,業務不用寫過多雙寫代碼。新生的 DB,己經原生支持 binlog 或是 transactions 事務事件訂閱,類比 go, rust 這些后發語言一樣,新特性的支持都是后發優勢
推薦一個 red hat 出品的 Debezium[6] 開源軟件,數據源支持 MySQL,MongoDB,PostgreSQL,SQL Server,Oracle,Db2,Cassandra, 通過 kafka connect 獲得增量數據寫到 kafka, 供后續業務訂閱與消費
事務
基于 binlog 解析實現的 CDC 要求 MySQL 開啟 row 模式,并且建議 full image 記錄全量字段,這點保證了數據更新的 order 順序,但是沒有保證事務的原子性。原因在于 row binlog 是一行行的,同一個事務可能更新多個表或是多行數據,那么要求同一個事務的數據要打包發送到下游,才能保證原子性
同樣有成熟的解決方案,ringbuffer 等待接收事務所有數據,然后一起提交到消費端。這個也不是萬能,只能做到 try best to guarantee, 比如超大事務 batch 更新
FSC
上面提到的 binlog 是持續增量更新,那么如何同步全部基量數據呢?這個術語叫做 Full State Capturue 簡稱 FSC, 叫啥無所謂 ... 方法很多
可以通過 Xtrabackup 備份全量 mysql 數據后,記錄下最新的 binlog pos X, 然后回放全量 + 從 X 開始消費
對于一致性要求不嚴格的場景直接掃 db 就可以,比如半夜定時任務,同到 mysql 數據到 es
相比于 Xtrabackup 比較重的實現,還需要回放數據,比較麻煩。DBLog 論文[7] 提到一種解決方案,比較有意思,大致思路是:不做全量備份,開通時間窗口,在窗口期內通過主鍵 pk 按 chunk (比如 10 條記錄一批)掃表,混合掃表和 binlog 解析出來的數據,去掉沖突的部份,不斷重復這個過程,直接全部掃完,這就是 Full State Capture, 讓我們看下實現細節
如何定義窗口? 創建一張單獨的 meta 表,表內只有一條記錄,更新這條記錄,此時 binlog 里就會生成此記錄,標記為 Low Watermark, 然后 select 選取塊數據,最后再更新該表記錄,生成一個新的 binlog,標記為 High Watermark. 因為 select 是不會記到 binlog 的,所以只能通過這種方式。那么如何混合 select 塊數據和 binlog 增量數據呢?
示例中我們有 k1,k2,k3,k4,k5,k6 全量數據,lw 表示低水位,hw 表示高水位的 binlog 位置。change log 也就是 binlog, 在 binlog k3 位置時我們暫停處理的,然后更新 meta 表,生成 lw 低水位,select 選擇了所有的 6 條數據,再生成 hw 關閉窗口。最后開啟 binlog 數據處理
根據 mysql 事務特性,我們可以確定,select 的 6 條數據一定比 lw 前的新,但是窗口期內是不確定的,也就是 lw 到 hw 如果和 select 有重疊的數據,那么是要從 select 的結果中刪除的
不斷重復上述步驟,最終 k2, k4, k5, k6 select 的數據混合進 binlog process 的 output buffer 中。原理不難,有個思考題給大家,如果 select 語句與 lw 或是 hw 更新放到同一個事務中,處理會不會更容易?
其它
一致性方面就不要想了,Eventual Consistent 的系統會有時間窗口延遲,尤其是 binlog 解析出來的日志如果要寫 kafka, 應用再去訂閱消費,更無法保證一致。大概率情況,每天都要重新校驗一次全量數據
CDC 服務也要做到高可用的,可以定期將消費的 binlog pos 同步到 zk/etcd 等外部存儲,多個 CDC 服務競爭任務。如果是 MySQL 切表的話,需要 CDC 服務也參與,還要區分是否開啟 GTID, 各種集群實現。一般為了避免這種情況,CDC 服務都是連接 slave 從庫
Schema?變更與映射要看具體 CDC?的實現,動態識別變更,自定義映射,比如 mysql?表與目標結構不一致,create_at 映射成 CreateAt?等等
小結
任何技術都是在特定背景下產生的,pc 時代單體服務大行其道,流量也不大,db 就能服務所有流量,所有數據 join 就可以。移動互聯網興趣后,單體服務,單 db 性能就很差,所以需要解耦,將異構數據同步交給專用的 CDC 服務。當微服務興起后,數據無法存儲在單個表中,分散在不同服務,基于 db 底層技術的 CDC 就顯得捉襟見肘,此時 CQRS Command and Query Responsibility Segregation[8] 命令查詢分離走進視野,關于 CQRS, Event Sourcing 我們下一篇再介紹
寫文章不容易,如果對大家有所幫助和啟發,請大家幫忙點擊在看,點贊,分享 三連
關于 CDC 大家有什么看法,歡迎留言一起討論,大牛多留言 ^_^
參考資料
[1]
change data capture wiki: https://en.wikipedia.org/wiki/Change_data_capture,
[2]tungsten replicator: https://github.com/enowy/tungsten-replicator-1,
[3]alibaba canal: https://github.com/alibaba/canal,
[4]go-mysql: https://github.com/go-mysql-org/go-mysql,
[5]ticdc: https://github.com/pingcap/ticdc,
[6]kafka cdc mysql: "https://dzone.com/articles/change-data-captures-cdc-from-mysql-database-to-ka",
[7]DBLog: "https://netflixtechblog.com/dblog-a-generic-change-data-capture-framework-69351fb9099b",
[8]CQRS: "https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs",
總結
以上是生活随笔為你收集整理的你真的了解 CDC 吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分布式链路跟踪中的 traceid 和
- 下一篇: 我在 Go 圈儿里的几位老朋友