Google BigTable 翻譯 ---大表(Bigtable):結構化數據的分佈存儲系統

[size=medium][b]特別聲明:[/b]
轉自:
[url]http://my.donews.com/eraera/category/%E6%8A%9B%E7%A0%96%E5%BC%95%E7%8E%89/[/url]
原文:
[url]http://labs.google.com/papers/bigtable-osdi06.pdf[/url]


{本文的翻譯可能有不準確的地方,詳細資料請參考原文.}
摘要
bigtable是設計來分佈存儲大規模結構化數據的,從設計上它可以擴展到上2^50字節,分佈存儲在幾千個普通服務器上.Google的很多項目使用BT來存儲數據,包括網頁查詢,google earth和google金融.這些應用程序對BT的要求各不相同:數據大小(從URL到網頁到衛星圖象)不同,反應速度不同(從後端的大批處理到實時數據服務).對於不同的要求,BT都成功的提供了靈活高效的服務.在本文中,我們將描述BT的數據模型.這個數據模型讓用戶動態的控制數據的分佈和結構.我們還將描述BT的設計和實現.

1.介紹

在過去兩年半里,我們設計,實現並部署了BT.BT是用來分佈存儲和管理結構化數據的.BT的設計使它能夠管理2^50 bytes(petabytes)數據,並可以部署到上千臺機器上.BT完成了以下目標:應用廣泛,可擴展,高性能和高可用性(high availability). 包括google analytics, google finance, orkut, personalized search, writely和google earth在內的60多個項目都使用BT.這些應用對BT的要求各不相同,有的需要高吞吐量的批處理,有的需要快速反應給用戶數據.它們使用的BT集羣也各不相同,有的只有幾臺機器,有的有上千臺,能夠存儲2^40字節(terabytes)數據.

BT在很多地方和數據庫很類似:它使用了很多數據庫的實現策略.並行數據庫[14]和內存數據庫[13]有可擴展性和高性能,但是BT的界面不同.BT不支持完全的關係數據模型;而是爲客戶提供了簡單的數據模型,讓客戶來動態控制數據的分佈和格式{就是隻存儲字串,格式由客戶來解釋},並允許客戶推斷底層存儲數據的局部性{以提高訪問速度}.數據下標是行和列的名字,數據本身可以是任何字串.BT的數據是字串,沒有解釋{類型等}.客戶會在把各種結構或者半結構化的數據串行化{比如說日期串}到數據中.通過仔細選擇數據表示,客戶可以控制數據的局部化.最後,可以使用BT模式來控制數據是放在內存裏還是在硬盤上.{就是說用模式,你可以把數據放在離應用最近的地方.畢竟程序在一個時間只用到一塊數據.在體系結構裏,就是:locality, locality, locality}

第二節描述數據模型細節.第三節關於客戶API概述.第四節簡介BT依賴的google框架.第五節描述BT的實現關鍵部分.第6節敘述提高BT性能的一些調整.第7節提供BT性能的數據.在第8節,我們提供BT的幾個使用例子,第9節是經驗教訓.在第10節,我們列出相關研究.最後是我們的結論.

2.數據模型
BT是一個稀疏的,長期存儲的{存在硬盤上},多維度的,排序的映射表.這張表的索引是行關鍵字,列關鍵字和時間戳.每個值是一個不解釋的字符數組.{數據都是字符串,沒類型,客戶要解釋就自力更生吧}.

(row:string, column:string,time:int64)->string {能編程序的都能讀懂,不翻譯了}

//彼岸翻譯的第二節

我們仔細查看過好些類似bigtable的系統之後定下了這個數據模型。舉一個具體例子(它促使我們做出某些設計決定),比如我們想要存儲大量網頁及相關信息,以用於很多不同的項目;我們姑且叫它Webtable。在Webtable裏,我們將用URL作爲行關鍵字,用網頁的某些屬性作爲列名,把網頁內容存在contents:列中並用獲取該網頁的時間戳作爲標識,如圖一所示。

