金融級分散式資料庫架構設計要點

資料和雲發表於2020-02-04


行業背景


銀行業從最初的手工記賬到會計電算化,到金融電子化,再到現在的金融科技,可以看到金融與科技的結合越來越緊密,人工智慧、大資料、物聯網、區塊鏈等新興技術改變了金融的交易方式,為金融行業的創新前行提供了源源不斷的動力。同時網際網路金融的興起是一把雙刃劍,帶來了機遇的同時也帶來了挑戰。普惠金融使得金融的門檻降低,更多的普通大眾參與到金融活動中,這讓金融資訊系統承受了越來越大的壓力。於是我們可以看到大型商業銀行、保險公司、證券公司、交易所等核心交易系統都在紛紛進行分散式改造,其中資料庫作為有狀態的應用,成為了資訊系統中唯一的單點,承擔了所有來自上層應用的壓力。隨著資料庫瓶頸的凸顯,進行分散式改造迫在眉睫。



資料庫分散式改造的途徑


 資料庫進行分散式改造主要有三種途徑:分散式訪問客戶端、分散式訪問中介軟體、分散式資料庫。由於其分散式能力實現在不同的層次(應用層、中間層、資料庫層),對應用程式有不同的侵入程度,其中分散式訪問客戶端對應用侵入性最大,改造難度最大,而分散式資料庫方案對應用侵入性最小,但是架構設計及研發難度最大。



 分散式資料庫總體架構


其實當前市面上的分散式資料庫總體架構都是類似的,由必不可缺的三個元件組成:接入節點、資料節點、全域性事務管理器。總體架構如下,協調節點負責sql解析,生成分散式執行計劃,sql轉發,資料彙總等;資料節點負責資料儲存與運算;全域性事務管理器負責全域性事務號的生成,保證事務的全域性一致性。這個架構或多或少都受到了google spanner F1論文的影響,這篇文章主要分析了這幾個元件在實現上有什麼難點,該如何進行架構設計。



兩階段提交的問題


我們知道兩階段提交是阻塞性協議,這也是它最大的問題。下圖pgxc架構下的兩階段提交為例,主要分為下面幾個階段:

①:CN prepare ->②:所有DN prepare ->③:CN commit->④:所有DN commit

 試想一下如果在cn commit階段發生cn/dn當機會發生什麼?


如果在cn下發完cn commit命令後當機,這時dn收到commit命令後會進行提交,但是返回commit ok時發生cn當機,事務進入阻塞狀態。如果cn下發commit之後某個dn發生當機,則會造成某些dn commit成功,某些dn commit失敗,造成不一致,但是如果dn重新啟動後會繼續去cn上拿事務提交資訊,發現是commit狀態,則會繼續執行commit操作,提交之前的事務。在這個地方我們可以探討一個更極端的情況,如果此時cn也當機了,那麼失敗的dn重啟後去cn拿狀態發現拿不到,這是這個失敗dn上的事務就處於一個未決態,不知道是應該提交還是回滾,這時候應該有一個程式能夠從其他dn上發現該狀態並報告給故障dn,通知它進行提交。這個角色就是pgxc_clean程式,其實之前幾種情況下的事務的回滾也是該程式的工作。那我們再深入一下,如果該dn是事務的唯一參與者,那麼此時pgxc_clean就無法從其他dn以及cn獲取狀態,這時該dn就是真正的未決態了。


為了解決兩階段提交的阻塞問題,出現了三階段提交,三階段提交在commit之前引入了cancommit的過程,同時加入超時機制。因為如果協調者發生當機,參與者無法得知協調者到底發出的是commit還是abort,三階段提交cancommit過程就是告知參與者我傳送的是commit或者abort命令,這時如果協調者發生失敗,參與者等待超時時間後可以選出新的協調者,而該協調者是知道應該發出什麼命令。


雖然三階段提交解決了阻塞問題,但是無法解決效能問題,分散式系統中為了保證事務一致性需要跟每個參與者通訊,一個事務的提交和參與需要分散式系統中每個節點的參與,必然帶來延時,不過在萬兆、infiniband、roce高速網路的支援下已經不再是問題了。



