數據同步一致性保障:OPPO自研JinS數據同步框架實踐

本文來自OPPO互聯網基礎技術團隊,轉載請註名做者。同時歡迎關注咱們的公衆號:OPPO_tech,與你分享OPPO前沿互聯網技術及活動。算法

1. 背景

隨着業務的快速發展,對於不少公司來講,構建於單地域的技術體系架構,會面臨諸以下面的多種問題:數據庫

  1. 基礎設施的有限性限制了業務的可擴展性。業務擴大到單個數據中心撐不住,主要機房已經不能再添加機器,但業務卻不斷要求擴展
  2. 機房、城市級別的故障災害,影響服務的可持續性。整個機房級別的故障時有發生,每次都引發業務的不可用,對公司的形象與收入都形成嚴重的影響。如2015年杭州某數據中心光纜被挖斷,形成某產品業務幾個小時的中斷,致使嚴重的損失
  3. 跨地域的訪問,影響用戶體驗等。隨着全球化腳步的逼近,試想用戶客戶端與服務一次交互,一次RTT最少須要10ms,若廣州到北京網絡延遲通常爲40ms,當用戶客戶端須要與服務器產生屢次交互時,對用戶體驗影響就很大,用戶體驗很是不友好。

OPPO互聯網業務發展快速,已經擴展到全球,隨之帶來的是技術上的多機房架構,對數據的全球多機房同步在一致性、響應時間、吞吐等方面有更高要求。爲了解決以上問題帶來的影響,本文將從異地多活底層,數據層面探索。如何給上層業務提供一個安全、可靠、穩定的數據環境,讓上層業務能夠集中精力專一業務開發,減小對數據多活的關注。緩存

1.1 面臨的挑戰

  • 數據多地寫入的衝突解決問題
  • 遠距離兩地傳輸網絡問題
  • 同步過程當中數據一致性問題
  • 數據同步的冪等性問題
  • 做爲同步中心,關於數據複用問題

基於上述挑戰,調研對比相關的幾個主流開源產品,均有其對應的優劣。如某部分產品針對業務場景變化頻繁的業務,數據複用問題沒法解決,對源端數據形成巨大的壓力。某部分產品針對衝突解決方案沒法達成最終的一致狀態等。最終咱們基於開源產品的基礎上自研 JinS 數據同步框架,全面解決上述問題。安全

1.2 多活原則

異地多活、同城雙活、異地災備等場景,上層業務有幾個基本原則須要遵循:服務器

  • 業務內聚:每一個完整的業務流程,須要在一個機房中完成,不容許跨機房調用。只有保證這樣一個前提,才能保證數據沒有延遲,業務數據的狀態扭轉足夠快。
  • 可用性優先:當發生故障切換機房時,優先保證系統可用。需容忍有限時間段內的數據不一致,在過後作修復
  • 數據正確:在確保可用的基礎下,須要避免數據出錯,在發生切換或故障時,若是發現某些數據異常衝突致使沒法覆蓋,會阻塞至人工介入,保證數據的正確(後續可直接針對數據鎖定跳過操做)

1.3 產品功能

當前產品已實現功能:網絡

  • 雙向同步:支持數據源間的增量實時同步,輕鬆完成異地多活等應用場景
  • 單向同步:支持MySQL的單向同步,MySQL -> Redis 異構同步等,幫助業務簡便完成異地災備,異構同步等場景
  • 數據訂閱:用於緩存通知、業務異步解耦,異構數據源的數據實時同步以及復ETL等多種業務場景
  • 一致性校驗:可完成MySQL端到端的數據一致性校驗以及修復
  • 數據遷移:可完成MySQL的數據遷移,異構遷移等場景

2. 一致性保障探索

咱們將從三個維度的方向去探索數據同步一致性。首先經過架構設計,保障系統的穩定、可靠、可擴展。接着會經過當前業內通用的一致性方案,引出數據同步一致性的基本實現。最後經過具體方案實施的維度,來探索實施過程當中的細節。數據結構

2.1 架構設計

