Tokyo Cabinet TCHDB源码阅读——delayed record pool和tchdbputasyncimpl相关函数代码注释

     在TCHDB结构中跟异步写操作相关的成员有以下几个:

 

bool async; /* whether asynchronous storing is called */ TCXSTR *drpool; /* delayed record pool */ TCXSTR *drpdef; /* deferred records of the delayed record pool */ uint64_t drpoff; /* offset of the delayed record pool */

 

    从上面的注释可知,DRP2个缓冲池,其中drpool是我们传统意义上的缓冲池,而drpdef又是针对drpool做了一次缓冲,仅仅看这里的英文注释我感觉有点难理解,后面看了源码我再尝试说说自己对这两个缓冲区的理解。  

    在tchdbputasyncimpl函数中下面这个语句片段初始化DRPdelayed record pool)区:

if(!hdb->drpool){ hdb->drpool = tcxstrnew3(HDBDRPUNIT + HDBDRPLAT); hdb->drpdef = tcxstrnew3(HDBDRPUNIT); hdb->drpoff = hdb->fsiz; }

      hdb->drpoff = hdb->fsiz这个语句可知,每次DRP区(由drpool指针指向)中的记录都是从文件末尾开始添加的,再由同步写操作tchdbput函数源代码可知,该函数会在写入记录前检查前面的写入操作是否为异步操作,如果是,则它会刷新异步写缓冲区并删除该异步写缓冲区,从而保证同步写操作执行时,所有的异步写操作都完成,因此,TC中的异步写操作和同步写操作不会交错混杂在一起,这也在一定程度上简化了异步写操作的实现。

下面是tchdbputasyncimpl源码及注释:

/* Store a record in asynchronus fashion. `hdb' specifies the hash database object. `kbuf' specifies the pointer to the region of the key. `ksiz' specifies the size of the region of the key. `bidx' specifies the index of the bucket array. `hash' specifies the hash value for the collision tree. `vbuf' specifies the pointer to the region of the value. `vsiz' specifies the size of the region of the value. If successful, the return value is true, else, it is false. */ static bool tchdbputasyncimpl(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx, uint8_t hash, const char *vbuf, int vsiz){ assert(hdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0); // 从缓存中删除指定记录,因为我们在更改该记录,保证数据一致性 if(hdb->recc) tcmdbout(hdb->recc, kbuf, ksiz); if(!hdb->drpool){ // drp区还没分配?分配好该区域 hdb->drpool = tcxstrnew3(HDBDRPUNIT + HDBDRPLAT); hdb->drpdef = tcxstrnew3(HDBDRPUNIT); hdb->drpoff = hdb->fsiz; } off_t off = tchdbgetbucket(hdb, bidx); // 取得hash桶数组中bidx索引位置记录的文件偏移量 off_t entoff = 0; TCHREC rec; char rbuf[HDBIOBUFSIZ]; while(off > 0){ // 当读到的冲突树中记录偏移量大于hdb->drpoff - hdb->runit时,则把传入的record相关 // 信息放到drpdef缓冲区,这里是一个优化。原因我还没想通。 if(off >= hdb->drpoff - hdb->runit){ TCDODEBUG(hdb->cnt_deferdrp++); TCXSTR *drpdef = hdb->drpdef; TCXSTRCAT(drpdef, &ksiz, sizeof(ksiz)); TCXSTRCAT(drpdef, &vsiz, sizeof(vsiz)); TCXSTRCAT(drpdef, kbuf, ksiz); TCXSTRCAT(drpdef, vbuf, vsiz); if(TCXSTRSIZE(hdb->drpdef) > HDBDRPUNIT && !tchdbflushdrp(hdb)) return false; return true; } rec.off = off; if(!tchdbreadrec(hdb, &rec, rbuf)) return false; // 读取记录到rec,可能过不会读到记录的key和value值 if(hash > rec.hash){ // 用传入的二级hash和读到的记录二级hash比较,寻找传入记录的正确位置 off = rec.left; entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)); // 这2句调整off和entoff,在冲突树中继续前进 } else if(hash < rec.hash){ // 同上 off = rec.right; entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)) + (hdb->ba64 ? sizeof(uint64_t) : sizeof(uint32_t)); } else { // 二级hash相等,将传入的记录缓存到drpdef缓冲区中,以后刷新缓冲区时再处理 TCDODEBUG(hdb->cnt_deferdrp++); TCXSTR *drpdef = hdb->drpdef; TCXSTRCAT(drpdef, &ksiz, sizeof(ksiz)); TCXSTRCAT(drpdef, &vsiz, sizeof(vsiz)); TCXSTRCAT(drpdef, kbuf, ksiz); TCXSTRCAT(drpdef, vbuf, vsiz); if(TCXSTRSIZE(hdb->drpdef) > HDBDRPUNIT && !tchdbflushdrp(hdb)) return false; return true; } } // 走到这里,说明没有在冲突树中找到二级hash相等的记录,插入的record为二叉树新节点,应放到drpool缓冲区中 // 检查entoff,entoff为0表示传入的record为冲突树的根节点 if(entoff > 0){ // entoff大于0,说明传入的record不为根,应该成为叶子几点,更新其父节点孩子指针 if(hdb->ba64){ uint64_t llnum = hdb->fsiz >> hdb->apow; llnum = TCHTOILL(llnum); if(!tchdbseekwrite(hdb, entoff, &llnum, sizeof(uint64_t))) return false; } else { uint32_t lnum = hdb->fsiz >> hdb->apow; lnum = TCHTOIL(lnum); if(!tchdbseekwrite(hdb, entoff, &lnum, sizeof(uint32_t))) return false; } } else {// 新记录文件偏移值写入hash桶数组,偏移值为文件大小,说明从文件尾部开始放 } tchdbsetbucket(hdb, bidx, hdb->fsiz); tchdbdrpappend(hdb, kbuf, ksiz, vbuf, vsiz, hash); // 添加传入的record到drpool缓冲区 hdb->rnum++; if(TCXSTRSIZE(hdb->drpool) > HDBDRPUNIT && !tchdbflushdrp(hdb)) return false; // 是否需要刷新缓冲区? return true; }

    通过阅读源代码,我大概说下自己对drpool缓冲区和drpdef缓冲区的功能的个人理解:在异步写入模式下,drpool缓冲区中的记录具有以下3个特点:

1)在记录所在的冲突树中,它们的二级hash值和树中任何记录的二级hash都不相同。