CAP與BASE的抉擇


 我們知道分散式系統無法戰勝CAP。那麼在設計分散式系統的時候該如何進行取捨?首先P(分割槽容錯性)是必須保證的,因為分散式系統必然是多個節點(分割槽)通過網路進行互聯,而網路是不可靠的,分散式系統是為了避免單點故障,如果因為網路問題或者某些節點失敗造成整體系統不可用,那麼也不符合分散式系統的設計初衷。如果保證A(可用性),那麼當網路失敗時,網路隔離的不同區域就要繼續提供服務,那麼就會造成不同分割槽的資料不一致(腦裂);如果保證C(一致性),那麼網路失敗時,就需要等待不同網路分割槽的節點同步完資料,如果網路一直失敗,那麼系統就會因為無法同步而一直不可用。


2PC就是典型的犧牲可用性保證一致性的例子,而BASE(basically available,soft state,eventual consistency)就是犧牲一致性保證可用性的例子,因為做到實時的強一致要犧牲的代價太大了,它允許資料在某些時間視窗內的不一致,通過記錄視窗內的每一個臨時狀態日誌做到在系統故障時,通過日誌繼續完成未完成的工作或者取消已經完成的工作回退到初始狀態,這種方式保證了最終一致性。BASE與傳統ACID理論其實是背離的,滿足BASE理論的事務也叫柔性事務,在遭遇失敗時需要有相應的補償機制,與業務耦合性較高,其實我並不是很贊同BASE的做法,因為它已經背離了資料庫最基本的設計理念。



raft的優勢


 不管是上面的XA還是BASE都無法徹底解決一致性問題,真正意義上的強一致一定是基於強一致協議的。paxos和raft是目前主流的兩種共識演算法。Paxos誕生於學院派,是分散式環境下基於訊息傳遞的共識演算法,它設計之初是考慮一個通用的模型,並沒有過多的考慮實際的應用,而且paxos考慮了多個節點同時寫入的情況,這就使得paxos的狀態機異常複雜,所以難以理解,不同的人可能理解出不同的意思,這一點一直遭人詬病,比如MGR引入write set的概念來處理多點寫入衝突的問題,這在高併發熱點資料的場景下是不可接受的。因為paxos的難以理解,史丹佛的兩名大學生設計了raft演算法,相比來說,raft是工業派,同一時刻leader只有一個,follower通過日誌複製實現一致性,相比paxos來說raft的狀態機更加簡單易懂,實現起來也更加簡單,因此在分散式環境上有著廣泛的應用,例如TiDB、RadonDB、etcd、kubernetes等。


Raft協議將共識問題分解為三個子問題分別解決:leader選舉、日誌複製、安全性。


Leader選舉

伺服器節點有三種狀態:領導者、跟隨者和候選者。正常情況下,系統中只有一個領導者,其他的節點全部都是跟隨者,領導者處理全部客戶端請求,跟隨者不會主動傳送任何請求,只是簡單的響應來自領導者或者候選者的請求。如果跟隨者接收不到訊息(選舉超時),那麼他就會變成候選者併發起一次選舉。獲得叢集中大多數選票的候選者將成為領導者,領導者一直都會是領導者直到自己當機了。Raft 演算法把時間分割成任意長度的任期(term),每一段任期從一次選舉開始,一個或者多個候選者嘗試成為領導者。如果一個候選者贏得選舉,然後他就在這個的任期內充當領導者。要開始一次選舉過程,跟隨者先要增加自己的當前任期號並且轉換到候選者狀態,然後他會並行的向叢集中的其他伺服器節點傳送請求投票的 RPCs 來給自己投票,候選者會繼續保持著當前狀態直到以下三件事情之一發生:(a) 他贏得了這次的選舉,(b) 其他伺服器成為領導者,(c) 沒有任何一個候選者贏得選舉。當一個候選者獲得了叢集大多數節點針對同一個任期號的選票,那麼他就贏得了選舉併成為領導者。然後他會向其他的伺服器傳送心跳訊息來建立自己的權威並且阻止新的領導人的產生。下圖為三種角色的轉換狀態機。


日誌複製

當leader被選舉出來,他就作為伺服器處理客戶端請求。客戶端的每一個請求都被看成複製狀態機所需要執行的指令。領導者把這條指令作為一條新的日誌條目附加到日誌中去,然後並行的發起附加條目 RPCs 給其他的伺服器,讓他們複製這條日誌條目。當這條日誌條目被安全的複製,領導者會應用這條日誌條目到它的狀態機中然後把執行的結果返回給客戶端。如果跟隨者崩潰或者網路丟包,領導者會不斷的重複嘗試附加日誌條目 RPCs (儘管已經回覆了客戶端)直到所有的跟隨者都最終儲存了所有的日誌條目。下圖為複製狀態機模型。


