下面列举几个典型的过程,对于不熟悉SequoiaDB源码的同学,通过跟这几个过程,结合前面的内容,应该可以很容易的熟悉代码的主要脉络,进而快速的学习到整个数据库引擎的实现过程。
6.1 数据库引擎启动过程相关
pmdMain.cpp为主入口,在pmdMainThreadMain中,实现了读取命令行参数,初始化KRCB,然后调用pmdController中的registerCB方法,根据数据库的角色,注册ControlBlock,最后启动一个while循环每100毫秒检测一下状态,完成数据库的启动。
pmdController本身也是一个ControlBlock,在前面pmdMain中调用其registerCB的过程中,pmdController实例也注册到了KRCB中,随后其状态被统一管理。所以,虽然在pmdMain中仅仅调用了registerCB方法,但是实际上很多初始化工作由pmdController才刚刚开始。
在init方法中,pmdController主要实现了对监听端口的绑定。
在active方法中,启动和注册了三个系统EDU,分别负责时钟同步,TCP连接处理和REST连接处理。
以TCPListener为例,其启动之后将不断的检查事件队列中是否有传递进来的连接,如果有,则启动一个pmdAgent EDU,由其工作线程负责处理后续的工作。
在pmdTcpListener.cpp中,可以看到其其实只是定义了一个入口函数名为pmdTcpListenerEntryPoint,由它来处理当EDU type为EDU_TYPE_TCPLISTENER时的具体工作。其他类型的EDU和其类型的映射,通过查看pmdEDUEntryPoint.cpp中的getEntryFuncByType函数可以得到。
6.2 数据查询的实现流程
pmdTcpListener.cpp中实现了tcpListener这个EDU的入口函数,在这个EDU执行时,会不断的检查客户端到来的tcp连接,如果建立了连接,就启动一个Agent类型的EDU来处理这个连接。在pmdAgent.cpp中实现了Agent这个类型的EDU的入口函数。其主要代码如下:
SOCKET s = *(( SOCKET *) &arg ) ;
pmdLocalSession localSession( s ) ;
localSession.attach( cb ) ;
if ( pmdGetDBRole() == SDB_ROLE_COORD )
{
pmdCoordProcessor coordProcessor ;
localSession.attachProcessor( &coordProcessor ) ;
rc = localSession.run() ;
localSession.detachProcessor() ;
}
else
{
pmdDataProcessor dataProcessor ;
localSession.attachProcessor( &dataProcessor ) ;
rc = localSession.run() ;
localSession.detachProcessor() ;
}
localSession.detach() ;
根据当前启动的数据库引擎的角色,是协调节点还是数据节点,分别交由pmdCoordProcessor和pmdDataProcessor来处理。在pmdSession中实现了对socket上来的信息进行处理和转换,提取消息头,并且封装成后续模块能够处理的消息,如果不对通信协议细节感兴趣,其消息的处理过程和定义可以略过,看pmdProcessor即可。在processMsg中可以看到,在processMsg函数中,根据消息类型不同,转而让各种对应的函数处理,我们现在感兴趣的是_onQueryReqMsg函数,接着往下跟。
对于一个查询请求,需要提取的是集合名,返回数量,查询条件,返回字段,排序等信息,通过msgExtractQuery函数对请求中的相关信息进行提取:
rc = msgExtractQuery ( (CHAR *)msg, &flags, &pCollectionName,
&numToSkip, &numToReturn, &pQueryBuff,
&pFieldSelector, &pOrderByBuffer, &pHintBuffer ) ;
随后封装为对应的BSON对象,交给rtnQuery执行:
BSONObj matcher ( pQueryBuff ) ;
BSONObj selector ( pFieldSelector ) ;
BSONObj orderBy ( pOrderByBuffer ) ;
BSONObj hint ( pHintBuffer ) ;
// 省略部分代码
rc = rtnQuery( pCollectionName, selector, matcher, orderBy,
hint, flags, eduCB(), numToSkip, numToReturn,
_pDMSCB, _pRTNCB, contextID, &pContext, TRUE ) ;
在rtnQuery中,根据需要查找的结果集的名字查找其存储的块的位置,最后建立对应的上下文环境:
rc = rtnResolveCollectionNameAndLock ( pCollectionName, dmsCB, &su,
&pCollectionShortName, suID ) ;//找到结果集对应的存储块
rc = su->data()->getMBContext( &mbContext, pCollectionShortName, -1 ) ;
rc = rtnCB->contextNew ( ( flags & FLG_QUERY_PARALLED ) ?
RTN_CONTEXT_PARADATA : RTN_CONTEXT_DATA,
(rtnContext**)&dataContext, contextID, cb ) ;
然后根据是否需要排序,调用dataContext的open方法,见rtnContext.cpp中的_rtnContextData::open的代码。根据查询的条件,如果有索引则使用索引扫描,如果没有则使用表扫描。然后落实在_openTBScan和_openIXScan两个函数的实现之上。以表扫描为例,会把表所涉及的分区块的id号dmsExtendID找到并存储在这个dataContext中;最后调用此dataContext->getMore方法读取所需的数据。
根据前面整理出来的模块关系,搞清楚了EDU和CB这两个核心概念,基本上循着所需要探索的功能特性一步步把每个模块的代码搞清楚,SequoiaDB的内部世界也就毫无保留的展现出来了。从源码中学习大牛的思想,进而提高自己的能力,也许这就是开源的魅力吧!