谷歌三大論文中文版之一:Bigtable:一個分佈式的結構化數據存儲系統

修正了alex翻譯版的幾個欠妥之處
Bigtable
:一個分佈式的結構化數據存儲系統

摘要

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

1 介紹

在過去兩年半時間裏,我們設計、實現並部署了一個用於管理結構化數據的分佈式的存儲系統—在Google,我們稱之爲BigtableBigtable的設計目的是可靠地適應PB級別的數據和成千上萬臺機器。Bigtable已經實現了下面的幾個目標:廣泛的適用性、可擴展、高性能和高可用性。已經有超過60Google的產品和項目在使用Bigtable,包括Google AnalyticsGoogle FinanceOrkutPersonalized SearchWritelyGoogle Earth。這些產品使用Bigtable完成迥異的工作負載需求,這些需求從面向吞吐量的批處理作業到對終端用戶而言延時敏感的數據服務。它們使用的Bigtable集羣的配置也有很大的差異,從少數機器到成千上萬臺服務器,這些服務器裏最多可存儲幾百TB的數據。

在很多方面,Bigtable和數據庫很類似:它使用了很多數據庫的實現策略。並行數據庫【14】和內存數據庫【13】已經具備可擴展性和高性能,但是Bigtable提供了一個和這些系統完全不同的接口。Bigtable不支持完整的關係數據模型;與之相反,Bigtable爲客戶提供了簡單的數據模型,利用這個模型,客戶可以動態控制數據的佈局和格式alex注:也就是對BigTable而言,數據是沒有格式的,用數據庫領域的術語說,就是數據沒有Schema,用戶自己去定義Schema,用戶也可以自己推測(alex注:reason about)在底層存儲中展示的數據的位置屬性(alex注:位置相關性可以這樣理解,比如樹狀結構,具有相同前綴的數據的存放位置接近。在讀取的時候,可以把這些數據一次讀取出來)。數據用行和列的名字進行索引,名字可以是任意的字符串。雖然客戶程序通常會在把各種結構化或半結構化的數據串行化到字符串裏,Bigtable同樣將數據視爲未經解析的字符串。通過仔細選擇數據的模式,客戶可以控制數據的位置。最後,可以通過BigTable的模式參數動態地控制數據讀或寫(control whether to serve data out of memory or from disk)

第二節更詳細地描述了數據模型,第三節概要介紹了客戶端API;第四節簡要介紹了BigTable依賴的底層Google基礎框架;第五節描述了BigTable實現的基本原理;第6節描述了爲了提高BigTable的性能而採用的一些精細的調優方法;第7節提供了BigTable的性能數據;第8節講述了幾個Google內部使用BigTable的例子;第9節討論了我們在設計和後期支持過程中得到一些經驗和教訓;最後,在第10節介紹了相關工作,第11節是我們的結論。

2 數據模型

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

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

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

圖一:一個存儲Web網頁的例子的表的片斷。行名是一個反向URLcontents列族容納的是網頁的內容,anchor列族容納引用該網頁的錨鏈接文本。CNN的主頁被Sports IllustraterMY-look的主頁引用,因此該行包含了名爲“anchor:cnnsi.com”和“anchhor:my.look.ca”的列。每個錨鏈接數據項只有一個版本alex注:注意時間戳標識了列的版本,t9t8分別標識了兩個錨鏈接的版本);而contents列則有三個版本,分別由時間戳t3t5,和t6標識。

表中的行關鍵字是任意字符串(目前支持最大64KB的字符串,但是對大多數用戶,10-100個字節就足夠了)。在單一行關鍵字下的每一個讀或者寫操作都是原子的(不管在這一行裏被讀或者寫的不同列的數目),這個設計決策能夠使用戶很容易地推測(reason about)對同一個行進行併發更新操作時的系統行爲。

Bigtable通過行關鍵字的字典順序來維護數據。表中一定範圍內的行被動態分區。每個分區叫做一個”Tablet”,Tablet是數據分佈和負載均衡的單位。這樣做的結果是,讀取一定範圍內的少數行很高效,並且往往只需要跟少數機器通信。用戶可以通過選擇他們的行關鍵字來開發這種特性,這樣可以爲他們的數據訪問獲得好的本地性(get good locality)。舉例來說,我們在關鍵字com.google.maps/index.html的索引下爲maps.google.com/index.htm存儲數據。把相同的域中的網頁存儲在連續的區域可以讓一些主機和域名的分析更加有效。

列族

列關鍵字組成的集合叫做“列族“,列族構成了訪問控制的基本單位。存放在同一列族下的所有數據通常都屬於同一個類型(我們把同一個列族下的數據壓縮在一起)。列族必須先創建,然後才能在列族中任何的列關鍵字下存放數據;列族創建後,其中的任何一個列關鍵字下都可以存放數據。我們的意圖是,一張表中不同列族的數目要小(最多幾百個),並且列族在操作中很少改變。與此相反,一張表可以有無限多個列。

列關鍵字的命名語法如下:列族:限定詞。列族的名字必須是可打印的字符串,但是限定詞可以是任意字符串。比如,Webtable有個列族language,用來存放撰寫網頁的語言。我們在language列族中只使用一個列關鍵字,用來存放每個網頁的語言標識IDWebtable中另一個有用的列族是anchor;這個列族的每一個列關鍵字代表單獨一個錨鏈接,如圖一所示。限定詞是引用該網頁的站點名;數據項內容是鏈接文本。

訪問控制、磁盤和內存的計數都是在列族層面進行的。在我們的Webtable的例子中,上述的控制權限能幫助我們管理不同類型的應用:一些應用可以添加新的基本數據、一些可以讀取基本數據並創建派生的列族、一些則只允許瀏覽現存數據(甚至可能因爲隱私的原因不能瀏覽所有現存列族)。

時間戳

Bigtable中,每一個數據項都可以包含同一數據的不同版本;這些版本通過時間戳來索引。Bigtable時間戳是64位整型數。時間戳可由Bigtable指定,這種情況下時間戳代表精確到毫秒的“實時”時間,或者該值由庫戶程序明確指定。需要避免衝突的程序必須自己生成一個唯一的時間戳。數據項中不同版本按照時間戳倒序排列,所以最新的版本可以被先讀到。

