1. 程式人生 > >谷歌三大核心技術(三)Google BigTable中文版

谷歌三大核心技術(三)Google BigTable中文版

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

Bigtable:一個分散式的結構化資料儲存系統

譯者:alex  


摘要

Bigtable是一個分散式的結構化資料儲存系統,它被設計用來處理海量資料:通常是分佈在數千臺普通伺服器上的PB級的資料。Google的很多專案使用Bigtable儲存資料,包括Web索引、Google Earth、Google Finance。這些應用對Bigtable提出的要求差異非常大,無論是在資料量上(從URL到網頁到衛星影象)還是在響應速度上(從後端的批量處理到實時資料服務)。儘管應用需求差異很大,但是,針對Google的這些產品,Bigtable還是成功的提供了一個靈活的、高效能的解決方案。本論文描述了Bigtable提供的簡單的資料模型,利用這個模型,使用者可以動態的控制資料的分佈和格式;我們還將描述Bigtable的設計和實現。

1 介紹

在過去兩年半時間裡,我們設計、實現並部署了一個分散式的結構化資料儲存系統 — 在Google,我們稱之為Bigtable。Bigtable的設計目的是可靠的處理PB級別的資料,並且能夠部署到上千臺機器上。Bigtable已經實現了下面的幾個目標:適用性廣泛、可擴充套件、高效能和高可用性。Bigtable已經在超過60個Google的產品和專案上得到了應用,包括Google Analytics、Google Finance、Orkut、PersonalizedSearch、Writely和Google Earth。這些產品對Bigtable提出了迥異的需求,有的需要高吞吐量的批處理,有的則需要及時響應,快速返回資料給終端使用者。它們使用的Bigtable叢集的配置也有很大的差異,有的叢集只有幾臺伺服器,而有的則需要上千臺伺服器、儲存幾百TB的資料。

在很多方面,Bigtable和資料庫很類似:它使用了很多資料庫的實現策略。並行資料庫【14】和記憶體資料庫【13】已經具備可擴充套件性和高效能,但是Bigtable提供了一個和這些系統完全不同的介面。Bigtable不支援完整的關係資料模型;與之相反,Bigtable為客戶提供了簡單的資料模型,利用這個模型,客戶可以動態控制資料的分佈和格式alex注:也就是對BigTable而言,資料是沒有格式的,用資料庫領域的術語說,就是資料沒有Schema,使用者自己去定義Schema),使用者也可以自己推測(alex注:reasonabout)底層儲存資料的位置相關性(alex注:位置相關性可以這樣理解,比如樹狀結構,具有相同字首的資料的存放位置接近。在讀取的時候,可以把這些資料一次讀取出來)。資料的下標是行和列的名字,名字可以是任意的字串。Bigtable將儲存的資料都視為字串,但是Bigtable本身不去解析這些字串,客戶程式通常會在把各種結構化或者半結構化的資料序列化到這些字串裡。通過仔細選擇資料的模式,客戶可以控制資料的位置相關性。最後,可以通過BigTable的模式引數來控制資料是存放在記憶體中、還是硬碟上。

第二節描述關於資料模型更多細節方面的東西;第三節概要介紹了客戶端API;第四節簡要介紹了BigTable底層使用的Google的基礎框架;第五節描述了BigTable實現的關鍵部分;第6節描述了我們為了提高BigTable的效能採用的一些精細的調優方法;第7節提供了BigTable的效能資料;第8節講述了幾個Google內部使用BigTable的例子;第9節是我們在設計和後期支援過程中得到一些經驗和教訓;最後,在第10節列出我們的相關研究工作,第11節是我們的結論。

2 資料模型

Bigtable是一個稀疏的、分散式的、持久化儲存的多維度排序Map(alex注:對於程式設計師來說,Map應該不用翻譯了吧。Map由key和value組成,後面我們直接使用key和value,不再另外翻譯了)。Map的索引是行關鍵字、列關鍵字以及時間戳;Map中的每個value都是一個未經解析的byte陣列。

 

(row:string,column:string,time:int64)->string

 

我們在仔細分析了一個類似Bigtable的系統的種種潛在用途之後,決定使用這個資料模型。我們先舉個具體的例子,這個例子促使我們做了很多設計決策;假設我們想要儲存海量的網頁及相關資訊,這些資料可以用於很多不同的專案,我們姑且稱這個特殊的表為Webtable。在Webtable裡,我們使用URL作為行關鍵字,使用網頁的某些屬性作為列名,網頁的內容存在“contents:”列中,並用獲取該網頁的時間戳作為標識(alex注:即按照獲取時間不同,儲存了多個版本的網頁資料),如圖一所示。


圖一:一個儲存Web網頁的例子的表的片斷。行名是一個反向URL。contents列族存放的是網頁的內容,anchor列族存放引用該網頁的錨鏈接文字(alex注:如果不知道HTML的Anchor,請Google一把)。CNN的主頁被Sports Illustrater和MY-look的主頁引用,因此該行包含了名為“anchor:cnnsi.com”和 “anchhor:my.look.ca”的列。每個錨鏈接只有一個版本(alex注:注意時間戳標識了列的版本,t9和t8分別標識了兩個錨鏈接的版本);而contents列則有三個版本,分別由時間戳t3,t5,和t6標識。

表中的行關鍵字可以是任意的字串(目前支援最大64KB的字串,但是對大多數使用者,10-100個位元組就足夠了)。對同一個行關鍵字的讀或者寫操作都是原子的(不管讀或者寫這一行裡多少個不同列),這個設計決策能夠使使用者很容易的理解程式在對同一個行進行併發更新操作時的行為。

Bigtable通過行關鍵字的字典順序來組織資料。表中的每個行都可以動態分割槽。每個分割槽叫做一個”Tablet”,Tablet是資料分佈和負載均衡調整的最小單位。這樣做的結果是,當操作只讀取行中很少幾列的資料時效率很高,通常只需要很少幾次機器間的通訊即可完成。使用者可以通過選擇合適的行關鍵字,在資料訪問時有效利用資料的位置相關性,從而更好的利用這個特性。舉例來說,在Webtable裡,通過反轉URL中主機名的方式,可以把同一個域名下的網頁聚集起來組織成連續的行。具體來說,我們可以把maps.google.com/index.html的資料存放在關鍵字com.google.maps/index.html下。把相同的域中的網頁儲存在連續的區域可以讓基於主機和域名的分析更加有效。

列族

列關鍵字組成的集合叫做“列族“,列族是訪問控制的基本單位。存放在同一列族下的所有資料通常都屬於同一個型別(我們可以把同一個列族下的資料壓縮在一起)。列族在使用之前必須先建立,然後才能在列族中任何的列關鍵字下存放資料;列族建立後,其中的任何一個列關鍵字下都可以存放資料。根據我們的設計意圖,一張表中的列族不能太多(最多幾百個),並且列族在執行期間很少改變。與之相對應的,一張表可以有無限多個列。

列關鍵字的命名語法如下:列族:限定詞。列族的名字必須是可列印的字串,而限定詞的名字可以是任意的字串。比如,Webtable有個列族language,language列族用來存放撰寫網頁的語言。我們在language列族中只使用一個列關鍵字,用來存放每個網頁的語言標識ID。Webtable中另一個有用的列族是anchor;這個列族的每一個列關鍵字代表一個錨鏈接,如圖一所示。Anchor列族的限定詞是引用該網頁的站點名;Anchor列族每列的資料項存放的是連結文字。

