Linux IO Stack 分析及调优实践
本文首先介绍Linux IO Stack 的整体链路,然后结合相关工具,跟踪IO的性能,并且根据实践中的场景,介绍如何进行IO的调优。 Linux IO Stack 文件读写的方式 根据上图,我们总结下文件读写的几种方式。 使用标准库的文件读写函数 直接根据文件描述符进行读写。先说明写数据的情况。主要两种情形: 在不使用Fsync 的情况下,实际写入Page Cache 就成功返回。 后续会由内核的线程异步将数据刷入磁盘。在系统内存充足的情况下,写入速度很快。但是内存紧张的情况下,可能涉及脏页的写入&内存的页替换,会有延迟的情况。如果写完数据,立即进行读取的话,速度也会很快,读取也是优先从Page Cache 读取。 如果涉及到WAL日志文件,防止意外断电情况下的数据丢失,会使用Fsync进行写入。这时也会写入到Page Cache ,但是会阻塞,直到提交到块IO,并且完成实际磁盘设备的写入才返回。这里有个优化的点,可以使用DSync 的模式,这样只会写入数据,而不进行元数据的更新,从而减少IO的调用。 在数据读取时,会先从Page Cache 读取。如果Page Cache 中没有,才会从磁盘读取。读取的性能一般会很高,尤其是顺序读取的情况下。 系统会进行预读,从而提前加载数据到Page Cache。 直接IO (O_DIRECT) 在打开文件时,设置 O_DIRECT 标志位,即可开启直接IO。Direct IO 的写入,不会经过Page Cache, 直接进行块IO的提交。这里会减少系统内核函数的调用。简化IO链路。这里不会涉及预读,需要上层应用控制。比如数据库软件都会使用Direct IO 来进行文件读写。 O_DIRECT 的写入,需要内存地址的对齐,以及IO的大小也需要对齐。否则会报错。 这里也可以单独设置Fsync的标志,如果没有设置,提交到块IO就返回了。否则需要等待磁盘的写入完成才返回。 使用 mmap 系统调用 mmap 系统调用可将文件映射到进程的虚拟地址空间,允许通过内存指针直接读写文件内容。 当使用 MAP_SHARED 模式写入时,数据会修改内核的 Page Cache 中对应的页面,并将其标记为“脏页”。 随后,内核的回写线程(如 flusher)会在后台异步地将这些脏页写入磁盘。 若需确保数据已持久化,应用程序应显式调用 msync()。 使用mmap 实现了零拷贝的(Zero Copy)机制,避免了数据在用户空间和内核空间之间的复制。 在读取文件时,使用 mmap 的方式居多。 当读取文件时,在内存中不存在的情况下,触发缺页中断,加载之后再返回。当内存紧张时,内存页可能被换出。 在mmap打开的内存地址,可以进行madvise调用,灵活的调整访问方式。 上面介绍的几种读写方式都是同步IO的方式,如果使用异步AIO,可以参考io_uring的使用,这里有介绍,即使使用AIO,也同样复用上图的IO Stack。 Block Layer 不管是通过Page Cache, 还是Direct IO, 最终都是提交到块IO。当数据写入到Page Cache 后,达到一定的阈值(内核脏页占比,内核脏页字节数量,过期时间)等等,会有内核线程把数据提交到块IO。在现代Linux内核中,块IO是由MQ(多队列)实现的。...