如何在分散式架構下完美實現“全域性資料一致性”?

螞蟻金服OceanBase資料庫團隊發表於2019-03-03

OB君:本文是 “OceanBase 2.0 技術解析系列” 的第五篇文章。今天我們繼續來聊分散式架構,說說2.0中大家都很關心的“全域性一致性快照”功能。更多精彩歡迎關注OceanBase公眾號持續訂閱本系列內容!

如何在分散式架構下完美實現“全域性資料一致性”?

前言

首先,我想有些朋友在看到這個標題之後可能會問:

  • 什麼是“全域性一致性快照”?
  • 它在OceanBase資料庫裡起什麼作用?
  • 為什麼OceanBase資料庫要在2.0版本中引入這個東西?

實際上,故事起源於資料庫中的兩個傳統概念:“快照隔離級別(Snapshot Isolation)”和“多版本併發控制(Multi-VersionConcurrency Control,簡稱MVCC)”。這兩種技術的大致含義是:為資料庫中的資料維護多個版本號(即多個快照),當資料被修改的時候,可以利用不同的版本號區分出正在被修改的內容和修改之前的內容,以此實現對同一份資料的多個版本做併發訪問,避免了經典實現中“鎖”機制引發的讀寫衝突問題。

因此,這兩種技術被很多資料庫產品(如Oracle、SQL Server、MySQL、PostgreSQL)所採用,而OceanBase資料庫也同樣採用了這兩種技術以提高併發場景下的執行效率。但和傳統的資料庫的單點全共享(即Shared-Everything)架構不同,OceanBase是一個原生的分散式架構,採用了多點無共享(即Shared-Nothing)的架構,在實現全域性(跨機器)一致的快照隔離級別和多版本併發控制時會面臨分散式架構所帶來的技術挑戰(後文會有詳述)。

為了應對這些挑戰,OceanBase資料庫在2.0版本中引入了“全域性一致性快照”技術。本文會介紹和OceanBase“全域性一致性快照”技術相關的概念以及基本實現原理。

(注:本文中的所有描述,都是針對採用了“快照隔離級別”和“多版本併發控制”技術的實現機制。對於使用“鎖”機制來實現傳統“隔離級別(Isolation Level)”的經典模式,不在本文的討論範圍之內。)

傳統資料庫的實現原理

首先,我們來看一下傳統資料庫中是如何實現“快照隔離級別”和“多版本併發控制”的。

以經典的Oracle資料庫為例,當資料的更改在資料庫中被提交的時候,Oracle會為它分配一個“System Change Number(SCN)”作為版本號。SCN是一個和系統時鐘強相關的值,可以簡單理解為等同於系統時間戳,不同的SCN代表了資料在不同時間點的“已提交版本(Committed Version)”,由此實現了資料的快照隔離級別。

假設一條記錄最初插入時對應的版本號為SCN0,當事務T1正在更改此記錄但還未提交的時候(注意:此時T1對應的SCN1尚未生成,需要等到T1的commit階段),Oracle會將資料更改之前的已提交版本SCN0放到“回滾段(Undo Segments)”中儲存起來,此時如果有另外一個併發事務T2要讀取這條記錄,Oracle會根據當前系統時間戳分配一個SCN2給T2,並按照兩個條件去尋找資料:

1)必須是已提交(Committed)的資料;

2)資料的已提交版本(Committed Version)是小於等於SCN2的最大值。

根據上面的條件,事務T2會從回滾段中獲取到SCN0版本所對應的資料,並不理會正在同一條記錄上進行修改的事務T1。利用這種方法,既避免了“髒讀(Dirty Read)”的發生,也不會導致併發的讀/寫操作之間產生鎖衝突,實現了資料的多版本併發控制。整個過程如下圖所示:

如何在分散式架構下完美實現“全域性資料一致性”?

關於“快照隔離級別”和“多版本併發控制”,不同資料庫產品的實現機制會有差異,但大多遵循以下原則:

  • 每次資料的更改被提交時,都會為資料分配一個新的版本號。
  • 版本號的變化必須保證“單調向前”。
  • 版本號取自系統時鐘裡的當前時間戳,或者是一個和當前時間戳強相關的值。
  • 查詢資料時,也需要一個最新版本號(同理,為當前時間戳或者和當前時間戳強相關的值),並查詢小於等於這個版本號的最近已提交資料。

分散式資料庫面臨的挑戰前面關於“多版本併發控制”的描述看上去很完美,但是這裡面卻有一個隱含的前提條件:資料庫中版本號的變化順序必須和真實世界中事務發生的時間順序保持一致,即:

