![防止大文件写入冻结系统](https://linux22.com/image/74176/%E9%98%B2%E6%AD%A2%E5%A4%A7%E6%96%87%E4%BB%B6%E5%86%99%E5%85%A5%E5%86%BB%E7%BB%93%E7%B3%BB%E7%BB%9F.png)
因此,在我的 Linux 桌面上,我将一些大文件写入本地磁盘或 NFS 挂载。
有某种系统缓冲区,用于缓存要写入的数据。(我认为我的系统上的大小在 0.5-2GB 范围内?)
如果缓冲区已满,所有文件访问都会被阻止,从而有效地冻结系统,直到写入完成。 (我很确定甚至读取访问也被阻止。)
我需要配置什么来确保绝不发生?
我想要的是:
如果进程无法足够快地将数据写入磁盘(或网络挂载等),那个过程可以阻塞直到磁盘赶上,但其他进程仍然可以以合理的速率和延迟读取/写入数据,而无需任何中断。
理想情况下,我能够设置 dsik 的总读/写速率可用于某种类型的程序(cp、git、mplayer、firefox 等),例如“无论系统的其余部分在做什么,所有 mplayer 进程的总速度至少为 10MB/s“。 但 ”无论如何,所有 mplayer 实例总共获得至少 50% 的总速率“也很好。(即,我不太关心是否可以设置绝对费率或总费率的比例)。
更重要的是(因为最重要的读/写很小),我想要类似的延迟设置。再次强调,无论如何,我保证单个进程的读/写不会阻塞系统的其余部分超过 10 毫秒(或其他时间)。理想情况下,我会有一个像“无论系统在做什么,mplayer 永远不需要等待超过 10 毫秒才能处理读/写操作”。
无论有问题的进程如何启动(包括它在哪个用户下运行等),这都必须有效,所以“在 ionice 中包裹一个大 cp“或者其他几乎没有用的东西。如果我记得对它们进行离子化,它只会阻止某些任务可预见地冻结所有内容,但是 cron 作业、来自某些正在运行的守护进程的 exec 调用等呢?
(我想我可以用一个总是离子化它们的 shell 脚本来包装最严重的罪犯,但即便如此,浏览 ionice 的手册页,它似乎对它给我的确切保证有些模糊,所以我更喜欢一个更系统的和可维护的替代方案。)
答案1
通常Linux使用缓存将数据异步写入磁盘。然而,可能会发生写请求与实际写入之间的时间跨度或未写(脏)数据量变得非常大的情况。在这种情况下,崩溃将导致大量数据丢失,因此如果脏缓存变得很大或过旧,Linux 就会切换到同步写入。由于写入顺序也必须得到尊重,因此您不能在不保证小 IO 完全独立于所有早期排队写入的情况下绕过小 IO。因此,依赖写入可能会导致巨大的延迟。 (这种依赖关系也可能是在文件系统级别引起的:参见https://ext4.wiki.kernel.org/index.php/Ext3_Data%3DOrdered_vs_Data%3DWriteback_mode)。
我的猜测是,您正在经历某种缓冲区膨胀与相关写入的结合。如果您写入一个大文件并具有较大的磁盘缓存,那么您最终会遇到必须写入大量数据才能完成同步写入的情况。 LWN 上有一篇很好的文章,描述了这个问题:https://lwn.net/Articles/682582/
调度程序的工作仍在进行中,随着新的内核版本的出现,情况可能会有所改善。然而,到目前为止: 有一些开关可以影响 Linux 上的缓存行为(还有更多,请参阅:https://www.kernel.org/doc/Documentation/sysctl/vm.txt):
- 脏率:包含包含空闲页和可回收页的总可用内存的百分比,生成磁盘写入的进程本身将开始写出脏数据的页数。总可用内存不等于总系统内存。
- 脏背景比例:包含(以包含空闲页和可回收页的总可用内存的百分比)后台内核刷新器线程将开始写出脏数据的页数。
- dirty_writeback_centisecs:内核刷新器线程将定期唤醒并将“旧”数据写入磁盘。该可调参数表示这些唤醒之间的间隔(以百分之一秒为单位)。将其设置为零会完全禁用定期写回。
- dirty_expire_centisecs:此可调参数用于定义脏数据何时足够旧,可以由内核刷新器线程写出。它以百分之一秒表示。内存中脏数据的时间超过此时间间隔的数据将在下次刷新器线程唤醒时被写出。
在这种情况下减少最大延迟的最简单解决方案是减少脏磁盘缓存的最大数量并导致后台作业提前写入。当然,在大缓存根本无法阻止同步写入的情况下,这可能会导致性能下降。例如,您可以在 /etc/sysctl.conf 中配置以下内容:
vm.dirty_background_ratio = 1
vm.dirty_ratio = 5
请注意,适合您系统的值取决于可用 RAM 量和磁盘速度。在极端条件下,上述脏配给量可能仍然很大。例如,如果您有 100GiB 可用 RAM 并且磁盘写入速度约为 100MiB,则上述设置将导致最大量为 5GiB 脏缓存,并且可能需要大约 50 秒才能写入。使用dirty_bytes
和dirty_background_bytes
您还可以以绝对方式设置缓存的值。
您可以尝试的另一件事是切换 io 调度程序。在当前的内核版本中,有 noop、deadline 和 cfq。如果您使用的是较旧的内核,与 cfq 相比,您可能会在截止日期调度程序中体验到更好的反应时间。但是,您必须对其进行测试。在你的情况下应该避免 Noop。还有非主线 BFQ 调度器,声称与 CFQ 相比可以减少延迟(http://algo.ing.unimo.it/people/paolo/disk_sched/)。但是,它并不包含在所有发行版中。您可以使用以下命令在运行时检查并切换调度程序:
cat /sys/block/sdX/queue/scheduler
echo <SCHEDULER_NAME> > /sys/block/sdX/queue/scheduler
第一个命令还将为您提供可用调度程序及其确切名称的摘要。请注意:重新启动后该设置将丢失。要永久选择调度,您可以添加内核参数:
elevator=<SCHEDULER_NAME>
NFS 的情况类似,但还存在其他问题。以下两个错误报告可能会提供有关 NFS 上处理统计数据的一些内部信息,以及为什么大文件写入会导致统计数据非常慢:
https://bugzilla.redhat.com/show_bug.cgi?id=688232 https://bugzilla.redhat.com/show_bug.cgi?id=469848
更新:(2017年8月14日)
内核 4.10引入了新的内核选项CONFIG_BLK_WBT
及其子选项。它们防止由硬件缓冲区引起的缓冲区膨胀,硬件缓冲区的大小和优先级无法由内核控制:BLK_WBT_SQ
CONFIG_BLK_WBT_MQ
Enabling this option enables the block layer to throttle buffered
background writeback from the VM, making it more smooth and having
less impact on foreground operations. The throttling is done
dynamically on an algorithm loosely based on CoDel, factoring in
the realtime performance of the disk
此外,BFQ-Scheduler 以内核 4.12 为主线。