RocketMQ、Kafka 为什么吞吐量那么高?
为什么用普通的 I/O 流传输文件的效率就那么低?
这就需要说到零拷贝和内存映射文件
传统的 I/O 模式
传统的 I/O 模式读取数据使用 read() 命令,当进程需要读取某个文件时,会调用 read() 函数, CPU 在读取到 read() 函数之后会将该进程阻塞,然后转变为内核态,DMA 接收到请求之后就会开始从磁盘中读取对应的文件并拷贝到内核缓冲区,此时 CPU 会转变为 用户态,去处理其他进程,当 DMA 拷贝完成之后,会将信息传递给 CPU 引发中断,CPU 会转变为内核态,将对应的文件拷贝到用户内存中,随后转变为用户态,开始继续执行对应进程的任务
这样子一次 进程的 I/O 调用就需要 拷贝至少 2 次,CPU 变态 至少两次,还不考虑要将此文件发送出去,调用其他 I/O 设备的此数
对于 CPU 这个数据处理怪兽来说,让他一直干搬运的活实在是降低了 CPU 的性能和价值,于是我们迫切需要一个办法,不让 CPU 持续地为这类搬运的工作消耗性能,提高 CPU 的吞吐量
什么是零拷贝
零拷贝并非就是不拷贝了,而是指数据放置在内核缓冲区不被拷贝进用户内存了,相对的能够让进程能够调用到对应的数据需要使用 内存映射文件
什么是内存映射文件
内存映射,简单来说就是内存中的用户空间和内核空间的逻辑地址都指向同一片物理地址
内存映射,即在进程需要对应数据时先将对应的数据标注为已有,如果进程想要调用对应的数据就会引发缺页错误从而使 OS 中断 CPU 然后从磁盘中获取对应的数据放入内核缓冲区,而用户内存中的映射则表示进程可以直接通过映射修改内核缓冲区中的数据,牺牲了一部分的安全性(不影响使用)但是提升了巨大的性能,CPU 也不需要进行数据拷贝这一个浪费性能的活动了
Java NIO
直观感受一下 Java 使用 BIO 和 NIO 之间的速度区别
BIO
import java.io.*;
import java.util.Date;
public class BIORead {
public static void main(String[] args) {
Long start = new Date().getTime();
File file = new File("./publicFile/video.mp4");
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[1024 * 4];
int bytesRead;
long total = 0;
while ((bytesRead = fis.read(buffer)) != -1) {
total += bytesRead;
}
Long end = new Date().getTime();
System.out.printf("BIO 读取 %d 字节,耗时 %d ms\n", total, end - start);
} catch (Exception e) {
e.printStackTrace();
}
}
}
耗时:
NIO
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Date;
public class NIODirectRead {
public static void main(String[] args) {
Long start = new Date().getTime();
try (RandomAccessFile file = new RandomAccessFile("./publicFile/video.mp4", "r");
FileChannel channel = file.getChannel()) {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 4); // 直接缓冲区
long total = 0;
while (channel.read(buffer) != -1) {
buffer.flip();
// 处理数据...
buffer.clear();
total += buffer.position();
}
Long end = new Date().getTime();
System.out.printf("NIO 直接缓冲区读取 %d 字节,耗时 %d ms\n", total, end - start);
} catch (Exception e) {
e.printStackTrace();
}
}
}
耗时
Java BIO使用传统的流式I/O,每次读取都涉及用户态和内核态的上下文切换,并且数据需要从内核缓冲区复制到用户缓冲区。
Java NIO的内存映射文件通过mmap()系统调用,将文件直接映射到进程的虚拟地址空间。这使得:
- 首次访问触发缺页中断,操作系统将文件内容加载到页面缓存
- 后续访问直接在用户空间进行,无需系统调用
- 适合大文件的顺序或随机访问
- 修改数据后可以同步回磁盘
对于顺序读取小文件,BIO和NIO性能差异不大。但对于大文件或需要随机访问的场景,内存映射的优势明显。
mmap & sendfile 的运行机理
mmap 可以进行文件修改,在 sendfile 中只能进行文件传输
mmap:
sendfile:
为什么 kafka 比 RocketMQ 吞吐量更高?
核心差异在于:Kafka 是为极高吞吐、持久化日志流而设计的“分布式提交日志”,而 RocketMQ 最初是为高可靠、强一致的消息队列而设计,后来功能不断丰富。 这个根本目标的差异,导致了架构和实现的诸多不同。
如果但看吞吐量, BocketMQ 确实存在不足,但是我们不能忽略了 RocketMQ 在性能和功能上的权衡,当下的 RocketMQ 在拥有可观吞吐量的前提下提供了更多的实用性功能,其中 kafka 和 RocketMQ 都实现了 mmap 和 senfile 但是由于两者数据结构传递方式上的不同导致吞吐量之间的区别。
零拷贝技术的原理与在java中应用_strace java-CSDN博客