由於最近在做分佈式存儲系統,需要一個小、性能好、靠譜的存儲引擎,我們瞄準了Kyoto Cabinet,希望通過分析源碼能對它進行改造成我們需要的引擎,這一工作落在我身上,於是我花了一星期時間徹底地分析了Kyoto Cabinet源碼,下面將分享下由Kyoto Cabinet源碼分析出的實現原理。
下面主要分析hashdb和treedb這兩種存儲結構,其他的要麼簡單,要麼類似,就不分析了。
一、hashdb
hashdb,顧名思義就是哈希表,只是哈希表存放在文件系統而不是內存,複雜度比存在內存大多了,衝突採用鏈表或二叉樹解決。
大家對哈希表應該都比較熟悉,它由三要素組成:哈希函數,哈希表和衝突解決方法。在無衝突情況下,查找、插入和更新複雜度爲O(1);有衝突情況下的複雜度就視衝突情況來定複雜度了。增加哈希表的桶數,可以減少衝突,但會犧牲空間,所以具體應用時,需要權衡衝突和空間。
下面分別對上面介紹的三要素是如何hashdb實現的。
(1)哈希函數
hashdb的哈希函數是採用MurMur算法。具體可以看維基百科的介紹:http://zh.wikipedia.org/wiki/Murmur%E5%93%88%E5%B8%8C,實現函數是hashmurmur,這裏就不貼代碼了。
(2)哈希表
hashdb的哈希表是文件系統裏一個文件,它的結構由下面圖所示:
b. 然後將後面的記錄的padding縮小,然後將記錄緊湊地前移,在有限步下完成後,會空出一大塊空間塊
(2)文件恢復
觸發條件:沒有調用close而非正常關閉db。
恢復思想:遍歷舊hashdb文件裏的record,然後將合法的record插入新hashdb,最後替換舊文件。
(3)非法close數據庫,可能產生數據丟失或數據被認爲合法但不正確
a. 寫padding size的時,成功寫入第一個字節,寫入第二字節失敗,可能引起不可回收的文件碎片,或認爲該記錄不合法;
b. 寫key size或value size寫一半,可能引起該記錄的邊界到了第二個記錄上,這就爲什麼恢復DB時,需要順利正確能讀出兩個記錄的第一個記錄才認爲是合法的;
c. 寫data寫一半,只會引起數據不正確;
d. 寫Address寫一半,可通過自動恢復dB恢復,而且數據沒有丟失或不正確。
(4)Free block pool大小超過最大值時,會有free block無法放入Free block pool,導致該free block無法利用,直到碎片整理到它。
(5)事務
hashdb是採用預寫日誌方式來實現事務的,它的事務隔離級別是可序列化的,事務的最高隔離級別,粒度最大。
日誌文件格式如下圖所示:
(6)hashdb的鎖粒度是record級別,但又不完全是,它是分配一定數量的鎖放到內存級哈希表,通過哈希的方式去獲取鎖。
二、treedb
treedb的數據結構是採用最小B+樹,B+持久化到磁盤方式是採用hashdb,我感覺該持久化方式是比較偷懶的,性能沒有直接將B+樹保持到磁盤好,因爲通過hashdb會產生較多的磁盤IO,但作者爲彌補這一點,treedb內部實現了一級lru的index page node cache和二級lru的leaf page node cache來減少讀寫磁盤。
treedb主要內部數據結構由下圖所示:
圖8 Leaf node結構
上面說了,treedb持久化方式是採用hashdb,上圖的index node和leaf node對應hashdb的value,key是node id。
另外,如果不設autsyn,treedb不會該更新立即寫入磁盤,而是寫在cache裏,後面某個時間再刷到磁盤上。
treedb的鎖粒度是node,即page。
treedb裏的lru cache是採用一般的實現方式:哈希表+雙向鏈表。