当MongoDB运行在journal开启的状态下, 写操作会在写入磁盘数据文件之前先写入内存和journal文件。本文讨论MongoDB 系统中journaling 的实现和技术细节。更多关于配置、调试、管理journaling的信息见管理Journaling。
(译者注:官方文档并未将journalling机制说清楚,我在白板上画了一幅图来辅助读者理解,在后边的篇幅中会有对这幅图的描述)
Journal 文件
Journaling开启后, MongoDB会在定义好的dbPath路径下创建一个 journal子目录,dbpath路径默认为/data/db。这个目录用来存放journal文件,文件中记录的是write-ahead日志。
(译者注:即WAL,预写式日志。数据库系统在运行时将对数据库的修改写到redo日志中而不是数据文件,然后redo日志通过checkpoint做group commit刷到最终的数据文件里。这是大多数数据库系统采用的做法)
目录下还有一个文件来保存某个最近的序列号。
(译者注:这个文件是journal目录下的lsn文件,里边保存的序列号并非是journal文件的序列号。这个就说来话长了,上边的注释提到写操作在写入数据文件之前会写到journal文件中,而在写入journal文件之前会先写入到一块内存区域,这个内存区域叫private view,官方表示说private view的数据默认每100ms刷到journal文件中,但其实是从private view分批刷到某个临时内存区域,然后从临时内存区域再刷到journal文件,这个刷到临时内存的批次就是lsn文件中保存的序列号)。
一次正常的退出会删除journal目录下所有文件,而一次非正常退出(比如崩溃)则不会;重启mongod进程时会根据journal下的文件来恢复数据以达到数据一致。
Journal文件是append-only文件,文件名以j._为前辍。当超过1G时,MongoDB会创建一个新的文件。一旦某个journal文件的数据全部刷到数据文件之后,MongoDB会删掉这个journal文件,因为它起不到恢复系统的作用了。除非你的系统每秒写入大量数据,否则通常情况下journal目录应该只有两三个文件。
如果你愿意的话,可以在启动mongod进程时设置storage.smallFiles属性,它会将journal文件大小限制到128M。
如果要提高journal文件非常频繁的顺序写入性能,你可以将journal目录放置在和数据文件不同的文件系统下。
重要: 如果将journal和数据文件放置在不同的文件系统中,将不能使用单独使用文件系统快照来备份dbPath目录下的文件。在这种场景下,先使用fsyncLock()来确保数据文件一致性,等快照生成完毕之后使用fsyncUnlock()来释放锁定。 |
注意: 根据你的文件系统的不同,开启journaling第一次启动mongod进程时系统可能会有点滞后,因为要为journal文件预分配空间。 如果mongod进程认为预分配journal文件比在需要时再去创建文件更高效,MongoDB会事先将它分配好。预分配文件的时间可能会持续几分钟,在这段时间内是连接不了数据库的。这是一次性的行为,在以后的调用中不会发生。 |
如果要避免预分配带来的滞后,参见官方文档 Avoid Preallocation Lag。
Journaling中的存储视图
整个Journaling机制中有了3个内部存储视图来服务于MongoDB。
shared view存储修改后的数据然后刷到磁盘的数据文件中。shared view是唯一一个能够直接访问数据文件的视图。当运行时journaling开启,mongod进程会让操作系统将磁盘上所有的数据文件映射到shared view虚拟内存。操作系统只映射文件不会加载真实数据。只有在需要时才会将数据加载到shared view中。
private view中的数据用来响应读操作。private view是一个接收MongoDB写操作的地方。当journal提交后,MongoDB将private view的更改复制到shared view, 这些更改最终通过shared view刷到磁盘数据文件中。
Journal文件是一个基于磁盘的视图,在数据改动刷到磁盘数据文件之前它用来存储private view接收到的写操作。Journal为数据库提供了健壮性,如果 mongod进程在将数据写入到磁盘数据文件之前崩溃了,下次启动时会回放 journal中的写操作到shared view,并最终会将改动刷到磁盘数据文件中。
译者注:官方文档对视图的描述比较简单,这里我对整个持久化流图做了一下梳理,也是对文章开头我画的图的解释。 ① Mongodb启动时将数据文件映射到shared view,这是内存映射,并非加载所有数据。 ② Shared view映射到private view。 ③ Private view通过group commit将客户端的写操作写入到journal文件,其实这中间还经历了一次aligned buffer,官方所说的group commit其实是从aligned buffer到journal文件。 ④ Jaournal文件将写操作回放给shared view。 ⑤ Shared view将数据改动刷到磁盘数据文件。 ⑥ Shared view重新映射到private view。 |
Journaling如何记录写操作
MongoDB将写操作按批次复制到journal文件, 这种方式称之为批量提交。将数据按照“批量提交” 可以有效的减少journaling机制的性能开销,因为每次提交发生时都会阻塞住所有写操作。批量提交的默认时间间隔见commitIntervalMs参数。
Journaling存储的都是原生操作,MongoDB能够利用它们来重放以下操作:
-
文档的插入/更新
-
索引的修改
-
对命名空间元数据进行修改
-
创建和删除数据库,以及一些相关数据文件
当一个写操作发生时,MongoDB将数据写入内存中的private view,然后从private view将写操作分批复制到到journal文件。Journal文件存储在磁盘上以保证健壮性。每条journal数据都描述了写操作让数据文件发生变化的具体地址。
接下来MongoDB将journal中的写操作提交到shared view中,这时shared view和数据文件中的数据会不一致。
默认情况下每隔60秒,MongoDB通过操作系统将shared view的数据改动刷到磁盘上,这样使得最新的写操作能体现在数据文件中。有时操作系统刷磁盘的间隔会超过60秒,特别是在系统空闲内存比较少的时候。
当MongoDB将数据往磁盘上刷时,会记住这些刷过的数据。一旦某个journal文件上记录的所有操作都被刷到数据文件中后,这个文件就再也起不到恢复数据的作用了,MongoDB会删除这个文件,也可能回收它用作一个新的journal文件。
做为整个journaling机制的一部分, MongoDB会照常请求操作系统将shared view重新映射到private view,这样是为了节约物理内存。基于一次新的重映射,操作系统会将物理内存页共享给shared view和private view。
(译者注:shared view初始映射到private view时,private view是只读的,当写操作进来时,mongodb将private view中映射数据的所在页变更为可写,然后从数据文件复制一份真实数据的拷贝,并将写操作的数据写入进来。只有在这时private view才会单独的去消耗内存,初始映射的时候是没有内存消耗的,所以官方说初始时是共享物理内存页)
注意: shared view和磁盘数据文件之间的这部分交互和不使用journaling时大致是一样的, 同样都是MongoDB每隔60秒请求操作系统将内存中的数据改动刷到数据文件中。
|