systemd 服务写完 unit、执行 enable,过一会儿再看状态却是 failed,日志里只有一行「Start request repeated too quickly」。这类问题多半不在程序本身,而在三个开关的配合:Type= 决定何时算作启动成功,Restart= 决定何时再次拉起,启动限流决定拉起的次数上限。本文按「是什么、原理、怎么配、怎么排查」的顺序讲完这三块。
三个开关各自管什么
一个 service 单元的行为可以拆成三段时间。启动阶段由 Type= 判定「起来了没有」;运行阶段由 Restart= 判定「挂了要不要再拉」;而每一次拉起都要经过 StartLimitIntervalSec= 与 StartLimitBurst= 组成的限流闸门。三者串在同一条链上,任何一环理解错,现象都会长得像「程序有 bug」。
先把默认值摆出来,后面的误区大多源于这些默认值:Type= 默认是 simple(在指定 ExecStart= 且未设 BusName= 时),Restart= 默认是 no,RestartSec= 默认 100 毫秒,启动限流默认 10 秒内允许 5 次。
把默认值串起来,就能还原一类常见故障。程序启动即退出,systemd 在 100 毫秒后再次拉起,十秒内连撞五次,限流生效,单元进入 failed,状态行只剩 start-limit-hit。表面看是「服务起不来」,实际是三层默认值叠加的结果。
Type=:什么时候算「启动成功」
Type= 的取值有 simple、exec、forking、oneshot、dbus、notify、notify-reload、idle。它们的差别只在一件事:systemd 在哪一刻认为单元已经起来,并开始启动后续单元。官方在 systemd.service 手册页里逐个给出了定义。
- simple:主进程 fork 出来就判定启动完成,此时还没执行 execve,可执行文件甚至可能不存在。
- exec:等主进程真正 exec 成功才算启动,文档明确写着这通常是更合适的选择。
- oneshot:主进程退出后才算启动完成,适合一次性任务;未写 Type= 与 ExecStart= 时的隐含默认值也是它。
- notify:等程序通过 sd_notify 发出 READY=1 才算启动完成,适合自己有初始化阶段的守护进程。
- forking:父进程退出后就算启动完成,文档对该类型的评价是「不建议使用」。
simple 的副作用值得单独说。man 手册写明,simple 服务执行 systemctl start 时会报成功,即便二进制无法执行——比如 User= 指定的账户不存在,或者路径写错。启动日志里看不到报错,只有等到进程真的挂掉,才会暴露出来。
如果程序本身支持 sd_notify,用 Type=notify 可以把「初始化完成」显式告知 systemd。配合 WatchdogSec= 还能识别卡死但未退出的进程,代价是需要程序或在包装脚本里发送通知。
Restart=:七种取值与触发矩阵
Restart= 可取 no、on-success、on-failure、on-abnormal、on-watchdog、on-abort、always。区别在于哪些退出原因会触发重启,下面的表按官方文档的退出原因分类整理。
| 退出原因 | always | on-failure | on-abnormal | on-abort | on-watchdog | on-success |
|---|---|---|---|---|---|---|
| 干净退出(退出码 0 或 SIGHUP/SIGINT/SIGTERM/SIGPIPE) | 是 | 否 | 否 | 否 | 否 | 是 |
| 非零退出码 | 是 | 是 | 否 | 否 | 否 | 否 |
| 异常信号(含核心转储) | 是 | 是 | 是 | 是 | 否 | 否 |
| 操作超时(启动、重载、停止) | 是 | 是 | 是 | 否 | 否 | 否 |
| 看门狗超时 | 是 | 是 | 是 | 否 | 是 | 否 |
| 因 OOM 被终止 | 是 | 是 | 是 | 否 | 否 | 否 |
对长期运行的服务,文档给出的建议是 on-failure。它覆盖非零退出、异常信号、超时、看门狗和 OOM,同时放过「程序自己正常退出」这种情况,避免一个设计上会退出的任务被反复拉起。若服务需要保留自行退出的能力,on-abnormal 更合适。
还有两个例外要记住。systemctl stop 主动停止不会触发重启;RestartPreventExitStatus= 里的退出码或信号也不会触发。反过来,RestartForceExitStatus= 里的状态会被强制重启。Type=oneshot 的服务则直接拒绝 always 与 on-success。
启动限流:为什么改完还是起不来
重启并不是无条件的。每一次启动都会计入限流窗口,默认窗口 10 秒、允许 5 次。超过之后单元进入 failed,日志出现 start-limit-hit,此时再手动 start 也会被拒。这就是「改完配置仍然起不来」的常见原因——不是配置没生效,而是窗口还没过去。限流的定义见 systemd.unit 手册页。
这里有两个容易忽略的细节。其一,限流对「启动」计数,不区分自动重启和手动 start。其二,systemctl reset-failed 会清空该服务的重启速率计数器,管理员想立刻手动拉起时,这是标准动作。
RestartSec= 默认只有 100 毫秒,配合 5 次/10 秒的限流,意味着一个必挂的程序在 0.5 秒内就能把额度用完。要观察真实的重启间隔,可以把 RestartSteps= 与 RestartMaxDelaySec= 一起用上。文档给的例子是 RestartSec=10s、RestartSteps=4、RestartMaxDelaySec=160s,实际间隔依次为 10、20、40、80、160 秒,之后稳定在 160 秒。RestartSteps= 默认 0,即关闭指数退避;RestartMaxDelaySec= 默认 infinity。
一份可套用的模板与验证步骤
下面这份配置把三段职责分开设置,参数值可直接替换成自己的路径与账户。
| 段落 | 指令 | 取值 |
|---|---|---|
| [Unit] | After= / Wants= | network-online.target |
| [Unit] | StartLimitIntervalSec= / StartLimitBurst= | 60 / 5 |
| [Service] | Type= | exec |
| [Service] | User= / WorkingDirectory= | app / /opt/app |
| [Service] | ExecStart= | /opt/app/bin/app --config /etc/app/config.toml |
| [Service] | Restart= / RestartSec= | on-failure / 2s |
| [Service] | RestartSteps= / RestartMaxDelaySec= | 4 / 30s |
| [Service] | TimeoutStopSec= | 20s |
| [Install] | WantedBy= | multi-user.target |
落地时按顺序走五步:用 systemd-analyze verify 检查语法;systemctl daemon-reload 让改动生效;systemctl enable --now 启动并设开机自启;systemctl show -p NRestarts -p Result 看重启次数与结果;journalctl -u 服务名 -b 查看本次引导的日志。若 Result 显示 start-limit-hit,先 reset-failed 再排查根因。
托管方式不止 systemd 一种。Windows 上让程序常驻要另走服务注册流程,站内 Syncthing 2.1 上手记录过完整步骤;想长期观察重启次数与资源曲线,可配合 Beszel 轻量服务器监控做告警。涉及网络的服务常在 After= 上出问题,WireGuard 隧道原理与配置那篇可作为启动顺序的参照。
还有一处默认值容易被忽略:KillMode= 默认是 control-group,停止时会清理整个 cgroup 的进程。若改成 process,只杀主进程,子进程可能残留并占着端口,表现为下一次启动报地址已被占用。TimeoutStopSec= 默认 90 秒,超时后才发 SIGKILL,缩短这个值能加快重启节奏。
五个常见误区
- 把 Type=simple 的启动成功当成程序正常运行。二进制缺失、User= 不存在时它同样报成功,换成 Type=exec 能提前暴露问题。
- 以为 Restart=always 会处理一切。主动 stop 不会重启,oneshot 也不接受 always,需要自行退出的服务更适合 on-failure。
- 保留 RestartSec= 默认的 100 毫秒。崩溃循环会瞬间耗尽限流额度,并把日志刷得难以阅读。
- 看到 start-limit-hit 就去改程序。先确认是不是限流拦住,reset-failed 后再观察一轮。
- 手改 unit 文件却不 daemon-reload。修改不会生效,状态看起来像「配置无效」。
本文面向需要长期托管后台进程的开发与运维人员。判断顺序可以简化成一句话:先确认 Type= 是否如实反映启动,再确认 Restart= 覆盖了你关心的失败类型,然后用限流与退避把重启频率压到可观测的范围内。
相关地址
以下为文中引用的规范与手册页,参数默认值以对应版本的 man 手册为准。
- systemd.service 手册页(freedesktop.org):https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html
- systemd.unit 手册页(freedesktop.org):https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html
- systemd.kill 手册页(freedesktop.org):https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html
- systemd-system.conf 手册页(freedesktop.org):https://www.freedesktop.org/software/systemd/man/latest/systemd-system.conf.html
- SUSE 知识库:Start request repeated too quickly 排查(suse.com):https://www.suse.com/support/kb/doc/?id=000019750
© 版权声明
文章版权归作者所有,转载需注明出处。未声明原创的皆为网络收集,如有侵权请邮箱lishiwenhuagx@sina.com联系下架处理。




