前言:GoldenDB 是中興通訊推出的一款自研的金融級交易型分布式數據。針對金融行業關注的資料庫事務一致性問題,中興通訊 GoldenDB 分布式資料庫架構師陸天煒,在DTCC2019資料庫大會上做了乾貨分享,重點介紹了 GoldenDB 的解決方案和對應的優化實踐。
▲中興通訊GoldenDB分布式資料庫架構師 陸天煒
眾所周知,中興通訊的主航道產業是5G通信,但是各行各業都會使用資料庫,中興通訊提交給客戶的通訊設備上面也都使用了大量的資料庫,為了給客戶提供更好的交付體驗,大部分都是自研的產品。
從2002年開始,中興通訊推出EBASE文件資料庫,2007年推出EBASE-MEM內存資料庫,做到2011年的DHSS分布式資料庫,開始有了一個雛形。2014年的時候,中興通訊開始研發了GoldenDB這個金融級的資料庫,2015年的時候已經有第一個商業版本GoldenDB 1.0,並在中信銀行的北京營業廳的冠字號業務上開始使用。到2016年的時候,已經在多個銀行不同業務等級的生產系統上做了商用投產。2017年,中興通訊做了另外一件事情,和中信銀行一起在總行的帳務系統上做了一個核心下移的測試驗證,並且驗證通過了,性能超過40000TPS。當時客戶就決定要在2019年的時候,把核心業務的資料庫,從基於小機的DB2上遷移到基於X86平臺的GoldenDB上。
GoldenDB分布式資料庫總體架構是什麼?
GoldenDB分布式資料庫總體架構分成四個重要部分:
第一部分,是計算節點集群,是多點讀寫模式,因為計算節點沒有狀態,所以可以做到橫向擴容。
第二部分,是數據節點集群。在一套GoldenDB裡面,可以有多個數據節點集群去承載多個不同的業務,每個數據節點集群是可以做到物理隔離的。每個集群內部根據對應的業務流量,還有存儲壓力,去做分片,做負載均衡。每個數據分片都是一主多備的結構,每個備機可以做容災和讀負載能力的擴展。
第三部分,全局事務管理節點,用來管理全局分布式事物的一個生命周期。它跟計算節點做交互,提供全局事務ID的分配、回收,以及全局活躍事物列表的查詢功能。
第四部分,是管理節點,主要包含幾個重要功能:第一,提供了一個Web控制臺頁面,在控制臺上可以做自動的安裝、更新,集群的組建,主備切換,備份恢復等各種與運維相關的操作;第二,管理節點裡面還包含了元數據管理器,源數據管理器存了兩部分信息,第一部分信息是GoldenDB拓撲的組網信息,包括各個集群的設備信息,主備信息、IP信息等,第二部分就是業務資料庫的源數據,包括DDL、庫表結構、分布狀態、分布規則;第三,管理節點還提供了監控,可視化告警等與管理相關的功能。
這就是GoldenDB資料庫的整體架構,對外連接業務,提供的是MySQL的標準協議。
GoldenDB現在正在做的一個事情是,在2019年底的時候要把中信銀行的DB2給替換掉。下面總結了GoldenDB資料庫滿足金融核心功能的一些關鍵需求。
第一,實時一致的分布式事務控制;在做分布式事務控制的時候,能夠做到實時一致,並且事務處理不影響業務原來的業務邏輯,業務原來的代碼遷到GoldenDB上的時候是不需要做邏輯變更。他們原來的代碼是RPG的代碼,直接通過工具改成Java代碼,業務開發的工作量非常少。
第二,支持同城異地容災和一致的備份恢復,符合異地監管的一致性。GoldenDB契合金融的兩個三中心架構,能夠做到同城RPO為0。無論是一致的備份恢復,還是異地接管的恢復,在做備份的時候各個節點單獨備份,但恢復的時候能夠能恢復到全局一致的狀態。所以,異地做接管的時候,雖然會有數據丟失,但是接管後的數據也是一個一致的狀態,更加方便業務進行數據回補。
第三,線性橫向擴展和聯機數據重分布,這包含兩部分內容:一個是性能的橫向擴展;一個是容量的橫向擴展。關於性能的橫向擴展,DBProxy能做到百分之百的橫向擴展,底層數據節點是瓶頸的時候,數據節點能做到95%的橫向擴展。支持在線數據重分布,並且通過追增量和凍結日誌的方式,能夠把真正對聯機交易的影響降低到秒級,一般默認的閾值配置都在5秒以下。
第四,豐富的監控體系和完善的運維能力。傳統核心基於DB2或者Oracle,只有一兩臺設備,運維人員和DBA人員只要監控一兩臺設備就行,換到這個X86的分布式架構下面,可能是十幾臺或者幾十臺,這對運維體系的易用性,對監控體系的完備性要求就更高。所以,中興通訊的GoldenDB在2018年一整年都在做這麼個事情,那就是豐富監控運維體系,跟很多第三方的平臺和第三方工具做對接。
如何看待GoldenDB 事務處理機制的各種問題?
1、事務理論的分布式延伸
關於GoldenDB的分布式事務處理機制,我們先看下事務處理在分布式架構有哪些延伸。我們理解的事務的一致性,其實是在事務內的原子性(原子性-A)和事物間的隔離性(隔離性-I),以及故障時的持久性(持久性-D),只有三者組合到一起才能保證數據的一致性(C)。
在分布式架構下,原子性的要求,是在多個數據分片上的多次操作,要麼一起成功,要麼一起失敗;而在單機架構下,僅是一臺機器上多條記錄的多次操作;隔離性方面,要求多個計算節點上的不同連接,不會相互訪問到在多個數據分片內未提交事務的數據;而單機資料庫的隔離性,是指不同連接(處理線程或進程)不會相互訪問到當前機器上未提交事務的數據;另外,從持久性角度看,分布式資料庫的事務提交前必須將日誌在分片主、 從節點都得到複製,主節點故障時,能在從節點上找回數據,繼續完成事務;單機資料庫的事務要求是,提交前必須先將日誌落盤,機器宕機恢復後不丟失數據。
2、分布式事務的難點
那麼,要實現分布式事務的實時一致性(保證ACID ),難點在哪?其實包括兩個部分:一是部分DB提交失敗,如何保證全局事務的原子性(A);另一部分是,並發訪問時,每個事務都不知道其他事務的狀態,如何保證事務之間的隔離性(I)。以轉帳交易為例: 交易前2個帳戶資金餘額各100,事務T1從帳戶1轉帳50到帳 戶2。需解決問題:在事務T1提交期間,由於DB1和DB2提交時間有空隙,若此時事務T2讀取2個帳戶的餘額,會發現餘額之和是 50+100=150;存在事務T1對帳戶1上扣錢成功,給帳戶2加錢失敗的情況。
3、如何保障原子性?
在業內,通常都用2PC協議保障原子性,在MySQL中就能看到很多場景,一個是MySQL裡跨存儲引擎的事務,二有存儲引擎裡bin log一致性的保障,都通過2PC實現。2PC有自己的優勢,參與者在投贊成票之前可以單方面取消事務,但是也有一些局限性。第一個問題,它本身是一個阻塞式的算法,進入Prepare以後一定要成功;第二個問題是,協同者的狀態其實是一個單點,協調者掛了以後,參與者沒有辦法繼續進行下去,參與者和協調者在某些狀態會等待消息,需要啟動定時監控;第三問題是,正常的提交流程,日誌寫入數量比較大,為 2N+3 ,消息數也多,為 4N ,其中 N 為參與節點的數量。
另外,還有很多通過增加消息中間件,或者使用TCC分布式事務框架,來實現事務原子性的方案。通過業務改造保障原子性,會有額外的問題:首先,是對業務的侵入性較大,所有業務都要增加反向操作邏輯/增加額外中間件;其次,處理過程中數據存在短暫不一致或導致更新延後,原子性不能100%保障;其三,無論2PC、MQ還是TCC,都存在的一個問題,就是在2階段提交Commit階段,在MQ不斷重試的階段,在TCC的Confirm階段如果出現了異常,沒有辦法解決,沒有補救措施,必須人工幹預。
至於,GoldenDB事務原子性機制如何實現?總結起來就兩句話:第一句是一階段提交。GoldenDB有一個全局事務,整體的提交是一階段。把一個全局事務拆成由若干個子系統單獨提交的一個個子事務,把這些子事務由計算節點交給對應DB去執行。執行之前,我們先在GTM那邊把事務標記成活躍的狀態;如果所有的DB都執行成功了,在GTM那邊把事務標記為不活躍,全局事務就結束了;第二句是自動回滾補償。遇到像轉帳失敗的流程後,子事務在單機上會自動回滾,反饋給計算節點後,不是全部成功的,計算節點需要向數據節點下發回滾的消息。回滾補償就是把下發回滾的操作做到資料庫層面,業務層不需要做補償交易。這種模式的優點,一是應用層無需增加額外補償邏輯;二是,失敗回滾是少數情況,整體性能高於兩階段提交,只需要一次交互就可以了,大大節省了單機資源。
4、已提交事務回滾實現和優化方式
在這一過程中,會涉及已提交事務回滾的實現和優化方式。整個過程要經歷定位-遍歷-生成-執行這樣一個流程。通過全局事務ID所對應的Binlog提升回滾性能,數據行裡會存有全局事務的ID, 當你Commit的時候,全局事務ID會隨著Binlog一起進入文件裡。通過Binlog裡面的GTID找到這個GTID所對應的所有Binlog的語句塊,然後解析Binlog,生成SQL,立刻組成新的事務,重新執行一遍。對整個操作過程,GoldenDB做了很多優化,包括表定義緩存、預分析並行查找、共享內存、key值利用、SQL格式,未來還要把正向Binlog直接生成反向 Binlog,不走SQL解析,直接走MySQL並行回放機制,直接應用到資料庫。
5、事務隔離性的各種異常
我們如何處理事務隔離性的各種異常?首先,事務隔離性的各種問題都是並行調度處理不好導致,如果寫寫衝突,處理不當,會導致 『髒寫』。比如: T1w、T2w、T1a,基於被回滾的數據做了update,造成了丟失回滾。再比如,寫讀衝突處理不當,導致『髒讀』,如果第一讀,讀到了未提交的數據,那就是髒讀,如果多次讀並讀了不同版本的數據,那是不可重複讀,由於事務並發導致前後讀出的多個數據間不滿足原有約束,那是讀偏序,由於事務並發導致滿足條件的結果集,變多或者變少,那是幻讀。此外,還有讀寫衝突,處理不當導致 『髒寫』 ,比如:T1r、T2w、T1w,T2w被覆蓋,會導致丟失更新。最後是寫偏序,這是一種違反語義的異常,在資料庫層面看正常,但是達不到你要求的效果,就像黑白球,我有一黑一白兩個球,事務一要把黑球改成白球,事務二要把白球改成黑球,當在快照級別隔離下,兩個事務一起並發操作時,會導致最終事務一改了一個球,事務二改了一個球,一黑一白變成了一白一黑,這種情況是錯誤的,不滿足於任意一種串行處理結果。以上這些異常都是並發調度沒有處理好的結果。
在《A Critique of ANSI SQL Isolation Levels》論文中,總結了隔離級別及對應的現象。首先,R R級別的隔離解決不了幻讀;其次,SI隔離解決不了寫偏序。只有可串行化的隔離級別才能解決所有問題。有人說MySQL級別的R R隔離能解決幻讀,其實MySQL級別的R R隔離並非論文裡指出的R R級別,它實際上是SI的隔離級別。
那麼,實現可串行化隔離級別的方式有哪些呢?第一種方式是嚴格按照串行順序執行,這種方式肯定能保證隔離性。像Voltdb,號稱是最fast的內存資料庫,通過串行保證隔離性,通過在存儲過程裡執行保證了原子性。Redis是單線程跑,所以隔離性也沒有問題,但是Redis只支持單語句事務,只能保證單語句的原子性。如果你需要Redis去做多行的事務,就需要自己去保證它的原子性。第二種,是運用SS2PL技術,加鎖解決幻讀 。比如:MySQL、SQL Server、Informix的隔離級別,實際上都是通過SS2PL技術做到可串行化的隔離級別。第三種,可串行化的快照隔離SSI,可解決寫偏序問題。像PostgreSQL、FoundationDB,都是這種模式的隔離。這三個都是正確的隔離,而其他的隔離都是對性能的妥協,都是不正確的。
6、如何通過2PL進行事務並發控制?
這裡主要看下如何通過2PL進行事務並發控制。Eswaran等人已經證明:遵守2PL算法的並發調度一定是可串行化的。什麼是2PL?是指在資料庫事務處理中的兩階段鎖定,前一個階段只能加鎖,後一個階段只能釋放鎖。如果所有的事務都能遵循這樣一個原則去處理,並發起來的最終結果就等同於某一種串行化的並發調度結果了。不考慮事務Commit時的失敗,那2PL這就夠了,已經能保證事務的一致性了。但是這個可串行化的可能太多了,而且LockPoint比較難找,所以大部分廠商的實現方式都是SS2PL,都是把讀鎖和寫鎖的釋放放到Commit階段一起去執行。
問題是,從2PL到SS2PL到底有哪些限制?第一個是到S2PL,它做了第一個限制,先把寫鎖放到Commit,當一個事務對某個數據做了修改,但是沒提交之前,所修改的數據的後像是不能被其他的事務讀或者寫的。SS2PL則在S2PL基礎上,把讀寫也放到了最後。如果一個事務做了修改,那麼所做修改的數據的前像是沒有被其他事務給讀過的,SS2PL最終達到的效果是,在所有的可串行化的並發調度裡最終選擇了某一種或者某幾種,來保障事務的特性。
GoldenDB是怎麼做事務的並發控制的?GoldenDB引入了全局事務管理器(GTM:Global Transaction Manager)實現全局事務的隔離性,並協助實現全局事務的原子性。
與SS2PL的比較,GTM封鎖是在事務提交之前,所有的GTID相關的數據都不能被讀取和修改。活躍事務列表裡的GTID,相當於寫鎖。保障了寫寫和寫讀衝突下的數據一致性。另外,通過對讀操作顯式加鎖,達到嚴格的SS2PL效果,保障了讀寫衝突下的數據一致性。而SS2PL只在事務提交的的時候才釋放讀鎖和寫鎖。
GoldenDB還有一個預鎖機制,用來解決如下問題:DBProxy查詢到的活躍事務列表是舊的;DBProxy基於活躍事務列表做的判斷是不準確的。GoldenDB的解決辦法是,將所有的可能性衝突都認為是衝突。在返回活躍事務列表中,增加Max_GITD,Proxy判斷如果GTID大於Max_GITD,也認為該事物是存在衝突的。
總結下來,對於事務處理中的寫一致處理,涉及兩個方面:一個是滿足條件的記錄上無鎖, GTID不活躍,就可以直接修改數據,如果GTID活躍,這個時候需要通過預鎖機制重新等待GTID被釋放;另外是滿足條件的記錄上有鎖,只能先走單機鎖衝突的事務處理機制,等到鎖釋放了,再去判斷GTID是否活躍。
對於事務處理中讀一致性的處理:在髒讀的場景下,不做任何判斷,直接讀,但是不推薦這種隔離級別;讀已提交,如果有排它鎖,證明這條數據有事務正在修改,直接讀它的前像。如果無排它鎖,GTID不活躍,沒有單機事務,全局事務,直接讀當前數據行。如果無排它鎖,GTID活躍,說明沒有單機事務,但是有全局事務,仍然不能讀單機上的數據,需要去讀 數據,這個前像數據的purge操作需要全局進行;在讀已提交的場景下,有排他鎖和無排他鎖、GTID活躍場景的處理方式是一樣的;在可重複讀,有排它鎖的情況下,會按照GTID版本號去找,找到快照開始時候的版本號。在可重複讀的場景下,無排它鎖,GTID不活躍,看上去沒有問題,但是當前數據行有可能已經被提交過了,不能直接讀,需要篩選版本判斷是否需要找前像數據。無排它鎖,GTID活躍,也是一樣的,需要去找滿足條件的GTID版本號的事務版本;可序列化級別也如此,加共享鎖無衝突,GTID不活躍,可以直接讀數據,如果GTID活躍,還可以讀數據,但是要把活躍的衝突的GTID號給帶上去,DBProxy根據預鎖的機制,判斷什麼時候GTID能正常釋放,如果正常釋放,數據就可以讀到。不能的話,需要把事務重啟。在可序列化、加共享鎖有衝突的場景下,仍然按照單機鎖衝突的並發控制去處理。
事務處理模塊優化有哪些實踐經驗?
在GoldenDB上有個GTM,是一個主要的優化點。怎麼解決性能的瓶頸?首先,GTM被設計得很簡單,只有三個流程,負責GTID的分配、回收,負責向計算節點提供活躍事務ID的查詢的一個接口。GTID的分配和回收需要實時落盤,GTID相當於一個大號的Binlog ,一次寫盤大概1μs,理論上限100萬 TPS,能否突破?另外一個優化點是,Proxy和GTM之間的消息交互,對網絡資源消耗過大,理論瓶頸1.25GB 帶寬如何利用?
1、申請/釋放GTID優化:Proxy批量請求
在釋放GTID的時候,做了批量釋放,能夠做到減少交互次數,降低交互的數據量。在申請GTID的時候也做了批量申請,但是這裡有個問題,在RC和串行隔離下面,可以任意使用批量申請,但是在RR隔離下面,是不能肆無忌憚的去使用批量申請的,在RR隔離級別下,是基於GTID的數據快照,要求GTID是嚴格單調的,所以在GTID申請的時候,還是要保障GTID單調,先來的事務先獲得GTID。但實際上,由於系統的調度都是有誤差的,比如Proxy1向GTM申請了GTID1,要去修改數據A, Proxy2向GTM申請了GTID2,也要修改數據A,雖然是Proxy1先申請,但是他不一定能最快地給數據I加上鎖,所以靠分配GTID去嚴格保障時間順序,有誤差。也就是資料庫調度會有問題,不能保證先來的請求一定會先被執行。所以,在一定誤差範圍內,分配的GTID可以不保證嚴格單調的,需要做額外處理。Proxy1先申請了1000個GTID,我需要他在很短的時間內把這1000個GTID給用掉,如果不用,需要有一個廢棄的機制,相當於保證GTID申請的這些事務在誤差內保證單調。這樣可也減少交互次數,降低交互數據量。
2、查詢GTID列表優化:Proxy進行組提交
Proxy北向是一個資料庫連接池,跟應用的連接池做交互,有的執行線程是在做建鏈,有的是在做SQL解析,但是也有可能他們都在做GTID的查詢,如果所有的線程都能直接和GTID去做交互,交互數據量會很大。所以,代理線程可匯總執行線程的請求,然後跟GTM做單獨的交互,並且這個交互是單線程單工的,使用一份活躍事務列表應答。當在途請求沒有返回時,代理線程不會發送新的查詢請求。
3、查詢GTID列表優化:GTM進行組提交
GTM和Proxy是一對多的,多個計算節點去訪問同一個GTM,GTM在匯總多個Proxy請求以後,他也可以產生一個子線程出來。專門處理GTM返回請求的拼裝,用同一份活躍事務列表應答不同Proxy發來的請求。 當子線程沒有拼裝完返回列表時,不會處理新的請求,這和Proxy處理機制類似。
4、組提交優化後對預鎖機制的影響
有一個問題是,剛在Proxy上和GTM上都做了組提交,但對之前的預鎖機制,對性能到底有沒有影響。在優化之前的場景下,Proxy上所有向GTM發的查詢請求,都需要過0.5ms(32k包大小萬兆下的雙向時延)才能收到響應,Proxy獲取到的事務列表都落後GTM0.25ms;優化之後,Proxy上所有的請求,在0~0.5ms 內會收到響應,Proxy獲取到的事務列表的時間沒有變化,仍然是落後GTM0.25ms,經過組提交優化後,並未增加單消息處理時延。可能有人會問,如果以後性能要求更高的情況下,如何處理帶寬佔用的問題?我們可以通過自適應算法精簡請求,依據請求數、統計時間和當前負載情況,實時修改發送周期。另外,可以通過數據壓縮技術,通過記錄GTID起始值(8Byte)和偏移量位圖(255Byte),將數據包大小由32k降到1K以內,壓縮後萬兆的帶寬允許提供1200個Proxy。所以,Proxy和GTM的帶寬不會是性能瓶頸。
5、申請釋放GTID優化:GTM線程優化
這是之前踩的一個坑,之前是按照做DB的思路去做GTM上的持久化,專門弄了一個Flush線程去寫日誌,但是沒有考慮到一個問題就是GTM上的數據結構非常簡單,消耗的不是I/O,消耗的是IOPS,專門弄一個線程去做異步處理,處理得很快,每次收到的請求很少,導致IOPS非常高,而且線程切換非常嚴重。但是,把它變成串行化處理了以後,請求積攢了,IO量變大了,但是IOPS會降下來,不用做CPU的線程的切換了,CPU也減低了。
經過各種優化後,GoldenDB在這個數據模型下,經過了嚴苛的技術驗證,實現了3億客戶15億帳戶轉帳交易、明細查詢40000 TPS的支撐能力。在這一過程中,還模擬了軟硬體故障的各種測試,能滿足核心業務強一致性要求,正確率達到100%。同時,在這個場景下,做了當Proxy是瓶頸的時候,去擴展Proxy;在DB是瓶頸的時候,去擴展DB的一個測試。其中,計算節點擴展線性率達到100%,數據節點擴展線性率95%。在故障隔離方面,計算節點故障、不影響整體系統交易;數據節點故障的時候,不影響其他節點交易。