RocketMQ的存储涉及中,很大一部分是基于Kafka的涉及进行优化的。
PageCache
现代操作系统内核被设计为按照Page读取文件,每个Page默认4KB,
因为程序一般符合局部性原理,所以操作系统在读取一段文件内容时,会将该段内容和附件的文件内容都读取到内核Page中(预读),下次读取的内容如果命中PageCache就可以直接返回内容,不用再次读取磁盘
PageCache机制也不是完全无缺点的,当遇到操作系统进行脏回写、内存回收、内存交换等情况,就会引起较大的消息读写延迟。对于这些情况,RocketMQ采用了多种优化技术,比如内存预分配、文件预热、
mlock系统调用等,以保证再最大限度地发徽PageCache机制的优点的同时,尽可能地减少消息读写延迟。所以在生产环境部署RocketMQ的时候,尽量采用SSD独享磁盘,这样就可以最大限度地保证读写性能
Virtual Memory(虚拟内存)
为了保证每个程序有足够地运行空间和编程空间,可以将一些暂时不用的内存数据保存到交换区(其实是磁盘)中,这样就可以运行更多的程序,这种"内存"被称为虚拟内存(因为不是真的存在)
操作系统的可分配内存大小=虚拟内存大小+物理内存大小
零拷贝和Java文件映射
从文件读取流程可以看到,读取到内核态的数据会经历两次拷贝,第一次从内核态内存拷贝到用户态内存,第二次从用户态内存拷贝到Java进程的某个变量地址,这样Java变量才能读取数据。
为了提高读写文件的效率,IBM实现了零拷贝技术,它是世界上最早实现该技术的公司,后来各个厂商(如甲骨文等)也纷纷实现了该技术。
java.nio.MappedByteBuffer.java文件中实现了零拷贝技术,即Java进程映射到内核态内存,原来内核态内存与用户态内存的互相拷贝过程就消失了。在消息系统中,用户关心的往往都是最新的数据,理论上,基本的操作都在PageCache中,PageCache的操作速度和内存基本持平,所以速度非常快。当然,也存在读取历史消息而历史消息不再PageCache
中的情况,比如在流处理和批处理中,经常将消费重置到历史消息位点,以重新计算全部结果。这种情况只是在第一次拉取消息时会读取磁盘,以后可以利用磁盘预读,几乎可以做到不再直接读取磁盘,其性能与利用PageCache相比,只在第一次有差异