1. 传统文件传输过程
1.1 Linux文件读写流程
用户视角:
具体逻辑:
- 发生了 4 次用户态与内核态的上下文切换
- 发生了 4 次数据拷贝
1.2 上下文切换
用户态和内核态:用户空间和内核空间权限不同,内核态权限为0,最大。
为什么区分用户空间和内核空间?隔离操作系统程序和应用程序,在内核空间运行的程序出现bug会造成整个机子崩溃。
内核空间为什么不能直接使用用户空间的数据?内核空间不能信任用户空间的指针。
1.3 传统I/O操作的瓶颈
1.3.1 I/O轮询
优点:实现简单
缺点:占用CPU全部资源,效率低。
1.3.2 I/O中断
优点:对比 I/O 轮询的方式一定程度上释放了CPU 资源
缺点:在大数据量传输的情况下CPU 会反复中断
1.3.3 DMA传输
优点:彻底减少了一次CPU拷贝
缺点:依赖设备硬件支持
2. 零拷贝的实现方式
零拷贝并不是0次拷贝数据,而是:
- 减少用户空间和内核空间之间的 CPU 拷贝次数
- 减少上下文切换次数
2.1 mmap+write
memory map,做虚拟地址映射,在内核空间内做数据拷贝。
4次上下文交换,1次CPU拷贝
2.2 sendfile
2次上下文交换,1次CPU拷贝
2.3 sendfile + gather
2次上下文交换,0次CPU拷贝
2.4 splice
2次上下文交换,0次CPU拷贝
3. Go 语言中的实现
可以splice,选择splice;可以sendfile,选择sendfile;都不支持,选择原始的方法mmap+write。
原始的mmap+write:
4. 零拷贝的应用
4.1 Kafka
4.1.1 顺序读写
producer 每次会追加写入到 partition
consumer 每次消费的时候,根据 offset进行顺序读取
批量刷盘
==只要是顺序读写,性能都会好很多,顺序读写的磁盘和顺序读写的SSD传输性能差不多==
4.1.2 页缓存技术
利用 linux 的page cache 技术
异步落盘,减少磁盘I/O次数
通过 Replication 机制去解决数据丢失的问题
4.1.3 零拷贝之mmap
4.1.4 零拷贝之sendfile
consumer 不需要对数据进行修改,可以采用零拷贝方式
对比传统read() + write():节省了2次cpu拷贝、2次上下文切换
4.2 RocketMQ
通过mmap + write() 实现,满足小数据需求
通过文件预热的方式来解决缺页的问题
mmap + write()可以对数据进行修改,适合RocketMQ这个偏业务的消息队列
Kafka的数据传输过程中不需要发生修改,使用sendfile,减少上下文切换的次数,实现高并发。
如果Kafka的生产者和消费者两者速度差不多,所有消息可以全部存储在page cache中,这也是高并发的一个原因。