MongoDB源码概述——内存管理和存储引擎

本文涉及的产品
云数据库 MongoDB,通用型 2核4GB
简介: <p>原文地址:<a target="_blank" href="http://creator.cnblogs.com/">http://creator.cnblogs.com/</a></p> <p></p> <div id="cnblogs_post_body" style="margin:0px 0px 30px; padding:0px; word-break:break-wo

原文地址:http://creator.cnblogs.com/

数据存储:

  之前在介绍Journal的时候有说到为什么MongoDB会先把数据放入内存,而不是直接持久化到数据库存储文件,这与MongoDB对数据库记录文件的存储管理操作有关。MongoDB采用操作系统底层提供的内存文件映射(MMap)的方式来实现对数据库记录文件的访问,MMAP可以把磁盘文件的全部内容直接映射到进程的内存空间,这样文件中的每条数据记录就会在内存中有对应的地址,这时对文件的读写可以直接通过操作内存来完成(而不是fread,fwrite之辈).

  这里顺便提一句,MMAP的只是将文件映射到进程空间,而不是直接全部map到物理内存,只有访问到这块数据时才会被操作系统以Page的方式换到物理内存。这部分的管理工作由操作系统完成,对于MongoDB的开发者而言,也是透明的.其实我们所能用的所有函数,包括系统内核里的实现函数,操作的统统都是虚拟内存,也就是每个进程所谓的4GB(32位系统)的虚拟地址空间.物理内存对于用户是不可见的,不可操作的。这也就是为什么MongoDB可以存储比内存更大的数据,但是却不建议热数据超过内存大小的原因。因为热数据大于内存的话,操作系统需要频繁的换入换出物理内存中的数据,会严重影响MongoDB的性能。

clip_image001

32位操作系统进程虚拟内存表图:

clip_image002  使用这种内存管理方式极大的减轻了MongoDB开发者的负担,把大量的内存管理的工作交由操作系统来完成,在写这篇文章的时候我自个儿我总结了下她的特点,可是后面发现有本书上有总结,于是直接贴上来(加了几个下划线),没办法,人家比我总结得好。

• MongoDB’s code for managing memory is small and clean, because most of that work is pushed to the operating system.

• The virtual size of a MongoDB server process is often very large, exceeding the size of the entire data set. This is OK, because the operating system will handle keeping the amount of data resident in memory contained.

• MongoDB cannot control the order that data is written to disk, which makes it

impossible to use a writeahead log to provide single-server durability. Work is

ongoing on an alternative storage engine for MongoDB to provide single-server

durability.

• 32-bit MongoDB servers are limited to a total of about 2GB of data per mongod.

This is because all of the data must be addressable using only 32 bits.

(如果你想了解更多MMAP相关的东东,可以翻阅《Unix网络编程 卷二》的12.2节)

 

  好了,抽象的东西讲述完毕,下面来点硬货!!!

存储源码分析:

  在MongoMMF类的定义(momgommf.h 29)中需要注意一下几个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
void * map( const  char  *filename, unsigned  long  long  &length,  int  options = 0 );
 
//将文件filename以MMAP的方式映射到进程的空间(称之为视图),返回在内存中的首地址
 
//如果文件不存在,会通过mmap_win里的CreateFile创建文件
 
void  flush( bool  sync);
 
//将映射到进程空间的数据Flush到磁盘
 
void * getView()  const
 
//获取视图首地址

  关于这三个方法的内部实现,自然我们可以想到是对操作系统的API的调用,对于不同的操作系统,方法签名以及参数还有变化,在这里我就不罗嗦了,各个系统的API都查得到。所以我们这里也并不会贴出其内部调用的系统API.

  究竟MongoDB是什么时候map数据库文件到内存的呢?又是何时将内存中映射的数据flush到磁盘进行持久化的呢?下面我们来分析一下这两个问题。

 

map数据库文件到内存:

  在我们第一次向一个未创建的数据库插入一条记录时,调用的函数会由如下流程:

