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