开启大数据时代谷歌三篇论文-BigTable
1 摘要
Bigtable 是一個分布式的結構化數據存儲系統,它被設計用來處理海量數據:通常是分布在數千臺普通服 務器上的 PB 級的數據。
Google 的很多項目使用 Bigtable 存儲數據,包括 Web 索引、Google Earth、Google Finance。這些應用對 Bigtable 提出的要求差異非常大,無論是在數據量上(從 URL 到網頁到衛星圖像)還是在響應速度上(從后 端的批量處理到實時數據服務)。盡管應用需求差異很大,但是,針對 Google 的這些產品,Bigtable 還是成功 的提供了一個靈活的、高性能的解決方案。
本論文描述了 Bigtable 提供的簡單的數據模型。利用這個模型,用戶可以動態的控制數據的分布和格式。 我們還將描述 Bigtable 的設計和實現。
2 介紹
在過去兩年半時間里,我們設計、實現并部署了一個分布式的結構化數據存儲系統 — 在 Google,我們 稱之為 Bigtable。Bigtable 的設計目的是可靠的處理 PB 級別的數據,并且能夠部署到上千臺機器上。Bigtable 已經實現了下面的幾個目標:適用性廣泛、可擴展、高性能和高可用性。
Bigtable 已經在超過 60 個 Google 的產品和項目上得到了應用,包括 Google Analytics、Google Finance、 Orkut、Personalized Search、Writely 和 Google Earth。這些產品對 Bigtable 提出了迥異的需求,有的需要高吞 吐量的批處理,有的則需要及時響應,快速返回數據給最終用戶。它們使用的 Bigtable 集群的配置也有很大 的差異,有的集群只有幾臺服務器,而有的則需要上千臺服務器、存儲幾百 TB 的數據。
在很多方面,Bigtable 和數據庫很類似:它使用了很多數據庫的實現策略。并行數據庫【14】和內存數據庫【13】已經具備可擴展性和高性能,但是 Bigtable 提供了一個和這些系統完全不同的接口。Bigtable 不支持完整的關系數據模型;與之相反,Bigtable 為客戶提供了簡單的數據模型,利用這個模型,客戶可以動態控制數據的分布和格式,用戶也可以自己推測底層存儲數據的位置相關性。數據的下標是行和列的名字,名 字可以是任意的字符串。Bigtable 將存儲的數據都視為字符串,但是 Bigtable 本身不去解析這些字符串,客戶程序通常會在把各種結構化或者半結構化的數據串行化到這些字符串里。通過仔細選擇數據的模式,客戶可 以控制數據的位置相關性。最后,可以通過 BigTable 的模式參數來控制數據是存放在內存中、還是硬盤上。
第 3 節描述關于數據模型更多細節方面的東西;
第 4 節概要介紹了客戶端 API;
第 5 節簡要介紹了 BigTable 底層使用的 Google 的基礎框架;
第 6 節描述了 BigTable 實現的關鍵部分;
第 7 節描述了我們為了提高 BigTable 的性能采用的一些精細的調優方法; 第 8 節提供了 BigTable 的性能數據;
第 9 節講述了幾個 Google 內部使用 BigTable 的例子;
第 10 節是我們在設計和后期支持過程中得到一些經驗和教訓; 最后,在第 11 節列出我們的相關研究工作,第 12 節是我們的結論。
3 數據模型
Bigtable 是一個稀疏的、分布式的、持久化存儲的多維度排序 Map5。Map 的索引是行關鍵字、列關鍵字 以及時間戳;Map 中的每個 value 都是一個未經解析的 byte 數組。
(row:string, column:string,time:int64)->string
我們在仔細分析了一個類似 Bigtable 的系統的種種潛在用途之后,決定使用這個數據模型。我們先舉個 具體的例子,這個例子促使我們做了很多設計決策;假設我們想要存儲海量的網頁及相關信息,這些數據可 以用于很多不同的項目,我們姑且稱這個特殊的表為 Webtable。在 Webtable 里,我們使用 URL 作為行關鍵 字,使用網頁的某些屬性作為列名,網頁的內容存在“contents:”列中,并用獲取該網頁的時間戳作為標識6, 如圖一所示。
行名是一個反向 URL。contents 列族存放的是網頁的內容,anchor 列族存放引用該網頁的錨鏈接文本7。 CNN 的主頁被 Sports Illustrator 和 MY-look 的主頁引用,因此該行包含了名為“anchor:cnnsi.com”和 “anchhor:my.look.ca”的列。每個錨鏈接只有一個版本8;而 contents 列則有三個版本,分別由時間戳 t3,t5,和 t6 標識。
3.1 行
表中的行關鍵字可以是任意的字符串(目前支持最大 64KB 的字符串,但是對大多數用戶,10-100 個字 節就足夠了)。對同一個行關鍵字的讀或者寫操作都是原子的(不管讀或者寫這一行里多少個不同列),這個 設計決策能夠使用戶很容易的理解程序在對同一個行進行并發更新操作時的行為。
Bigtable 通過行關鍵字的字典順序來組織數據。表中的每個行都可以動態分區。每個分區叫做一個”Tablet”, Tablet 是數據分布和負載均衡調整的最小單位。這樣做的結果是,當操作只讀取行中很少幾列的數據時效率很 高,通常只需要很少幾次機器間的通信即可完成。用戶可以通過選擇合適的行關鍵字,在數據訪問時有效利 用數據的位置相關性,從而更好的利用這個特性。舉例來說,在Webtable 里,通過反轉 URL 中主機名的方 式,可以把同一個域名下的網頁聚集起來組織成連續的行。具體來說,我們可以把 maps.google.com/index.html 的數據存放在關鍵字 com.google.maps/index.html 下。把相同的域中的網頁存儲在連續的區域可以讓基于主機 和域名的分析更加有效。
3.2 列族
列關鍵字組成的集合叫做“列族“,列族是訪問控制的基本單位。存放在同一列族下的所有數據通常都 屬于同一個類型(我們可以把同一個列族下的數據壓縮在一起)。列族在使用之前必須先創建,然后才能在列 族中任何的列關鍵字下存放數據;列族創建后,其中的任何一個列關鍵字下都可以存放數據。根據我們的設 計意圖,一張表中的列族不能太多(最多幾百個),并且列族在運行期間很少改變。與之相對應的,一張表可 以有無限多個列。
列關鍵字的命名語法如下:列族:限定詞。 列族的名字必須是可打印的字符串,而限定詞的名字可以是 任意的字符串。比如,Webtable 有個列族 language,language 列族用來存放撰寫網頁的語言。我們在 language 列族中只使用一個列關鍵字,用來存放每個網頁的語言標識 ID。Webtable 中另一個有用的列族是 anchor;這 個列族的每一個列關鍵字代表一個錨鏈接,如圖一所示。Anchor 列族的限定詞是引用該網頁的站點名;Anchor 列族每列的數據項存放的是鏈接文本。
訪問控制、磁盤和內存的使用統計都是在列族層面進行的。在我們的 Webtable 的例子中,上述的控制權 限能幫助我們管理不同類型的應用:我們允許一些應用可以添加新的基本數據、一些應用可以讀取基本數據并創建繼承的列族、一些應用則只允許瀏覽數據(甚至可能因為隱私的原因不能瀏覽所有數據)。
3.3 時間戳
在 Bigtable 中,表的每一個數據項都可以包含同一份數據的不同版本;不同版本的數據通過時間戳來索 引。Bigtable 時間戳的類型是 64 位整型。Bigtable 可以給時間戳賦值,用來表示精確到毫秒的“實時”時間; 用戶程序也可以給時間戳賦值。如果應用程序需要避免數據版本沖突,那么它必須自己生成具有唯一性的時 間戳。數據項中,不同版本的數據按照時間戳倒序排序,即最新的數據排在最前面。
為了減輕多個版本數據的管理負擔,我們對每一個列族配有兩個設置參數,Bigtable 通過這兩個參數可以 對廢棄版本的數據自動進行垃圾收集。用戶可以指定只保存最后 n 個版本的數據,或者只保存“足夠新”的 版本的數據(比如,只保存最近 7 天的內容寫入的數據)。
在 Webtable 的舉例里,contents:列存儲的時間戳信息是網絡爬蟲抓取一個頁面的時間。上面提及的垃圾 收集機制可以讓我們只保留最近三個版本的網頁數據。
4 API
Bigtable 提供了建立和刪除表以及列族的 API 函數。Bigtable 還提供了修改集群、表和列族的元數據的API,比如修改訪問權限。
| // Open the table |
| Apply(&op, &r1); |
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖表 2 Writing to Bigtable.
客戶程序可以對 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 框架的輸入和輸出。
5 BigTable 構件
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 服務的會話。如果客戶程序不能在租約到期的時間內重新簽訂會話的 租約,這個會話就過期失效了9。當一個會話失效時,它擁有的鎖和打開的文件句柄都失效了。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%。
6 介紹
Bigtable 包括了三個主要的組件:鏈接到客戶程序中的庫、一個 Master 服務器和多個 Tablet 服務器。針 對系統工作負載的變化情況,BigTable 可以動態的向集群中添加(或者刪除)Tablet 服務器。
Master 服務器主要負責以下工作:為 Tablet 服務器分配 Tablets、檢測新加入的或者過期失效的 Table 服 務器、對 Tablet 服務器進行負載均衡、以及對保存在 GFS 上的文件進行垃圾收集。除此之外,它還處理對模 式的相關修改操作,例如建立表和列族。
每個 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。
6.1 Tablet 的位置
我們使用一個三層的、類似B+樹[10]的結構存儲 Tablet 的位置信息(如圖 4)。
? ? ? ? ? ? ? ? ? ? ? ? ? ?
第一層是一個存儲在 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次網絡來回通信才能更新數據,因為只有在緩存中沒有查到 數據的時候才能發現數據過期11。盡管 Tablet 的地址信息是存放在內存里的,對它的操作不必訪問 GFS 文件 系統,但是,通常我們會通過預取 Tablet 地址來進一步的減少訪問的開銷:每次需要從 METADATA 表中讀取一個 Tablet 的元數據的時候,它都會多讀取幾個 Tablet 的元數據。在 METADATA 表中還存儲了次級信息,包括每個 Tablet 的事件日志(例如,什么時候一個服務器開始為該 Tablet 提供服務)。這些信息有助于排查錯誤和性能分析。
6.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 服務器就不能再提供服務了,它會自行退出13。當 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 服務器在啟動的時候執行以下步驟:
Master 服務器從 Chubby 獲取一個唯一的 Master 鎖,用來阻止創建其它的 Master 服務器實例;
Master 服務器掃描 Chubby 的服務器文件鎖存儲目錄,獲取當前正在運行的服務器列表;
Master 服務器和所有的正在運行的 Tablet 表服務器通信,獲取每個 Tablet 服務器上 Tablet 的分配信
息;
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 服務器發送通知信息。
6.3 Tablet 服務
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
如圖 5 所示,Tablet 的持久化狀態信息保存在 GFS 上。更新操作提交到 REDO 日志中14。在這些更新操 作中,最近提交的那些存放在一個排序的緩存中,我們稱這個緩存為 memtable;較早的更新存放在一系列 SSTable 中。為了恢復一個 Tablet,Tablet 服務器首先從 METADATA 表中讀取它的元數據。Tablet 的元數據包 含了組成這個 Tablet 的 SSTable 的列表,以及一系列的 Redo Point15,這些 Redo Point 指向可能含有該 Tablet 數據的已提交的日志記錄。Tablet 服務器把 SSTable 的索引讀進內存,之后通過重復 Redo Point 之后提交的更 新來重建 memtable。
當對 Tablet 服務器進行寫操作時,Tablet 服務器首先要檢查這個操作格式是否正確、操作發起者是否有執 行這個操作的權限。權限驗證的方法是通過從一個 Chubby 文件里讀取出來的具有寫權限的操作者列表來進行 驗證(這個文件幾乎一定會存放在 Chubby 客戶緩存里)。成功的修改操作會記錄在提交日志里。可以采用批 量提交方式16來提高包含大量小的修改操作的應用程序的吞吐量。當一個寫操作提交后,寫的內容 插入到 memtable 里面。
當對 Tablet 服務器進行讀操作時,Tablet 服務器會作類似的完整性和權限檢查。一個有效的讀操作在一個 由一系列 SSTable 和 memtable 合并的視圖里執行。由于 SSTable 和 memtable 是按字典排序的數據結構,因此 可以高效生成合并視圖。當進行 Tablet 的合并和分割時,正在進行的讀寫操作能夠繼續進行。
6.4 空間收縮
隨著寫操作的執行,memtable 的大小不斷增加。當 memtable 的尺寸到達一個門限值的時候,這個 memtable 就會被凍結,然后創建一個新的 memtable;被凍結住 memtable 會被轉換成 SSTable,然后寫入 GFS。Minor Compaction 過程有兩個目的: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 中已經刪除的數據。而 Major Compaction 過程生成的 SSTable 不包含已經刪除的信息或數據。Bigtable 循環掃描它所有的 Tablet,并且定期對它們執行 Major Compaction。Major Compaction 機制允許 Bigtable 回收 已經刪除的數據占有的資源,并且確保 BigTable 能及時清除已經刪除的數據,這對存放敏感數據的服務是 非常重要。
7 優化
上一章我們描述了 Bigtable 的實現,我們還需要很多優化工作才能使 Bigtable 到達用戶要求的高性能、 高可用性和高可靠性。本章描述了 Bigtable 實現的其它部分,為了更好的強調這些優化工作,我們將深入細節。
7.1 局部性群組
客戶程序可以將多個列族組合成一個局部性群族。對 Tablet 中的每個局部性群組都會生成一個單獨的
SSTable。將通常不會一起訪問的列族分割成不同的局部性群組可以提高讀取操作的效率。例如,在 Webtable
表中,網頁的元數據(比如語言和 Checksum)可以在一個局部性群組中,網頁的內容可以在另外一個群組: 當一個應用程序要讀取網頁的元數據的時候,它沒有必要去讀取所有的頁面內容。
此外,可以以局部性群組為單位設定一些有用的調試參數。比如,可以把一個局部性群組設定為全部存 儲在內存中。Tablet 服務器依照惰性加載的策略將設定為放入內存的局部性群組的 SSTable 裝載進內存。加載 完成之后,訪問屬于該局部性群組的列族的時候就不必讀取硬盤了。這個特性對于需要頻繁訪問的小塊數據特別有用:在 Bigtable 內部,我們利用這個特性提高 METADATA 表中具有位置相關性的列族的訪問速度。
7.2 壓縮
客戶程序可以控制一個局部性群組的 SSTable 是否需要壓縮;如果需要壓縮,那么以什么格式來壓縮。 每個 SSTable 的塊(塊的大小由局部性群組的優化參數指定)都使用用戶指定的壓縮格式來壓縮。雖然分塊 壓縮浪費了少量空間22,但是,我們在只讀取 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 中存儲同一份數據的多個版本的時候, 壓縮效率會更高。
7.3 通過緩存提高讀操作的性能
為了提高讀操作的性能,Tablet 服務器使用二級緩存的策略。掃描緩存是第一級緩存,主要緩存 Tablet服務器通過 SSTable 接口獲取的 Key-Value 對;Block 緩存是二級緩存,緩存的是從 GFS 讀取的 SSTable的
Block。對于經常要重復讀取相同數據的應用程序來說,掃描緩存非常有效;對于經常要讀取剛剛讀過的數據 附近的數據的應用程序來說,Block 緩存更有用(例如,順序讀,或者在一個熱點的行的局部性群組中隨機讀
取不同的列)。
7.3.1 Bloom 過濾器23
如 6.3 節所述,一個讀操作必須讀取構成 Tablet 狀態的所有 SSTable 的數據。如果這些 SSTable 不在內存 中,那么就需要多次訪問硬盤。我們通過允許客戶程序對特定局部性群組的 SSTable 指定 Bloom 過濾器【7】, 來減少硬盤訪問的次數。我們可以使用 Bloom 過濾器查詢一個 SSTable 是否包含了特定行和列的數據。對于 某些特定應用程序,我們只付出了少量的、用于存儲 Bloom 過濾器的內存的代價,就換來了讀操作顯著減少 的磁盤訪問的次數。使用 Bloom 過濾器也隱式的達到了當應用程序訪問不存在的行或列時,大多數時候我們 都不需要訪問硬盤的目的。
7.3.2 Commit 日志的實現
如果我們把對每個 Tablet 的操作的 Commit 日志都存在一個單獨的文件的話,那么就會產生大量的文件, 并且這些文件會并行的寫入 GFS。根據 GFS 服務器底層文件系統實現的方案,要把這些文件寫入不同的磁盤 日志文件時24,會有大量的磁盤 Seek 操作。另外,由于批量提交25中操作的數目一般比較少,因此,對每個 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 服務器能夠檢測出并忽略掉那些由于線程切換而導致的重復的記錄。
7.3.3 Tablet 恢復提速
當 Master 服務器將一個 Tablet 從一個 Tablet 服務器移到另外一個 Tablet 服務器時,源 Tablet 服務器會對 這個 Tablet 做一次 Minor Compaction。這個 Compaction 操作減少了 Tablet 服務器的日志文件中沒有歸并的記 錄,從而減少了恢復的時間。Compaction 完成之后,該服務器就停止為該 Tablet 提供服務。在卸載 Tablet 之 前,源 Tablet 服務器還會再做一次(通常會很快)Minor Compaction,以消除前面在一次壓縮過程中又產生的 未歸并的記錄。第二次 Minor Compaction 完成以后,Tablet 就可以被裝載到新的 Tablet 服務器上了,并且不 需要從日志中進行恢復。
7.3.4 利用不變性
我們在使用 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 集合。
8 性能評估
為了測試 Bigtable 的性能和可擴展性,我們建立了一個包括 N 臺 Tablet 服務器的 Bigtable 集群,這里 N 是可變的。每臺 Tablet 服務器配置了 1GB 的內存,數據寫入到一個包括 1786 臺機器、每臺機器有 2 個 IDE 硬盤的 GFS 集群上。我們使用 N 臺客戶機生成工作負載測試 Bigtable。(我們使用和 Tablet 服務器相同數目的 客戶機以確保客戶機不會成為瓶頸。) 每臺客戶機配置 2GZ 雙核 Opteron 處理器,配置了足以容納所有進程 工作數據集的物理內存,以及一張 Gigabit 的以太網卡。這些機器都連入一個兩層的、樹狀的交換網絡里,在 根節點上的帶寬加起來有大約 100-200Gbps。所有的機器采用相同的設備,因此,任何兩臺機器間網絡來回一 次的時間都小于 1ms。
Tablet 服務器、Master 服務器、測試機、以及 GFS 服務器都運行在同一組機器上。每臺機器都運行一個 GFS 的服務器。其它的機器要么運行 Tablet 服務器、要么運行客戶程序、要么運行在測試過程中,使用這組 機器的其它的任務啟動的進程。
R 是測試過程中,Bigtable 包含的不同的列關鍵字的數量。我們精心選擇 R 的值,保證每次基準測試對每 臺 Tablet 服務器讀/寫的數據量都在 1GB 左右。
在序列寫的基準測試中,我們使用的列關鍵字的范圍是 0 到 R-1。這個范圍又被劃分為 10N 個大小相同 的區間。核心調度程序把這些區間分配給 N 個客戶端,分配方式是:只要客戶程序處理完上一個區間的數據, 調度程序就把后續的、尚未處理的區間分配給它。這種動態分配的方式有助于減少客戶機上同時運行的其它 進程對性能的影響。我們在每個列關鍵字下寫入一個單獨的字符串。每個字符串都是隨機生成的、因此也沒 有被壓縮26。另外,不同列關鍵字下的字符串也是不同的,因此也就不存在跨行的壓縮。隨機寫入基準測試采 用類似的方法,除了行關鍵字在寫入前先做 Hash,Hash 采用按 R 取模的方式,這樣就保證了在整個基準測 試持續的時間內,寫入的工作負載均勻的分布在列存儲空間內。
序列讀的基準測試生成列關鍵字的方式與序列寫相同,不同于序列寫在列關鍵字下寫入字符串的是,序 列讀是讀取列關鍵字下的字符串(這些字符串由之前序列寫基準測試程序寫入)。同樣的,隨機讀的基準測試 和隨機寫是類似的。
掃描基準測試和序列讀類似,但是使用的是 BigTable 提供的、從一個列范圍內掃描所有的 value 值的 API。 由于一次 RPC 調用就從一個 Tablet 服務器取回了大量的 Value 值,因此,使用掃描方式的基準測試程序可以 減少 RPC 調用的次數。
隨機讀(內存)基準測試和隨機讀類似,除了包含基準測試數據的局部性群組被設置為“in-memory”, 因此,讀操作直接從 Tablet 服務器的內存中讀取數據,不需要從 GFS 讀取數據。針對這個測試,我們把每臺Tablet 服務器存儲的數據從 1GB 減少到 100MB,這樣就可以把數據全部加載到 Tablet 服務器的內存中了。
圖 6 中有兩個視圖,顯示了我們的基準測試的性能;圖中的數據和曲線是讀/寫 1000-byte value 值時取得 的。圖中的表格顯示了每個 Tablet 服務器每秒鐘進行的操作的次數;圖中的曲線顯示了每秒種所有的 Tablet 服務器上操作次數的總和。
8.1 單個 Tablet 服務器的性能
我們首先分析下單個 Tablet 服務器的性能。隨機讀的性能比其它操作慢一個數量級或以上27。 每個隨機 讀操作都要通過網絡從 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 調用的 消耗基本抵消了。
8.2 性能提升
隨著我們將系統中的 Tablet 服務器從 1 臺增加到 500 臺,系統的整體吞吐量有了夢幻般的增長,增長的 倍率超過了 100。比如,隨著 Tablet 服務器的數量增加了 500 倍,內存中的隨機讀操作的性能增加了 300 倍。 之所以會有這樣的性能提升,主要是因為這個基準測試的瓶頸是單臺 Tablet 服務器的 CPU。
盡管如此,性能的提升還不是線性的。在大多數的基準測試中我們看到,當 Tablet 服務器的數量從 1 臺 增加到 50 臺時,每臺服務器的吞吐量會有一個明顯的下降。這是由于多臺服務器間的負載不均衡造成的,大 多數情況下是由于其它的程序搶占了 CPU。 我們負載均衡的算法會盡量避免這種不均衡,但是基于兩個主要 原因,這個算法并不能完美的工作:一個是盡量減少 Tablet 的移動導致重新負載均衡能力受限(如果 Tablet 被移動了,那么在短時間內 — 一般是 1 秒內 — 這個 Tablet 是不可用的),另一個是我們的基準測試程序產 生的負載會有波動28。
隨機讀基準測試的測試結果顯示,隨機讀的性能隨 Tablet 服務器數量增加的提升幅度最小(整體吞吐量 只提升了 100 倍,而服務器的數量卻增加了 500 倍)。這是因為每個 1000-byte 的讀操作都會導致一個 64KB 大的 Block 在網絡上傳輸。這樣的網絡傳輸量消耗了我們網絡中各種共享的 1GB 的鏈路,結果導致隨著我們 增加服務器的數量,每臺服務器上的吞吐量急劇下降。
9 實際應用
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
截止到 2006 年 8 月,Google 內部一共有 388 個非測試用的 Bigtable 集群運行在各種各樣的服務器集群上, 合計大約有 24500 個 Tablet 服務器。表 1 顯示了每個集群上 Tablet 服務器的大致分布情況。這些集群中,許 多用于開發目的,因此會有一段時期比較空閑。通過觀察一個由 14 個集群、8069 個 Tablet 服務器組成的集群 組,我們看到整體的吞吐量超過了每秒 1200000 次請求,發送到系統的 RPC 請求導致的網絡負載達到了 741MB/s,系統發出的 RPC 請求網絡負載大約是 16GB/s。
表 2 提供了一些目前正在使用的表的相關數據。一些表存儲的是用戶相關的數據,另外一些存儲的則是 用于批處理的數據;這些表在總的大小、 每個數據項的平均大小、從內存中讀取的數據的比例、表的 Schema 的復雜程度上都有很大的差別。本節的其余部分,我們將主要描述三個產品研發團隊如何使用 Bigtable 的。
9.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%。
9.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 的列族。
9.3 個性化查詢
個性化查詢(www.google.com/psearch)是一個雙向服務;這個服務記錄用戶的查詢和點擊,涉及到各種 Google 的服務,比如 Web 查詢、圖像和新聞。用戶可以瀏覽他們查詢的歷史,重復他們之前的查詢和點擊; 用戶也可以定制基于 Google 歷史使用習慣模式的個性化查詢結果。
個性化查詢使用 Bigtable 存儲每個用戶的數據。每個用戶都有一個唯一的用戶 id,每個用戶 id 和一個列 名綁定。一個單獨的列族被用來存儲各種類型的行為(比如,有個列族可能是用來存儲所有的 Web 查詢的)。 每個數據項都被用作 Bigtable 的時間戳,記錄了相應的用戶行為發生的時間。個性化查詢使用以 Bigtable 為 存儲的 MapReduce 任務生成用戶的數據圖表。這些用戶數據圖表用來個性化當前的查詢結果。
個性化查詢的數據會復制到幾個 Bigtable 的集群上,這樣就增強了數據可用性,同時減少了由客戶端和 Bigtable 集群間的“距離”造成的延時。個性化查詢的開發團隊最初建立了一個基于 Bigtable 的、“客戶側” 的復制機制為所有的復制節點提供一致性保障。現在的系統則使用了內建的復制子系統。
個性化查詢存儲系統的設計允許其它的團隊在它們自己的列中加入新的用戶數據,因此,很多 Google 服 務使用個性化查詢存儲系統保存用戶級的配置參數和設置。在多個團隊之間分享數據的結果是產生了大量的 列族。為了更好的支持數據共享,我們加入了一個簡單的配額機制限制用戶在共享表中使用的空間;配額也 為使用個性化查詢系統存儲用戶級信息的產品團體提供了隔離機制。
10 經驗教訓
在設計、實現、維護和支持 Bigtable 的過程中,我們得到了很多有用的經驗和一些有趣的教訓。
一個教訓是,我們發現,很多類型的錯誤都會導致大型分布式系統受損,這些錯誤不僅僅是通常的網絡 中斷、或者很多分布式協議中設想的 fail-stop 類型的錯誤30。比如,我們遇到過下面這些類型的錯誤導致的問 題:內存數據損壞、網絡中斷、時鐘偏差、機器掛起、擴展的和非對稱的網絡分區31、我們使用的其它系統的 Bug(比如 Chubby)、GFS 配額溢出、計劃內和計劃外的硬件維護。我們在解決這些問題的過程中學到了很多 經驗,我們通過修改協議來解決這些問題。比如,我們在我們的 RPC 機制中加入了 Checksum。我們在設計 系統的部分功能時,不對其它部分功能做任何的假設,這樣的做法解決了其它的一些問題。比如,我們不再 假設一個特定的 Chubby 操作只返回錯誤碼集合中的一個值。
另外一個教訓是,我們明白了在徹底了解一個新特性會被如何使用之后,再決定是否添加這個新特性是 非常重要的。比如,我們開始計劃在我們的 API 中支持通常方式的事務處理。但是由于我們還不會馬上用到 這個功能,因此,我們并沒有去實現它。現在,Bigtable 上已經有了很多的實際應用,我們可以檢查它們真實 的需求;我們發現,大多是應用程序都只是需要單個行上的事務功能。有些應用需要分布式的事務功能,分 布式事務大多數情況下用于維護二級索引,因此我們增加了一個特殊的機制去滿足這個需求。新的機制在通 用性上比分布式事務差很多,但是它更有效(特別是在更新操作的涉及上百行數據的時候),而且非常符合我 們的“跨數據中心”復制方案的優化策略。
還有一個具有實踐意義的經驗:我們發現系統級的監控對 Bigtable 非常重要(比如,監控 Bigtable 自身 以及使用 Bigtable 的客戶程序)。比如,我們擴展了我們的 RPC 系統,因此對于一個 RPC 調用的例子,它可 以詳細記錄代表了 RPC 調用的很多重要操作。這個特性允許我們檢測和修正很多的問題,比如 Tablet 數據結 構上的鎖的內容、在修改操作提交時對 GFS 的寫入非常慢的問題、以及在 METADATA 表的 Tablet 不可用時, 對 METADATA 表的訪問掛起的問題。關于監控的用途的另外一個例子是,每個 Bigtable 集群都在 Chubby 中 注冊了。這可以幫助我們跟蹤所有的集群狀態、監控它們的大小、檢查集群運行的我們軟件的版本、監控集 群流入數據的流量,以及檢查是否有引發集群高延時的潛在因素。
對我們來說,最寶貴的經驗是簡單設計的價值。考慮到我們系統的代碼量(大約 100000 行生產代碼32), 以及隨著時間的推移,新的代碼以各種難以預料的方式加入系統,我們發現簡潔的設計和編碼給維護和調試 帶來的巨大好處。這方面的一個例子是我們的 Tablet 服務器成員協議。我們第一版的協議很簡單:Master 服
務器周期性的和 Tablet 服務器簽訂租約,Tablet 服務器在租約過期的時候 Kill 掉自己的進程。不幸的是,這 個協議在遇到網絡問題時會大大降低系統的可用性,也會大大增加 Master 服務器恢復的時間。我們多次重新 設計這個協議,直到它能夠很好的處理上述問題。但是,更不幸的是,最終的協議過于復雜了,并且依賴一 些 Chubby 很少被用到的特性。我們發現我們浪費了大量的時間在調試一些古怪的問題33,有些是 Bigtable 代 碼的問題,有些是 Chubby 代碼的問題。最后,我們只好廢棄了這個協議,重新制訂了一個新的、更簡單、只使用 Chubby 最廣泛使用的特性的協議。
結論
我們已經講述完了 Bigtable,Google 的一個分布式的結構化數據存儲系統。Bigtable 的集群從 2005 年 4 月開始已經投入使用了,在此之前,我們花了大約 7 年設計和實現這個系統。截止到 2006 年 4 月,已經有 超過 60 個項目使用 Bigtable 了。我們的用戶對 Bigtable 提供的高性能和高可用性很滿意,隨著時間的推移, 他們可以根據自己的系統對資源的需求增加情況,通過簡單的增加機器,擴展系統的承載能力。
由于 Bigtable 提供的編程接口并不常見,一個有趣的問題是:我們的用戶適應新的接口有多難?新的使 用者有時不太確定使用 Bigtable 接口的最佳方法,特別是在他們已經習慣于使用支持通用事務的關系型數據 庫的接口的情況下。但是,Google 內部很多產品都成功的使用了 Bigtable 的事實證明了,我們的設計在實踐 中行之有效。
我們現在正在對 Bigtable 加入一些新的特性,比如支持二級索引,以及支持多 Master 節點的、跨數據中心復制的 Bigtable 的基礎構件。我們現在已經開始將 Bigtable 部署為服務供其它的產品團隊使用,這樣不同 的產品團隊就不需要維護他們自己的 Bigtable 集群了。隨著服務集群的擴展,我們需要在 Bigtable 系統內部處理更多的關于資源共享的問題了。最后,我們發現,建設 Google 自己的存儲解決方案帶來了很多優勢。通過為 Bigtable 設計我們自己的數據模型,是我們的系統極具靈活性。另外,由于我們全面控制著 Bigtable 的實現過程,以及 Bigtable 使用到 的其它的 Google 的基礎構件,這就意味著我們在系統出現瓶頸或效率低下的情況時,能夠快速的解決這些問題。
?
總結
以上是生活随笔為你收集整理的开启大数据时代谷歌三篇论文-BigTable的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 3-spark学习笔记-SparkAPI
- 下一篇: 4-spark学习笔记-spark运行模