MySQL 億級數據量實時同步,如何完美 Hold 住

背景

MySQL 由於自身簡單、高效、可靠的特點,成為小米內部使用最廣泛的數據庫,但是當數據量達到千萬 / 億級別的時候,MySQL 的相關操作會變的非常遲緩;如果這時還有實時 BI 展示的需求,對於 MySQL 來說是一種災難。

為了解決 SQL 查詢慢,查不了的業務痛點,我們探索出一套完整的實時同步,即席查詢的解決方案,本文主要從實時同步的角度介紹相關工作。早期業務藉助 Sqoop 將 Mysql 中的數據同步到 Hive 來進行數據分析,使用過程中也帶來了一些問題:

  • 雖然 Sqoop 支持增量同步但還屬於粗粒度的離線同步,無法滿足實時性的需求;
  • 每次同步 Sqoop 以 SQL 的方式向 MySQL 發出數據請求也在一定程度上對 MySQL 帶來一定的壓力;
  • 同時 Hive 對數據更新的支持也相對較弱。

為了更有效地連接前端業務數據系統(MySQL)和後端統計分析系統(查詢分析引擎),我們需要一套實時同步 MySQL 數據的解決方案。

小米內部實踐

如何能夠做到數據的實時同步呢?我們想到了 MySQL 主從複製時使用的 Binlog 日誌,它記錄了所有的 DDL 和 DML 語句(除了數據查詢語句 select、show 等),以事件形式記錄,還包含語句所執行的消耗時間。

下面來看一下 MySQL 主從複製的原理,主要有以下幾個步驟:

  1. master(主庫)在每次準備提交事務完成數據更新前,將改變記錄到二進制日誌 (binary log) 中;
  2. slave(從庫)發起連接,連接到 master,請求獲取指定位置的 Binlog 文件;
  3. master 創建 dump 線程,推送 Binlog 的 slave;
  4. slave 啟動一個 I/O 線程來讀取主庫上 binary log 中的事件,並記錄到 slave 自己的中繼日誌 (relay log) 中;
  5. slave 還會起動一個 SQL 線程,該線程從 relay log 中讀取事件並在備庫執行,完成數據同步;
  6. slave 記錄自己的 Binlog。
MySQL 億級數據量實時同步,如何完美 Hold 住

Binlog 記錄了 MySQL 數據的實時變化,是數據同步的基礎,服務需要做的就是遵守 MySQL 的協議,將自己偽裝成 MySQL 的 slave 來監聽業務從庫,完成數據實時同步。

結合小米內部系統特點,構建了 MySQL 數據同步服務–-LCSBinlog,作為一種獨立的數據接入方式整合在 Talos Platform 中,Talos Platform 作為大數據集成的基礎解決方案,以自研消息隊列 Talos 為數據總線,連接各種系統為主要目標,提供豐富的數據 Source 輸入和數據 Sink 輸出,並且 Talos 天然支持流式計算,因此業務可以充分利用 Talos Platform 互聯互通的特性,並結合自身的業務需求實現更加高階的業務場景。

MySQL 億級數據量實時同步,如何完美 Hold 住

上圖是 Talos Platform 中的整體流程架構,其中標紅部分是目前 LCSBinlog 在小米內部使用最廣泛的一條鏈路:MySQL —> Talos —> Kudu —> BI,數據同步到 kudu 後藉助 Spark SQL 查詢引擎為上層 BI 系統提供即席查詢服務,Kudu 和 Spark SQL 的整合細節可以參見往期內容:告別”紛紛擾擾”—小米 OLAP 服務架構演進。

LCSBinlog 服務的主體架構

服務一共有兩種角色:

Master :主要負責作業的調度。

Worker: 主要完成具體的數據同步任務。

在 Worker 上運行兩種作業:

  1. BinlogSyncJob:每一個 MySQL 庫都會對應這樣一個 Job,將 Binlog 日誌完整地寫入到服務創建的 Talos topic 中;
  2. MySQLSyncJob:同步歷史數據,消費 Binlog 數據,過濾特定庫表數據實時同步至用戶配置的 topic 中。

服務整體依賴於 Zookeeper 來同步服務狀態,記錄作業調度信息和標記作業運行狀態;在 kudu 表中記錄作業同步進度。

MySQL 億級數據量實時同步,如何完美 Hold 住

控制流程如下:

  1. Worker 節點通過在 Zookeeper 上註冊告知自己可以被調度;
  2. 通過在 Zookeeper 上搶佔 EPHEMERAL 臨時節點實現 Master 的 HA;
  3. 用戶在融合雲(Web)上註冊 BinlogSource 同步任務;
  4. Master 週期性從配置服務讀取 Binlog 同步作業配置;
  5. Master 更新 Zookeeper 中的調度信息;
  6. Worker 節點 根據 Zookeeper 上的調度信息啟動新分配任務,停止配置失效任務;作業啟動後完成數據實時同步並週期性將同步進度記錄在 kudu 中;
  7. 服務上報監控信息到 Falcon 平臺,作業異常退出發送報警郵件。

如何保障數據正確性

順序性

用戶配置的每一個 Binlog Source 都會綁定一個 Talos 的 topic,在進行消費的時候需要保證同一條 MySQL 記錄操作的順序性,消息隊列 Talos 是無法保證全局消息有序的,只能保證 partition 內部有序。

對於配置分庫分表或者多庫同步任務的 Binlog Source,服務會根據庫表信息進行 hash,將數據寫入相應的 partiton,保證同一張表的數據在一個 partition 中,使得下游消費數據的順序性。