爲了減輕多個版本數據的管理負擔,我們對每一個列族提供兩個設置參數,Bigtable通過這兩個參數可以對廢棄版本的數據進行自動垃圾收集。用戶既可以指定只保存最後n個版本的數據,也可以只保存“足夠新”的版本的數據(比如,只保存最近7天的內容寫入的數據)。

在我們的例子Webtable中,我們將存儲在contents:列中爬蟲經過的頁面的時間戳設置爲這個版本的頁面被實際爬過的時(alex: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);

客戶程序可以對Bigtable進行如下的操作:寫入或者刪除Bigtable中的值、從個別行中查找值、或者遍歷表中的一個數據子集。圖2中的C++代碼使用RowMutation抽象對象執行一系列的更新操作。(爲了保持示例簡潔,我們省略了無關細節)。對Apply的調用執行了了對Webtable的一個原子修改(mutation)操作:它爲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());

}

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

Bigtable還支持一些其它的特性,這些特性允許用戶以更復雜的方法操作數據。首先,Bigtable支持單行上事務處理,利用這個功能,用戶可以對存儲在一個單獨行關鍵字下的數據執行原子性的讀取-更新-寫入操作。雖然Bigtable提供了一個允許用戶跨行關鍵字(at the clients?)批量寫入的接口,但是,Bigtable目前還不支持通用的跨行事務處理。其次,Bigtable允許把數據項用做整數計數器。最後,Bigtable支持在服務器的地址空間內執行腳本程序。腳本程序使用Google開發的用於數據處理的Sawzall語言【28】書寫。目前,我們基於SawzallAPI還不允許客戶腳本將數據寫回Bigtable,但是它允許多種形式的數據轉換、基於任意表達式的數據過濾、以及通過多種操作符的彙總歸納。

Bigtable可以和MapReduce12】一起使用,MapReduceGoogle開發的運行大規模並行計算的框架。我們已經開發了一套封裝器(wrapper),這些封裝器使Bigtable既可以作爲MapReduce作業的源輸入也可以作爲目標輸出輸出。

4 BigTable構件

Bigtable是建立在一些其他Google基礎架構之上的。BigTable使用Google分佈式文件系統(GFS)17】存儲日誌和數據文件。BigTable集羣往往運行在一個共享的機器池中,池中的機器還會運行其它各種各樣的分佈式應用程序,BigTable的進程經常要和其它應用的進程共享機器。BigTable依賴集羣管理系統在共享機器上調度作業、管理資源、處理機器的故障、以及監視機器的狀態。

SSTable文件格式

BigTable數據在內部使用Google SSTable文件格式存儲。SSTable提供一個從鍵(key)到值(value)的持久化的、已排序、不可更改的映射(Map),這裏的keyvalue 的都是任意的字節(Byte)串。對SSTable提供瞭如下操作:查詢與一個指定key值相關的value,或者遍歷指定key值範圍內的所有鍵值對。從內部看,SSTable是一連串的數據塊(通常每個塊的大小是64KB,但是這個大小是可以配置的)。SSTable使用塊索引(通常存儲在SSTable 的最後)來定位數據塊;在打開SSTable的時候,索引被加載到內存。一次查找可以通過一次磁盤搜索完成:首先執行二分查找在內存索引裏找到合適數據塊的位置,然後在從硬盤中讀取合適的數據塊。也可以選擇把整個SSTable都映射到內存中,這樣就可以在不用訪問硬盤的情況下執行查詢搜索了。

Chubby分佈式鎖服務

BigTable還依賴一個高可用的、持久化的分佈式鎖服務組件,叫做Chubby8】。一個Chubby服務包括了5 個活動的副本,其中一個副本被選爲Master,並且積極處理請求。只有在大多數副本正常運行,並且彼此之間能夠互相通信的情況下,Chubby服務纔是可用的。當有副本失效的時候,出現故障時Chubby使用Paxos算法【9,23】保證副本的一致性。Chubby提供了一個名字空間,裏面包括了目錄和小文件。每個目錄或者文件可以當成一個鎖使用,對文件的讀寫操作都是原子的。Chubby客戶程序庫提供對Chubby文件的一致性緩存。每個Chubby客戶程序都維護一個與Chubby服務的會話。如果客戶程序不能在租約到期的時間內重新簽訂會話租約,這個會話就過期失效了(A clients session expires if it is unable to renew its session lease within the lease expiration time.)。當一個客戶會話失效時,它擁有的鎖和打開的文件句柄都失效了。Chubby客戶程序可以在Chubby文件和目錄上註冊回調函數,當文件或目錄改變、或者會話過期時,回調函數會通知客戶程序。

Bigtable使用Chubby完成以下各種任務:保證在任意時間最多隻有一個活動的Master;存儲BigTable數據的引導程序的位置(參考5.1節);發現tablet服務器,以及在Tablet服務器失效時進行善後(5.2節);存儲BigTable的模式信息(每張表的列族信息);以及存儲訪問控制列表。如果Chubby長時間無法訪問,BigTable就會失效。最近我們在跨越11Chubby服務實例的14BigTable集羣上測量了這個影響。Bigtable服務器時鐘的平均比率是0.0047%,在這期間由於Chubby不可用而導致BigTable中的部分數據不能訪問(Chubby不能訪問的原因可能是Chubby本身失效或者網絡問題)。單個集羣裏受Chubby失效影響最大的百分比是0.0326%

5 實現

Bigtable的實現有三個主要的組件:鏈接到每個客戶程序的庫、一個Master服務器和多個tablet服務器。在一個集羣中可以動態地添加(或者刪除)一個tablet服務器來適應工作負載的變化。

Master主要負責以下工作:爲tablet服務器分配tablets,檢測新加入的或者過期失效的table服務器、平衡tablet 服務器的負載、以及對GFS中的文件進行垃圾收集。除此之外,它還處理模式修改操作,例如建立表和列族。

每個Tablet服務器都管理一組tablet(通常每個tablet服務器有大約數十個至上千個tablet)。tablet服務器處理它所加載的tablet的讀寫操作,以及分割增長的過大的tablet

