Tokyo Cabinet(下文简称TC)是一个DBM的实现。这里的数据库由一系列key-value对的记录构成。key和value都可以是任意长度的字节序列,既可以是二进制也可以是字符串。这里没有数据类型和数据表的概念。经过测试我们发现,当数据文件的尺寸小于mmap size(xmsiz)的时候,TC的读写速度是非常快的。
TC有6种存储方式:
1)Hash Database 数据文件后缀为.tch
2)B+ tree database 数据文件后缀为.tcb
3)fixed-length database 数据文件后缀为.tcf
4)table database 数据文件后缀为.tct
5)内存Hash Database
6)内存B+ tree Database
前4种存储形态,TC会以文件形式将所有数据记录在一个数据文件中,下面我们将会简单介绍Hash Database存储方式下的空闲块管理。
我们先看看整个数据文件的内部结构图:
由上面的结构图我们可以看到,TC在数据文件中维护着一个空闲块的管理池,该管理池默认有1024个节点,每个节点的结构如下:
ps:管理池在任意一个时刻都保证这1024个节点会按空闲块在数据文件的偏移来排序或者按空闲块的大小来排序,保证有序是为了能快速定位找到适合和空闲块。
管理池主要有以下几个功能
1)空闲块管理池按rsiz排序,在申请空间存放数据的时候,可以快速查找合适大小的空闲块,复用已有的空闲块,减缓数据文件尺寸增长速度,减少碎片率。
2)空闲块管理池按off排序,在删除数据的时候,可以通过空闲块地址偏移的关系,将2块或者更多的空闲块合并成一大块,减少碎片率。
ps:空闲块除了可以合并还是按需(对齐)切分的,也就是将一块大的切成2块小的
TC的空闲块管理池,是TC的核心组成部分,他存在以下几点问题:
1)空闲块管理池的节点数不能太大,默认1024,默认上限是100万,否则会出现很严重的性能问题,这是因为空闲池需要经常转换他的排序形态,一时按off来排序,一时按rsiz来排序。
2)空闲块管理池的节点数,在数据库文件初始化的时候就要决定,后面是不能更改的。
3)默认节点数是1024,也就是说同一时刻TC最多只能管理1024个空闲块,当我们批量删除数据的时候,部分的空闲块将无法管理,为此TC提供了
我们下面要说的动态整理碎片的机制
TC的空闲块管理可以用一个词来形象,就是动静结合,上面提到的空闲块池操作算是”静”,而我们下面来说的动态整理碎片机制就是”动”
在说动态整理碎片之前,我们先说说几个关键的变量:
1.启动参数dfunit,当我在打开TC数据文件的时候带上dfunit=<int>,我们将激活TC的动态整理碎片机制。
2.hdb->dfunit = 启动参数dfunit,TC会在每hdb->dfunit次操作后,进行一次碎片整理。
3.hdb->dfcnt TC当前进行了多少次写操作,用于判断当前是否应该进行一次碎片整理,在没有碎片操作后,将重置为0。
4.hdb->fsiz 当前数据文件的大小
5.hdb->dfcur 最后一次碎片整理的位置,下一次碎片整理从该位置开始。
6.hdb->frec 数据文件第一条记录的位置,当hdb->dfcur > hdb->fsiz的时候,hdb->dfcur = hdb->frec,也就是将整理游标复位,从头开始,继续碎片整理。
7.step,step = hdb->dfunit * 2 + 1,假如dfunit = 16,那hdb->dfunit = 16,step = 33,step是用于控制每次碎片整理的跨度(不严谨地说,可以认为是本次碎片整理的最大条数)
下面我们再来看几个关键的数据结构
一条记录的存储格式
一块空闲块的存储格式
ps:数据记录变成空闲块,只需要改写数据记录的前5个字节,并在管理池插入一条记录就ok,而空闲块变成数据记录直接改写整个空闲块,并且将对应的空闲块记录删除就ok
动态整理过程如下:
当我们启动打开TC的时候带上dfunit=16,我们将激活动态碎片整理功能,在get add delete update的时候(默认是只有在delete的时候),我们都会不断累积hdb->dfcnt,当累积的操作数等于我们设置定的阀值(hdb->dfcnt == hdb->dfunit)的时候,我们将会进行一次碎片整理(hdb->dfcnt重置为0),整理的记录数的上限为step(16 * 2 + 1 = 33),本次动态碎片整理的开始地址为hdb->dfcur(第一次初始化的时候已经指向hdb->frec,也就是tc的第一条记录),然后循环向后寻找最近的一块空闲块,在循环过程中会不断修改hdb->dfcur,假如连续的33块中都没有一块空闲块,则退出循环并返回,表示本次碎片整理结束,假如在循环过程中找到了一块空闲块,则把该空闲块记录下来,并且继续向后遍历,在继续找的过程中,会不断将空闲块往后挪动,假如后面的记录是一块空闲块,则将这2块空闲块合并成一块(经过不断挪动,这2块空闲块是必定相邻的),当挪动的数据的数量遍历的记录数达到33条的时候,就会退出循环,并返回,表示本次动态碎片结束。
下面是模拟动画:
TC在这2种管理方式下,可以有效地管理空闲块,控制数据文件的增长速度,减少碎片率。
我们现在正着手解决以下已知的问题:
1)空闲块管理池size不能太的问题。
2)空闲块管理池,按大小排序,按偏移排序而导致的性能问题。
3)开启动态碎片整理,存在破坏数据,丢失数据的风险。