訪問控制、磁碟和記憶體的使用統計都是在列族層面進行的。在我們的Webtable的例子中,上述的控制權限能幫助我們管理不同型別的應用:我們允許一些應用可以新增新的基本資料、一些應用可以讀取基本資料並建立繼承的列族、一些應用則只允許瀏覽資料(甚至可能因為隱私的原因不能瀏覽所有資料)。

時間戳

在Bigtable中,表的每一個數據項都可以包含同一份資料的不同版本;不同版本的資料通過時間戳來索引。Bigtable時間戳的型別是64位整型。Bigtable可以給時間戳賦值,用來表示精確到毫秒的“實時”時間;使用者程式也可以給時間戳賦值。如果應用程式需要避免資料版本衝突,那麼它必須自己生成具有唯一性的時間戳。資料項中,不同版本的資料按照時間戳倒序排序,即最新的資料排在最前面。

為了減輕多個版本資料的管理負擔,我們對每一個列族配有兩個設定引數,Bigtable通過這兩個引數可以對廢棄版本的資料自動進行垃圾收集。使用者可以指定只儲存最後n個版本的資料,或者只儲存“足夠新”的版本的資料(比如,只儲存最近7天的內容寫入的資料)。

在Webtable的舉例裡,contents:列儲存的時間戳資訊是網路爬蟲抓取一個頁面的時間。上面提及的垃圾收集機制可以讓我們只保留最近三個版本的網頁資料。

3 API

Bigtable提供了建立和刪除表以及列族的API函式。Bigtable還提供了修改叢集、表和列族的元資料的API,比如修改訪問許可權。

 // Open the table

Table *T = OpenOrDie(“/bigtable/web/webtable”);

// Write a new anchor and delete an old anchor

RowMutation r1(T, “com.cnn.www”);

r1.Set(“anchor:www.c-span.org”, “CNN”);

r1.Delete(“anchor:www.abc.com”);

Operation op;

Apply(&op, &r1)

Figure 2: Writing to Bigtable.

 

客戶程式可以對Bigtable進行如下的操作:寫入或者刪除Bigtable中的值、從每個行中查詢值、或者遍歷表中的一個數據子集。圖2中的C++程式碼使用RowMutation抽象物件進行了一系列的更新操作。(為了保持示例程式碼的簡潔,我們忽略了一些細節相關程式碼)。呼叫Apply函式對Webtable進行了一個原子修改操作:它為www.cnn.com增加了一個錨點,同時刪除了另外一個錨點。

 Scanner scanner(T);

ScanStream *stream;

stream = scanner.FetchColumnFamily(“anchor”);

stream->SetReturnAllVersions();

scanner.Lookup(“com.cnn.www”);

