Journal工作原理

[title]Journal工作原理[/title]

journal文件在MongoDB中的作用相当于redo日志文件在oracle中的作用,它可以在即使服务器意外宕机的情况下,将数据库操作进行重演。
第一次启动服务前,通常磁盘上是没有journal file的,这时mongodb就会现在磁盘上为journal文件分配磁盘空间,这个过程会花比较长的时间,在这段时间内服务是不可用的。如果想避免这个预分配动作也是可以的,就是从别的mongodb实例中拷贝一个已经预分配的文件,然后放到自己的journal路径中,这个预分配文件是不含数据的,因此是这种操作方式是安全的。
默认情况下mongodb每100毫秒往journal文件中flush一次数据,不过这是在数据文件和journal文件处于同一磁盘卷上的情况,而如果数据文件和journal文件不在同一磁盘卷上时,默认刷新输出时间是30毫秒。不过这个毫秒值是可以修改的,可修改范围是2~300,值越低,刷新输出频率越高,数据安全度也就越高,但磁盘性能上的开销也更高。
journal文件是以“j._”开头命名的,且是append only的,如果1个journal文件满了1G大小,mongodb就会新创建一个journal文件来使用,一旦某个journal文件所记载的写操作都被使用过了,mongodb就会把这个journal文件删除。通常在journal文件所在的文件夹下,只会存在2~3个journal文件,除非你使用mongodb每秒都写入大量的数据。而使用 smallfiles 这个运行时选项可以将journal文件大小减至128M大小。lsn文件保存最后使用的journal序列号,是个二进制文件,它实际保存的是系统启动后到现在的一个时间戳。prealloc.x代表还未使用的初始化的journal文件。使用db.shutdownServer()和kill -2关闭的系统,也就是clean shutdown,journal文件夹下除prealloc.*文件 都会被删除。 如果系统掉电或者运行时死机,再启动时,mongo就会使用journal进行恢复,不用运行repair。
Journal的工作原理:
开启journal的系统中,写操作从请求到写入磁盘共经历5个步骤,在serverStatus()中已经列出各个步骤消耗的时间。

①、Write to privateView

②、prepLogBuffer

③、WritetoJournal

④、WritetoDataFile

⑤、RemaptoPrivateView

 

1、preplogbuffer:

Private view(PV) 中的数据并不是直接刷新到journal文件,而是通过一个中间内存块(journalbuffer,或者aligned buffer)一部分一部分的刷新到journal,这样可以提高并发。preplogbuffer即是将PV中的数据写入到aligned buffer中的过程。这个过程有两部分,basic write 操作和非 basic write操作(e.g.create file)。一次preplogbuffer是以一个commitJob为一个单位,可能会有很多个commitJob写入到aligned buffer,然后提交。一个commitJob中包含多个basic write 和非basic write 操作,basic write是存在Writeintent结构体中的,Writeintent记录了写操作的地址信息。非basic write 操作存在一个vector中

Aligned buffer 有自己的结构,这也是写入到journalfile中的结构。包含Jheader,JsectHeader lsn,Durop,JSectFooter:

每个JsectHeader之间的Durop是属于一个事务范围,一起提交,一起成功,一起失败,即all-or-nothing.上篇文章中介绍的lsn文件,就是记录这个lsn号。

2、WritetoJournal:

writetoJournal操作是将alignedbuffer刷新到JournalFile的过程。默认100ms刷新一次,由–journalCommitInterval 参数控制。writetoJournal会做一些checksum验证,将alignedbuffer进行压缩,然后将压缩过后的alignedbuffer写入到磁盘中的journal文件中。写入磁盘后将删除已经满的Journal文件,更新lsn号到lsn文件。写操作到这一步就是安全的了,因为数据已经在磁盘上,如果使用getlasterror(j=true),这一步即可返回。

3、WritetoDataFile:

WritetoDataFile是将未压缩的aligned buffer写入到shared view的过程,然后由操作系统刷新到磁盘文件中。WritetoDataFile首先会对aligned buffer进行严格的验证,确保没有改变过,然后解析aligned buffer,通过memcpy函数拷贝到shareview

4、RemaptoprivateView:

