顺序读写提升磁盘IO性能
磁盘有个特性:顺序读写性能远好于随机读写。
在SSD上,顺序读写的性能要比随机读写快几倍,如果是机械硬盘,这个差距会达到几十倍。
os每次从磁盘读写数据时,需先寻址,即找到数据在磁盘的物理位置,然后再读写数据。
若是机械硬盘,寻址需要较长时间,因为要移动磁头。顺序读写相比随机读写省去大量寻址时间,只要寻址一次,就可连续读写下去,所以性能比随机读写好。
Kafka充分利用磁盘特性。存储设计非常简单,对每个分区,它把从Producer收到的消息,顺序地写入对应log文件,一个文件写满,就开启新文件顺序写。
消费时,也是从某个全局位置开始,即某个log文件的某位置开始,顺序读出消息。
这简单的设计,充分利用顺序读写特性,极大提升Kafka在使用磁盘时的IO性能。
PageCache加速消息读写
PageCache是os在内存中给磁盘的文件建立的缓存。
无论使用什么高级语言,在调用系统API读写文件时,并不会直接去读写磁盘的文件,实际操作的都是PageCache,即文件在内存中缓存的副本。
应用程序在写入文件时,操作系统会先把数据写入到内存中的PageCache,再一批批写到磁盘。
读取文件的时候,也是从PageCache中来读取数据,这时候会出现两种可能情况。
- PageCache中有数据,直接读取,这样就节省了从磁盘上读取数据的时间;另一种情况是,PageCache中没有数据,这时候操作系统会引发一个缺页中断,应用程序的读取线程会被阻塞,操作系统把数据从文件中复制到PageCache中,然后应用程序再从PageCache中继续把数据读出来,这时会真正读一次磁盘上的文件,这个读的过程就会比较慢。
应用程序使用完某块PageCache后,os并不会立刻清除该PageCache,而是尽可能地利用空闲的物理内存保存这些PageCache,除非系统内存不够用,操作系统才会清理部分PageCache。清理的策略一般是LRU或它的变种算法:优先保留最近一段时间最常使用的那些PageCache。
Kafka在读写消息文件的时候,充分利用了PageCache的特性。一般来说,消息刚刚写入到服务端就会被消费,按照LRU的“优先清除最近最少使用的页”这种策略,读取时候,对于这种刚刚写入的PageCache,命中的几率会非常高。
大部分情况下,消费读消息都会命中PageCache,带来的好处有:
- 读取的速度会非常快
- 给写入消息让出磁盘的IO资源,间接也提升了写入的性能
零拷贝
Kafka的服务端在消费过程中,还使用了一种“零拷贝”的os特性提升消费的性能。
在服务端,处理消费的大致逻辑:
- 首先,从文件中找到消息数据,读到内存
- 然后,把消息通过网络发给客户端
数据实际上做了2或3次复制:
- 从文件复制数据到PageCache,若命中PageCache,这一步可省
- 从PageCache复制到应用程序的内存空间,即我们可以操作的对象所在的内存
- 从应用程序的内存空间复制到Socket缓冲区,该过程就是我们调用网络应用框架API发送数据的过程
Kafka使用零拷贝技术可把这复制次数减少一次,上面的2、3步骤两次复制合并成一次复制。
直接从PageCache中把数据复制到Socket缓冲区:
- 不仅减少一次数据复制
- 由于不用把数据复制到用户内存空间,DMA控制器可直接完成数据复制,无需CPU参与,速度更快。
该零拷贝对应的系统调用:
#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
前两个参数分别目的端、源端的文件描述符
- 后面两个参数是源端的偏移量和复制数据的长度
- 返回值是实际复制数据的长度
如果你遇到这种从文件读出数据后再通过网络发送出去的场景,并且这过程中你不需对这些数据处理,那一定要使用零拷贝方法,有效提升性能。