for (; !stream->Done(); stream->Next()) {

    printf(“%s %s %lld %s\n”,

    scanner.RowName(),

    stream->ColumnName(),

    stream->MicroTimestamp(),

    stream->Value());

Figure3: Reading from Bigtable.

 

圖3中的C++程式碼使用Scanner抽象物件遍歷一個行內的所有錨點。客戶程式可以遍歷多個列族,有幾種方法可以對掃描輸出的行、列和時間戳進行限制。例如,我們可以限制上面的掃描,讓它只輸出那些匹配正則表示式*.cnn.com的錨點,或者那些時間戳在當前時間前10天的錨點。

Bigtable還支援一些其它的特性,利用這些特性,使用者可以對資料進行更復雜的處理。首先,Bigtable支援單行上的事務處理,利用這個功能,使用者可以對儲存在一個行關鍵字下的資料進行原子性的讀-更新-寫操作。雖然Bigtable提供了一個允許使用者跨行批量寫入資料的介面,但是,Bigtable目前還不支援通用的跨行事務處理。其次,Bigtable允許把資料項用做整數計數器。最後,Bigtable允許使用者在伺服器的地址空間內執行指令碼程式。指令碼程式使用Google開發的Sawzall【28】資料處理語言。雖然目前我們基於的Sawzall語言的API函式還不允許客戶的指令碼程式寫入資料到Bigtable,但是它允許多種形式的資料轉換、基於任意表達式的資料過濾、以及使用多種操作符的進行資料彙總。

Bigtable可以和MapReduce【12】一起使用,MapReduce是Google開發的大規模平行計算框架。我們已經開發了一些Wrapper類,通過使用這些Wrapper類,Bigtable可以作為MapReduce框架的輸入和輸出。

4 BigTable構件

Bigtable是建立在其它的幾個Google基礎構件上的。BigTable使用Google的分散式檔案系統(GFS)【17】儲存日誌檔案和資料檔案。BigTable叢集通常執行在一個共享的機器池中,池中的機器還會執行其它的各種各樣的分散式應用程式,BigTable的程序經常要和其它應用的程序共享機器。BigTable依賴叢集管理系統來排程任務、管理共享的機器上的資源、處理機器的故障、以及監視機器的狀態。

BigTable內部儲存資料的檔案是GoogleSSTable格式的。SSTable是一個持久化的、排序的、不可更改的Map結構,而Map是一個key-value對映的資料結構,key和value的值都是任意的Byte串。可以對SSTable進行如下的操作:查詢與一個key值相關的value,或者遍歷某個key值範圍內的所有的key-value對。從內部看,SSTable是一系列的資料塊(通常每個塊的大小是64KB,這個大小是可以配置的)。SSTable使用塊索引(通常儲存在SSTable的最後)來定位資料塊;在開啟SSTable的時候,索引被載入到記憶體。每次查詢都可以通過一次磁碟搜尋完成:首先使用二分查詢法在記憶體中的索引裡找到資料塊的位置,然後再從硬碟讀取相應的資料塊。也可以選擇把整個SSTable都放在記憶體中,這樣就不必訪問硬碟了。

BigTable還依賴一個高可用的、序列化的分散式鎖服務元件,叫做Chubby【8】。一個Chubby服務包括了5個活動的副本,其中的一個副本被選為Master,並且處理請求。只有在大多數副本都是正常執行的,並且彼此之間能夠互相通訊的情況下,Chubby服務才是可用的。當有副本失效的時候,Chubby使用Paxos演算法【9,23】來保證副本的一致性。Chubby提供了一個名字空間,裡面包括了目錄和小檔案。每個目錄或者檔案可以當成一個鎖,讀寫檔案的操作都是原子的。Chubby客戶程式庫提供對Chubby檔案的一致性快取。每個Chubby客戶程式都維護一個與Chubby服務的會話。如果客戶程式不能在租約到期的時間內重新簽訂會話的租約,這個會話就過期失效了(alex注:又用到了lease。原文是:Aclient’s session expires if it is unable to renew its session lease within the leaseexpiration time.)。當一個會話失效時,它擁有的鎖和開啟的檔案控制代碼都失效了。Chubby客戶程式可以在檔案和目錄上註冊回撥函式,當檔案或目錄改變、或者會話過期時,回撥函式會通知客戶程式。

Bigtable使用Chubby完成以下的幾個任務:確保在任何給定的時間內最多隻有一個活動的Master副本;儲存BigTable資料的自引導指令的位置(參考5.1節);查詢Tablet伺服器,以及在Tablet伺服器失效時進行善後(5.2節);儲存BigTable的模式資訊(每張表的列族資訊);以及儲存訪問控制列表。如果Chubby長時間無法訪問,BigTable就會失效。最近我們在使用11個Chubby服務例項的14個BigTable叢集上測量了這個影響。由於Chubby不可用而導致BigTable中的部分資料不能訪問的平均比率是0.0047%(Chubby不能訪問的原因可能是Chubby本身失效或者網路問題)。單個叢集裡,受Chubby失效影響最大的百分比是0.0326%(alex注:有點莫名其妙,原文是: The percentage for the single cluster that was most affected byChubby unavailability was 0.0326%.)。

5 介紹

Bigtable包括了三個主要的元件:連結到客戶程式中的庫、一個Master伺服器和多個Tablet伺服器。針對系統工作負載的變化情況,BigTable可以動態的向叢集中新增(或者刪除)Tablet伺服器。

Master伺服器主要負責以下工作:為Tablet伺服器分配Tablets、檢測新加入的或者過期失效的Table伺服器、對Tablet伺服器進行負載均衡、以及對儲存在GFS上的檔案進行垃圾收集。除此之外,它還處理對模式的相關修改操作,例如建立表和列族。

每個Tablet伺服器都管理一個Tablet的集合(通常每個伺服器有大約數十個至上千個Tablet)。每個Tablet伺服器負責處理它所載入的Tablet的讀寫操作,以及在Tablets過大時,對其進行分割。

和很多Single-Master型別的分散式儲存系統【17.21】類似,客戶端讀取的資料都不經過Master伺服器:客戶程式直接和Tablet伺服器通訊進行讀寫操作。由於BigTable的客戶程式不必通過Master伺服器來獲取Tablet的位置資訊,因此,大多數客戶程式甚至完全不需要和Master伺服器通訊。在實際應用中,Master伺服器的負載是很輕的。

一個BigTable叢集儲存了很多表,每個表包含了一個Tablet的集合,而每個Tablet包含了某個範圍內的行的所有相關資料。初始狀態下,一個表只有一個Tablet。隨著表中資料的增長,它被自動分割成多個Tablet,預設情況下,每個Tablet的尺寸大約是100MB到200MB。

5.1Tablet的位置

我們使用一個三層的、類似B+樹[10]的結構儲存Tablet的位置資訊(如圖4)。


第一層是一個儲存在Chubby中的檔案,它包含了Root Tablet的位置資訊。Root Tablet包含了一個特殊的METADATA表裡所有的Tablet的位置資訊。METADATA表的每個Tablet包含了一個使用者Tablet的集合。RootTablet實際上是METADATA表的第一個Tablet,只不過對它的處理比較特殊 — Root Tablet永遠不會被分割 — 這就保證了Tablet的位置資訊儲存結構不會超過三層。

在METADATA表裡面,每個Tablet的位置資訊都存放在一個行關鍵字下面,而這個行關鍵字是由Tablet所在的表的識別符號和Tablet的最後一行編碼而成的。METADATA的每一行都儲存了大約1KB的記憶體資料。在一個大小適中的、容量限制為128MB的METADATA Tablet中,採用這種三層結構的儲存模式,可以標識2^34個Tablet的地址(如果每個Tablet儲存128MB資料,那麼一共可以儲存2^61位元組資料)。

客戶程式使用的庫會快取Tablet的位置資訊。如果客戶程式沒有快取某個Tablet的地址資訊,或者發現它快取的地址資訊不正確,客戶程式就在樹狀的儲存結構中遞迴的查詢Tablet位置資訊;如果客戶端快取是空的,那麼定址演算法需要通過三次網路來回通訊定址,這其中包括了一次Chubby讀操作;如果客戶端快取的地址資訊過期了,那麼定址演算法可能需要最多6次網路來回通訊才能更新資料,因為只有在快取中沒有查到資料的時候才能發現數據過期(alex注:其中的三次通訊發現快取過期,另外三次更新快取資料)(假設METADATA的Tablet沒有被頻繁的移動)。儘管Tablet的地址資訊是存放在記憶體裡的,對它的操作不必訪問GFS檔案系統,但是,通常我們會通過預取Tablet地址來進一步的減少訪問的開銷:每次需要從METADATA表中讀取一個Tablet的元資料的時候,它都會多讀取幾個Tablet的元資料。

在METADATA表中還儲存了次級資訊(alex注:secondary information),包括每個Tablet的事件日誌(例如,什麼時候一個伺服器開始為該Tablet提供服務)。這些資訊有助於排查錯誤和效能分析。

5.2Tablet分配

在任何一個時刻,一個Tablet只能分配給一個Tablet伺服器。Master伺服器記錄了當前有哪些活躍的Tablet伺服器、哪些Tablet分配給了哪些Tablet伺服器、哪些Tablet還沒有被分配。當一個Tablet還沒有被分配、並且剛好有一個Tablet伺服器有足夠的空閒空間裝載該Tablet時,Master伺服器會給這個Tablet伺服器傳送一個裝載請求,把Tablet分配給這個伺服器。

BigTable使用Chubby跟蹤記錄Tablet伺服器的狀態。當一個Tablet伺服器啟動時,它在Chubby的一個指定目錄下建立一個有唯一性名字的檔案,並且獲取該檔案的獨佔鎖。Master伺服器實時監控著這個目錄(伺服器目錄),因此Master伺服器能夠知道有新的Tablet伺服器加入了。如果Tablet伺服器丟失了Chubby上的獨佔鎖 — 比如由於網路斷開導致Tablet伺服器和Chubby的會話丟失 — 它就停止對Tablet提供服務。(Chubby提供了一種高效的機制,利用這種機制,Tablet伺服器能夠在不增加網路負擔的情況下知道它是否還持有鎖)。只要檔案還存在,Tablet伺服器就會試圖重新獲得對該檔案的獨佔鎖;如果檔案不存在了,那麼Tablet伺服器就不能再提供服務了,它會自行退出(alex注:so it killsitself)。當Tablet伺服器終止時(比如,叢集的管理系統將執行該Tablet伺服器的主機從叢集中移除),它會嘗試釋放它持有的檔案鎖,這樣一來,Master伺服器就能儘快把Tablet分配到其它的Tablet伺服器。

Master伺服器負責檢查一個Tablet伺服器是否已經不再為它的Tablet提供服務了,並且要儘快重新分配它載入的Tablet。Master伺服器通過輪詢Tablet伺服器檔案鎖的狀態來檢測何時Tablet伺服器不再為Tablet提供服務。如果一個Tablet伺服器報告它丟失了檔案鎖,或者Master伺服器最近幾次嘗試和它通訊都沒有得到響應,Master伺服器就會嘗試獲取該Tablet伺服器檔案的獨佔鎖;如果Master伺服器成功獲取了獨佔鎖,那麼就說明Chubby是正常執行的,而Tablet伺服器要麼是宕機了、要麼是不能和Chubby通訊了,因此,Master伺服器就刪除該Tablet伺服器在Chubby上的伺服器檔案以確保它不再給Tablet提供服務。一旦Tablet伺服器在Chubby上的伺服器檔案被刪除了,Master伺服器就把之前分配給它的所有的Tablet放入未分配的Tablet集合中。為了確保Bigtable叢集在Master伺服器和Chubby之間網路出現故障的時候仍然可以使用,Master伺服器在它的Chubby會話過期後主動退出。但是不管怎樣,如同我們前面所描述的,Master伺服器的故障不會改變現有Tablet在Tablet伺服器上的分配狀態。

當叢集管理系統啟動了一個Master伺服器之後,Master伺服器首先要了解當前Tablet的分配狀態,之後才能夠修改分配狀態。Master伺服器在啟動的時候執行以下步驟:(1)Master伺服器從Chubby獲取一個唯一的Master鎖,用來阻止建立其它的Master伺服器例項;(2)Master伺服器掃描Chubby的伺服器檔案鎖儲存目錄,獲取當前正在執行的伺服器列表;(3)Master伺服器和所有的正在執行的Tablet表伺服器通訊,獲取每個Tablet伺服器上Tablet的分配資訊;(4)Master伺服器掃描METADATA表獲取所有的Tablet的集合。在掃描的過程中,當Master伺服器發現了一個還沒有分配的Tablet,Master伺服器就將這個Tablet加入未分配的Tablet集合等待合適的時機分配。

可能會遇到一種複雜的情況:在METADATA表的Tablet還沒有被分配之前是不能夠掃描它的。因此,在開始掃描之前(步驟4),如果在第三步的掃描過程中發現Root Tablet還沒有分配,Master伺服器就把Root Tablet加入到未分配的Tablet集合。這個附加操作確保了Root Tablet會被分配。由於Root Tablet包括了所有METADATA的Tablet的名字,因此Master伺服器掃描完Root Tablet以後,就得到了所有的METADATA表的Tablet的名字了。

儲存現有Tablet的集合只有在以下事件發生時才會改變:建立了一個新表或者刪除了一箇舊表、兩個Tablet被合併了、或者一個Tablet被分割成兩個小的Tablet。Master伺服器可以跟蹤記錄所有這些事件,因為除了最後一個事件外的兩個事件都是由它啟動的。Tablet分割事件需要特殊處理,因為它是由Tablet伺服器啟動。在分割操作完成之後,Tablet伺服器通過在METADATA表中記錄新的Tablet的資訊來提交這個操作;當分割操作提交之後,Tablet伺服器會通知Master伺服器。如果分割操作已提交的資訊沒有通知到Master伺服器(可能兩個伺服器中有一個宕機了),Master伺服器在要求Tablet伺服器裝載已經被分割的子表的時候會發現一個新的Tablet。通過對比METADATA表中Tablet的資訊,Tablet伺服器會發現Master伺服器要求其裝載的Tablet並不完整,因此,Tablet伺服器會重新向Master伺服器傳送通知資訊。

5.3Tablet服務


如圖5所示,Tablet的持久化狀態資訊儲存在GFS上。更新操作提交到REDO日誌中(alex注:Updates are committed to a commit log that stores redo records)。在這些更新操作中,最近提交的那些存放在一個排序的快取中,我們稱這個快取為memtable;較早的更新存放在一系列SSTable中。為了恢復一個Tablet,Tablet伺服器首先從METADATA表中讀取它的元資料。Tablet的元資料包含了組成這個Tablet的SSTable的列表,以及一系列的Redo Point(alex注:a set of redo points),這些Redo Point指向可能含有該Tablet資料的已提交的日誌記錄。Tablet伺服器把SSTable的索引讀進記憶體,之後通過重複Redo Point之後提交的更新來重建memtable。

當對Tablet伺服器進行寫操作時,Tablet伺服器首先要檢查這個操作格式是否正確、操作發起者是否有執行這個操作的許可權。許可權驗證的方法是通過從一個Chubby檔案裡讀取出來的具有寫許可權的操作者列表來進行驗證(這個檔案幾乎一定會存放在Chubby客戶快取裡)。成功的修改操作會記錄在提交日誌裡。可以採用批量提交方式(alex注:group commit)來提高包含大量小的修改操作的應用程式的吞吐量【13,16】。當一個寫操作提交後,寫的內容插入到memtable裡面。

當對Tablet伺服器進行讀操作時,Tablet伺服器會作類似的完整性和許可權檢查。一個有效的讀操作在一個由一系列SSTable和memtable合併的視圖裡執行。由於SSTable和memtable是按字典排序的資料結構,因此可以高效生成合並檢視。

當進行Tablet的合併和分割時,正在進行的讀寫操作能夠繼續進行。

5.4Compactions

(alex注:這個詞挺簡單,但是在這節裡面挺難翻譯的。應該是空間縮減的意思,但是似乎又不能完全概括它在上下文中的意思,乾脆,不翻譯了)

隨著寫操作的執行,memtable的大小不斷增加。當memtable的尺寸到達一個門限值的時候,這個memtable就會被凍結,然後建立一個新的memtable;被凍結住memtable會被轉換成SSTable,然後寫入GFS(alex注:我們稱這種Compaction行為為Minor Compaction)。MinorCompaction過程有兩個目的:shrink(alex注:shrink是資料庫用語,表示空間收縮)Tablet伺服器使用的記憶體,以及在伺服器災難恢復過程中,減少必須從提交日誌裡讀取的資料量。在Compaction過程中,正在進行的讀寫操作仍能繼續。

每一次Minor Compaction都會建立一個新的SSTable。如果Minor Compaction過程不停滯的持續進行下去,讀操作可能需要合併來自多個SSTable的更新;否則,我們通過定期在後臺執行Merging Compaction過程合併檔案,限制這類檔案的數量。Merging Compaction過程讀取一些SSTable和memtable的內容,合併成一個新的SSTable。只要Merging Compaction過程完成了,輸入的這些SSTable和memtable就可以刪除了。

合併所有的SSTable並生成一個新的SSTable的Merging Compaction過程叫作Major Compaction。由非Major Compaction產生的SSTable可能含有特殊的刪除條目,這些刪除條目能夠隱藏在舊的、但是依然有效的SSTable中已經刪除的資料(alex注:令人費解啊,原文是SSTables produced by non-major compactions can contain specialdeletion entries that suppress deleted data in older SSTables that are stilllive)。而MajorCompaction過程生成的SSTable不包含已經刪除的資訊或資料。Bigtable迴圈掃描它所有的Tablet,並且定期對它們執行Major Compaction。Major Compaction機制允許Bigtable回收已經刪除的資料佔有的資源,並且確保BigTable能及時清除已經刪除的資料(alex注:實際是回收資源。資料刪除後,它佔有的空間並不能馬上重複利用;只有空間回收後才能重複使用),這對存放敏感資料的服務是非常重要。

6 優化

上一章我們描述了Bigtable的實現,我們還需要很多優化工作才能使Bigtable到達使用者要求的高效能、高可用性和高可靠性。本章描述了Bigtable實現的其它部分,為了更好的強調這些優化工作,我們將深入細節。

區域性性群組

客戶程式可以將多個列族組合成一個區域性性群族。對Tablet中的每個區域性性群組都會生成一個單獨的SSTable。將通常不會一起訪問的列族分割成不同的區域性性群組可以提高讀取操作的效率。例如,在Webtable表中,網頁的元資料(比如語言和Checksum)可以在一個區域性性群組中,網頁的內容可以在另外一個群組:當一個應用程式要讀取網頁的元資料的時候,它沒有必要去讀取所有的頁面內容。

此外,可以以區域性性群組為單位設定一些有用的除錯引數。比如,可以把一個區域性性群組設定為全部儲存在記憶體中。Tablet伺服器依照惰性載入的策略將設定為放入記憶體的區域性性群組的SSTable裝載進記憶體。載入完成之後,訪問屬於該區域性性群組的列族的時候就不必讀取硬碟了。這個特性對於需要頻繁訪問的小塊資料特別有用:在Bigtable內部,我們利用這個特性提高METADATA表中具有位置相關性的列族的訪問速度。

壓縮

客戶程式可以控制一個區域性性群組的SSTable是否需要壓縮;如果需要壓縮,那麼以什麼格式來壓縮。每個SSTable的塊(塊的大小由區域性性群組的優化引數指定)都使用使用者指定的壓縮格式來壓縮。雖然分塊壓縮浪費了少量空間(alex注:相比於對整個SSTable進行壓縮,分塊壓縮壓縮率較低),但是,我們在只讀取SSTable的一小部分資料的時候就不必解壓整個檔案了。很多客戶程式使用了“兩遍”的、可定製的壓縮方式。第一遍採用Bentleyand McIlroy’s方式[6],這種方式在一個很大的掃描窗口裡對常見的長字串進行壓縮;第二遍是採用快速壓縮演算法,即在一個16KB的小掃描視窗中尋找重複資料。兩個壓縮的演算法都很快,在現在的機器上,壓縮的速率達到100-200MB/s,解壓的速率達到400-1000MB/s。

雖然我們在選擇壓縮演算法的時候重點考慮的是速度而不是壓縮的空間,但是這種兩遍的壓縮方式在空間壓縮率上的表現也是令人驚歎。比如,在Webtable的例子裡,我們使用這種壓縮方式來儲存網頁內容。在一次測試中,我們在一個壓縮的區域性性群組中儲存了大量的網頁。針對實驗的目的,我們沒有儲存每個文件所有版本的資料,我們僅僅儲存了一個版本的資料。該模式的空間壓縮比達到了10:1。這比傳統的Gzip在壓縮HTML頁面時3:1或者4:1的空間壓縮比好的多;“兩遍”的壓縮模式如此高效的原因是由於Webtable的行的存放方式:從同一個主機獲取的頁面都存在臨近的地方。利用這個特性,Bentley-McIlroy演算法可以從來自同一個主機的頁面裡找到大量的重複內容。不僅僅是Webtable,其它的很多應用程式也通過選擇合適的行名來將相似的資料聚簇在一起,以獲取較高的壓縮率。當我們在Bigtable中儲存同一份資料的多個版本的時候,壓縮效率會更高。

通過快取提高讀操作的效能

為了提高讀操作的效能,Tablet伺服器使用二級快取的策略。掃描快取是第一級快取,主要快取Tablet伺服器通過SSTable介面獲取的Key-Value對;Block快取是二級快取,快取的是從GFS讀取的SSTable的Block。對於經常要重複讀取相同資料的應用程式來說,掃描快取非常有效;對於經常要讀取剛剛讀過的資料附近的資料的應用程式來說,Block快取更有用(例如,順序讀,或者在一個熱點的行的區域性性群組中隨機讀取不同的列)。

Bloom過濾器

(alex注:Bloom,又叫布隆過濾器,什麼意思?請參考Google黑板報http://googlechinablog.com/2007/07/bloom-filter.html請務必先認真閱讀)

如5.3節所述,一個讀操作必須讀取構成Tablet狀態的所有SSTable的資料。如果這些SSTable不在記憶體中,那麼就需要多次訪問硬碟。我們通過允許客戶程式對特定區域性性群組的SSTable指定Bloom過濾器【7】,來減少硬碟訪問的次數。我們可以使用Bloom過濾器查詢一個SSTable是否包含了特定行和列的資料。對於某些特定應用程式,我們只付出了少量的、用於儲存Bloom過濾器的記憶體的代價,就換來了讀操作顯著減少的磁碟訪問的次數。使用Bloom過濾器也隱式的達到了當應用程式訪問不存在的行或列時,大多數時候我們都不需要訪問硬碟的目的。

Commit日誌的實現

如果我們把對每個Tablet的操作的Commit日誌都存在一個單獨的檔案的話,那麼就會產生大量的檔案,並且這些檔案會並行的寫入GFS。根據GFS伺服器底層檔案系統實現的方案,要把這些檔案寫入不同的磁碟日誌檔案時(alex注:differentphysicallog files),會有大量的磁碟Seek操作。另外,由於批量提交(alex注:groupcommit)中操作的數目一般比較少,因此,對每個Tablet設定單獨的日誌檔案也會給批量提交本應具有的優化效果帶來很大的負面影響。為了避免這些問題,我們設定每個Tablet伺服器一個Commit日誌檔案,把修改操作的日誌以追加方式寫入同一個日誌檔案,因此一個實際的日誌檔案中混合了對多個Tablet修改的日誌記錄。

使用單個日誌顯著提高了普通操作的效能,但是將恢復的工作複雜化了。當一個Tablet伺服器宕機時,它載入的Tablet將會被移到很多其它的Tablet伺服器上:每個Tablet伺服器都裝載很少的幾個原來的伺服器的Tablet。當恢復一個Tablet的狀態的時候,新的Tablet伺服器要從原來的Tablet伺服器寫的日誌中提取修改操作的資訊,並重新執行。然而,這些Tablet修改操作的日誌記錄都混合在同一個日誌檔案中的。一種方法新的Tablet伺服器讀取完整的Commit日誌檔案,然後只重複執行它需要恢復的Tablet的相關修改操作。使用這種方法,假如有100臺Tablet伺服器,每臺都載入了失效的Tablet伺服器上的一個Tablet,那麼,這個日誌檔案就要被讀取100次(每個伺服器讀取一次)。

為了避免多次讀取日誌檔案,我們首先把日誌按照關鍵字(table,row name,log sequence number)排序。排序之後,對同一個Tablet的修改操作的日誌記錄就連續存放在了一起,因此,我們只要一次磁碟Seek操作、之後順序讀取就可以了。為了並行排序,我們先將日誌分割成64MB的段,之後在不同的Tablet伺服器對段進行並行排序。這個排序工作由Master伺服器來協同處理,並且在一個Tablet伺服器表明自己需要從Commit日誌檔案恢復Tablet時開始執行。

在向GFS中寫Commit日誌的時候可能會引起系統顛簸,原因是多種多樣的(比如,寫操作正在進行的時候,一個GFS伺服器宕機了;或者連線三個GFS副本所在的伺服器的網路擁塞或者過載了)。為了確保在GFS負載高峰時修改操作還能順利進行,每個Tablet伺服器實際上有兩個日誌寫入執行緒,每個執行緒都寫自己的日誌檔案,並且在任何時刻,只有一個執行緒是工作的。如果一個執行緒的在寫入的時候效率很低,Tablet伺服器就切換到另外一個執行緒,修改操作的日誌記錄就寫入到這個執行緒對應的日誌檔案中。每個日誌記錄都有一個序列號,因此,在恢復的時候,Tablet伺服器能夠檢測出並忽略掉那些由於執行緒切換而導致的重複的記錄。

Tablet恢復提速

當Master伺服器將一個Tablet從一個Tablet伺服器移到另外一個Tablet伺服器時,源Tablet伺服器會對這個Tablet做一次Minor Compaction。這個Compaction操作減少了Tablet伺服器的日誌檔案中沒有歸併的記錄,從而減少了恢復的時間。Compaction完成之後,該伺服器就停止為該Tablet提供服務。在解除安裝Tablet之前,源Tablet伺服器還會再做一次(通常會很快)Minor Compaction,以消除前面在一次壓縮過程中又產生的未歸併的記錄。第二次Minor Compaction完成以後,Tablet就可以被裝載到新的Tablet伺服器上了,並且不需要從日誌中進行恢復。

利用不變性

我們在使用Bigtable時,除了SSTable快取之外的其它部分產生的SSTable都是不變的,我們可以利用這一點對系統進行簡化。例如,當從SSTable讀取資料的時候,我們不必對檔案系統訪問操作進行同步。這樣一來,就可以非常高效的實現對行的並行操作。memtable是唯一一個能被讀和寫操作同時訪問的可變資料結構。為了減少在讀操作時的競爭,我們對記憶體表採用COW(Copy-on-write)機制,這樣就允許讀寫操作並行執行。

因為SSTable是不變的,因此,我們可以把永久刪除被標記為“刪除”的資料的問題,轉換成對廢棄的SSTable進行垃圾收集的問題了。每個Tablet的SSTable都在METADATA表中註冊了。Master伺服器採用“標記-刪除”的垃圾回收方式刪除SSTable集合中廢棄的SSTable【25】,METADATA表則儲存了Root SSTable的集合。

最後,SSTable的不變性使得分割Tablet的操作非常快捷。我們不必為每個分割出來的Tablet建立新的SSTable集合,而是共享原來的Tablet的SSTable集合。

7 效能評估

為了測試Bigtable的效能和可擴充套件性,我們建立了一個包括N臺Tablet伺服器的Bigtable叢集,這裡N是可變的。每臺Tablet伺服器配置了1GB的記憶體,資料寫入到一個包括1786臺機器、每臺機器有2個IDE硬碟的GFS叢集上。我們使用N臺客戶機生成工作負載測試Bigtable。(我們使用和Tablet伺服器相同數目的客戶機以確保客戶機不會成為瓶頸。)每臺客戶機配置2GZ雙核Opteron處理器,配置了足以容納所有程序工作資料集的實體記憶體,以及一張Gigabit的乙太網卡。這些機器都連入一個兩層的、樹狀的交換網路裡,在根節點上的頻寬加起來有大約100-200Gbps。所有的機器採用相同的裝置,因此,任何兩臺機器間網路來回一次的時間都小於1ms。

Tablet伺服器、Master伺服器、測試機、以及GFS伺服器都執行在同一組機器上。每臺機器都執行一個GFS的伺服器。其它的機器要麼執行Tablet伺服器、要麼執行客戶程式、要麼執行在測試過程中,使用這組機器的其它的任務啟動的程序。

R是測試過程中,Bigtable包含的不同的列關鍵字的數量。我們精心選擇R的值,保證每次基準測試對每臺Tablet伺服器讀/寫的資料量都在1GB左右。

在序列寫的基準測試中,我們使用的列關鍵字的範圍是0到R-1。這個範圍又被劃分為10N個大小相同的區間。核心排程程式把這些區間分配給N個客戶端,分配方式是:只要客戶程式處理完上一個區間的資料,排程程式就把後續的、尚未處理的區間分配給它。這種動態分配的方式有助於減少客戶機上同時執行的其它程序對效能的影響。我們在每個列關鍵字下寫入一個單獨的字串。每個字串都是隨機生成的、因此也沒有被壓縮(alex注:參考第6節的壓縮小節)。另外,不同列關鍵字下的字串也是不同的,因此也就不存在跨行的壓縮。隨機寫入基準測試採用類似的方法,除了行關鍵字在寫入前先做Hash,Hash採用按R取模的方式,這樣就保證了在整個基準測試持續的時間內,寫入的工作負載均勻的分佈在列儲存空間內。

序列讀的基準測試生成列關鍵字的方式與序列寫相同,不同於序列寫在列關鍵字下寫入字串的是,序列讀是讀取列關鍵字下的字串(這些字串由之前序列寫基準測試程式寫入)。同樣的,隨機讀的基準測試和隨機寫是類似的。

掃描基準測試和序列讀類似,但是使用的是BigTable提供的、從一個列範圍內掃描所有的value值的API。由於一次RPC呼叫就從一個Tablet伺服器取回了大量的Value值,因此,使用掃描方式的基準測試程式可以減少RPC呼叫的次數。

隨機讀(記憶體)基準測試和隨機讀類似,除了包含基準測試資料的區域性性群組被設定為“in-memory”,因此,讀操作直接從Tablet伺服器的記憶體中讀取資料,不需要從GFS讀取資料。針對這個測試,我們把每臺Tablet伺服器儲存的資料從1GB減少到100MB,這樣就可以把資料全部載入到Tablet伺服器的記憶體中了。


圖6中有兩個檢視,顯示了我們的基準測試的效能;圖中的資料和曲線是讀/寫 1000-byte value值時取得的。圖中的表格顯示了每個Tablet伺服器每秒鐘進行的操作的次數;圖中的曲線顯示了每秒種所有的Tablet伺服器上操作次數的總和。

單個Tablet伺服器的效能

我們首先分析下單個Tablet伺服器的效能。隨機讀的效能比其它操作慢一個數量級或以上(alex注:by the order of magnitude or more) 。 每個隨機讀操作都要通過網路從GFS傳輸64KB的SSTable到Tablet伺服器,而我們只使用其中大小是1000 byte的一個value值。Tablet伺服器每秒大約執行1200次讀操作,也就是每秒大約從GFS讀取75MB的資料。這個傳輸頻寬足以佔滿Tablet伺服器的CPU時間,因為其中包括了網路協議棧的消耗、SSTable解析、以及BigTable程式碼執行;這個頻寬也足以佔滿我們系統中網路的連結頻寬。大多數採用這種訪問模式BigTable應用程式會減小Block的大小,通常會減到8KB。

記憶體中的隨機讀操作速度快很多,原因是,所有1000-byte的讀操作都是從Tablet伺服器的本地記憶體中讀取資料,不需要從GFS讀取64KB的Block。

隨機和序列寫操作的效能比隨機讀要好些,原因是每個Tablet伺服器直接把寫入操作的內容追加到一個Commit日誌檔案的尾部,並且採用批量提交的方式,通過把資料以流的方式寫入到GFS來提高效能。隨機寫和序列寫在效能上沒有太大的差異,這兩種方式的寫操作實際上都是把操作內容記錄到同一個Tablet伺服器的Commit日誌檔案中。

序列讀的效能好於隨機讀,因為每取出64KB的SSTable的Block後,這些資料會快取到Block快取中,後續的64次讀操作直接從快取讀取資料。

掃描的效能更高,這是由於客戶程式每一次RPC呼叫都會返回大量的value的資料,所以,RPC呼叫的消耗基本抵消了。

效能提升

隨著我們將系統中的Tablet伺服器從1臺增加到500臺,系統的整體吞吐量有了夢幻般的增長,增長的倍率超過了100。比如,隨著Tablet伺服器的數量增加了500倍,記憶體中的隨機讀操作的效能增加了300倍。之所以會有這樣的效能提升,主要是因為這個基準測試的瓶頸是單臺Tablet伺服器的CPU。

儘管如此,效能的提升還不是線性的。在大多數的基準測試中我們看到,當Tablet伺服器的數量從1臺增加到50臺時,每臺伺服器的吞吐量會有一個明顯的下降。這是由於多臺伺服器間的負載不均衡造成的,大多數情況下是由於其它的程式搶佔了CPU。我們負載均衡的演算法會盡量避免這種不均衡,但是基於兩個主要原因,這個演算法並不能完美的工作:一個是儘量減少Tablet的移動導致重新負載均衡能力受限(如果Tablet被移動了,那麼在短時間內 — 一般是1秒內 — 這個Tablet是不可用的),另一個是我們的基準測試程式產生的負載會有波動(alex注:the load generated by our benchmarks shifts around as the benchmarkprogresses)

隨機讀基準測試的測試結果顯示,隨機讀的效能隨Tablet伺服器數量增加的提升幅度最小(整體吞吐量只提升了100倍,而伺服器的數量卻增加了500倍)。這是因為每個1000-byte的讀操作都會導致一個64KB大的Block在網路上傳輸。這樣的網路傳輸量消耗了我們網路中各種共享的1GB的鏈路,結果導致隨著我們增加伺服器的數量,每臺伺服器上的吞吐量急劇下降。

8 實際應用

截止到2006年8月,Google內部一共有388個非測試用的Bigtable叢集執行在各種各樣的伺服器叢集上,合計大約有24500個Tablet伺服器。表1顯示了每個叢集上Tablet伺服器的大致分佈情況。這些叢集中,許多用於開發目的,因此會有一段時期比較空閒。通過觀察一個由14個叢集、8069個Tablet伺服器組成的叢集組,我們看到整體的吞吐量超過了每秒1200000次請求,傳送到系統的RPC請求導致的網路負載達到了741MB/s,系統發出的RPC請求網路負載大約是16GB/s。

 

表2提供了一些目前正在使用的表的相關資料。一些表儲存的是使用者相關的資料,另外一些儲存的則是用於批處理的資料;這些表在總的大小、每個資料項的平均大小、從記憶體中讀取的資料的比例、表的Schema的複雜程度上都有很大的差別。本節的其餘部分,我們將主要描述三個產品研發團隊如何使用Bigtable的。

8.1Google Analytics

Google Analytics是用來幫助Web站點的管理員分析他們網站的流量模式的服務。它提供了整體狀況的統計資料,比如每天的獨立訪問的使用者數量、每天每個URL的瀏覽次數;它還提供了使用者使用網站的行為報告,比如根據使用者之前訪問的某些頁面,統計出幾成的使用者購買了商品。

為了使用這個服務,Web站點的管理員只需要在他們的Web頁面中嵌入一小段JavaScript指令碼就可以了。這個Javascript程式在頁面被訪問的時候呼叫。它記錄了各種Google Analytics需要使用的資訊,比如使用者的標識、獲取的網頁的相關資訊。Google Analytics彙總這些資料,之後提供給Web站點的管理員。

我們粗略的描述一下Google Analytics使用的兩個表。Row Click表(大約有200TB資料)的每一行存放了一個終端使用者的會話。行的名字是一個包含Web站點名字以及使用者會話建立時間的元組。這種模式保證了對同一個Web站點的訪問會話是順序的,會話按時間順序儲存。這個表可以壓縮到原來尺寸的14%。

Summary表(大約有20TB的資料)包含了關於每個Web站點的、各種型別的預定義彙總資訊。一個週期性執行的MapReduce任務根據Raw Click表的資料生成Summary表的資料。每個MapReduce工作程序都從Raw Click表中提取最新的會話資料。系統的整體吞吐量受限於GFS的吞吐量。這個表的能夠壓縮到原有尺寸的29%。

8.2Google Earth

Google通過一組服務為使用者提供了高解析度的地球表面衛星影象,訪問的方式可以使通過基於Web的Google Maps訪問介面(maps.google.com),也可以通過Google Earth定製的客戶端軟體訪問。這些軟體產品允許使用者瀏覽地球表面的影象:使用者可以在不同的解析度下平移、檢視和註釋這些衛星影象。這個系統使用一個表儲存預處理資料,使用另外一組表儲存使用者資料。

資料預處理流水線使用一個表儲存原始影象。在預處理過程中,影象被清除,影象資料合併到最終的服務資料中。這個表包含了大約70TB的資料,所以需要從磁碟讀取資料。影象已經被高效壓縮過了,因此儲存在Bigtable後不需要再壓縮了。

Imagery表的每一行都代表了一個單獨的地理區域。行都有名稱,以確保毗鄰的區域儲存在了一起。Imagery表中有一個列族用來記錄每個區域的資料來源。這個列族包含了大量的列:基本上市每個列對應一個原始圖片的資料。由於每個地理區域都是由很少的幾張圖片構成的,因此這個列族是非常稀疏的。

資料預處理流水線高度依賴執行在Bigtable上的MapReduce任務傳輸資料。在執行某些MapReduce任務的時候,整個系統中每臺Tablet伺服器的資料處理速度是1MB/s。

這個服務系統使用一個表來索引GFS中的資料。這個表相對較小(大約是500GB),但是這個表必須在保證較低的響應延時的前提下,針對每個資料中心,每秒處理幾萬個查詢請求。因此,這個表必須在上百個Tablet伺服器上儲存資料,並且使用in-memory的列族。

8.3 個性化查詢

個性化查詢(www.google.com/psearch)是一個雙向服務;這個服務記錄使用者的查詢和點選,涉及到各種Google的服務,比如Web查詢、影象和新聞。使用者可以瀏覽他們查詢的歷史,重複他們之前的查詢和點選;使用者也可以定製基於Google歷史使用習慣模式的個性化查詢結果。

個性化查詢使用Bigtable儲存每個使用者的資料。每個使用者都有一個唯一的使用者id,每個使用者id和一個列名繫結。一個單獨的列族被用來儲存各種型別的行為(比如,有個列族可能是用來儲存所有的Web查詢的)。每個資料項都被用作Bigtable的時間戳,記錄了相應的使用者行為發生的時間。個性化查詢使用以Bigtable為儲存的MapReduce任務生成使用者的資料圖表。這些使用者資料圖表用來個性化當前的查詢結果。

個性化查詢的資料會複製到幾個Bigtable的叢集上,這樣就增強了資料可用性,同時減少了由客戶端和Bigtable叢集間的“距離”造成的延時。個性化查詢的開發團隊最初建立了一個基於Bigtable的、“客戶側”的複製機制為所有的複製節點提供一致性保障。現在的系統則使用了內建的複製子系統。

個性化查詢儲存系統的設計允許其它的團隊在它們自己的列中加入新的使用者資料,因此,很多Google服務使用個性化查詢儲存系統儲存使用者級的配置引數和設定。在多個團隊之間分享資料的結果是產生了大量的列族。為了更好的支援資料共享,我們加入了一個簡單的配額機制(alex注:quota,參考AIX的配額機制)限制使用者在共享表中使用的空間;配額也為使用個性化查詢系統儲存使用者級資訊的產品團體提供了隔離機制。

9 經驗教訓

在設計、實現、維護和支援Bigtable的過程中,我們得到了很多有用的經驗和一些有趣的教訓。

一個教訓是,我們發現,很多型別的錯誤都會導致大型分散式系統受損,這些錯誤不僅僅是通常的網路中斷、或者很多分散式協議中設想的fail-stop型別的錯誤(alex注:fail-stop failture,指一旦系統fail就stop,不輸出任何資料;fail-fastfailture,指fail不馬上stop,在短時間內return錯誤資訊,然後再stop)。比如,我們遇到過下面這些型別的錯誤導致的問題:記憶體資料損壞、網路中斷、時鐘偏差、機器掛起、擴充套件的和非對稱的網路分割槽(alex注:extended and asymmetric network partitions,不明白什麼意思。partition也有中斷的意思,但是我不知道如何用在這裡)、我們使用的其它系統的Bug(比如Chubby)、GFS配額溢位、計劃內和計劃外的硬體維護。我們在解決這些問題的過程中學到了很多經驗,我們通過修改協議來解決這些問題。比如,我們在我們的RPC機制中加入了Checksum。我們在設計系統的部分功能時,不對其它部分功能做任何的假設,這樣的做法解決了其它的一些問題。比如,我們不再假設一個特定的Chubby操作只返回錯誤碼集合中的一個值。

另外一個教訓是,我們明白了在徹底瞭解一個新特性會被如何使用之後,再決定是否新增這個新特性是非常重要的。比如,我們開始計劃在我們的API中支援通常方式的事務處理。但是由於我們還不會馬上用到這個功能,因此,我們並沒有去實現它。現在,Bigtable上已經有了很多的實際應用,我們可以檢查它們真實的需求;我們發現,大多是應用程式都只是需要單個行上的事務功能。有些應用需要分散式的事務功能,分散式事務大多數情況下用於維護二級索引,因此我們增加了一個特殊的機制去滿足這個需求。新的機制在通用性上比分散式事務差很多,但是它更有效(特別是在更新操作的涉及上百行資料的時候),而且非常符合我們的“跨資料中心”複製方案的優化策略。

還有一個具有實踐意義的經驗:我們發現系統級的監控對Bigtable非常重要(比如,監控Bigtable自身以及使用Bigtable的客戶程式)。比如,我們擴充套件了我們的RPC系統,因此對於一個RPC呼叫的例子,它可以詳細記錄代表了RPC呼叫的很多重要操作。這個特性允許我們檢測和修正很多的問題,比如Tablet資料結構上的鎖的內容、在修改操作提交時對GFS的寫入非常慢的問題、以及在METADATA表的Tablet不可用時,對METADATA表的訪問掛起的問題。關於監控的用途的另外一個例子是,每個Bigtable叢集都在Chubby中註冊了。這可以幫助我們跟蹤所有的叢集狀態、監控它們的大小、檢查叢集執行的我們軟體的版本、監控叢集流入資料的流量,以及檢查是否有引發叢集高延時的潛在因素。

對我們來說,最寶貴的經驗是簡單設計的價值。考慮到我們系統的程式碼量(大約100000行生產程式碼(alex注:non-test code)),以及隨著時間的推移,新的程式碼以各種難以預料的方式加入系統,我們發現簡潔的設計和編碼給維護和除錯帶來的巨大好處。這方面的一個例子是我們的Tablet伺服器成員協議。我們第一版的協議很簡單:Master伺服器週期性的和Tablet伺服器簽訂租約,Tablet伺服器在租約過期的時候Kill掉自己的程序。不幸的是,這個協議在遇到網路問題時會大大降低系統的可用性,也會大大增加Master伺服器恢復的時間。我們多次重新設計這個協議,直到它能夠很好的處理上述問題。但是,更不幸的是,最終的協議過於複雜了,並且依賴一些Chubby很少被用到的特性。我們發現我們浪費了大量的時間在除錯一些古怪的問題(alex注:obscure corner cases),有些是Bigtable程式碼的問題,有些事Chubby程式碼的問題。最後,我們只好廢棄了這個協議,重新制訂了一個新的、更簡單、只使用Chubby最廣泛使用的特性的協議。

10 相關工作

Boxwood【24】專案的有些元件在某些方面和Chubby、GFS以及Bigtable類似,因為它也提供了諸如分散式協議、鎖、分散式Chunk儲存以及分散式B-tree儲存。Boxwood與Google的某些元件儘管功能類似,但是Boxwood的元件提供更底層的服務。Boxwood專案的目的是提供建立類似檔案系統、資料庫等高階服務的基礎構件,而Bigtable的目的是直接為客戶程式的資料儲存需求提供支援。

現在有不少專案已經攻克了很多難題,實現了在廣域網上的分散式資料儲存或者高階服務,通常是“Internet規模”的。這其中包括了分散式的Hash表,這項工作由一些類似CAN【29】、Chord【32】、Tapestry【37】和Pastry【30】的專案率先發起。這些系統的主要關注點和Bigtable不同,比如應對各種不同的傳輸頻寬、不可信的協作者、頻繁的更改配置等;另外,去中心化和Byzantine災難冗餘(alex注:Byzantine,即拜占庭式的風格,也就是一種複雜詭祕的風格。Byzantine Fault表示:對於處理來說,當發錯誤時處理器並不停止接收輸出,也不停止輸出,錯就錯了,只管算,對於這種錯誤來說,這樣可真是夠麻煩了,因為使用者根本不知道錯誤發生了,也就根本談不上處理錯誤了。在多處理器的情況下,這種錯誤可能導致運算正確結果的處理器也產生錯誤的結果,這樣事情就更麻煩了,所以一定要避免處理器產生這種錯誤。)也不是Bigtable的目的。

就提供給應用程式開發者的分散式資料儲存模型而言,我們相信,分散式B-Tree或者分散式Hash表提供的Key-value pair方式的模型有很大的侷限性。Key-value pair模型是很有用的元件,但是它們不應該是提供給開發者唯一的元件。我們選擇的模型提供的元件比簡單的Key-value pair豐富的多,它支援稀疏的、半結構化的資料。另外,它也足夠簡單,能夠高效的處理平面檔案;它也是透明的(通過區域性性群組),允許我們的使用者對系統的重要行為進行調整。

有些資料庫廠商已經開發出了並行的資料庫系統,能夠儲存海量的資料。Oracle的RAC【27】使用共享磁碟儲存資料(Bigtable使用GFS),並且有一個分散式的鎖管理系統(Bigtable使用Chubby)。IBM並行版本的DB2【4】基於一種類似於Bigtable的、不共享任何東西的架構(a shared-nothing architecture)【33】。每個DB2的伺服器都負責處理儲存在一個關係型資料庫中的表中的行的一個子集。這些產品都提供了一個帶有事務功能的完整的關係模型。

Bigtable的區域性性群組提供了類似於基於列的儲存方案在壓縮和磁碟讀取方面具有的效能;這些以列而不是行的方式組織資料的方案包括C-Store【1,34】、商業產品Sybase IQ【15,36】、SenSage【31】、KDB+【22】,以及MonetDB/X100【38】的ColumnDM儲存層。另外一種在平面檔案中提供垂直和水平資料分割槽、並且提供很好的資料壓縮率的系統是AT&T的Daytona資料庫【19】。區域性性群組不支援Ailamaki系統中描述的CPU快取級別的優化【2】。

Bigtable採用memtable和SSTable儲存對錶的更新的方法與Log-StructuredMerge Tree【26】儲存索引資料更新的方法類似。這兩個系統中,排序的資料在寫入到磁碟前都先存放在記憶體中,讀取操作必須從記憶體和磁碟中合併資料產生最終的結果集。

C-Store和Bigtable有很多相似點:兩個系統都採用Shared-nothing架構,都有兩種不同的資料結構,一種用於當前的寫操作,另外一種存放“長時間使用”的資料,並且提供一種機制在兩個儲存結構間搬運資料。兩個系統在API介面函式上有很大的不同:C-Store操作更像關係型資料庫,而Bigtable提供了低層次的讀寫操作介面,並且設計的目標是能夠支援每臺伺服器每秒數千次操作。C-Store同時也是個“讀效能優化的關係型資料庫”,而Bigtable對讀和寫密集型應用都提供了很好的效能。