— 真實世界中較早發生的事務必然獲取更小(或者相等)的版本號;

— 真實世界中較晚發生的事務必然獲取更大(或者相等)的版本號。

如果不能滿足這個一致性,會導致什麼結果呢?以下面的場景為例:

1)記錄R1首先在事務T1裡被插入並提交,對應的SCN1是10010;

2)隨後,記錄R2在事務T2裡被插入並提交,對應的SCN2是10030;

3)隨後,事務T3要讀取這兩條資料,它獲取的SCN3為10020,因此它只獲取到記錄R1(SCN1<SCN3,滿足條件),而沒有獲取到R2(SCN2>SCN3,不滿足條件)。示意圖如下:

如何在分散式架構下完美實現“全域性資料一致性”?這對應用來說就是一個邏輯錯誤:我明明向資料庫中插入了兩條記錄並且都提成功提交了,但卻只能讀到其中的一條記錄。導致這個問題的原因,就是這個場景違反了上面所說的一致性,即SCN(版本號)的變化順序沒有和真實世界中事務發生的時間順序保持一致。

其實,違反了這種一致性還可能引發更極端的情況,考慮下面的場景:

1)記錄R1首先在事務T1裡被插入並提交,對應的SCN1是10030;

2)隨後,記錄R2在事務T2裡被插入並提交,對應的SCN2是10010

3)隨後,事務T3要讀取這兩條資料,它獲取的SCN3為10020,因此它只能獲取到記錄R2(SCN2<SCN3,滿足條件),而無法獲取到R1(SCN1>SCN3,不滿足條件)。示意圖如下:

如何在分散式架構下完美實現“全域性資料一致性”?對於應用來說,這種結果從邏輯上講更加難以理解:先插入的資料查不到,後插入的資料反而能查到,完全不合理。

有的朋友可能會說:上面這些情況在實際中是不會發生的,因為系統時間戳永遠是單調向前的,因此真實世界中先提交的事務一定有更小的版本號。是的,對於傳統資料庫來說,由於採用單點全共享(Shared-Everything)架構,資料庫只有一個系統時鐘來源,因此時間戳(即版本號)的變化的確能做到單調向前,並且一定和真實世界的時間順序保持一致。

但對於OceanBase這樣的分散式資料庫來說,由於採用無共享(Shared-Nothing)架構,資料分佈和事務處理會涉及不同的物理機器,而多臺物理機器之間的系統時鐘不可避免存在差異,如果以本地系統時間戳作為版本號,則無法保證不同機器上獲取的版本號和真實世界的時間序保持一致。還是以上面的兩個場景為例,如果T1、T2和T3分別在不同的物理機器上執行,並且它們都分別以本地的系統時間戳作為版本號,那麼由於機器間的時鐘差異,完全可能發生上面所說的兩種異常。

為了解決上面所說的問題,在分散式領域引入了兩個概念: “外部一致性(External Consistency)” 和 “因果一致性(Causal Consistency)”。還是以上面的兩個場景為例,真實世界中事務的發生順序為T1 -> T2-> T3,如果SCN的變化能保證SCN1 < SCN2 < SCN3的順序,並且可以完全不關心事務發生時所在的物理機器,則認為SCN的變化滿足了“外部一致性”。

而“因果一致性”則是“外部一致性”的一種特殊情況:事務的發生不僅有前後順序,還要有前後關聯的因果關係。因此“外部一致性”的覆蓋範圍更廣,“因果一致性”只是其中的一種情況,如果滿足了“外部一致性”則一定能滿足“因果一致性”。OceanBase在實現中滿足了“外部一致性”,同時也就滿足了“因果一致性”,本文後半段的內容也主要針對“外部一致性”來展開。

業內常用的解決方案

那麼,分散式資料庫應如何在全域性(跨機器)範圍內保證外部一致性,進而實現全域性一致的快照隔離級別和多版本併發控制呢?大體來說,業界有兩種實現方式:
1)利用特殊的硬體裝置,如GPS和原子鐘(Atomic Clock),使多臺機器間的系統時鐘保持高度一致,誤差小到應用完全無法感知的程度。在這種情況下,就可以繼續利用本地系統時間戳作為版本號,同時也能滿足全域性範圍內的外部一致性。

如何在分散式架構下完美實現“全域性資料一致性”?

