磁盘缓存:从一次文件丢失问题说起
如果你遇到了这样的问题:
刚往你的 Linux 机器中,写入了个文件,然后直接断电重启,发现文件只剩下了空壳,即产生了一个没有内容,大小为 0 的文件。
那么,你遇到了磁盘缓存问题,或者更确切的说法是掉电导致的磁盘缓存丢失问题。
什么是磁盘缓存
我们在小学二年级学过(是的,我也是天天给毕导视频点赞的),在 Linux 系统(实际上是任何操作系统)中,磁盘读写都是有缓存的,因为这种缓存往往有利于系统的读写加速,毕竟我们大部分场景下遇到的都是多读少写,因此,用暂时用不到的内存来当缓存,空间换时间是非常值得的。
缓存的作用原理是,当你读了一个文件,Linux 会先检查内存中的缓存有没有对应的内容,没有才会去读磁盘上的内容,然后会先将磁盘上的内存读到内存中,再返回给用户。这样,下一次读的时候,就不用再次从磁盘中读了,这样就会大大减少文件读的时间。
如果你这时候往这个文件中写了新的内容,Linux 会往缓存中写,而不是直接往磁盘里写,这样,你写文件的时间就会大大减少。只是,写过的缓存会被 Linux 标记为脏了,也就是所谓的内存脏页,Linux 会周期性地收集所有内存脏页,排序整理,然后往磁盘中真正写入,这就是所谓的回写(writeback),也可以叫做刷盘。
所以,我们的问题的原因就找到了:因为突然掉电时,造成了内存中的脏页来不及刷入磁盘,导致数据丢失。
这里说个题外话:这个缓存有多大呢?我们来随便看个 Linux 系统的内存。
1 | $ free -m |
我们可以看到,第五列的名称是 buff/cache
,表示这个系统缓存占用了 5563 MB 的内存,几乎是大部分内存(其实 buffer 与 cache 还是有一定区别的1,但是在我们今天讨论的问题中,可以一起对待),但最后一列 available 告诉我们,缓存也是可用的,当你的应用申请内存时,Linux 就会清理缓存,让出内存给你的应用2。
至于这些缓存具体用来了干什么,不是今天的主题,这里就略过了,你可以自己做实验3。
1 | To free pagecache: |
让我们回到开头提到的问题,看看如何解决。
如何解决
目前的解决方案有两种,可以单独使用,也可以一起使用。
第一种是 open
文件的时候,加上 O_SYNC
标志,表示这个文件写的操作需要直接刷盘,也就是说每次调用 write
之后,文件数据和元数据都会写入磁盘,或者调用 fsync/fdatasync/sync
这几个系统调用,效果是一样的。
不过这种方案的缺点是很明显的,即所有写操作的延迟都会大大增加,不建议在频繁写的地方使用。( O_DIRECT
不在考虑范围之内,如果你做的是数据库才可以考虑,不然造成的后果是你无法承受的。)
第二种就是内核参数大法,通过调整内核缓存相关的参数来进行调优,这种方法相对于第一种会温和一些,可以做到尽量不影响系统性能。
下面就具体说说跟当前这个问题涉及到的几个内核参数。
缓存相关的内核参数
大家回想下小学三年级学过的虚拟内存相关的内核参数,对的,就在 /proc/sys/vm/
这个目录下,有我们需要的参数。
当然了,三年级的同学都知道还有 sysctl 这个工具,我们可以直接获取所需要的名称以及对应的值,具体涉及到的有如下几个:
1 | $ sysctl -a | grep dirty |
vm.dirty_background_bytes
以及 vm.dirty_background_ratio
是用来表示脏内存「软」阈值,一旦超过这个阈值,系统后台刷盘进程就会开始运行,将脏数据刷到磁盘上,增大这个值就会增加缓存大小,反之亦然。bytes
与 ratio
的区别就在于前者是绝对值,后者是相对值(内存的百分比),下面同理;
vm.dirty_bytes
以及 vm.dirty_ratio
是用来表示脏内存「硬」阈值,一旦脏数据到达了这个阈值,系统就会阻塞所有 I/O 操作,然后进行刷盘。这个值受磁盘写的速度影响非常大,假如磁盘写很慢,就不能设置太低,否则就会让整个系统卡住比较长的时间了;
vm.dirty_expire_centisecs
是用来表示脏数据的时间阈值,一旦超过这个阈值,就表示对应的脏数据应该刷盘了,减少这个值会减少遇到掉电丢数据问题的概率;
vm.dirty_writeback_centisecs
是用来表示内核检查脏数据的运行间隔,单位是厘秒(秒的百分之一),与 vm.dirty_expire_centisecs
配合起来使用,减少这个值也会进一步减少遇到掉电丢数据问题的概率,但是多少都会影响系统的性能;
vm.dirtytime_expire_seconds
这个主要是给 lazytime inode 设置的过期时间,比如 inode 只是更新了 atime,这种更新非常频繁的数据就没必要短时间就更新,而且负责刷盘的是另外一个专门的 dirtytime writeback 进程,因此这个的默认时间比较长:12 小时,这个就不建议调整了,作用不大;
具体如何调整,就可以按照机器的磁盘读写速度、内存大小以及具体应用场景来平衡系统 IO 性能、数据安全与成本这三者之间的关系(毕竟有钱的情况下,磁盘换 SSD、加大内存以及买个 UPS 就能搞定这些问题了,嗯,有钱真好)。
Ref
- What is the difference between buffer and cache memory in Linux?
- Help! Linux ate my RAM!
- Experiments and fun with the Linux disk cache
- linux kernel doc - sysctl vm
- networking - Adjust linux disk flush intervals to avoid blocking user processes - Super User
- Better Linux Disk Caching & Performance with vm.dirty_ratio
首发于 Github issues: https://github.com/xizhibei/blog/issues/153 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可作者:习之北 (@xizhibei)
原链接:https://blog.xizhibei.me/zh-cn/2020/11/21/disk-cache-start-with-a-file-missing-problem/