rocksdb-share

最近項目中用到這個nb的玩意,所以就花時間研究了下,同時整理下助自己記憶。這個猛虎上山的logo就是rocksdb官方的logo。 同時借用官網的一句話來綜述下: 一個持久性的key-value存儲,為了更快速的存儲環境而生。

rocks特性

rocksdb是一個可嵌入的C++庫, 可用于存儲任意大小字節流的key, value 結構。支持的方法有Put, Get, Delete, 傳統的數據庫的常用方法CRUD.其中update這個方法對rocksdb來說也是一種Put方法。同時因為rocksdb是按key的順序存儲數據,所以還支持Iterator迭代的方法,即就是定位到數據庫中的一個key后,可以從這個key開始正向的掃描,也可以逆向的掃描。同時支持PrefixExtractor前綴迭代,假設我們存儲的key有20位,前10位為時間信息,那么我們可以根據前綴迭代把某個時間段的數據拉出來,這個需要我們在創建數據庫時配置PrefixExtractor的位數為10,默認是0。同時rocksdb支持快照Snapshot, 每次以只讀方式Get數據時都會創建一個快照,在這個時間點之前的數據對Get可見,之后不可見。同時rocksdb還提供一些用戶可定義的filter, 比如提供bloomfilter可以優化讀性能;提供DB級別的ttlfilter, 使得超過過期時間的數據在compaction過程中被刪除。

Rocksdb還支持寫入的原子性,數據的持久化,數據校驗,數據壓縮,數據備份,數據緩存等一些特性。

寫邏輯

我們從一條數據對寫入來了解rocksdb的存儲結構吧。


首先當一條數據寫入rocksdb時, 會將這條記錄封裝成一個batch, 也可以是多條記錄一個batch,由batch來保證原子操作。就是一個batch里的數據要么全部成功要么全部失敗。

第一步先以日志的形式落地磁盤,記write ahead log, 落地成功后再寫入memtable。這里記錄wal的原因就是防止重啟時內存中的數據丟失。所以在db重新打開時會先從wal恢復內存中的mentable. 可配置WAL保存在可靠的存儲里。

這里的memtable是在內存中的一個跳表結構(skiplist)。每一個節點都是存儲著一個key, value. 跳表可使查找的復雜度為logn, 同時插入數據非常簡單。每個batch獨占memtable的寫鎖。這個是為了避免多線程寫造成的數據錯亂。

當memtable的數據大小超過閾值(write_buffer_size)后,會新生成一個memtable繼續寫,將前一個memtable保存為只讀memtable.

當只讀memtable的數量超過閾值后,會將所有的只讀memtable合并并flush到磁盤生成一個SST文件。這里的SST屬于level0, level0中的每個SST有序,整個level0不一定有序。

當level0的sst文件數超過閾值或者總大小超過閾值,會觸發compaction操作,將level0中的數據合并到level1中。同樣level1的文件數超過閾值或者總大小超過閾值,也會觸發compaction操作, 這時候隨機選擇一個sst合并到更高層的level中。這里有兩點比較重要,1:level1 及其以上的level都整體有序。每個sst存儲一個范圍的數據互不交叉互不重合;2: level1 以上的 compaction操作可以多線程執行,前提是每個線程所操作的數據互不交叉。

那么這樣數據就由內存流入到高層的level。我們理想狀態下,所有的數據都存在非level0的一層level上。這樣可以保證最高效的查詢速度。所以對rocksdb來說, 每次寫都保證著原子性,所有數據都會落地到磁盤,保證著數據的可靠性和持久性。

讀邏輯

同樣,我們看一條數據是如何被讀取的。

首先RocksDB中的每一條記錄(KeyValue)都有一個LogSequenceNumber,從最初的0開始,每次寫入加1。lsn在memtable中單調遞增。之前提到的snapshot即就是基于lsn實現,每次以只讀模式打開時,記錄一個lsn, 大于該lsn的key不可見。

首先讀操作先訪問memtable。跳表的時間復雜度可達到logn, 如果不存在會訪問level0, 而level0整體不是有序的, 所以會按創建時間由新到老依次訪問每一個sst文件。所以時間復雜度為m*logn。如果仍不存在,則繼續訪問level1,由于level1及其以上的level都整體有序,所以只需要訪問一個sst文件即可。 直到查找到最高層或者找到這個key。所以讀操作可能會被放大好多倍。

