深入mongoDB(1)--mongod的线程模型与网络框架
最近工作需要开始研究mongoDB,我准备从其源代码角度,对于mongod和mongos服务的架构、sharding策略、 replicaset策略、数据同步容灾、索引等机制做一个本质性的了解。其代码约20万行(我研究的是 2.0.6版本源码),本篇先从mongod的 启动流程说起,它本是一个多线程程序,所以本文在于说明mongod有多少个线程,每个线程的意义所在。希望大家阅读本文时关注在mongod的外围框 架,暂不涉及数据文件的组织、索引B树的组织等,仅focus in在网络框架、线程模型上。
弄清楚这点的好处很明显:之后就可以有的放矢的研究mongod某个模块究竟是如何实现的,可以快速的跳到相应的类中阅读源码,解决我们在产品中的实际问题。我认为这是研究其庞大源码一个好的开始。
在说明mongod前,须了解mongoDB大量代码是基于boost库构建的,因此这里先行对boost库建立线程做个简单的了解。
1、boost库如何建立线程
boost::thread是boost中跨平台的多线程库,mongoDB创建线程时大多数情况下是使用thread库的(少量情况直接调用pthread_create方法),主要使用了以下两种方式:
(1)直接运行让线程运行func
例如durThread线程:
void durThread() {
while( !inShutdown() ) { ... }
}
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 ) );
}
};
2、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事件的线程是怎样处理请求的,最后讨论性能稍好一点的主服务线程是怎样处理请求的。
3、5个基于BackgroundJob类实现的工作线程
这5个线程分别是DataFileSync,SnapshotThread, ClientCursorMonitor, TTLMonitor, PeriodicTask,类图如下所示:
上面这5个类也是用boost::threadfunction方法创建线程运行的,它们继承了BackgroundJob类,使用go方法启动线程执行jobBody就是在启动线程执行run方法,如下所示:
- BackgroundJob& BackgroundJob::go() {
- boost::thread t( boost::bind( &BackgroundJob::jobBody , this, _status ) );
- return *this;
- }
- void BackgroundJob::jobBody( boost::shared_ptr<JobStatus> status ) {
- ...
- run();
- ...
- }
这些线程的意义如下:
DataFileSync
主要在调用MemoryMappedFile::flush方法将内存中的数据刷到磁盘上。
我们知道,mongodb是调用mmap把磁盘中的数据映射到内存中的,所以必须有一个机制时刻的刷数据到硬盘才能保证可靠性,多久刷一次是与
syncdelay参数相关的。
SnapshotThread将生成快照文件帮助快速恢复。
ClientCursorMonitor将管理用户的游标,每4秒调用一次idleTimeReport()方法,每一分钟调用sayMemoryStatus()方法。
TTLMonitor管理TTL,通过调用doTTLForDB()方法检查所有db。
PeriodicTask将从动态数组std::vector<PeriodicTask* > _tasks中获取周期性任务执行。
4、5个直接提供全局方法执行的线程
FileAllocator用于分配新文件,它决定分配文件的大小,例如用翻倍的方式。
interruptThread只处理信号量。
durThread做批量提交和回滚工作。
replSlaveThread是当前结点作为secondary时的同步线程。
replMasterThread是当前结点作为master时的同步线程。
5、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的副业,仅用于查询状态。
6、主监听线程和数据请求的处理线程
处理数据库请求的是上图中的PortMessageServer 类,它运行在主线程中。
我们先看看PortMessageServer 类是如何实现acceptedMP方法的:
- virtual voidacceptedMP(MessagingPort * p) {
- if ( !connTicketHolder.tryAcquire() ) {
- sleepmillis(2); // otherwisewe'll hard loop
- return;
- }
- …
- int failed =pthread_create(&thread, &attrs, (void*(*)(void*)) &pms::threadRun,p);
- …
- }
很清晰,它开启了一个线程独立的执行这个请求。虽然这种方式依然性能极差:大量的进程间上下文切换在等着我们,但总比web请求处理要好多了,而且mongod的并发能力本来就不是它的长项。
对于每个新连接,都会有类封装成对象,如下:
接下来pms::threadRun方法是在处理MessagingPort对象。
下面看看pms::threadRun方法中做了些什么:
- void threadRun( MessagingPort *inPort) {
- TicketHolderReleaserconnTicketReleaser( &connTicketHolder );
- Message m;
- try {
- LastError * le = newLastError();
- lastError.reset( le ); //lastError now has ownership
- handler->connected( p.get());
- while ( ! inShutdown() ) {
- if ( ! p->recv(m) ) {
- p->shutdown();
- break;
- }
- handler->process( m ,p.get() , le );
- }
- }
- handler->disconnected( p.get());
- }
可以看到,它会在这个连接上接收完整的请求,之后会调用handler的process方法。这个handler又是什么呢?如下图所示:
所以,普通的数据库请求是由MyMessageHandler的process方法处理的。这个方法里也只是个封装,真正处理业务的是全局方法assembleResponse。
assembleResponse方法中会按照8种操作方式分别的调用DataFileMgr中的方法处理实际文件,例如:
- enum Operations {
- opReply = 1, /* reply. responseTo is set. */
- dbMsg = 1000, /* generic msg command followed by a string */
- dbUpdate = 2001, /* update object */
- dbInsert = 2002,
- //dbGetByOID = 2003,
- dbQuery = 2004,
- dbGetMore = 2005,
- dbDelete = 2006,
- dbKillCursors = 2007
- };
在方法中有类似这样的代码在调用实际的业务类处理操作:
- else if ( op == dbInsert ) {
- receivedInsert(m, currentOp);
- }
- else if ( op == dbUpdate ) {
- receivedUpdate(m, currentOp);
- }
- else if ( op == dbDelete ) {
- receivedDelete(m, currentOp);
- }
当然本篇志不在此,下篇我们再讨论索引和数据文件的操作。
深入mongoDB(1)--mongod的线程模型与网络框架的更多相关文章
- 看我是如何处理自定义线程模型---java
看过我之前文章的园友可能知道我是做游戏开发,我的很多思路和出发点是按照游戏思路来处理的,所以和web的话可能会有冲突,不相符合. 来说说为啥我要自定义线程模型呢? 按照我做的mmorpg或者mmoar ...
- HBase的Write Ahead Log (WAL) —— 整体架构、线程模型
解决的问题 HBase的Write Ahead Log (WAL)提供了一种高并发.持久化的日志保存与回放机制.每一个业务数据的写入操作(PUT / DELETE)执行前,都会记账在WAL中. 如果出 ...
- Netty学习三:线程模型
1 Proactor和Reactor Proactor和Reactor是两种经典的多路复用I/O模型,主要用于在高并发.高吞吐量的环境中进行I/O处理. I/O多路复用机制都依赖于一个事件分发器,事件 ...
- Mina、Netty、Twisted一起学(十):线程模型
要想开发一个高性能的TCP服务器,熟悉所使用框架的线程模型非常重要.MINA.Netty.Twisted本身都是高性能的网络框架,如果再搭配上高效率的代码,才能实现一个高大上的服务器.但是如果不了解它 ...
- WPF QuickStart系列之线程模型(Thread Model)
这篇博客将介绍WPF中的线程模型. 首先我们先来看一个例子,用来计算一定范围内的素数个数. XAML: <Grid> <Grid.RowDefinitions> <Row ...
- servlet的生命周期与运行时的线程模型
第 14 章 生命周期 注意 讲一下servlet的生命周期与运行时的线程模型,对了解servlet的运行原理有所帮助,这样才能避免一些有冲突的设计. 如果你不满足以下任一条件,请继续阅读,否则请跳过 ...
- eventloop & actor模式 & Java线程模型演进 & Netty线程模型 总结
eventloop的基本概念可以参考:http://www.ruanyifeng.com/blog/2013/10/event_loop.html Eventloop指的是独立于主线程的一条线程,专门 ...
- 理解RxJava线程模型
RxJava作为目前一款超火的框架,它便捷的线程切换一直被人们津津乐道,本文从源码的角度,来对RxJava的线程模型做一次深入理解.(注:本文的多处代码都并非原本的RxJava的源码,而是用来说明逻辑 ...
- Hbase WAL线程模型源码分析
版权声明:本文由熊训德原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/257 来源:腾云阁 https://www.qclo ...
随机推荐
- HBase-配置说明
转载自:http://www.aboutyun.com/thread-7914-1-1.html hbase.rootdir这个目录是region server的共享目录,用来持久化Hbase.UR ...
- 对x264_macroblock_cache_load的理解
X264版本: 2004/06/03 函数作用: 将编码该宏块所需的信息加载到mb.pic.mb.cache两个结构体中,记录相邻宏块的存在性. 函数过程: 初始化坐标信息,这些坐标信息将在下面用作下 ...
- LeetCode 342
Power of Four Given an integer (signed 32 bits), write a function to check whether it is a power of ...
- [改善Java代码]避免在构造函数中初始化其他类
建议35: 避免在构造函数中初始化其他类 构造函数是一个类初始化必须执行的代码,它决定着类的初始化效率,如果构造函数比较复杂,而且还关联了其他类,则可能产生意想不到的问题,我们来看如下代码: publ ...
- 【记忆化搜索】Codeforces Round #295 (Div. 2) B - Two Buttons
题意:给你一个数字n,有两种操作:减1或乘2,问最多经过几次操作能变成m: 随后发篇随笔普及下memset函数的初始化问题.自己也是涨了好多姿势. 代码 #include<iostream> ...
- 踩到两只“bug”
近期在修复ex和头儿的代码时,碰到两个特别点的bug,其实也不能称之为bug,非常简单的用法,稍不严谨点可能就出错了. 第一个是in_array,大家都知道功能是检查一个值是否在数组中,第三个参数传入 ...
- ASP.NET运行原理
1,ASP.NET运行原理: 客户端向服务器发出请求 → 服务器处理请求→ 处理好的数据以报文发给浏览器 → 浏览器显示请求结果 2,Chrome浏览器,查看请求过程:F12打开浏览器的调试窗口: 3 ...
- Linux 命令 - mv: 移动或重命名文件
命令格式 cp [OPTION]... [-T] SOURCE DEST cp [OPTION]... SOURCE... DIRECTORY cp [OPTION]... -t DIRECTORY ...
- 每天一道LeetCode--206. Reverse Linked List
Reverse a singly linked list. package cn.magicdu; import cn.magicdu.extra.ListNode; public class _20 ...
- Xcode6:The file couldn’t be opened because you don’t have permission to view it
最近为了兼容iOS8升级到Xcode6.0编译之前的工程,结果App无法在真机上运行.报错如下: The file “xxxx.app” couldn’t be opened because you ...