bigtable論文筆記

Bigtable不支持完整的關係數據模型;與之相反,Bigtable爲客戶提供了簡單的數據模型,利用這個模型,客戶可以動態控制數據的layout和format。數據通過行和列的名字訪問,名字可以是任意的字符串。Bigtable將存儲的數據都視爲字符串,但是Bigtable本身不去解析這些字符串,客戶程序通常會在把各種結構化或者半結構化的數據串行化爲字符串。通過選擇數據的schema,客戶可以控制數據的位置相關性。通過BigTable的schema參數來控制數據是存放在內存中還是硬盤上。

 

數據模型:

Big table是稀疏、分佈式、多維sorted map。The map is indexed by a row key, column key and a timestamp。Each value in the map is anuninterpreted array of bytes.

 

 

行關鍵字可以是任意的字符串(目前支持最大64KB的字符串,但是對大多數用戶,10-100個字節就足夠了)。對row key之下的數據讀寫都是原子的(不管讀或者寫這一行裏多少個列)

 

Bigtable通過row key的字典順序來組織數據。Row range動態分區。每個row range稱爲”Tablet”,Tablet是 數據分佈和負載均衡調整的單位。Short  row range讀效率很高。用戶可以通過選擇合適的row key,在數據訪問時有效利用數據的位置相關性,從而更好的利用這個特性。舉例來說,在Webtable裏,通過反轉URL中主機名的方式, 可以把同一個域名下的網頁聚集起來組織成連續的行。具體來說,我們可以把maps.google.com/index.html的數據存放在key com.google.maps/index.html下。把相同的域中的網頁存儲在連續的區域可以讓基於主機和域名的分析更加有效。

Columnfamily

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

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

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

 

時間戳:

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

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

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

 

 

API

 

 

Bigtable提供了建立和刪除表以及列族的API函數。Bigtable還提供了修改集羣、表和列族的元數據的API,比如修改訪問權限。

 

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

 

圖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框架的輸入和輸出。

 

Buildingblocks

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

