Leveldb原始碼分析 1

2021-06-12 13:55:18 字數 4619 閱讀 2943

【前言:看了一點oceanbase,沒有意志力繼續堅持下去了,暫時就此中斷,基本上算把master看完了,比較重要的update server和merge server**卻沒有細看。中間又陸續研究了hadoop的原始碼,主要是name node和寫入pipeline。主要的目的是想看看name node對namespace的管理,以及hadoop在寫入操作時,client、data node和name node之間是如何互動的,特別是涉及到namenode的,以及寫入出現錯誤時的處理邏輯。沒辦法,和分布式儲存扯不開了。

其後看到了leveldb,除去測試部分,**不超過1.5w行。這是乙個單機k/v儲存系統,決定看完它,並把原始碼分析完整的寫下來,還是會很有幫助的。我比較厭煩太複雜的東西,而leveldb的邏輯很清晰,**不多、風格很好,功能就不用講了,正合我的胃口。btw,分析leveldb也參考了網上一些朋友寫的分析blog,如巴山獨釣。】

2023年1月21號開始研究下leveldb的**,google兩位大牛開發的單機kv儲存系統,涉及到了skip list、記憶體kv table、lru cache管理、table檔案儲存、operation log系統等。先從邊邊角角的小角色開始掃。

不得不說,google大牛的**風格太好了,讀起來很舒服,不像有些開源專案,很快就看不下去了。

開始之前先來看看leveldb的基本框架,幾大關鍵元件,如圖1-1所示。

圖1-1

leveldb是一種基於operation log的檔案系統,是log-structured-merge tree的典型實現。lsm源自ousterhout和rosenblum在2023年發表的經典**<<

the design and implementation of a log-structured file system 

>>。

當op log檔案大小超過限定值時,就定時做check point。leveldb會生成新的log檔案和memtable,後台排程會將immutable memtable的資料匯出到磁碟,形成乙個新的sstable檔案。sstable就是由記憶體中的資料不斷匯出並進行compaction操作後形成的,而且sstable的所有檔案是一種層級結構,第一層為level 0,第二層為level 1,依次類推,層級逐漸增高,這也是為何稱之為leveldb的原因。

先說下**中的一些約定:

leveldb

對於數字的儲存是

little-endian

的,在把int32或者int64轉換為char*的函式中,是按照先低位再高位的順序存放的,也就是little-endian的。

把乙個int32或者int64格式化到字串中,除了上面說的little-endian位元組序外,大部分還是變長儲存的,也就是varint。對於varint,每byte的有效儲存是7bit的,用最高的8bit位來表示是否結束,如果是1就表示後面還有乙個byte的數字,否則表示結束。直接見encode和decode函式。

在操作log中使用的是fixed儲存格式。

是基於unsigned char的,而非char。

別看是基本資料結構,有些也不是那麼簡單的,像lru cache管理和skip list那都算是leveldb的核心資料結構。

leveldb中的基本資料結構,它包括length和乙個指向外部位元組陣列的指標。和string一樣,允許字串中包含』\0』。

提供一些基本介面,可以把const char*和string轉換為slice;把slice轉換為string,取得資料指標const char*。

leveldb 中的返回狀態,將錯誤號和錯誤資訊封裝成status類,統一進行處理。並定義了幾種具體的返回狀態,如成功或者檔案不存在等。

為了節省空間status並沒有用std::string來儲存錯誤資訊,而是將返回碼(code), 錯誤資訊message及長度打包儲存於乙個字串陣列中。

成功狀態ok 是null state_,否則state_ 是乙個包含如下資訊的陣列:  

state_[0..3] == 訊息message長度

state_[4]    == 訊息code

state_[5..]  ==訊息message 

leveldb的簡單的記憶體池,它所作的工作十分簡單,申請記憶體時,將申請到的記憶體塊放入std::vector blocks_中,在arena的生命週期結束後,統一釋放掉所有申請到的記憶體,內部結構如圖2.3-1所示。

圖2.3-1

arena主要提供了兩個申請函式:其中乙個直接分配記憶體,另乙個可以申請對齊的記憶體空間。arena沒有直接呼叫delete/free函式,而是由arena的析構函式統一釋放所有的記憶體。

應該說這是和leveldb特定的應用場景相關的,比如乙個memtable使用乙個arena,當memtable被釋放時,由arena統一釋放其記憶體。

