轉載自:http://blog.sina.com.cn/s/blog_6a0e086b0100mpig.html
Bigtable:A Distributed System Storage System for Structured Data
Bigtable:一個分佈式的結構化數據存儲系統
摘要
1 介紹
2 數據模型
(row:string, column:string,time:int64)->string
行
列族
列關鍵字組成的集合叫做“列族“,列族是訪問控制的基本單位。存放在同一列族下的所有數據通常都屬於同一個類型(我們可以把同一個列族下的數據壓縮在一起)。列族在使用之前必須先創建,然後才能在列族中任何的列關鍵字下存放數據;列族創建後,其中的任何一個列關鍵字下都可以存放數據。根據我們的設計意圖,一張表中的列族不能太多(最多幾百個),並且列族在運行期間很少改變。與之相對應的,一張表可以有無限多個列。
列關鍵字的命名語法如下:列族:限定詞。列族的名字必須是可打印的字符串,而限定詞的名字可以是任意的字符串。比如,Webtable有個列族language,language列族用來存放撰寫網頁的語言。我們在language列族中只使用一個列關鍵字,用來存放每個網頁的語言標識ID。Webtable中另一個有用的列族是anchor;這個列族的每一個列關鍵字代表一個錨鏈接,如圖一所示。Anchor列族的限定詞是引用該網頁的站點名;Anchor列族每列的數據項存放的是鏈接文本。
訪問控制、磁盤和內存的使用統計都是在列族層面進行的。在我們的Webtable的例子中,上述的控制權限能幫助我們管理不同類型的應用:我們允許一些應用可以添加新的基本數據、一些應用可以讀取基本數據並創建繼承的列族、一些應用則只允許瀏覽數據(甚至可能因爲隱私的原因不能瀏覽所有數據)。
時間戳
在Bigtable中,表的每一個數據項都可以包含同一份數據的不同版本;不同版本的數據通過時間戳來索引。Bigtable時間戳的類型是64位整型。Bigtable可以給時間戳賦值,用來表示精確到毫秒的“實時”時間;用戶程序也可以給時間戳賦值。如果應用程序需要避免數據版本衝突,那麼它必須自己生成具有唯一性的時間戳。數據項中,不同版本的數據按照時間戳倒序排序,即最新的數據排在最前面。
爲了減輕多個版本數據的管理負擔,我們對每一個列族配有兩個設置參數,Bigtable通過這兩個參數可以對廢棄版本的數據自動進行垃圾收集。用戶可以指定只保存最後n個版本的數據,或者只保存“足夠新”的版本的數據(比如,只保存最近7天的內容寫入的數據)。
3 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. |
Scanner scanner(T); |
圖3中的C++代碼使用Scanner抽象對象遍歷一個行內的所有錨點。客戶程序可以遍歷多個列族,有幾種方法可以對掃描輸出的行、列和時間戳進行限制。例如,我們可以限制上面的掃描,讓它只輸出那些匹配正則表達式*.cnn.com的錨點,或者那些時間戳在當前時間前10天的錨點。
Bigtable還支持一些其它的特性,利用這些特性,用戶可以對數據進行更復雜的處理。首先,Bigtable支持單行上的事務處理,利用這個功能,用戶可以對存儲在一個行關鍵字下的數據進行原子性的讀-更新-寫操作。雖然Bigtable提供了一個允許用戶跨行批量寫入數據的接口,但是,Bigtable目前還不支持通用的跨行事務處理。其次,Bigtable允許把數據項用做整數計數器。最後,Bigtable允許用戶在服務器的地址空間內執行腳本程序。腳本程序使用Google開發的Sawzall【28】數據處理語言。雖然目前我們基於的Sawzall語言的API函數還不允許客戶的腳本程序寫入數據到Bigtable,但是它允許多種形式的數據轉換、基於任意表達式的數據過濾、以及使用多種操作符的進行數據彙總。
4 BigTable構件
5 介紹
5.1 Tablet的位置
第一層是一個存儲在Chubby中的文件,它包含了Root Tablet的位置信息。Root Tablet包含了一個特殊的METADATA表裏所有的Tablet的位置信息。METADATA表的每個Tablet包含了一個用戶Tablet的集合。Root Tablet實際上是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.2 Tablet分配
在任何一個時刻,一個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 kills itself)。當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.3 Tablet服務
如圖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.4 Compactions
(alex注:這個詞挺簡單,但是在這節裏面挺難翻譯的。應該是空間縮減的意思,但是似乎又不能完全概括它在上下文中的意思,乾脆,不翻譯了)
隨着寫操作的執行,memtable的大小不斷增加。當memtable的尺寸到達一個門限值的時候,這個memtable就會被凍結,然後創建一個新的memtable;被凍結住memtable會被轉換成SSTable,然後寫入GFS(alex注:我們稱這種Compaction行爲爲Minor Compaction)。Minor Compaction過程有兩個目的: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 special deletion entries that suppress deleted data in older SSTables that are still live)。而Major Compaction過程生成的SSTable不包含已經刪除的信息或數據。Bigtable循環掃描它所有的Tablet,並且定期對它們執行Major Compaction。Major Compaction機制允許Bigtable回收已經刪除的數據佔有的資源,並且確保BigTable能及時清除已經刪除的數據(alex注:實際是回收資源。數據刪除後,它佔有的空間並不能馬上重複利用;只有空間回收後才能重複使用),這對存放敏感數據的服務是非常重要。
6 優化
局部性羣組
壓縮
客戶程序可以控制一個局部性羣組的SSTable是否需要壓縮;如果需要壓縮,那麼以什麼格式來壓縮。每個SSTable的塊(塊的大小由局部性羣組的優化參數指定)都使用用戶指定的壓縮格式來壓縮。雖然分塊壓縮浪費了少量空間(alex注:相比於對整個SSTable進行壓縮,分塊壓縮壓縮率較低),但是,我們在只讀取SSTable的一小部分數據的時候就不必解壓整個文件了。很多客戶程序使用了“兩遍”的、可定製的壓縮方式。第一遍採用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服務器通過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注:different physical log files),會有大量的磁盤Seek操作。另外,由於批量提交(alex注:group commit)中操作的數目一般比較少,因此,對每個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恢復提速
利用不變性
我們在使用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 性能評估
單個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 benchmark progresses)。
隨機讀基準測試的測試結果顯示,隨機讀的性能隨Tablet服務器數量增加的提升幅度最小(整體吞吐量只提升了100倍,而服務器的數量卻增加了500倍)。這是因爲每個1000-byte的讀操作都會導致一個64KB大的Block在網絡上傳輸。這樣的網絡傳輸量消耗了我們網絡中各種共享的1GB的鏈路,結果導致隨着我們增加服務器的數量,每臺服務器上的吞吐量急劇下降。
8 實際應用
表2提供了一些目前正在使用的表的相關數據。一些表存儲的是用戶相關的數據,另外一些存儲的則是用於批處理的數據;這些表在總的大小、每個數據項的平均大小、從內存中讀取的數據的比例、表的Schema的複雜程度上都有很大的差別。本節的其餘部分,我們將主要描述三個產品研發團隊如何使用Bigtable的。
8.1 Google 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.2 Google 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-fast failture,指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-Structured Merge Tree【26】存儲索引數據更新的方法類似。這兩個系統中,排序的數據在寫入到磁盤前都先存放在內存中,讀取操作必須從內存和磁盤中合併數據產生最終的結果集。
C-Store和Bigtable有很多相似點:兩個系統都採用Shared-nothing架構,都有兩種不同的數據結構,一種用於當前的寫操作,另外一種存放“長時間使用”的數據,並且提供一種機制在兩個存儲結構間搬運數據。兩個系統在API接口函數上有很大的不同:C-Store操作更像關係型數據庫,而Bigtable提供了低層次的讀寫操作接口,並且設計的目標是能夠支持每臺服務器每秒數千次操作。C-Store同時也是個“讀性能優化的關係型數據庫”,而Bigtable對讀和寫密集型應用都提供了很好的性能。
Bigtable也必須解決所有的Shared-nothing數據庫需要面對的、類型相似的一些負載和內存均衡方面的難題(比如,【11,35】)。我們的問題在某種程度上簡單一些:(1)我們不需要考慮同一份數據可能有多個拷貝的問題,同一份數據可能由於視圖或索引的原因以不同的形式表現出來;(2)我們讓用戶決定哪些數據應該放在內存裏、哪些放在磁盤上,而不是由系統動態的判斷;(3)我們的系統中沒有複雜的查詢執行或優化工作。
11 結論
我們已經講述完了Bigtable,Google的一個分佈式的結構化數據存儲系統。Bigtable的集羣從2005年4月開始已經投入使用了,在此之前,我們花了大約7人年設計和實現這個系統。截止到2006年4月,已經有超過60個項目使用Bigtable了。我們的用戶對Bigtable提供的高性能和高可用性很滿意,隨着時間的推移,他們可以根據自己的系統對資源的需求增加情況,通過簡單的增加機器,擴展系統的承載能力。
由於Bigtable提供的編程接口並不常見,一個有趣的問題是:我們的用戶適應新的接口有多難?新的使用者有時不太確定使用Bigtable接口的最佳方法,特別是在他們已經習慣於使用支持通用事務的關係型數據庫的接口的情況下。但是,Google內部很多產品都成功的使用了Bigtable的事實證明了,我們的設計在實踐中行之有效。
我們現在正在對Bigtable加入一些新的特性,比如支持二級索引,以及支持多Master節點的、跨數據中心複製的Bigtable的基礎構件。我們現在已經開始將Bigtable部署爲服務供其它的產品團隊使用,這樣不同的產品團隊就不需要維護他們自己的Bigtable集羣了。隨着服務集羣的擴展,我們需要在Bigtable系統內部處理更多的關於資源共享的問題了【3,5】。
最後,我們發現,建設Google自己的存儲解決方案帶來了很多優勢。通過爲Bigtable設計我們自己的數據模型,是我們的系統極具靈活性。另外,由於我們全面控制着Bigtable的實現過程,以及Bigtable使用到的其它的Google的基礎構件,這就意味着我們在系統出現瓶頸或效率低下的情況時,能夠快速的解決這些問題。
Acknowledgements
We thank the anonymous reviewers, David Nagle, and our shepherd Brad Calder, for their feedback on this paper.The Bigtable system has benefited greatly from the feedback of our many users within Google. In addition,we
thank the following people for their contributions to Bigtable: Dan Aguayo, Sameer Ajmani, Zhifeng Chen,Bill Coughran, Mike Epstein, Healfdene Goguen, Robert Griesemer, Jeremy Hylton, Josh Hyman, Alex Khesin,
Joanna Kulik, Alberto Lerner, Sherry Listgarten, Mike Maloney, Eduardo Pinheiro, Kathy Polizzi, Frank Yellin,and Arthur Zwiegincew.
References
[1] ABADI, D. J., MADDEN, S. R., AND FERREIRA, M. C. Integrating compression and execution in columnoriented database systems. Proc. of SIGMOD (2006).
[2] AILAMAKI, A., DEWITT, D. J., HILL, M. D., AND SKOUNAKIS, M. Weaving relations for cache performance.In The VLDB Journal (2001), pp. 169-180.
[3] BANGA, G., DRUSCHEL, P., AND MOGUL, J. C. Resource containers: A new facility for resource management in server systems. In Proc. of the 3rd OSDI (Feb. 1999), pp. 45-58.
[4] BARU, C. K., FECTEAU, G., GOYAL, A., HSIAO, H., JHINGRAN, A., PADMANABHAN, S., COPELAND,G. P., AND WILSON, W. G. DB2 parallel edition. IBM Systems Journal 34, 2 (1995), 292-322.
[5] BAVIER, A., BOWMAN, M., CHUN, B., CULLER, D., KARLIN, S., PETERSON, L., ROSCOE, T., SPALINK, T., AND WAWRZONIAK, M. Operating system support for planetary-scale network services. In Proc. of the 1st NSDI(Mar. 2004), pp. 253-266.
[6] BENTLEY, J. L., AND MCILROY, M. D. Data compression using long common strings. In Data Compression Conference (1999), pp. 287-295.
[7] BLOOM, B. H. Space/time trade-offs in hash coding with allowable errors. CACM 13, 7 (1970), 422-426.
[8] BURROWS, M. The Chubby lock service for looselycoupled distributed systems. In Proc. of the 7th OSDI (Nov. 2006).
[9] CHANDRA, T., GRIESEMER, R., AND REDSTONE, J.Paxos made live ? An engineering perspective. In Proc. of PODC (2007).
[10] COMER, D. Ubiquitous B-tree. Computing Surveys 11, 2 (June 1979), 121-137.
[11] COPELAND, G. P., ALEXANDER, W., BOUGHTER, E. E., AND KELLER, T. W. Data placement in Bubba. In Proc. of SIGMOD (1988), pp. 99-108.
[12] DEAN, J., AND GHEMAWAT, S. MapReduce: Simplified data processing on large clusters. In Proc. of the 6th OSDI (Dec. 2004), pp. 137-150.
[13] DEWITT, D., KATZ, R., OLKEN, F., SHAPIRO, L., STONEBRAKER, M., AND WOOD, D. Implementation techniques for main memory database systems. In Proc. of SIGMOD (June 1984), pp. 1-8.
[14] DEWITT, D. J., AND GRAY, J. Parallel database systems: The future of high performance database systems. CACM 35, 6 (June 1992), 85-98.
[15] FRENCH, C. D. One size ts all database architectures do not work for DSS. In Proc. of SIGMOD (May 1995), pp. 449-450.
[16] GAWLICK, D., AND KINKADE, D. Varieties of concurrency control in IMS/VS fast path. Database Engineering Bulletin 8, 2 (1985), 3-10.
[17] GHEMAWAT, S., GOBIOFF, H., AND LEUNG, S.-T. The Google file system. In Proc. of the 19th ACM SOSP (Dec.2003), pp. 29-43.
[18] GRAY, J. Notes on database operating systems. In Operating Systems ? An Advanced Course, vol. 60 of Lecture Notes in Computer Science. Springer-Verlag, 1978.
[19] GREER, R. Daytona and the fourth-generation language Cymbal. In Proc. of SIGMOD (1999), pp. 525-526.
[20] HAGMANN, R. Reimplementing the Cedar file system using logging and group commit. In Proc. of the 11th SOSP (Dec. 1987), pp. 155-162.
[21] HARTMAN, J. H., AND OUSTERHOUT, J. K. The Zebra striped network file system. In Proc. of the 14th SOSP(Asheville, NC, 1993), pp. 29-43.
[22] KX.COM. kx.com/products/database.php. Product page.
[23] LAMPORT, L. The part-time parliament. ACM TOCS 16,2 (1998), 133-169.
[24] MACCORMICK, J., MURPHY, N., NAJORK, M., THEKKATH, C. A., AND ZHOU, L. Boxwood: Abstractions as the foundation for storage infrastructure. In Proc. of the 6th OSDI (Dec. 2004), pp. 105-120.
[25] MCCARTHY, J. Recursive functions of symbolic expressions and their computation by machine. CACM 3, 4 (Apr. 1960), 184-195.
[26] O’NEIL, P., CHENG, E., GAWLICK, D., AND O’NEIL, E. The log-structured merge-tree (LSM-tree). Acta Inf. 33, 4 (1996), 351-385.
[27] ORACLE.COM. www.oracle.com/technology/products/database/clustering/index.html.
Product page.
[28] PIKE, R., DORWARD, S., GRIESEMER, R., AND QUINLAN, S. Interpreting the data: Parallel analysis with Sawzall. Scientific Programming Journal 13, 4 (2005), 227-298.
[29] RATNASAMY, S., FRANCIS, P., HANDLEY, M., KARP, R., AND SHENKER, S. A scalable content-addressable network. In Proc. of SIGCOMM (Aug. 2001), pp. 161-172.
[30] ROWSTRON, A., AND DRUSCHEL, P. Pastry: Scalable, distributed object location and routing for largescale peer-to-peer systems. In Proc. of Middleware 2001(Nov. 2001), pp. 329-350.
[31] SENSAGE.COM. sensage.com/products-sensage.htm. Product page.
[32] STOICA, I., MORRIS, R., KARGER, D., KAASHOEK, M. F., AND BALAKRISHNAN, H. Chord: A scalable peer-to-peer lookup service for Internet applications. In Proc. of SIGCOMM (Aug. 2001), pp. 149-160.
[33] STONEBRAKER, M. The case for shared nothing. Database Engineering Bulletin 9, 1 (Mar. 1986), 4-9.
[34] STONEBRAKER,M., ABADI, D. J., BATKIN, A., CHEN, X., CHERNIACK, M., FERREIRA, M., LAU, E., LIN, A., MADDEN, S., O’NEIL, E., O’NEIL, P., RASIN, A., TRAN, N., AND ZDONIK, S. C-Store: A columnoriented DBMS. In Proc. of VLDB (Aug. 2005), pp. 553-564.
[35] STONEBRAKER, M., AOKI, P. M., DEVINE, R., LITWIN, W., AND OLSON, M. A. Mariposa: A new architecture for distributed data. In Proc. of the Tenth ICDE(1994), IEEE Computer Society, pp. 54-65.
[36] SYBASE.COM. www.sybase.com/products/databaseservers/sybaseiq.
Product page.
[37] ZHAO, B. Y., KUBIATOWICZ, J., AND JOSEPH, A. D. Tapestry: An infrastructure for fault-tolerant wide-area location and routing. Tech. Rep. UCB/CSD-01-1141, CS Division, UC Berkeley, Apr. 2001.
[38] ZUKOWSKI, M., BONCZ, P. A., NES, N., AND HEMAN, S. MonetDB/X100 ?A DBMS in the CPU cache. IEEE Data Eng. Bull. 28, 2 (2005), 17-22.