和很多單主節點(Single-Master)類型的分佈式存儲系統【17.21】類似,客戶數據都不經過master服務器:客戶程序直接和tablet服務器通信來進行讀寫操作。由於BigTable的客戶程序不依賴master服務器來獲取tablet的位置信息,大多數客戶程序甚至完全不和master通信。因此,在實際應用中master的負載是很輕的。

一個BigTable集羣存儲了很多表,每個表包含了一組tablet,而每個tablet包含了某個範圍內的行的所有相關數據。初始狀態下,每個表只有一個tablet組成。隨着表中數據的增長,它被自動分割成多個tablet,默認情況下每個tablet的大小大約是100MB200MB

5.1 Tablet的位置信息

我們使用一個三層的、類似於B+[10]的結構存儲tablet的位置信息(如圖4)

第一層是一個存儲在Chubby中的文件,它包含了root tablet的位置信息。root tablet在一個特殊的元數據(METADATA)表包含了裏所有的tablet的位置信息。每一個元數據tablet包含了一組用戶tablet的的位置信息。root tablet實際上只是元數據表的第一個tablet,只不過對它的處理比較特殊—root tablet永遠不會被分割—這就保證了tablet的位置層次不會超過三層。

元數據表將每個tablet的位置信息存儲在一個行關鍵字下,而這個行關鍵字是由tablet所在的表的標識符和tablet的最後一行編碼而成的。每一個元數據行在內存中大約存儲了1KB數據。在一個大小適中的、大小限制爲128MB的元數據 tablet中,我們的三層結構位置信息模式足夠尋址234(三層27+10*27+10)個tablet(或者說在128M的元數據中可以存儲261個字節)。

客戶程序庫會緩存tablet的位置信息。如果客戶程序不知道一個tablet的位置信息,或者發現它緩存的地址信息不正確,那麼客戶程序就遞歸移動到tablet位置層次;如果客戶端緩存是空的,那麼尋址算法需要通過三次網絡來回通信尋址,這其中包括了一次Chubby讀操作。如果客戶端緩存的地址信息過期了,那麼尋址算法可能進行多達6次(alex注:其中的三次通信發現緩存過期,另外三次更新緩存數據)網絡來回通信,因爲過期緩存條目只有在沒有查到數據(upon misses)的時候才能發現 (假設元數據tablet沒有被頻繁的移動)。儘管tablet的位置信息是存放在內存裏的,所以不需訪問GFS,但是,通常我們會通過預取tablet地址來進一步的減少訪問開銷:無論何時讀取元數據表,都會爲不止一個tablet讀取元數據。

在元數據表中還存儲了次級信息(alex注:secondary information),包括與tablet有關的所有事件日誌(例如,什麼時候一個服務器開始爲該tablet提供服務)。這些信息有助於排除故障和性能分析。

5.2 Tablet分配

每個tablet一次分配給一個tablet服務器。master服務器記錄活躍的tablet服務器、當前tablettablet服務器的分配、包括哪些tablet還沒有被分配。當一個tablet還沒有被分配、並且有一個tablet服務器有足夠的空閒空間來裝載該tablet並且可用,master通過給這個tablet服務器發送一個tablet裝載請求分配該tablet

BigTable使用Chubby跟蹤記錄tablet服務器。當一個tablet服務器啓動時,它在一個指定的Chubby目錄下建立一個有唯一名字的文件,並且獲取該文件的獨佔鎖。master監控着這個目錄(服務器目錄)y以便範閒tablet服務器。如果tablet服務器失去了Chubby上的獨佔鎖—比如由於網絡斷開導致tablet服務器丟失Chubby會話—它就停止對tablet提供服務。(Chubby提供了一種高效的機制,利用這種機制,tablet服務器能夠在不招致網絡擁堵的情況下檢查其是否還持有該鎖)。只要該文件還存在,tablet服務器就會試圖重新獲得對該獨佔鎖;如果文件不存在了,那麼tablet服務器就永遠不能再提供服務了,它會自行退出(so it kills itself)。只要tablet 服務器終止(比如,集羣的管理系統將該tablet服務器的主機從集羣中移除),它會嘗試釋放它持有的鎖,以便master儘快重新分配它的tablet

Master負責探測一個tablet服務器何時不再爲它的tablet提供服務,並且儘快重新分配那些tabletmaster通過輪詢tablet服務器鎖的狀態來探測tablet服務器何時不再爲tablet提供服務。如果一個tablet服務器報告它丟失了鎖,或者master最近幾次嘗試都無法和該服務器通信,master就會嘗試獲取該tablet服務器文件的獨佔鎖;如果master能夠獲取獨佔鎖,那麼就說明Chubby是正常運行的,而tablet 服務器要麼是宕機了、要麼是不能和Chubby通信了,因此,爲了保證該tablet服務器不能再提供服,master就刪除該tablet服務器在Chubby上的服務器文件。一旦服務器文件被刪除了,master就把之前分配給該服務器的所有的tablet放入未分配的tablet集合中。爲了確保Bigtable集羣面對masterChubby之間網絡問題不那麼脆弱,master在它的Chubby會話過期時會主動退出。但是不管怎樣,如上所述,master的故障不會改變現有tablettablet服務器的分配。