對於單表同步的作業目前使用一個 partition 保證其數據有序。

一致性

如何保證在作業異常退出後,作業重新啟動能夠完整地將 MySQL 中的數據同步到下游系統,主要依賴於以下三點:

  1. 服務會記錄作業同步的 offset,重啟後從上次 commit 的 offset 繼續消費;
  2. Binlog 數據的順序性保證了即便數據被重複消費(未 commit 的數據),也能對同一條記錄的操作以相同的順序執行;
  3. 下游存儲系統 kudu,Es ,Redis 基於主鍵的操作能夠保證 Binlog 重複回放後數據的最終一致性。

應用場景

有了這份數據我們可以做些什麼事情呢,本節例舉了幾種常見的應用場景。

實時更新緩存

業務查詢類服務往往會在 MySQL 之上架設一個緩存,減少對底層數據庫的訪問;當 MySQL 庫數據變化時,如果緩存還沒有過期那麼就會拿到過期的數據,業務期望能夠實時更新緩存。

利用 Binlog 服務,根據策略實時將數據同步到 redis 中,這樣就能夠保證了緩存中數據有效性,減少了對數據庫的調用,從而提高整體性能。

MySQL 億級數據量實時同步,如何完美 Hold 住

異步處理,系統解耦

隨著業務的發展,同一份數據可能有不同的分析用途,數據成功寫入到 MySQL 的同時也需要被同步到其他系統;如果用同步的方式處理,一方面拉長了一次事務整個流程,另一方面系統間也會相互影響。

數據在 MySQL 中操作成功後才會記錄在 Binlog 中,保證下游處理到時的一致性;使用 Binlog 服務完成數據的下發,有助於系統的解耦。

關於異步處理,系統解耦在消息隊列價值思考一文中有更深入的解讀。

即席查詢的 BI 系統

就如文章開篇提到的,MySQL 在一定場景下的性能瓶頸,MySQL 數據同步到 kudu 後可以藉助 SparkSQL 完成性能的提升。

因為同樣是 SQL 接口,對使用者的切換成本也是較低的,數據同步到更適合的存儲中進行查詢,也能夠避免因大查詢而對原 MySQL 庫其他查詢的影響。

目前小米內部穩定運行 3000+ 的同步作業,使用 Binlog 服務同步數據到 kudu 中;小米內部 BI 明星產品 XDATA 藉助整套同步流程很好地支持了運營、SQL 分析同學日常統計分析的需求。

如何使用 Binlog 數據

用戶接入數據的時候要求 MySQL 庫開啟 Binlog 日誌格式必須為 Row 模式:記錄的是每一行記錄的每個字段變化前後的值,雖然會造成 Binlog 數據量的增多,但是能夠確保每一條記錄準確性,避免數據同步不一致情況的出現。

最終通過監聽 Binlog 日誌,LCSBinlog 服務將數據轉換成如下的數據結構,寫入用戶註冊的 Topic 中, 目前 Sink 服務使用 SparkStreaming 實時轉儲數據到 kudu 中,後續也將逐步遷移到 Flink 上以提升資源利用、降低延遲。

MySQL 億級數據量實時同步,如何完美 Hold 住

業務用戶也可以根據我們提供的數據格式,實時消費 Talos 數據以實現更復雜的業務邏輯,下表為每一種數據操作,是否保存修改前後的列表。

MySQL 億級數據量實時同步,如何完美 Hold 住

疑難雜症

下面分享 2 個上線後遇到的有趣問題。

數據不一致問題,業務使用唯一索引

業務接入一段時間後, 發現部分表會偶爾存在 kudu 表的數據條目數多於同步的 MySQL 表的數據條目數,我們將多出來的數據與 MySQL 產生的 Binlog 日誌經過一一對比,發現用戶在 MySQL 表中設置了唯一索引,通過唯一索引修改了主鍵,而 kudu 中的數據是通過主鍵標識或更新一條記錄的,於是 update 操作變成了 insert 操作,這就造成了原來的 1 條記錄變成了 2 條。

解決辦法:對於這種類型的表,LCSBinlog 服務會把一次 Update 操作轉換成一條 Delete 數據和一條 Insert 數據。

Full Dump 同步歷史數據時,客戶端超時

服務剛上線的時候,通過 jdbc 執行 SQL 的方式完成全量歷史數據的同步,在同步的過程中會發現 dump 任務會卡頓很長時間才會返回結果,當數據量很大會出現超時同步失敗的情況,會造成數據的延遲。調研後發現使用 MySQL 官方 jdbc 在客戶端查詢數據的時候,默認為從服務器一次取出所有數據放在客戶端內存中,fetch size 參數不起作用,當一條 SQL 返回數據量較大時可能會出現 OOM。

解決辦法:當 statement 設置以下屬性時,採用的是流數據接收方式,每次只從服務器接收部份數據,直到所有數據處理完畢。優化後歷史數據同步穩定運行,對 MySQL 端的壓力也很小。

MySQL 億級數據量實時同步,如何完美 Hold 住

總結

MySQL 以 Binlog 日誌的方式記錄數據變化,基於流式數據的 Change Data Caputre (CDC) 機制實現了 LCSBinlog 服務。

本文主要對 LCSBinlog 的服務架構、應用場景以及在小米內部的實踐經驗進行了介紹,也和大家分享了我們實際中遇到的問題和解決方案,希望能夠幫助到大家理解服務的原理,帶來啟發,也歡迎大家和我們一起交流。


分享到:


相關文章: