Linux 服务器 D 状态进程排查与处理
D 状态(也叫 uninterruptible sleep、Disk sleep)是 Linux 进程状态中最让人头疼的一种。一旦出现大量或持续的 D 状态进程,通常意味着系统正经历严重的阻塞,最常见的表现就是:
- load average 异常高
- iowait / wa 显著升高
- 响应变慢甚至假死(ssh 卡顿、命令迟迟无返回)
- top/htop 里看到大量进程状态为 D
一、D 状态到底是什么?(内核视角)
在 Linux 任务(task_struct)中,进程状态(task->state)有几种主要取值:
- R → Running / Runnable(可运行或正在运行)
- S → Interruptible sleep(可被信号中断的睡眠,例如等待网络、锁、sleep)
- D → Uninterruptible sleep(不可被信号中断的睡眠)
- T → Stopped(被 SIGSTOP 暂停)
- Z → Zombie(僵尸进程)
D 状态的核心定义:
进程正在等待内核中某个 不可中断 的 I/O 操作完成,且故意不响应任何信号(包括 SIGKILL),直到该 I/O 操作结束或出错。
换句话说: 只要进程进入了 D 状态,kill -9 也杀不掉它,只能等内核把这次 I/O 做完(或超时/出错)。
二、哪些情况会导致进程进入 D 状态?(最常见 Top 10)
| 排名 | 场景类型 | 典型表现(iostat / 其他指标) | 占比(经验) | 是否可被 kill -9 杀死 | 是否会引起系统假死 / 高 load |
|---|---|---|---|---|---|
| 1 | 磁盘 / 文件系统 IO 阻塞 | iostat %util 高、await 极高 | ★★★★★ | 否 | 是(最常见) |
| 2 | NFS / 网络文件系统挂起 | NFS 服务器宕机 / 网络断开 | ★★★★☆ | 否 | 是(经典“卡死”场景) |
| 3 | 脏页回写压力过大(kswapd / pdflush) | Dirty + Writeback 几 GB 且持续上升 | ★★★★ | 否 | 是 |
| 4 | 块设备驱动卡住(坏块、超时) | dmesg 出现 I/O error / timeout | ★★★ | 否 | 是 |
| 5 | 某些文件系统元数据操作卡住 | 高并发小文件场景,dentry/inode 操作阻塞 | ★★★ | 否 | 部分场景 |
| 6 | swap 设备(机械盘 / 慢盘)读写慢 | si/so 非零且持续,swap 设备 %util 高 | ★★☆ | 否 | 是 |
| 7 | 某些虚拟化 / 容器存储驱动问题 | virtio_blk / nvme 队列满 / 宿主机存储慢 | 云主机常见 | 否 | 是 |
| 8 | dm-crypt / LUKS 加密设备卡住 | 加密盘性能瓶颈或密码输入超时 | 少见 | 否 | 是 |
| 9 | FUSE 文件系统(如 s3fs、gluster)挂起 | FUSE 进程本身卡住或网络问题 | 少见 | 否 | 是 |
| 10 | 某些第三方内核模块 bug | dmesg 出现模块相关错误 | 极少 | 否 | 是 |
三、D 状态进程排查标准流程(推荐顺序)
- 先确认 D 状态进程数量与持续时间Bash
# 当前有多少个 D 状态进程 ps -eo pid,ppid,state,cmd | grep ' D' | wc -l # 列出所有 D 进程详细信息(最常用) ps -eo pid,ppid,state,lstart,time,cmd | grep ' D' # 看哪些进程的父进程是谁(常用于找罪魁祸首) ps -eo pid,ppid,state,cmd | awk '$3=="D" {print $2 " " $1 " " $4}' | sort | uniq -c | sort -nr - 看 iowait 与磁盘饱和度(90% 以上 D 状态都跟这个有关)Bash
iostat -xmdz 1 5 # %util、await、avgqu-sz、r/s、w/s vmstat 1 5 # wa 列、b 列(b 就是 D 状态进程数) - 定位具体是哪个磁盘、哪个进程在制造大量 IOBash
iotop -o -P # 实时显示有 IO 的进程(最直观) pidstat -d 1 5 # 进程级读写统计 - 检查是否有被删除但仍在写的文件(deleted but open)Bash
lsof +L1 | grep deleted # 或 lsof -p PID | grep '(deleted)' - 查看内核日志,看是否有 I/O 错误或超时Bash
dmesg -T | grep -i -E 'error|timeout|I/O|failed|hung|hung_task' journalctl -k -p warning..crit --since "1 hour ago" - 如果是 NFS / 网络存储Bash
nfsstat -m # 查看 NFS 挂载点状态 mount | grep nfs showmount -e nfs_server_ip
四、处理 D 状态进程的正确优先级(从安全到激进)
原则:D 状态进程 kill -9 是无效的,只能等内核完成或放弃这次 I/O。
| 优先级 | 处理方式 | 适用场景 | 风险级别 | 备注 |
|---|---|---|---|---|
| 1 | 等待(最安全) | D 状态进程数量少、业务可忍受 | ★☆☆☆☆ | 通常 几秒~几分钟会自行恢复 |
| 2 | 优化业务写模式 | 日志级别太高、sync_binlog=1、appendfsync always | ★★☆☆☆ | 异步日志、调 innodb_flush_log_at_trx_commit=2 |
| 3 | 杀掉产生大量 IO 的上游进程 | 找到罪魁祸首(如某个疯狂写日志的 worker) | ★★★☆☆ | kill -9 上游进程,D 状态下游进程会自然退出 |
| 4 | 重启相关服务(优雅) | 服务支持平滑重启(nginx reload、systemctl restart) | ★★★★☆ | D 状态的 worker 进程会随服务退出而消失 |
| 5 | echo b > /proc/sysrq-trigger(魔法键) | 整个系统卡死,无法 ssh 或执行命令 | ★★★★★ | 强制同步 + 重启,数据可能丢失,慎用 |
| 6 | 硬重启机器 | 所有办法无效,业务可接受宕机 | ★★★★★ | 最后手段,通常伴随数据损坏风险 |
五、预防 D 状态大量出现的常用措施
- 日志类
- 降日志级别(info → warn)
- 使用异步日志库(log4j2 AsyncAppender、logback AsyncAppender)
- logrotate + copytruncate 而不是 mv + create
- 数据库
- innodb_flush_log_at_trx_commit = 2(非严格一致性可接受)
- sync_binlog = 1000 或 0
- 开启 innodb_doublewrite = 1(但权衡性能)
- 系统参数
- vm.dirty_ratio / vm.dirty_background_ratio 调小,避免脏页堆积
- vm.dirty_expire_centisecs 调小,加快过期脏页回写
- 监控告警
- D 状态进程数 > 5 持续 1min 告警
- iowait > 30% 持续 3min 告警
- 单个盘 %util > 85% 持续 5min 告警
一句话总结:
D 状态 = 进程在等待不可中断的块设备 I/O,且故意不响应任何信号。 iowait 高 + 大量 D 状态 → 几乎一定是磁盘或文件系统层面的阻塞。 先找哪个进程在写/读 → 再看是日志、数据库、还是网络存储 → 最后优化写策略或隔离 IO。