安全性

安全性指的是每臺複製狀態機都需要按照同樣的順序執行相同的指令,以保證每臺伺服器資料的一致性。假想一臺跟隨者在某段時間處於不可用狀態,後來可能被選為領導者,這時就會造成之前的日誌被覆蓋。Raft演算法通過在leader選舉時增加一些限制來避免這個問題,這一限制保證所有領導者對於給定的任期號,都擁有了之前任期的所有被提交的日誌條目。日誌條目只會從領導者傳給跟隨者,不會出現因為新領導者缺日誌而需要跟隨者向領導者傳日誌的情況,並且領導者從不會覆蓋本地日誌中已經存在的條目。Raft 演算法使得在投票時投票者拒絕掉那些日誌沒有自己新的投票請求,從而阻止該候選者贏得選票。




CN的設計




接入節點的設計可能看起來很簡單,但是裡面有些地方內容還是有些玄機的。設計cn需要重點考量的地方主要是cn到底是做重還是做輕。這是把雙刃劍,主要有下面兩方面問題。


No.1

如何做到sql語法相容性?


接入節點主要負責sql的解析、執行計劃的生成與下發,這些東西其實是sql解析器做的事情,我們可以直接將mysql或者pg的解析器甚至server層拿過來做sql解析和執行計劃生成,而且就天然的相容了mysql或者pg的語法。


No.2

如何處理後設資料的問題?


上面的方案看似很完美的事情,但是有個問題:如果直接將mysql或者pg的server層搬過來的話,後設資料怎麼辦?cn上到底放不放後設資料?如果不放後設資料,那麼就需要一個統一的存放和管理後設資料的地方,我在cn上建的表需要到某個固定地方更新後設資料資訊,查詢也是一樣。如果cn上存放後設資料,那麼後設資料的更新就需要在各個cn之間進行同步,如果發生某個cn當機,則任何ddl操作都會hang住,這時就需要有一個機制:在cn超時無響應後將cn剔除出叢集。




DN的設計




資料節點的設計主要考慮下面幾個方面問題。


No.1

資料節點如何做高可用?


資料庫的資料當然是最寶貴的,任何資料庫都要有資料冗餘方案,資料節點一定要有高可用,在保證rpo=0的基礎上儘量縮短rto。細想一下,其實每個dn其實都是一個資料庫例項,這裡以mysql或者pg為例,mysql和pg本身是有高可用方案的,不管是基於主從半同步還是流複製,都可以在dn層面作為資料的冗餘和切換方案。當然還有些資料庫在dn層面引入了paxos、raft、quorum等的強一致方案,這也是在分散式資料庫中很常見的設計。


No.2

如何做到線上擴容?


線上擴容是分散式資料庫的一項巨大優點,而擴容資料節點必然涉及到資料向新節點的遷移,目前市面上的分散式資料庫基本上都做到了自動的資料重分佈。但是做到資料庫自動重分佈還不夠,如何做到只遷移少部分資料以降低伺服器IO壓力成為關鍵問題。傳統的雜湊方式是根據分割槽鍵雜湊值對分割槽數量進行取模操作,得到的結果就是資料應該落入的分割槽,但是這種分佈方法在增加刪除節點時會造成大量的資料重分佈,而一致性雜湊的核心思想是每個分割槽不再是對應一個數字,而是對應一個範圍,對計算的雜湊值進行範圍的匹配,大體思路是將資料節點和鍵的hash值都對映到0~2^32的圓環上,然後從對映值的位置開始順時針查詢,將資料儲存到找到的第一個節點上。如果超過2^32仍然找不到服務節點,就會儲存到第一個節點上。一致性雜湊最大程度解決了資料重分佈問題,但是可能會造成節點資料分佈不均勻的問題,當然針對這個問題還有一些改進,比如增加虛擬節點。




GTM的設計




GTM顧名思義是一個全域性概念,分散式資料庫本來就是為了可擴充套件、提升效能、降低全域性風險,然而GTM這個東西打破了這一切。


No.1

為什麼需要GTM?


簡單一句話總結就是:GTM是為了保證全域性讀一致性,而兩階段提交是為了保證寫一致性。這裡我們可能有個誤區,如果沒有gtm那麼會不會造成資料不一致?會,但是隻是某個時間點讀的不一致,這個不一致也是暫時的,但是不會造成資料寫的不一致,寫的一致性通過兩階段提交來保證。