1
DataFileMgr::insert()——》Database::allocExtent()——》Database::suitableFile()——》 Database::getFile()——》MongoDataFile::open()——》 MongoMMF::create()

  DataFileMgr::insert()之前有些方法我已经省略了,这个调用流程比较长,但是最终会调用到MongoMMF::create()来创建第一个数据库文件

1
2
3
4
5
6
bool  MongoMMF::create(string fname, unsigned  long  long & len,  bool  sequentialHint) {
         setPath(fname);
         _view_write = map(fname.c_str(), len, sequentialHint ? SEQUENTIAL : 0);
         //如果文件不存在,会通过mmap_win里的CreateFile创建文件,MemoryMappedFile::map方法
         return  finishOpening();
     }

  观察代码后我们发现create方法直接调用了map,而map的内部,就有文件创建功能,创建完后就map到内存了。

  若是向现有数据库插入记录,则在Database构造的期间会调用openAllFiles(),进入上面流程的Database::getFile()部分

  终上所述两种情况,我们明白了MongoDB何时将数据库记录文件map到内存.

Flush数据进行持久化:

  MongoDB中默认每分钟Flush一次进行持久化存储,当然这个间歇可以通过"--syncdelay"启动参数来进行设置.执行流程为main()——》dataFileSync.go()。DataFileSync派生自BackgroundJob,其go()方法会创建一个新的线程来运行虚函数run()。

  Run()最后调用MemoryMappedFile::flushAll方法对所有的映射文件进行flush操作,将更改持久化到磁盘.前面在介绍MongoMMF的时候就介绍过此方法.这里不再累述。

  这里顺便提一句,其实mmap不调用fsync强刷到磁盘,操作系统也是会帮我们自动刷到磁盘的,linux有个dirty_writeback_centisecs参数用于定义脏数据在内存停留的时间(默认为500,即5秒),过了这个timeout时间就会被系统刷到磁盘上。在这个自动刷的过程中是会阻塞所有的IO操作的,如果要刷的数据特别多的话,容易产生一些长耗时的操作,例如有些使用mmap的程序每隔一段时间就会出现有超时操作,一般的优化手段是考虑修改系统参数dirty_writeback_centisecs,加快脏页刷写频率来减少长耗时。mongodb是定时强刷,不会有此问题。

问题的出现:

  弄清楚了MongoDB的存储引擎何时将数据库记录文件map到进程的内存空间以及何时flush到原文件时,不知道您发现了问题没有?持久化的flush过程是每分钟调用一次,而写数据是时时刻刻进行的,若还没有到一分钟,在59秒的时候服务器断电了怎么办?是不是这59秒内对数据库的所有操作都不会提交到持久化的数据库文件?丢失59秒的数据,这还不是最可怕的. 如果在60秒后,在进行flushAll的过程中系统宕机,则会造成数据文件错乱,一部分是新数据,一部分是旧数据,这种情况下,有可能我们的数据库就不能用了。

  不知道为什么,MongoDB在正确的退出流程中(调用dbexit(EXIT_CLEAN)),非"--dur模式启动 也并没有调用MemoryMappedFile::flushAll来进行持久化操作,这令我非常费解.一开始我以为是我这个版本的代码没有完善,立马又查阅了2.2版本的源码,发现也并没有在非"--dur"调用flush方法。都仅仅是调用MemoryMappedFile::closeAllFiles.

