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

2023-10-21 02:58

本文主要是介绍深入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 loopreturn;}…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 ownershiphandler->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的线程模型与网络框架的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/251466

相关文章

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

Spring 框架之Springfox使用详解

《Spring框架之Springfox使用详解》Springfox是Spring框架的API文档工具,集成Swagger规范,自动生成文档并支持多语言/版本,模块化设计便于扩展,但存在版本兼容性、性... 目录核心功能工作原理模块化设计使用示例注意事项优缺点优点缺点总结适用场景建议总结Springfox 是

Python的端到端测试框架SeleniumBase使用解读

《Python的端到端测试框架SeleniumBase使用解读》:本文主要介绍Python的端到端测试框架SeleniumBase使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全... 目录SeleniumBase详细介绍及用法指南什么是 SeleniumBase?SeleniumBase

一文深入详解Python的secrets模块

《一文深入详解Python的secrets模块》在构建涉及用户身份认证、权限管理、加密通信等系统时,开发者最不能忽视的一个问题就是“安全性”,Python在3.6版本中引入了专门面向安全用途的secr... 目录引言一、背景与动机:为什么需要 secrets 模块?二、secrets 模块的核心功能1. 基

Java中实现线程的创建和启动的方法

《Java中实现线程的创建和启动的方法》在Java中,实现线程的创建和启动是两个不同但紧密相关的概念,理解为什么要启动线程(调用start()方法)而非直接调用run()方法,是掌握多线程编程的关键,... 目录1. 线程的生命周期2. start() vs run() 的本质区别3. 为什么必须通过 st

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

Java中常见队列举例详解(非线程安全)

《Java中常见队列举例详解(非线程安全)》队列用于模拟队列这种数据结构,队列通常是指先进先出的容器,:本文主要介绍Java中常见队列(非线程安全)的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一.队列定义 二.常见接口 三.常见实现类3.1 ArrayDeque3.1.1 实现原理3.1.2