天天看點

Google Spanner原理:地球上最大的單一資料庫

Google Spanner原理:地球上最大的單一資料庫

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的背景,設計和并發控制。希望不久的将來,會有開源産品出現。

原文釋出時間為:2014-04-17

本文來自雲栖社群合作夥伴“大資料文摘”,了解相關資訊可以關注“bigdatadigest”微信公衆号