[img]http://i106.photobucket.com/albums/m261/beyond-the-sea/Bigtable_Figure1.jpg[/img]
圖一:一個存儲Web網頁的範例列表片斷。行名是一個反向URL{即com.cnn.www}。contents列族{原文用 family,譯爲族,詳見列族}存放網頁內容,anchor列族存放引用該網頁的錨鏈接文本。CNN的主頁被Sports Illustrater{即所謂SI,CNN的王牌體育節目}和MY-look的主頁引用,因此該行包含了名叫“anchor:cnnsi.com”和 “anchhor:my.look.ca”的列。每個錨鏈接只有一個版本{由時間戳標識,如t9,t8};而contents列則有三個版本,分別由時間戳t3,t5,和t6標識。



表中的行關鍵字可以是任意字符串(目前支持最多64KB,多數情況下10-100字節足夠了)。在一個行關鍵字下的每一個讀寫操作都是原子操作(不管讀寫這一行裏多少個不同列),這是一個設計決定,這樣在對同一行進行併發操作時,用戶對於系統行爲更容易理解和掌控。

Bigtable通過行關鍵字的字典序來維護數據。一張表可以動態劃分成多個連續行。連續行在這裏叫做“子表”{tablet},是數據分佈和負載均衡的單位。這樣一來,讀較少的連續行就比較有效率,通常只需要較少機器之間的通信即可。用戶可以利用這個屬性來選擇行關鍵字,從而達到較好數據訪問地域性{locality}。舉例來說,在Webtable裏,通過反轉URL中主機名的方式,可以把同一個域名下的網頁組織成連續行。具體來說,可以把 maps.google.com/index.html中的數據存放在關鍵字com.google.maps/index.html下。按照相同或屬性相近的域名來存放網頁可以讓基於主機和基於域名的分析更加有效。

列族

一組列關鍵字組成了“列族”,這是訪問控制的基本單位。同一列族下存放的所有數據通常都是同一類型(同一列族下的數據可壓縮在一起)。列族必須先創建,然後在能在其中的列關鍵字下存放數據;列族創建後,族中任何一個列關鍵字均可使用。我們希望,一張表中的不同列族不能太多(最多幾百個),並且列族在運作中絕少改變。作爲對比,一張表可以有無限列。

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

訪問控制,磁盤使用統計,內存使用統計,均可在列族這個層面進行。在Webtable舉例中,我們可以用這些控制來管理不同應用:有的應用添加新的基本數據,有的讀取基本數據並創建引申的列族,有的則只能瀏覽數據(甚至可能因爲隱私權原因不能瀏覽所有數據)。

時間戳

Bigtable表中每一個表項都可以包含同一數據的多個版本,由時間戳來索引。Bigtable的時間戳是64位整型。可以由Bigtable來賦值,表示準確到毫秒的“實時”;或者由用戶應用程序來賦值。需要避免衝突的應用程序必須自己產生具有唯一性的時間戳。不同版本的表項內容按時間戳倒序排列,即最新的排在前面。

爲了簡化對於不同數據版本的數據的管理,我們對每一個列族支持兩個設定,以便於Bigtable對錶項的版本自動進行垃圾清除。用戶可以指明只保留表項的最後n個版本,或者只保留足夠新的版本(比如,只保留最近7天的內容)。

在Webtable舉例中,我們在contents:列中存放確切爬行一個網頁的時間戳。如上所述的垃圾清除機制可以讓我們只保留每個網頁的最近三個版本。

//我開始翻譯3,4節

3.API
BT的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);
圖 2: 寫入Bigtable.

在BT中,客戶應用可以寫或者刪除值,從每個行中找值,或者遍歷一個表中的數據子集.圖2的C++代碼是使用RowMutation抽象表示來進行一系列的更新(爲保證代碼精簡,沒有包括無關的細節).調用Apply函數,就對Webtable進行了一個原子修改:它爲http://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: 從Bigtable讀數據.

圖3的C++代碼是使用Scanner抽象來遍歷一個行內的所有錨點.客戶可以遍歷多個列族.有很多方法可以限制一次掃描中產生的行,列和時間戳.例如,我們可以限制上面的掃描,讓它只找到那些匹配正則表達式*.cnn.com的錨點,或者那些時間戳在當前時間前10天的錨點.

