必须用 Redis 实现跨节点互斥锁,因 flock 仅单机有效且无法保护共享的 packages.json;正确做法是用 SET NX PX 加唯一 token 锁,仅在 rename 阶段加锁并用 Lua 脚本安全释放。

大规模并发打包时,packages.json 被多个构建节点同时写入,不加分布式锁必然损坏——本地 flock() 完全无效,必须用 Redis 实现跨节点互斥。
为什么 composer install 不能靠 flock 保护 metadata 写入
很多团队误以为给 composer.lock 加 flock 就能保住整个镜像一致性,其实完全跑偏:flock 只锁文件描述符,且只在单机生效;而 packages.json 是所有构建节点共享的权威索引,写入发生在 CI 构建机、Satis 服务、甚至 Webhook 触发的后台任务中,节点之间根本看不到彼此的文件锁。
常见错误现象包括:
-
Failed to decode response: JSON data is invalid—— 某个节点写到一半崩溃,文件被截断 - 下游
composer require突然拉不到新包 —— 两个节点同时写,后写的覆盖了前写的完整包列表 - 同一 commit 下多次构建生成的
packages.jsoncontent-hash不一致 —— 并发导致 JSON 数组顺序、缩进、字段顺序随机变化
Redis SET NX PX 是唯一靠谱的加锁原语
PHP 原生 redis->set() 支持原子性参数,但必须显式传入 ['NX', 'PX' => 30000],不能依赖布尔返回值判断是否成功——失败时它返回 false,但网络超时也可能返回 false,无法区分。
正确做法是用完整命令字符串 + 显式响应校验:
if ($redis->set('composer:metadata:write:lock', $token, ['NX', 'PX' => 30000]) === 'OK') { /* got lock */ }关键点:
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
-
token必须是全局唯一字符串(如uniqid('build-')),用于后续安全释放 - 过期时间设为 30 秒足够:实际 metadata 生成(聚合包版本、构造 JSON)都在内存完成,真正落盘只有
rename()一瞬间 - 绝不能在锁内做远程请求或数据库查询——这些操作一旦超时,锁就卡死,整个镜像服务冻结
写入阶段必须分离计算与落盘,且只锁 rename
锁的粒度越小越好。整个流程应拆成两段:
- 锁外:扫描 vendor 目录、读取各包
composer.json、聚合versions、生成完整packages数组(全部在 PHP 数组里完成) - 锁内:仅做三件事——
file_put_contents('packages.json.tmp', $json)→rename('packages.json.tmp', 'packages.json')→ 释放锁
注意:rename() 在同一文件系统下是原子的,比直接 file_put_contents('packages.json', $json) 安全得多;如果 packages.json 和临时文件不在同一挂载点(比如 tmpfs 和 NFS 混用),rename 会失败,此时必须换用 atomicWriteFile() 类库或拒绝写入。
释放锁必须用 Lua 脚本,且 value 校验不可省略
释放锁不是 $redis->del('composer:metadata:write:lock') 就完事。如果 A 拿到锁,30 秒后自动过期,B 紧接着获得锁并开始写,此时 A 的释放逻辑若还在执行(比如网络延迟),DEL 就会误删 B 的锁。
标准 Lua 脚本必须用:
if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end调用时传入 key 和当初 set 进去的 $token,否则等于没锁。
最容易被忽略的一点:锁 key 的命名要带环境标识,比如 composer:metadata:write:lock:prod,否则 dev/staging/prod 共用一套 Redis 时会互相干扰。

