我們知道postgresql通過快照(snapshot)來實現MVCC與事務可見性判斷。對於read commit隔離級別,要求每個事務中的查詢僅能看到在該事務啟動前已經提交的更改,以及當前事務中該查詢之前所做的更改,這都要通過快照來實現。快照的資料結構中會包含事務的xmin(插入tuple的事務號)、xmax(更新或者刪除事務的事務號)、正在執行的事務列表等相關資訊。pg的每條元組(tuple)頭資訊中也會記錄事務的xmin和xmax資訊。Pg取得snapshot後會進行事務可見性判斷,對於所有id小於xmin的tuple對當前快照可見,同時id大於xmax的tuple對當前事務可見。當前擴充套件到分散式叢集后,每臺機器上都存在pg的例項,為了保證全域性的讀一致性,需要一個全域性的元件來負責snapshot的分配,使得快照資訊在各個節點之間共享,這就是gtm的工作。


No.2

GTM高可用的問題?


GTM作為分配全域性快照和事務id的唯一元件,只能有一個,當然gtm可以做主備高可用,但是同一時刻只能有一個gtm在工作,gxid資訊在主備之間進行同步,而且必須是實時強同步,這樣就造成一個問題,雖然其他節點都分散式了,但是gtm始終是一個單點,單點故障時就會涉及到切換,切換過程是影響全域性的,而且為了保證切換後gxid資訊不丟失,gtm之間必須做到gxid的同步。針對高可用這塊問題,可以將gtm的事務號儲存資訊剝離,將事務號資訊存在第三方儲存中,例如etcd就是個很好的選擇,etcd是個強一致高可用的分散式儲存叢集,etcd比較輕量,適合用來儲存事務號資訊,同時它自身保證了高可用與強一致,這時gtm就不需要在主備之間同步gxid,如果發生主備切換,新主gtm只需要再去從etcd中取得最新事務號,寫事務號也同理,主gtm會向主etcd節點寫入事務號資訊,通過etcd自身的raft複製協議保證一致性。這樣的設計使得gtm的壓力減輕很多。


No.3

GTM效能的問題?


GTM是大部分分散式資料庫的效能瓶頸,它使得一套叢集的整體效能甚至不如一臺單機。也很好理解,任何一個事務開啟都要先通過cn到gtm取事務號和快照資訊,然後結果解析後下發到dn執行,然後cn進行彙總再返回給應用,路徑很明顯變長了,那麼效率肯定變低,目前優勢在於可以利用多臺機器的組合能力進行計算,計算資源得到了擴充套件。針對gtm的瓶頸問題當然也有解決方案,比如華為GaussDB就提出GTM-Free和GTM-Lite,gtm-free是在那種強一致讀要求不高的場景下關閉gtm的功能,所有事物都不走gtm,這種情況下效能基本能夠得到線性提升,該功能已經實現;gtm-lite是將事務分類,全域性事務就走gtm,本地事務就直接下發,因為大多數情況下都是本地事務,所以效能提升也很明顯,該功能還在研發階段。




分散式資料庫如何實現PITR




資料庫的PITR一般都是通過一個基礎備份加上持續不間斷的wal歸檔來做到的,這個基礎備份可以是線上的,因為它並不需要資料庫當時處於一致性狀態,一致性可以通過replay redo來實現,所以基礎備份可以是檔案系統tar命令而不需要檔案系統級別的快照。PITR是通過基礎備份加上redo日誌能夠恢復到任意時間點,這個任意時間點不同資料庫有不同定義,可能是某個lsn,可能是某個snapshot,可能是某個timestamp。Postgresql資料庫中能夠基於redo恢復到任意的timestamp。


分散式資料庫的PITR理論上和單機區別不大,每個節點備份自己的基礎資料,這個資料不需要一致性,但是要考慮到分散式事務的問題,在做基礎備份的時候必須保證之前的分散式事務(如果存在)已經全部完成,因為分散式事務是走兩階段提交協議,2pc在提交階段不同的機器commit肯定有時間差,如果在這個時間差做了備份,會發現最後一臺機器有這個事務的redo,另一臺沒有,這樣恢復的話就會造成資料不一致。這個問題可以通過pg中一個barries的概念實現,在分散式事務結束後打一個barrier,獲得一致性點,然後再進行基礎備份。對於redo的前滾來說,只需要將所有節點的redo前滾到一個一致性點即可。



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31556440/viewspace-2674445/,如需轉載,請註明出處,否則將追究法律責任。

相關文章