BT還支持其他一些更復雜的處理數據的功能.首先,BT支持單行處理.這個功能可以用來對存儲在一個行關鍵字下的數據進行原子的讀-修改-寫操作.BT目前不支持跨行關鍵字的處理,但是它有一個界面,可以用來讓客戶進行批量的跨行關鍵字處理操作.其次,BT允許把每個表項用做整數記數器.最後,BT支持在服務器的地址空間內執行客戶端提供的腳本程序.腳本程序的語言是google開發的Sawzall[28]數據處理語言.目前,我們基於的 Sawzall的API還不允許客戶腳本程序向BT內寫數據,但是它允許多種形式的數據變換,基於任何表達式的過濾和通過多種操作符的摘要.

BT可以和MapReduce[12]一起使用.MapReduce是google開發的大規模並行計算框架.我們爲編寫了一套外層程序,使BT可以作爲MapReduce處理的數據源頭和輸出結果.

4.建立BT的基本單元
BT是建立在其他數個google框架單元上的.BT使用google分佈式文件系統(GFS)[17]來存儲日誌和數據文件{yeah, right, what else can it use, FAT32?}.一個BT集羣通常在一個共享的機器池中工作,池中的機器還運行其他的分佈式應用{雖然機器便宜的跟白菜似的,可是一樣要運行多個程序,命苦的象小白菜},BT和其他程序共享機器{BT的瓶頸是IO/內存,可以和CPU要求高的程序並存}.BT依賴集羣管理系統來安排工作,在共享的機器上管理資源,處理失效機器並監視機器狀態{典型的server farm結構,BT是上面的應用之一}.

BT內部存儲數據的格式是google SSTable格式.一個SSTable提供一個從關鍵字到值的映射,關鍵字和值都可以是任意字符串.映射是排序的,存儲的{不會因爲掉電而丟失},不可改寫的.可以進行以下操作:查詢和一個關鍵字相關的值;或者根據給出的關鍵字範圍遍歷所有的關鍵字和值.在內部,每個SSTable包含一列數據塊(通常每個塊的大小是64KB,但是大小是可以配置的{索引大小是16 bits,應該是比較好的一個數}).塊索引(存儲在SSTable的最後)用來定位數據塊;當打開SSTable的時候,索引被讀入內存{性能}.每次查找都可以用一個硬盤搜索完成{根據索引算出數據在哪個道上,一個塊應該不會跨兩個道,沒必要省那麼點空間}:首先在內存中的索引裏進行二分查找找到數據塊的位置,然後再從硬盤讀去數據塊.最佳情況是:整個SSTable可以被放在內存裏,這樣一來就不必訪問硬盤了.{想的美,前面是誰口口聲聲說要跟別人共享機器來着?你把內存佔滿了別人上哪睡去?}

BT還依賴一個高度可用的,存儲的分佈式數據鎖服務Chubby[8]{看你怎麼把這個high performance給說圓嘍}.一個Chubby服務由5個活的備份{機器}構成,其中一個被這些備份選成主備份,並且處理請求.這個服務只有在大多數備份都活着並且互相通信的時候纔是活的{繞口令?去看原文吧,是在有出錯的前提下的冗餘算法}.當有機器失效的時候,Chubby使用Paxos算法[9,23]來保證備份的一致性{這個問題還是比較複雜的,建議去看引文了解一下問題本身}.Chubby提供了一個名字空間,裏面包括了目錄和小文件{萬變不離其宗}.每個目錄或者文件可以當成一個鎖來用,讀寫文件操作都是原子化的.Chubby客戶端的程序庫提供了對Chubby文件的一致性緩存{究竟是提高性能還是降低性能?如果訪問是分佈的,就是提高性能}.每個Chubby客戶維護一個和Chubby服務的會話.如果一個客戶不能在一定時間內更新它的會話,這個會話就過期失效了{還是針對大server farm裏機器失效的頻率設計的}.當一個會話失效時,其擁有的鎖和打開的文件句柄都失效{根本設計原則:失效時回到安全狀態}.Chubby客戶可以在文件和目錄上登記回調函數,以獲得改變或者會話過期的通知.{翻到這裏,有沒有人聞到java的味道了?}

