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


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

    时光如梭。

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

    但是还是想坚持把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<===== 

  • 相关阅读:
    flutter 和 NTFS
    APIO2020 游记
    CF1336F Journey
    ZJOI2020 游记
    CF568E Longest Increasing Subsequence
    CSP2020 游记
    洛谷 P6217 简单数论题
    CF587F Duff is Mad
    CF526G Spiders Evil Plan
    WC2021 游记
  • 原文地址:https://www.cnblogs.com/tynia/p/mainEntry.html
Copyright © 2020-2023  润新知