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

systemd PathChanged 怎样触发服务处理文件变更

来源:17golang原创

时间:2026-11-14 06:27:51 300浏览 收藏

如果你希望“某个文件写完后,交给一个一次性服务处理”,systemd 的 PathChanged= 可以把这个关系写成一个 .path unit。它监视绝对路径,在被监视文件结束一次写入并关闭后激活关联服务;它不是每次写入都触发的实时队列,也不会替你保存多次变更。

官方地址:https://www.freedesktop.org/software/systemd/man/latest/systemd.path.html

下面用“收到数据文件后重建报表”的场景说明完整做法。读者需要记住三个边界:PathChanged= 更接近“写入完成后的变化”,PathModified= 才会把简单写入也纳入触发;路径必须是绝对路径;默认情况下 report-refresh.path 会激活同名的 report-refresh.service。

先分清 PathChanged 的触发边界

PathChanged= 可以监视文件或目录。当被监视文件曾经打开写入并关闭时,systemd 会认为它发生了需要处理的变化;文档明确说明它不会对每一次写操作都单独激活服务。若业务要求“写入过程中也要感知”,应评估 PathModified=,不要只把 PathChanged= 的触发频率调高。

systemd PathChanged 监听边界与服务关联的静态结构说明图
图1:PathChanged 的输入路径、关闭写入边界和被激活服务之间的静态关系说明图,不是运行截图。

这个差异决定了它适合“文件写完再处理”的工作流,例如导入文件落盘后生成索引、配置文件替换后重载服务、报表源文件更新后执行一次重建。它不适合要求逐条消费每一个文件事件的队列系统。

规则关注点适合的判断
PathExists=路径是否存在文件出现后激活一次关联 unit
PathChanged=文件或目录发生变化,写入文件关闭后触发写入完成后交给一次性服务
PathModified=变化更敏感,简单写入也可触发需要更早感知写入,但要防止重复处理
DirectoryNotEmpty=目录中至少有一个文件把目录作为待处理入口,但仍需自行定义消费规则

准备匹配的 service 与 path unit

先创建一个只处理一次的 service。这里让脚本把输入文件复制到一个带时间戳的归档目录,实际业务可以替换成校验、转换或重建索引。示例中的命令和路径都是公开的示例路径,不代表已经在本文环境执行。

# /etc/systemd/system/report-refresh.service
[Unit]
Description=处理报表输入文件

[Service]
Type=oneshot
# 让服务只负责一次处理,脚本自行检查输入并返回退出码
ExecStart=/usr/local/bin/report-refresh.sh
#!/usr/bin/env bash
set -eu

# 输入和归档目录保持固定,便于与 path unit 的绝对路径对应
input=/var/lib/report/inbox/data.json
archive=/var/lib/report/archive

# 目录不存在时直接失败,让 systemd 保留失败状态供排查
test -f "$input"
mkdir -p "$archive"

# 用纳秒时间组成归档名,避免覆盖上一次处理结果
stamp=$(date +%Y%m%d%H%M%S%N)
cp -- "$input" "$archive/data-$stamp.json"
printf 'archived %s\n' "$input"

接着创建同名的 .path 文件。这里不写 Unit= 也可以,因为默认目标就是同名的 report-refresh.service;显式写出它能让关联关系更容易被读者和运维人员确认。

# /etc/systemd/system/report-refresh.path
[Unit]
Description=监听报表输入文件

[Path]
# 只使用绝对路径;写入文件关闭后激活 report-refresh.service
PathChanged=/var/lib/report/inbox/data.json
Unit=report-refresh.service

[Install]
# 让 systemctl enable 把监听加入系统启动目标
WantedBy=multi-user.target

如果写入方采用“临时文件写完后 rename 覆盖目标文件”的原子替换方式,应把它当作需要单独确认的文件事件模型:监听的是具体路径,且 systemd 的 path unit 依赖 inotify,远端 NFS 等文件系统还有 inotify 本身的限制。不要用一个未经确认的编辑器行为,推导出所有写入方式都完全相同。

安装、授权并启用监听

两个 unit 文件放到系统 unit 目录后,先让 systemd 重新读取配置,再启用并立即启动 path unit。注意真正需要持续运行的是 report-refresh.path;report-refresh.service 是被触发时短暂运行的 oneshot 服务。

# 重新读取 /etc/systemd/system 下的 unit 文件
sudo systemctl daemon-reload

# 设置开机启用,并在当前系统立即开始监听
sudo systemctl enable --now report-refresh.path

# 查看 path unit 的监听状态与绑定目标
systemctl status report-refresh.path --no-pager

# 查看解析后的 PathChanged、Unit 与触发关系
systemctl cat report-refresh.path

这里的权限分成两层:安装和启用系统级 unit 通常需要管理员权限;被触发的服务能否读取 /var/lib/report/inbox/data.json,还取决于服务进程用户、目录的搜索权限以及可能存在的沙箱配置。path 显示为 active,并不等于脚本一定能成功读取输入。

systemd path unit、oneshot service、文件权限和失败状态的静态边界图
图2:path unit、关联 service、输入文件和失败状态之间的静态边界说明图,不是终端截图或运行证据。

设置流水线门禁与重复触发边界

把它当作一个小型自动化流水线会更容易设计:触发器负责发现变化,service 负责一次处理,脚本返回码负责门禁,journal 负责通知和复盘。最容易踩的坑是把 path unit 当成可靠消息队列:如果文件在服务处理期间再次变化,systemd 的语义仍然是再次检查并按 unit 状态安排触发,不会替你保存每一次事件的完整历史。

对于可能连续变化的输入,先让脚本具备幂等性,再考虑在 service 中增加互斥或批处理策略。不要为了“看起来更灵敏”直接切到 PathModified=,因为更敏感通常意味着更多启动机会。

# /etc/systemd/system/report-refresh.service.d/limits.conf
[Unit]
# 失败时保留 failed 状态,交给值班流程处理,不自动掩盖错误
StartLimitIntervalSec=60
StartLimitBurst=5

当前 systemd 文档还提供 path unit 自身的 TriggerLimitIntervalSec= 与 TriggerLimitBurst=,用于限制一个 path unit 在时间窗口内被激活的次数。触发上限命中后,path unit 会进入失败状态并停止继续监视,直到被重新启动。对于高频目录监听,要把这个限制当成保护门,而不是性能调优开关。

按证据处理未触发和服务失败

排查时先分清“没有发现变化”和“发现变化但服务失败”。前者看 path unit 是否 active、路径是否为绝对路径、目标文件或目录是否可被 systemd 访问;后者看 service 的退出码和日志,不要只看 path unit 的绿色状态。

# 检查 path unit 是否仍在监听,以及最近一次结果
systemctl status report-refresh.path --no-pager

# 查看服务最近一次执行的完整日志,包含脚本的退出原因
journalctl -u report-refresh.service -n 80 --no-pager

# 查看 systemd 解析后的关键属性,避免被旧文件或拼写误导
systemctl show report-refresh.path \
  -p ActiveState -p SubState -p Unit -p Paths -p Result

# 修复 unit 或脚本后,重新加载并重新启动监听
sudo systemctl daemon-reload
sudo systemctl restart report-refresh.path

如果输入在远端 NFS 文件系统上,不能简单照搬本地文件的预期;官方文档说明 path unit 使用 inotify,因此受到 inotify 的限制,远端 NFS 上由其他机器改变的文件不能作为同等可靠的监听对象。若脚本失败,先读取 service 日志,确认输入权限、目录存在性和脚本退出码,再决定是否重启 path unit。

结论与速查

要让 PathChanged= 稳定工作,关键不是把 unit 写得复杂,而是把职责拆开:.path 只负责监听绝对路径,.service 只负责一次处理,脚本负责幂等与错误码,journal 负责留下失败证据。需要“写完再处理”时优先使用 PathChanged=;需要感知简单写入时再评估 PathModified=;需要保存每个事件时改用真正的队列或持久化事件表。

  • 为什么 PathChanged 没有触发?先查 path unit 是否 active、监听路径是否绝对、写入是否发生在本机可观察的文件系统上。
  • 为什么 path 是 active 但任务失败?path 只说明监听器工作,实际处理结果要看关联 service 的 journal。
  • PathChanged 和 PathModified 有什么区别?前者偏向写入结束后的变化,后者还覆盖简单写入,触发更敏感。
  • PathChanged 能替代消息队列吗?不能。它适合触发一次处理,不提供逐事件持久化、消费确认和历史补偿。
声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>