簡介
PalDB 是 Linkedin 公司開源的一款只讀型的 KV 存儲數(shù)據(jù)庫轰坊,目的是在某些場景下替代 HashMap/HashSet 或 LevelDB火本,在性能和內(nèi)存之間做了一個良好的平衡灭贷。下面是官方給出的測試圖表:
使用方式
作為一個存儲工具包温学,其使用方式也很簡單,一看就會明白:
//寫數(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é)果
- 機器學習訓練出的模型
- 詞典
實現(xiàn)原理
PalDB 本質(zhì)上是一個哈希表览妖,用開放尋址法處理哈希沖突轧拄。下面從讀寫兩方面來分析其實現(xiàn)細節(jié)。
寫
寫數(shù)據(jù)的過程主要分為3塊:序列化讽膏,預寫入檩电,最終寫入。
序列化:序列化過程主要負責將準備寫入的 key-value 值進行序列化。PalDB 自己實現(xiàn)了對 java 基本對象的序列化俐末,對數(shù)據(jù)進行了一定的壓縮(如果覺得壓縮的仍不夠料按,PalDB 默認支持 Snappy 壓縮算法,可手動開啟)卓箫。
預寫入:程序每調(diào)用一次 writer.put(Object key, Object value) 载矿,PalDB 就進行一次預寫入。預寫入負責寫兩類文件:
- 索引文件:存儲 key 以及 value 在數(shù)據(jù)文件中的位置
- 數(shù)據(jù)文件:存儲 value 長度以及 value
這兩類文件都有一個或者多個烹卒,成對出現(xiàn)闷盔,文件數(shù)量決于 key(序列化后)的長度,一個 key 長度對應(yīng)一對<索引文件旅急,數(shù)據(jù)文件>逢勾。也就是說,key 長度是一個一級索引藐吮,這個在讀的時候會用到敏沉。下面用一張圖總結(jié)下預寫入的過程。
預寫入過程中還會記錄一些重要的值炎码,如:value 位置的最大長度盟迟,key 總數(shù)以及每個 key 長度下的 key 數(shù)量。
3.最終寫入
當寫完數(shù)據(jù)最終調(diào)用 writer.close() 時就進入最終寫入階段潦闲。預寫入生成的索引文件只是順序的存儲了 key 以及 value 在數(shù)據(jù)文件中的位置攒菠,最終寫入階段負責將索引文件轉(zhuǎn)化成哈希表,跟索引文件一樣歉闰,每一個 key 長度對應(yīng)一個哈希表辖众。對每一個哈希表:
- 哈希表 slot 數(shù)量 = 該 key 長度下的 key 數(shù)量 / loadFactor(默認0.75,可手動指定)
- 每個 slot 的大小是固定的和敬,等于 key 長度 + value 位置的最大長度(因此凹炸,slot 里的數(shù)據(jù)其實是有部分空閑的)。
寫這個哈希表的過程是順序讀預寫入階段生成的索引文件昼弟,按 key hash 到指定 slot(用開放尋址法處理哈希沖突)并寫入 key 以及 value 位置的過程啤它。
遍歷處理完所有 key 長度對應(yīng)的索引文件后,將所有哈希表舱痘、數(shù)據(jù)文件变骡、meta 信息拼接,形成最終的數(shù)據(jù)庫文件芭逝。文件結(jié)構(gòu)如下:
讀
首先 PalDB 會將數(shù)據(jù)庫文件初始化塌碌,初始化過程分為三步:
- 讀取 meta 信息,如 key 數(shù)據(jù)旬盯,key 長度數(shù)量台妆、每個 key 長度對應(yīng)的索引文件 slot 數(shù)等翎猛,并存儲在內(nèi)存中。
- 以一個只讀內(nèi)存映射文件方式(MappedByteBuffer)打開 key 索引集合接剩。由于 PalDB 將 key 索引集合當做一個文件打開办成,由于內(nèi)存映射文件的大小限制,key 索引集合的大小不能超過 2G搂漠。
- 以一個或多個只讀內(nèi)存映射文件方式打開數(shù)據(jù)文件集合迂卢。如果數(shù)據(jù)文件過大(大于2G),PalDB 會將其切分成多塊桐汤。
初始化完成后而克,就可以調(diào)用 reader.get(Object o) 方法進行數(shù)據(jù)讀取,數(shù)據(jù)讀取的流程如下:
總結(jié)
PalDB 的實現(xiàn)原理還是比較簡單的怔毛,但是在某些場景下效果會比常規(guī)方法更好员萍。就筆者的實踐來說,用 PalDB 存儲推薦模型來代替之前的文件 load 到內(nèi)存的方式拣度,在性能影響很小的情況下大大減少了內(nèi)存的使用碎绎,值得一試。