SequoiaDB 系列之五 :源码分析之main函数

时间:2022-04-28 22:10:38

好久好久没有写博客了,因为一直要做各种事,工作上的,生活上的,这一下就是半年。

时光如梭。

这两天回头看了看写的博客,感觉都是贻笑大方。

但是还是想坚持把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函数:

 INT32 pmdMasterThreadMain ( INT32 argc, CHAR** argv )
{
INT32 rc = SDB_OK ;
PD_TRACE_ENTRY ( SDB_PMDMSTTHRDMAIN );
pmdKRCB *krcb = pmdGetKRCB () ;
UINT32 startTimerCount = ; rc = pmdResolveArguments ( argc, argv ) ;
if ( rc )
{
ossPrintf( "Failed resolving arguments(error=%d), exit"OSS_NEWLINE,
rc ) ;
goto error ;
}
if ( PMD_IS_DB_DOWN )
{
return rc ;
} sdbEnablePD( pmdGetOptionCB()->getDiagLogPath(),
pmdGetOptionCB()->diagFileNum() ) ;
setPDLevel( (PDLEVEL)( pmdGetOptionCB()->getDiagLevel() ) ) ;
// 设置log日志级别,以免输出不关心的日志
PD_LOG ( ( getPDLevel() > PDEVENT ? PDEVENT : getPDLevel() ) ,
"Start sequoiadb(%s) [Ver: %d.%d, Release: %d, Build: %s]...",
pmdGetOptionCB()->krcbRole(), SDB_ENGINE_VERISON_CURRENT,
SDB_ENGINE_SUBVERSION_CURRENT, SDB_ENGINE_RELEASE_CURRENT,
SDB_ENGINE_BUILD_TIME ) ; {
BSONObj confObj ;
krcb->getOptionCB()->toBSON( confObj ) ;
PD_LOG( PDEVENT, "All configs: %s", confObj.toString().c_str() ) ;
}
// 捕捉操作系统信号
rc = pmdEnableSignalEvent( pmdGetOptionCB()->getDiagLogPath(),
(PMD_ON_QUIT_FUNC)pmdOnQuit ) ;
PD_RC_CHECK ( rc, PDERROR, "Failed to enable trap, rc: %d", rc ) ;
// 根据role类型,注册不同的功能模块
sdbGetPMDController()->registerCB( pmdGetDBRole() ) ;
// 启动分析
rc = _pmdSystemInit() ;
if ( rc )
{
goto error ;
}
// 初始化各个功能模块
rc = krcb->init() ;
if ( rc )
{
PD_LOG( PDERROR, "Failed to init krcb, rc: %d", rc ) ;
goto error ;
} rc = _pmdPostInit() ;
if ( rc )
{
goto error ;
}
// 进入while循环,等待收到功能都完成初始化,可以提供服务的通知
while ( PMD_IS_DB_UP && startTimerCount < PMD_START_WAIT_TIME &&
!krcb->isBusinessOK() )
{
ossSleepmillis( ) ;
startTimerCount += ;
} if ( PMD_IS_DB_DOWN )
{
rc = krcb->getExitCode() ;
PD_LOG( PDERROR, "Start failed, rc: %d", rc ) ;
goto error ;
}
else if ( startTimerCount >= PMD_START_WAIT_TIME )
{
PD_LOG( PDWARNING, "Start warning (timeout)" ) ;
} #if defined (_LINUX)
{
CHAR pmdProcessName [ OSS_RENAME_PROCESS_BUFFER_LEN + ] = {} ;
ossSnprintf ( pmdProcessName, OSS_RENAME_PROCESS_BUFFER_LEN,
"%s(%s) %s", utilDBTypeStr( pmdGetDBType() ),
pmdGetOptionCB()->getServiceAddr(),
utilDBRoleShortStr( pmdGetDBRole() ) ) ;
ossEnableNameChanges ( argc, argv ) ;
ossRenameProcess ( pmdProcessName ) ;
}
#endif // _LINUX
{
EDUID agentEDU = PMD_INVALID_EDUID ;
pmdEDUMgr *eduMgr = pmdGetKRCB()->getEDUMgr() ;
eduMgr->startEDU ( EDU_TYPE_PIPESLISTENER,
(void*)pmdGetOptionCB()->getServiceAddr(),
&agentEDU ) ;
eduMgr->regSystemEDU ( EDU_TYPE_PIPESLISTENER, agentEDU ) ;
}
// 大while循环,如果程序没有收到退出信号,就一直在while中;收到退出信号,PMD_IS_DB_UP所代表的变量就会变成 FALSE
while ( PMD_IS_DB_UP )
{
ossSleepsecs ( ) ;
sdbGetPMDController()->onTimer( OSS_ONE_SEC ) ;
}
rc = krcb->getExitCode() ; done :
PMD_SHUTDOWN_DB( rc ) ;
pmdSetQuit() ;
krcb->destroy () ;
pmdGetStartup().final() ;
PD_LOG ( PDEVENT, "Stop sequoiadb, exit code: %d",
krcb->getExitCode() ) ;
PD_TRACE_EXITRC ( SDB_PMDMSTTHRDMAIN, rc );
return utilRC2ShellRC( rc ) ;
error :
goto done ;
}

