好久好久没有写博客了,因为一直要做各种事,工作上的,生活上的,这一下就是半年。
时光如梭。
这两天回头看了看写的博客,感觉都是贻笑大方。
但是还是想坚持把SequoiaDB系列写完。
初步的打算已经确定好,已经更新的 前言 中。
从本篇开始,进入源码分析篇。
为了能让自己坚持下去,也让看我的博客学习的同学由浅入深逐步学习,我们先从简单的开始。
如果你觉得本系列的博文让你觉得有用,请收藏我的博客地址 :)
分析SequoiaDB的进程模型,免不了要从进程的Main函数开涮。
SequoiaDB源码编译出来之后,主要的进程就是一个,在bin目录下的sequoiadb。
这个执行程序,能根据配置文件,化身成不用的角色,比如coord节点进程,data节点进程,以及catalog节点进程。
sequoiadb的主函数入口,代码位于 SequoiaDB/engine/pmd/pmdMain.cpp 中,非常简单,堪比“Hello world”:
INT32 main ( INT32 argc, CHAR** argv ) { INT32 rc = SDB_OK ; PD_TRACE_ENTRY ( SDB_PMDMAIN ); rc = engine::pmdMasterThreadMain ( argc, argv ) ; PD_TRACE_EXITRC ( SDB_PMDMAIN, rc ); return rc ; }
main函数的函数体,其实就是执行engine::omdMasterThreadMain函数,在函数退出的时候,取得执行后的错误码结束进程。
SequoiaDB的源码中,C和C++混合存在。源码中定义了不少宏,像 PD_TRACE_ENTRY,PD_TRACE_EXITRC等等,这类函数只要是检测用的,我就不表述了,有兴趣的自己去研究一下 : )
接下来我们来看pmdMasterThreadMain函数:
1 INT32 pmdMasterThreadMain ( INT32 argc, CHAR** argv ) 2 { 3 INT32 rc = SDB_OK ; 4 PD_TRACE_ENTRY ( SDB_PMDMSTTHRDMAIN ); 5 pmdKRCB *krcb = pmdGetKRCB () ; 6 UINT32 startTimerCount = 0 ; 7 8 rc = pmdResolveArguments ( argc, argv ) ; 9 if ( rc ) 10 { 11 ossPrintf( "Failed resolving arguments(error=%d), exit"OSS_NEWLINE, 12 rc ) ; 13 goto error ; 14 } 15 if ( PMD_IS_DB_DOWN ) 16 { 17 return rc ; 18 } 19 20 sdbEnablePD( pmdGetOptionCB()->getDiagLogPath(), 21 pmdGetOptionCB()->diagFileNum() ) ; 22 setPDLevel( (PDLEVEL)( pmdGetOptionCB()->getDiagLevel() ) ) ; 23 // 设置log日志级别,以免输出不关心的日志 24 PD_LOG ( ( getPDLevel() > PDEVENT ? PDEVENT : getPDLevel() ) , 25 "Start sequoiadb(%s) [Ver: %d.%d, Release: %d, Build: %s]...", 26 pmdGetOptionCB()->krcbRole(), SDB_ENGINE_VERISON_CURRENT, 27 SDB_ENGINE_SUBVERSION_CURRENT, SDB_ENGINE_RELEASE_CURRENT, 28 SDB_ENGINE_BUILD_TIME ) ; 29 30 { 31 BSONObj confObj ; 32 krcb->getOptionCB()->toBSON( confObj ) ; 33 PD_LOG( PDEVENT, "All configs: %s", confObj.toString().c_str() ) ; 34 } 35 // 捕捉操作系统信号 36 rc = pmdEnableSignalEvent( pmdGetOptionCB()->getDiagLogPath(), 37 (PMD_ON_QUIT_FUNC)pmdOnQuit ) ; 38 PD_RC_CHECK ( rc, PDERROR, "Failed to enable trap, rc: %d", rc ) ; 39 // 根据role类型,注册不同的功能模块 40 sdbGetPMDController()->registerCB( pmdGetDBRole() ) ; 41 // 启动分析 42 rc = _pmdSystemInit() ; 43 if ( rc ) 44 { 45 goto error ; 46 } 47 // 初始化各个功能模块 48 rc = krcb->init() ; 49 if ( rc ) 50 { 51 PD_LOG( PDERROR, "Failed to init krcb, rc: %d", rc ) ; 52 goto error ; 53 } 54 55 rc = _pmdPostInit() ; 56 if ( rc ) 57 { 58 goto error ; 59 } 60 // 进入while循环,等待收到功能都完成初始化,可以提供服务的通知 61 while ( PMD_IS_DB_UP && startTimerCount < PMD_START_WAIT_TIME && 62 !krcb->isBusinessOK() ) 63 { 64 ossSleepmillis( 100 ) ; 65 startTimerCount += 100 ; 66 } 67 68 if ( PMD_IS_DB_DOWN ) 69 { 70 rc = krcb->getExitCode() ; 71 PD_LOG( PDERROR, "Start failed, rc: %d", rc ) ; 72 goto error ; 73 } 74 else if ( startTimerCount >= PMD_START_WAIT_TIME ) 75 { 76 PD_LOG( PDWARNING, "Start warning (timeout)" ) ; 77 } 78 79 #if defined (_LINUX) 80 { 81 CHAR pmdProcessName [ OSS_RENAME_PROCESS_BUFFER_LEN + 1 ] = {0} ; 82 ossSnprintf ( pmdProcessName, OSS_RENAME_PROCESS_BUFFER_LEN, 83 "%s(%s) %s", utilDBTypeStr( pmdGetDBType() ), 84 pmdGetOptionCB()->getServiceAddr(), 85 utilDBRoleShortStr( pmdGetDBRole() ) ) ; 86 ossEnableNameChanges ( argc, argv ) ; 87 ossRenameProcess ( pmdProcessName ) ; 88 } 89 #endif // _LINUX 90 { 91 EDUID agentEDU = PMD_INVALID_EDUID ; 92 pmdEDUMgr *eduMgr = pmdGetKRCB()->getEDUMgr() ; 93 eduMgr->startEDU ( EDU_TYPE_PIPESLISTENER, 94 (void*)pmdGetOptionCB()->getServiceAddr(), 95 &agentEDU ) ; 96 eduMgr->regSystemEDU ( EDU_TYPE_PIPESLISTENER, agentEDU ) ; 97 } 98 // 大while循环,如果程序没有收到退出信号,就一直在while中;收到退出信号,PMD_IS_DB_UP所代表的变量就会变成 FALSE 99 while ( PMD_IS_DB_UP ) 100 { 101 ossSleepsecs ( 1 ) ; 102 sdbGetPMDController()->onTimer( OSS_ONE_SEC ) ; 103 } 104 rc = krcb->getExitCode() ; 105 106 done : 107 PMD_SHUTDOWN_DB( rc ) ; 108 pmdSetQuit() ; 109 krcb->destroy () ; 110 pmdGetStartup().final() ; 111 PD_LOG ( PDEVENT, "Stop sequoiadb, exit code: %d", 112 krcb->getExitCode() ) ; 113 PD_TRACE_EXITRC ( SDB_PMDMSTTHRDMAIN, rc ); 114 return utilRC2ShellRC( rc ) ; 115 error : 116 goto done ; 117 }
看起来有点大,慢慢来。
首先,函数通过 pmdGetKRCB() 得到了一个krcb的对象指针。所谓krcb,其实全面大概就是 kernel control block了。如果你跟进它的产生里面,你会发现它是 static的,全局的静态变量。基本上可以确定,这个是数据库的一个核心模块。这里我们先不管。
接下来,对入参进行解析,通过pmdResolveArguments函数,main函数很简单,只是简单地把程序的附加参数,传给了pmdMasterThreadMain,然后在这个地方解析。
再就是sdbEnablePD,setPDLevel等,这些是和打印程序运行中的一些关键信息相关的,比如日志等级啊,日志文件路径等等。
这里,我们不关心这些。
再下来就是用pmdEnableSignalEvent函数处理操作系统信号:当收到操作系统发给进程信号的时候,catch到信号事件,然后对针对信号做一些处理的。一个好的服务端程序,是应该能catch到信号事件,然后对信号做一些处理的。例如,程序跑着跑着,收到一个SIGPIPE信号,如果没有捕捉到信号,程序就退出了。没有留下任何帮助信息。这个时候,如果能捕捉到这个信号,抓取进程的栈数据,放倒一个文件里面,就可以根据这些信息,去定位程序出问题的地方。很多程序的dump收集,就是基于这个原理的。感兴趣的可以跟进 pmdEnableSignalEvent函数,看看它怎么捕捉信号事件,怎么处理的。
接下来就到了重点了:
sdbGetPMDController()->registerCB( pmdGetDBRole() ) ;
_pmdSystemInit() ;
krcb->init() ;
这几个函数,主要是根据当前进程的角色,用来初始化已经注册的引擎模块。
先看注册:
1 void _pmdController::registerCB( SDB_ROLE dbrole ) 2 { 3 // 根据不同的数据节点角色,注册对应的模块 4 // DPS 数据保护服务模块,这块的核心是记录写操作的日志,方便同步,如果你对mongodb熟悉的话,有个oplog,功能和它类似,它是一致性的保证之一 5 // TRANS 事务功能模块 6 // CLS 集群管理服务模块,管理集群中的节点 7 // BPS 8 // FMP 外部消息协议模块 9 // CATALOGUE 编目信息服务模块 10 // AUTH 鉴权模块 11 // DMS 数据管理服务模块,这是数据库存储的核心 12 // SQL sql语言支持模块 13 // RTN 平台运行库,主要是跨平台的api封装 14 // OMSVC OM服务,支持rest等协议支持模块 15 // AGGR 数据聚集服务模块 16 if ( SDB_ROLE_DATA == dbrole ) 17 { 18 PMD_REGISTER_CB( sdbGetDPSCB() ) ; // DPS 19 PMD_REGISTER_CB( sdbGetTransCB() ) ; // TRANS 20 PMD_REGISTER_CB( sdbGetClsCB() ) ; // CLS 21 PMD_REGISTER_CB( sdbGetBPSCB() ) ; // BPS 22 } 23 else if ( SDB_ROLE_COORD == dbrole ) 24 { 25 PMD_REGISTER_CB( sdbGetTransCB() ) ; // TRANS 26 PMD_REGISTER_CB( sdbGetCoordCB() ) ; // COORD 27 PMD_REGISTER_CB( sdbGetFMPCB () ) ; // FMP 28 } 29 else if ( SDB_ROLE_CATALOG == dbrole ) 30 { 31 PMD_REGISTER_CB( sdbGetDPSCB() ) ; // DPS 32 PMD_REGISTER_CB( sdbGetTransCB() ) ; // TRANS 33 PMD_REGISTER_CB( sdbGetClsCB() ) ; // CLS 34 PMD_REGISTER_CB( sdbGetCatalogueCB() ) ; // CATALOGUE 35 PMD_REGISTER_CB( sdbGetBPSCB() ) ; // BPS 36 PMD_REGISTER_CB( sdbGetAuthCB() ) ; // AUTH 37 } 38 else if ( SDB_ROLE_STANDALONE == dbrole ) 39 { 40 PMD_REGISTER_CB( sdbGetDPSCB() ) ; // DPS 41 PMD_REGISTER_CB( sdbGetTransCB() ) ; // TRANS 42 PMD_REGISTER_CB( sdbGetBPSCB() ) ; // BPS 43 } 44 else if ( SDB_ROLE_OM == dbrole ) 45 { 46 PMD_REGISTER_CB( sdbGetDPSCB() ) ; // DPS 47 PMD_REGISTER_CB( sdbGetTransCB() ) ; // TRANS 48 PMD_REGISTER_CB( sdbGetBPSCB() ) ; // BPS 49 PMD_REGISTER_CB( sdbGetAuthCB() ) ; // AUTH 50 PMD_REGISTER_CB( sdbGetOMManager() ) ; // OMSVC 51 } 52 PMD_REGISTER_CB( sdbGetDMSCB() ) ; // DMS 53 PMD_REGISTER_CB( sdbGetRTNCB() ) ; // RTN 54 PMD_REGISTER_CB( sdbGetSQLCB() ) ; // SQL 55 PMD_REGISTER_CB( sdbGetAggrCB() ) ; // AGGR 56 PMD_REGISTER_CB( sdbGetPMDController() ) ; // CONTROLLER 57 }
以上表明,数据库节点的角色,分为data,calalog,coord,om,和standalone等。不同的角色,会注册(加载)不同的功能模块。
再看_pmdSystemInit函数,这个函数会初始化系统模块:
1 static INT32 _pmdSystemInit() 2 { 3 INT32 rc = SDB_OK ; 4 5 rc = pmdGetStartup().init( pmdGetOptionCB()->getDbPath() ) ; 6 PD_RC_CHECK( rc, PDERROR, "Start up check failed[rc:%d]", rc ) ; 7 8 rc = getQgmStrategyTable()->init() ; 9 PD_RC_CHECK( rc, PDERROR, "Init qgm strategy table failed, rc: %d", 10 rc ) ; 11 12 done: 13 return rc ; 14 error: 15 goto done ; 16 }
这是函数从指定的路径中读取启动文件并初始化,然后初始化SQL相关的策略。启动文件是一个隐藏文件,当数据库正常或者异常退出时候,会记录数据库的状态。如果是从异常退出,则在再次启动的时候,会重新找主节点做全量同步,是自身数据和主节点一致。至于SQL相关的策略,我没有细看,应该是SQL语法树相关。
在完成启动分析之后,接下来就开始初始化前面注册的功能模块了,krcb->init()
1 INT32 _SDB_KRCB::init () 2 { 3 INT32 rc = SDB_OK ; 4 INT32 index = 0 ; 5 IControlBlock *pCB = NULL ; 6 7 _mainEDU.setName( "Main" ) ; 8 if ( NULL == pmdGetThreadEDUCB() ) 9 { 10 pmdDeclareEDUCB( &_mainEDU ) ; 11 } 12 13 rc = ossGetHostName( _hostName, OSS_MAX_HOSTNAME ) ; 14 PD_RC_CHECK( rc, PDERROR, "Failed to get host name, rc: %d", rc ) ; 15 16 _init = TRUE ; 17 // 一次初始化已经注册的功能模块 18 for ( index = 0 ; index < SDB_CB_MAX ; ++index ) 19 { 20 pCB = _arrayCBs[ index ] ; 21 if ( !pCB ) 22 { 23 continue ; 24 } 25 if ( SDB_OK != ( rc = pCB->init() ) ) 26 { 27 PD_LOG( PDERROR, "Init cb[Type: %d, Name: %s] failed, rc: %d", 28 pCB->cbType(), pCB->cbName(), rc ) ; 29 goto error ; 30 } 31 } 32 // 依次激活已经初始化的功能模块 33 for ( index = 0 ; index < SDB_CB_MAX ; ++index ) 34 { 35 pCB = _arrayCBs[ index ] ; 36 if ( !pCB ) 37 { 38 continue ; 39 } 40 if ( SDB_OK != ( rc = pCB->active() ) ) 41 { 42 PD_LOG( PDERROR, "Active cb[Type: %d, Name: %s] failed, rc: %d", 43 pCB->cbType(), pCB->cbName(), rc ) ; 44 goto error ; 45 } 46 } 47 48 _isActive = TRUE ; 49 // 时间采样,不表 50 _curTime.sample() ; 51 52 done: 53 return rc ; 54 error: 55 goto done ; 56 }
前面有提到 krcb是一个全局的变量,是整个数据库的核心。 SequoiaDB中的各个模块,都继承自同一个控制接口 IControlBlock,由虚函数来实现多态,并且交给KRCB模块集中管理,体现了软件开发中“谁产生,谁管理”的思想。
1 class _IControlBlock : public SDBObject, public _ISDBRoot 2 { 3 public: 4 _IControlBlock () {} 5 virtual ~_IControlBlock () {} 6 7 virtual SDB_CB_TYPE cbType() const = 0 ; 8 virtual const CHAR* cbName() const = 0 ; 9 10 virtual INT32 init () = 0 ; 11 virtual INT32 active () = 0 ; 12 virtual INT32 deactive () = 0 ; 13 virtual INT32 fini () = 0 ; 14 virtual void onConfigChange() {} 15 16 } ; 17 typedef _IControlBlock IControlBlock ;
函数中通过for循环,初始化各个模块,然后并激活各个模块。如此,数据库就开始真正的提供服务了,_isActive = TRUE很坦白地说明了这一点。
至于最后的_pmdPostInit() 函数,就是给初始化工作做一些扫尾工作,具体是把异常启动的standalone模式的节点或提供om服务节点的节点状态标记为正常。具体内容就不再贴代码了。
然后,程序就开始做一些启动后的扫尾工作:重命名进程,以便ps命令看到整齐的进程角色和服务端口;创建PIPE监听的服务,只是用于检测数据库状态,(载体是EDU)。
然后主线程进入了while循环,直到收到退出信号:
1 while ( PMD_IS_DB_UP ) 2 { 3 ossSleepsecs ( 1 ) ; 4 sdbGetPMDController()->onTimer( OSS_ONE_SEC ) ; 5 }
至此,我们的main函数分析到一段落。
从整个main函数的分析,可以看出SequoiaDB中的功能划分很清楚。一个KRCB控制块,管理其他功能模块(也是控制块);其他模块,管理自身下的功能。以后会分析到几个主要模块的功能,如DPS,DMS等。
这和我认识的软件开发思想大致相同:一个软件产品,从粗粒度来看,无非就是功能模块的组装,但是要合理地去协调各个模块的关系,使之井然有序稳定地工作,这就关系到技术细节上了。
感谢您能耐心看到这里。
下一篇开始分析SequoiaDB的插入以及相关源码。
=====>THE END<=====