BT使用Chubby來做以下幾個任務:保證任何時間最多隻有一個活躍的主備份;來存儲BT數據的啓動位置(參考5.1節);發現小表(tablet)服務器,並完成tablet服務器消亡的善後(5.2節);存儲BT數據的模式信息(每張表的列信息);以及存儲訪問權限列表.如果有相當長的時間Chubby不能訪問,BT就也不能訪問了{任何系統都有其弱點}.最近我們在使用11個Chubby服務實例的14個BT集羣中度量了這個效果,由於Chubby不能訪問而導致BT中部分數據不能訪問的平均百分比是0.0047%,這裏Chubby不能訪問的原因是Chubby本身失效或者網絡問題.單個集羣裏,受影響最大的百分比是0.0326%{基於文件系統的Chubby還是很穩定的}.

//這裏是第二部分.我發現如果一篇文章太大,我的blog就打開特別慢,不知是WORDPRESS的問題還是什麼.所以這裏另起一篇.

5.實現
BT的實現有三個主要組件:客戶程序庫,一個主服務器和多個子表服務器.針對負載的變化,可以動態的從服務器羣中添加(或者去除)子表服務器.主服務器的任務是:給子表服務器指定子表,檢測加入或者失效的子表服務器,子表服務器負載均衡,以及對google文件系統的文件進行垃圾收集.除此之外,它還處理諸如建立表和列族之類的表模式改變工作.

每個子表服務器管理一個子表集合(通常每個服務器處理數十乃至上千個子表).子表服務器負責處理對它管理的子表進行的讀寫操作,當子表變的太大時,服務器會將子表分割.和很多單個主服務器分佈式系統[17.21]一樣,客戶數據不經過主服務器.客戶的讀寫操作是通過直接和子表服務器通信完成的.由於BT的客戶不必通過主服務器獲取子表位置信息,大多數客戶完全不和主服務器通信.因此,實際使用中主服務器的負載很輕.

一個BT集羣存儲多個表.每個表由一些子表組成,每個子表包含一個行域內的所有數據.在起始狀態下,一個表只有一個子表.當一個表長大以後,它自動的分割成多個子表,每個子表的缺省大小是100到200MB.

5.1 子表的地址

子表地址信息是存儲在一個三層類似B+樹[10]的結構中的(圖4).

[img]http://my.donews.com/eraera/files/2006/09/fig4.JPG[/img]

圖4:子表地址結構

第一層是Chubby中的一個文件,它存儲根子表的地址.根子表裏存儲一個特殊的表裏的所有子表的地址,地址這個特殊的表是元數據表.每個元數據子表裏存儲一組用戶子表的地址.根子表其實是元數據表裏的第一個子表,但是對它的處理比較特殊:根子表永遠不會被分割,這樣一來保證了子表地址結構不會超過三層.

元數據表裏面,每個子表的地址都對應一個行關鍵字,這個關鍵字是由子表所在的表的標識符,和子表的最後一行編碼而成的.每個元數據行在內存裏存儲大約1kb的數據.元數據子表的大小限制是128MB,限制看似不大,不過已經可以讓這個三層的地址樹足夠表示2^34個子表了(如果每個子表存儲 128MB數據,一共是2^61字節數據).

客戶程序庫緩存了子表地址.如果客戶沒有一個子表的地址,或者它發現地址不正確,客戶就遞歸的查詢子表地址樹.如果緩存是空的,那麼尋址算法需要三次網絡來回通信來尋址,其中包括一次Chubby讀操作.如果緩存數據過期,那麼尋址算法可能最多需要6次網絡來回通信才能更新數據,因爲只有在緩存不命中的時候才能發現數據過期{三次通信發現過期,另外三次更新數據}(這裏的假定是,元數據子表沒有頻繁的移動).子表的地址是放在內存裏的,所以不必訪問google文件系統GFS,我們通過預取子表地址來進一步的減少了訪問開銷{體系結構裏的老花招:緩存,預取}:每次讀取子表的元數據的時候,都讀取幾個子表的元數據{爲什麼不說預取幾個子表地址?倆?四個?這裏就是有價值的東西了,需要時間去積累經驗}.

