深刻mongoDB(1)--mongod的线程模型与网络框架

最近工做须要开始研究mongoDB,我准备从其源代码角度,对于mongod和mongos服务的架构、sharding策略、replicaset策略、数据同步容灾、索引等机制作一个本质性的了解。其代码约20万行(我研究的是?2.0.6版本源码),本篇先从mongod的启动流程提及,它本是一个多线程程序,因此本文在于说明mongod有多少个线程,每一个线程的意义所在。但愿你们阅读本文时关注在mongod的外围框架,暂不涉及数据文件的组织、索引B树的组织等,仅focus in在网络框架、线程模型上。web

 

弄清楚这点的好处很明显:以后就能够有的放矢的研究mongod某个模块到底是如何实现的,能够快速的跳到相应的类中阅读源码,解决咱们在产品中的实际问题。我认为这是研究其庞大源码一个好的开始。mongodb

 

在说明mongod前,须了解mongoDB大量代码是基于boost库构建的,所以这里先行对boost库创建线程作个简单的了解。数据库

 

一、boost库如何创建线程数组

boost::thread是boost中跨平台的多线程库,mongoDB建立线程时大多数状况下是使用thread库的(少许状况直接调用pthread_create方法),主要使用了如下两种方式:网络

(1)直接运行让线程运行func多线程

例如durThread线程:架构

 

void durThread() {并发

while( !inShutdown() ) { ... }app

}框架

 

boost::thread t(durThread);

(2)在类中定义静态的run方法,调用thread建立线程

? ? class FileAllocator : boost::noncopyable {
? ? ? ? static void run( FileAllocator * fa );

 

? ? ? ? void FileAllocator::start() {
? ? ? ? ? ? ?boost::thread t( boost::bind( &FileAllocator::run , this ) );
? ? ? ? }
? ? };

 

二、mongod的入口

mongod的入口main函数在src/mongo/db/db.cpp文件中,我画了个简单的活动图简要介绍其启动流程:

如上图所示,这里出现了12个固定线程,尚未包括mongod运行之后处理请求时派生出来的线程,以下所示:

 

–? interruptThread

–? DataFileSync::run

–? FileAllocator::run

–? durThread

–? SnapshotThread::run

–? ClientCursorMonitor::run

–? PeriodicTask::Runner::run

–? TTLMonitor::run

–? replSlaveThread

–? replMasterThread

–? webServerThread

– ? ? ?处理数据库请求的主线程

若是不属于任何replica set,那么至少有10个固定线程(去除?replSlaveThread和?replMasterThread)。

下面咱们先讨论这10个固定的线程,再讨论性能很是弱的监听web事件的线程是怎样处理请求的,最后讨论性能稍好一点的主服务线程是怎样处理请求的。

 

三、5个基于BackgroundJob类实现的工做线程

这5个线程分别是DataFileSync,SnapshotThread, ClientCursorMonitor, TTLMonitor, PeriodicTask,类图以下所示:

上面这5个类也是用boost::threadfunction方法建立线程运行的,它们继承了BackgroundJob类,使用go方法启动线程执行jobBody就是在启动线程执行run方法,以下所示:

 

 


这些线程的意义以下:

 

 

DataFileSync主要在调用MemoryMappedFile::flush方法将内存中的数据刷到磁盘上。 咱们知道,mongodb是调用mmap把磁盘中的数据映射到内存中的,因此必须有一个机制时刻的刷数据到硬盘才能保证可靠性,多久刷一次是与syncdelay参数相关的。

SnapshotThread将生成快照文件帮助快速恢复。

ClientCursorMonitor将管理用户的游标,每4秒调用一次idleTimeReport()方法,每一分钟调用sayMemoryStatus()方法。

TTLMonitor管理TTL,经过调用doTTLForDB()方法检查全部db。

PeriodicTask将从动态数组std::vector<PeriodicTask* > _tasks中获取周期性任务执行。

 

四、5个直接提供全局方法执行的线程

 

FileAllocator用于分配新文件,它决定分配文件的大小,例如用翻倍的方式。

interruptThread只处理信号量。

durThread作批量提交和回滚工做。

replSlaveThread是当前结点做为secondary时的同步线程。

replMasterThread是当前结点做为master时的同步线程。

 

五、web监听线程

mongod是如何处理web请求的呢?它是经过网络框架中的核心类Listerner实现的,类图以下所示:

怎么理解这幅类图呢?

首先看?Listener类,它负责监听、建立新链接,其工做步骤以下:

a、建立socket句柄,绑定端口,监听

b、调用select检测新链接事件

c、对检测到的事件调用accept创建新链接

d、调用void Listener::acceptedMP(MessagingPort*mp)方法处理新链接,谁从新实现acceptedMP方法谁决定处理方式

 

这个Listener类既用于处理web请求,也用于处理普通的数据库请求。

OK,如今咱们看web请求是如何处理的。MiniWebServer类继承了Listener类,它从新实现了acceptedMP方法,开始接收TCP流,解析HTTP协议,同时还会负责组装HTTP响应包并发送TCP流到客户端。那么实际完成http请求的类是谁呢?它是继承了MiniWebServer类的DbWebServer类。这个类从新实现了doRequest方法,它会在完整接收到HTTP请求后被调用,HTTP请求的处理过程不在本篇的讨论范围内,这里略过。但咱们清楚了,这个线程采用同步的阻塞的方式处理请求,它意味着它同一时刻只能处理一个web请求,并发能力超级弱,还好web请求只是mongod的副业,仅用于查询状态。

 

六、主监听线程和数据请求的处理线程

处理数据库请求的是上图中的PortMessageServer 类,它运行在主线程中。

咱们先看看PortMessageServer 类是如何实现acceptedMP方法的:

 

 


很清晰,它开启了一个线程独立的执行这个请求。虽然这种方式依然性能极差:大量的进程间上下文切换在等着咱们,但总比web请求处理要好多了,并且mongod的并发能力原本就不是它的长项。

 

对于每一个新链接,都会有类封装成对象,以下:

接下来pms::threadRun方法是在处理MessagingPort对象。

下面看看pms::threadRun方法中作了些什么:

 

 


能够看到,它会在这个链接上接收完整的请求,以后会调用handler的process方法。这个handler又是什么呢?以下图所示:

 

因此,普通的数据库请求是由MyMessageHandler的process方法处理的。SAT这个方法里也只是个封装,真正处理业务的是全局方法assembleResponse。

assembleResponse方法中会按照8种操做方式分别的调用DataFileMgr中的方法处理实际文件,例如:

 

 


在方法中有相似这样的代码在调用实际的业务类处理操做:

 

 

 

固然本篇志不在此,下篇咱们再讨论索引和数据文件的操做。

相关文章
相关标签/搜索