磁盘与文件描述符
一句话总结
df 显示还有空间却无法写入,说明要么 inode 已耗尽,要么有人仍打开着已经删除的文件。 这两种情况都无法通过 df -h 看出来。
为什么需要理解这些现象
磁盘相关报障中,最常见的是下面两种情况。
inode 耗尽。 每个文件都会占用一个 inode。存在数百万个小文件时,即使容量仍有剩余,inode 也可能率先用完。只有通过 df -i 才能看到。
文件已删除,但仍处于打开状态。 如果用 rm 删除日志文件时,某个进程仍然打开着它,Linux 只会删除链接,并不会立即释放实际数据块。此时 df 仍显示磁盘已满,du 却找不到对应文件。这种差异就是决定性线索。
# du 와 df 가 다르면 이걸 본다
ls -l /proc/*/fd 2>/dev/null | grep deleted
解决方法是让进程重新打开文件。如果是日志,可以使用 logrotate 的 copytruncate,或向守护进程发送 SIGHUP;也可以重新启动进程。
它是如何工作的
文件描述符泄漏会让系统缓慢走向死亡。
ls /proc/<PID>/fd | wc -l # 지금 몇 개 열었나
cat /proc/<PID>/limits | grep 'open files' # 한도
如果这个数字随时间单调增长,代码中就存在没有关闭文件的路径。达到上限的一刻,所有请求都会以 Too many open files 失败。提高上限只能争取时间,并不等于修复问题。
常见误解
误以为 ulimit -n 无限制就一定安全。 容器的默认 RLIMIT_NOFILE 实际上几乎没有限制,达到十亿级别;但有些老旧守护进程会在启动时按照这个数字分配连接表,最终耗尽内存并退出。无限制并不总是好事。
判断磁盘是否缓慢的两个数字
iostat -x 1 有很多列,实际需要关注的只有两列。
Device r/s w/s rkB/s wkB/s r_await w_await aqu-sz %util
nvme0n1 120 380 4800 15200 0.31 0.52 1.24 38.2
await(r_await、w_await)——单个请求包含队列等待时间在内的响应 时间,单位为毫秒。NVMe 通常低于 1 ms,SATA SSD 为数毫秒,HDD 为十几毫秒。 如果这个值突然升到平时的数倍,磁盘就是瓶颈。aqu-sz——平均队列长度。超过 1 表示请求正在排队。
不要相信 %util。 这个值表示“至少有一个请求正在处理的时间比例”。
能够并行处理的 NVMe 即使达到 100%,也可能仍有余量。它是旋转磁盘时代的
指标。
哪个进程正在使用磁盘
# 실시간으로 I/O 상위 프로세스
iotop -oPa # -o: 실제로 I/O 하는 것만, -a: 누적
# iotop 이 없을 때 (컨테이너에서 흔하다)
cat /proc/<PID>/io # read_bytes, write_bytes 를 두 번 읽어 차이를 낸다
/proc/<PID>/io 中的 read_bytes 表示实际从磁盘读取的数据量,而 rchar 表示
通过读取系统调用传递的数据量。两者差异很大,说明页缓存工作良好,属于正面信号;
反过来,如果两者接近,则说明缓存没有生效,每次都在访问磁盘。
在容器中限制 I/O
与 CPU 和内存不同,Kubernetes 没有 I/O 限制字段。cgroup v2 虽然提供
io.max,却没有通过 Pod 规范暴露。因此,实际工作中的处理方式也不同。
- 把吵闹的邻居进行物理隔离。 对日志采集器、备份等集中使用 I/O 的 工作负载,安排独立节点或独立磁盘。
- 在应用程序中进行节流。 可以给备份脚本设置
ionice -c3(idle 类), 或使用rsync --bwlimit等工具自带的限制。 - 购买网络存储的 IOPS 等级,而不是使用本地磁盘。 在云环境中,这是 最可靠的办法。
ionice 只对 CFQ 与 BFQ 调度器有效。在现代 NVMe 默认使用的 none(多队列)
模式下,它完全不起作用,所以应先运行 cat /sys/block/nvme0n1/queue/scheduler
进行确认。
实际工作中真正重要的事
日志轮转配置中 create 与 copytruncate 的差别,正与这里的问题相关。
create:重命名原文件,再创建新文件。进程会继续写入旧文件,只是该文件现在已经改名,因此必须向守护进程发送 SIGHUP,让它重新打开文件。copytruncate:复制内容后,把原文件截断为 0 字节。进程可以继续写入原文件,但在复制与截断之间写入的日志会丢失。
两种方式都不是没有代价。应用程序能够处理 SIGHUP 时使用 create;无法处理时使用 copytruncate,但必须清楚存在数据丢失的可能。