當集羣管理系統啓動了一個master之後,master首先要了解當前tablet的分配狀態,之後才能夠修改它們。master在啓動的時候執行以下步驟:(1masterChubby中獲取一個唯一的master鎖,用來阻止併發的master實例;(2master掃描Chubby的服務器目錄,獲取尋找正在運行的服務器;(3master和每一個正在運行的tablet服務器通信,搜尋哪些tablet已經分配到了tablet服務器中;(4master服務器掃描元數據表獲取tablet的集合。只要掃描發現了一個還沒有分配的tabletmaster就將這個tablet加入未分配的tablet 集合,該集合使該talbet有機會參與talbet分配。

有一種複雜情況是:元數據tablet還沒有被分配之前是不能夠掃描它的。因此,在開始掃描之前(步驟4),如果在第三步中沒有發現對root tablet的分配,master就把root tablet加入到未分配的tablet集合中。這個附加操作確保了root tablet會被分配。由於root tablet包括了所有元數據tablet的名字,master在掃描完root tablet以後才瞭解所有元數據信息。

現存tablet的集合只有在以下事件發生時纔會改變:建立了一個新表或者刪除了一箇舊表、兩個現存tablet合併組成一個大的tablet、或者一個現存tablet被分割成兩個小的tabletmaster可以跟蹤這些改變,因爲除了最後一個事件外的兩個事件都是由它初始化的。tablet分割事件需要特殊處理,因爲它是由tablet服務器初始化的。tablet服務器通過在元數據表中爲新的tablet記錄信息的方式提交分割操作。在分割操作提交之後tablet服務器會通知master。假如分割操作通知丟失(tablet服務器或者master宕機),master在請求tablet服務器裝載已經被分割的tablet的時候會探測到一個新的tablet。由於在元數據tablet中發現的tablet條目只是列舉了master請求加載的tablet的一部分,tablet服務器會通知master分割信息。

5.3 Tablet服務

如圖5所示,tablet的持久化狀態信息保存在GFS上。更新操作提交到存儲撤銷(REDO)記錄的提交日誌中。在這些更新操作中,最近提交的那些存放在一個叫做memtable的排序的緩衝區中;較早的更新存放在一系列SSTable中。爲了恢復一個tablettablet服務器在元數據表中讀取它的元數據。這些元數據包含組成一個tabletSSTable列表和一組還原點(redo points),這些點是指向包含tablet數據的任一提交日誌的指針。tablet服務器把SSTable的索引讀進內存,之後通過應用還原點之後提交的所有更新來重構memtable

當寫操作到達tablet服務器時,tablet服務器首先要檢查這個操作格式是否正確、發送者是否有執行這個改變的權限。權限驗證是通過從一個Chubby文件裏讀取具有寫權限的操作者列表來進行的(這個文件幾乎總會在Chubby客戶緩存裏命中)。有效的修改操作會記錄在提交日誌裏。可以採用組提交方式(alex注:group commit)來提高大量小的修改操作的吞吐量【1316】。當一個寫操作提交後,它的內容被插入到memtable裏面。

當讀操作到達tablet服務器時,它同樣會檢查良構性和適當的權限。一個有效的讀操作在一個由一系列SSTablememtable合併的視圖裏執行。由於SSTablememtable是按字典排序的數據結構,因此可以高效生成合並視圖。

當進行tablet的合併和分割時,引入(incoming)的讀寫操作能夠繼續進行。

5.4 Compactions

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

隨着寫操作的執行,memtable的大小不斷增加。當memtable的尺寸到達一個臨界值的時候,這個memtable就會被凍結,然後創建一個新的memtable;被凍結住memtable會被轉換成SSTable並寫入GFSalex注:我們稱這種Compaction行爲爲Minor Compaction)。Minor Compaction過程有兩個目的:一是收縮tablet服務器內存使用,二是在服務器災難恢復過程中,減少必須從提交日誌裏讀取的數據量。在Compaction過程中,引入(incoming)的讀寫操作仍能繼續。

每一次Minor Compaction都會創建一個新的SSTable。如果這個行爲未經檢查地持續下去,讀操作可能需要合併來任意個SSTable的更新;反之,我們通過定期在後臺執行Merging Compaction 過程限制這類文件(shijinSStable)的數量。Merging Compaction過程讀取一些SSTablememtable的內容,輸出一個新的SSTable。只要Merging Compaction過程完成了,作爲輸入的SSTablememtable就可以丟棄了。

重寫所有的SSTable到一個新的SSTableMerging Compaction過程叫作Major Compaction。由非Major Compaction產生的SSTable可以包含特殊的刪除條目,這些刪除條目能夠禁止仍然可用的較早SSTable中已刪除的數據(STables produced by non-major compactions can contain special deletion  entries that suppres s deleted data in older SSTables that are still live)。另一方面,Major Compaction過程生成的SSTable不包含已經刪除的信息或數據。Bigtable循環掃描它所有的tablet並且定期對它們應用Major CompactionMajor Compaction機制允許Bigtable回收已經刪除的數據使用的資源,並且確保已刪除的數據及時從系統內消失(alex注:實際是回收資源。數據刪除後,它佔有的空間並不能馬上重複利用;只有空間回收後才能重複使用),這對存儲敏感數據的服務是非常重要的。

6 優化

上一節描述的實現需要很多優化來達到用戶要求的高性能、高可用性和高可靠性的目標。爲了突出這些優化,本節描述了部分實現的詳細細節。

局部性羣組

客戶程序可以將多個列族組合成一個局部性羣族。對每個tablet中的每個局部性羣組都會生成一個單獨的SSTable。將通常不會一起訪問的列族分割成單獨的局部性羣組使讀取操作更高效。例如,在Webtable表中,網頁的元數據(比如語言和檢驗和Checksum)可以在一個局部性羣組中,網頁的內容可以在不同的羣組:要讀取網頁元數據的應用程序沒有必要讀取整個頁面內容。

此外,可以以局部性羣組爲單位指定一些有用的調整參數。比如,可以把一個局部性羣組設定爲全部存儲在內存中。設定爲存入內存的局部性羣組的SSTable依照惰性加載的策略裝載進tablet服務器內存。加載完成之後,屬於該局部性羣組的列族的不用訪問硬盤即可讀取。這個特性對於需要頻繁訪問的小塊數據特別有用:在Bigtable內部,我們利用這個特性進行元數據表內的列族定位(for the location column family in the metadata table)。

壓縮

客戶程序可以控制一個局部性羣組的SSTable是否壓縮;如果壓縮,用什麼格式壓縮。用戶指定的壓縮格式應用到每個SSTable的塊中(塊的大小由局部性羣組的調整參數操縱)。儘管爲每個分別壓縮浪費了少量空間(alex注:相比於對整個SSTable進行壓縮,分塊壓縮壓縮率較低),我們卻受益於在只讀取小部分數據SSTable的時候就不必解壓整個文件了。許多客戶程序使用雙步(two-pass)定製壓縮模式。第一步採用Bentley and McIlroys模式[6],這種模式橫跨一個很大窗口壓縮常見的長字符串;第二步採用快速壓縮算法,即在一個16KB數據的小窗口中尋找重複數據。兩步壓縮都很快,在現代的機器上,編碼的速率達到100-200MB/s,解碼的速率達到400-1000MB/s

雖然我們在選擇壓縮算法的時候強調的是速度而不是壓縮的空間,但是這種兩步壓縮模式效果卻驚人的好。比如,在Webtable的例子裏,我們使用這種壓縮方式來存儲網頁內容。在一次實驗中,我們在一個壓縮的局部性羣組中存儲了大量的網頁。針對實驗的目的,對每個文檔我們限制其只有一個版本,而不是存儲對我們可用的所有版本。該模式在空間上達到了10:1的壓縮比。這比經典的Gzip在壓縮HTML頁面時3:1或者4:1的空間壓縮比好的多;這是由於Webtable的行的佈局方式:從單一主機獲取的所有頁面緊密存儲。利用這個特性,Bentley-McIlroy算法可以從來自同一個主機的頁面裏識別大量共享的引用。不僅僅是Webtable,很多應用程序也通過選擇行名來將相似的數據集聚,從而獲取較高的壓縮率。當我們在Bigtable中存儲同一份數據的多個版本的時候,壓縮效率會更高。

通過緩存提高讀操作的性能

爲了提高讀操作的性能,tablet服務器使用二級緩存的策略。對tablet服務器代碼而言(to tablet server code),掃描緩存是第一級緩存,其緩存SSTable接口返回的鍵值對;Block緩存是二級緩存,其緩存從GFS讀取的SSTable塊。對於趨向於重複讀取相同數據的應用程序來說,掃描緩存非常有效;對於趨向於讀取剛讀過的數據附近的數據的應用程序來說,Block緩存很有用(例如,順序讀,或者在一個熱點的行的同一局部性羣組中隨機讀取不同的列)

Bloom過濾器

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

5.3節所述,一個讀操作必須讀取組成tablet狀態的所有SSTable的數據。如果這些SSTable不在內存中,那麼就需要多次訪問硬盤。我們通過允許客戶程序對特定局部性羣組的SSTable指定Bloom過濾器【7】,來減少硬盤訪問的次數。通過bloom過濾器我們可以查詢一個SSTable是否包含了特定行/列對的數據。對於某些應用程序,只使用了少量的tablet服務器內粗來存儲Bloom過濾器,卻大幅度減少了讀操作需要的磁盤訪問次數。Bloom過濾器的使用也意味着對不存在的行或列的大多數查詢不需要訪問硬盤。

提交日誌的實現

如果我們爲每個tablet在一個單獨的文件裏保存提交日誌,那麼大量的文件會併發地寫入GFS。取決於每個GFS 服務器底層文件系統實現方案,爲了寫入不同的物理日誌文件,這些寫操作會引起大量的詢盤操作。另外,由於批量提交(group)中操作的數目趨向於比較少,每個tablet擁有單獨的日誌文件也會降低批量提交優化的效果。爲了修正這些問題,我們對每個tablet服務器的唯一提交日誌追加修改,不同tablet的修改操作協同混合到一個相同的物理日誌文件中。

在普通操作中使用單個日誌提供了重大的性能收益,但是將恢復的工作複雜化了。當一個tablet服務器宕機時,它服務的tablet將會被移動到大量其它的tablet服務器上:每個tablet服務器通常都裝載少量原來的服務器的tablet。爲了恢復一個tablet的狀態,新的tablet服務器要爲該tablet重新應用原來的tablet服務器寫的提交日誌中的修改操作。然而,這些tablet修改操作被混合在同一個物理日誌文件中。一種方法可以是對每一個新的tablet服務器讀取完整的提交日誌文件,然後只重新應用它需要恢復的tablet的相關條目。然而,在這種模式下,假如100臺機器中每臺都加載了來自失效的tablet服務器的一個單獨的tablet,那麼這個日誌文件就要被讀取100次(每個服務器讀取一次)。

爲了避免重複讀取日誌文件,我們首先把提交日誌的條目按照關鍵字(tablerow namelog sequence number)排序。排序之後,對一個特定tablet的修改操作連續存放,因此,隨着一次詢盤操作之後的順序讀取,修改操作的讀取將更高效。爲了並行排序,我們將日誌文件分割成64MB的段,之後在不同的tablet服務器對每段進行並行排序。這個排序過程由master來協調,並且當一個tablet服務器指出它需要從一些提交日誌文件中回覆修改時排序被初始化。

在向GFS寫提交日誌時有時引起性能顛簸,原因是多種多樣的(比如,寫操作相關GFS服務器崩潰;或者穿過到達特定組合的三個GFS服務器的網絡擁塞或者過載)。爲了使修改操作免受GFS瞬時延遲的影響,每個tablet服務器實際上有兩個日誌寫入線程,每個線程寫自己的日誌文件,並且同一時刻,兩個線程只有其中之一是活躍的。如果寫入活躍日誌文件的效率很低,日誌文件寫入切換到另外一個線程,在提交日誌隊列中的修改操作就會由新的活躍日誌寫入線程寫入。日誌條目包含序列號,這使得恢復進程可以省略掉由於日誌進程切換而造成的重複條目。

Tablet恢復提速

如果master將一個tablet從一個tablet服務器移到另外一個tablet服務器,源tablet服務器會對這個tablet做一次Minor Compaction。這個Compaction操作減少了tablet服務器日誌文件中沒有壓縮的狀態的數目,從而減少了恢復的時間。Compaction完成之後,該tablet服務器停止爲該tablet提供服務。在真正卸載tablet之前,tablet服務器還會再做一次(通常會很快)Minor Compaction,以消除tablet服務器日誌中第一次minor compaction執行過程中產生的未壓縮的狀態殘留。當第二次minor compaction完成以後,tablet就在不需要任何日誌條目恢復的情況下被裝載到另一個tablet服務器上了。

利用不變性

我們在使用Bigtable時,除了SSTable緩存之外,實際上所有我們產生的SSTable都是不變的,因而Bigtable系統的其它部分就被簡化了。例如,當從SSTable讀取數據的時候,我們不必對文件系統訪問操作進行同步。這樣一來,就可以非常高效的實現對行的併發操作。memtable是唯一一個能被讀和寫操作同時訪問的可變數據結構。爲了減少對memtable進行讀操作時的競爭,我們讓每個memtable表的行寫備份copy-on-write,這樣就允許讀寫操作並行執行。

因爲SSTable是不變的,所以永久移除已被刪除數據的問題就轉換成對廢棄的SSTable進行垃圾收集的問題了。每個tabletSSTable都在註冊在元數據表中。Master將廢棄的SSTable作爲對SSTable集合的“標記-清除”的垃圾回收而刪除【25】,元數據表則保存了root的集合。

最後,SSTable的不變性使得分割tablet的操作非常快捷。與爲每個子tablet生成新的SSTable集合相反,我們讓子tablet共享父tabletSSTable

7 性能評估

我們建立一個包括Ntablet服務器的Bigtable集羣,通過改變N的值來測量Bigtable的性能和可擴展性。tablet服務器配置了1GB的內存,數據寫入到一個包含1786臺機器、每臺機器有2400G IDE硬盤驅動的GFS單元上。N臺客戶機生成測試Bigtable工作負載。(我們使用和tablet服務器相同數目的客戶機以確保客戶機不會成爲瓶頸。)每臺機器有主頻2GHZ的雙核Opteron處理器,配置了足以容納所有進程工作集的物理內存,以及一張千兆以太網卡。這些機器都分配到一個兩層的、樹狀的交換網絡裏,在根節點上的可用總帶寬大約100-200Gbps。所有的機器採用相同的主機設備,因此,任何兩臺機器間的往返時間都小於1ms

Tablet 服務器、master、測試客戶機、以及GFS服務器都運行在同一組機器上。每臺機器都運行一個GFS服務器。其它的機器要麼運行tablet服務器、要麼運行客戶程序、要麼運行在測試過程中,同時使用這個機器池的其它作業的進程。

R是與測試相關的相異Bigtable行關鍵字的數量。我們精心選擇R值,保證每次基準測試對每臺tablet服務器讀/寫的數據量都在1GB左右。

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

序列讀的基準測試生成行關鍵字的方式與序列寫相同,只是說不是在行關鍵字下寫入,而是讀取行關鍵字下的字符串(這些字符串由之前序列寫基準測試調用寫入)。同樣的,隨機讀的基準測試也是附於隨機寫操作之上的。

 掃描基準測試和序列讀類似,但是使用的是BigTable提供的、從一個行範圍內掃描所有值的API。由於一次RPC從一個tablet服務器取回了大量的值,因此,使用掃描減少了基準測試執行RPC的次數。

隨機讀(內存)基準測試和隨機讀類似,但是包含基準測試數據的局部性羣組被標記爲“in-memory”,因此,要讀的數據從tablet服務器的內存中即可得到滿足,而不需要從GFS 讀取數據。僅對這個測試,我們把每臺tablet服務器存儲的數據從1GB減少到100MB,這樣就充裕地滿足了tablet服務器的可用內存。

6 中的兩個視圖展示了我們在Bigtable中讀寫1000-byte時基準測試的性能。圖表顯示了每個tablet服務器每秒鐘進行的操作次數;圖中的曲線顯示了每秒種操作次數的總和。

單個tablet服務器的性能

我們首先考慮單個tablet服務器的性能。隨機讀的性能比其它所有操作慢一個數量級或以上(by the order of magnitude or more)。每個隨機讀操作都涉及通過網絡從GFS傳輸64KBSSTabletablet服務器,這其中只有一個1000-byte的值被用到。Tablet服務器每秒大約執行1200次讀操作,也就是說每秒大約從GFS讀取75MB64*1200/1024)的數據。由於網絡協議層的消耗、SSTable解析、以及BigTable代碼執行,這個傳輸帶寬足以佔滿tablet服務器的CPU,這個帶寬也幾乎足以佔滿我們系統中使用的網絡鏈接。大多數採用這種訪問模式的BigTable應用程序減小Block到一個很小的值,通常取8KB

從內存隨機讀速度快很多,原因是這樣的,每個1000-byte的讀操作都是由tablet服務器的本地內存滿足的,不需要從GFS讀取64KB的大數據塊。

隨機和序列寫操作性能比隨機讀要好些,原因是每個tablet服務器把寫入的內容追加到一個提交日誌上,並且採用批量提交的方式,高效地流式寫入GFS。隨機寫和序列寫的性能沒有顯著的差別,兩種方式下對tablet服務器的寫操作都記錄到同一提交日誌中。

序列讀的性能好於隨機讀,原因是每次從GFS獲取的64KBSSTable數據塊會緩存到Block緩存中,這些緩存用來服務下一次64KB的讀請求。

掃描的性能更高,這是由於tablet服務器響應一次客戶RPC就會返回大量值,所以,RPC開銷基本被大量的數據攤銷了。

擴大規模(Scaling

隨着我們將系統中的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 benchmark progresses)。

隨機讀基準測試在擴大規模後表現最差(整體吞吐量只提升了100倍,而服務器的數量卻增加了500倍)。這種現象的出現是因爲(就像上面解釋的那樣)每讀1000-byte我們都會在網絡上傳輸一個64KB大的塊。這樣的網絡傳輸消耗了我們網絡中各種共享的1GB的鏈路,結果導致隨着我們增加服務器的數量,每臺服務器上的吞吐量急劇下降。

8 實際應用

截止到20068月有388個非測試用的Bigtable集羣運行在各種各樣的Google機器集羣上,合計大約有24500tablet服務器。表1顯示了每個集羣上tablet服務器的大致分佈情況。這些集羣中,許多用於開發目的,因此在引人注意的一段時期內比較空閒。通過觀察一個由14個忙碌集羣、8069tablet服務器組成的羣組,我們看到整體的流量超過了每秒120萬次請求,發送到系統的RPC請求導致的網絡負載達到了741MB/s,系統發出的RPC請求網絡負載大約是16GB/s

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

8.1 Google Analytics

Google Analytics是用來幫助Web站點的管理員在他們網站上分析流量模式的服務。它提供了整體狀況的統計數據,比如每天的獨立訪問的用戶數量、每天每個URL的瀏覽量;它還提供了站點追蹤報告,比如假定用戶之前訪問了一個指定頁面,購買商品的用戶的比例。

爲了使用這個服務,Web站點的管理員需要在他們的Web頁面中嵌入一小段JavaScript腳本。這個Javascript程序在頁面被訪問的時候調用。它記錄了各種Google Analytics需要的信息,比如用戶的標識、獲取的網頁的相關信息。Google Analytics彙總這些數據,之後提供給Web站點的管理員。

我們簡略描述Google Analytics使用的兩個表。原始點擊Raw Click表(200TB)爲每一個終端用戶會話維護一行數據。行的名字是一個包含Web站點名字以及用戶會話創建時間的元組。這種模式保證了訪問同一個Web站點的會話是連續的,會話按時間順序存儲。這個表壓縮到原來尺寸的14%

彙總表summary20TB)包含了每個Web站點的、各種類型的預定義彙總信息。通過週期性地調度MapReduce作業,從raw click表中生成summary表的數據。每個MapReduce作業從raw click表中提取最新的會話數據。系統的整體吞吐量受限於GFS的吞吐量。這個表的壓縮到原有尺寸的29%

8.2 Google Earth

Google運轉着一批爲用戶提供高分辨率地球表面衛星圖像的服務,既可以通過基於WebGoogle 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生成用戶配置文件。這些用戶配置文件用來個性化當前(live)查詢結果。

個性化查詢的數據在多個Bigtable的集羣上備份,以便提高數據可用性同時減少由客戶端的距離而造成的延時。個性化查詢的開發團隊最初在Bigtable之上建立了一個客戶側(client side)的複製機制,該機制保證了所有副本的最終一致性。現在的系統則使用了內嵌的複製子系統。

個性化查詢存儲系統的設計允許其它團隊在它們自己的列中加入新的用戶級(per-user)數據,因此,很多其他需要按用戶配置選項和設置的Google屬性使用了該系統。在多個團隊之間共享表的結果是產生了非比尋常的衆多列族。爲了更好的支持數據共享,我們加入了一個簡單的配額機制(alex注:quota,參考AIX的配額機制),限制任意特定客戶在共享表中消耗的空間;該機制也爲使用該系統存儲用戶級信息的產品團體提供了隔離。

9 經驗教訓

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

一個教訓是,我們發現,很多類型的錯誤都會導致大型分佈式系統受損,不僅僅是通常的網絡中斷、或者很多分佈式協議中設想的fail-stop錯誤(alex注:fail-stop failture,指一旦系統failstop,不輸出任何數據;fail-fast failture,指fail不馬上stop,在短時間內return錯誤信息,然後再stop)。比如,我們遇到過下面這些類型的錯誤導致的問題:內存數據損壞、網絡中斷、時鐘偏差、機器掛起、擴展的和非對稱的網絡分區(alex注:extended and asymmetric network partitio ns,不明白什麼意思。partition也有中斷的意思,但是我不知道如何用在這裏)、我們使用的其它系統的Bug(比如Chubby)、GFS 配額溢出、計劃內和計劃外的硬件維護。隨着我們在這些問題中得到更多經驗,我們通過修改各種協議來解決(address)這些問題。比如,我們在RPC機制中加入了檢驗和Checksum。我們通過移除系統的其他部分針對另一部分作出的假設來解決這些問題。例如,我們不再假設一個給定的Chubby操作只返回固定錯誤碼集合中的一個值。

我們學到的另外一個教訓是,我們明白了在徹底瞭解一個新特性如何使用之前,延遲添加這個新特性是非常重要的。比如,我們最初計劃在我們的API中支持通用目的的事務處理。但是由於我們還不會馬上用到這個功能,因此,我們並沒有去實現它。現在Bigtable上已經運行了很多實際應用,我們已經能夠檢查它們的真實需求;然後發現大多數應用程序只是需要單行事務處理。在人們需要分佈式的事務處理的地方,最重要的使用時用來維護二級索引,因此我們增加了一個特殊的機制去滿足這個需求。新的機制在通用性上比分佈式事務差很多,但是更高效(特別是在更新遍佈上百行或者更多數據的時候),而且與我們積極“跨數據中心”備份模式交互的很好。

我們從支持Bigtable中得到的一個實際的經驗就是,適當的系統級監控是非常重要的(比如,既監控Bigtable自身,也監控使用Bigtable的客戶程序)。比如,我們擴展了我們的RPC系統,因此對於一個RPC的例子,它詳細記錄了代表RPC的很多重要操作。這個特性允許我們檢測和修正很多的問題,比如tablet數據結構上的鎖的競爭、在提交Bigtable修改操作時對GFS的寫入非常慢的問題、以及在元數據表的tablet不可用時,元數據表無法訪問的問題。關於監控的用途的另外一個例子是,每個Bigtable集羣都在Chubby中註冊了。這可以允許我們追蹤所有集羣,發現它們的大小、檢查運行的我們軟件的版本、他們接收的流量,以及檢查是否有類似於意外高延遲等問題。

我們得到的最寶貴的經驗是簡單設計的價值。考慮到我們系統的代碼量(大約100000行生產代碼(alex注:non-test code)),以及隨着時間的推移,代碼以難以預料的方式演變的現實,我們發現清晰的設計和編碼給維護和調試帶來的巨大幫助。這方面的一個例子是我們的tablet服務器成員協議。我們第一版的協議很簡單:maste週期性地和tablet服務器簽訂租約,tablet服務器在租約過期自動退出。不幸的是,這個協議在網絡問題面前大大降低系統的可用性,並且對master服務器恢復時間很敏感。我們多次重新設計這個協議,直到它表現優異。然而,最終的協議太複雜,並且依賴一些Chubby很少被其他應用程序運用的特性的行爲。我們發現我們花費了過量的時間調試一些古怪的邊角問題(obscure corner cases) ,不僅在Bigtable代碼中,也在Chubby代碼中。最後,我們廢棄了這個協議,轉向了一個新的簡單的協議,該協議僅僅依賴最廣泛使用Chubby的特性。

10  相關工作

Boxwood24】項目的有些組件在某些方面和ChubbyGFS 以及Bigtable重疊,因爲它也提供了諸如分佈式協議、鎖、分佈式塊存儲以及分佈式B-tree存儲。在有重疊部分的實例中,看來Boxwood組件的定位要比相應Google低。Boxwood項目的目標是爲諸如文件系統、數據庫等高級服務提供基礎架構,然而Bigtable的目標是直接爲希望存儲數據的客戶程序提供支持。

許多近期的項目已經處理了很多難題,例如在廣域網上提供了分佈式存儲或者高級服務,通常是“Internet規模”的。這其中包括了分佈式的Hash表方面的工作,這項工作由一些諸如CAN29】、Chord32】、Tapestry37】和Pastry30】的項目發起。這些系統的強調的關注點不是由於Bigtable出現的,比如高度變化的帶寬、不可信的參與者、頻繁的更改配置等;去中心化和拜占庭式容錯(alex注:Byzantine,即拜占庭式的風格,也就是一種複雜詭祕的風格。Byzantine Fault表示:對於處理來說,當發錯誤時處理器並不停止接收輸入,也不停止輸出,錯就錯了,只管算,對於這種錯誤來說,這樣可真是夠麻煩了,因爲用戶根本不知道錯誤發生了,也就根本談不上處理錯誤了。在多處理器的情況下,這種錯誤可能導致運算正確結果的處理器也產生錯誤的結果,這樣事情就更麻煩了,所以一定要避免處理器產生這種錯誤。)也不是Bigtable的目標。

就提供給應用程序開發者的分佈式數據存儲模型而言,我們相信,分佈式B-Tree或者分佈式Hash表提供的鍵值對模型有很大的侷限性。鍵值對模型是很有用的組件,但是它們不應該是提供給開發者唯一的組件。我們選擇的模型比簡單的鍵值對豐富的多,它支持稀疏的、半結構化的數據。儘管如此,它也足夠簡單,可以標榜爲高效普通文件的代表(it lends itself to a very efficient flat-file representation);它也是透明的(通過局部性羣組),允許我們的使用者對系統的重要行爲進行調整。

有些數據庫廠商已經開發了並行的數據庫系統,能夠存儲海量的數據。Oracle的實時應用集羣數據庫RAC27】使用共享磁盤存儲數據(Bigtable使用GFS),並且有一個分佈式的鎖管理系統(Bigtable使用Chubby)。IBM DB2並行版本【4】基於一種類似於Bigtable的、無共享的架構(a shared-nothing architecture)【33】。每個DB2的服務器都負責處理存儲在一個本地關係型數據庫中的表中的行的一個子集。這兩種產品都提供了一個帶有事務功能的完整的關係模型。

Bigtable的局部性羣組認識到了類似於壓縮和磁盤讀取方面性能的收益,這是從其他系統觀察到的,這些系統以基於列而不是行的存儲方式組織數據,這種系統包括C-Store134】、商業產品例如Sybase IQ1536】、SenSage31】、KDB+22】,以及MonetDB/X10038】的ColumnDM存儲層。另外一種在普通文件中進行垂直和水平分區並獲得很好的數據壓縮率的系統是AT&TDaytona數據庫【19】。局部性羣組不支持Ailamaki系統中描述的CPU級緩存優化【2】。

Bigtable採用memtableSSTable存儲對tablet的更新的方法與Log-Structured Merge Tree26】存儲索引數據更新的方法類似。這兩個系統中,排序的數據在寫入到磁盤前都先緩衝在內存中,讀取操作必須從內存和磁盤中合併數據。

C-StoreBigtable共享很多特點:兩個系統都採用無共享架構,都有兩種不同的數據結構,一種用於當前的寫操作,另外一種存放“長時間使用”的數據,並且提供一種機制將數據從一種格式轉換爲另一種。兩個系統在API上有很大的不同:C-Store操作更像關係型數據庫,而Bigtable提供跟低層次的讀寫接口,並且設計用來支持每臺服務器每秒數千次操作。C-Store也是個“讀性能優化的關係型數據庫管理系統”,然而Bigtable對密集型讀寫應用提供了很好的性能。

Bigtable的負載均衡器也必須解決無共享數據庫面對的一些類似的負載和內存均衡問題(比如,【1135】)。我們的問題在某種程度上簡單一些:(1)我們不需要考慮同一份數據可能有多個副本的問題,同一份數據可能由於視圖或索引的原因以替換的形式表現出來;(2)我們讓用戶決定哪些數據應該放在內存裏、哪些放在磁盤上,而不是試圖動態的決斷;(3)我們的不用執行復雜的查詢或者對其進行優化。

11  結論

我們已經講述完了BigtableGoogle中一個存儲結構化數據的分佈式系統。Bigtable的集羣從20054月開始已經投入產業使用了,在此之前我們花了大約7-年設計和實現這個系統。截止到20068月,有超過60個項目使用Bigtable。我們的用戶對Bigtable實現提供的高性能和高可用性很滿意,隨着時間的推移,當資源需求改變時,他們可以通過簡單的多增加機器來擴展集羣的容量。

考慮到Bigtable的編程接口並不常見,一個有趣的問題是:我們的用戶適應它有多難?新的使用者有時不太確定如何最好地使用Bigtable接口,特別是如果他們已經習慣於支持通用目的的事務處理的關係型數據庫。然而,許多Google產品都成功的使用了Bigtable的事實證明,我們的設計在實踐中行之有效。

我們現在正在實現對Bigtable加入一些新的特性,比如支持二級索引,以及多master副本的、跨數據中心複製的Bigtable的基礎架構。我們也已經開始將Bigtable部署爲服務供其它的產品團隊使用,這樣個人團隊就不需要維護他們自己的Bigtable集羣了。隨着我們服務集羣的擴大,我們需要在Bigtable系統內部處理更多的資源共享問題了【35 】。

最後,我們發現,建設Google自己的存儲解決方案有很多重大優勢。通過爲Bigtable設計我們自己的數據模型,我們獲得了很大的靈活性。另外,我們對Bigtable實現,以及Bigtable依賴的其它的Google的基礎組件的控制,意味着我們在系統出現瓶頸或效率低下的情況時,可以清除這些問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章