skip list(跳躍表)是一種可以代替平衡樹的資料結構。skip lists應用概率保證平衡,平衡樹採用嚴格的旋轉(比如平衡二叉樹有左旋右旋)來保證平衡,因此skip list比較容易實現,而且相比平衡樹有著較高的執行效率。

從概率上保持資料結構的平衡比顯式的保持資料結構平衡要簡單的多。對於大多數應用,用skip list要比用樹更自然,演算法也會相對簡單。由於skip list比較簡單,實現起來會比較容易,雖然和平衡樹有著相同的時間複雜度(o(logn)),但是skip list的常數項相對小很多。skip list在空間上也比較節省。乙個節點平均只需要1.333個指標(甚至更少),並且不需要儲存保持平衡的變數。

如圖2.4-1所示。

圖2.4-1

在leveldb中,skip list是實現memtable的核心資料結構,memtable的kv資料都儲存在skip list中。

leveldb內部通過雙向鍊錶實現了乙個標準版的lrucache,先上個示意圖,看看幾個資料之間的關係,如圖2.5-1。

圖2.5-1

接下來說說leveldb實現lrucache的幾個步驟,很直觀明了。

s1 定義乙個lruhandle結構體,代表cache中的元素。它包含了幾個主要的成員:

void* value; 這個儲存的是cache的資料;

void (*deleter)(const slice&, void* value);這個是資料從cache中清除時執行的清理函式;

後面的三個成員事關lrucache的資料的組織結構:

> lruhandle *next_hash;

指向節點在hash table鍊錶中的下乙個hash(key)相同的元素,在有碰撞時leveldb採用的是鍊錶法。最後乙個節點的next_hash為null。

> lruhandle *next, *prev;

節點在雙向鍊錶中的前驅後繼節點指標,所有的cache資料都是儲存在乙個雙向list中,最前面的是最新加入的,每次新加入的位置都是head->next。所以每次剔除的規則就是剔除list tail。

s2 leveldb自己實現了乙個hash table:handletable,而不是使用系統提供的hash table。這個類就是基本的hash操作:lookup、insert和delete。hash table的作用是根據key快速查詢元素是否在cache中,並返回lruhandle節點指標,由此就能快速定位節點在hash表和雙向鍊錶中的位置。

它是通過lruhandle的成員next_hash組織起來的。

handletable使用lruhandle **list_儲存所有的hash節點,其實就是乙個二維陣列,一維是不同的hash(key),另一維則是相同hash(key)的碰撞list。

每次當hash節點數超過當前一維陣列的長度後,都會做resize操作:

lruhandle** new_list = new lruhandle*[new_length];

然後複製list_到new_list中,並刪除舊的list_。

s3 基於handletable和lruhandle,實現了乙個標準的lrucache,並內建了mutex保護鎖,是執行緒安全的。

其中儲存所有資料的雙向鍊錶是lruhandle lru_,這是乙個list head;

hash表則是handletable table_;

s4 shardedlrucache類,實際上到s3,乙個標準的lru cache已經實現了,為何還要更近一步呢?答案就是速度!

為了多執行緒訪問,盡可能快速,減少鎖開銷,shardedlrucache內部有16個lrucache,查詢key時首先計算key屬於哪乙個分片,分片的計算方法是取32位hash值的高4位,然後在相應的lrucache中進行查詢,這樣就大大減少了多執行緒的訪問鎖的開銷。

lrucache shard_[knumshards]

它就是乙個包裝類,實現都在lrucache類中。

2.6 其它

此外還有其它幾個random、hash、crc32、histogram等,都在util資料夾下,不仔細分析了。

leveldb原始碼分析1

leveldb是乙個key value型的儲存引擎,由google開發,並宣布在bsd許可下開放源 plain git clone plain cd leveldb make all 此時生成libleveldb.a庫檔案。拷貝leveldb的標頭檔案到 usr include下 plain cp ...

levelDB原始碼分析 SSTable

sstable是bigtable中至關重要的一塊,對於leveldb來說也是如此,對leveldb的sstable實現細節的了解也有助於了解bigtable中一些實現細節。本節內容主要講述sstable的靜態布局結構,sstable檔案形成了不同level的層級結構,至於這個層級結構是如何形成的我們...

Leveldb原始碼分析 2

輕鬆一刻,前面約定中講過leveldb使用了很多varint型編碼,典型的如後面將涉及到的各種key。其中的編碼 解碼函式分為varint和fixedint兩種。int32和int64操作都是類似的。首先是fixedint編碼,直接上 很簡單明瞭。void encodefixed32 char bu...