BigTable詳解

WangHuAi2020發表於2020-12-28

BigTable

BigTable設計思想

Bigtable 依託於 Google 的 GFS、Chubby 及 SSTable 而誕生,用於解決 Google 內部不同產品在對資料儲存的容量和響應時延需求的差異化,力求在確保能夠容納大量資料的同時減少資料的查詢耗時。為此,作為分散式結構化資料儲存系統的BigTable有以下設計目標:

  • BigTable是用於處理海量資料的,通常是分佈在數千臺普通伺服器上的PB級資料。

  • BigTable要求能夠提供靈活的、高效能的資料儲存方案,因為不同的產品在高吞吐量的批處理或者及時響應以快速返回資料給使用者上的要求迥異;另外,Bigtable叢集配置也有很大差異:有的叢集只有幾臺伺服器,而有的則需要上千臺伺服器、儲存幾百TB的資料。

  • BigTable使用了很多類似資料庫的實現策略,但是它不支援完整的關係資料模型,與之相反,Bigtable採取的是相對寬泛、稀疏而簡單的資料模型,客戶可以動態控制資料的分佈和格式。BigTable中資料沒有嚴格的Schema,客戶可以自己定義Schema,通過選擇資料模式,客戶可以控制資料的位置相關性;同時,客戶也可以自己推測資料底層儲存的位置相關性。

BigTable資料模型

Bigtable 把資料儲存在若干個 Table 中,其資料特徵是一個稀疏的分散式的持久化儲存多維度排序Map由key和value組成,其中key=value的構成如下式子所示,Map中的索引key包含行關鍵字 + 列關鍵字 + 時間戳 三個部分,Map中的每個value都是一個未經解析的byte陣列,這些儲存的資料都被視為字串,Bigtable本身不去解析這些字串,它們通過客戶程式把各種結構化或者半結構化的資料序列化到這些字串裡。

如上圖一個表Table的示例,經過上訴多維度排序Map轉換為BigTable資料模型如下所示,其中行關鍵字為appleLogo列有六個版本,分別由時間戳1976 ,1977 ,1998 ,2000 ,2001 ,2013標識。

BigTable行在儲存資料時BigTable通過行關鍵字的字典順序組織資料,並提供行級事務的支援,即程式在對某一行進行併發更新操作時都是原子的。Table中的每一行都可參與動態分割槽,一個分割槽稱為一個Tablet,Tablet是BigTable中資料分佈和負載均衡調整的最小單位。作為分散式的儲存引擎,Bigtable 會把一個 Table 按行切分成若干個相鄰的 Tablet,這樣,使用者便可通過選擇合適的行關鍵字在資料訪問時有效利用資料的位置相關性

BigTable列列關鍵字組成的集合稱為列族,它是對錶進行訪問控制的基本單位,訪問控制許可權包括新增新的基本資料、讀取基本資料並建立繼承的列族和瀏覽資料等,除了訪問控制之外,磁碟和記憶體的使用統計也是在列族層面進行的。一般情況下一張表的列族不能太多,並且列族在執行期間很少改變。列關鍵字的命名語法是列族:限定詞,其中列族的名字必須是可列印的字串,限定詞可以是任意的字串,使用者在使用列族之前先建立,然後可以在列族中任何的列關鍵字下存放資料。由於存放在同一列族下的所有資料通常都屬於同一個型別的,所以可以對屬於同一列族的資料進行合併壓縮。

BigTable時間戳表中每個資料項可包含同一份資料的不同版本,通過時間戳來索引進行區分。時間戳本質上為 64 位整數,可由 Bigtable 自動設定為資料寫入時精確到毫秒的實時時間,也可由應用程式自己生成具有唯一性的時間戳。每個資料項中不同版本的資料按照時間戳倒序排序,讓最新的資料排在最前面可以優先被讀取。另外,每個列族配有兩個時間戳設定引數,一個是指定儲存個數的只儲存最後N個版本的資料,另一個是指定儲存時間範圍的只儲存“足夠新”版本的資料,使用者由此指定個數或時間範圍內廢棄版本資料的自動垃圾收集。

BigTable體系架構

Bigtable是建立在其它幾個Google基礎構件上的分散式儲存系統,BigTable使用Google分散式檔案系統GFS儲存日誌檔案和資料檔案,同時依賴高可用的、序列化的分散式鎖服務元件Chubby。

BigTable內部使用SSTable儲存資料檔案,SSTable是一個持久化的、排序的、不可更改的Map結構,是key-value的對映,其中key和value的值都是任意位元組串。從內部看,SSTable是一系列的資料塊,通常每個塊的大小可配置;並使用儲存在SSTable末尾的塊索引進行資料塊的定位。在查詢過程中,每次查詢都可以通過一次磁碟搜尋完成,首先使用二分查詢法在已經被載入到記憶體Memory中的塊索引裡找到資料塊的位置Address,然後從硬碟Disk中讀取相應的資料塊,這些資料塊是以副本分散式地儲存在不同ChunkServer中。

