凌晨三点,一个 Java 服务因为内存溢出悄悄退出了,没有告警,没有日志,直到早上九点用户打来电话才发现——这种"服务静默死亡"是运维最怕的场景之一。进程是业务的实际载体,CPU、内存、磁盘的问题最终都会体现在进程上,而进程本身的问题(崩溃退出、卡死、内存泄漏、僵尸化)又往往不会被资源监控直接反映出来。所以进程监控是监控体系里不可跳过的一层。本文讲三件事:怎么看清进程状态、怎么让服务挂了自动拉起、怎么在自动拉起失败时及时通知到人,形成"自愈 + 告警"的完整闭环。
一、进程监控到底要监控什么
很多人以为进程监控就是"进程在不在",实际上至少要看五个维度:
- 存活状态:进程是否存在,服务是否在运行。
- 资源占用:CPU、内存(RSS)、打开文件数、线程数是否异常增长。
- 数量:预期是几个进程?少了说明崩溃,多了说明重复启动或 fork 异常。
- 状态异常:僵尸进程(Z 状态)、不可中断睡眠(D 状态,通常卡在 I/O)、被 OOM Killer 杀掉。
- 健康度:进程在,但接口不响应(假死)。这一条最容易被忽略,也是最危险的——监控全绿但业务已经不可用。
这五条对应不同的检测手段:前四条靠系统命令和 exporter,第五条必须靠业务健康检查(HTTP 探针、端口探测、业务指标)。
二、看清进程:命令与判读
# 最常用:查看所有进程,BSD 风格输出 ps aux # 查看某服务的进程(aux 列:USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND) ps aux | grep -v grep | grep nginx # 按 CPU / 内存排序,找消耗大户 ps aux --sort=-%cpu | head -10 ps aux --sort=-%mem | head -10 # 看进程树,理解父子关系(对排查 fork 异常很有用) ps -ef --forest pstree -p # 只看 PID 与命令行 pgrep -a nginx pgrep -f "java -jar myapp.jar" # 实时视图:按 P 按 CPU 排序,M 按内存排序,c 显示完整命令,1 展开多核 top htop # 需安装,更友好 # 查看某进程的资源上限与实际占用 cat /proc/PID/status | grep -E 'VmRSS|Threads|Name|State' cat /proc/PID/limits | grep -E 'Max open files|Max processes' ls /proc/PID/fd | wc -l # 已打开的文件描述符数量
| STAT 状态 | 含义 | 是否需要处理 |
|---|---|---|
| R | 运行中 / 可运行 | 正常 |
| S | 可中断睡眠(等待事件) | 正常 |
| D | 不可中断睡眠(通常卡在磁盘或网络 I/O) | 需排查,大量 D 说明存储有问题 |
| Z | 僵尸进程,已退出但父进程未回收 | 数量持续增长需处理 |
| T | 被信号暂停 | 确认是否人为 stop |
| < | 高优先级 | 一般无需处理 |
| N | 低优先级(nice) | 一般无需处理 |
| s / l / + | 会话组长 / 多线程 / 前台进程 | 附加标志,无需处理 |
几个实用判断:僵尸进程(Z)少量存在是正常的(父进程还没来得及 wait),但如果数量持续增长,说明父进程有 bug 没有回收子进程,最终会耗尽 PID;D 状态进程无法用 kill -9 杀死,因为它卡在内核态 I/O,只能等 I/O 恢复或重启机器,所以发现大量 D 状态进程要立刻去查存储(磁盘满、NFS 挂了、RAID 卡故障);进程被 OOM Killer 杀掉不会在应用日志里留下痕迹,需要用 dmesg 或 journalctl -k 查内核日志确认。
# 查 OOM:内核日志里会有 "Out of memory: Kill process" dmesg -T | grep -i "out of memory" journalctl -k | grep -i -E "oom|killed process" # 看系统 OOM 总次数 grep -i oom /var/log/messages # 统计僵尸进程数量 ps -eo stat | grep -c '^Z' ps -eo pid,ppid,stat,cmd | awk '$3 ~ /^Z/' # 查某进程被杀前的内存峰值(systemd 服务) systemctl show myapp -p MemoryCurrent -p MemoryPeak 2>/dev/null systemctl status myapp | grep -i -E 'memory|killed'
三、自动重启:用 systemd 把服务托管起来
现代 Linux 发行版都用 systemd,它本身就自带完善的进程守护能力,不需要额外装 supervisor 之类的工具。核心就是 [Service] 段的几个 Restart 相关参数。
# /etc/systemd/system/myapp.service [Unit] Description=My Application After=network.target OnFailure=alert-mail@%n.service # 最终失败时触发告警单元 [Service] Type=simple # simple/forking/notify/oneshot,见下表 User=appuser WorkingDirectory=/opt/myapp ExecStart=/usr/bin/java -jar /opt/myapp/myapp.jar ExecReload=/bin/kill -HUP $MAINPID Restart=always # 退出就重启 RestartSec=5s # 重启前等待 5 秒 StartLimitBurst=5 # 300 秒内最多重启 5 次 StartLimitIntervalSec=300 # 超过上限后进入 failed 状态 # 资源限制,防止单服务拖垮整机 MemoryMax=2G # 硬限制,超过会触发 OOM MemoryHigh=1500M # 软限制,超过会加大回收压力但不杀进程 CPUQuota=200% # 最多用 2 核 LimitNOFILE=65535 # 最大打开文件数 TasksMax=4096 # 安全加固(按需) NoNewPrivileges=true PrivateTmp=true StandardOutput=journal StandardError=journal SyslogIdentifier=myapp [Install] WantedBy=multi-user.target
# 使配置生效 systemctl daemon-reload systemctl enable --now myapp # 查看状态、重启次数、最近日志 systemctl status myapp systemctl show myapp -p NRestarts journalctl -u myapp -f journalctl -u myapp --since "10 minutes ago" # 手动模拟崩溃,验证自愈是否生效 kill -9 $(pgrep -f myapp) sleep 8 systemctl status myapp # 应看到 Active: active (running) 且 NRestarts 增加
| 参数 | 可选值 | 说明 |
|---|---|---|
| Restart | no / always / on-success / on-failure / on-abnormal / on-abort / on-watchdog | always 最常用;on-failure 仅在非 0 退出时重启 |
| RestartSec | 如 5s、100ms | 重启间隔,避免高频重启刷爆日志 |
| StartLimitBurst | 整数,如 5 | 时间窗口内允许的最大重启次数 |
| StartLimitIntervalSec | 如 300 | 时间窗口长度,配合上一条使用 |
| Type | simple / forking / notify / oneshot | 前台进程用 simple;会 fork 到后台的老程序用 forking 并配 PIDFile |
| WatchdogSec | 如 30s | 需程序支持 sd_notify 心跳,超时自动重启(对付假死) |
| OOMPolicy | stop / continue | 被 OOM 后是否继续运行,通常保持默认 |
StartLimitBurst 是这套配置里最容易被忽略却最关键的一条。没有它,一个"启动即崩溃"的服务会进入无限重启循环:每秒重启一次、每次都写几条日志,几分钟内把磁盘写满、把 CPU 打高,还会触发海量告警。设了 StartLimitBurst=5 与 StartLimitIntervalSec=300 后,5 次尝试失败 systemd 就放弃,把服务置为 failed 并触发 OnFailure——既给了服务自愈的机会,又不会失控,还保证了人能收到通知。这个"有限自愈 + 最终告警"的模式是进程守护的标准做法。
另外 Type=forking 的老式守护进程(如某些老版本 MySQL、Nginx 自定义编译)必须配 PIDFile,否则 systemd 跟踪不到主进程,重启时会报"PID file not readable"或出现重复进程。判断方法:进程启动后前台是否一直占用终端,是则 simple,若自动转后台则 forking。
四、对付"假死":健康检查与看门狗
进程在、端口在,但接口返回 500 或超时——这种情况 Restart=always 完全无能为力,因为进程根本没退出。解决办法有两个层次:
层次一,应用内支持 systemd 的 watchdog。程序定期调用 sd_notify("WATCHDOG=1") 发送心跳,systemd 配 WatchdogSec=30s,超过 30 秒没心跳就杀掉重启。这需要改造程序,成本较高但最精准。
层次二,外部健康探测脚本 + 自动重启。用 cron 定时探测接口,连续失败 N 次就重启服务。这是最通用的方案,不需要改程序。
#!/bin/bash
# /opt/scripts/health_guard.sh —— 服务健康检查与自动重启
SERVICE=myapp
URL="http://127.0.0.1:8080/health"
STATE=/var/tmp/health_fail_count
LIMIT=3 # 连续失败 3 次才重启
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$URL" 2>/dev/null || echo 000)
if [ "$code" != "200" ]; then
cnt=$(cat "$STATE" 2>/dev/null || echo 0); cnt=$((cnt+1)); echo "$cnt" > "$STATE"
logger -t health-guard "$SERVICE 健康检查失败($code),连续失败 $cnt 次"
if [ "$cnt" -ge "$LIMIT" ]; then
echo 0 > "$STATE"
systemctl restart "$SERVICE"
printf "主机: $(hostname)\n服务: %s\n动作: 已自动重启\n原因: 连续 %s 次健康检查失败(HTTP %s)\n" \
"$SERVICE" "$LIMIT" "$code" | mail -s "[AUTO-HEAL] $(hostname) $SERVICE 已重启" ops@yourdomain.com
fi
else
echo 0 > "$STATE"
fi
# crontab:每分钟执行一次
# * * * * * /bin/bash /opt/scripts/health_guard.sh >/dev/null 2>&1
自动重启必须有配套的保护:一是重启频率上限(上面的 LIMIT 加上 systemd 的 StartLimitBurst),二是每次自动重启都要发通知,让运维知道系统做了什么,避免"服务反复重启三个月没人知道"。三是自动重启不要用在有状态服务上——数据库、消息队列的重启可能触发数据恢复、主从重选,风险远大于收益,这类服务应该只告警不自动重启。
五、长期监控:Prometheus 侧的进程指标
node_exporter 自带的进程指标比较有限,要做细粒度的进程监控需要 process-exporter。它按进程名分组统计 CPU、内存、读写 IO、线程数、僵尸子进程等。
# 启动 process-exporter(监听 9256)
# /etc/process-exporter/config.yml —— 定义进程分组规则
process_names:
- name: "{{.Comm}}" # 按进程名分组
cmdline:
- '.+' # 匹配所有进程
# 更实用的写法:只监控关心的进程
process_names:
- name: "java_apps"
cmdline:
- 'java\s+-jar'
- name: "nginx"
cmdline:
- 'nginx:\s+worker'
- name: "{{.Comm}}"
cmdline:
- 'mysqld'
- 'redis-server'
# systemd 服务片段
# ExecStart=/usr/local/bin/process-exporter \
# --config.path=/etc/process-exporter/config.yml --web.listen-address=:9256
# 常用 PromQL
# 某进程组是否至少有一个实例在跑(0 表示全挂了)
sum by (groupname) (namedprocess_namegroup_num_procs{groupname="java_apps"})
# 进程内存占用(字节)
namedprocess_namegroup_memory_bytes{groupname="java_apps", memtype="resident"}
# 进程 CPU 使用率(%),注意要用 rate
rate(namedprocess_namegroup_cpu_seconds_total{groupname="java_apps"}[5m]) * 100
# 进程打开文件数
namedprocess_namegroup_open_filedesc{groupname="java_apps"}
# 僵尸进程数
sum by (groupname) (namedprocess_namegroup_states{state="Zombie"})
# 告警:关键进程消失
- alert: ProcessDown
expr: sum(namedprocess_namegroup_num_procs{groupname="java_apps"}) == 0
for: 1m
labels:
severity: critical
annotations:
summary: "关键进程 java_apps 已全部退出"
如果不想引入 process-exporter,用 node_exporter 也能做基础的存活检测:node_systemd_unit_state{name="myapp.service",state="active"} 这个指标(需要 node_exporter 开启 systemd collector)直接反映 systemd 服务状态,值为 1 表示处于该状态。告警表达式写成 node_systemd_unit_state{name="myapp.service",state="active"} == 0,比查进程数更简洁可靠。
# 直接查 systemd 服务状态(node_exporter 需加 --collector.systemd)
node_systemd_unit_state{name="nginx.service",state="active"}
# 告警规则
- alert: ServiceNotActive
expr: node_systemd_unit_state{name=~"nginx.service|myapp.service",state="active"} == 0
for: 2m
labels:
severity: critical
六、建设进程监控的实施步骤
- 梳理关键进程清单:列出每个服务器上"必须活着"的服务,标注是否有状态(决定能否自动重启)。
- 把所有自研服务改成 systemd 托管,配置
Restart=always、RestartSec=5s、StartLimitBurst=5、StartLimitIntervalSec=300。 - 为服务加上资源限制(MemoryMax、LimitNOFILE),避免单进程拖垮整机。
- 配置
OnFailure告警单元,确保重启次数用尽后能通知到人。 - 用
kill -9做一次崩溃演练,确认服务能在 RestartSec 后自动拉起,且systemctl show -p NRestarts计数增加。 - 为无状态服务加健康检查脚本(cron 每分钟),连续失败 3 次自动重启并发通知。
- 部署 process-exporter 或启用 node_exporter 的 systemd collector,配置"进程消失"告警。
- 配置僵尸进程与 OOM 监控:
dmesgOOM 关键字告警、僵尸进程数持续增长告警。 - 每月复盘一次自动重启记录,把"反复重启"的服务列为技术债优先治理。
小结
进程监控的完整方案是三层叠加:systemd 提供基础自愈(Restart=always + StartLimitBurst 防止失控)、健康检查脚本解决"进程在但服务假死"、Prometheus 提供长期趋势与告警。其中最关键的设计细节是自愈必须有上限、有通知——无上限的自动重启会把小故障放大成磁盘写满、告警风暴的连锁事故,而无通知的自愈会掩盖真实问题,让技术债越积越多。避坑清单:一是只配 Restart=always 不配 StartLimitBurst,导致无限重启循环;二是 Type 选错(forking 程序写成 simple)导致 systemd 跟踪不到主进程、出现重复进程;三是对数据库等有状态服务开启自动重启,风险大于收益;四是只监控进程存在性不做健康检查,进程假死时监控全绿但业务已挂;五是忽略 OOM,应用被内核杀掉后只看到"进程没了"却不知道原因,需要查 dmesg | grep -i oom 确认。把这几点处理好,服务静默死亡的问题基本就能消除。
关键词:服务器进程监控、服务自动重启、systemd Restart、StartLimitBurst、进程健康检查、僵尸进程
企业QQ咨询