2)版本號不再依賴各個機器自己的本地系統時鐘,所有的資料庫事務通過集中式的服務獲取全域性一致的版本號,由這個服務來保證版本號的單調向前。這樣一來,分散式架構下獲取版本號的邏輯模型和單點架構下的邏輯模型就一樣了,徹底消除了機器之間時鐘差異的因素。

第一種方式的典型代表是Google的Spanner資料庫。它使用GPS系統在全球的多個機房之間保持時間同步,並使用原子鐘確保本地系統時鐘的誤差一直維持在很小的範圍內,這樣就能保證全球多個機房的系統時鐘能夠在一個很高的精度內保持一致,這種技術在Spanner資料庫內被稱為TrueTime。在此基礎上,Spanner資料庫就可以沿用傳統的方式,以本地系統時間戳作為版本號,而不用擔心破壞全域性範圍內的外部一致性。

這種方式的好處,是軟體的實現比較簡單,並且避免了採用集中式的服務可能會導致的效能瓶頸。但這種方式也有它的缺點,首先對機房的硬體要求明顯提高,其次“GPS+原子鐘”的方式也不能100%保證多個機器之間的系統時鐘完全一致,如果GPS或者原子鐘的硬體偏差導致時間誤差過大,還是會出現外部一致性被破壞的問題。根據GoogleSpanner論文中的描述,發生時鐘偏差(clock drift)的概率極小,但並不為0。下圖是Google Spanner論文中對上千臺機器所做的關於時鐘誤差範圍的統計:如何在分散式架構下完美實現“全域性資料一致性”?

OceanBase則選用了第二種實現方式,即用集中式的服務來提供全域性統一的版本號。做這個選擇主要是基於以下考慮:

  • 可以從邏輯上消除機器間的時鐘差異因素,從而徹底避免這個問題。
  • 避免了對特殊硬體的強依賴。這對於一個通用資料庫產品來說尤其重要,我們不能假設所有使用OceanBase資料庫的使用者都在機房裡部署了“GPS+原子鐘”的裝置。

OceanBase的“全域性一致性快照”技術如前文所述, OceanBase資料庫是利用一個集中式服務來提供全域性一致的版本號。事務在修改資料或者查詢資料的時候,無論請求源自哪臺物理機器,都會從這個集中式的服務處獲取版本號,OceanBase則保證所有的版本號單調向前並且和真實世界的時間順序保持一致。

有了這樣全域性一致的版本號,OceanBase就能根據版本號對全域性(跨機器)範圍內的資料做一致性快照,因此我們把這個技術命名為“全域性一致性快照”。有了全域性一致性快照技術,就能實現全域性範圍內一致的快照隔離級別和多版本併發控制,而不用擔心發生外部一致性被破壞的情況。

但是,相信有些朋友看到這裡就會產生疑問了,比如:

  • 這個集中式服務裡是如何生成統一版本號的?怎麼能保證單調向前?
  • 這個集中式服務的服務範圍有多大?整個OceanBase叢集裡的事務都使用同一個服務嗎?
  • 這個集中式服務的效能如何?尤其在高併發訪問的情況下,是否會成為效能瓶頸?
  • 如果這個集中式服務發生中斷怎麼辦?
  • 如果在事務獲取全域性版本號的過程中,發生了網路異常(比如瞬時網路抖動),是否會破壞“外部一致性”?

下面針對這些疑問逐一為大家解答。

首先,這個集中式服務所產生的版本號就是本地的系統時間戳,只不過它的服務物件不再只是本地事務,而是全域性範圍內的所有事務,因此在OceanBase中這個服務被稱作“全域性時間戳服務(Global Timestamp Service,簡稱GTS)”。由於GTS服務是集中式的,只從一個系統時鐘裡獲取時間戳,因此能保證獲取的時間戳(即版本號)一定是單調向前的,並且一定和真實世界的時間順序保持一致。

那麼,是否一個OceanBase資料庫叢集中只有一個GTS服務,叢集中所有的事務都從這裡獲取時間戳呢?對OceanBase資料庫有了解的朋友都知道,“租戶”是OceanBase中實現資源隔離的一個基本單元,比較類似傳統資料庫中“例項”的概念,不同租戶之間的資料完全隔離,沒有一致性要求,也無需實現跨租戶的全域性一致性版本號,因此OceanBase資料庫叢集中的每一個“租戶”都有一個單獨的GTS服務。這樣做不但使GTS服務的管理更加靈活(以租戶為單位),而且也將叢集內的版本號請求分流到了多個GTS服務中,大大減少了因單點服務導致效能瓶頸的可能。下面是OceanBase資料庫叢集中GTS服務的簡單示意圖:

