一种是 PageCache 中有数据,那就直接读?。?这样就节省了从磁盘上读取的时间;另一种情况是,PageCache 中没有数据,这时候操作系统会引发一个缺页中断,应用程序的读取线程会被阻塞 , 操作系统把数据从文件复制到 PageCache 中,然后应用程序再从 PageCache 继续把数据读出来,这时会真正读一次磁盘上的文件,这个读的过程就会比较慢 。
用户的应用程序在使用完某块 PageCache 后 , 操作系统并不会立刻就清除这个 PageCache,而是尽可能地利用空闲的物理内存保存这些 PageCache,除非系统内存不够用,操作系统才会清理掉一部分 PageCache 。清理的策略一般是 LRU 或它的变种算法 , 核心逻辑就是:优先保留最近一段时间最常使用的那些 PageCache 。
另外 PageCache 还有预读功能,假设我们读取了 1M 的内容,但 linux 实际读取的却并不止 1M,因为这样你后续再读取的时候就不需要从磁盘上加载了 。因为从磁盘到内存的数据传输速度是很慢的,如果物理内存有空余,那么就可以多缓存一些内容 。
而 Kafka 在读写消息文件的时候,充分利用了 PageCache 的特性 。一般来说 , 消息刚刚写入到服务端就会被消费,读取的时候,对于这种刚刚写入的 PageCache,命中的几率会非常高 。也就是说,大部分情况下,消费读消息都会命中 PageCache , 带来的好处有两个:一个是读取的速度会非常快,另外一个是,给写入消息让出磁盘的 IO 资源 , 间接也提升了写入的性能 。
ZeroCopy(零拷贝)Kafka 还使用了零拷贝技术,首先 Broker 将消息发送给消费者的过程如下:
- 将指定的消息日志从文件读到内存中;
- 将消息通过网络发送给消费者客户端;

文章插图
整个过程大概是以上 6 个步骤,我们分别解释一下 。
1)应用程序要读取磁盘文件,但只有内核才能操作硬件设备,所以此时会从用户空间切换到内核空间 。
2)通过 DMA 将文件读到 PageCache 中,此时的数据拷贝是由 DMA 来做的,不耗费 CPU 。关于 DMA , 它是一种允许硬件系统访问计算机内存的技术 , 说白了就是给 CPU 打工的,帮 CPU 干一些搬运数据的简单工作 。
CPU 告诉 DMA 自己需要哪些数据,然后 DMA 负责搬运到 PageCache,等搬运完成后 , DMA 控制器再通过中断通知 CPU,这样就极大地节省了 CPU 的资源 。
但如果要读取的内容已经命中 PageCache,那么这一步可以省略 。
3)将文件内容从 PageCache 拷贝到用户空间中 , 因为应用程序在用户空间,磁盘数据必须从内核空间搬运到用户空间,应用程序才能操作它 。注意:这一步的数据搬运不再由 DMA 负责 , 而是由 CPU 负责 。
因为 DMA 主要用于硬件设备与内存之间的数据传输,例如从磁盘到 RAM,从 RAM 到网卡 。虽然 DMA 可以减少 CPU 的负担,但通常不用于内核空间和用户空间之间的数据搬运,至于原因也很简单:
- 操作系统需要保护内核空间,防止用户程序直接访问 , 以维护系统的安全和稳定 。通过 CPU 进行数据拷贝,操作系统可以控制哪些数据和资源可以被用户程序访问 。
- CPU 可以处理复杂的逻辑和任务调度,更适合执行这种涉及系统安全和资源管理的任务 。
- 在数据从内核空间传输到用户空间的过程中 , 可能需要进行一些额外的处理,例如格式转换、权限检查等,这些都是 CPU 更擅长的 。
因此这一步会涉及用户态和内核态之间的切换,和一个数据的拷贝 。
4) 文件内容读取之后,要通过网络发送给消费者客户端 。而内核提供了一个 Socket 缓冲区,位于用户空间的应用程序在发送数据时,会先通过 CPU 将数据拷贝到内核空间的 Socket 缓冲区中,再由内核通过网卡发送给消费者 。
【为什么 Kafka 的吞吐量那么高?】同样的,当数据从网络到达时,也会先被放在 Socket 缓冲区中 。应用程序从该缓冲区读取数据 , 数据被拷贝到用户空间 。
推荐阅读
- 八个提升编程体验的VS Code插件
- Python的集合模块,使用数据容器处理数据集合
- 基于Kubernetes网关API策略的流量管理
- 深入理解实践场景下的DNS隧道通信
- RabbitMQ发送和接收消息的几种方式
- 一文搞定双链表,让你彻底弄懂线性表的链式实现
- 不吃饭也要掌握的Synchronized锁升级过程
- Java中的泛型,看完这个还不会,我倒立洗头!
- Springboot之把外部依赖包纳入Spring容器管理的两种方式
- 探索Java的HTTP请求与响应处理机制
