场景概述
服务反复崩溃时,日志分散在三处,排查顺序也应当固定:systemd 侧给出退出码与是否触发启动频率限制,应用侧给出崩溃前的最后输出,内核侧负责解释「进程为什么突然不见了」(OOM 击杀或段错误)。先看退出码能大幅缩小范围——退出码是应用自己返回的,说明它遇到了业务或配置问题;被信号杀掉(如 137 对应 SIGKILL),则多半是资源或运维动作。
场景信息
| 触发条件 | systemd 服务反复重启,或启动后短时间内再次退出 |
|---|---|
| 排查目标 | 确定崩溃原因属于应用逻辑、资源限制还是外部杀进程 |
| 工具链 | systemctl status 与 show 看退出码 → journalctl -u 看应用输出 → dmesg 与 journalctl -k 看内核处置 |
| 退出码速查 | 1 常见于通用错误;126 命令不可执行;127 找不到命令;137 与 143 分别对应被 SIGKILL 与 SIGTERM 终止;203 表示 systemd 无法执行命令 |
常见崩溃类型
- 配置或依赖问题:启动时连不上数据库、证书读不到,进程自行退出并返回非 0
- 内存不足:被内核 OOM 击杀,退出码表现为被信号终止,dmesg 有明确记录
- 代码缺陷:空指针、断言失败或未捕获异常导致进程段错误或异常退出
- 启动频率限制:短时间内反复重启触发 StartLimitBurst,systemd 拒绝再拉起
- 依赖服务未就绪:未声明 After 与 Requires,启动顺序不稳定导致间歇性失败
- 文件描述符或线程数限制:LimitNOFILE 等设置过低,运行一段时间后崩溃
四步定位命令
四步定位命令
# 第 1 步:退出码与重启次数
systemctl status myapp --no-pager -l
systemctl show myapp -p ExecMainStatus,ExecMainCode,NRestarts,Result
# 第 2 步:应用崩溃前的最后输出
journalctl -u myapp -n 200 --no-pager
journalctl -u myapp --since '10 min ago' | grep -iE 'error|exception|fail|panic|assert'
# 第 3 步:判断是否被内核处置
dmesg -T | grep -iE 'oom|killed process|segfault' | tail
journalctl -k --since '10 min ago' | grep -i oom
# 第 4 步:核对单元定义与运行环境
systemctl cat myapp
sudo -u appuser /opt/myapp/bin/start --config /etc/myapp/app.conf; echo "exit=$?"
ulimit -n; systemctl show myapp -p LimitNOFILE退出码与方向
| 退出码 | 来源 | 排查方向 |
|---|---|---|
| 1 | 应用自身返回 | 配置错误、依赖不可用等业务层问题 |
| 126 / 127 | shell 语义 | 可执行权限不足或解释器、命令不存在 |
| 137 | 被 SIGKILL 终止 | 常见于 OOM 击杀或人工强制停止 |
| 143 | 被 SIGTERM 终止 | 正常停止流程或编排系统下发终止 |
| 203 | systemd 保留段 | ExecStart 路径、权限或可执行格式有问题 |
| Result=start-limit-hit | systemd 状态 | 触发启动频率限制,需 reset-failed 后排查根因 |
判断「谁杀了进程」有明确的分工:退出码 137 与 dmesg 里对应的 killed process 记录同时出现,才能确认是 OOM 击杀;只有 137 而没有内核记录时,更可能是人工操作或编排系统下发的强制停止。两者的处置方向完全不同——前者要调内存与限额,后者要查是谁、为什么下发了停止动作,混为一谈会让排查长期停在错误方向上。
频繁重启与启动频率限制:systemd 默认在 10 秒内重启超过 5 次会进入 start-limit-hit 状态并停止尝试,此时 systemctl start 也会失败。先 systemctl reset-failed 清除状态,再排查根因;把 Restart 改为 on-failure 并合理设置 RestartSec,可以避免无效重启掩盖真实报错。
排查阶段先把自动重启停掉(systemctl stop),否则单元会不断重启并滚动刷掉日志缓冲,既看不清崩溃前的输出,也可能因为反复占用资源引发更大范围的影响。确认根因后再恢复并观察一段时间。
先分清失败阶段
| 阶段 | 特征 | 证据来源 |
|---|---|---|
| 启动前 | 单元与路径校验失败 | 单元状态与执行错误码 |
| 启动中 | 进程立即退出 | 退出码与标准错误 |
| 运行中 | 运行一段时间后崩溃 | 崩溃前日志与转储 |
| 被外部终止 | 收到信号而结束 | 内核与资源限制记录 |
| 拒绝再拉起 | 触发启动频率限制 | 单元状态提示 |
排查前先把自动重启关掉或延长重启间隔,否则日志被反复滚动覆盖,崩溃前的关键输出很难还原;同时记录每次崩溃的时刻,与内存、磁盘、下游依赖的波动对齐,往往能直接看出是资源周期还是业务高峰触发。