2)它们会按put的顺序被添加到数据文件尾部。

3drpool中存放的记录格式和它们在文件中的格式完全一致,因此在下次写入数据文件的时候不需要额外处理了。

    至于在drpdef缓冲区中的记录,都能在它们对应的冲突树中找到二级hash值相等的记录,并且它们添加到数据文件的方式暂时也没有决定,只有等到刷新缓冲区时调用tchdbputimpl才决定如何写入数据文件。另外,drpdef中存放的是recordkey大小、value大小、key值和value值,在写入数据文件时还需要构造才合适的文件记录格式。

 

    下面是tchdbdrpappend函数代码及注释:

/* Append a record to the delayed record pool. `hdb' specifies the hash database object. `kbuf' specifies the pointer to the region of the key. `ksiz' specifies the size of the region of the key. `vbuf' specifies the pointer to the region of the value. `vsiz' specifies the size of the region of the value. `hash' specifies the second hash value. */ static void tchdbdrpappend(TCHDB *hdb, const char *kbuf, int ksiz, const char *vbuf, int vsiz, uint8_t hash){ assert(hdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0); TCDODEBUG(hdb->cnt_appenddrp++); char rbuf[HDBIOBUFSIZ]; // rbuf用于构建存放在磁盘上的record,包含magic nuber、hash值、record头、左右孩子指 // 针及具体的key和value相关信息等等,下面几条语句分别给这些成员预留空间并赋值。 char *wp = rbuf; *(uint8_t *)(wp++) = HDBMAGICREC; *(uint8_t *)(wp++) = hash; if(hdb->ba64){ // 存放左右孩子指针 memset(wp, 0, sizeof(uint64_t) * 2); wp += sizeof(uint64_t) * 2; } else { memset(wp, 0, sizeof(uint32_t) * 2); wp += sizeof(uint32_t) * 2; } uint16_t snum; // 此变量临时存放填充区的长度 char *pwp = wp; wp += sizeof(snum); // 预留存放填充区长度空间 int step; TCSETVNUMBUF(step, wp, ksiz); // 存放ksiz到rbuf中,ksize的长度为变长,step返回ksize占用的长度 wp += step; TCSETVNUMBUF(step, wp, vsiz); // 存放vsiz,处理方式和ksiz一样 wp += step; int32_t hsiz = wp - rbuf; int32_t rsiz = hsiz + ksiz + vsiz; // 不包含填充区的record长度 uint16_t psiz = tchdbpadsize(hdb, hdb->fsiz + rsiz); // record填充区长度 hdb->fsiz += rsiz + psiz; // hdb文件长度增加新record的长度 snum = TCHTOIS(psiz); memcpy(pwp, &snum, sizeof(snum)); // 填写填充区长度到rbuf预留空间中 TCXSTR *drpool = hdb->drpool; // 将新记录追加到drp区 TCXSTRCAT(drpool, rbuf, hsiz); TCXSTRCAT(drpool, kbuf, ksiz); TCXSTRCAT(drpool, vbuf, vsiz); if(psiz > 0){ // 同时追加填充区,填充区都置为0 char pbuf[psiz]; memset(pbuf, 0, psiz); TCXSTRCAT(drpool, pbuf, psiz); } }

你可能感兴趣的:(优化,database,asynchronous,磁盘)