上圖爲數據同步的總體架構圖。主流程中分爲訂閱模塊和消費模塊。訂閱模塊經過dump協議從源端MySQL 中拉取數據,消費端經過與訂閱模塊的通信,獲取須要同步的數據,寫入到目標端中。架構

  • 訂閱模塊併發

    訂閱模塊包含parser(dump拉取)、sink(過濾)、store(存儲)、registry(註冊)、monitor(監控)、protocol(協議)框架

  • 消費模塊

    消費模塊包含input(與訂閱模塊交互)、filter(過濾)、output(輸出)、registry(註冊)、monitor(監控)

  • Manager

    管理平臺模塊,負責完成流程一體化。服務部署,分類,監控,告警等流程管理

  • Consul

    註冊中心,負責完成訂閱、消費節點的服務自動註冊,與prometheus完成監控自動發現,並於Manager模塊完成kv告警指標存儲

  • Consul template

    經過consul kv實現告警規則文件生成,並交付至prometheus

  • Prometheus、Granfa、AlertManager

    開源監控平臺、開源監控平臺可視化界面、開源告警平臺

2.2 架構實踐

因爲數據同步中,數據一致性是重中之重。而隨着業務的發展,軟件須要不斷的迭代。如何從架構層面平衡好其中的關係,使軟件在快速迭代中還要保持穩定、可靠,是架構實施過程當中須要考慮的問題。

在瞭解數據同步架構實踐前,先了解一下微內核架構。

2.2.1 微內核架構

微內核架構,又稱爲插件化架構。例如Eclipse這類IDE軟件、UNIX這類操做系統、淘寶APP客戶端軟件、甚至到Dubbo,採用的都是微內核架構。

微內核架構的設計核心:

  1. 插件管理(如何加載插件以及插件加載時機等)
  2. 插件鏈接(如何鏈接到核心系統,如Spring中的依賴注入,eclipse中的OSGI)
  3. 插件通訊(如何完成數據的交互工做)

微內核架構,實際就是面向功能進行拆分的擴展性架構

  1. 核心系統負責具體業務功能無關的通用功能(如插件加載、插件通訊等)
  2. 插件模塊負責實現具體業務邏輯

2.2.2 數據同步架構

數據同步中,消費模塊參考微內核架構,將各個模塊插件化。registry、monitor、input、filter、output都實現插件可插拔功能。

以Output舉例,參考Java SPI、Dubbo SPI機制,基於 「接口 + 策略模式 + 配置文件」 的形式,實現Output的擴展。

配置文件以下:

經過讀取配置文件中name的類型,完成對應插件的初始化。主流程中只須要完成接口調用,便可完成插件的調用。

當前消費模塊中,已全面實現插件化。訂閱模塊仍然有一部分未完成插件化改造。

2.3 一致性模型探索

章節2.1,2.2中從架構的維度來保證總體的穩定性和擴展性,平衡了軟件開發週期中的擴展與穩定之間的關係。下面從實現一致性的角度來探索數據同步過程當中如何達到數據的一致性。

2.3.1 分佈式系統問題

隨着摩爾定律遇到瓶頸,愈來愈多狀況下要依靠分佈式架構,才能實現海量數據處理能力和可擴展計算能力。若是分佈式集羣沒法保證處理結果一致的話,那任何創建於其上的業務系統都沒法正常工做。一致性問題是分佈式領域最基礎也是最重要的問題。

因爲咱們面臨多節點的分佈式架構,不可避免會出現上圖中所描述的問題,而這些問題正是構成一致性問題的主要成因。

實現絕對理想的嚴格一致性代價是很大的,一般分爲順序一致性和線性一致性。

  • 順序一致性,籠統的說就是保證一個進程內的順序性,多個節點之間的一致性
  • 線性一致性,難度更大,讓一個系統看起來像只有一個數據副本,全部操做都是原子性的,籠統說就是多個進程內也須要保證順序性

相似Google Spanner,經過 原子時鐘 + GPS 的 trueTime 方案,實現分佈式數據庫的線性一致性。

一致性每每是指分佈式系統中多個副本對外呈現的數據狀態,而共識算法,則是描述某一個狀態達成一致結果的過程。所以咱們須要區分,一致性描述的是結果的狀態,共識只是實現某一狀態一致的手段。分佈式數據庫在經過共識算法實現狀態一致的前提下,還須要對多個狀態進行順序識別排序,這是一個至關複雜的過程。

對應的共識算法有paxos、raft、zab等。

