PalDB 詳解

簡介

PalDB 是 Linkedin 公司開源的一款只讀型的 KV 存儲數(shù)據(jù)庫,目的是在某些場景下替代 HashMap/HashSet 或 LevelDB,在性能和內(nèi)存之間做了一個良好的平衡。下面是官方給出的測試圖表:

讀的吞吐量是 leveldb 和 rocksdb 的5倍
內(nèi)存使用是 hashset 的1/6

使用方式

作為一個存儲工具包,其使用方式也很簡單,一看就會明白:

//寫數(shù)據(jù)
StoreWriter writer = PalDB.createWriter(new File("store.paldb"));
writer.put("foo", "bar");
writer.put(1213, new int[] {1, 2, 3});
writer.close();
//讀數(shù)據(jù)
StoreReader reader = PalDB.createReader(new File("store.paldb"));
String val1 = reader.get("foo");
int[] val2 = reader.get(1213);
reader.close();

應(yīng)用場景

PalDB 適合一次寫入,多次讀取,且數(shù)據(jù)量較大的場景,如:

  • Hadoop/Spark 計算時產(chǎn)生的一些中間結(jié)果
  • 機器學(xué)習(xí)訓(xùn)練出的模型
  • 詞典

實現(xiàn)原理

PalDB 本質(zhì)上是一個哈希表,用開放尋址法處理哈希沖突。下面從讀寫兩方面來分析其實現(xiàn)細節(jié)。

寫數(shù)據(jù)的過程主要分為3塊:序列化,預(yù)寫入,最終寫入。

  1. 序列化:序列化過程主要負責(zé)將準(zhǔn)備寫入的 key-value 值進行序列化。PalDB 自己實現(xiàn)了對 java 基本對象的序列化,對數(shù)據(jù)進行了一定的壓縮(如果覺得壓縮的仍不夠,PalDB 默認支持 Snappy 壓縮算法,可手動開啟)。

  2. 預(yù)寫入:程序每調(diào)用一次 writer.put(Object key, Object value) ,PalDB 就進行一次預(yù)寫入。預(yù)寫入負責(zé)寫兩類文件:

  • 索引文件:存儲 key 以及 value 在數(shù)據(jù)文件中的位置
  • 數(shù)據(jù)文件:存儲 value 長度以及 value

這兩類文件都有一個或者多個,成對出現(xiàn),文件數(shù)量決于 key(序列化后)的長度,一個 key 長度對應(yīng)一對<索引文件,數(shù)據(jù)文件>。也就是說,key 長度是一個一級索引,這個在讀的時候會用到。下面用一張圖總結(jié)下預(yù)寫入的過程。


預(yù)寫入工程

預(yù)寫入過程中還會記錄一些重要的值,如:value 位置的最大長度,key 總數(shù)以及每個 key 長度下的 key 數(shù)量。

3.最終寫入
當(dāng)寫完數(shù)據(jù)最終調(diào)用 writer.close() 時就進入最終寫入階段。預(yù)寫入生成的索引文件只是順序的存儲了 key 以及 value 在數(shù)據(jù)文件中的位置,最終寫入階段負責(zé)將索引文件轉(zhuǎn)化成哈希表,跟索引文件一樣,每一個 key 長度對應(yīng)一個哈希表。對每一個哈希表:

  • 哈希表 slot 數(shù)量 = 該 key 長度下的 key 數(shù)量 / loadFactor(默認0.75,可手動指定)
  • 每個 slot 的大小是固定的,等于 key 長度 + value 位置的最大長度(因此,slot 里的數(shù)據(jù)其實是有部分空閑的)。

寫這個哈希表的過程是順序讀預(yù)寫入階段生成的索引文件,按 key hash 到指定 slot(用開放尋址法處理哈希沖突)并寫入 key 以及 value 位置的過程。
遍歷處理完所有 key 長度對應(yīng)的索引文件后,將所有哈希表、數(shù)據(jù)文件、meta 信息拼接,形成最終的數(shù)據(jù)庫文件。文件結(jié)構(gòu)如下:


最終數(shù)據(jù)庫文件結(jié)構(gòu)

首先 PalDB 會將數(shù)據(jù)庫文件初始化,初始化過程分為三步:

  1. 讀取 meta 信息,如 key 數(shù)據(jù),key 長度數(shù)量、每個 key 長度對應(yīng)的索引文件 slot 數(shù)等,并存儲在內(nèi)存中。
  2. 以一個只讀內(nèi)存映射文件方式(MappedByteBuffer)打開 key 索引集合。由于 PalDB 將 key 索引集合當(dāng)做一個文件打開,由于內(nèi)存映射文件的大小限制,key 索引集合的大小不能超過 2G
  3. 以一個或多個只讀內(nèi)存映射文件方式打開數(shù)據(jù)文件集合。如果數(shù)據(jù)文件過大(大于2G),PalDB 會將其切分成多塊。

初始化完成后,就可以調(diào)用 reader.get(Object o) 方法進行數(shù)據(jù)讀取,數(shù)據(jù)讀取的流程如下:


讀取數(shù)據(jù)流程

總結(jié)

PalDB 的實現(xiàn)原理還是比較簡單的,但是在某些場景下效果會比常規(guī)方法更好。就筆者的實踐來說,用 PalDB 存儲推薦模型來代替之前的文件 load 到內(nèi)存的方式,在性能影響很小的情況下大大減少了內(nèi)存的使用,值得一試。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容