如何在分散式架構下完美實現“全域性資料一致性”?

說到效能,經過實測,單個GTS服務能夠在1秒鐘內響應2百萬次申請時間戳(即版本號)的請求,因此只要租戶內的QPS不超過2百萬,就不會遇到GTS的效能瓶頸,實際業務則很難觸及這個上限。雖然GTS的效能不是一個問題,但從GTS獲取時間戳畢竟比獲取本地時間戳有更多的開銷,至少網路的時延是無法避免的,對此我們也做過實測,在滿負荷壓測並且網路正常的情況下,和採用本地時間戳做版本號相比,採用GTS對效能所帶來的影響不超過5%,絕大多數應用對此都不會有感知。

除了保證GTS的正常處理效能之外,OceanBase資料庫還在不影響外部一致性的前提下,對事務獲取GTS的流程做了優化,比如:

  • 將某些GTS請求轉化為本地請求,從本地控制檔案中獲取版本號,避免了網路傳輸的開銷;
  • 將多個GTS請求合併為一個批量請求,以提高GTS的全域性吞吐量;
  • 利用“本地GTS快取技術”,減少GTS請求的次數。

這些優化措施進一步提高了GTS的處理效率,因此,使用者完全不用擔心GTS的效能。
前面所說的都是正常情況,但對於集中式服務來說一定要考慮異常情況。首先就是高可用的問題,GTS服務也像OceanBase中基本的資料服務一樣,以Paxos協議實現了高可用,如果GTS服務由於異常情況(比如當機)而中斷,那麼OceanBase會根據Paxos協議自動選出一個新的服務節點,整個過程自動而且迅速(1~15秒),無需人工干預。

如何在分散式架構下完美實現“全域性資料一致性”?那如果發生網路異常怎麼辦?比如網路抖動了10秒鐘,會不會影響版本號的全域性一致性,並進而影響外部一致性?對資料庫來說,外部一致性反映的是真實世界中“完整事務”之間的前後順序,比如前面說到的T1 -> T2-> T3,其實準確來說是T1`s Begin-> T1`s End -> T2`s Begin -> T2`s End -> T3`s Begin -> T3`s End,即任意兩個完整的事務視窗之間沒有任何重疊。如果發生了重疊,則事務之間並不具備真正的“先後順序”,外部一致性也就無從談起。
如何在分散式架構下完美實現“全域性資料一致性”?因此,不管網路如何異常,只要真實世界中的“完整事務”滿足這種前後順序,全域性版本號就一定會滿足外部一致性。

最後,如果事務發現GTS響應過慢,會重新傳送GTS請求,以避免由於特殊情況(如網路丟包)而導致事務的處理被GTS請求卡住。
總之,GTS在設計和開發的過程中已經考慮到了諸多異常情況的處理,確保可以提供穩定可靠的服務。

總結有了“全域性一致性快照”技術之後,OceanBase資料庫便具備了在全域性(跨機器)範圍內實現“快照隔離級別”和“多版本併發控制”的能力,可以在全域性範圍內保證“外部一致性”,並在此基礎之上實現眾多涉及全域性資料一致性的功能,比如全域性一致性讀、全域性索引等。

這樣一來,和傳統單點資料庫相比,OceanBase在保留分散式架構優勢的同時,在全域性資料一致性上也沒有任何降級,應用開發者就可以像使用單點資料庫一樣使用OceanBase,完全不必擔心機器之間的底層資料一致性問題。可以說,藉助“全域性一致性快照”技術,OceanBase資料庫完美地實現了分散式架構下的全域性資料一致性!

參考文獻

1. Snapshot isolation
2. Multiversionconcurrency control
3. Isolation(database systems)
4. Spanner (database)
5. CloudSpanner: TrueTime and External Consistency
6. Causal consistency

2.0解析系列文章

OceanBase TechTalk · 北京站

10月27日(本週六),OceanBase TechTalk 第二期線下技術交流活動將在北京啟動。
屆時,OceanBase三大重量級嘉賓:陳萌萌(酒滿)、韓富晟(顏然)、喬國治(鷙騰)將跟大家一起聊聊支撐了今年天貓雙11的 OceanBase 2.0版本的產品新特性和重大的技術革新。北京中關村,我們不見不散!

報名連結:2018 年第二屆 OceanBase TechTalk 技術沙龍 · 北京站

相關文章