因爲當前作的是過後的數據複製,在各個機房作了對應commit的前提下,再作數據複製,相似跨機房的主從複製。所以無須採用相似分佈式數據庫實現數據的共識。沒有了共識以及順序的引入,總體實現就相對簡單,只須要實現數據複製過程當中的一致性便可。

2.3.2 CrashSafe的探索

咱們參考 MySQL InnoDB 的 redo log 與 binlog 兩個日誌保證一致性,並實現對應crash safe,運用到數據同步保證一致性的場景中。

2.3.2.1 InnoDB CrashSafe保證

咱們先設想一下,MySQL 能夠經過binlog恢復到指定某一個時刻的數據,爲何binlog中的數據一定是你所想要的?

經過上圖咱們看到,binlog 日誌是 MySQL server 層實現的,而 InnoDB 存儲引擎本身也實現了本身的redo、undo 日誌。咱們暫時不考慮 InndoDB 是如何經過 redo、undo 日誌實現事務的ACD特性。咱們先考慮,MySQL 是如何實現 binlog 與 redo、undo 的一致性的。

當一條DML語句到達server層

  • 先完成寫 redo、undo,後寫 binlog

數據寫入 redo、undo 日誌完成後,此時還未寫入binlog,MySQL宕機重啓,重啓後因爲InnoDB經過redo恢復數據,而因爲binlog沒有寫入該數據,若之後經過binlog恢復數據,則形成數據不一致。

  • 先完成寫 binlog, 後寫 redo、undo

數據寫入binlog日誌完成後,此時未寫入redo、undo,MySQL宕機重啓,重啓後因爲InnDB經過redo恢復數據,沒有宕機前寫入的數據,而之後經過binlog恢復數據,因爲binlog已經寫入該數據,致使數據不一致。

從上述兩種狀況看,不管先寫入binlog仍是redo log,都會形成數據的不一致。所以爲了保證redo與binlog的數據一致性,MySQL採用2PC機制保證redo與binlog的一致性(具體2PC機制後續會有介紹),同時保證了兩個日誌的一致性後,redo log就能夠實現其crash safe能力,不管寫入在哪一刻宕機,都不會形成數據的不一致。

2.3.2.2 單向一致性

數據同步中,參照 MySQL 機制,經過 2PC 實現數據複製過程當中的一致性,同時也實現了crash safe能力,以下圖:

如上圖所示,消費模塊爲2PC中的協調者,訂閱模塊爲2PC中參與者

  1. 消費模塊(協調者)經過rpc get請求到訂閱模塊

  2. 訂閱模塊(參與者)接收到消費模塊的get請求後,將須要複製的數據發送給消費模塊(協調者)完成prepare階段,同時訂閱模塊會將此複製的數據寫入內存中(redo log)

  3. 消費模塊(協調者)接受到訂閱模塊(參與者)的數據後,即表明參與者已經認同該操做,消費模塊(協調者)便可拿着該數據實現目標數據源的寫入操做,若寫入成功/失敗,都將開啓2PC的下一個階段

  4. 消費模塊(協調者)不管寫入目標成功失敗,都會返回給訂閱模塊(參與者,ack/rollback),訂閱模塊根據協調者的返回結果,決定內存中的redo log數據commit仍是rollback,若commit,將該數據持久化

  5. 訂閱模塊(參與者)回覆消費模塊(協調者)成功完成2PC

  1. 因爲2PC主要用於解決分佈式事務問題,它是一種阻塞性協議,主要是爲了保證數據一致性於是形成的阻塞。而複製的場景中,因爲只有一個參與者,所以咱們能夠引入超時機制,而不會形成非阻塞引發的數據一致性問題
  2. redo log咱們採用內存來實現,是由於MySQL的binlog中已經幫咱們實現了對應的持久化存儲,所以咱們能夠藉助binlog該機制,簡化咱們的CrashSafe能力

2.3.2.3 雙向一致性

經過2PC的機制,咱們能夠完成單向同步過程當中的數據一致性以及CrashSafe能力。咱們再來看看,雙向同步中,因爲有可能有兩邊同時修改同一條記錄的場景,致使場景更加複雜,咱們看如何解決雙向過程當中的一致性。

對於雙向過程當中,對同一記錄的修改,因爲某地的修改,在另一個地方處於未可見的一段時間範圍,那咱們如何確認應該保留哪邊修改的?