我个人的理解是,在生产环境下一定会开启"--dur",甚至在新版本中在64位运行环境下默认开启,所以给非dur模式下来一次flush就不那么必要了.

  如果您在使用MongoDB的windows版本进行调试的以验证我上面的描述的话,您会得到相反的结果,可能你的第一感觉就会是我完全的搞错了。的确,一般的人都会这样认为,我们来进行一次简单的测试流程:

  • 以非"--dur"模式启动Mongod,启动时最好调整一下--syncdelay,设置一个较大值如600
  • 使用mogo对数据库的数据进行修改(如修改删除)
  • 使用任务管理器强制结束进程mongod(模拟系统宕机)
  • 删除掉mongod.lock(模拟宕机一定会留下这个),重新启动非"--dur"模式的Mongod
  • 使用mongo进行db.collectiob.find()观察第一次的更改是否已经生效

  使用上述测试流程,您会惊奇的发现,我们的任何更改都已经持久化了,这样是不是就说明我前面所提到的都是胡扯呢?起初我自己也有点怀疑这个结果,反复的测试了很多遍,并进行了跟踪调试,我发现即便MongoDB没有运行过一次flushAll,并且连任何一个MongoMMF类的对象(代表一个数据库记录文件)也不曾调用flush()方法,所做的更改仍然能被持久化。至此,我开始怀疑Windows上并不是显示调用flush才会持久化,而是memcopy更改时就会被持久化,搜索了一下网上,发现了别人在Windows也遇到了相同的问题.(CSDN上命名为 "内存映射,没有FlushViewOfFile,也可以保存到文件"的贴子也遇到了相同的问题).

  对于Windows这个特例,我也就不再深究了,大家知道是这个地方的问题就OK了,其实在它的这种机制下,整个用于flush数据到磁盘的DataFileSync线程都不用,对于Linux,Unix,我上面的总结还是正确的.

问题的解决:

  事实上曾经有人就是因为上面提到的问题丢失了所有数据,所以MongoDB的团队成员才在1.7版本的最新分支上开始对单机高可靠性的提升,这就是引入的Journal\durability模块,着重解决这个问题。(导火索见文章"MongoDB的数据可靠性,单机可靠性有望在1.8版本后增强“)

  在MongoDB源码概述——日志 一文中也提到这个Journal\durability模块,不过最后还有一部分没有讲完,下次将会有专门的博文介绍后续问题。


博客地址:Zealot Yin

欢迎转载,转载请注明出处[http://creator.cnblogs.com/]


相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
目录
相关文章
|
2月前
|
存储 调度
操作系统基础:内存管理概述【下】
操作系统基础:内存管理概述【下】
|
4月前
|
存储 消息中间件 缓存
读Flink源码谈设计:有效管理内存之道
在最初接触到Flink时,是来自于业界里一些头部玩家的分享——大家会用其来处理海量数据。在这种场景下,`如何避免JVM GC带来StopTheWorld带来的副作用`这样的问题一直盘绕在我心头。直到用了Flink以后,阅读了相关的源码(以1.14.0为基准),终于有了一些答案。在这篇文章里也是会分享给大家。
541 1
|
2月前
|
算法
操作系统基础:内存管理概述【上】
操作系统基础:内存管理概述【上】
|
3月前
|
存储 缓存 Unix
内存学习(一):物理地址空间内存概述
内存学习(一):物理地址空间内存概述
38 0
|
2月前
|
存储 调度
操作系统基础:内存管理概述【中】
操作系统基础:内存管理概述【中】
|
3月前
|
应用服务中间件 nginx
Nginx源码阅读:共享内存ngx_shm_t和它的组织方式ngx_shm_zone_t、ngx_list_t
Nginx源码阅读:共享内存ngx_shm_t和它的组织方式ngx_shm_zone_t、ngx_list_t
24 0
|
3月前
|
存储 网络协议 应用服务中间件
Nginx源码阅读:ngx_palloc 内存池
Nginx源码阅读:ngx_palloc 内存池
55 0
|
3月前
|
存储 监控 NoSQL
【Redis深度专题】「核心技术提升」从源码角度探究Redis服务的内存使用、清理以及逐出等底层实现原理
Redis作为一种高性能的内存NoSQL数据库,其容量受限于最大内存的限制。用户在使用阿里云Redis时,除了对性能和稳定性有较高的要求外,对内存占用也非常敏感。然而,在实际使用中,一些用户可能会发现他们的线上实例的内存占用比预期的要大。
63 1
【Redis深度专题】「核心技术提升」从源码角度探究Redis服务的内存使用、清理以及逐出等底层实现原理
|
4月前
|
存储 分布式计算 资源调度
[hadoop3.x]HDFS中的内存存储支持(七)概述
[hadoop3.x]HDFS中的内存存储支持(七)概述
50 0
|
4月前
|
存储 Java C#
C# 垃圾回收机制(GC) 的概述 资源清理 内存管理
C# 垃圾回收机制(GC) 的概述 资源清理 内存管理