>

几种 IO 方式介绍

Linux 中文件访问的几种方式

1、标准文件访问 (缓存IO)
用户访问磁盘中数据时,通常通过read()或者write()调用完成,操作系统会先将数据读取或者写入到page cache中,然后再写入磁盘,Linux默认写入磁盘方式为deferred write

2、同步访问
相对于标准访问方式,同步访问需要在写入磁盘后才返回,而标准访问可以在写入page cache后就返回

3、内存映射
Linux通过将一块内存区域和一个外部设备或者文件关联起来,操作系统将对内存的操作映射为对外部设备或文件的操作,Linux提供了mmap

4、直接访问(直接IO)
直接在用户空间访问文件,不需要内核空间的page cache支持,对于一些通过应用程序自身进行缓存控制的程序(例如数据库)比较适用

5、异步访问
用户程序在写入或者读取磁盘数据时无需阻塞,由操作系统异步,提升了用户程序的效率

缓存IO和直接IO对比

缓存IO需要由DMA将文件数据读入到page cache,然后通过page cache读取数据,如果一个文件需要频繁访问,能够减少文件直接访问次数,但是在大量文件访问的程序中,存在很多用户程序,内核page cache和磁盘文件的数据copy,就会带来很高的cpu负载。

如何进行直接IO调用

linux中文件操作主要通过read(),write(),open()这三个操作完成,在函数操作中通过O_DIRECT标识符完成

直接IO的使用场景

由于直接IO调用减少了数据的copy次数,对于一些需要大量数据复制(到内存或者网络)或者对数据缓存能够通过应用控制(例如数据库)的场景,会有很大的性能提升

Zero Copy

Linux 中传统的 I/O 操作是一种缓冲 I/O,I/O 过程中产生的数据传输通常需要在缓冲区中进行多次的拷贝操作,例如一次网络请求中,如果需要读取磁盘文件,需要经过四次copy过程和多次的内核和用户态的上下文切换,下面是一次传统web请求中,从磁盘读取数据到返回数据结果的过程中数据在内存中的copy过程

也可以通过 Efficient data transfer through zero copy 这篇文章详细了解

zero copy技术可以避免数据的多次copy,减少不必要的上下文切换(内核态和用户态),降低CPU的资源浪费,尤其在处理大量网络请求的应用中,zero copy显得更加重要。

linux中zero copy实现方式主要实现方式

1) 直接IO技术(减少page cache)
2) 从page cache 直接copy到目标缓冲区
3) mmap():应用程序调用了 mmap() 之后,数据会先通过 DMA 拷贝到操作系统内核的缓冲区中去。接着,应用程序跟操作系统共享这个缓冲区,适用于大数据范围的copy,但是当多个进行修改时,会收到一个SIGBUS中断信号,而且必须有效的解决才能避免一些问题。
4) sendfile():sendfile() 系统调用利用 DMA 引擎将文件中的数据拷贝到操作系统内核缓冲区中,然后数据被拷贝到与 socket 相关的内核缓冲区中去
5) splice():Linux2.6.17引入,和sendFile机制类似,但是可以在fin和fout之间互相copy数据,而不是单向。
6) copy-on-write:多个进程共享一块 buffer,当一个进程需要修改 buffer 数据时,会将 buffer 数据 copy 到进行内存单独修改,COW 技术通过读共享内存减少了内存的 copy
7) 通过硬件的支持,直接从 kernel buffer 到网卡等终端,减少了一次 kernel buffer 到 socket buffer 的 copy

Zero Copy的实际应用场景

Nginx,在 Nginx 性能优化中,有一个配置是 sendfile on/off,以下是官方文档的说明
By default, NGINX handles file transmission itself and copies the file into the buffer before sending it. Enabling the sendfile directive eliminates the step of copying the data into the buffer and enables direct copying data from one file descriptor to another
当nginx在处理静态资源请求时,开启sendfile对性能有一定的提升,当然还需要根据实际场景,结合其他配置使用。可以通过 Optimisations Nginx, bien comprendre sendfile, tcpnodelay et tcpnopush 详细了解

C10k 问题的解决中,Zero Copy 对于 IO 的提升是一个关键点,具体可以通过 [The C10K problem][] 查看

Kafka, Netty, RocketMQ 等中间件

消息中间件中对于消息持久化通常保存在文件中,对于一个高吞吐量的消息中间件,对文件读写通常采用以下方案(Java为例)

  • NIO:主要在java.nio包中,通过FileChannel相关方法实现
1
2
3
4
5
FileChannel fileChannel = new RandomAccessFile(new File("message.data"), "rw").getChannel();

//然后通过read和write方法写入数据
fileChannel.read(ByteBuffer[] dsts, int offset, int length);
fileChannel.write(ByteBuffer[] srcs, int offset, int length);
  • MMAP:RocketMQ默认采用异步mmap的方式实现消息刷盘
1
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, filechannel.size();

Reference:
https://www.jishuwen.com/d/2QwJ

[The C10K problem][http://www.kegel.com/c10k.html#top]