看起来有点大,慢慢来。

首先,函数通过 pmdGetKRCB() 得到了一个krcb的对象指针。所谓krcb,其实全面大概就是 kernel control block了。如果你跟进它的产生里面,你会发现它是 static的,全局的静态变量。基本上可以确定,这个是数据库的一个核心模块。这里我们先不管。

接下来,对入参进行解析,通过pmdResolveArguments函数,main函数很简单,只是简单地把程序的附加参数,传给了pmdMasterThreadMain,然后在这个地方解析。

再就是sdbEnablePD,setPDLevel等,这些是和打印程序运行中的一些关键信息相关的,比如日志等级啊,日志文件路径等等。

这里,我们不关心这些。

再下来就是用pmdEnableSignalEvent函数处理操作系统信号:当收到操作系统发给进程信号的时候,catch到信号事件,然后对针对信号做一些处理的。一个好的服务端程序,是应该能catch到信号事件,然后对信号做一些处理的。例如,程序跑着跑着,收到一个SIGPIPE信号,如果没有捕捉到信号,程序就退出了。没有留下任何帮助信息。这个时候,如果能捕捉到这个信号,抓取进程的栈数据,放倒一个文件里面,就可以根据这些信息,去定位程序出问题的地方。很多程序的dump收集,就是基于这个原理的。感兴趣的可以跟进 pmdEnableSignalEvent函数,看看它怎么捕捉信号事件,怎么处理的。

接下来就到了重点了:

sdbGetPMDController()->registerCB( pmdGetDBRole() ) ;

_pmdSystemInit() ;

krcb->init() ;

这几个函数,主要是根据当前进程的角色,用来初始化已经注册的引擎模块。

先看注册:

 void _pmdController::registerCB( SDB_ROLE dbrole )
{
// 根据不同的数据节点角色,注册对应的模块
// DPS 数据保护服务模块,这块的核心是记录写操作的日志,方便同步,如果你对mongodb熟悉的话,有个oplog,功能和它类似,它是一致性的保证之一
// TRANS 事务功能模块
// CLS 集群管理服务模块,管理集群中的节点
// BPS
// FMP 外部消息协议模块
// CATALOGUE 编目信息服务模块
// AUTH 鉴权模块
// DMS 数据管理服务模块,这是数据库存储的核心
// SQL sql语言支持模块
// RTN 平台运行库,主要是跨平台的api封装
// OMSVC OM服务,支持rest等协议支持模块
// AGGR 数据聚集服务模块
if ( SDB_ROLE_DATA == dbrole )
{
PMD_REGISTER_CB( sdbGetDPSCB() ) ; // DPS
PMD_REGISTER_CB( sdbGetTransCB() ) ; // TRANS
PMD_REGISTER_CB( sdbGetClsCB() ) ; // CLS
PMD_REGISTER_CB( sdbGetBPSCB() ) ; // BPS
}
else if ( SDB_ROLE_COORD == dbrole )
{
PMD_REGISTER_CB( sdbGetTransCB() ) ; // TRANS
PMD_REGISTER_CB( sdbGetCoordCB() ) ; // COORD
PMD_REGISTER_CB( sdbGetFMPCB () ) ; // FMP
}
else if ( SDB_ROLE_CATALOG == dbrole )
{
PMD_REGISTER_CB( sdbGetDPSCB() ) ; // DPS
PMD_REGISTER_CB( sdbGetTransCB() ) ; // TRANS
PMD_REGISTER_CB( sdbGetClsCB() ) ; // CLS
PMD_REGISTER_CB( sdbGetCatalogueCB() ) ; // CATALOGUE
PMD_REGISTER_CB( sdbGetBPSCB() ) ; // BPS
PMD_REGISTER_CB( sdbGetAuthCB() ) ; // AUTH
}
else if ( SDB_ROLE_STANDALONE == dbrole )
{
PMD_REGISTER_CB( sdbGetDPSCB() ) ; // DPS
PMD_REGISTER_CB( sdbGetTransCB() ) ; // TRANS
PMD_REGISTER_CB( sdbGetBPSCB() ) ; // BPS
}
else if ( SDB_ROLE_OM == dbrole )
{
PMD_REGISTER_CB( sdbGetDPSCB() ) ; // DPS
PMD_REGISTER_CB( sdbGetTransCB() ) ; // TRANS
PMD_REGISTER_CB( sdbGetBPSCB() ) ; // BPS
PMD_REGISTER_CB( sdbGetAuthCB() ) ; // AUTH
PMD_REGISTER_CB( sdbGetOMManager() ) ; // OMSVC
}
PMD_REGISTER_CB( sdbGetDMSCB() ) ; // DMS
PMD_REGISTER_CB( sdbGetRTNCB() ) ; // RTN
PMD_REGISTER_CB( sdbGetSQLCB() ) ; // SQL
PMD_REGISTER_CB( sdbGetAggrCB() ) ; // AGGR
PMD_REGISTER_CB( sdbGetPMDController() ) ; // CONTROLLER
}

