Google全球級分散式資料庫Spanner原理

發表於2013-04-15

來源:EMC中國研究院,作者:顏開( @yankay

Google Spanner簡介

Spanner 是Google的全球級的分散式資料庫 (Globally-Distributed Database) 。Spanner的擴充套件性達到了令人咋舌的全球級,可以擴充套件到數百萬的機器,數已百計的資料中心,上萬億的行。更給力的是,除了誇張的擴充套件性之外,他還能同時通過同步複製和多版本來滿足外部一致性,可用性也是很好的。衝破CAP的枷鎖,在三者之間完美平衡。
全球級的分散式資料庫 Google Spanner原理

Spanner是個可擴充套件,多版本,全球分散式還支援同步複製的資料庫。他是Google的第一個可以全球擴充套件並且支援外部一致的事務。Spanner能做到這些,離不開一個用GPS和原子鐘實現的時間API。這個API能將資料中心之間的時間同步精確到10ms以內。因此有幾個給力的功能:無鎖讀事務,原子schema修改,讀歷史資料無block。

EMC中國研究院實時緊盯業界動態,Google最近釋出的一篇論文《Spanner: Google’s Globally-Distributed Database》, 筆者非常感興趣,對Spanner進行了一些調研,並在這裡分享。由於Spanner並不是開源產品,筆者的知識主要來源於Google的公開資料,通過現有公開資料僅僅只能窺得Spanner的滄海一粟,Spanner背後還依賴有大量Google的專有技術。

下文主要是Spanner的背景,設計和併發控制。

 

Spanner背景

要搞清楚Spanner原理,先得了解Spanner在Google的定位。
全球級的分散式資料庫 Google Spanner原理

從上圖可以看到。Spanner位於F1和GFS之間,承上啟下。所以先提一提F1和GFS。

 

F1

和眾多網際網路公司一樣,在早期Google大量使用了Mysql。Mysql是單機的,可以用Master-Slave來容錯,分割槽來擴充套件。但是需要大量的手工運維工作,有很多的限制。因此Google開發了一個可容錯可擴充套件的RDBMS——F1。和一般的分散式資料庫不同,F1對應RDMS應有的功能,毫不妥協。起初F1是基於Mysql的,不過會逐漸遷移到Spanner。

F1有如下特點:

·        7×24高可用。哪怕某一個資料中心停止運轉,仍然可用。

·        可以同時提供強一致性和弱一致。

·        可擴充套件

·        支援SQL

·        事務提交延遲50-100ms,讀延遲5-10ms,高吞吐

眾所周知Google BigTable是重要的NoSql產品,提供很好的擴充套件性,開源世界有HBase與之對應。為什麼Google還需要F1,而不是都使用BigTable呢?因為BigTable提供的最終一致性,一些需要事務級別的應用無法使用。同時BigTable還是NoSql,而大量的應用場景需要有關係模型。就像現在大量的網際網路企業都使用Mysql而不願意使用HBase,因此Google才有這個可擴充套件資料庫的F1。而Spanner就是F1的至關重要的底層儲存技術。

 

Colossus(GFS II)

Colossus也是一個不得不提起的技術。他是第二代GFS,對應開源世界的新HDFS。GFS是著名的分散式檔案系統。
全球級的分散式資料庫 Google Spanner原理

初代GFS是為批處理設計的。對於大檔案很友好,吞吐量很大,但是延遲較高。所以使用他的系統不得不對GFS做各種優化,才能獲得良好的效能。那為什麼Google沒有考慮到這些問題,設計出更完美的GFS ?因為那個時候是2001年,Hadoop出生是在2007年。如果Hadoop是世界領先水平的話,GFS比世界領先水平還領先了6年。同樣的Spanner出生大概是2009年,現在我們看到了論文,估計Spanner在Google已經很完善,同時Google內部已經有更先進的替代技術在醞釀了。筆者預測,最早在2015年才會出現Spanner和F1的山寨開源產品。

Colossus是第二代GFS。Colossus是Google重要的基礎設施,因為他可以滿足主流應用對FS的要求。Colossus的重要改進有:

·        優雅Master容錯處理 (不再有2s的停止服務時間)

·        Chunk大小隻有1MB (對小檔案很友好)

·        Master可以儲存更多的Metadata(當Chunk從64MB變為1MB後,Metadata會擴大64倍,但是Google也解決了)

Colossus可以自動分割槽Metadata。使用Reed-Solomon演算法來複制,可以將原先的3份減小到1.5份,提高寫的效能,降低延遲。客戶端來複制資料。具體細節筆者也猜不出。

 

與BigTable, Megastore對比

Spanner主要致力於跨資料中心的資料複製上,同時也能提供資料庫功能。在Google類似的系統有BigTable和Megastore。和這兩者相比,Spanner又有什麼優勢呢。

BigTable在Google得到了廣泛的使用,但是他不能提供較為複雜的Schema,還有在跨資料中心環境下的強一致性。Megastore有類RDBMS的資料模型,同時也支援同步複製,但是他的吞吐量太差,不能適應應用要求。Spanner不再是類似BigTable的版本化 key-value儲存,而是一個“臨時多版本”的資料庫。何為“臨時多版本”,資料是儲存在一個版本化的關係表裡面,儲存的時間資料會根據其提交的時間打上時間戳,應用可以訪問到較老的版本,另外老的版本也會被垃圾回收掉。

Google官方認為 Spanner是下一代BigTable,也是Megastore的繼任者。

 

Google Spanner設計

功能

從高層看Spanner是通過Paxos狀態機將分割槽好的資料分佈在全球的。資料複製全球化的,使用者可以指定資料複製的份數和儲存的地點。Spanner可以在叢集或者資料發生變化的時候將資料遷移到合適的地點,做負載均衡。使用者可以指定將資料分佈在多個資料中心,不過更多的資料中心將造成更多的延遲。使用者需要在可靠性和延遲之間做權衡,一般來說複製1,2個資料中心足以保證可靠性。

作為一個全球化分散式系統,Spanner提供一些有趣的特性。

·        應用可以細粒度的指定資料分佈的位置。精確的指定資料離使用者有多遠,可以有效的控制讀延遲(讀延遲取決於最近的拷貝)。指定資料拷貝之間有多遠,可以控制寫的延遲(寫延遲取決於最遠的拷貝)。還要資料的複製份數,可以控制資料的可靠性和讀效能。(多寫幾份,可以抵禦更大的事故)

·        Spanner還有兩個一般分散式資料庫不具備的特性:讀寫的外部一致性,基於時間戳的全域性的讀一致。這兩個特性可以讓Spanner支援一致的備份,一致的MapReduce,還有原子的Schema修改。

這寫特性都得益有Spanner有一個全球時間同步機制,可以在資料提交的時候給出一個時間戳。因為時間是系列化的,所以才有外部一致性。這個很容易理解,如果有兩個提交,一個在T1,一個在T2。那有更晚的時間戳那個提交是正確的。

這個全球時間同步機制是用一個具有GPS和原子鐘的TrueTime API提供了。這個TrueTime API能夠將不同資料中心的時間偏差縮短在10ms內。這個API可以提供一個精確的時間,同時給出誤差範圍。Google已經有了一個TrueTime API的實現。筆者覺得這個TrueTimeAPI 非常有意義,如果能單獨開源這部分的話,很多資料庫如MongoDB都可以從中受益。

體系結構

Spanner由於是全球化的,所以有兩個其他分散式資料庫沒有的概念。

·        Universe。一個Spanner部署例項稱之為一個Universe。目前全世界有3個。一個開發,一個測試,一個線上。因為一個Universe就能覆蓋全球,不需要多個。

·        Zones. 每個Zone相當於一個資料中心,一個Zone內部物理上必須在一起。而一個資料中心可能有多個Zone。可以在執行時新增移除Zone。一個Zone可以理解為一個BigTable部署例項。
全球級的分散式資料庫 Google Spanner原理

 

如圖所示。一個Spanner有上面一些元件。實際的元件肯定不止這些,比如TrueTime API Server。如果僅僅知道這些知識,來構建Spanner是遠遠不夠的。但Google都略去了。那筆者就簡要介紹一下。

·        Universemaster: 監控這個universe裡zone級別的狀態資訊

·        Placement driver:提供跨區資料遷移時管理功能

·        Zonemaster:相當於BigTable的Master。管理Spanserver上的資料。

·        Location proxy:儲存資料的Location資訊。客戶端要先訪問他才知道資料在那個Spanserver上。

·        Spanserver:相當於BigTable的ThunkServer。用於儲存資料。

可以看出來這裡每個元件都很有料,但是Google的論文裡只具體介紹了Spanserver的設計,筆者也只能介紹到這裡。下面詳細闡述Spanserver的設計。

 

Spanserver

本章詳細介紹Spanserver的設計實現。Spanserver的設計和BigTable非常的相似。參照下圖:
全球級的分散式資料庫 Google Spanner原理

從下往上看。每個資料中心會執行一套Colossus (GFS II) 。每個機器有100-1000個tablet。Tablet概念上將相當於資料庫一張表裡的一些行,物理上是資料檔案。打個比方,一張1000行的表,有10個tablet,第1-100行是一個tablet,第101-200是一個tablet。但和BigTable不同的是BigTable裡面的tablet儲存的是Key-Value都是string,Spanner儲存的Key多了一個時間戳:

(Key: string, timestamp: int64) ->string。

因此spanner天生就支援多版本,tablet在檔案系統中是一個B-tree-like的檔案和一個write-ahead日誌。

每個Tablet上會有一個Paxos狀態機。Paxos是一個分散式一致性協議。Table的後設資料和log都儲存在上面。Paxos會選出一個replica做leader,這個leader的壽命預設是10s,10s後重選。Leader就相當於複製資料的master,其他replica的資料都是從他那裡複製的。讀請求可以走任意的replica,但是寫請求只有去leader。這些replica統稱為一個paxos group。

每個leader replica的spanserver上會實現一個lock table還管理併發。Lock table記錄了兩階段提交需要的鎖資訊。但是不論是在Spanner還是在BigTable上,但遇到衝突的時候長時間事務會將效能很差。所以有一些操作,如事務讀可以走lock table,其他的操作可以繞開lock table。

每個leader replica的spanserver上還有一個transaction manager。如果事務在一個paxos group裡面,可以繞過transaction manager。但是一旦事務跨多個paxos group,就需要transaction manager來協調。其中一個Transactionmanager被選為leader,其他的是slave聽他指揮。這樣可以保證事務。

 

Directories and Placement

之所以Spanner比BigTable有更強的擴充套件性,在於Spanner還有一層抽象的概念directory, directory是一些key-value的集合,一個directory裡面的key有一樣的字首。更妥當的叫法是bucketing。Directory是應用控制資料位置的最小單元,可以通過謹慎的選擇Key的字首來控制。據此筆者可以猜出,在設計初期,Spanner是作為F1的儲存系統而設立,甚至還設計有類似directory的層次結構,這樣的層次有很多好處,但是實現太複雜被摒棄了。

Directory作為資料放置的最小單元,可以在paxos group裡面移來移去。Spanner移動一個directory一般出於如下幾個原因:

·        一個paxos group的負載太大,需要切分

·        將資料移動到access更近的地方

·        將經常同時訪問的directory放到一個paxos group裡面

Directory可以在不影響client的前提下,在後臺移動。移動一個50MB的directory大概需要的幾秒鐘。

那麼directory和tablet又是什麼關係呢。可以理解為Directory是一個抽象的概念,管理資料的單元;而tablet是物理的東西,資料檔案。由於一個Paxos group可能會有多個directory,所以spanner的tablet實現和BigTable的tablet實現有些不同。BigTable的tablet是單個順序檔案。Google有個專案,名為Level DB,是BigTable的底層,可以看到其實現細節。而Spanner的tablet可以理解是一些基於行的分割槽的容器。這樣就可以將一些經常同時訪問的directory放在一個tablet裡面,而不用太在意順序關係。

在paxos group之間移動directory是後臺任務。這個操作還被用來移動replicas。移動操作設計的時候不是事務的,因為這樣會造成大量的讀寫block。操作的時候是先將實際資料移動到指定位置,然後再用一個原子的操作更新後設資料,完成整個移動過程。

Directory還是記錄地理位置的最小單元。資料的地理位置是由應用決定的,配置的時候需要指定複製數目和型別,還有地理的位置。比如(上海,複製2份;南京複製1分) 。這樣應用就可以根據使用者指定終端使用者實際情況決定的資料儲存位置。比如中國隊的資料在亞洲有3份拷貝, 日本隊的資料全球都有拷貝。

前面對directory還是被簡化過的,還有很多無法詳述。

 

資料模型

Spanner的資料模型來自於Google內部的實踐。在設計之初,Spanner就決心有以下的特性:

·        支援類似關聯式資料庫的schema

·        Query語句

·        支援廣義上的事務

為何會這樣決定呢?在Google內部還有一個Megastore,儘管要忍受效能不夠的折磨,但是在Google有300多個應用在用它,因為Megastore支援一個類似關聯式資料庫的schema,而且支援同步複製 (BigTable只支援最終一致的複製) 。使用Megastore的應用有大名鼎鼎的Gmail, Picasa, Calendar, Android Market和AppEngine。 而必須對Query語句的支援,來自於廣受歡迎的Dremel,筆者不久前寫了篇文章來介紹他。 最後對事務的支援是比不可少了,BigTable在Google內部被抱怨的最多的就是其只能支援行事務,再大粒度的事務就無能為力了。Spanner的開發者認為,過度使用事務造成的效能下降的惡果,應該由應用的開發者承擔。應用開發者在使用事務的時候,必須考慮到效能問題。而資料庫必須提供事務機制,而不是因為效能問題,就乾脆不提供事務支援。

資料模型是建立在directory和key-value模型的抽象之上的。一個應用可以在一個universe中建立一個或多個database,在每個database中建立任意的table。Table看起來就像關係型資料庫的表。有行,有列,還有版本。Query語句看起來是多了一些擴充套件的SQL語句。

Spanner的資料模型也不是純正的關係模型,每一行都必須有一列或多列元件。看起來還是Key-value。主鍵組成Key,其他的列是Value。但這樣的設計對應用也是很有裨益的,應用可以通過主鍵來定位到某一行。
全球級的分散式資料庫 Google Spanner原理

上圖是一個例子。對於一個典型的相簿應用,需要儲存其使用者和相簿。可以用上面的兩個SQL來建立表。Spanner的表是層次化的,最頂層的表是directory table。其他的表建立的時候,可以用interleave in parent來什麼層次關係。這樣的結構,在實現的時候,Spanner可以將巢狀的資料放在一起,這樣在分割槽的時候效能會提升很多。否則Spanner無法獲知最重要的表之間的關係。

 

TrueTime
全球級的分散式資料庫 Google Spanner原理

TrueTime API 是一個非常有創意的東西,可以同步全球的時間。上表就是TrueTime API。TT.now()可以獲得一個絕對時間TTinterval,這個值和UnixTime是相同的,同時還能夠得到一個誤差e。TT.after(t)和TT.before(t)是基於TT.now()實現的。

那這個TrueTime API實現靠的是GFS和原子鐘。之所以要用兩種技術來處理,是因為導致這兩個技術的失敗的原因是不同的。GPS會有一個天線,電波干擾會導致其失靈。原子鐘很穩定。當GPS失靈的時候,原子鐘仍然能保證在相當長的時間內,不會出現偏差。

實際部署的時候。每個資料中心需要部署一些Master機器,其他機器上需要有一個slave程式來從Master同步。有的Master用GPS,有的Master用原子鐘。這些Master物理上分佈的比較遠,怕出現物理上的干擾。比如如果放在一個機架上,機架被人碰倒了,就全宕了。另外原子鐘不是並很貴。Master自己還會不斷比對,新的時間資訊還會和Master自身時鐘的比對,會排除掉偏差比較大的,並獲得一個保守的結果。最終GPS master提供時間精確度很高,誤差接近於0。

每個Slave後臺程式會每個30秒從若干個Master更新自己的時鐘。為了降低誤差,使用Marzullo演算法。每個slave還會計算出自己的誤差。這裡的誤差包括的通訊的延遲,機器的負載。如果不能訪問Master,誤差就會越走越大,知道重新可以訪問。

 

Google Spanner併發控制

Spanner使用TrueTime來控制併發,實現外部一致性。支援以下幾種事務。

·        讀寫事務

·        只讀事務

·        快照讀,客戶端提供時間戳

·        快照讀,客戶端提供時間範圍

例如一個讀寫事務發生在時間t,那麼在全世界任何一個地方,指定t快照讀都可以讀到寫入的值。
全球級的分散式資料庫 Google Spanner原理

上表是Spanner現在支援的事務。單獨的寫操作都被實現為讀寫事務 ; 單獨的非快照被實現為只讀事務。事務總有失敗的時候,如果失敗,對於這兩種操作會自己重試,無需應用自己實現重試迴圈。

時間戳的設計大大提高了只讀事務的效能。事務開始的時候,要宣告這個事務裡沒有寫操作,只讀事務可不是一個簡單的沒有寫操作的讀寫事務。它會用一個系統時間戳去讀,所以對於同時的其他的寫操作是沒有Block的。而且只讀事務可以在任意一臺已經更新過的replica上面讀。

對於快照讀操作,可以讀取以前的資料,需要客戶端指定一個時間戳或者一個時間範圍。Spanner會找到一個已經充分更新好的replica上讀取。

還有一個有趣的特性的是,對於只讀事務,如果執行到一半,該replica出現了錯誤。客戶端沒有必要在本地快取剛剛讀過的時間,因為是根據時間戳讀取的。只要再用剛剛的時間戳讀取,就可以獲得一樣的結果。

 

讀寫事務

正如BigTable一樣,Spanner的事務是會將所有的寫操作先快取起來,在Commit的時候一次提交。這樣的話,就讀不出在同一個事務中寫的資料了。不過這沒有關係,因為Spanner的資料都是有版本的。

在讀寫事務中使用wound-wait演算法來避免死鎖。當客戶端發起一個讀寫事務的時候,首先是讀操作,他先找到相關資料的leader replica,然後加上讀鎖,讀取最近的資料。在客戶端事務存活的時候會不斷的向leader發心跳,防止超時。當客戶端完成了所有的讀操作,並且快取了所有的寫操作,就開始了兩階段提交。客戶端閒置一個coordinator group,並給每一個leader傳送coordinator的id和快取的寫資料。

leader首先會上一個寫鎖,他要找一個比現有事務晚的時間戳。通過Paxos記錄。每一個相關的都要給coordinator傳送他自己準備的那個時間戳。

Coordinatorleader一開始也會上個寫鎖,當大家傳送時間戳給他之後,他就選擇一個提交時間戳。這個提交的時間戳,必須比剛剛的所有時間戳晚,而且還要比TT.now()+誤差時間 還有晚。這個Coordinator將這個資訊記錄到Paxos。

在讓replica寫入資料生效之前,coordinator還有再等一會。需要等兩倍時間誤差。這段時間也剛好讓Paxos來同步。因為等待之後,在任意機器上發起的下一個事務的開始時間,都比如不會比這個事務的結束時間早了。然後coordinator將提交時間戳傳送給客戶端還有其他的replica。他們記錄日誌,寫入生效,釋放鎖。

 

只讀事務

對於只讀事務,Spanner首先要指定一個讀事務時間戳。還需要了解在這個讀操作中,需要訪問的所有的讀的Key。Spanner可以自動確定Key的範圍。

如果Key的範圍在一個Paxos group內。客戶端可以發起一個只讀請求給group leader。leader選一個時間戳,這個時間戳要比上一個事務的結束時間要大。然後讀取相應的資料。這個事務可以滿足外部一致性,讀出的結果是最後一次寫的結果,並且不會有不一致的資料。

如果Key的範圍在多個Paxos group內,就相對複雜一些。其中一個比較複雜的例子是,可以遍歷所有的group leaders,尋找最近的事務發生的時間,並讀取。客戶端只要時間戳在TT.now().latest之後就可以滿足要求了。

 

最後的話

本文介紹了GoogleSpanner的背景,設計和併發控制。希望不久的將來,會有開源產品出現。

相關文章