BigTable使用Chubby的分散式鎖服務進行一致性控制,一個Chubby服務包括了5個活動的副本,在任何給定的時間內最多只有一個活動的副本被選為Master並處理請求,當活動的副本失效時,Chubby採用演算法來保證副本的一致性。Chubby在BigTable中也被用於查詢Tablet伺服器,以及在Tablet伺服器失效時進行善後。另外,Chubby也被用於儲存一些關鍵資訊,例如,儲存BigTable資料的自引導指令的位置,儲存BigTable的模式資訊即每張表的列族資訊和儲存訪問控制列表。

Bigtable包括三類主要的元件:連結到客戶程式中的庫、一個Master伺服器、多個Tablet伺服器如上圖所示。

在Client端的客戶程式中,每個Chubby客戶程式都維護一個與Chubby服務的會話 (session)並持有租約,以此實現Chubby客戶程式一致性快取。當會話失效則客戶程式擁有的鎖、已開啟的檔案控制程式碼等相關資訊都會失效,如果客戶程式不能在租約到期的時間內重新簽訂會話的租約,則該會話過期失效。Chubby擁有一個名稱空間,它包括目錄和小檔案,每個目錄或檔案可以看作一個鎖,讀寫檔案的操作都是原子性的。Chubby客戶程式可以在檔案和目錄上註冊回撥函式,當檔案或目錄改變或者會話過期時,回撥函式通知客戶程式上述情況。

Master伺服器負責檢測新加入的或者過期失效的Table伺服器,會為Tablet伺服器分配Tablets,並負責均衡Tablet伺服器間的儲存負載以及對儲存在GFS上的檔案進行垃圾收集。除外,Master 還負責處理對模式的相關修改操作,例如建立表和列族。

每個Tablet伺服器都管理一個由Master指定的Tablet集合,負責處理針對這些Tablet的讀寫操作,並負責在Tablet變得過大時對其分割。和很多其他單Master的分散式儲存系統一樣,BigTable中Client讀取資料時也不經過Mater,直接和Tablet伺服器通訊進行讀寫操作,這樣可以降低Master的負載。多個Tablet伺服器組成的BigTable叢集儲存了多個,每個表中都包含了一個Tablet集合,每個Tablet關聯的是一個指定範圍內的的所有相關資料。初始狀態下,一個表只有一個Tablet,隨著表中資料的增長,它被自動分割成多個Tablet。

BigTable執行管理

Tablet定位

BigTable中Tablet的位置資訊使用一個三層的、類似B+樹的結構儲存。如下圖所示,一個儲存在Chubby中的檔案,包含Root Tablet的位置資訊;第一層,Root Tablet中包含一個特殊的METADATA表,記錄METADATA裡所有Tablet的位置資訊;第二層,METADATA表,其中的每個Tablet包含了一個UserTablet的集合;第三層,由多個UserTable儲存最終資料項。

Root Table實際上是METADATA表的第一個Tablet,它永遠不會被分割 ,從而保證Tablet的位置資訊儲存結構不會超過三層。METADATA表中,每個Tablet的位置資訊都存放在一個行關鍵字下,該行關鍵字該Tablet所在表的識別符號該Tablet的最後一行編碼而成。另外,METADATA表還儲存次級資訊,主要包括每個Tablet的事件日誌,例如一個伺服器何時開始為該Tablet提供服務,這種記錄有助於進行效能分析和排除錯誤。

Tablet的客戶端快取 當Client 想要定位某個 Tablet 時,便會遞迴地安裝上述層次向下求得位置,並把中間獲得的Tablet的位置資訊快取在自己的記憶體中,對其進行操作時不必再訪問GFS檔案系統。同時,為了進一步減少訪問的開銷,Client採用預取Tablet地址的手段,即每次需要從METADATA表中讀取一個Tablet的後設資料時,都會多讀取幾個Tablet的後設資料。

如果某一時刻Client發現未快取某個Tablet的地址資訊,或快取在記憶體中的地址已不再有效,它便會再次遞迴地沿著該樹狀儲存結構向上,最終再次向下獲取所需 Tablet 的位置。該定址過程存在兩種情形,一種情形是定址演算法通過三次網路來回通訊定址,其中包括一次Chubby讀操作;另一種情形是定址演算法可能需要最多六次網路來回通訊更新資料,只有在快取中沒有查到資料的時候才能發現資料過期,三次通訊發現快取過期,首先發現檔案region快取失效,接著發現本地取META表的位置快取失效,最後取ROOT的本地快取仍是快取過期,這樣就需要另外三次通訊更新快取資料。

Tablet伺服器狀態檢測