rocksdb做了幾點優化,一點是為每個SST提供一個可配置的bloomfilter. 每個level的配置不一樣。這樣可以快速的確認一個key在不在某個SST中,這點以犧牲磁盤空間來換取時間。另一點是提供可配置的cache, 用于保存訪問過的key在內存中, 這里有一點就是它緩存的是某個key在SST文件中的整個block里的記錄。

存儲結構之memtable

rocksdb的memtable, 默認是skiplist跳表結構, 但它也同時支持hash-skiplist, hash-linklist結構。在創建數據庫時,可配置選擇存儲合適結構。什么時候選擇合適的結構類型,這塊還值得研究。如果將所有的數據都保存到內存中,這時候用hash-linklist是不是更合適呢。

skiplist 結構:


hash-skiplist結構:


hash-linklist結構:


存儲結構之SST

SST(Sorted Sequence Table)

SST作為rocksdb第二個存儲方式, 它的數據主要以block塊的方式存儲,默認是4k大小。ps之所以默認設定4k是因為我們一般的操作系統存儲的每個頁剛好是4k, 這樣每次加載一份數據一個block剛好就是一頁。這樣就不會造成,block過大需要分頁存儲或者block過小一頁存儲一個block造成內存浪費。


其中每個block以最后的crc碼來效驗數據的正確性。

根據block存儲的不同數據可以分為多種類型。其中Datablock里存儲著壓縮的key, value 記錄,是數據層。Metablock存儲著filter的數據,其中bloomfilter, prefix_bloomfilter, 還有用戶自定義的一些filter的數據存在這里。 Metablock Index記錄著filter的大小偏移量等信息。Indexblock記錄著每一個Datablock的最大key和最小key的偏移量等信息。 Footer記錄著Metablockindex塊的偏移量和大小以及Indexblock的偏移量和大小等信息。


Flush操作

* 首先當只讀memtable的數量大于閾值,如果沒有其它線程flush,則將該次操作加入隊列。

* 遍歷skiplist,通過迭代器逐一掃描key-value,將key-value寫入到data-block,

* 如果data block大小已經超過閾值,或者key-value對是最后的一對,則觸發一次block-flush, 同時根據壓縮算法對block進行壓縮,并生成對應的index block記錄;

* 所有數據更新完后, 寫入index block,meta block,metaindex block以及footer信息到文件尾;

* 并將變化sst文件的元信息寫入manifest文件。 同時清理wal日志文件。

Compaction操作

* rocksdb會定期的檢測每個level的狀態, 并為每個level計算score. score通過level實際的size/base_size來計算;level0的score是通過實際sst數/ level0 的sst文件數閾值計算。

* 如果score>1 則會觸發compaction, 找score最大的level,根據一定策略從中選擇一個sst文件進行compaction.?

* 根據這個sst文件的minkey, maxkey找到leveln+1層中有重疊的sst文件。 多個sst文件進行歸并排序,生成新的有序sst文件。

* 將變化的sst文件的信息寫入manifest文件。

數據庫文件結構


SST

rocksdb的數據文件, 嚴格說包含SST文件,還有內存中的memtable數據。

memtable寫入到一定閾值,可能是這個參數max_total_wal_size(待驗證),會flush memtable里的數據到SST level0文件。level0文件的key不一定是有序的,但leveln(n>0)的key必然是有序的.

可通過ldb工具來查看SST的內容

```

# ../ldb --db=./data/fansnum_tail scan

1162218773 : 34

1162222073 : 4433

1162231091 : 1025

1162231094 : 4

1162231403 : 72

1162233913 : 66

1162241104 : 35

./db_bench --db=/data1/xiaodong28/PacketServer/PacketComputation/offline_update/fansnum/ --benchmarks=fillrandom --num=10000 --compression_type=none

```

MANIFEST

*MANIFEST*: 記錄rocksdb最近的狀態變化日志。其中包含manifest日志 和最新的文件指針 *CURRENT*, 記錄最近的MANIFEST