在元數據表中還存儲了次要信息,包括每個子表的事件日誌(例如,什麼時候一個服務器開始服務該子表).這些信息有助於排錯和性能分析{一筆代過重要信息,比如都存了什麼事件,事件屬性是什麼等}.

5.2子表分配

在任一時刻,一個子表只會分配給一個子表服務器.主服務器知道當前有哪些活躍的子表服務器,還知道哪些子表分配到哪些子表服務器,哪些以及哪些子表沒有被分配.當一個子表沒有被分配到服務器,同時又有一個服務器的空閒空間足夠裝載該子表,主服務器就給這個子表服務器材發送一個裝載請求,把子表分配給這個服務器.

{這裏是協議描述}BT使用Chubby來追蹤子表服務器.當一個子表服務器啓動時,它在一個特定的Chubby目錄裏建立一個有唯一名字的文件,並獲取該文件的獨佔的鎖.主服務器監視這個目錄{服務器目錄},就可以發現新的子表服務器.一個子表服務器如果喪失了對文件的鎖,就停止對它的子表們的服務.服務器可能喪失鎖的原因很多,例如:網絡斷開導致服務器丟失了Chubby會話(Chubby提供一種有效的服務,使子表服務器不必通過網絡就能查詢它是否還擁有文件鎖{這個比較神,難道是tablet server自己只查本地文件,chubby server來幫它在本地建立文件?要認真看看chubby的協議才知道}).如果文件依然存在,子表服務器會試圖重新獲得對文件的獨佔鎖.如果文件不存在了,那麼子表服務器就不能服務了,它就退出.當子表服務器終止時(例如,集羣管理系統將子表服務器的機器從集羣中移除),它會試圖釋放文件鎖,這樣一來主服務器就能更快的把子表分配給其他服務器.

當一個子表服務器不再服務它的子表的時候,主服務器有責任發現問題,並把子表儘快重新分配.主服務器發現問題的方法是定期詢問子表服務器的文件鎖狀態.如果一個子表服務器報告丟失了文件鎖,或者過去幾次詢問都沒有反應,主服務器就會試圖獲取子表服務器的文件鎖.如果主服務器能夠獲取鎖,說明 Chubby是好的,子表服務器或者是死了,或者不能和Chubby通信,主服務器就刪除子表服務器的文件,以確保子表服務器不再服務子表.一旦一個服務器的文件被刪除,主服務器就可以把它的子表都放入未分配的子表集合中.爲了保證在主服務器和Chubby之間有網絡故障的時候BT仍然可以使用,主服務器的Chubby會話一旦過期,主服務器就退出.但是,如前所述,主服務器故障不影響子表到子表服務器的分配.

當一個集羣管理系統啓動一個主服務器時,它需要發現當前的子表分配狀態,然後才能修改分配狀態{設計思想:永遠考慮失效+恢復}.主服務器執行以下啓動步驟:(1)主服務器在Chubby中獲取唯一的主文件鎖,來阻止其他主服務器實例{singlton}.(2)主服務器材掃描Chubby服務器目錄,獲取當前活躍服務器列表.(3)主服務器和活躍子表服務器通信,獲取子表分配狀態{注意子表分配不是主服務器存儲的,保證了失效時主服務器不會成爲性能瓶頸}.(4)主服務器掃描元數據表,每次遇到一個沒有分配的子表,就加入未分配子表集合,這個子表就可以被分配了.

這裏有一個複雜的情況:在元數據表沒有被分配之前,是不能掃描元數據表的{雞和蛋}.因此,在開始第四步的掃描之前,如果第三步的掃描沒有發現根子表沒分配,主服務器就把根子表加入未分配的子表集合.這一附加步驟保證了根子表肯定會被分配.由於根子表包括了所有元數據子表的名字,主服務器在掃描過根子表以後,就知道了所有的元數據子表.

