Bigtable:一個分佈式的結構化數據存儲系統javascript
Bigtable是一個管理結構化數據的分佈式存儲系統,它被設計用來處理海量數據:分佈在數千臺通用服務器上的PB級的數據。Google的不少項目將數據存儲在Bigtable中,包括Web索引、Google Earth、Google Finance。這些應用對Bigtable提出的要求差別很是大,不管是在數據規模(從URL到網頁到衛星圖像)仍是在響應速度上(從後端的批量處理到實時數據服務)。儘管應用需求差別很大,可是,針對全部Google這些產品,Bigtable仍是成功地提供了一個靈活的、高性能的解決方案。本文描述了Bigtable提供的簡單的數據模型,利用這個模型,用戶能夠動態的控制數據的佈局和格式;而且咱們還將描述Bigtable的設計和實現。html
在過去兩年半時間裏,咱們設計、實現並部署了一個用於管理結構化數據的分佈式的存儲系統—在Google,咱們稱之爲Bigtable。Bigtable的設計目的是可靠地適應PB級別的數據和成千上萬臺機器。Bigtable已經實現了下面的幾個目標:普遍的適用性、可擴展、高性能和高可用性。已經有超過60個Google的產品和項目在使用Bigtable,包括Google Analytics、Google Finance、Orkut、Personalized Search、Writely和Google Earth。這些產品使用Bigtable完成迥異的工做負載需求,這些需求從面向吞吐量的批處理做業到對終端用戶而言延時敏感的數據服務。它們使用的Bigtable集羣的配置也有很大的差別,從少數機器到成千上萬臺服務器,這些服務器裏最多可存儲幾百TB的數據。java
在不少方面,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)。mysql
第二節更詳細地描述了數據模型,第三節概要介紹了客戶端API;第四節簡要介紹了BigTable依賴的底層Google基礎框架;第五節描述了BigTable實現的基本原理;第6節描述了爲了提升BigTable的性能而採用的一些精細的調優方法;第7節提供了BigTable的性能數據;第8節講述了幾個Google內部使用BigTable的例子;第9節討論了咱們在設計和後期支持過程當中獲得一些經驗和教訓;最後,在第10節介紹了相關工做,第11節是咱們的結論。程序員
Bigtable是一個稀疏的、分佈式的、持久化存儲的多維度排序Map(alex注:對於程序員來講,Map應該不用翻譯了吧。Map由key和value 組成,後面咱們直接使用key和value,再也不另外翻譯了)。Map由行關鍵字、列關鍵字以及時間戳索引;Map中的每一個value都是一個未經解析的字節數組。web
(row:string, column:string,time:int64)->string正則表達式
咱們在仔細分析了一個相似Bigtable的系統的種種潛在用途以後,決定選用這個數據模型。咱們先舉個具體的例子,這個例子促使咱們作了不少設計決策;假設咱們想要備份海量的網頁及相關信息,這些數據能夠用於不少不一樣的項目,咱們姑且稱這個特殊的表爲Webtable。在Webtable裏,咱們使用URL做爲行關鍵字,使用網頁的各類屬性(aspect)做爲列名,網頁的內容存在「contents:」列中,並用獲取該網頁的時間戳做爲標識(alex注:即按照獲取時間不一樣,存儲了多個版本的網頁數據),如圖一所示。算法
圖一:一個存儲Web網頁的例子的表的片段。行名是一個反向URL。contents列族容納的是網頁的內容,anchor列族容納引用該網頁的錨連接文本。CNN的主頁被Sports Illustrater和MY-look的主頁引用,所以該行包含了名爲「anchor:cnnsi.com」和「anchhor:my.look.ca」的列。每一個錨連接數據項只有一個版本(alex注:注意時間戳標識了列的版本,t9和t8分別標識了兩個錨連接的版本);而contents列則有三個版本,分別由時間戳t3,t5,和t6標識。sql
表中的行關鍵字是任意字符串(目前支持最大64KB的字符串,可是對大多數用戶,10-100個字節就足夠了)。在單一行關鍵字下的每個讀或者寫操做都是原子的(無論在這一行裏被讀或者寫的不一樣列的數目),這個設計決策可以使用戶很容易地推測(reason about)對同一個行進行併發更新操做時的系統行爲。數據庫
Bigtable經過行關鍵字的字典順序來維護數據。表中必定範圍內的行被動態分區。每一個分區叫作一個」Tablet」,Tablet是數據分佈和負載均衡的單位。這樣作的結果是,讀取必定範圍內的少數行很高效,而且每每只須要跟少數機器通訊。用戶能夠經過選擇他們的行關鍵字來開發這種特性,這樣能夠爲他們的數據訪問得到好的本地性(get good locality)。舉例來講,咱們在關鍵字com.google.maps/index.html的索引下爲maps.google.com/index.htm存儲數據。把相同的域中的網頁存儲在連續的區域可讓一些主機和域名的分析更加有效。
列關鍵字組成的集合叫作「列族「,列族構成了訪問控制的基本單位。存放在同一列族下的全部數據一般都屬於同一個類型(咱們把同一個列族下的數據壓縮在一塊兒)。列族必須先建立,而後才能在列族中任何的列關鍵字下存放數據;列族建立後,其中的任何一個列關鍵字下均可以存放數據。咱們的意圖是,一張表中不一樣列族的數目要小(最多幾百個),而且列族在操做中不多改變。與此相反,一張表能夠有無限多個列。
列關鍵字的命名語法以下:列族:限定詞。列族的名字必須是可打印的字符串,可是限定詞能夠是任意字符串。好比,Webtable有個列族language,用來存放撰寫網頁的語言。咱們在language列族中只使用一個列關鍵字,用來存放每一個網頁的語言標識ID。Webtable中另外一個有用的列族是anchor;這個列族的每個列關鍵字表明單獨一個錨連接,如圖一所示。限定詞是引用該網頁的站點名;數據項內容是連接文本。
訪問控制、磁盤和內存的計數都是在列族層面進行的。在咱們的Webtable的例子中,上述的控制權限能幫助咱們管理不一樣類型的應用:一些應用能夠添加新的基本數據、一些能夠讀取基本數據並建立派生的列族、一些則只容許瀏覽現存數據(甚至可能由於隱私的緣由不能瀏覽全部現存列族)。
在Bigtable中,每個數據項均可以包含同一數據的不一樣版本;這些版本經過時間戳來索引。Bigtable時間戳是64位整型數。時間戳可由Bigtable指定,這種狀況下時間戳表明精確到毫秒的「實時」時間,或者該值由庫戶程序明確指定。須要避免衝突的程序必須本身生成一個惟一的時間戳。數據項中不一樣版本按照時間戳倒序排列,因此最新的版本能夠被先讀到。
爲了減輕多個版本數據的管理負擔,咱們對每個列族提供兩個設置參數,Bigtable經過這兩個參數能夠對廢棄版本的數據進行自動垃圾收集。用戶既能夠指定只保存最後n個版本的數據,也能夠只保存「足夠新」的版本的數據(好比,只保存最近7天的內容寫入的數據)。
在咱們的例子Webtable中,咱們將存儲在contents:列中爬蟲通過的頁面的時間戳設置爲這個版本的頁面被實際爬過的時(alex注:contents:列存儲的時間戳信息是網絡爬蟲抓取一個頁面的時間)。上面說起的垃圾收集機制可讓咱們只保留每一個網頁的最近三個版本。
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】書寫。目前,咱們基於Sawzall的API還不容許客戶腳本將數據寫回Bigtable,可是它容許多種形式的數據轉換、基於任意表達式的數據過濾、以及經過多種操做符的彙總概括。
Bigtable能夠和MapReduce【12】一塊兒使用,MapReduce是Google開發的運行大規模並行計算的框架。咱們已經開發了一套封裝器(wrapper),這些封裝器使Bigtable既能夠做爲MapReduce做業的源輸入也能夠做爲目標輸出輸出。
Bigtable是創建在一些其餘Google基礎架構之上的。BigTable使用Google分佈式文件系統(GFS)【17】存儲日誌和數據文件。BigTable集羣每每運行在一個共享的機器池中,池中的機器還會運行其它各類各樣的分佈式應用程序,BigTable的進程常常要和其它應用的進程共享機器。BigTable依賴集羣管理系統在共享機器上調度做業、管理資源、處理機器的故障、以及監視機器的狀態。
BigTable數據在內部使用Google SSTable文件格式存儲。SSTable提供一個從鍵(key)到值(value)的持久化的、已排序、不可更改的映射(Map),這裏的key和value 的都是任意的字節(Byte)串。對SSTable提供了以下操做:查詢與一個指定key值相關的value,或者遍歷指定key值範圍內的全部鍵值對。從內部看,SSTable是一連串的數據塊(一般每一個塊的大小是64KB,可是這個大小是能夠配置的)。SSTable使用塊索引(一般存儲在SSTable 的最後)來定位數據塊;在打開SSTable的時候,索引被加載到內存。一次查找能夠經過一次磁盤搜索完成:首先執行二分查找在內存索引裏找到合適數據塊的位置,而後在從硬盤中讀取合適的數據塊。也能夠選擇把整個SSTable都映射到內存中,這樣就能夠在不用訪問硬盤的狀況下執行查詢搜索了。
BigTable還依賴一個高可用的、持久化的分佈式鎖服務組件,叫作Chubby【8】。一個Chubby服務包括了5 個活動的副本,其中一個副本被選爲Master,而且積極處理請求。只有在大多數副本正常運行,而且彼此之間可以互相通訊的狀況下,Chubby服務纔是可用的。當有副本失效的時候,出現故障時Chubby使用Paxos算法【9,23】保證副本的一致性。Chubby提供了一個名字空間,裏面包括了目錄和小文件。每一個目錄或者文件能夠當成一個鎖使用,對文件的讀寫操做都是原子的。Chubby客戶程序庫提供對Chubby文件的一致性緩存。每一個Chubby客戶程序都維護一個與Chubby服務的會話。若是客戶程序不能在租約到期的時間內從新簽定會話租約,這個會話就過時失效了(A client’s 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就會失效。最近咱們在跨越11個Chubby服務實例的14個BigTable集羣上測量了這個影響。Bigtable服務器時鐘的平均比率是0.0047%,在這期間因爲Chubby不可用而致使BigTable中的部分數據不能訪問(Chubby不能訪問的緣由多是Chubby自己失效或者網絡問題)。單個集羣裏受Chubby失效影響最大的百分比是0.0326%。
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的大小大約是100MB到200MB。
咱們使用一個三層的、相似於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提供服務)。這些信息有助於排除故障和性能分析。
每一個tablet一次分配給一個tablet服務器。master服務器記錄活躍的tablet服務器、當前tablet到tablet服務器的分配、包括哪些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提供服務,而且儘快從新分配那些tablet。master經過輪詢tablet服務器鎖的狀態來探測tablet服務器什麼時候再也不爲tablet提供服務。若是一個tablet服務器報告它丟失了鎖,或者master最近幾回嘗試都沒法和該服務器通訊,master就會嘗試獲取該tablet服務器文件的獨佔鎖;若是master可以獲取獨佔鎖,那麼就說明Chubby是正常運行的,而tablet 服務器要麼是宕機了、要麼是不能和Chubby通訊了,所以,爲了保證該tablet服務器不能再提供服,master就刪除該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服務器掃描元數據表獲取tablet的集合。只要掃描發現了一個尚未分配的tablet,master就將這個tablet加入未分配的tablet 集合,該集合使該talbet有機會參與talbet分配。
有一種複雜狀況是:元數據tablet尚未被分配以前是不可以掃描它的。所以,在開始掃描以前(步驟4),若是在第三步中沒有發現對root tablet的分配,master就把root tablet加入到未分配的tablet集合中。這個附加操做確保了root tablet會被分配。因爲root tablet包括了全部元數據tablet的名字,master在掃描完root tablet之後才瞭解全部元數據信息。
現存tablet的集合只有在如下事件發生時纔會改變:創建了一個新表或者刪除了一箇舊表、兩個現存tablet合併組成一個大的tablet、或者一個現存tablet被分割成兩個小的tablet。master能夠跟蹤這些改變,由於除了最後一個事件外的兩個事件都是由它初始化的。tablet分割事件須要特殊處理,由於它是由tablet服務器初始化的。tablet服務器經過在元數據表中爲新的tablet記錄信息的方式提交分割操做。在分割操做提交以後tablet服務器會通知master。假如分割操做通知丟失(tablet服務器或者master宕機),master在請求tablet服務器裝載已經被分割的tablet的時候會探測到一個新的tablet。因爲在元數據tablet中發現的tablet條目只是列舉了master請求加載的tablet的一部分,tablet服務器會通知master分割信息。
如圖5所示,tablet的持久化狀態信息保存在GFS上。更新操做提交到存儲撤銷(REDO)記錄的提交日誌中。在這些更新操做中,最近提交的那些存放在一個叫作memtable的排序的緩衝區中;較早的更新存放在一系列SSTable中。爲了恢復一個tablet,tablet服務器在元數據表中讀取它的元數據。這些元數據包含組成一個tablet的SSTable列表和一組還原點(redo points),這些點是指向包含tablet數據的任一提交日誌的指針。tablet服務器把SSTable的索引讀進內存,以後經過應用還原點以後提交的全部更新來重構memtable。
當寫操做到達tablet服務器時,tablet服務器首先要檢查這個操做格式是否正確、發送者是否有執行這個改變的權限。權限驗證是經過從一個Chubby文件裏讀取具備寫權限的操做者列表來進行的(這個文件幾乎總會在Chubby客戶緩存裏命中)。有效的修改操做會記錄在提交日誌裏。能夠採用組提交方式(alex注:group commit)來提升大量小的修改操做的吞吐量【13,16】。當一個寫操做提交後,它的內容被插入到memtable裏面。
當讀操做到達tablet服務器時,它一樣會檢查良構性和適當的權限。一個有效的讀操做在一個由一系列SSTable和memtable合併的視圖裏執行。因爲SSTable和memtable是按字典排序的數據結構,所以能夠高效生成合並視圖。
當進行tablet的合併和分割時,引入(incoming)的讀寫操做可以繼續進行。
(alex注:這個詞挺簡單,可是在這節裏面挺難翻譯的。應該是空間縮減的意思,可是彷佛又不能徹底歸納它在上下文中的意思,乾脆,不翻譯了)
隨着寫操做的執行,memtable的大小不斷增長。當memtable的尺寸到達一個臨界值的時候,這個memtable就會被凍結,而後建立一個新的memtable;被凍結住memtable會被轉換成SSTable並寫入GFS(alex注:咱們稱這種Compaction行爲爲Minor Compaction)。Minor Compaction過程有兩個目的:一是收縮tablet服務器內存使用,二是在服務器災難恢復過程當中,減小必須從提交日誌裏讀取的數據量。在Compaction過程當中,引入(incoming)的讀寫操做仍能繼續。
每一次Minor Compaction都會建立一個新的SSTable。若是這個行爲未經檢查地持續下去,讀操做可能須要合併來任意個SSTable的更新;反之,咱們經過按期在後臺執行Merging Compaction 過程限制這類文件(shijin:SStable)的數量。Merging Compaction過程讀取一些SSTable和memtable的內容,輸出一個新的SSTable。只要Merging Compaction過程完成了,做爲輸入的SSTable和memtable就能夠丟棄了。
重寫全部的SSTable到一個新的SSTable的Merging 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 Compaction。Major Compaction機制容許Bigtable回收已經刪除的數據使用的資源,而且確保已刪除的數據及時從系統內消失(alex注:實際是回收資源。數據刪除後,它佔有的空間並不能立刻重複利用;只有空間回收後才能重複使用),這對存儲敏感數據的服務是很是重要的。
上一節描述的實現須要不少優化來達到用戶要求的高性能、高可用性和高可靠性的目標。爲了突出這些優化,本節描述了部分實現的詳細細節。
客戶程序能夠將多個列族組合成一個局部性羣族。對每一個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 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服務器代碼而言(to tablet server code),掃描緩存是第一級緩存,其緩存SSTable接口返回的鍵值對;Block緩存是二級緩存,其緩存從GFS讀取的SSTable塊。對於趨向於重複讀取相同數據的應用程序來講,掃描緩存很是有效;對於趨向於讀取剛讀過的數據附近的數據的應用程序來講,Block緩存頗有用(例如,順序讀,或者在一個熱點的行的同一局部性羣組中隨機讀取不一樣的列)。
(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次(每一個服務器讀取一次)。
爲了不重複讀取日誌文件,咱們首先把提交日誌的條目按照關鍵字(table,row name,log sequence number)排序。排序以後,對一個特定tablet的修改操做連續存放,所以,隨着一次詢盤操做以後的順序讀取,修改操做的讀取將更高效。爲了並行排序,咱們將日誌文件分割成64MB的段,以後在不一樣的tablet服務器對每段進行並行排序。這個排序過程由master來協調,而且當一個tablet服務器指出它須要從一些提交日誌文件中回覆修改時排序被初始化。
在向GFS寫提交日誌時有時引發性能顛簸,緣由是多種多樣的(好比,寫操做相關GFS服務器崩潰;或者穿過到達特定組合的三個GFS服務器的網絡擁塞或者過載)。爲了使修改操做免受GFS瞬時延遲的影響,每一個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進行垃圾收集的問題了。每一個tablet的SSTable都在註冊在元數據表中。Master將廢棄的SSTable做爲對SSTable集合的「標記-清除」的垃圾回收而刪除【25】,元數據表則保存了root的集合。
最後,SSTable的不變性使得分割tablet的操做很是快捷。與爲每一個子tablet生成新的SSTable集合相反,咱們讓子tablet共享父tablet的SSTable。
咱們創建一個包括N臺tablet服務器的Bigtable集羣,經過改變N的值來測量Bigtable的性能和可擴展性。tablet服務器配置了1GB的內存,數據寫入到一個包含1786臺機器、每臺機器有2個400G IDE硬盤驅動的GFS單元上。N臺客戶機生成測試Bigtable工做負載。(咱們使用和tablet服務器相同數目的客戶機以確保客戶機不會成爲瓶頸。)每臺機器有主頻2GHZ的雙核Opteron處理器,配置了足以容納全部進程工做集的物理內存,以及一張千兆以太網卡。這些機器都分配到一個兩層的、樹狀的交換網絡裏,在根節點上的可用總帶寬大約100-200Gbps。全部的機器採用相同的主機設備,所以,任何兩臺機器間的往返時間都小於1ms。
Tablet 服務器、master、測試客戶機、以及GFS服務器都運行在同一組機器上。每臺機器都運行一個GFS服務器。其它的機器要麼運行tablet服務器、要麼運行客戶程序、要麼運行在測試過程當中,同時使用這個機器池的其它做業的進程。
R是與測試相關的相異Bigtable行關鍵字的數量。咱們精心選擇R值,保證每次基準測試對每臺tablet服務器讀/寫的數據量都在1GB左右。
在序列寫的基準測試中,咱們使用的列關鍵字的名字從0到R-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服務器的性能。隨機讀的性能比其它全部操做慢一個數量級或以上(by the order of magnitude or more)。每一個隨機讀操做都涉及經過網絡從GFS傳輸64KB的SSTable到tablet服務器,這其中只有一個1000-byte的值被用到。Tablet服務器每秒大約執行1200次讀操做,也就是說每秒大約從GFS讀取75MB(64*1200/1024)的數據。因爲網絡協議層的消耗、SSTable解析、以及BigTable代碼執行,這個傳輸帶寬足以佔滿tablet服務器的CPU,這個帶寬也幾乎足以佔滿咱們系統中使用的網絡連接。大多數採用這種訪問模式的BigTable應用程序減少Block到一個很小的值,一般取8KB。
從內存隨機讀速度快不少,緣由是這樣的,每一個1000-byte的讀操做都是由tablet服務器的本地內存知足的,不須要從GFS讀取64KB的大數據塊。
隨機和序列寫操做性能比隨機讀要好些,緣由是每一個tablet服務器把寫入的內容追加到一個提交日誌上,而且採用批量提交的方式,高效地流式寫入GFS。隨機寫和序列寫的性能沒有顯著的差異,兩種方式下對tablet服務器的寫操做都記錄到同一提交日誌中。
序列讀的性能好於隨機讀,緣由是每次從GFS獲取的64KB的SSTable數據塊會緩存到Block緩存中,這些緩存用來服務下一次64KB的讀請求。
掃描的性能更高,這是因爲tablet服務器響應一次客戶RPC就會返回大量值,因此,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 benchmark progresses)。
隨機讀基準測試在擴大規模後表現最差(總體吞吐量只提高了100倍,而服務器的數量卻增長了500倍)。這種現象的出現是由於(就像上面解釋的那樣)每讀1000-byte咱們都會在網絡上傳輸一個64KB大的塊。這樣的網絡傳輸消耗了咱們網絡中各類共享的1GB的鏈路,結果致使隨着咱們增長服務器的數量,每臺服務器上的吞吐量急劇降低。
截止到2006年8月有388個非測試用的Bigtable集羣運行在各類各樣的Google機器集羣上,合計大約有24500個tablet服務器。表1顯示了每一個集羣上tablet服務器的大體分佈狀況。這些集羣中,許多用於開發目的,所以在引人注意的一段時期內比較空閒。經過觀察一個由14個忙碌集羣、8069個tablet服務器組成的羣組,咱們看到總體的流量超過了每秒120萬次請求,發送到系統的RPC請求致使的網絡負載達到了741MB/s,系統發出的RPC請求網絡負載大約是16GB/s。
表2 提供了一些目前正在使用的表的相關數據。一些表存儲的是服務用戶的數據,然而另外一些存儲的則是用於批處理的數據;這些表在總的大小、平均數據項大小、從內存中讀取的數據的比例、表的模式的複雜程度上都有很大的差異。本節的其他部分,咱們將主要描述三個產品研發團隊如何使用Bigtable的。
Google Analytics是用來幫助Web站點的管理員在他們網站上分析流量模式的服務。它提供了總體情況的統計數據,好比天天的獨立訪問的用戶數量、天天每一個URL的瀏覽量;它還提供了站點追蹤報告,好比假定用戶以前訪問了一個指定頁面,購買商品的用戶的比例。
爲了使用這個服務,Web站點的管理員須要在他們的Web頁面中嵌入一小段JavaScript腳本。這個Javascript程序在頁面被訪問的時候調用。它記錄了各類Google Analytics須要的信息,好比用戶的標識、獲取的網頁的相關信息。Google Analytics彙總這些數據,以後提供給Web站點的管理員。
咱們簡略描述Google Analytics使用的兩個表。原始點擊Raw Click表(200TB)爲每個終端用戶會話維護一行數據。行的名字是一個包含Web站點名字以及用戶會話建立時間的元組。這種模式保證了訪問同一個Web站點的會話是連續的,會話按時間順序存儲。這個表壓縮到原來尺寸的14%。
彙總表summary(20TB)包含了每一個Web站點的、各類類型的預約義彙總信息。經過週期性地調度MapReduce做業,從raw click表中生成summary表的數據。每一個MapReduce做業從raw click表中提取最新的會話數據。系統的總體吞吐量受限於GFS的吞吐量。這個表的壓縮到原有尺寸的29%。
Google運轉着一批爲用戶提供高分辨率地球表面衛星圖像的服務,既能夠經過基於Web的Google Maps接口(maps.google.com),也能夠經過Google Earth可定製客戶端軟件訪問。這些軟件產品容許用戶瀏覽地球表面:用戶能夠在許多不一樣的分辨率下平移、查看和註釋這些衛星圖像。這個系統使用一個表存儲預處理數據,使用另一組表存儲用戶數據。
預處理流水線使用一個表存儲原始圖像。在預處理過程當中,圖像被清除,而後被合併到最終的服務數據中。這個表包含了大約70TB的數據,所以須要從磁盤讀取數據。圖像已經被高效壓縮過了,所以Bigtable壓縮被禁用。
Imagery表的每一行與一個單獨的地理區塊對應。行都有名稱,以確保毗鄰的區域存儲在了一塊兒。Imagery表中有一個記錄每一個區塊的數據源的列族。這個列族包含了大量的列:基本上是一個原始數據圖像一列。因爲每一個區塊都是由不多的幾張圖片構成的,所以這個列族是很是稀疏的。
預處理流水線高度依賴運行在Bigtable上的MapReduce傳輸數據。在一些MapReduce做業中,整個系統中每臺tablet服務器的處理速度是1MB/s。
這個服務系統使用一個表來索引GFS中的數據。這個表相對較小(500GB),可是這個表必須在低延遲下,針對每一個數據中心每秒處理幾萬個查詢請求。所以,這個表必須存儲在上百個tablet服務器上,而且包含in-memory的列族。
個性化查詢(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的配額機制),限制任意特定客戶在共享表中消耗的空間;該機制也爲使用該系統存儲用戶級信息的產品團體提供了隔離。
在設計、實現、維護和支持Bigtable的過程當中,咱們獲得了有用的經驗和一些有趣的教訓。
一個教訓是,咱們發現,不少類型的錯誤都會致使大型分佈式系統受損,不只僅是一般的網絡中斷、或者不少分佈式協議中設想的fail-stop錯誤(alex注:fail-stop failture,指一旦系統fail就stop,不輸出任何數據;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的特性。
Boxwood【24】項目的有些組件在某些方面和Chubby、GFS 以及Bigtable重疊,由於它也提供了諸如分佈式協議、鎖、分佈式塊存儲以及分佈式B-tree存儲。在有重疊部分的實例中,看來Boxwood組件的定位要比相應Google低。Boxwood項目的目標是爲諸如文件系統、數據庫等高級服務提供基礎架構,然而Bigtable的目標是直接爲但願存儲數據的客戶程序提供支持。
許多近期的項目已經處理了不少難題,例如在廣域網上提供了分佈式存儲或者高級服務,一般是「Internet規模」的。這其中包括了分佈式的Hash表方面的工做,這項工做由一些諸如CAN【29】、Chord【32】、Tapestry【37】和Pastry【30】的項目發起。這些系統的強調的關注點不是因爲Bigtable出現的,好比高度變化的帶寬、不可信的參與者、頻繁的更改配置等;去中心化和拜占庭式容錯(alex注:Byzantine,即拜占庭式的風格,也就是一種複雜詭祕的風格。Byzantine Fault表示:對於處理來講,當發錯誤時處理器並不中止接收輸入,也不中止輸出,錯就錯了,只管算,對於這種錯誤來講,這樣可真是夠麻煩了,由於用戶根本不知道錯誤發生了,也就根本談不上處理錯誤了。在多處理器的狀況下,這種錯誤可能致使運算正確結果的處理器也產生錯誤的結果,這樣事情就更麻煩了,因此必定要避免處理器產生這種錯誤。)也不是Bigtable的目標。
就提供給應用程序開發者的分佈式數據存儲模型而言,咱們相信,分佈式B-Tree或者分佈式Hash表提供的鍵值對模型有很大的侷限性。鍵值對模型是頗有用的組件,可是它們不該該是提供給開發者惟一的組件。咱們選擇的模型比簡單的鍵值對豐富的多,它支持稀疏的、半結構化的數據。儘管如此,它也足夠簡單,能夠標榜爲高效普通文件的表明(it lends itself to a very efficient flat-file representation);它也是透明的(經過局部性羣組),容許咱們的使用者對系統的重要行爲進行調整。
有些數據庫廠商已經開發了並行的數據庫系統,可以存儲海量的數據。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存儲對tablet的更新的方法與Log-Structured Merge Tree【26】存儲索引數據更新的方法相似。這兩個系統中,排序的數據在寫入到磁盤前都先緩衝在內存中,讀取操做必須從內存和磁盤中合併數據。
C-Store和Bigtable共享不少特色:兩個系統都採用無共享架構,都有兩種不一樣的數據結構,一種用於當前的寫操做,另一種存放「長時間使用」的數據,而且提供一種機制將數據從一種格式轉換爲另外一種。兩個系統在API上有很大的不一樣:C-Store操做更像關係型數據庫,而Bigtable提供跟低層次的讀寫接口,而且設計用來支持每臺服務器每秒數千次操做。C-Store也是個「讀性能優化的關係型數據庫管理系統」,然而Bigtable對密集型讀寫應用提供了很好的性能。
Bigtable的負載均衡器也必須解決無共享數據庫面對的一些相似的負載和內存均衡問題(好比,【11,35】)。咱們的問題在某種程度上簡單一些:(1)咱們不須要考慮同一份數據可能有多個副本的問題,同一份數據可能因爲視圖或索引的緣由以替換的形式表現出來;(2)咱們讓用戶決定哪些數據應該放在內存裏、哪些放在磁盤上,而不是試圖動態的決斷;(3)咱們的不用執行復雜的查詢或者對其進行優化。
咱們已經講述完了Bigtable,Google中一個存儲結構化數據的分佈式系統。Bigtable的集羣從2005年4月開始已經投入產業使用了,在此以前咱們花了大約7人-年設計和實現這個系統。截止到2006年8月,有超過60個項目使用Bigtable。咱們的用戶對Bigtable實現提供的高性能和高可用性很滿意,隨着時間的推移,當資源需求改變時,他們能夠經過簡單的多增長機器來擴展集羣的容量。
考慮到Bigtable的編程接口並不常見,一個有趣的問題是:咱們的用戶適應它有多難?新的使用者有時不太肯定如何最好地使用Bigtable接口,特別是若是他們已經習慣於支持通用目的的事務處理的關係型數據庫。然而,許多Google產品都成功的使用了Bigtable的事實證實,咱們的設計在實踐中行之有效。
咱們如今正在實現對Bigtable加入一些新的特性,好比支持二級索引,以及多master副本的、跨數據中心複製的Bigtable的基礎架構。咱們也已經開始將Bigtable部署爲服務供其它的產品團隊使用,這樣我的團隊就不須要維護他們本身的Bigtable集羣了。隨着咱們服務集羣的擴大,咱們須要在Bigtable系統內部處理更多的資源共享問題了【3,5 】。
最後,咱們發現,建設Google本身的存儲解決方案有不少重大優點。經過爲Bigtable設計咱們本身的數據模型,咱們得到了很大的靈活性。另外,咱們對Bigtable實現,以及Bigtable依賴的其它的Google的基礎組件的控制,意味着咱們在系統出現瓶頸或效率低下的狀況時,能夠清除這些問題。