1、mmap:Broker读写c盘文件的核心技术
明天我们要给你们介绍一个极其关键的黑科技mmap linux 文件,好多人可能都不太熟悉,这个技术就是mmap技术,而Broker中就是大量的使用mmap技术去实现CommitLog这些大c盘文件的高性能读写优化的。
通过之前的学习,我们晓得了一点linux多线程,就是Broker对c盘文件的写入主要是利用直接写入oscache来实现性能优化的,由于直接写入oscache,相当于就是写入显存一样的性能,后续等os内核中的线程异步把cache中的数据刷入c盘文件即可。
这么明天我们就要对这个过程中涉及到的mmap技术进行一定的剖析。
2、传统文件IO操作的多次数据拷贝问题
首先我们先来给你们剖析一下,假定RocketMQ没有使用mmap技术,就是使用最传统和基本的普通文件IO操作去进行c盘文件的读写,这么会存在哪些样的性能问题?
答案是:多次数据拷贝问题
首先,假定我们有一个程序,这个程序须要对c盘文件发起IO操作读取他上面的数据到自己这里来,这么会经过以下一个次序:
首先从c盘上把数据读取到内核IO缓冲区里去,之后再从内核IO缓存区里读取到用户进程私有空间里去,之后我们能够领到这个文件里的数据
你们看右图
为了读取c盘文件里的数据,是不是发生了两次数据拷贝?
没错,所以这个就是普通的IO操作的一个隐忧,必然涉及到两次数据拷贝操作,对c盘读写性能是有影响的。
这么假如我们要将一些数据写入到c盘文件里去呢?
那这个就是一样的过程了,必须先把数据写入到用户进程私有空间里去,之后从这儿再步入内核IO缓冲区,最后步入c盘文件里去
我们看下边的图
在数据步入c盘文件的过程中,是不是再一次发生了两次数据拷贝?没错,所以这就是传统普通IO的问题,有两次数据拷贝问题。
3、RocketMQ是怎样基于mmap技术+pagecache技术优化的?
接着我们来看一下,RocketMQ怎样借助mmap技术配合pagecache技术进行文件读写优化的?
首先,RocketMQ底层对CommitLog、ConsumeQueue之类的c盘文件的读写操作,基本上就会采用mmap技术来实现。
假如具体到代码层面,就是基于JDKNIO包下的MappedByteBuffer的map()函数,来先将一个c盘文件(例如一个CommitLog文件,或则是一个ConsumeQueue文件)映射到显存里来
这儿我必须给你们解释一下,这个所谓的显存映射是哪些意思
虽然有的人可能会误以为是直接把这些c盘文件里的数据给读取到显存里来了,类似这个意思,然而并不完全是对的。
由于刚开始你完善映射的时侯,并没有任何的数据拷贝操作,虽然c盘文件还是逗留在哪里,只不过他把化学上的c盘文件的一些地址和用户进程私有空间的一些虚拟显存地址进行了一个映射
我们看下边的图
这个地址映射的过程,就是JDKNIO包下的MappedByteBuffer.map()函数干的事情,底层就是基于mmap技术实现的。
另外这儿给你们说明白的一点是,这个mmap技术在进行文件映射的时侯,通常有大小限制,在1.5GB~2GB之间
所以RocketMQ才让CommitLog单个文件在1GB,ConsumeQueue文件在5.72MB,不会太大。
这样限制了RocketMQ底层文件的大小,就可以在进行文件读写的时侯,很便捷的进行显存映射了。
之后接出来要给你们讲的一个概念,就是之前给你们说的PageCache,实际上在这儿就是对应于虚拟显存
所以我们在下边的图里就给你们画出了这个示意。
4、基于mmap技术+pagecache技术实现高性能的文件读写
接出来就可以对这个早已映射到显存里的c盘文件进行读写操作了,例如要写入消息到CommitLog文件,你先把一个CommitLog文件通过MappedByteBuffer的map()函数映射其地址到你的虚拟显存地址。
接着就可以对这个MappedByteBuffer执行写入操作了,写入的时侯他会直接步入PageCache中,之后过一段时间过后,由os的线程异步刷入c盘中,如右图我们可以看见这个示意。
见到这儿我们有没有发觉哪些问题?
对了!就是前面的图里,虽然只有一次数据拷贝的过程,他就是从PageCache里拷贝到c盘文件里而已!这个就是你使用mmap技术以后,相比于传统c盘IO的一个性能优化。
接着假如我们要从c盘文件里读取数据呢?
这么此时才会判定一下,当前你要读取的数据是否在PageCache里?假如在的话,就可以直接从PageCache里读取了!
例如刚写入CommitLog的数据还在PageCache里,此时你Consumer来消费肯定是从PageCache里读取数据的。
然而若果PageCache里没有你要的数据,这么此时才会从c盘文件里加载数据到PageCache中去,如右图
但是PageCache技术在加载数据的时侯,就会将你加载的数据块的临近的其他数据块也一起加载到PageCache里去。
你们可以看见,在你读取数据的时侯,虽然也仅仅发生了一次拷贝,而不是两次拷贝,所以这个性能相较于传统IO来说,肯定又是提升了。
5、预映射机制+文件预热机制
接着给你们说几个Broker针对上述的c盘文件高性能读写机制作的一些优化:
(1)显存预映射机制:Broker会针对c盘上的各类CommitLog、ConsumeQueue文件预先分配好MappedFile,也就是提早对一些可能接出来要读写的c盘文件,提早使用MappedByteBuffer执行map()函数完成映射,这样后续读写文件的时侯,就可以直接执行了。
(2)文件预热:在提早对一些文件完成映射以后,由于映射不会直接将数据加载到显存里来,这么后续在读取尤其是CommitLog、ConsumeQueue的时侯,虽然有可能会频繁的从c盘里加载数据到显存中去。
所以虽然在执行完map()函数然后redhat linux 9.0下载,会进行madvise系统调用,就是提早尽可能多的把c盘文件加载到显存里去。
通过上述优化,才真正能实现一个疗效,就是写c盘文件的时侯都是步入PageCache的,保证写入高性能;
同时尽可能多的通过map+madvise的映射后预热机制,把c盘文件里的数据尽可能多的加载到PageCache里来,后续对CosumeQueue、CommitLog进行读取的时侯mmap linux 文件,就能尽可能从显存里读取数据。
6、对昨天文章的一点总结
明天我们在之前给你们讲解的PageCache技术基础之上,引入了Broker底层大量采用的mmap技术
实际上在Broker读写c盘的时侯,是大量把mmap技术和pagecache技术结合上去使用的,通过mmap技术降低数据拷贝次数,之后借助pagecache技术实现尽可能优先读写显存,而不是化学c盘。