現存子表集合僅在以下事件中才會改變:一個子表被建立或者刪除,兩個子表被合併,或者一個子表被分割成兩個.主服務器可以監控所有這些事件,因爲前三個事件都是主服務器啓動的.最後一個事件:分割子表,是由子表服務器啓動的.這個事件是特別處理的.子表服務器在分割完畢時,在元數據表中記錄新的子表的信息.當分割完成時,子表服務器通知主服務器.如果分割的通知沒有到達(兩個服務器中間死了一個),主服務器在請求子表服務器裝載已經分割的子表的時候,就會發現這個沒有通知的分割操作{設計的時候一定要考慮到錯誤和失敗的恢復.古人云,未思進,先思退}.子表服務器就會重新通知主服務器,因爲在元數據表中找到的子表入口只包含要求裝載的子表的部分信息{細節,細節呢?}.

//今天比較忙,只有這些了.抱歉.

//更新:彼岸翻譯了第5章的剩餘部分,貼在這裏:

5.3 子表服務

[img]http://my.donews.com/beyondthesea/files/2006/09/Bigtable_Figure5.jpg[/img]

圖5:子表表示

子表的狀態存放在GFS裏,如圖5所示。更新內容提交到存放redo記錄的提交日誌裏{比較繞,看原文可能清楚點}。在這些更新中,最近提交的那些存放在內存裏一個叫memtable的有序緩衝裏;老一點的更新則存放在一系列SSTable裏。若要恢復一個子表,子表服務器從METADATA表中讀取元數據。元數據包括了由一個子表和一系列redo點{redo怎麼翻好?}組成的SSTable列表,這些是指向可能含有該子表數據的提交日誌的指針{煩死定語從句了}。 該服務器把這些SSTable的索引讀進內存,並通過重複redo點之後提交的更新來重建memtable。

當一個寫操作到達子表服務器時,該服務器檢查確信這個操作完整無誤,而且發送方有權執行所描述的變換。授權是通過從一個Chubby文件裏讀取具有寫權限的操作者列表來進行的(幾乎一定會存放在Chubby客戶緩存裏)。合法的變換會寫到提交日誌裏。可以用成組提交來提高大量小變換的吞吐量[13,16]。寫操作提交後,寫的內容就插入到memtable裏。

當一個讀操作到達子表服務器時,會作類似的完整性和授權檢查。合法的讀操作在一個由SSTable系列和memtable合併的視圖裏執行。由於SSTable和memtable是字典序的數據結構,合併視圖可以很有效地形成。

進來方向的{incoming}讀寫操作在子表分拆和合並時仍能繼續。

5.4 緊縮{compaction}

在執行寫操作時,memtable的大小不斷增加。當memtable大小達到一定閾值時,memtable就會被凍結,然後創建一個新的memtable,凍結住的memtable則被轉換成SSTable並寫到GFS裏。這種次要緊縮過程有兩個目的:縮小了子表服務器的內存用度,以及減少了在服務器當機後恢復過程中必須從提交日誌裏讀取的數據量。 進來方向的讀寫操作在緊縮進行當中仍能繼續。

每一個次要緊縮會創建一個新的SSTable。如果這種行爲一直繼續沒有停止的跡象,讀操作可能需要合併來自任意多SSTable的更新。相反,我們通過定期在後臺執行合併緊縮來限定這類文件的數量。合併緊縮讀取一些SSTable和memtable的內容,並寫成一個新的SSTable。一旦緊縮完成,作爲輸入的這些個SSTable和memtable就可以扔掉了。

把所有SSTable重寫成唯一一個SSTable的合併緊縮叫作主要緊縮。由非主要緊縮產生的SSTable可以含有特殊的刪除條目,它們使得老一點但仍活躍的SSTable中已刪除的數據不再出現。而主要緊縮則產生不包含刪除信息或刪除數據的SSTable。Bigtable在它所有的子表中循環,並且定期對它們執行主要緊縮。這些主要緊縮使得Bigtable可以回收已刪除數據佔有的資源,並且還能保證已刪除數據及時從系統裏小時,這對存放敏感數據的服務很重要。


//更新:彼岸翻譯了第5章的後面部分

6.優化
前面一章描述了BT的實現,我們還需要很多優化工作來獲得用戶需要的高性能,高可用性和高可靠性.本章描述實現的一些部分,以強調這些優化.

局部性羣組

