@HUST张友东 work@taobao zyd_com@126.com
分类: C/C++
2011-05-27 17:56:39
TCFDB是Tokyo Cabinet中的fix length定长数据库的实现,key由整数id标示,所有value的长度都在某一个长度范围内,TCFDB为某个记录分配固定宽度的区域。TCFDB的设计与实现是KISS(Keep it simple & stupid)原则很好的一个体现。
TCFDB将存储区域分为两部分,头部和记录区,头部包含TCFDB的一些全局信息(长度固定),记录区依次存储多个记录,每个记录开始处存储记录的长度(可能是1,2,4个字节,具体由记录宽度决定),接下来是固定宽度的区域存储value。定长记录默认宽度为255,可以在open前通过tcfdbtune来设置。TCFDB的记录区如下图所示:
TCFDB将整个数据库映射到内存中,所以数据库的长度是受内存大小的限制的。TCFDB按ID值顺序存放记录,所以给定ID即可快速定位到记录存储的位置。
put接口的实现
根据id定位到记录应该存储的位置,根据put类型(覆盖、保持、追加等)执行存储操作,如果当前操作在transaction中,则将原来的纪录先写到wal log中(用于事务abort的restore)。
get接口的实现
根据id定位到记录应该存储的位置,读取记录。
如果当前操作在transaction中,则将操作记录写到wal log中。
out接口的实现
根据id定位到记录应该存储的位置,删除记录。TCFDB根据记录的大小判断对应的区域是否存在有记录,0表示没有记录。如果当前操作在transaction中,则将操作记录写到wal log中(用于事务abort的restore)。
数据库的遍历
TCFDB提供按照id顺序遍历数据库的接口tcfdbnextid获取下一个记录的id,对于每一个id,TCFDB检查对应记录位置的大小值是否为0,如果为0说明没有记录,否则返回记录id。当某个记录加入后,如果记录的位置大于当前文件大小,就会出现文件洞,洞中的数据都为0,所以才会有上面的判断逻辑。
事务的实现
TCFDB通过三个方法tcfdbtranbegin、tcfdbtrancommit、tcfdbtranabort来实现事务。首先在开始事务之前调用tcfdbtranbegin将数据库当前的内容同步到磁盘(msync),并设置数据库的tran字段为true;当事务完成后,通过tcfdbtrancommit提交事务,其将数据库的更新同步到磁盘,并将wal日志文件截短至0;如果需要中止事务,则调用tcfdbtranabort,其将wal日志文件中的记录restore到文件中,恢复数据库的内容到事务开始前的状态。
锁机制
typedef struct { /* type of structure for a fixed-length database */
void *mmtx; /* mutex for method */
void *amtx; /* mutex for attribute */
void *rmtxs; /* mutexes for records */
void *tmtx; /* mutex for transaction */
void *wmtx; /* mutex for write ahead logging */
…..
} TCFDB;
TCFDB主要提供了对方法锁、属性锁、记录锁、事务锁、wal日志锁。
l 方法锁是读写锁,对于可能改变数据库内容的操作如out以及某些put操作,会在操作开始前加写锁,否则加读锁,如get。
l 属性锁是互斥锁,在更新数据库的头部前需加锁,如在put、out时需要更新头部信息。
l 记录锁是读写锁,默认127个,每次对记录的操作需要对某个锁(记录id对127取余)加锁,设置127实际上是时间和空间上的折中,如果要达到性能最优化,需对每个记录设置一个锁。另外,TCFDB还提供了对所有记录锁加解锁的接口,tcfdbcopy(拷贝数据库)、tcfdbforeach(对数据库的每一项执行某种操作)两个接口需要对所有记录加锁。
l 事务锁在TCFDB的实现中没有看到过使用,只有初始化和销毁的操作。其实数据库的tran字段已经起到了锁的作用。
l wal日志锁是互斥锁,tcfdbwalwrite接口在望wal日志写数据前会加锁。