OB君:本文是 「 OceanBase 2.0 技術解析系列」 的第五篇文章。今天咱們繼續來聊分佈式架構,說說2.0中你們都很關心的「全局一致性快照」功能。更多精彩歡迎關注OceanBase公衆號持續訂閱本系列內容!
首先,我想有些朋友在看到這個標題以後可能會問:數據庫
實際上,故事起源於數據庫中的兩個傳統概念:「快照隔離級別(Snapshot Isolation)」和「多版本併發控制(Multi-VersionConcurrency Control,簡稱MVCC)」。這兩種技術的大體含義是:爲數據庫中的數據維護多個版本號(即多個快照),當數據被修改的時候,能夠利用不一樣的版本號區分出正在被修改的內容和修改以前的內容,以此實現對同一份數據的多個版本作併發訪問,避免了經典實現中「鎖」機制引起的讀寫衝突問題。緩存
所以,這兩種技術被不少數據庫產品(如Oracle、SQL Server、MySQL、PostgreSQL)所採用,而OceanBase數據庫也一樣採用了這兩種技術以提升併發場景下的執行效率。但和傳統的數據庫的單點全共享(即Shared-Everything)架構不一樣,OceanBase是一個原生的分佈式架構,採用了多點無共享(即Shared-Nothing)的架構,在實現全局(跨機器)一致的快照隔離級別和多版本併發控制時會面臨分佈式架構所帶來的技術挑戰(後文會有詳述)。markdown
爲了應對這些挑戰,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,不知足條件)。示意圖以下:
其實,違反了這種一致性還可能引起更極端的狀況,考慮下面的場景:
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)架構,數據分佈和事務處理會涉及不一樣的物理機器,而多臺物理機器之間的系統時鐘不可避免存在差別,若是以本地系統時間戳做爲版本號,則沒法保證不一樣機器上獲取的版本號和真實世界的時間序保持一致。仍是以上面的兩個場景爲例,若是T一、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的「全局一致性快照」技術如前文所述, 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服務也像OceanBase中基本的數據服務同樣,以Paxos協議實現了高可用,若是GTS服務因爲異常狀況(好比宕機)而中斷,那麼OceanBase會根據Paxos協議自動選出一個新的服務節點,整個過程自動並且迅速(1~15秒),無需人工干預。
最後,若是事務發現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解析系列文章
10月27日(本週六),OceanBase TechTalk 第二期線下技術交流活動將在北京啓動。
屆時,OceanBase三大重量級嘉賓:陳萌萌(酒滿)、韓富晟(顏然)、喬國治(鷙騰)將跟你們一塊兒聊聊支撐了今年天貓雙11的 OceanBase 2.0版本的產品新特性和重大的技術革新。北京中關村,咱們不見不散!