[转]豆瓣beansdb浅析

来源:互联网 发布:深圳市的企业数据库 编辑:程序博客网 时间:2024/05/22 06:30


作者:tos

来源:http://hi.baidu.com/dongliqian/item/21e2fb1d07d3fc7a7a5f2565 


豆瓣beansdb浅析 (1)

一直以来对这种分布式的东西就非常好奇,很想学习一下它们究竟是如何是实现的,于是决定从实现相对比较简单的beansdb开始。 beansdb是豆瓣的一位工程师借鉴memcache开发的分布式的数据库,整个项目是开源的,可以从google code下到较新的版本(code.google.com/p/beansdb/)。

在网络事件处理方面,benasdb借助libevent来进行管理,所以整个代码非常简洁,读起来赏心悦目。

按照线程划分,beansdb分为以下几个部分:
1. 主线程负责监听server socket和接受新的连接。(阅读发现socket的配置信息貌似只能在启动时从命令行获得)。
2. worker thread负责处理具体的accept socket。具体的实现方式每个是worker thread都有一个base_event, 里面只有一个管道的读事件。每当有一个新的连接建立起来时,主线程都会用round robin算法选择一个worker thread来处理新连接。而叫醒的方式就是通过写这个pipe。
准确的说,beansdb里面主线程也扮演了work thread的角色,猜测应该是为了单线程模式和多线程模式可以重用一套代码。因为在dispatch_new_conn中并没有对它作出区分。
大体流程如下所示: (PS: Open office好难用啊。。)
                                       

3. 其它的两个线程是负责维护数据库的辅助线程: hs_flush和hs_check。

接下来偶就分析一下网络连接的处理 和 数据库的维护。


豆瓣beansdb浅析 (2)

beansdb在处理网络数据方面复用了memcache的代码。每个连接都对应了一个conn结构,其中conn_state保存了每个连接的状态。 整个网络数据处理的核心是driver_machine函数。连接的状态和处理动作如下:

1. conn_listening. 监听socket始终处于这种状态。监听socket对应的事件被唤醒只有两种情况:接收到意外的信号,有新的连接到来。如果有新的连接,则调用dispatch_new_conn将将其添加到相应的worker thread的connection queue中,并向这个worker thread的pipe发送一个字符。对应的worker thread将从其connection queue中提取新接受的连接,并添加这个连接的事件添加到线程的event_base中。

2. conn_read. conn_read是接受的连接的初始状态和复位状态(?)。在这个状态时,首先会检查当前接受的缓存中是否有\r\n。根据memcache协议,每一条命令都会以\r\n字符结尾。如果当前缓存有\r\n,那么表明当前read buffer中存在完整的命令,于是就提取出该条命令,并对其进行处理。处理机制如下:
2.1 如果是set命令,则将状态转入conn_nread,从而接受二进制数据;
2.2 如果是get命令,则从beansdb中提取key对应的value,并将状态转入conn_mwrite,从而发送查询结果数据;
2.3 如果是其他的命令,例如delete,version,stats等,由于回应消息都比较简单,都是直接调用out_string,跳转到conn_write状态。

3. conn_nread. conn_nread标志着正在从客户端读取二进制数据。这种情况发生在客户端发送set 命令的时候。如果全部二进制数据读取完成,则将其存储到beansdb中,并调用out_string将存储结果返回给客户。

4. conn_write. 当服务器需要发送一些简单的回应的时候(out_string函数),会将连接的状态设置为conn_write. conn_write状态的处理机制就是首先调用add_iov来将对应的回应消息添加到msghdr中的iov中,然后调用transmit函数将未发送的数据发送出去,最后全部数据传输完成时,将状态置为conn_read。

5. conn_mwrite. 这种情况一般是发生在需要向用户返回大量的二进制查询结果时。处理动作也是调用transmit将缓存的数据发送出去。

6. conn_closing. 对应的动作就是清除连接。

memcache协议可以参考 www.ccvita.com/306.html 或者 www.gaobo.info/read.php/447.htm 。


豆瓣beansdb浅析 (3)

