1.基本介绍
tokyo cabinet是key value数据库(官方主页为:http://fallabs.com/tokyocabinet/),由日本人开发应用比较广泛,作者本人是在日本的一个社交网站工作(貌似是日本国内最大的)。张宴的blog对其在金山公司的应用有相应的阐述。
toky cabinet数据库主要的实现有:(1)HASH 结构;(2)B+树结构;(3)Fixed-Length结构;(4)Abstract 结构。
由于key value数据库本身的实现涉及到数据结构、内存组织、文件读写等多个方面,对于其中的实现细节能够有了解,对于数据结构、程序设计功底有较高的提升。所以打算对于tokyo cabinet的源代码进行一个分析。先主要对于abstract类型数据库进行一个分析。
2. Abstract database
Abstract数据库主要是通过内存实现Hash 数据库,B+树数据库等。主要对于Hash结构进行分析。
2.1 Abastrat数据库主要数据结构
先对于TCADB的初始化进行分析。
- /* Create an abstract database object. */
- TCADB *tcadbnew(void){
- TCADB *adb;
- TCMALLOC(adb, sizeof(*adb));
- adb->omode = ADBOVOID;
- adb->mdb = NULL;
- adb->ndb = NULL;
- adb->hdb = NULL;
- adb->bdb = NULL;
- adb->fdb = NULL;
- adb->tdb = NULL;
- adb->capnum = -1;
- adb->capsiz = -1;
- adb->capcnt = 0;
- adb->cur = NULL;
- adb->skel = NULL;
- return adb;
- }
其中,根据初始化传入参数不同,建立不同类型数据库。
2.2TCADB数据库打开操作
通过tcadbopen打开数据库,并对于TCADB结构体指针进行初始化。
由于是实现hash database,对于TCMDB结构体进行初始化。
- /* Open an abstract database. */
- bool tcadbopen(TCADB *adb, const char *name){
- assert(adb && name);
- /*根据配置进行相应的打开操作*/
- if(adb->omode != ADBOVOID) return false;
- /*应该是可以配置数据库相关参数,然后通过#进行分割*/
- TCLIST *elems = tcstrsplit(name, "#");
- /*获得实际的路径,如果是*,则为内存数据库*/
- char *path = tclistshift2(elems);
- if(!path){
- tclistdel(elems);
- return false;
- }
- /* 此时设置为on-memory hash database,
- * 对于mdb进行内存分配*/
- else if(!tcstricmp(path, "*")){
- adb->mdb = bnum > 0 ? tcmdbnew2(bnum) : tcmdbnew();
- adb->capnum = capnum;
- adb->capsiz = capsiz;
- adb->capcnt = 0;
- adb->omode = ADBOMDB;
2.2.1 TCMDB结构
TCMDB结构是on-memory hash database。
- /*HASH数据库结构*/
- typedef struct { /* type of structure for a on-memory hash database */
- void **mmtxs; /* mutexes for method */
- void *imtx; /* mutex for iterator */
- TCMAP **maps; /* internal map objects */
- int iter; /* index of maps for the iterator */
- } TCMDB;
其中TCMAP结构是MAP数组。
- /*TCPMAP数据结构*/
- typedef struct { /* type of structure for a map */
- TCMAPREC **buckets; /* bucket array */
- TCMAPREC *first; /* pointer to the first element */
- TCMAPREC *last; /* pointer to the last element */
- TCMAPREC *cur; /* pointer to the current element */
- uint32_t bnum; /* number of buckets */
- uint64_t rnum; /* number of records */
- uint64_t msiz; /* total size of records */
- } TCMAP;
MAP结构中含有Hash buckets数组。
2.2.2 TCMDB结构初始化
在TCMDB结构中,含有TCMAP结构数组,然后各TCMAP结构在含有hash buckets数组。即:在操作过程中先在hash到TCMAP数组中的某个元素上。然后再次hash,定位到TCMAP元素的hash buckets数组元素中。不是单一的采用hash buckets数组直接进行hash映射。
通过tcmdbnew2对于TCMDB结构进行了初始化,先分配TCMAP数组,然后对于TCMAP结构再分配hash buckets.
- /*分配方法为tcmdbnew*/
- /* Create an on-memory hash database with specifying the number of the buckets. */
- TCMDB *tcmdbnew2(uint32_t bnum){
- TCMDB *mdb;
- /* 默认hash桶的个数为65536*/
- if(bnum < 1) bnum = TCMDBDEFBNUM;
- /* 对于bucket number又进行了处理,应该使得hash更为均匀*/
- bnum = bnum / TCMDBMNUM + 17;
- TCMALLOC(mdb, sizeof(*mdb));
- /*对于每个MAP分配读写锁*/
- TCMALLOC(mdb->mmtxs, sizeof(pthread_rwlock_t) * TCMDBMNUM);
- TCMALLOC(mdb->imtx, sizeof(pthread_mutex_t));
- TCMALLOC(mdb->maps, sizeof(TCMAP *) * TCMDBMNUM);
- if(pthread_mutex_init(mdb->imtx, NULL) != 0) tcmyfatal("mutex error");
- for(int i = 0; i < TCMDBMNUM; i++){
- if(pthread_rwlock_init((pthread_rwlock_t *)mdb->mmtxs + i, NULL) != 0)
- tcmyfatal("rwlock error");
- /*对于MAP进行分配,先有MAP,然后基于MAP,分配多个bucket */
- mdb->maps[i] = tcmapnew2(bnum);
- }
- mdb->iter = -1;
- return mdb;
- }
然后通过tcmapnew2对于TCMAP进行初始化。
- /*分配MAP的方法*/
- /* Create a map object with specifying the number of the buckets. */
- TCMAP *tcmapnew2(uint32_t bnum){
- if(bnum < 1) bnum = 1;
- TCMAP *map;
- TCMALLOC(map, sizeof(*map));
- TCMAPREC **buckets;
- /*如果bnum过大,才用了mmap方式进行了映射。
- *否则直接在内存中分配*/
- if(bnum >= TCMAPZMMINSIZ / sizeof(*buckets)){
- buckets = tczeromap(bnum * sizeof(*buckets));
- } else {
- TCCALLOC(buckets, bnum, sizeof(*buckets));
- }
- map->buckets = buckets; //真正使用的hash buckets桶数组
- map->first = NULL; //指向头一个record记录
- map->last = NULL; //指向最后一个record记录
- map->cur = NULL; //通过first和last将所有的节点串联起来
- map->bnum = bnum;
- map->rnum = 0;
- map->msiz = 0;
- return map;
- }
TCMAPREC结构为:
TCMAPREC存储了key和value内容。
----------------------------------------
|TCMAPREC结构|keybuf'/0'|valuebuf'/0'|
-------------------------------------
其中的指针left,right为二叉树结构,prev和next将该bucket桶上的record全部
连起来,方面进行遍历。
- /*数据记录结构*/
- typedef struct _TCMAPREC { /* type of structure for an element of a map */
- int32_t ksiz; /* size of the region of the key */
- int32_t vsiz; /* size of the region of the value */
- struct _TCMAPREC *left; /* pointer to the left child */
- struct _TCMAPREC *right; /* pointer to the right child */
- struct _TCMAPREC *prev; /* pointer to the previous element */
- struct _TCMAPREC *next; /* pointer to the next element */
- } TC
2.3数据记录的写入
在前面的2节对于内存hash memory的基本数据结构进行了分析。通过tcmdbput将key,value结构写入到内存中。
- /*通过tcmdbput进行存储
- *先通过key映射到某个map上面
- *然后在map上面进行bucket的操作*/
- /* Store a record into an on-memory hash database. */
- void tcmdbput(TCMDB *mdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
- assert(mdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
- unsigned int mi;
- TCMDBHASH(mi, kbuf, ksiz);
- if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0) return;
- tcmapput(mdb->maps[mi], kbuf, ksiz, vbuf, vsiz);
- pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
- }
2.3.1 Hash算法映射
先通过hash算法,映射到maps数据的元素上。
- /*将一个对象存储在map object中*/
- /* Store a record into a map object. */
- void tcmapput(TCMAP *map, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
- assert(map && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
- if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
- uint32_t hash;
- /*第一次HASH映射到map数组上*/
- TCMAPHASH1(hash, kbuf, ksiz);
- int bidx = hash % map->bnum;
- TCMAPREC *rec = map->buckets[bidx];
- /*entp保存父节点地址*/
- TCMAPREC **entp = map->buckets + bidx;
- /*第二次HASH映射到特定map的bucket上*/
- TCMAPHASH2(hash, kbuf, ksiz);
- hash &= ~TCMAPKMAXSIZ;
再通过hash算法,映射到特定maps元素的buckets数组中。即:先找个MAP,再找buckets。
2.3.2记录初次插入二叉树
我们先讨论初次加入二叉树结构的方法。
- /*如果为首次加入,则直接放入到buckets中*/
- /*计算需要pad的字节大小*?
- int psiz = TCALIGNPAD(ksiz);
- map->msiz += ksiz + vsiz;
- /*分配key,value,pad大小,末尾'/0'
- *rec表示了左右子树,父节点*/
- TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
- char *dbuf = (char *)rec + sizeof(*rec);
- /*在TCMAPREC结构体之后,存放key的内容*/
- memcpy(dbuf, kbuf, ksiz);
- /*key值通过在末尾增加'/0'分割*/
- dbuf[ksiz] = '/0';
- rec->ksiz = ksiz | hash;
- /*value值的拷贝*/
- memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
- dbuf[ksiz+psiz+vsiz] = '/0';
- /*修改rec记录的大小*/
- rec->vsiz = vsiz;
- rec->left = NULL;
- rec->right = NULL;
- /*record的prev节点指向原先的最后一个节点
- *构成双向链表结构,并通过Map的first和last
- *节点进行连接*/
- rec->prev = map->last;
- rec->next = NULL;
- /*加入到树中*/
- *entp = rec;
- /*如果该buckets数组为初次插入节点,则first头节点指向该记录*/
- if(!map->first) map->first = rec;
- /*如果last节点不为空,(已经有插入节点),则将原先节点的next节点
- *指向该记录,然后last节点指向新的记录,即:将原先的节点都连接起来*/
- if(map->last) map->last->next = rec;
- map->last = rec;
- /*record记录数增加*/
- map->rnum++;
2.3.3记录插入二叉树
在已有二叉树结构中增加记录。循环遍历二叉树结构,插入节点。
- /*二叉树遍历过程,先比较hash,找到相应的节点,
- *再比较相应的key值,如果key值不相同,同样作为
- *二叉树进行比较插入*/
- while(rec){
- uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
- uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
- /*如果hash值大,则选择左子树,entp存放父节点*/
- if(hash > rhash){
- entp = &(rec->left);
- rec = rec->left;
- } else if(hash < rhash){
- entp = &(rec->right);
- rec = rec->right;
- } else {
- /*如果相同,则比较key是否相同,如果不相同继续遍历
- *二叉树*/
- char *dbuf = (char *)rec + sizeof(*rec);
- int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
- if(kcmp < 0){
- entp = &(rec->left);
- rec = rec->left;
- } else if(kcmp > 0){
- entp = &(rec->right);
- rec = rec->right;
- } else {
- /*如果key值相同,则对于原先的rec进行更新*/
- map->msiz += vsiz - rec->vsiz;
- int psiz = TCALIGNPAD(ksiz);
- if(vsiz > rec->vsiz){
- TCMAPREC *old = rec;
- TCREALLOC(rec, rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
- if(rec != old){
- if(map->first == old) map->first = rec;
- if(map->last == old) map->last = rec;
- if(map->cur == old) map->cur = rec;
- *entp = rec;
- if(rec->prev) rec->prev->next = rec;
- if(rec->next) rec->next->prev = rec;
- dbuf = (char *)rec + sizeof(*rec);
- }
- }
- memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
- dbuf[ksiz+psiz+vsiz] = '/0';
- rec->vsiz = vsiz;
- return;
- }
- }
2.3.4 memory hash database 结构层次图
memory Hash MAP 结构图:
默认TCMAP数组8个元素,先映射到不同TCMAP元素之上。
--------------------------------
|TCMAP1 | TCMAP2 | .....| TCMAP8 |
--------------------------------
|
V再次映射到buckets数组中的元素
--------------------------------------------------------------
|rec_buckets数组|rec_first|rec_last|rec_cur|bucket数目|rec数目|
-------------------------------------------------------------
|
VREC被映射到具体的bucket元素,然后按照二叉树的结构进行存储
-------------------------------------------------
| ksiz|vsiz|left_rec|right_rec|prev_rec|next_rec|
-------------------------------------------------
map中的last和first指针,将所有的rec结构进行串联,从而方便遍历。
2.4数据记录的查找
在前面的部分对于记录的插入进行了阐述。本节对通过key查找value方法进行了分析。
2.4.1TCMAP数组查找
先映射到MAP数组的一个元素,然后基于该元素对于hash buckets数组进行访问。
- 通过tcmdbget进行查找
- /* Retrieve a record in an on-memory hash database. */
- void *tcmdbget(TCMDB *mdb, const void *kbuf, int ksiz, int *sp){
- assert(mdb && kbuf && ksiz >= 0 && sp);
- unsigned int mi;
- /*将key映射到map数组的一个元素上*/
- TCMDBHASH(mi, kbuf, ksiz);
- /*通过mutex锁唯一访问*/
- if(pthread_rwlock_rdlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0) return NULL;
- int vsiz;
- const char *vbuf = tcmapget(mdb->maps[mi], kbuf, ksiz, &vsiz);
- char *rv;
- if(vbuf){
- TCMEMDUP(rv, vbuf, vsiz);
- *sp = vsiz;
- } else {
- rv = NULL;
- }
- pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
- return rv;
- }
2.4.2 hash buckets二叉树查找
查找二叉树结构,获得value对象,返回的是分配好的内存地址,所以查找使用以后,
需要进行释放。
- /*通过tcmapget遍历二叉搜索树,查找value*/
- /* Retrieve a record in a map object. */
- const void *tcmapget(const TCMAP *map, const void *kbuf, int ksiz, int *sp){
- assert(map && kbuf && ksiz >= 0 && sp);
- if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
- uint32_t hash;
- TCMAPHASH1(hash, kbuf, ksiz);
- /*先定位到bucket一个元素上*/
- TCMAPREC *rec = map->buckets[hash%map->bnum];
- TCMAPHASH2(hash, kbuf, ksiz);
- hash &= ~TCMAPKMAXSIZ;
- /*遍历二叉树
- *hash值相同,然后比较key值
- */
- while(rec){
- uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
- uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
- if(hash > rhash){
- rec = rec->left;
- } else if(hash < rhash){
- rec = rec->right;
- } else {
- /*dbuf的结构是rec结构体之后存放了key
- *TCKEYCMP先比较了两个的keybuffer的大小,
- *然后再比较值*/
- char *dbuf = (char *)rec + sizeof(*rec);
- int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
- if(kcmp < 0){
- rec = rec->left;
- } else if(kcmp > 0){
- rec = rec->right;
- } else {
- *sp = rec->vsiz;
- /*找到之后,翻译value所在buffer的地址
- *通过TCMEMDUP复制内存,
- *所在在使用完了Value以后,应该释放*/
- return dbuf + rksiz + TCALIGNPAD(rksiz);
- }
- }
- }
注: hashmap中使用了2插树的方式来加速查询时间. 每个桶和桶中元素的连接是通过2插数的形式进行的