客戶可以將多個列族組合成局部性羣族.對每個子表中的每個局部性羣組都會生成一個單獨的SSTable.將通常不會一起訪問的列族分割成不同的局部性羣組,將會提高讀取效率.例如,Webtable中的網頁元數據(語言和校驗和之類的)可以在一個局部性羣組,網頁內容可以在另外一個羣組:如果一個應用要讀取元數據,它就沒有必要去訪問頁面內容.

此外,每個羣組可以設定不同的調試參數.例如,一個局部性羣組可以被設定在內存中.內存中的局部性羣組的SSTable在被子表服務器裝載進內存的時候,使用的裝載策略是懶惰型的.一旦屬於該局部性羣組的列族被裝載進內存,再訪問它們就不必通過硬盤了{讀不懂?知道機器翻譯有多難了吧?人翻譯都不行}.這個特性對於需要頻繁訪問的小塊數據特別有用:在BT內部,我們用這個特性來訪問元數據表中的地址列族.

壓縮

客戶可以控制是否壓縮一個局部性羣組的SSTable.每個SSTable塊(塊的大小由局部性羣組的調試參數確定),都會使用用戶指定的壓縮格式.儘管這樣分塊壓縮{比全表壓縮}浪費了少量空間,但是在讀取SSTable的一小部分數據的時候,就不必解壓整個文件了{那是,你們文件巨大,硬盤又便宜}.很多客戶使用兩遍的訂製壓縮方式.第一遍是Bentley and McIlroy’s方式[6],該方式在一個大的掃描窗口中將常見的長串進行壓縮.第二遍是一種快速壓縮算法,在一個16KB的小掃描窗口中尋找重複數據.兩個算法都很快,現有機器上壓縮速率爲100到200MB/s,解壓速率是400到1000MB/s.

儘管我們選擇壓縮算法的重點是速率,而非空間效率,這種兩遍的壓縮方式空間效率也令人驚歎{老大,別吹了,您老是在壓字符串哪!你去壓壓運行代碼看看?}.例如,在Webtable,我們用這種壓縮方式來存儲網頁內容.針對實驗的目的,我們對每個文檔只存儲一個版本,而非存儲所有能訪問的版本.該模式獲得了 10比1的壓縮率.這比一般的Gzip的3比1或者4比1的HTML頁面壓縮率好很多,因爲Webtable的行是這樣安排的:從一個主機獲取的頁面都存在臨近的地方,這種特性讓Bentley-McIlroy算法可以從一個主機那裏來的頁面裏找到大量的重複內容.不僅是Webtable,其他很多應用也通過選擇行名來將類似內容聚集在一起,因此壓縮效果非常的好{針對數據和程序特性選擇的壓縮算法}.當在BT中存儲同一數據的多個版本的時候,壓縮效率更高.

使用緩存來提高讀取性能

爲了提高讀操作性能,子表服務機構使用兩層緩存.掃描緩存是高層,它緩存子表服務器代碼從SSTable獲取的關鍵字-值對.塊緩存是底層,緩存的是從GFS讀取的SSTable塊.對於經常要重複讀取同一部分數據的應用程序來說,掃描緩存很有用.對於經常要讀取前面剛讀過的數據附近的其他數據(例如,順序讀{性能提升老花招:預取},或者在一個熱門的行中的同一局部性羣組中,隨機讀取不同的列)的應用程序來說,塊緩存很有用{後面這句比較拗口,是說一個局部性羣組裏的列都在緩存裏,可以隨機讀取}.

Bloom過濾器{需要讀參考文獻才知道是什麼意思.從標題看,bloom是一種雜湊函數,有相對低的不命中率,所以可以用它來猜測一個關鍵字對應的存儲數據在哪裏,從而減少訪問硬盤的次數,以提高性能}

如5.3節所述,一個讀操作要知道一個子表的所有狀態,必須從所有SSTable中讀取數據.如果這些SSTable不在內存,那麼就需要多次訪問硬盤.我們通過允許客戶對特定局部性羣組的SSTable指定bloom過濾器[7],來降低訪問硬盤的次數.使用bloom過濾器,我們就可以猜測一個 SSTable是否可能包含特定行和列的數據.對於某些特定應用程序,使用少量內存來存儲bloom過濾器換來的,是顯著減少的訪問磁盤次數.我們使用 bloom過濾器也使當應用程序要求訪問不存在的行或列時,我們不會訪問硬盤.

