服务无故重启?从systemd日志到OOM Killer的完整排查指南 📅 发布时间:2026/8/30 4:07:40 👁 浏览次数: 很多后端开发或运维同学应该都有过类似的时刻凌晨被监控电话叫醒打开服务器一看进程还在但日志停在了几分钟前系统时间似乎跳了一段好像凭空发生了一次重启。每个人心里都会蹦出一句I have no idea how that happened。如果以这句话结束排查那这个故障就会永久变成“玄学”。真正有价值的做法是用一套能重复执行的思路把“莫名其妙”变成“有迹可循”。这篇文章就以一次“服务凌晨自动重启”的真实排查过程为背景分享一套通用的线上故障定位方法怎么建立时间线、怎么查 systemd 日志、怎么看 OOM、怎么确认定时任务以及如何通过审计日志和脚本优化避免同类问题。文中所有命令都可以直接复制命令输出均做了脱敏处理大家按实际环境观察到的内容对照即可。1. 什么是 I have no idea how that happened 式故障1.1 神秘故障的几种典型类型“我不知道怎么发生的”在技术团队里非常常见尤其是线上服务偶发异常时。典型表现有下面几类服务进程在某个时间点被直接杀死系统又自动拉起前台看起来只是“重启了一下”。应用日志某段时间只有心跳业务请求全部失败但代码没有发布配置也没有改过。数据库连接数突然被打满过几分钟自动恢复找不到发起方。磁盘空间莫名升高查看目录时却找不到大文件因为文件可能已被删除但仍被进程占用。定时任务执行后产生异常数据但执行记录被覆盖或清理无法直接追溯。这些故障的共同点是现象清晰根因隐藏。如果只根据现象“重启一下服务”或者“清一下磁盘”问题大概率还会再次出现。1.2 现象不等于根因排查时最容易犯的错误是把现象当成根因。例如服务发生 OOM进程被系统杀死你看到的可能是“服务重启了”。于是你重启服务继续观察。但真正的根因可能是某个定时备份脚本触发了大量内存申请也可能是日志文件增长过快导致磁盘写入压力更可能是 JVM 堆参数设置不合理。所以当所有人都在说“I have no idea how that happened”时第一步不是猜测而是回到服务器上收集证据。保留现场比立刻解决更重要。1.3 排查的四条核心原则我把这套方法总结为四句话怀疑一切不要轻信“没人操作过”要通过登录记录、命令审计去验证。按时间线推进先确定“故障发生时刻”再围绕这个时间点搜集前后数据。先看系统再看应用先确认系统有没有重启、内存和磁盘是否异常再进入业务日志分析。最小化操作排查过程中尽量使用只读命令不在生产环境随意执行重启动作或破坏性命令。这套原则看起来朴素但非常实用。接下来我们先把环境理清再走一遍完整排查流程。2. 环境准备与排查工具2.1 最低环境要求本文案例基于 Linux 环境常见的发行版都可以例如 CentOS 7/8、Ubuntu 20.04/22.04 等。不同发行版日志路径或 systemd 版本略有差异但核心概念一致。要求如下Linux 操作系统systemd 作为 init 系统。具备 sudo 权限但排查时优先使用只读命令。服务由 systemd 管理存在对应的 unit 文件。已开启审计服务 auditd未开启时也可以用last、history、journalctl补位。如果你的环境是其他系统命令输出可能略有不同但思路完全一致。2.2 常用命令与日志文件在开始实战前先列出我们最常用的工具。你可以先保存下面这张表。目的命令说明查看系统启动时间uptime/who -b判断系统是否发生过重启查看内核日志journalctl -k/dmesg查找 OOM、硬件异常、文件系统错误查看服务日志journalctl -u 服务名查看 systemd 管理的服务运行记录查看登录历史last/lastb排查是否有登录操作查看命令历史history//home/用户/.bash_history排查人工误操作查看定时任务crontab -lcat /etc/crontab排查自动触发任务查看系统资源free -hdf -hdu -sh *分析内存、磁盘占用查看审计日志ausearch -m USER_CMD/cat /var/log/audit/audit.log如果配置了 auditd可追查执行的命令查看进程状态systemctl status 服务名查看服务的进程号、启动时间和重启次数2.3 先做好现场保护排查有一个前提不要急着修改系统状态。如果有条件建议先做以下记录记录当前时间、服务状态、日志文件大小。把关键日志复制一份到安全目录例如/tmp/diagnose/。使用journalctl导出故障前后数小时的日志方便后续分析。mkdir -p /tmp/diagnose journalctl --since 2025-01-10 01:50:00 --until 2025-01-10 02:40:00 /tmp/diagnose/service.log journalctl -k --since 2025-01-10 01:50:00 --until 2025-01-10 02:40:00 /tmp/diagnose/kernel.log free -h /tmp/diagnose/memory.txt df -h /tmp/diagnose/disk.txt这样即使后续有人误操作或自动化清理原始证据也不会丢失。3. 先搞清楚“发生了什么”建立事故时间线3.1 时间线是排查的第一张地图遇到“I have no idea how that happened”时我最先做的事情不是打开业务日志而是确定故障发生的准确时间点。这里的“时间点”有两个用户/监控发现异常的时间。系统或服务实际上发生变化的时间。这两个时间点可能相差几分钟甚至几小时。例如应用日志显示请求在某时刻全部超时但服务进程实际在 3 分钟前已经被 OOM killer 杀掉。只有先还原时间线才能避免被表面现象带偏。我们可以用一条简单的流程来描述用户报告异常凌晨 2:21系统告警推送“服务不可用”。监控曲线显示凌晨 2:17 响应成功率为 0。服务日志显示凌晨 2:17:03 进程退出。内核日志显示凌晨 2:17:02 出现 OOM 事件。定时任务显示凌晨 2:15 开始执行数据库备份脚本。通过这个顺序根因基本浮出水面。3.2 查看系统启动时间和服务重启记录如果怀疑系统发生过重启先执行下面几条命令uptime who -b last reboot | head -10 systemctl status 你的服务名 | head -20uptime能看到当前系统已运行时长who -b能看到本次系统启动时间last reboot能列出历史重启记录。如果系统根本没有重启就重点看服务本身的重启原因。3.3 查看 systemd 服务日志systemd 会记录服务启动、停止、退出、重启的详细过程。比如服务退出时如果有Main process exited, codekilled, status9/KILL说明是被某个信号杀死的。常用命令如下journalctl -u your-service --since 2025-01-10 01:50:00 --until 2025-01-10 02:40:00输出大致是Jan 10 02:17:01 hostname systemd[1]: Started Your Service. Jan 10 02:17:03 hostname systemd[1]: your-service.service: Main process exited, codekilled, status9/KILL Jan 10 02:17:03 hostname systemd[1]: your-service.service: Failed with result signal. Jan 10 02:17:04 hostname systemd[1]: your-service.service: Scheduled restart job, restart counter is at 3.看到status9/KILL基本可以判断是进程被强制 kill。至于是谁 kill 的还需要继续往内核日志看。3.4 登录记录与命令审计如果所有日志都显示“没有人为操作”不要急着下结论先用last查看登录历史再用history和审计日志查看用户命令。last -20 last -i | head -20 ausearch -m USER_CMD -ts 02:00 -te 02:30如果审计日志里没有发现可疑命令再把重点转移到自动任务和系统异常。3.5 定时任务排查服务重启还有一个非常常见的隐藏原因某个 cron 任务在指定时间执行了 systemctl restart或者脚本内部出错后误执行了重启动作。查看定时任务crontab -l cat /etc/crontab ls -l /etc/cron.d/如果使用了 Ansible、SaltStack 等自动化平台也需要同步排查对应平台的执行记录。很多“没人操作”的假象其实是定时任务或自动化平台在背后操作。4. 完整实战案例服务凌晨自动重启下面我们模拟一个完整案例。某天凌晨 2:21监控系统告警demo-service不可用。应用负责人打开服务器后服务已经恢复日志里只看到“进程退出后又被拉起”于是发出那句经典提问I have no idea how that happened。接下来按步骤复现排查过程。4.1 故障现象服务demo-service一个 Java 后端服务由 systemd 管理。监控告警凌晨 2:21 连续 3 次健康检查失败。当前状态服务已自动恢复。应用日志从 2:17:02 开始没有新日志2:17:05 后服务重新启动。4.2 第 1 步确认服务重启时间点首先通过 systemd 查看服务状态和最近启动时间。systemctl status demo-service输出示例● demo-service.service - Demo Service Loaded: loaded (/etc/systemd/system/demo-service.service; enabled; vendor preset: disabled) Active: active (running) since Fri 2025-01-10 02:17:05 CST; 4min ago Main PID: 12820 (java) Tasks: 45 Memory: 1.2G结合journalctl查看服务详细日志journalctl -u demo-service --since 2025-01-10 02:10:00 --until 2025-01-10 02:25:00可以发现关键行Jan 10 02:17:03 demo systemd[1]: demo-service.service: Main process exited, codekilled, status9/KILL Jan 10 02:17:03 demo systemd[1]: demo-service.service: Failed with result signal. Jan 10 02:17:04 demo systemd[1]: demo-service.service: Scheduled restart job, restart counter is at 3. Jan 10 02:17:05 demo systemd[1]: demo-service.service: Started Demo Service.到这里可以确定服务不是自然退出而是被kill -9强杀。系统会自动重启是因为 unit 文件里配置了Restartalways。4.3 第 2 步查看内核日志与 OOM被kill -9的原因大概率是 Linux 的 OOM Killer。我们查看同一时间窗口的内核日志journalctl -k --since 2025-01-10 02:15:00 --until 2025-01-10 02:20:00输出示例Jan 10 02:17:02 demo kernel: Out of memory: Killed process 11987 (java) total-vm:6259136kB, anon-rss:1402420kB, file-rss:0kB, shmem-rss:0kB Jan 10 02:17:02 demo kernel: oom-kill:constraintCONSTRAINT_NONE, noma_pages0, cpusetmems_allowed0 Jan 10 02:17:02 demo kernel: Memory cgroup out of memory: Killed process 11987 (java)输出中明确出现了Out of memory。这说明服务进程被系统 OOM Killer 选中并强制结束。为什么内存会不足我们需要继续去看同一时间点有没有其他进程大量占内存。检查系统内存free -h查看当时是否有高内存消耗的进程journalctl -k --since 2025-01-10 02:15:00 --until 2025-01-10 02:18:00 | grep -i oom\|memory还可以查看系统日志中 OOM 之前是否有 mysqldump、备份脚本、日志切割等任务在运行。4.4 第 3 步检查定时任务和备份脚本查看 crontabcrontab -l cat /etc/crontab ls -l /etc/cron.d/假设我们发现系统中有一个每日备份任务0 2 * * * root /opt/backup/backup_db.sh该任务的执行时间正好是凌晨 2:00。我们打开脚本检查cat /opt/backup/backup_db.sh脚本内容大致是#!/bin/bash set -e MYSQL_HOST127.0.0.1 BACKUP_DIR/data/backup DATE$(date %F) mysqldump -h $MYSQL_HOST -u backup_user -pxxx --single-transaction --quick --all-databases $BACKUP_DIR/db_$DATE.sql gzip $BACKUP_DIR/db_$DATE.sql这个脚本看起来没什么问题但结合 OOM 现象需要关注两个细节mysqldump备份所有数据库时会在内存中做大量数据读取如果数据库表很大内存占用会快速上升。脚本启动时间 2:00备份大量数据大约在 2:16 进入高峰期正好和 2:17 OOM 时间吻合。我们可以通过journalctl查看 2:15-2:17 是否存在 mysqldump 进程的日志journalctl --since 2025-01-10 02:10:00 --until 2025-01-10 02:18:00 | grep -i mysqldump如果服务器内存本身比较紧张Java 服务占用 1.4GMySQL 占用 600Mmysqldump 再一次性申请大量内存就很容易触发 OOM。4.5 第 4 步确认根因并调整配置到这里根因已经清晰root cause备份脚本backup_db.sh执行mysqldump时内存申请过大触发 Linux OOM Killer。被选中进程demo-serviceJava 服务。服务自动恢复systemd 的Restartalways配置生效。解决方案可以从几个方向入手优化备份脚本降低内存消耗。限制mysqldump运行时的资源占用。为关键服务调整 OOM 优先级降低其被误杀的概率。增加系统内存或为应用和服务设置独立的内存 cgroup。调整 OOM 优先级是最快的缓解手段。我们可以编辑服务 unit 文件[Service] Restartalways RestartSec10 OOMScoreAdjust-500其中OOMScoreAdjust-500表示降低 OOM 被杀的概率数值范围是 -1000 到 1000越小越不容易被 OOM Killer 选中。修改后执行sudo systemctl daemon-reload sudo systemctl restart demo-service但这只是缓解治本仍要优化备份脚本。优化后的备份脚本核心部分如下#!/bin/bash set -e MYSQL_HOST127.0.0.1 BACKUP_DIR/data/backup DATE$(date %F) # 使用 nice 降低进程优先级并通过 ionice 降低磁盘占用 nice -n 10 ionice -c2 -n7 mysqldump \ -h $MYSQL_HOST \ -u backup_user \ -pxxx \ --single-transaction \ --quick \ --max-allowed-packet1G \ --set-gtid-purgedOFF \ --all-databases $BACKUP_DIR/db_$DATE.sql gzip $BACKUP_DIR/db_$DATE.sql还可以考虑分库备份避免一次备份所有数据库。使用mydumper等并行备份工具。将备份执行时间调整到业务低峰。在 cgroup 中限制备份任务的内存上限。4.6 第 5 步验证与长期监控修改完成后可以继续观察下一个备份周期在第二天凌晨 2:00-2:30 查看journalctl -k是否还有 OOM 记录。使用systemctl status demo-service查看服务重启次数。为服务增加内存监控例如在 Prometheus 中采集process_resident_memory_bytes。同时建议在 fault 复盘中补充一条经验不要只看到服务重启就重启了事一定要查 OOM、查定时任务、查内存申请。5. 常见问题与排查思路即使掌握了方法线上排查仍然会遇到各种意外。我把常见问题整理成一张排查表方便大家对照。问题现象常见原因解决思路服务被 kill状态码为 9OOM Killer、人为 kill、自动化平台误操作看journalctl -k、审计日志、控制节点操作记录服务日志中断但没有进程退出磁盘满、网络中断、JVM 长时间 GC使用df -h查磁盘使用dmesg查 IO 错误日志显示“Process exited”但原因不明服务自身崩溃、健康检查失败查看应用错误日志、coredump确认退出码服务自动重启次数过多Restartalways 启动即崩溃先看启动阶段日志再考虑去掉自动重启定时任务触发了服务重启脚本逻辑错误、环境变量为空给脚本增加 set -u并在执行前打印关键变量审计日志中没有用户操作auditd 未开启或未覆盖到该用户完善审计规则覆盖 sudo、登录、关键路径时区不一致导致时间错位服务器未同步时间或时区设置错误统一时区使用 NTP/chrony 同步时间内存明明够用还是发生 OOMcgroup 内存限制、swap 配置不合理查看 cgroup 信息确认进程是否在容器内5.1 通用排查顺序如果你被一句 “I have no idea how that happened” 卡住建议按以下顺序操作记录当前时间、服务状态、日志长度。打开journalctl -u 服务名找到服务状态变更点。打开journalctl -k查看内核是否报告 OOM、硬件错误。用last和审计日志排查登录与命令执行。用crontab -l检查定时任务。用free -h、df -h检查系统资源。最后结合应用日志还原完整调用链。6. 最佳实践与工程建议6.1 日志和审计要提前配置很多事故定位困难都是因为日志缺失。建议在系统层面至少做三件事开启 systemd journal 持久化避免重启后日志丢失。部署 auditd并设置关键目录、关键命令的审计规则。对 sudo 操作、cron 任务、服务启停进行统一记录。例如在 auditd 中记录 sudo 行为sudo auditctl -w /usr/bin/sudo -p x -k sudo_cmd注意在生产环境执行审计规则前需要确认与安全策略一致。更标准的方式是写入/etc/audit/rules.d/audit.rules并重启 auditd 或加载规则。6.2 系统内存与 OOM 防护关键服务建议设置 OOMScoreAdjust避免在内存紧张时第一个被杀。但更要关注整体内存水位通过监控观察现网内存真实使用量。避免在一个物理机上部署多个内存型应用。合理设置 JVM-Xmx不要超过容器或物理机实际内存的 70%。使用 swap 时需要评估性能影响不能把 swap 当作内存扩容手段。6.3 备份脚本与定时任务规范定时任务是“神秘故障”的高发区。建议养成以下习惯脚本开头增加set -eu变量未定义时立即报错。关键命令执行前打印变量方便审计。避免在脚本中直接调用systemctl restart如果必须调用要记录原因和触发条件。对备份等资源密集型任务使用nice、ionice或 cgroup 限制资源占用。6.4 监控告警与事故复盘没有监控一切排查都等于盲人摸象。建议至少覆盖服务进程状态和探活。系统内存、CPU、磁盘使用率。OOM 事件和内核错误。cron 任务执行时长和退出码。事故复盘中可以重点问三个问题触发点是什么为什么系统会选择这个进程作为牺牲品需要哪些自动化和降级措施才能避免下次再发生7. 我学到的几点经验写到这里再回头看那句I have no idea how that happened你会发现它并不是结论而是排查的起点。真正让故障不再发生的方法并不是运气而是平时就写好日志、配置好监控、保留好审计记录在故障发生时保持冷静按时间线一步步缩小范围。如果你刚接手一个项目建议先处理三件事检查服务的 systemd 配置是否存在不合理的自动重启检查服务器是否配置了 OOM 监控检查定时任务里是否有容易引发资源争抢的脚本。这三件事做完大部分“不知道怎么就重启了”的故障都会提前暴露。如果这篇文章里的排查思路对你有帮助可以收藏备用当你下次遇到类似问题也欢迎把排查过程记录下来分享。毕竟每一次“I have no idea how that happened”最后都能变成一份更有价值的故障复盘。