系統中Master使用Chubby跟蹤記錄Tablet伺服器的狀態,Tablet伺服器啟動時在Chubby的一個指定的伺服器目錄下建立一個有唯一性名字的檔案,並且獲取該檔案的獨佔鎖。當網路斷開等原因使得Tablet伺服器和Chubby的會話失效,這時Tablet伺服器就丟失了其在Chubby上的獨佔鎖,那麼Chubby就停止了對該Tablet提供服務。當Tablet恢復時,它通過驗證其在Chubby中的檔案是否存在執行後續操作,若檔案還存在,Tablet伺服器試圖重新獲得對該檔案的獨佔鎖;若檔案不存在,Tablet伺服器就不能再提供服務,並自行退出。

Master實時輪詢Tablet伺服器檔案鎖的狀態,來檢測Tablet伺服器是否還在為Tablet服務,若某個Tablet伺服器報告丟失了檔案鎖,或者Master最近幾次嘗試和它通訊都沒有得到響應,Master將嘗試獲取該Tablet伺服器檔案的獨佔鎖。如果Master成功獲取了獨佔鎖且Chubby執行正常,則認為該Tablet伺服器終止執行或無法與Chubby通訊。此時,Master刪除該Tablet伺服器在Chubby上的伺服器檔案,以確保它不再給Tablet提供服務,並儘快把該節點的Tablet重新分配到其它Tablet伺服器。

如果Master的Chubby會話過期,那麼Master便會認為自己已經失效並主動退出Master退出或故障不會改變現有Tablet在Tablet伺服器上的分配狀態,因為可以通過Chubby獲得Root Table上的字典資料,如果Chubby長時間無法訪問,則認為BigTable失效。

Master自動關閉後重新啟動時執行以下步驟:

  • 從Chubby獲取一個唯一的Master鎖,用來阻止建立其它的Master伺服器例項;

  • 掃描Chubby的伺服器檔案鎖儲存目錄,獲取當前正在執行的伺服器列表;

  • 和所有的正在執行的Tablet表伺服器通訊,獲取每個Tablet伺服器上Tablet的分配資訊;

  • 掃描METADATA表獲取所有的Tablet的集合,在掃描過程中如果發現還沒有分配的Tablet,就將其加入未分配的Tablet集合等待合適的時機分配。

Tablet集合變更

Tablet集合的變更主要存在四種情況,由Master啟動的三種包括建立一個新表、刪除一箇舊表和兩個Tablet的合併;由Tablet伺服器啟動的一種是一個Tablet被分割成兩個小的Tablet。

Tablet伺服器啟動分割事件並完成後,需要在METADATA表中記錄新的Tablet的資訊,以示提交該操作,並通知Master已經提交資訊。若Master沒有收到該通知資訊,Master在要求Tablet伺服器裝載已被分割的子表時會發現一個新的Tablet。這時,通過對比METADATA表中Tablet的資訊,Tablet伺服器會發現Master要求其裝載的Tablet不完整,Tablet伺服器重新向Master伺服器傳送通知資訊。

Tablet讀寫操作

Tablet的資料實際上是儲存在GFS中的,由 GFS 提供資料的冗餘備份。Tablet資料讀寫過程如下圖所示,其中一個 Tablet 由若干個位於 GFS 上的 SSTable 檔案、一個位於記憶體內的 MemTable 以及一份Log 組成。

Tablet寫操作

在進行寫操作時,Bigtable 首先會用更新日誌的方式,把Tablet的更新操作提交到REDO日誌中。而後,插入的資料會被放入到位於記憶體內的一個 MemTable 中,其中 MemTable 保持其內部的資料有序。而對於那些已經持久化的資料則會作為一個個 SSTable 檔案儲存在 GFS 中。在進行寫操作時,Tablet伺服器會先進行相應的檢查,一方面檢查操作格式是否正確,另一方面檢查操作發起者是否有執行許可權,許可權驗證過程是通過從一個Chubby檔案中讀取具有寫許可權的操作者列表,從而驗證許可權。

Tablet伺服器在載入 Tablet 時,首先從 METADATA表中獲取 Tablet的後設資料,包含組成這個Tablet的SSTable列表以及一系列的Redo Point這些Redo Point指向可能含有該Tablet資料的已提交日誌記錄等資訊。然後,Tablet伺服器把SSTable的索引讀進記憶體,並通過重複Redo Point之後提交的更新來重建MemTable。

Memtable 與 SSTable 本身都採取了資料不可變的設計思路:更改操作產生的新條目以Copy On Write的方式放入到 MemTable 中;在這個過程中MemTable的大小不斷增加,當MemTable內的條目數達到一定閾值後,Bigtable 便會將該MemTable凍結,並建立一個新的MemTable,將新到來的請求寫入到新的MemTable中,同時將被凍結的MemTable寫入到新的 SSTable 檔案中,該操作被稱為 Bigtable 的 Minor Compaction。對於已在原有 SSTable 檔案中的舊資料,Bigtable 也不會將其移除。