manifest日志: *MANIFEST-(seq number)*, 記錄一系列版本更新記錄。

在RocksDB中任意時間存儲引擎的狀態都會保存為一個Version(也就是SST的集合),而每次對Version的修改都是一個VersionEdit,而最終這些VersionEdit就是 組成manifest-log文件的內容

```

/data1/rocksdb/rocksdb/bin/ldb --path=./fansnum/MANIFEST-000014? manifest_dump

```

log

WAL文件。 rocksdb在寫數據時, 先會寫WAL,再寫memtable。為了避免crash時, memtable的數據丟失。服務重啟時會從該文件恢復memtable。

OPTIONS

rocksdb的配置文件, 配置參數說明可參考下一小節。

IDENTITY

存放當前rocksdb的唯一標識

LOCK

LOCK 進程的全局鎖,DB一旦被open, 其他進程將無法修改,報類似以下錯誤。

```

Open rocksdb ../data/fansnum_rocksdb/ failed,? reason: IO error: while open a file for lock: ../data/fansnum_rocksdb//LOCK: Permission deniedCommand init,? costtime: 0.946000 ms

```

LOG

rocksdb的操作日志文件, 可配置定期的統計信息寫入LOG. 可通過info_log_level調整日志輸出級別; 通過keep_log_file_num限制文件數量 等等。

寫數據測試

對于rocksdb的寫操作做了個測試數據分析:

* 測試機器是20CPU, 32G的內存, STAF磁盤

* 測試數據庫的總key量為3.9億,大小為5.2G。

* 最終生成的數據文件大小為2.63G, 數據壓縮比為50.6%,

* 生成的level結構為Level0沒有數據,Level1 5個sst文件,總大小290.22M; Level2 43個文件,總大小2.35G. 我們從level文件結構可以看出結果還是比較好的,比如查一個key,只需讀level1和level2中各一個sst文件即可。

* 總compaction次數255次,寫磁盤總數據量為22.9G, 寫放大為8.7,這就相當于在這次測試中平均每條記錄寫了8.7次磁盤。而且這個值會隨著數據量的增大而增大。

* 總耗時1140s, 平均每秒寫34.2w條數據。

讀性能測試

針對LRUcache, 做了一個讀性能的測試的數據分析

測試機器是20CPU, 32G的內存, STAF磁盤, 測試數據庫的總key量為3.9億, 數據庫大小為2.63G,測試數據的key量為1kw。根據創建不同大小的LRUcache, 主要關注了兩個性能指標,平均耗時和cache命中率。其中db默認的cache是8M的LRUcache。上圖可以看出隨著cache大小的增加, 平均耗時再遞減,命中率在遞增。16GB的cache可以將所有數據全部存在cache中。那時查詢一個key的平均耗時需要3.3微妙。





從上圖可以看出不同比例的數據的最大耗時, 比如在配置cache的db中,95%數據的都可以在14微秒之內返回, 99%的數據都可以在25微秒之內返回。配置最大cache時單線程訪問可以達到30w的qps。

可優化的點

寫放大

由于存在數據的compaction操作,所以rocksdb實際總的寫磁盤數據量并不是等同于輸入的數據量, 我們看下我寫一次全量粉絲數的數據量5.2G, 經過壓縮后2.63G, 直到所有數據更新完成后寫磁盤的量達到22.9G, 近8.7倍的寫放大。

讀放大

同時rocksdb也存在讀放大,基于之前的讀操作內容,我們可以知道一次get操作可能包含多次讀磁盤操作。而且每次讀一個記錄會將該記錄所在的block都讀入內存。

空間放大

空間放大主要表現在數據的更新和刪除實際都在compaction操作中執行,這樣在compaction之前一個key在數據庫中會存好多份記錄。這樣造成的空間的浪費。

應用場景

通過以上對rocksdb的了解,rocksdb比較適合小規模數據存儲,因為數據量越大,其寫放大,讀放大,空間放大就會越嚴重, 但具體到什么規模這個還定義不了,當前我們本地存儲10G左右的數據,性能杠杠的。適用于對寫性能要求高,同時有大量內存來緩存SST數據以達到快速讀取的場景。適用于存儲變長key,value的場景。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容