为什么如果我不连续删除缓存,我的 Linux 系统就会卡顿?

为什么如果我不连续删除缓存,我的 Linux 系统就会卡顿?

在过去的几个月里,我的 Linux 系统出现了一个非常恼人的问题:Firefox 音频播放、鼠标移动等都会卡顿,每隔几秒钟就会出现一小段时间(但仍然很明显)的卡顿。当内存缓存填满时,或者当我运行大量占用磁盘/内存的程序(例如备份软件restic)时,这个问题会变得更糟。但是,当缓存未满时(例如在非常轻的负载下),一切都运行得非常顺畅。

通过查看perf top输出,我发现list_lru_count_one在这些滞后期间开销很高(约 20%)。htop还显示kswapd0使用 50-90% 的 CPU(尽管感觉影响远大于此)。在极端滞后期间,htopCPU 计量表通常由内核 CPU 使用率主导。

我发现的唯一解决方法是强制内核保留可用内存(sysctl -w vm.min_free_kbytes=1024000)或通过 持续删除内存缓存echo 3 > /proc/sys/vm/drop_caches。当然,这两种方法都不是理想的,也不能完全解决卡顿问题;它只是使卡顿不那么频繁。

有谁知道为什么会发生这种情况?

系统信息

  • 配备 20 GB(不匹配)DDR3 RAM 的 i7-4820k
  • 在 NixOS 不稳定版 Linux 4.14-4.18 上重现
  • 在后台运行 Docker 容器和 Kubernetes(我觉得这不应该产生微卡顿?)

我已经尝试过的方法

  • 更改 I/O 调度程序 (bfq),使用多队列 I/O 调度程序
  • 使用-ckCon Kolivas 的补丁集(没有帮助)
  • 禁用交换,更改交换性,使用 zram

编辑:为了清晰起见,下面是此类延迟峰值期间的图片。请注意高htopCPU负载和高内核 CPU 使用率。perflist_lru_count_onekswapd0

htop 和 perf 输出

答案1

听起来你已经尝试了我最初建议的很多事情(调整交换配置、更改 I/O 调度程序等)。

除了您已经尝试更改的内容之外,我建议您研究更改 VM 写回行为的有些愚蠢的默认值。这由以下六个 sysctl 值管理:

  • vm.dirty_ratio:控制触发写回之前必须等待多少写入。处理前台(每个进程)写回,并以 RAM 的整数百分比表示。默认为 RAM 的 10%
  • vm.dirty_background_ratio:控制触发写回之前必须等待多少写入。处理后台(系统范围)写回,并以 RAM 的整数百分比表示。默认为 RAM 的 20%
  • vm.dirty_bytes:与 相同vm.dirty_ratio,但以总字节数表示。这或者 vm.dirty_ratio将被使用,以最后写入的为准。
  • vm.dirty_background_bytes:与 相同vm.dirty_background_ratio,但以总字节数表示。这或者 vm.dirty_background_ratio将被使用,以最后写入的为准。
  • vm.dirty_expire_centisecs:当上述四个 sysctl 值尚未触发挂起写回时,必须经过百分之几秒才能开始挂起写回。默认为 100(一秒)。
  • vm.dirty_writeback_centisecs:内核评估脏页是否写回的频率(以百分之一秒为单位)。默认值为 10(十分之一秒)。

因此,使用默认值,每十分之一秒,内核将执行以下操作:

  • 如果最后修改时间是在一秒之前,则将任何已修改的页面写入持久存储中。
  • 如果某个进程的未写出的已修改内存总量超过 RAM 的 10%,则写出该进程的所有已修改页面。
  • 如果尚未写出的已修改内存总量超过 RAM 的 20%,则写出系统中所有已修改的页面。

因此,应该很容易理解为什么默认值可能会给你带来问题,因为你的系统可能会尝试写出最多 4千兆字节的数据永久存储第十一秒钟。

目前普遍的共识是调整vm.dirty_ratio为 RAM 的 1% 和vm.dirty_background_ratio2%,对于 RAM 小于 64GB 的系统,其行为与最初的预期相同。

其他一些值得关注的事项:

  • 尝试vm.vfs_cache_pressure稍微增加 sysctl。这控制内核在需要 RAM 时从文件系统缓存中回收内存的积极程度。默认值为 100,不要将其降低到 50 以下(您将要如果低于 50,则会出现非常糟糕的行为,包括 OOM 情况),并且不要将其提高到超过 200(太高,内核将浪费时间尝试回收它实际上无法回收的内存)。我发现,如果您拥有相当快的存储,将其提高到 150 实际上可以明显提高响应能力。
  • 尝试更改内存过量使用模式。这可以通过更改 sysctl 的值来实现vm.overcommit_memory。默认情况下,内核将使用启发式方法来尝试预测它实际上可以负担得起多少 RAM。将其设置为 1 会禁用启发式方法,并告诉内核表现得像它有无限内存一样。将其设置为 2 会告诉内核不要提交超过系统上交换空间总量加上实际 RAM 百分比(由 控制vm.overcommit_ratio)的内存。
  • 尝试调整vm.page-clustersysctl。这可控制一次换入或换出的页面数(这是一个以 2 为底的对数值,因此默认值 3 表示 8 个页面)。如果您确实在进行交换,这可以帮助提高换入和换出页面的性能。

答案2

问题已经找到!

事实证明,当有大量容器/内存 cgroup 时,这是 Linux 内存回收器中的一个性能问题。(免责声明:我的解释可能有缺陷,我不是内核开发人员。)该问题已在 4.19-rc1+ 中修复此补丁集

此补丁集解决了在具有许多收缩器和内存 cgroup(即具有许多容器)的机器上 shrink_slab() 速度缓慢的问题。问题是 shrink_slab() 的复杂度为 O(n^2),并且随着容器数量的增长而增长得太快。

假设我们有 200 个容器,每个容器有 10 个挂载点和 10 个 cgroup。所有容器任务都是隔离的,不会接触外部容器挂载点。

如果是全局回收,任务必须遍历所有 memcg,并调用所有 memcg 感知的收缩器。这意味着,任务必须为每个 memcg 访问 200 * 10 = 2000 个收缩器,由于有 2000 个 memcg,do_shrink_slab() 的总调用次数为 2000 * 2000 = 4000000。

由于我运行了大量容器,因此我的系统受到的打击特别严重,这很可能是导致问题出现的原因。

我的故障排除步骤,希望对遇到类似问题的人有帮助:

  1. 当我的电脑卡顿时,会注意到kswapd0CPU 占用率很高
  2. 尝试停止 Docker 容器并再次填充内存 → 计算机不再卡顿!
  3. 运行ftrace(以下Julia Evan 的精彩解释博客)来获取踪迹,请参见kswapd0往往会卡在shrink_slabsuper_cache_count和中list_lru_count_one
  4. 谷歌shrink_slab lru slow,找到补丁集!
  5. 切换到 Linux 4.19-rc3 并验证问题是否已修复。

相关内容