Skip to content

systemd 强杀服务导致配置文件损坏的排查 ​

一个由 systemd 托管的后台服务,重启后读到了自己损坏的 JSON 配置文件,无法启动。本文记录这次排查的完整过程:从报错日志确认文件损坏,复盘出两个损坏场景——直接断电,以及 systemd 停机超时后强杀进程导致写入中断,最后给出退出顺序与原子写入两个修复方向。对任何「服务退出时要落盘配置」的场景都适用。

现象 ​

参数服务启动时报错,配置文件 parameter.json 解析失败:

[2026-05-27 16:20:34.254] [error] JSON parse failed: [json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal
[2026-05-27 16:20:34.254] [error] /opt/controller/cfg/parameter.json, Json file has duplicate keys.
[2026-05-27 16:20:34.254] [error] Failed to load parameter file: '/opt/controller/cfg/parameter.json'
[2026-05-27 16:20:36.244] [warning] Cannot save to JSON: JSON data is empty.

根因分析 ​

启动时读到损坏的配置文件,初始化失败。参数服务随后尝试把内存中的参数落盘,但此时参数列表为空,打印 Cannot save to JSON: JSON data is empty. 后直接返回,没有执行写文件操作。

结合 JSON parse failed 的报错判断:文件不为空,但内容是不完整的 JSON——上一次退出时写了一半。问题从「这次为什么起不来」转化为「上次退出时谁打断了写入」。

损坏是怎么发生的 ​

场景一:直接断电 ​

断电瞬间配置文件正在写入,页缓存来不及刷盘,文件内容截断为不完整的 JSON。这个场景没有优雅退出流程可走,只能靠写入方式本身兜底(见文末原子写入)。

场景二:systemd 停机超时强杀 ​

整套服务由一个 systemd 服务单元托管,systemctl stop 触发停止。systemd 按 KillMode=mixed 的规则执行停机:

  1. 向主进程发送 SIGTERM(信号 15),请求优雅退出
  2. 等待最多 TimeoutStopSec(该单元配置为 5s;不配置时默认 90s)
  3. 超时后向控制组内所有残留进程发送 SIGKILL(信号 9)

这个单元的主进程是一个服务管理进程,它收到 SIGTERM 后的设计流程是:先回收自身资源,再向各业务服务发送退出信号,等待 2s,最后调用 sync() 强制落盘。

实际日志显示,它自身的资源回收花了接近 5s。等它开始给业务服务发退出信号时,systemd 的超时窗口已经用完,控制组内所有进程直接被 SIGKILL 强杀,业务服务的配置写入被中断:

[15:32:41.048] 收到 SIGTERM,开始资源回收
[15:32:43.023] 第一个子模块回收完成(~2s)
[15:32:44.449] 采集服务回收完成(~1.4s)
[15:32:45.969] 资源回收完毕,开始向业务服务发送退出信号   ← 距 SIGTERM 已过 ~5s
[~15:32:46]   systemd 超时,SIGKILL 强杀全部进程

从 SIGTERM 到开始发送退出信号耗时约 5s,业务服务没有时间完成退出和落盘,写到一半的配置文件就此留在磁盘上。

修复与预防 ​

三个方向,前两个针对本例根因,第三个针对排查过程中的盲区:

  1. 退出顺序:管理进程应先向各业务服务发送退出信号,再做自身资源回收;或者压缩自身回收耗时,确保在 TimeoutStopSec 窗口内给业务服务留足安全退出的时间。停机窗口实在不够时,也可以直接调大 TimeoutStopSec
  2. 原子写入:配置文件落盘用 write-to-temp + rename 模式——先写临时文件,写完并 fsync 后用 rename() 替换原文件。rename 在同一文件系统上是原子操作,无论进程在哪一步被杀,磁盘上的文件要么是旧版本、要么是完整的新版本,不会出现半截文件
  3. 让断电可观测:本例中直接断电的场景没有收到电源管理组件的异常上报,缺乏直接证据,只能靠日志推理。关键事件(断电、异常关机)有上报渠道,排查时能少走弯路

要点 ​

  • systemd 停机不是无限等待:TimeoutStopSec 超时后 SIGKILL 直接发给整个控制组,优雅退出流程必须在这个窗口内完成
  • 管理进程自身的清理逻辑会挤占被管理服务的退出时间;退出顺序上先通知别人,再收拾自己
  • 配置类文件一律用临时文件 + rename 的原子写入,把「写到一半被杀」从不可恢复变成无害
最近更新

基于 VitePress 构建