以上表明,数据库节点的角色,分为data,calalog,coord,om,和standalone等。不同的角色,会注册(加载)不同的功能模块。

再看_pmdSystemInit函数,这个函数会初始化系统模块:

 static INT32 _pmdSystemInit()
{
INT32 rc = SDB_OK ; rc = pmdGetStartup().init( pmdGetOptionCB()->getDbPath() ) ;
PD_RC_CHECK( rc, PDERROR, "Start up check failed[rc:%d]", rc ) ; rc = getQgmStrategyTable()->init() ;
PD_RC_CHECK( rc, PDERROR, "Init qgm strategy table failed, rc: %d",
rc ) ; done:
return rc ;
error:
goto done ;
}

这是函数从指定的路径中读取启动文件并初始化,然后初始化SQL相关的策略。启动文件是一个隐藏文件,当数据库正常或者异常退出时候,会记录数据库的状态。如果是从异常退出,则在再次启动的时候,会重新找主节点做全量同步,是自身数据和主节点一致。至于SQL相关的策略,我没有细看,应该是SQL语法树相关。

在完成启动分析之后,接下来就开始初始化前面注册的功能模块了,krcb->init()

 INT32 _SDB_KRCB::init ()
{
INT32 rc = SDB_OK ;
INT32 index = ;
IControlBlock *pCB = NULL ; _mainEDU.setName( "Main" ) ;
if ( NULL == pmdGetThreadEDUCB() )
{
pmdDeclareEDUCB( &_mainEDU ) ;
} rc = ossGetHostName( _hostName, OSS_MAX_HOSTNAME ) ;
PD_RC_CHECK( rc, PDERROR, "Failed to get host name, rc: %d", rc ) ; _init = TRUE ;
// 一次初始化已经注册的功能模块
for ( index = ; index < SDB_CB_MAX ; ++index )
{
pCB = _arrayCBs[ index ] ;
if ( !pCB )
{
continue ;
}
if ( SDB_OK != ( rc = pCB->init() ) )
{
PD_LOG( PDERROR, "Init cb[Type: %d, Name: %s] failed, rc: %d",
pCB->cbType(), pCB->cbName(), rc ) ;
goto error ;
}
}
// 依次激活已经初始化的功能模块
for ( index = ; index < SDB_CB_MAX ; ++index )
{
pCB = _arrayCBs[ index ] ;
if ( !pCB )
{
continue ;
}
if ( SDB_OK != ( rc = pCB->active() ) )
{
PD_LOG( PDERROR, "Active cb[Type: %d, Name: %s] failed, rc: %d",
pCB->cbType(), pCB->cbName(), rc ) ;
goto error ;
}
} _isActive = TRUE ;
// 时间采样,不表
_curTime.sample() ; done:
return rc ;
error:
goto done ;
}

前面有提到 krcb是一个全局的变量,是整个数据库的核心。 SequoiaDB中的各个模块,都继承自同一个控制接口 IControlBlock,由虚函数来实现多态,并且交给KRCB模块集中管理,体现了软件开发中“谁产生,谁管理”的思想。

 class _IControlBlock : public SDBObject, public _ISDBRoot
{
public:
_IControlBlock () {}
virtual ~_IControlBlock () {} virtual SDB_CB_TYPE cbType() const = ;
virtual const CHAR* cbName() const = ; virtual INT32 init () = ;
virtual INT32 active () = ;
virtual INT32 deactive () = ;
virtual INT32 fini () = ;
virtual void onConfigChange() {} } ;
typedef _IControlBlock IControlBlock ;

函数中通过for循环,初始化各个模块,然后并激活各个模块。如此,数据库就开始真正的提供服务了,_isActive = TRUE很坦白地说明了这一点。

至于最后的_pmdPostInit() 函数,就是给初始化工作做一些扫尾工作,具体是把异常启动的standalone模式的节点或提供om服务节点的节点状态标记为正常。具体内容就不再贴代码了。

然后,程序就开始做一些启动后的扫尾工作:重命名进程,以便ps命令看到整齐的进程角色和服务端口;创建PIPE监听的服务,只是用于检测数据库状态,(载体是EDU)。

然后主线程进入了while循环,直到收到退出信号:

 while ( PMD_IS_DB_UP )
{
ossSleepsecs ( ) ;
sdbGetPMDController()->onTimer( OSS_ONE_SEC ) ;
}

至此,我们的main函数分析到一段落。

从整个main函数的分析,可以看出SequoiaDB中的功能划分很清楚。一个KRCB控制块,管理其他功能模块(也是控制块);其他模块,管理自身下的功能。以后会分析到几个主要模块的功能,如DPS,DMS等。

这和我认识的软件开发思想大致相同:一个软件产品,从粗粒度来看,无非就是功能模块的组装,但是要合理地去协调各个模块的关系,使之井然有序稳定地工作,这就关系到技术细节上了。

感谢您能耐心看到这里。

下一篇开始分析SequoiaDB的插入以及相关源码。

=====>THE END<=====