1. 背景知识
IO写入的过程是这样的:
用户数据 –> 进程IO缓冲区 –> 内核缓冲区 –> (磁盘缓冲区->磁盘)
通常我们认为一个写请求(注意我们讨论的粒度一定是一个request,在不同环节request可能会被拆分合并)落盘,则是在它从内核缓冲区(内存中的一块区域)刷到磁盘上(不关心磁盘缓冲区)。对于持久化存储磁盘,当数据落盘成功后,断电/宕机数据依然会在磁盘上。
缓冲区公共的作用是聚集小IO,提高性能。
- 进程缓冲区。经常做IO写出的同学都知道,必要的时候我们会在代码里手动对outputstream等对象进行flush调用,这里的flush就是刷进程缓冲区。进程缓冲区可以减少内核态write的触发。
- 内核缓冲区。如果应用的写很零碎,甚至是按字节写的,如果零碎不对齐小IO持续写入,会对写入性能、磁盘存储和寿命产生极大影响。内核缓冲区可以减少实际IO的触发。
- 磁盘缓冲区。这里指的是硬盘上自己的一块容量固定的缓冲区,作用其实也是提高写入性能和保护磁盘。
2. IO类型:
- 默认带缓冲IO: 应用写出数据到进程IO缓冲区中直接返回写成功。缓冲区会在写满或者文件句柄关闭时刷到下个环节。这种模式下,应用write接口成功并不能代表数据落盘成功。即此时宕机可能会丢失部分write返回成功的数据。
- Sync IO:强制同步IO,即数据写入内核缓冲区后,强制立即将内核缓冲区数据刷出到磁盘。sync会让磁盘缓冲区也触发flush。
- Direct IO:直写IO,不经过进程IO缓冲区和内核缓冲区。bypass OS cache, 但不会flush磁盘中的缓冲区。
在linux open()系统调用的时候可以指定flag来决定当前写出使用哪种类型IO。
- 不指定:默认缓冲IO
- O_DIRECT: 直写模式。
- O_SYNC: 每次写出将数据和文件元信息一起更新落盘。等同于write() + fsync()
- O_DSYNC: 每次写出仅更新数据,元信息只在必要的时候更新 (譬如不改变文件长度的写操作)。等同于 write() + fdatasync()
- O_RSYNC: linux未实现该语义,早些版本的glibc把它等同于O_SYNC。大致理解是写出后不直接强制立即刷缓存,而是异步刷出。但他会保证当应用下一个read到来时,数据都已经从缓冲区刷出落盘。感觉是个有点为了性能delaying flushing 妥协的方案。
O_RSYNC: this flag, which only affects read operations, must be used in combination with either O_SYNC or O_DSYNC. It will cause a read() call to block until the data (and maybe metadata) being read has been flushed to disk (if necessary). This flag thus gives the kernel the option of delaying the flushing of data to disk; any number of writes can happen, but data need not be flushed until the application reads it back.
3. TroubleShooting:
- 如何保证写出数据落盘?
采用SYNC方式写出,或者采用DIRECT/默认方式写出后显式调用 sync/fsync/fdatasync等系统函数。 - 平时写出数据应采用哪种方式?
O_SYNC是最安全的,但是也是性能最低的。因此实际要从性能和安全两个角度去衡量。