由于最近在做分布式存储系统,需要一个小、性能好、靠谱的存储引擎,我们瞄准了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的哈希表是文件系统里一个文件,它的结构由下面图所示:
(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是采用一般的实现方式:哈希表+双向链表。