高阶数据结构:SSTable
1. 前言
最近在組會上面通過小組討論論文時,發現了SSTable這個數據結構。課后為了深入分析和學習這個數據結構,我做了一些資料查閱。在查詢相關分布式的書籍后,找到了SSTable的數據結構,現將其作為筆記記錄下來。之前整理的BigTable論文里面提及到了SStable,但是當時并沒有引起我的注意。現在將深入理解這個數據結構——SSTable。
2. SSTable的定義
Google SSTable文件格式在內部用于存儲Bigtable數據。 它的格式為文件本身就是一個排序的、不可變的、持久的Key/Value對Map,其中Key和value都可以是任意的byte字符串。提供操作以查找與指定鍵相關聯的值,并遍歷指定鍵范圍內的所有鍵/值對。使用Key來查找Value,或通過給定Key范圍遍歷所有的Key/Value對。每個SSTable包含一系列的Block(一般Block大小為64KB,但是它是可配置的),在SSTable的末尾是Block索引,用于定位Block,這些索引在SSTable打開時被加載到內存中,在查找時首先從內存中的索引二分查找找到Block,然后一次磁盤尋道即可讀取到相應的Block。還有一種方案是將這個SSTable加載到內存中,從而在查找和掃描中不需要讀取磁盤。
3. BigTable的架構
BigTalbe構建在GFS之上,為文件系統增加了一層分布式索引層。另外,BigTable依賴Google的Chubby(即分布式鎖)進行服務器選舉以及維護全局信息維護。
如圖,Bigtable將大表劃分為大小在100M~200M的子表(tablet),每個子表對應一個連續的數據范圍。Bigtable主要由三個部分組成:客戶端程序庫(client)、一共主控服務器(Master)和多個子表服務器(tablet server)。
- 客戶端程序庫(Client):提供Bigtable到應用程序的接口,應用程序通過客戶端程序庫對表格的數據的單元進行增、刪、改、查等操作。客戶端通過Chubby鎖服務器獲取一些控制信息,但所有表格的數據內容都在客戶端與子表服務器之間進行傳輸。
- 主控服務器(Master):管理所有的子表服務器,包括分配子表給子表服務器,指導子表服務器實現子表的合并,接收來自子表服務器的分裂信息,監控子表服務器,在子表服務器之間進行負載均衡并實現子表服務器的故障恢復。
- 子表服務器(tablet server):實現子表的裝載/卸出、表格內容的讀和寫,子表的合并和分裂。Tablet Server服務的數據包括操作日志以及每個子表上的sstable數據,這些數據都存儲在底層的GFS中。
4. BigTable中的存儲引擎
Bigtable采用Merge-dump存儲引擎。數據寫入時需要先寫操作日志,成功后應用到內存中的MemTable中,寫操作日志是往磁盤中的日志文件追加數據,很好地利用了磁盤設備順序讀寫的特性。當內存中的MemTable達到一定大小,需要將MemTable轉儲(Dump)到磁盤中生成SSTable文件。由于數據同時存在MemTable和可能多個SSTable中,讀取操作需要按從舊到新的時間順序合并SSTable和內存中的MemTable數據。數據在SSTable中連續存放,因此可以同時滿足隨機讀取和順序讀取兩種需求。為了防止磁盤中的SSTable文件過多,需要定時將多個SSTable通過Compaction過程合并成一個SSTable,從而減少后續讀操作需要讀取的文件個數。一般情況下,如果寫操作比較少,我們總是能夠使得對每一份數據同時只存在一個SSTable和一個MemTable,也就是說,隨機讀取和順序讀取都只需要訪問一次磁盤。插入、刪除、更新、增加等操作在Merge-dump引擎中都看成一回事,除了最早生成的SSTable外,SSTable中記錄的只是操作,而不是最終的結果,需要等到讀取時才合并到最終結果。
Bigtable中包含三種Compaction策略:Minor Compaction、Merging Compaction和Major Compaction。其中,Minor Compaction把內存中的MemTable轉儲到GFS中,Merging Compaction和Major Compaction合并GFS中的多個SSTable文件生成一個更大的SSTable。Minor Compaction主要是為了防止內存占用過多,Merging和Major Compaction則是為了防止讀取文件個數過多。Merging Compaction和Major Compaction的區別在于Major Compaction會合并所有的SSTable文件和內存中的MemTable,生成最終結果;而Merging Compaction生成的SSTable文件可能包含一些操作,比如刪除、增加等。
5. SSTable的數據結構
數據在SSTable中按照主鍵有序存儲,每個SSTable由若干個大小相近的數據塊(Block)組成,每個數據塊包含若干行。數據塊的大小一般在8~64KB之間,允許用戶配置。Tablet Server的緩存包括兩種:塊緩存(Block Cache)和行緩存(Row Cache)。其中,塊緩存的單位為SSTable中的數據塊,行緩存的單位為一行記錄。隨機讀取時,首先查找行緩存;如果行緩存不命中,接著再查找塊緩存。另外,Bigtable還支持布隆過濾器(Bloom Filter),如果讀取的數據行在SSTable中不存在,可以通過布隆過濾器(Bloom Filter)發現,從而避免一次讀取GFS文件操作。注:布隆過濾器可以用于檢索一個元素是否在一個集合中。它的優點是空間效率和查詢時間都比一般的算法要好的多,缺點是有一定的誤識別率和刪除困難。
SSTable中的數據按主鍵排序后存放在連續的數據塊(Block)中,塊之間也有序。接著,存放數據塊索引,由每個Block最后一行的主鍵組成,由于數據查詢中的Block定位。接著,存放布隆過濾器和表格的Schema信息。最后,存放固定大小的Trailer以及Trailer的偏移位置。
SSTable數據存儲結構
- Data Block:存放連續的數據塊
- Block Index:存放連續的塊索引。描述一個data block,存儲著對應data block的最大Key值,以及data block在文件中的偏移量和大小
- Bloom Filter:布隆過濾器(Bloom Filter),用于判斷讀取的數據是否在當前SSTable上。
- Table Schema: 當前SSTable的表格Schema信息
- Fixed Trailer:當前SSTable的Block Index的塊索引大小
- Trailer Offset:當前SSTable的Block Index的塊索引在文件存儲下的偏移量
查找SSTable時,首先從子表的索引信息中讀取SSTable Trailer的偏移位置,接著獲取Trailer信息。根據Trailer中記錄的信息,可以獲取塊索引的大小和偏移,從而將整個塊索引加載到內存中。根據塊索引記錄的每個Block的最后一行的主鍵,可以通過二分查找定位到查找的Block。最后將Block加載到內存中,通過二分查找Block中記錄的行索引查找到相應的偏移量,然后查找到具體某一行Row X。本質上看,SSTable是一個兩級索引結構:塊索引以及行索引;而整個ChunkServer是一個三級索引結構:子表索引、塊索引以及行索引。
SSTable分為兩種格式:稀疏格式和稠密格式。
- 稀疏格式:某些列可能存在,也可能不存在,因此,每一行只存儲包含實際值的列,每一列存儲的內容為:<列ID,列值>(<Column ID, Column Value>);
- 稠密格式:每一行都需要存儲所有列,每一列只需要存儲列值,不需要存儲列ID,這是因為列ID可以從表格Schema中獲取。
5.1 舉例說明
假設有一張表格包含10列,列ID為1~10,表格中有一行的數據內容為:
那么,采用稀疏格式存儲,內容為:<2, 20>,??, 30>,<5, 50>,<7, 70>,<8, 80>;如果采用稠密格式存儲,內容為:null,20,30,null,50,null,70,80,null,null。
ChunkServer中的SSTable為稠密格式,而UpdateServer中的SSTable為稀疏格式,且存儲了多張表格的數據。另外,SSTable支持列組Cloumn Group,將同一個列組下的多個列的內容存儲在一起。列組是一種行列混合存儲模式,將每一行的所有列分成多個組(稱為列組),每個列組內部按行存儲。
當一個SSTable中包含多個表格/列組時,數據按照[表格ID,列組ID,行主鍵]([table_id, column group id, row_key])的形式有序存儲。
6. SSTable在BigTable中的相應操作
6.1 Tablet Serving
在新數據寫入時,這個操作首先提交到日志中作為redo紀錄,最近的數據存儲在內存的排序緩存memtable中;舊的數據存儲在一系列的SSTable 中。在recover中,tablet server從METADATA表中讀取metadata,metadata包含了組成Tablet的所有SSTable(紀錄了這些SSTable的元 數據信息,如SSTable的位置、StartKey、EndKey等)以及一系列日志中的redo點。Tablet Server讀取SSTable的索引到內存,并replay這些redo點之后的更新來重構memtable。
當寫操作到達tablet server時,tablet server將檢查其格式是否正確,以及發送方是否有權限執行該寫操作。通過從Chubby文件中讀取允許寫操作的權限列表來執行授權(這在Chubby客戶端緩存中幾乎總是命中)。有效的變動將寫入提交日志。Group Commit通過提交多個寫操作用于提高吞吐量[13,16]。提交寫入后,其內容將插入到memtable中。
在讀操作時時,完成格式、授權等檢查后,讀會同時讀取SSTable、memtable(HBase中還包含了BlockCache中的數據)并合并他們的結果,由于SSTable和memtable都是字典序排列,因而合并操作可以很高效完成。
6.2 Compaction
Bigtable中包含三種Compaction策略:Minor Compaction、Merging Compaction和Major Compaction。
-
Minor Compaction:隨著memtable大小增加到一個閥值,這個memtable會被凍住而創建一個新的memtable以供使用,而舊的memtable會轉換成一個SSTable而寫入到底層存儲GFS中,這個過程叫做minor compaction。這個minor compaction可以減少內存使用量,并可以減少日志大小,因為持久化后的數據可以從日志中刪除。在minor compaction過程中,可以繼續處理讀寫請求。
-
Merge Compaction:每次minor compaction會生成新的SSTable文件,如果SSTable文件數量增加,則會影響讀的性能,因而每次讀都需要讀取所有SSTable文件,然后合并結果,因而對SSTable文件個數需要有上限,并且時不時的需要在后臺做merging compaction,這個merging compaction讀取一些SSTable文件和memtable的內容,并將他們合并寫入一個新的SSTable中。當這個過程完成后,這些源SSTable和memtable就可以被刪除了。
-
Merge Compaction:如果一個merging compaction是合并所有SSTable到一個SSTable,則這個過程稱做major compaction。一次major compaction會將mark成刪除的信息、數據刪除,而其他兩次compaction則會保留這些信息、數據(mark的形式)。Bigtable會時不時的掃描所有的Tablet,并對它們做major compaction。這個major compaction可以將需要刪除的數據真正的刪除從而節省空間,并保持系統一致性。
6.3 SSTable的locality和In Memory
在Bigtable中,它的本地性是由Locality group來定義的,即多個column family可以組合到一個locality group中,在同一個Tablet中,使用單獨的SSTable存儲這些在同一個locality group的column family。HBase把這個模型簡化了,即每個column family在每個HRegion都使用單獨的HFile存儲,HFile沒有locality group的概念,或者一個column family就是一個locality group。
在Bigtable中,還可以支持在locality group級別設置是否將所有這個locality group的數據加載到內存中,在HBase中通過column family定義時設置。這個內存加載采用延時加載,主要應用于一些小的column family,并且經常被用到的,從而提升讀的性能,因而這樣就不需要再從磁盤中讀取了。
6.4 SSTable壓縮
客戶端可以控制是否壓縮locality group的SSTable,以及如果壓縮,則使用哪種壓縮格式。 用戶指定的壓縮格式將應用于每個SSTable塊(其大小可通過特定于位置組的調整參數來控制)。 盡管我們通過分別壓縮每個塊而損失了一些空間,但我們的好處是,可以讀取SSTable的一小部分而無需解壓縮整個文件。 許多客戶端使用兩遍自定義壓縮方案。 第一遍使用Bentley和McIlroy的方案[6],該方案在一個大窗口中壓縮長的公共字符串。 第二遍使用快速壓縮算法,該算法在一個小的16 KB數據窗口中查找重復項。 兩種壓縮過程都非常快-在現代機器上,它們的編碼速度為100-200 MB / s,解碼速度為400-1000 MB / s。
6.5 SSTable的讀緩存
為了提升讀的性能,Bigtable采用兩層緩存機制:
- Scan Cache:Scan Cache是一個更高級別的緩存,它將SSTable接口返回的Key/Value緩存到tablet server code。
- Block Cache:Block Cache對于傾向于讀取與其最近讀取的數據接近的數據的應用程序很有用(例如,順序讀取或對熱點行內同一locality group中不同列隨機讀取)
Block Cache是較低級的緩存,它緩存從GFS讀取的SSTables塊。 Scan Cache對于傾向于重復讀取相同數據的應用程序最有用。
6.6 Bloom Filter
前文有提到Bigtable采用合并讀,即需要讀取每個SSTable中的相關數據,并合并成一個結果返回,然而每次讀都需要讀取所有SSTable,自然會耗費性能,因而引入了Bloom Filter,它可以很快速的找到一個RowKey不在某個SSTable中的事實(注:反過來則不成立)。
7. SSTable設計成Immutable的好處
在SSTable定義中就有提到SSTable是一個Immutable的order map,這個Immutable的設計可以讓系統簡單很多:
關于Immutable的優點有以下幾點:
總結
以上是生活随笔為你收集整理的高阶数据结构:SSTable的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Kafka:Zero-Copy零拷贝
- 下一篇: 一周一论文(翻译)——[Acta 199