zz from: http://blog.sina.com.cn/s/blog_4a1f59bf01017xiv.htmlhtml
ZZ From http://qing.weibo.com/2294942122/88ca09aa3300221n.html
Google Spanner原理- 全球級的分佈式數據庫算法
Google Spanner簡介數據庫
Spanner 是Google的全球級的分佈式數據庫 (Globally-Distributed Database) 。Spanner的擴展性達到了使人咋舌的全球級,能夠擴展到數百萬的機器,數已百計的數據中心,上萬億的行。更給力的是,除了誇張的擴展性以外,他還能 同時經過同步複製和多版原本知足外部一致性,可用性也是很好的。衝破CAP的枷鎖,在三者之間完美平衡。緩存
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的定位。oop
從上圖能夠看到。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是著名的分佈式文件系統。
初代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部署實例。
如圖所示。一個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很是的類似。參照下圖
從下往上看。每一個數據中心會運行一套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。但這樣的設計對應用也是頗有裨益的,應用能夠經過主鍵來定位到某一行。
上圖是一個例子。對於一個典型的相冊應用,須要存儲其用戶和相冊。能夠用上面的兩個SQL來建立表。Spanner的表是層次化 的,最頂層的表是directory table。其餘的表建立的時候,能夠用interleave in parent來什麼層次關係。這樣的結構,在實現的時候,Spanner能夠將嵌套的數據放在一塊兒,這樣在分區的時候性能會提高不少。不然Spanner 沒法獲知最重要的表之間的關係。
TrueTime
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快照讀均可以讀到寫入的值。
上表是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的背景,設計和併發控制。但願不久的未來,會有開源產品出現。