BigTable內部存儲數據的文件是Google SSTable格式的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服務的會話。如果客戶程序不能在租約到期的時間內重新簽訂會話的租約,這個會話就過期失效了(A client’s session expires if it is unable to renew its sessionlease within the lease ‘s expiration 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%(The percentage for the singlecluster that was most affected byChubby unavailability was 0.0326%.)。

 

實現

Bigtable包括了三個主要的組件:客戶程序連接庫、一個Master服務器和多個Tablet服務器。針對系統工作負載的變化情況,BigTable可以動態的向集羣中添加(或者刪除)Tablet服務器。

 

Master服務器主要負責以下工作:爲Tablet服務器分配Tablets、檢測新加入的或者過期失效的Table服務器、對Tablet服務器進行負載均衡、以及對保存在GFS上的文件進行垃圾收集。除此之外,它還處理schema更改,例如建立表和列族。

 

每個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的位置信息

The root tablet contains the location of all tablets in aspecial METADATA table。each METADATA tablet contains thelocation of a set of user tables。The Root Tablet實際上是METADATA表的第一個Tablet,只不過對它的處理比較特殊 — Root Tablet永遠不會被分割 — 這就保證了Tablet的位置信息存儲結構不會超過三層。

 

METADATA表中每個row key存儲一個Tablet的位置信息,row key由Tablet所在的表的標識符和該Tablet的結束行編碼而成的(The METADATA table storesthe location of a tablet under a row key thatis an encoding of the tablet’s table identifier and it is end row

在內存中,METADATA的一行存儲大約1KB數據。對於128MB的METADATA Tablet,採用這種三層結構的存儲模式,可以標識2^34個Tablet的地址(2^61字節 in 128M Tablet)。

 

客戶程序鏈接庫會緩存Tablet的位置信息。如果客戶程序不知道某個Tablet的地址信息,或者發現緩存的地址信息不正確,then it recursively moves up the tablet location hierarchy。(客戶程序就在樹狀的存儲結構中遞歸的查詢Tablet位置信息);如果客戶端緩存爲空,那麼尋址算法需要三次網絡通信,其中包括了一次Chubby讀操作;如果客戶端緩存地址過期,那麼尋址算法可能需要最多6次網絡通信,because stale cache entries are only discovered upon (cache) misses.因爲只有在緩存中沒有查到數據的時候才能發現數據過期 (假設METADATA的Tablet沒有被頻繁的移動)。Tablet地址信息存放在內存裏,不用訪問GFS,我們會通過預取Tablet地址來進一步的減少開銷:每次讀METADATA表時read metadata for more than 1tablet.

 

在METADATA表中還存儲了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服務器就不能再提供服務了,它會自行退出(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還沒有被assign之前是不能夠掃描它的。因此,在開始掃描之前(步驟4),如果在第三步的掃描過程中發現Root Tablet還沒有assign,Master服務器就把Root Tablet加入到unassigned Tablet集合。這個附加操作確保了Root Tablet會被assign。由於Root Tablet包括了所有METADATA的Tablet的名字,因此Master服務器掃描完Root Tablet以後,就得到了所有的METADATA表的Tablet的名字了。

 

Tablet集合只有在以下事件發生時纔會改變:

創建表、刪除表、合併tablet、分割Tablet。

Master跟蹤記錄這些事件,除了最後一個事件外其他事件都是由它發起的。Tablet分割事件需要特殊處理,因爲它是由Tablet服務器發起。分割操作完成後,Tablet服務器在METADATA表中記錄新的Tablet的信息、提交這個操作,並通知Master服務器。如果未能通知到Master服務器(由於tablet server或master die),隨後Master要求Tablet服務器裝載這個已分割tablet時會檢測到新的Tablet。Tablet server會通知master分割事件,因爲tablet server在METADATA表中找到的tablet信息只指定了master要求load tablet的一部分。

 

5.3Tablet服務

如圖5所示,Tablet的持久化狀態信息保存在GFS上。更改操作提交到REDO日誌中(Updates are committed to acommit log that stores redo records)。最近提交的更改操作存放在一個排序的內存緩存中,稱爲memtable;較早的更新存放在一系列SSTable中。爲了恢復一個Tablet,Tablet服務器首先從METADATA表中讀取它的元數據。Tablet的元數據包含了該Tablet對應的SSTable列表,以及a set of Redo Point,這些Redo Point指向可能含有該Tablet相關數據的commit logs。Tablet服務器把SSTable的索引讀進內存,之後通過apply Redo Point之後提交的更新來重建memtable。

 

 

當Tablet server收到寫請求,首先檢查請求格式、請求者權限。權限驗證方法:讀取特定的Chubby文件,包含有寫權限的操作者列表。(幾乎都能從Chubby緩存中命中)。Valid修改操作會記錄在提交日誌裏。採用批量提交方式(group commit)來提高大量小修改操作的throughput 13,16】。當寫操作提交後,更改內容插入到memtable裏面。

 

Tablet服務器對讀請求會作類似的格式檢查和權限檢查。合法的讀操作is executed on a merged

view of the sequence of SSTablesand the memtable。由於SSTable和memtable是按字典排序的數據結構,因此可以高效生成合並視圖。

Incoming read and write operations whiletablets are split and merged.

 

5.4Compactions

隨着寫操作的執行,memtable的大小不斷增加。當memtable的尺寸到達一個門限值的時候,這個memtable就會被凍結,然後創建一個新的memtable;被凍結住memtable會被轉換成SSTable,然後寫入GFS(Minor Compactionhbase中稱爲flush)。Minor Compaction有兩個目的:shrink Tablet服務器使用的內存,以及在服務器災難恢復過程中,減少從提交日誌裏讀取的數據量。在Compaction過程中,incoming讀寫操作仍能繼續。

 

每一次MinorCompaction都會創建一個新的SSTable。如果Minor Compaction過程持續進行下去,讀操作可能需要合併來自多個SSTable的更新;因此我們通過定期在後臺執行Merging Compaction過程合併文件,限制這類文件的數量。Merging Compaction過程讀取一些SSTable和memtable的內容,合併成一個新的SSTable。Merging Compaction完成後,相關SSTable和memtable就可以刪除了。

 

合併所有的SSTable並生成一個新的SSTable的Merging Compaction過程叫作Major Compaction。非Major Compaction產生的SSTable可能含有已刪除數據,SSTables produced by non-major compactions can contain special deletionentries that suppress deleted data in older SSTables that are stilllive)。而Major Compaction生成的SSTable不包含已刪除數據。Bigtable循環掃描它所有的Tablet,並且定期對它們執行Major Compaction。Major Compaction機制允許Bigtable回收已刪除數據空間,並且確保BigTable能及時清除已經刪除的數據,這對存放敏感數據的服務是非常重要。

 

6 優化

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

 

localitygroups

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

 

此外,可以以localitygroup爲單位設定一些有用的調試參數。比如,可以把一個locality group設定爲全部存儲在內存中。Tablet服務器依照延遲加載策略將設定爲放入內存的locality group的SSTable裝載進內存。加載完成之後,訪問屬於該locality group的列族的時候就不必讀取硬盤了。這個特性對於需要頻繁訪問的小塊數據特別有用:Bigtable內部,我們利用這個特性提高METADATA表中具有位置相關性的列族的訪問速度

 

壓縮

 

客戶程序可以控制一個localitygroup的SSTable是否需要壓縮;如果需要壓縮,那麼以什麼格式來壓縮。每個SSTable的塊(塊的大小由locality group的優化參數指定)都使用用戶指定的壓縮格式來壓縮。雖然分塊壓縮浪費了少量空間,但是,我們在只讀取SSTable的一小部分數據的時候就不必解壓整個文件了。很多客戶程序使用了two-pass custom compression scheme。第一遍採用Bentley and McIlroy’s方式[6],這種方式在一個large window裏對常見的長字符串進行壓縮;第二遍是採用快速壓縮算法,即在一個16KB的small window中尋找重複數據。兩個壓縮的算法都很快,在現在的機器上,壓縮的速率達到100-200MB/s,解壓的速率達到400-1000MB/s。

 

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

 

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

爲了提高讀操作的性能,Tablet服務器使用二級緩存的策略。Scan cache一級緩存,主要緩存Tablet服務器通過SSTable接口獲取的Key-Value對;Block cache二級緩存,緩存從GFS讀取的SSTable的Block。對於經常要重複讀取相同數據的應用程序來說,掃描緩存非常有效;對於經常要讀取剛剛讀過的數據附近的數據的應用程序來說,Block緩存更有用(例如,順序讀,orrandom reads of different columns in the same locality group within a hot row)。

 

Bloom過濾器

(http://www.cnblogs.com/heaad/archive/2011/01/02/1924195.html

http://googlechinablog.com/2007/07/bloom-filter.html

)

 

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

 

Commit日誌的實現

 

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

 

使用單個日誌顯著提高了常規操作的性能,但使得恢復複雜化。當tablet server宕機時,宕機Tablet服務器所加載的Tablet被移到很多其它的Tablet服務器上:每個Tablet服務器都裝載很少的幾個原來的服務器的Tablet。當恢復一個Tablet的狀態的時候,新的Tablet服務器要從原來的Tablet服務器寫的日誌中提取修改操作的信息,並apply。然而,這些Tablet修改操作的日誌記錄都混合在同一個日誌文件中的。一種方法是新的Tablet服務器讀取完整的Commit日誌文件,然後只apply它需要恢復的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,以消除前面在一次壓縮過程中又產生的未歸併的記錄。第二次MinorCompaction完成以後,Tablet就可以被裝載到新的Tablet服務器上了,並且不需要從日誌中進行恢復。

 

Exploitingimmutability

包括SSTable緩存在內,bigtable內產生的所有的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集合。

 

9 經驗教訓

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

 

一個教訓是,我們發現,很多類型的錯誤都會導致大型分佈式系統受損,這些錯誤不僅僅是通常的網絡中斷、或者很多分佈式協議中設想的fail-stop類型的錯誤。比如,我們遇到過下面這些類型的錯誤導致的問題:內存數據損壞、網絡中斷、時鐘偏差、機器掛起、擴展的和非對稱的網絡分區(extended and asymmetric network partitions)、我們使用的其它系統的Bug(比如Chubby)、GFS配額溢出、計劃內和計劃外的硬件維護。我們在解決這些問題的過程中學到了很多經驗,我們通過修改協議來解決這些問題。比如,我們在我們的RPC機制中加入了Checksum。我們在設計系統的部分功能時,不對其它部分功能做任何的假設,這樣的做法解決了其它的一些問題。比如,我們不再假設一個特定的Chubby操作只返回錯誤碼集合中的一個值。

 

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

 

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

 

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

發佈了11 篇原創文章 · 獲贊 4 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章