每一次 Minor Compaction 都會產生一個新的 SSTable 檔案,而過多的 SSTable 檔案會導致後續的讀操作需要合併掃描多個 SSTable 檔案以獲得最新的正確資料。為了限制 SSTable 檔案數,Bigtable 會定期在後臺執行 Merging Compaction,讀取一些SSTable和MemTable的內容,合併成一個新的SSTable ,以此限制這類檔案的數量。Merging Compaction過程完成後可刪除輸入的這些SSTable和MemTable。

Table讀操作

Tablet伺服器進行讀操作時會作類似的完整性和許可權檢查。SSTable和memtable是按字典排序的資料結構,因此可高效生成合併檢視,讀操作就在這樣一個由一系列SSTable和MemTable合併的檢視裡執行 。當進行Tablet的合併和分割時,正在進行的讀寫操作仍然能夠繼續進行。

為了提高Tablet讀操作的訪問速度,可以以區域性性群組 (Locality Group)為單位設定一些除錯引數,客戶程式將通常不會一起訪問的列族分割成不同的區域性性群組,多個可能相鄰訪問列族組合成一個區域性性群組,對應一個單獨的SSTable。當設定一個區域性性群組全部儲存在記憶體中,便可以優化需要頻繁訪問的小塊資料,利用這一特性可以提高METADATA表中具有位置相關性列族的訪問速度。

以區域性性群組為基礎,Tablet伺服器使用二級快取策略進一步提高讀操作的效率,第一級快取稱為掃描快取,主要快取Tablet伺服器通過SSTable介面獲取的Key-Value對;第二級快取稱為Block快取,快取從GFS讀取的SSTable的Block。

BigTable優化

Bloom Filter

為了提高檢索的效率,Bigtable 也允許使用者為特定區域性性群組的SSTable指定Bloom Filter快取來減少硬碟訪問次數。通過消耗一定量的記憶體儲存為 SSTable 檔案構建的 Bloom Filter,以在Client檢索記錄時利用 Bloom Filter 快速地排除某些不包含該記錄的 SSTable,減少需要讀取的 SSTable 檔案數。

Bloom Filter的作用是快速判斷當前SSTable是否在包含目標記錄的SSTable集合中。Bloom filter採用的是雜湊函式的方法:將一個元素對映到一個m長度的陣列上的一個點,當這個點是 1 時,那麼這個元素在集合內,反之則不在集合內。這種方法存在的一個缺陷是當檢測的元素很多時可能發生衝突,一種解決方法是使用k個雜湊函式對應k個點,如果所有點都是 1,則元素在集合中;如果有0,則元素不在集合中,該過程如下圖所示:

初始狀態時,BloomFilter是一個長度為m的位陣列,每一位都置為0。新增元素x時,對x使用k個雜湊函式得到k個雜湊值,對m取餘,對應的bit位設定為1。 判斷y是否屬於集合,對y使用k個雜湊函式得到k個雜湊值,對m取餘,所有對應的位置都是1,則認為y屬於該集合(雜湊衝突,可能存在誤判),否則就認為y不屬於該集合。從上圖結果可以看出y1不是集合中的元素,y2屬於這個集合或者是一個false positive。

BloomFilter的關鍵在於hash演算法的設定和bit陣列的大小,通過權衡得到一個錯誤概率可以接受的結果。關於BloomFilter的詳細內容可以參照Bloom Filter

Commit日誌實現

Bigtable 使用了 Write-Ahead Log 的做法來確保資料高可用,如果把對每個Tablet的操作的Commit日誌都存一個檔案,則會產生大量的日誌檔案及其並行寫操作,這將導致大量的磁碟Seek操作,同時影響批量提交的優化效果。

為此,設定每個Tablet伺服器一個Commit日誌檔案,混合對多個Tablet的修改日誌記錄,以追加方式寫入同一個日誌檔案。但是這樣的設計給Tablet伺服器恢復帶來了麻煩:如果該 Tablet伺服器下線,其所負責的Tablet可能會被重新分配到其他若干個Tablet伺服器上,它們在恢復 Tablet MemTable的過程中會重複讀取上一個Tablet伺服器產生的 Commit Log。

為了解決Tablet伺服器當機恢復的問題,Tablet伺服器在讀取Commit Log前會向Master傳送訊號,Master就會發起一次對原Commit Log的排序操作,該操作將原Commit Log按64MB切分為若干部分,每個部分併發地按照 (table, row name, log sequence number) 進行排序。完成排序後,Tablet伺服器讀取Commit Log時便可只讀取自己需要的那一部分,減少重複讀取。

 

Reference

Bigtable: A Distributed Storage System for Structured Data

Bigtable 論文詳述