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

时光如梭。

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

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

SequoiaDB 系列之五 :源码分析之main函数的更多相关文章

  1. external-attacher源码分析(1)-main方法与启动参数分析

    更多 ceph-csi 其他源码分析,请查看下面这篇博文:kubernetes ceph-csi分析目录导航 摘要 ceph-csi分析-external-attacher源码分析.external- ...

  2. Spring基础系列-AOP源码分析

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9560803.html 一.概述 Spring的两大特性:IOC和AOP. AOP是面向切 ...

  3. Flask系列之源码分析(一)

    目录: 涉及知识点 Flask框架原理 简单示例 路由系统原理源码分析 请求流程简单源码分析 响应流程简单源码分析 session简单源码分析 涉及知识点 1.装饰器 闭包思想 def wapper( ...

  4. Tomcat详解系列(3) - 源码分析准备和分析入口

    Tomcat - 源码分析准备和分析入口 上文我们介绍了Tomcat的架构设计,接下来我们便可以下载源码以及寻找源码入口了.@pdai 源代码下载和编译 首先是去官网下载Tomcat的源代码和二进制安 ...

  5. LiteOS-任务篇-源码分析-删除任务函数

    目录 前言 笔录草稿 源码分析 LOS_TaskDelete函数源码分析 完整源码 参考 链接 前言 20201009 LiteOS 2018 需要会通用链表 笔录草稿 源码分析 LOS_TaskDe ...

  6. jQuery 源码分析(五) map函数 $.map和$.fn.map函数 详解

    $.map() 函数用于使用指定函数处理数组中的每个元素(或对象的每个属性),并将处理结果封装为新的数组返回,该函数有三个参数,如下: elems Array/Object类型 指定的需要处理的数组或 ...

  7. jQuery 源码分析(四) each函数 $.each和$.fn.each方法 详解

    $.each一般用来遍历一个数组或对象,$.fn.each()就是指jQuery实例可以执行的操作(因为$.fn是jQuery对象的原型) $.each用来遍历一个数组或对象,并依次执行回掉函数,最后 ...

  8. 【LiteOS】LiteOS任务篇-源码分析-创建任务函数

    目录 前言 链接 参考 笔录草稿 部分源码分析 源码分析 LOS_TaskCreate函数 LOS_TaskCreateOnly函数 宏 OS_TCB_FROM_PENDLIST 和 宏 LOS_DL ...

  9. Android进阶系列之源码分析Activity的启动流程

    美女镇楼,辟邪! 源码,是一个程序猿前进路上一个大的而又不得不去翻越障碍,我讨厌源码,看着一大堆.5000多行,要看完得啥时候去了啊.不过做安卓的总有这一天,自从踏上这条不归路,我就认命了.好吧,我慢 ...

随机推荐

  1. Linux查看BIOS信息

    http://www.linuxde.net/2013/02/12499.html

  2. A*算法详解 BZOJ 1085骑士精神

    转载1:A*算法入门 http://www.cppblog.com/mythit/archive/2009/04/19/80492.aspx 在看下面这篇文章之前,先介绍几个理论知识,有助于理解A*算 ...

  3. 图的遍历之深度优先搜索(DFS)

    深度优先搜索(depth-first search)是对先序遍历(preorder traversal)的推广.”深度优先搜索“,顾名思义就是尽可能深的搜索一个图.想象你是身处一个迷宫的入口,迷宫中的 ...

  4. 测试思考[持续更新ing]

    1.如何设计好的测试用例,提高测试覆盖率?提高测试效率? 2.如何展现测试成果? 3.如何编写专业化.优质的测试文档? 4.如何适时借助测试工具,测试一些重复性比较高的测试? 5.如何引入自动化测试? ...

  5. python刷题专用函数。。

    无它,非bin()莫属. bin(x) Convert an integer number to a binary string. The result is a valid Python expre ...

  6. [3D跑酷] DataManager

    DataManager管理游戏中数据,当然这个类中大部分的属性和方法都是Public 函数列表

  7. Dvwa writeup

    DVWA(Dam vulnerable Web Application)是使用PHP+Mysql编写的一套用于常规漏洞教学和漏洞挖掘的一个测试学习程序,在此程序中包含了常见的web方面的漏洞,如命令行 ...

  8. 观察器observes与对象初始化

    Demo.Person2 = Ember.Object.extend({ init: function() { alert('lljsd'); this.set('salutation', " ...

  9. 搞懂function(*args,**kwargs)

    给出一个例子: def foo(*args,**kwargs): print 'args=',args print 'kwargs=',kwargs print '------------------ ...

  10. Gradle多项目构建

    1. 创建项目 首先创建项目,名称为 test: mkdir test && cd test gradle init 这时候的项目结构如下: ➜ test tree . ├── bui ...