beansdb在数据存储方面使用了tokyo cabinet. 在beansdb中数据存储在两种文件中:*.tch 和 *.index 。tch文件用于存储数据和meta data。index文件用于存储当前使用的key, version 和 hash。

在代码结构中,hstore.c 和 htree.c用于管理数据。hstore.c中提供了beansdb.c访问数据的接口。主要的接口有: hs_open, hs_set, hs_get, hs_delete, hs_clear, hs_flush, hs_check。 这个文件主要用于维护.tch数据文件。htree.c 主要用来维护hash tree, 即index文件。

beansdb为了提高并发,支持若干个数据库组成一个森林。在查找时,会根据key计算出index,然后到森林中对应的数据库去提取数据。
beansdb中设计了三种数据容器来存储数据:hash database, memory database, hash tree. 其中hash database 和 memory database是真正存储数据的。hash tree起一个辅助的作用。 hash database 和 memory database是基于tokyo cabinet,而hash tree是beansdb自己设计和实现的。hash database是可以持久化的,而memory database是不可以持久化的。

beansdb中支持获取三种数据:1. 普通的数据。 2. meta data (key以?开头). 主要包括了version 和 hash。 3. hash tree的信息。(key以@开头)
当获取普通数据和metadata时,首先会根据key计算出index。然后到相应的memory database中去获取key对应的数据,如果没有查找到,则会到hash database获取数据。key对应的meta data在数据库中就直接存放在value的后面。
当需要获取hash tree的信息时,会根据参数的长度返回相应的森林信息或者hash tree 中某个node节点下存储的item个数以及hash值。

设置数据的流程相对比较麻烦:
1. 从hash tree中获取出key对应的原来数据的hash值和version。
2. 将原来的version和要设置的version进行比较。如果要设置的version更小,则直接返回。
3. 将原来的hash值同新数据计算出的hash值比较。如果旧的hash值同新的hash值相同,则从memory database 和 hash database中获取出key值对应的原有数据,并新数据进行比较。
4. 如果数据不同则将数据写入到memory database中。
5. 将key值对应的新的version和hash值更新到hash tree中。

删除数据的过程如下:
1. 从hash tree中删除掉key值。
2. 从memory database 和 hash database 中删除掉key值和对应的数据。

从上面可以看出数据在更新的时候,只是存放在memory database中,并没有直接更新到hash database中。如果系统出现故障,则更新过的数据就会丢失掉。因此beansdb通过hs_flush机制来对memory database中的数据刷新到hash database中,并同时将hash tree中的数据持久化到hash database。

beansdb同时提供了一个函数hs_scan用来同步hash database 和 hash tree。如果两者中保存的key数量超过了一个阈值,则同步就会发生。同步默认会对结果进行持久化。

以上的持久化和同步都对应着一个线程。



豆瓣beansdb浅析 (4)

beansdb中的hash tree用来存储key对应的hash, version 以及count信息等。hash tree可以持久化到tokyo cabinet中。这样在每次打开的时候key信息就可以直接从cabinet中读出,不用做费时间的hs_scan调用。

hash tree的结构

同普通的树一样,hash tree也分为叶子节点和内节点。每个内节点有16个子节点。叶子节点中最多存放128个item。每个item中存放了key的名字,hash,version。内节点记录了他的所有的子节点存放的item数目和hash值。内节点的hash值是所有子节点的hash值的和,起一个校验的作用。

hash tree中的数据结构主要由两个部分组成:Node 和 Data。Node对应了hash tree中的内节点。 每一个Node都对应了一个Data。但是只有叶子节点对应的Data才是有用的。在获取key值对应的信息时就回到Data中去获取。当查询一个key时,首先会计算这个key值的index,然后根据index值在内节点中定位子节点。最后定位到某个叶子节点。

在插入key的过程中,如果发现item的个数超出某个限制了,就会将该节点分裂。同样,如果内节点中item的个数少于某个阈值,则会将节点合并。

最后总结一下beansdb到底提供了额外的东西呢?

1. 内建了版本支持。
2. 将memcache和tokyo cabinet合到了一起。
3. 支持查询存储数据的meta data。