如上圖所示,咱們能夠分爲事前控制以及過後處理。

一、事前處理,一般採用共識算法來實現

流程圖1

流程圖2

咱們看經過共識算法的上述兩個流程圖

  1. 流程圖1中 A地包含共識算法中(paxos)的 Proposer、Acceptor,B地中只包含共識算法中的Learner,假設A地寫入,從圖中可得數據將作 一次Paxos時間 + 一次網絡傳輸時間
  2. 流程圖1中 A地包含共識算法中(paxos)的 Proposer、Acceptor,B地中只包含共識算法中的Learner,假設B地寫入,從圖中可得數據將作 一次Paxos時間 + 一次RTT網絡傳輸時間
  3. 流程圖2中,因爲A機房的不斷擴容,必然形成有一天沒法再擴展,於是就會形成 Proposer、Acceptor跨機房,假設A地寫入,從圖中可得數據將作 一次Paxos時間 + 一次RTT網絡傳輸時間
  4. 流程圖2中,假設B地寫入,從圖中可得數據將作 一次Paxos時間 + 2次RTT網絡傳輸時間

若北京廣州機房網絡延遲40ms狀況下,經過paxos協議會形成數據寫入延遲加大。當前MySQL本機房寫入延遲基本在1ms內

二、過後處理,因爲數據在各地commit之後才作數據處理,所以確定會形成一段時間窗口的不一致,主要保證數據的最終一致性

因爲CRDT方案須要數據結構的支持,咱們不改造MySQL,所以該方案直接略過。

Trusted Source方案:當出現數據不一致時,永遠以某一規則來覆蓋數據(修改地、timestamp等)

問題如何判斷數據不一致呢?業內通用方案會採用構建衝突池

從上圖能夠看出,若是某端因爲延遲或者同步宕機形成構建衝突池失敗,就會形成數據不一致。

單向迴環方案:

將數據量較少端作一次環狀同步,保證數據最終一致

從上述3個時序圖能夠得出,單向迴環會保證數據最終一致性(圖1),可是當某地同步延遲或宕機場景下,有可能形成數據的版本交替現象(圖2)、版本丟失(圖3)狀況,可是最終兩地數據必定保證一致。

注: 後續爲了優化版本交替以及版本丟失的場景,會進行數據超過必定閾值溯源反查,提升同步效率以及雙向同步過程當中的全局控制鎖來解決

3. 最佳實踐

前文中介紹了架構以及一致性算法的保證,咱們再來看看咱們實際運用中,如何將方案更好的落地。

3.1 文件存儲

咱們知道,當前大部分開源產品,數據訂閱、同步爲了能更快同步,一般會採用全程不落地on fly同步,隨着業務的變動頻繁以及發展,有可能形成一個數據庫實例須要有多個下游業務使用。這樣就須要啓多個訂閱來實現對應功能。後果隨着業務的臃腫,對數據庫的壓力愈來愈大。

擴展文件存儲的方式,能夠經過文件隊列作下游業務的分發,減小對數據庫的壓力。

如上圖EventStore模塊,經過MMap文件隊列,每一個MMap文件對應一個索引文件。後續能夠經過索引二分查詢定位至具體文件位置。寫入MMap文件隊列爲順序寫入。

涉及到文件的讀寫,就須要考慮到文件的併發控制。當前主流的併發控制有以下:

  • 讀寫鎖
  • COW(Copy On Write)
  • MVCC

因爲咱們的場景都是順序讀寫,MVCC能夠首先忽略。採用讀寫鎖的機制理論上是最簡單。所以初版的文件隊列採用的讀寫鎖來實現,咱們看以下效果:

從上面兩個圖能夠看到,上圖的爲批量操做數據庫,下圖爲單事務操做數據庫。業務更多的場景在單事務操做數據庫,性能嚴重的不足,只有800多的qps,讀寫鎖形成的性能徹底不知足需求。

所以咱們經過改造,去除讀寫鎖。利用ByteBuffer與MMapByteBuffer同時映射至同一塊物理內存來去除讀寫鎖,ByteBuffer負責寫入操做,MMapByteBuffer負責讀,實現讀寫分離,以下圖所示:

最後經過ByteBuffer實現一個讀寫指針,完成物理內存的映射,映射後,MMapByteBuffer就能夠瓜熟蒂落的讀取對應寫入的數據,而沒有了讀寫鎖了,讀寫指針代碼以下:

因爲ByteBuffer中包含HeapByteBuffer以及DirectByteBuffer,咱們建立時採用DirectByteBuffer,防止數據流動形成過多的young gc。

最後咱們經過利用Linux中斷機制,來最後優化磁盤的寫入操做。

每一個JVM進程實際操做的地址都是虛擬內存,而虛擬內存實際會經過MMU與物理內存地址關聯起來。當咱們操做一個文件時,若是文件內容沒有加載至物理內存,會產生一個硬中斷(major page fault),若物理內存存在了,虛擬內存沒有與物理內存關聯,會產生一次軟中斷(minor page fault),而Linux中斷關聯,是以page的維度來發生關聯的,一個page是4kb。所以咱們在寫入文件刷盤時,就能夠充分利用中斷的機制,以page的維度來刷盤,當達到必定的髒頁後,咱們再發生flush操做,減小硬中斷的產生,代碼以下:

相關優化完成後,咱們再來看單事務的測試效果,基本可達到2W qps的寫入:

因爲當前一次順序寫入外,還須要寫入索引文件,所以索引文件的性能形成總體寫入性能不高,後續會繼續針對這塊持續優化。

3.2 relay log

有了上述的文件存儲的優化後,咱們再來看同步過程當中的網絡優化

上圖是MySQL主從同步的流程,從節點會經過I/O線程讀取binlog的內容寫入中繼日誌(relay)中,實現I/O與SQL Thread的分離。讓同步的流程更高,減小網絡帶來的影響。

參考 MySQL 的relay流程,數據同步也在消費模塊中實現了relay log的模塊來保證跨網傳輸的優化,原理大體相似。

在store中實現relay的機制,同時在上層讀取時,用ringbuffer來作讀取的緩存,提升讀取效率。由於ABQ性能雖然高,可是ABQ在讀寫時,會有鎖的競爭,所以二者性能差別較大,性能對好比下:

3.3 兩階段鎖

在 InnoDB 事務中,行鎖是在須要的時候加上的,加上使用完之後,也並不是馬上釋放,而是須要等到事務結束之後纔會釋放。以下圖所示:

這樣的機制會形成,若是事務須要鎖多個行,最好的優化手段是把衝突、併發的鎖儘可能日後放,減小鎖的時間,以下圖:

一樣是一個事務的操做,只是轉換一下順序,庫存的行鎖就減小了鎖等待的時間。間接的提升了總體的操做效率。

基於兩階段鎖的原理,爲了減小鎖等待的時間,數據同步在針對一個table,同一個pk的狀況下,作了數據的合併操做,如將同一個pk的 insert、update語句,合併成一個insert語句等。合併完成後再進行同一個表的batch操做。

由於每一個 DML 操做到達 MySQL須要通過server層的分析器去解析SQL,優化器去優化SQL等,經過batch,內部使用緩存的機制,減小了解析等操做,性能會達到很大的提高。

簡單測試,insert 1000條記錄,採用batch模式大概500ms,而非batch模式須要13000ms。

不過因爲引入了batch,也會引入其餘問題,譬如當數據庫中存在惟一鍵、外鍵時,有可能形成數據DML失敗。後續會針對這種狀況再作深刻優化。

3.4 性能指標

主要針對行業主流開源軟件與商業產品與 JinS 數據同步框架的對比測試

結論:

  1. 對比開源產品,性能提升較爲顯著,基本達到一倍的性能提高。
  2. 對比商業產品,性能較低,大概下降30%,可是從測試結果看,穩定性比商業產品更高。

4. 將來規劃

JinS 數據同步框架上線以來,已支撐衆多OPPO業務線的底層數據傳輸場景,在可用性上達到99.999%,跨機房平均耗時達到秒級傳輸。解決了業務在不停服的前提下,完成數據遷移、跨地域的實時同步、實時增量分發、異地多活等場景,使業務能輕鬆構建高可用的數據庫容災架構。

未來會不斷完善JinS 數據同步框架,在數據的一致性保障、數據的二次加工定製化、性能的線性擴展以及多數據源不斷優化賦能,使其不斷走向完善、通用。

相關文章
相關標籤/搜索