SSTable 介紹(二)


作者:Jack47

上一篇SSTable 介紹(一)介紹了SSTable的適用場景和leveldb中SSTable的設計。本篇介紹SSTable文件的結構組成。

SSTable的特點##

首先明確一下上文中提到的SSTable特點:

  1. 需要存儲的<鍵,值>格式的字節數據
  2. 鍵可以重復,鍵值對不需要對齊,即可以是任意長度的
  3. 需要支持高效的隨機讀取操作

讀者們不妨想一想,如果讓你設計一種數據結構來支持上述的設計目標,你會怎么設計呢?

SSTable文件的結構##

一些輔助的類和結構體###

在去看leveldb/table/table.cc文件內容時,經常會看到以下的幾個類或者結構體,他們是對SSTable文件底層內容的抽象,它們使用起來像積木一樣方便,能夠幫助上層調用者關注底層的抽象,而且類型的名稱取的也很到位,讓代碼閱讀者很容易理解其用途。

BlockHandle:####

BlockHandle的結構如下圖所示:

BlockHandle

BlockHandle的結構和其指向的文件數據

BlockHandle是指向文件中一部分連續數據的指針,顧名思義,可以指向一個數據塊[data block]或者元信息塊[meta block]。其實就是存儲了兩個成員變量:代表文件偏移的uint64_t offset_和代表數據長度的uint64_t size_。這樣有了數據的起始地址和數據的長度,就可以定位到這片數據了。從其BlockHandle.Encode函數的實現可以知道總共需要占用16個字節就可以存儲它序列化后的數據。

Slice:####

Slice的結構如下圖所示:

Slice

Slice和其指向的數據空間

Slice是一個簡單的類,但是發揮的作用十分強大,可以用它來方便地指向一片連續存儲的數據,可以認為指向的就是一個數組。它存儲了指向的數據的起始地址和數據的字節長度。想想這樣操作數據是不是就十分簡潔了,再也不需要配套的兩個變量來表示數據的起始地址和數據的大小了,使用 slice.data()拿到數組首地址,使用slice.size()拿到數組的長度,還有 slice.empty()來判斷數組是否為空。需要注意的是這個類只是引用了一個數組,數組的存儲空間由調用者管理,調用者必須保證在引用的數據被釋放后,對應的slice對象不再使用。

BlockContents:####

代表從文件中讀取到的每一塊[block]的數據的信息,包含實際的數據[Slice data],是否可以緩存[cachable]和是否是在堆上新申請的[heap_allocated]這三個屬性。

SSTable存儲到磁盤上的數據是支持壓縮的,如果磁盤上的數據是經過壓縮之后的,那就需要在從磁盤上讀取的時候解壓縮,此時BlockContents存儲的是解壓縮之后的數據,是在讀取塊數據時從堆上新申請的。heap_allocated就是用來標示這種情況,這片內存需要調用者負責最終釋放。

SSTable文件的布局###

從leveldb/doc/table_format.txt中可以看到,一個SSTable文件的內部數據的布局如下圖所示:

sstable

SSTable文件的布局示意圖

下文中提到的這些塊數據是根據block_builder.cc中的代碼來生成的。

  • 數據塊[data block]

文件中鍵值對key/value是有序存儲的,被划分成一系列的數據塊[data block]。這些塊在文件的開頭一塊緊挨一塊地存儲起來。可以選擇開啟壓縮選項來壓縮數據之后再存儲到數據塊里。

可見數據塊的存儲方式就跟數組一樣,是連續存儲

  • 元信息塊[meta block]

在數據塊之后,存儲的是一些元信息塊。每個元信息塊數據也是由block_builder.cc中的代碼生成的,也是支持壓縮選項。可見元信息塊的存儲方式也跟數據塊類似。

  • 元索引數據塊[metaindex block]

為每個元信息塊生成一條記錄,記錄的key是元信息塊的名稱,記錄的值是指向元信息塊的BlockHandle

可見每個元信息塊中存儲的數據用途不一樣,而且一個塊大小就完全能夠存儲的下。否則需要多個塊,那記錄的key就不能取元信息塊的名稱了。由於元信息有好幾類,所以需要在這里存儲給各個元信息塊建立的索引。

  • 索引塊[index block]

這個塊包含了為每個數據塊生成的索引記錄,key是一個>=這數據塊中最后一個key並小於后續數據塊首個key的字符串,value是指向數據塊的BlockHandle。

可以看到上述的每種類型的塊數據,都可以用一個BlockHandle來表示。

  • Footer

每個文件尾部都包含一個固定長度[40 Bytes]的Footer,它包含了指向元索引塊[metaindex block]和索引塊[index block]的兩個BlockHandle,以及一個8字節的magicNumber。

默認塊的大小是大概是未壓縮的4KB字節的數據。塊的大小可以根據應用程序使用SSTable數據的方式來調整,例如主要是對數據庫內容進行批量掃描的應用,可以適當調大塊的大小;對於大量讀取小數據的應用,可以調小塊的大小來看看效率是否提升。

第一次接觸這種類型的文件布局,可能很難理解上述的各個組成部分,可以嘗試這樣去理解:

  1. 由於機械磁盤本身是物理分塊的[固態硬盤除外],所以SSTable也參照磁盤分塊大小來進行數據分塊,能夠盡量減少訪問磁盤的開銷,而且即使有數據毀壞,也會局限在幾個塊上,不會影響其他塊數據的讀取。
  2. 為這些數據/元信息塊建立索引,就類似給書籍制作目錄頁一樣,便於讀取某個key時,可以快速定位key落在哪個數據塊上,而不需要順序遍歷SSTable的每個塊。
  3. Footer相當於是最高一級的索引,從這里開始,就能夠獲取到SSTable所有的信息。因此把它設計成固定大小,這樣從磁盤上反序列化SSTable時,首先把Footer讀取出來,然后按圖索驥,就可以把其他的塊也加載到內存里。

上面提到了meta block,目前支持的meta block有以下兩種:

  1. "過濾“元信息塊["filter" Meta Block]

當數據庫創建的時候指定了“過濾策略”,那么每個SSTable會存儲一個過濾塊[filter block][過濾策略所需的數據不超過一個塊的大小]。“元索引”塊包含一條記錄,映射了從"filter. "到指向過濾塊的BlockHandle,這里" "是過濾策略的"Name()"函數返回的字符串。

過濾塊存儲了一系列過濾器,其中filter i 包含了對一個文件偏移落於范圍[ibase ... (i+1)base-1]上的數據塊所存儲的所有key執行FilterPolicy::CreateFilter()后的輸出。

目前,“base”是2KB。所以舉個例子,當塊X和Y起始於范圍[0KB .. 2KB-1],所有的X和Y的key都會通過調用FilterPolicy::CreateFilter()來被轉換到同一個過濾器里,同時這個過濾器會被存儲為過濾塊里的第一個過濾器。
由於過濾串的長度不一,所以需要存儲每個過濾器的起始地址。過濾塊的格式見上圖。

  1. “統計”元信息["stats" Meta Block]

這一塊包含了一些統計信息。key是統計數據的名稱。值是統計數字。
記錄了如下統計信息:

數據大小

索引大小

鍵大小(未壓縮)

值大小(未壓縮)

記錄的數量

數據塊的數量

剩下還有一些細節沒有介紹,例如索引塊、數據塊內部的格式,數據塊的讀取操作等,留待SSTable介紹系列的第三篇,也是最后一篇來介紹。

回到本系列目錄:leveldb源碼學習系列


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM