inotify仅能实现镜像本地元数据更新后的秒级分发,无法感知packagist上游变更;其监听需覆盖moved_to/close_write事件并配合.sync文件或rsync--delay-updates保障原子性,且须调高inotify限制、加超时保护与上下文完整性校验。

Composer镜像同步任务用 inotify 实现“秒级分发”是可行的,但必须明确:它只管本地元数据文件(如 packages.json、p2/vendor/package.json)的变更通知,不负责上游源(Packagist)的实时感知——那部分仍是定时 pull。真正能压到秒级的,仅限于“镜像服务本地生成/更新完元数据后,立刻推给下游节点或触发校验”的环节。
为什么 inotify 不能替代 rsync 定时同步
inotify 只监听本机文件系统事件,而 Composer 镜像的源头数据来自 Packagist 的 HTTP 接口,不是本地写入。镜像服务通常是先跑一次 rsync 或 curl 拉取全量/增量元数据,再落地为 JSON 文件;inotify 只能在这些文件被写完后才捕获 IN_CLOSE_WRITE 事件。所以它无法解决“上游已发布、镜像还没拉”的延迟,只能加速“拉完之后的分发”。
- 常见错误现象:
inotifywait -m -e close_write /data/mirror/composer/packages.json一直没输出,但实际packages.json已更新——原因往往是 rsync 写入用了--inplace或临时文件覆盖(如先写packages.json.tmp再mv),后者会触发IN_MOVED_TO而非close_write - 正确监听方式应覆盖两类事件:
inotifywait -m -e moved_to,close_write /data/mirror/composer/,并过滤目标文件名 - 若 rsync 使用
--delete-after,删除动作不会触发写事件,需额外监听IN_DELETE_SELF或改用IN_MOVED_FROM捕获临时文件移出
inotify + rsync 组合中容易漏掉的原子性保障
单纯监听文件变化后直接触发 rsync 推送,会导致下游拿到半截文件。比如 packages.json 正在被写入,inotify 捕获到 IN_MOVED_TO 就立刻推送,下游 rsync 拉到的可能是未 flush 的脏数据。
- 必须等文件写入完成且 fsync 落盘后再通知:镜像生成脚本末尾加
sync && touch /data/mirror/composer/.ready,inotify改监听这个.ready文件更可靠 - 或者用
rsync --delay-updates参数,让 rsync 把所有更新暂存在临时目录,最后原子 rename - 避免用
inotifywait -m长守进程,建议配合 systemd 的Restart=on-failure和StartLimitIntervalSec=60,防止进程僵死导致事件丢失
如何验证 inotify 是否真在工作
别依赖日志输出,要验证事件是否被准确捕获和消费。
- 手动触发测试:
echo '{}' > /data/mirror/composer/test.json && sleep 1 && rm /data/mirror/composer/test.json,观察监听脚本是否输出对应事件 - 检查 inotify 资源限制:
cat /proc/sys/fs/inotify/max_user_watches,镜像目录层级深、文件多时容易 hit limit,需调高(如设为524288) - 确认事件未被内核丢弃:
dmesg | grep -i "inotify",出现"inotify: user limit reached"就说明漏事件了 - 生产环境务必加超时保护:监听逻辑里用
select或timeout包裹 rsync 调用,防止某个节点网络卡住阻塞后续所有事件
真正难的不是监听到变化,而是确保每次变更都带完整上下文——比如 p2/laravel/framework/10.0.0.json 更新时,packages.json 很可能也得重生成。单文件监听容易漏关联更新,最好把整个 p2/ 目录作为原子单元处理,或者用 Merkle Tree 校验根哈希变动再触发分发。











