真正的大师永远怀着一颗学徒的心

引言

一款性能强劲的​​MQ​​​中间件不仅可以处理数以万计的客户端连接,同时在持久化上面也表现优异才能够实现千万级别消息吞吐量。今天就和大家一起看下阿里巴巴开源的​​RocketMQ​​消息中间件是如何实现高性能读写的。

RocketMQ高手之路系列之十一:RocketMQ如何实现高性能读写_用户态

系列文章

​RocketMQ系列文章​

传统IO读写

在分析​​RocketMQ​​​高性能读写之前,我们先来看下传统的​​IO​​数据交互方式是怎样的?

RocketMQ高手之路系列之十一:RocketMQ如何实现高性能读写_用户态_02

从上图可知,一个看似简单的读数据、写数据的常规操作总共经历了以下的过程:

1、用户程序通过​​read()​​方法(实际会转化为CPU指令)向系统发起系统调用,此时系统上下文从用户态切换为内核态;

2、从硬盘中拷贝数据到内核态中的读缓冲区;

3、​​CPU​​通过调度把内核态读缓冲区的文件数据拷贝到用户态的用户缓冲区中,同时系统上下文切换为用户态;

4、经过逻辑处理后,用户程序需要将处理后的数据通过​​write()​​方法进行系统调用,此时系统上下文切换为内核态;

5、​​CPU​​通过调度将用户缓冲区的数据拷贝到​​socket​​缓冲区中;

6、将​​socket​​缓冲区中的数据拷贝至网卡​​io​​,系统上下文切换为用户态。

内核主要的作用总结起来就是对硬件来说进行统一对硬件管理,对上层应用来说提供统一对系统调用。由于硬件资源很敏感需要被保护起来,那么内核就起到了用户程序与硬件交互的桥梁作用。通过区分内核空间和用户空间的设计,隔离了操作系统代码与应用程序代码。

通过以上的分析我们可以看出来,传统的​​IO​​交互不仅有用户态内核态的多次切换,同时数据也是来回的复制,极其的繁琐以及耗费资源。

RocketMQ高性能读写的秘密

上文我们已经分析过传统的​​IO​​​交互模型中涉及多次的用户态与内核态的切换以及多次的数据复制,交互性能不高,在大量文件读写的场景下这种交互方式必定会成为消息中间件的性能瓶颈。因此如果需要克服传统文件​​IO​​​的缺点,必定要从减少数据复制等方面进行入手。那么​​RocketMQ​​到底是通过什么手段来操作的呢?

​RocketMQ​​​实现高性能读写,最主要的是其​​Broker​​​针对持久化文件​​CommitLog​​​以及​​ConsumerQueue​​​进行高性能读写操作的。所谓的高性能实际是利用了​​mmap​​​技术以及操作系统的​​PageCache​​来实现的,下面我们来具体分析下。

​PageCache​​​是操作系统对于文件的缓存机制,主要用于加速文件的读写,提升文件读写效率。​​PageCache​​​其实是操作系统的一部分的内存,如果一次读取文件时出现未命中​​PageCache​​​的情况,操作系统会从物理磁盘上访问加载读取新的文件的同时,会顺序对其他相邻块的数据文件进行预读取。这样只要下次访问的文件已经被加载至​​PageCache​​时,读取操作的速度基本等于访问内存,所以其访问性能很高。

那​​mmap​​​技术又是一个什么鬼呢?​​mmap​​​ 把磁盘文件直接映射到用户空间里的虚拟内存,这样就省去了从内核缓冲区复制到用户空间的过程,文件中的位置在虚拟内存中有了对应的地址,可以像操作内存一样操作这个文件,相当于已经把整个文件放入内存,但在真正使用到这些数据前却不会消耗物理内存,也不会有读写磁盘的操作。实现文件物理地址和进程虚拟地址的一一映射关系。​​RocketMQ​​​主要通过​​JDK NIO​​​包下的​​MappedByteBuffer.map()​​函数来进行映射实现。

RocketMQ高手之路系列之十一:RocketMQ如何实现高性能读写_新星计划_03

以上分析可知,​​mmap​​通过映射技术直接将磁盘文件映射到用户空间的内存中去,这样就减少了内核态到用户态的数据复制,提升了数据处理的效率。

1、数据写入

当​​Broker​​进行数据写入的时候,举个例子,⽐如​​Broker​​要写⼊消息到​​CommitLog​​⽂件中,​​Broker​​首先将⼀个​​CommitLog​​⽂件通过​​MappedByteBuffer​​的​​map()​​函数映射其地址到操作系统的虚拟内存地址中。接着就可以对这个​​MappedByteBuffer​​执⾏写⼊操作了,写⼊的时候他会直接进⼊​​PageCache​​中。然后过⼀段时间之后,由操作系统的线程异步刷⼊磁盘中,如下图所示。因此数据写入的时候是和内存进行交互的,因此数据写入效率非常高。

RocketMQ高手之路系列之十一:RocketMQ如何实现高性能读写_数据_04

2、数据读取

​RocketMQ​​在进行数据读取的时候,首先从​​PageCache​​中进行数据获取。如果此时​​PageCache​​中有需要的数据则直接进行进行读取。如果​​PageCache​​中没有需要的数据,那么需要将磁盘中的文件拷贝到​​PageCache​​中。另外需要注意的是加载数据的时候,如​​CommtLog​​文件都是进行顺序写入的,会将相邻的数据一同进行加载,提升数据读取的效率。

RocketMQ高手之路系列之十一:RocketMQ如何实现高性能读写_用户态_05

总结

本文首先分析了传统​​IO​​​操作的存在的效能问题,从而引出​​RocketMQ​​​如何利用​​MMAP​​​以及​​PageCache​​​来实现高性能的文件读写。后续再继续分析​​RocketMQ​​如何保证消息数据可靠持久化。


我是慕枫,感谢各位小伙伴点赞、收藏和评论,文章持续跟新,我们下期再见!


微信搜索:慕枫技术笔记,优质文章持续更新,我们有学习打卡的群可以拉你进,一起努力冲击大厂,另外有很多学习以及面试的材料提供给大家。
RocketMQ高手之路系列之十一:RocketMQ如何实现高性能读写_java_06