登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  linux

systemd service Restart=always 为什么导致失败循环

来源:17golang原创

时间:2026-09-12 12:04:51 167浏览 收藏

Restart=always 写进 service 后,进程一退出就被再次拉起;如果程序本身每次启动都立刻失败,就会看到“启动—退出—再启动”的循环。它并不代表 systemd 能修复应用的根因。更准确的理解是:Restart=always 决定“退出后是否尝试重启”,RestartSec= 决定“隔多久再试”,而 StartLimitIntervalSec=StartLimitBurst= 决定“时间窗口内最多允许多少次启动”。

官方资料:https://github.com/systemd/systemd/blob/main/man/systemd.service.xml

要点速览
  • 先看 ResultExecMainStatus 和 journal,再改 Restart 参数。
  • 长期运行的守护进程通常优先用 Restart=on-failure,避免正常退出也被拉起。
  • 启动限流触发后,必须修复真实退出原因,再用 reset-failed 和一次受控启动复查。
很多朋友配完 systemd 服务加了 `Restart=always` 规则之后,发现服务崩了之后不是正常拉起,反而陷入反复启动又立刻退出的死循环,甚至把系统CPU、内存资源占满,排查半天找不到原因。
出现这类循环的核心原因大多是进程自身启动时就触发了前置错误,加上 systemd 的重启策略没加边界限制,每次刚拉起立刻报错退出,被规则再次触发重启,最终形成无限失败重启的闭环。

先分清 Restart=always 和启动限流各管什么

systemd 把服务重启拆成两个判断。服务进程退出、被信号终止或启动超时后,Restart=always 会把它纳入自动重启;但每次重启仍然是一次新的启动尝试,受到 unit 的启动速率限制。于是,一个立即退出的二进制会快速消耗启动次数,最后出现 Start request repeated too quickly,服务进入失败状态。

这也是为什么单纯把 Restart=always 改得更“强”通常没有用:它只会让 systemd 更积极地重复同一个错误。建议先把等待时间和上限写清楚:

[Unit]
Description=Example worker service
# 只描述启动顺序;依赖失败仍要回到对应日志定位
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=60s
StartLimitBurst=3

[Service]
Type=simple
ExecStart=/opt/example/bin/worker --config /etc/example/worker.yaml
# 守护进程异常退出时重启,正常退出不强行拉起
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

StartLimitIntervalSecStartLimitBurst 放在 [Unit]RestartRestartSec 放在 [Service]。如果这个程序是一次性任务,正常退出本来就是成功结果,不应拿 Restart=always 把它变成长驻服务。

systemd Restart=always、RestartSec 和 StartLimit 启动限流关系示意图
图1:systemd 重启策略与启动限流的结构示意图,帮助区分“会不会重启”和“允许多快重试”。

用退出码和日志找到真正的失败原因

看到循环时不要先执行多次 restart 把现场冲掉。先保存状态与最近日志:

# 读取当前状态、主进程退出码和最近一段日志
sudo systemctl status example-worker.service --no-pager
sudo systemctl show example-worker.service \
  -p Result -p ExecMainCode -p ExecMainStatus -p NRestarts
sudo journalctl -u example-worker.service -b -n 80 --no-pager

如果 Result=exit-codeExecMainStatus=1,优先检查配置文件、工作目录、权限和环境变量;如果是 status=203/EXEC,重点看 ExecStart 路径、执行权限和解释器;如果日志只剩“repeated too quickly”,说明限流是结果,不是最初的根因。NRestarts 可以帮助确认是否真的发生了多次自动拉起。

证据通常说明下一步
ExecMainStatus=1程序主动以非零码退出看应用日志、配置和参数
203/EXEC执行文件或执行方式有问题检查路径、权限、shebang
Start request repeated too quickly启动速率限制已触发先修复前两项,再复位失败状态
systemd 服务退出码、重启次数与启动限流诊断状态示意图
图2:服务失败诊断状态示意图,按退出码、重启次数和限流提示区分根因。

依赖关系会放大问题,但不会替代根因

After=network-online.target 只表达排序,不能保证网络服务已经满足应用的业务条件;Wants= 会在当前服务启动时顺带拉起目标,但目标失败不一定让本服务失败;Requires= 的失败影响更强,依赖停止时本服务也可能被停止。排查时应同时看依赖树:

# 查看 unit 的依赖与排序关系,避免把依赖链误认为重启根因
sudo systemctl list-dependencies example-worker.service
sudo systemctl cat example-worker.service
sudo systemd-analyze verify /etc/systemd/system/example-worker.service

如果服务依赖了一个同样会失败的 unit,先修复被依赖方;如果配置里存在相互触发的 Wants= 或外部 watchdog,也要确认是谁真正发起了下一次启动。不要用增加 StartLimitBurst 来掩盖依赖链上的配置错误。

修改后怎样安全恢复并确认没有再循环

确认配置已保存后,先让 PID 1 重新读取 unit,再清掉“因限流留下的失败状态”,最后只启动一次:

# 重新载入 unit,清除失败标记,然后进行一次受控启动
sudo systemctl daemon-reload
sudo systemctl reset-failed example-worker.service
sudo systemctl start example-worker.service
sudo systemctl status example-worker.service --no-pager
sudo journalctl -u example-worker.service -b -n 40 --no-pager

成功标准不是“命令返回成功”这么简单,而是服务保持 active (running)NRestarts 不再持续增加,日志中没有新的退出与限流提示。如果程序确实会因临时依赖失败而退出,可以保留 Restart=on-failure,把 RestartSec 调到足以等待依赖恢复的值;不要无限放大限流窗口,让错误变成高频重启。

相关问题

Restart=always 和 Restart=on-failure 有什么区别?

always 连正常退出也会尝试重启;on-failure 主要针对非零退出、异常信号和超时。长时间运行的服务通常先选后者。

为什么 Restart=always 仍然不重启?

可能已触发启动速率限制,也可能是手动停止、条件判断未满足或 unit 根本没有成功进入可重启的服务状态。先看 journal 和 systemctl show,不要只看配置文件。

改完 service 文件为什么没有生效?

unit 文件变更需要 systemctl daemon-reload;它只重新载入配置,不等于已经启动新进程。之后再按需执行 reset-failed 和一次 start

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>