RemaptoprivateView会将持久化的数据重新映射到PV,以减小PV的大小,防止它不断扩大,按照源码上说,RemaptoprivateView会两秒钟重新映射一次,大约有1000个view,不是一次全做完,而是一部分一部分的做。由于读操作是读取PV,所以在映射完成之后会有短暂的时间读取磁盘。经过这四步,一个写操作就完成了.

首先在这个原理中,存在着两个file,两个view。两个file是 data file 和 journal file,两个view是 shared view 和 private view。两个file是对磁盘而言的,而两个view是对内存而言的.下面以图解的方式解释:

启动服务前:

图片[1]-Journal工作原理-吾爱博客

启动服务后,MongoDB请求操作系统将Data file映射到Shared view,此时操作系统只管映射这个动作,并不将数据加载到Shared view中,而是由MongoDB在需要时再将数据进行加载到Shared view。

图片[2]-Journal工作原理-吾爱博客

然后,MongoDB再请求操作系统将Shared view映射到Private view,之后MongDB对数据的读写操作都是直接操作的Private view:

图片[3]-Journal工作原理-吾爱博客

如果发生了写操作:

图片[4]-Journal工作原理-吾爱博客

Private view变脏以后,根据journalCommitInterval的设置,将在一定时间后将写操作往Journal file中复制,这个过程称为“group commit”:

图片[5]-Journal工作原理-吾爱博客

Journal file中记录的是原生的操作(raw operation),这些原生的操作可以使MongoDB完成以下操作:
对文档的插入/更新(document insertion/updates)
对索引的修改(index modifications)
对命名空间文件的修改(changes to the namespace files)
这些原生操作告诉了Journal file数据变化发生在Data file的什么位置。至此,MongoDB上发生的写事件可以被认为是安全的了,因为这些写操作已经被记录在了Journal file上,即使服务器掉电了,在下次启动MongoDB时,Journal file上的写操作将会被重演。
接下来,Journal file中记录的写操作会应用在Shared view上:

图片[6]-Journal工作原理-吾爱博客

默认每隔60秒,MongoDB请求操作系统将Shared view刷新输出到Data file:

图片[7]-Journal工作原理-吾爱博客

数据就被写入到数据文件了。这时MongoDB还会将Journal file中已输出到Data file的写操作删除掉(由于MongoDB在将Journal file中写操作放到Shared view时,是通过了一个前指针和一个后指针来操作的,所以MongoDB知道哪些写操作是被放到Shared view了的,哪些没有)。
最后,MongoDB还会例行地如一开始一样,将Shared view映射到Private view,以保持一致性(也是防止Private view变得太过于脏了)。

图片[8]-Journal工作原理-吾爱博客

查看journal运行情况

db.serverStatus()

 

commits:在journalCommitInterval时间内提交的操作数。

journaledMB:在journalCommitInterval时间内写到journal文件中的数据量 。

writeToDataFilesMB:在journalCommitInterval时间内从journal刷新到磁盘的数据量 。

compression:v>2.0,表示客户端提交写入到journal的数据的压缩比率,注意,写入到journal的数据并不是全部的数据。( journaled_size_of_data / uncompressed_size_of_data ) 。

commitsInWriteLock:在有写锁的情况下提交的数量,这表示写的压力很大。

earlyCommits:表示在journalCommitInterval之前的时间,mongod请求提交的次数。用这个参数确定journalCommitInterval是不是设置的过长。

dur.timeMS.prepLogBuffer:从privateView映射到Logbuffer的时间。

dur.timeMS.writeToJournal:从logbuffer刷新到journalfile 的时间。

dur.timeMS.writeToDataFiles:从journalbuffer映射到MMF,然后从MMF刷新到磁盘的时间,文件系统和磁盘会影响写入性能。

dur.timeMS.remapPrivateView:重新映射数据到PrivateView的时间,越小性能越好。这个之后会介绍,这也是为什么journal会使用更多内存的原因,因为journal会另外使用一个叫PrivateView的内存区域

温馨提示: 本文最后更新于2021-04-22,至今已有1340天,某些文章具有时效性,若有错误或已失效,请在下方留言
© 版权声明
THE END
喜欢就支持一下吧❀
点赞0投币 分享