修改日誌{commit-log}的實現

如果我們把對每個子表的修改日誌都另存一個文件的話,就會產生非常多的文件,這些文件會並行的寫入GFS.根據每個GFS底層實現的細節,這些寫操作在最終寫入實際日誌文件的時候,可能造成大量的硬盤尋道動作.此外,由於羣組不大,爲每個子表建立一個新的日誌文件也降低了羣組修改的優化程度.爲了避免這些問題,我們將對每個子表服務器的修改動作添加到一個修改日誌中,將多個子表的修改混合到一個實際日誌文件中[18,20].

使用單個日誌顯著提高了正常使用中的性能,但是將恢復的工作複雜化了.當一個子表服務器死掉時,它以前服務的子表們將會被移動到很多其他的子表服務器上:它們每個都裝載很少的幾個子表.要恢復子表的狀態,新的子表服務器要按照原來的服務器寫的修改日誌來重新進行修改.但是,這些修改記錄是混合在一個實際日誌文件中的.一種方法是把日誌文件都讀出來,然後只重複需要進行的修改項.但是,用這種方法,假如有100臺機器都裝載了要恢復的子表,那麼這個日誌文件要讀取100次(每個服務器一次).

避免這個問題的方法是先把日誌按照關鍵字排序.在排序以後,所有的修改項都是連續的了,只要一次尋道操作,然後順序讀取.爲了並行的排序,我們將日誌分割成64MB的段,並在不同的子表服務器上並行的排序.這個排序工作是由主服務器來協同的,當一個子表服務器表示需要從某些日誌文件中開始恢復修改,這個過程就開始了.

有時,向GFS中寫修改日誌文件會導致性能不穩定,原因很多(例如,正在寫的時候,一個GFS服務器不行了,或者訪問某三個GFS服務器的網絡路由斷了或者擁塞).爲了在GFS延遲的高峯時還能保證修改順利進行,每個子表服務器實際上有兩個線程:各自寫不同的日誌文件;兩個線程裏只有一個活躍.如果一個線程的寫操作性能不好,就切換到另外一個線程,修改的記錄就寫入新的活躍線程的日誌文件.每個日誌項都有序列號,在恢復的時候,由於線程切換而導致的重複的序列號將被忽略.

加速子表的恢復

如果主服務器將一個子表從一個子表服務器移動到另外一個服務器,第一個子表服務器對子表進行輕度壓縮.該壓縮減少了子表服務器的日誌文件中沒有被緊縮的狀態,從而減少了恢復時間.壓縮完成以後,該服務器就停止服務該子表.然後,在卸載該子表前,該服務器再次進行一次(通常很快)輕度壓縮,以消除在前面一次壓縮時遺留下來的未緊縮的狀態.第二次壓縮做完以後,子表就可以被裝載到另外一個服務器上,而不必請求從日誌中恢復了.

利用不變性{immutability,不可寫,可以並行讀取}

除了SSTable緩存以外,由於所有生成的SSTable都是不變的,所以BT的很多其他部分都變的簡單了.例如,當從SSTable讀的時候,就不必進行同步.這樣一來,對行的並行操作就可以非常有效的實現了.內存表是唯一一個被讀和寫操作同時訪問的可變數據結構.爲了減少在讀操作中對內存表的競爭,內存表是寫複製的,這樣一來就可以並行進行讀寫操作.

因爲SSTable是不變的,因此永久消除被刪除的數據的問題,就轉換成對過時的SSTable進行垃圾收集的問題了.每個子表的SSTable們都在元數據表進行註冊.主服務器對SSTable集合進行標記-掃除的垃圾收集工作[25],元數據表保存了根SSTable集合。

最後,SSTable的不變性使分裂子表的操作更加快速。我們不必爲每個分裂出來的子表建立新的SSTable集合,而是讓分裂的子表集合共享原來子表的SSTable集合。

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