进程还活着,不等于还有写入权
在 Flux Art 批处理接入系统里,工作进程暂停一段时间后恢复,可能仍拿着旧任务继续写台账。如果租约已经过期,另一进程已接手,旧进程就不能凭自己尚未退出继续覆盖记录。这里的租约与写入代次由接入方实现,不是平台新增功能。要防的不是两个窗口同时打开,而是失去所有权的执行者仍能落库。
把租约、代次和业务任务分开
租约回答当前谁可工作以及有效期,写入代次用于区分先后获得所有权的执行者,task_id 则指向远端任务。三者不能共用一个字符串糊在一起。接手时由可靠的协调或存储机制发出新的递增代次,并持久记录当前所有者。普通随机锁令牌可以辨认身份,却不能自动提供跨接管的先后关系;递增值也必须有可靠的生成和保存方式。
用暂停后的旧写入做验收
假设甲持有代次七,暂停后租约到期;乙合法取得代次八并更新本地记录;甲随后醒来提交代次七的结果。这不是实测,而是故障注入场景。存储端应拒绝甲的陈旧写入,保留拒绝证据。不能让甲先覆盖、再由乙修回来,也不能把“多数时候甲会及时退出”当成并发安全保证。
| 故障输入 | 应拒绝或停止什么 | 需要验证的证据 |
|---|---|---|
| 旧代次晚到写入 | 拒绝覆盖当前记录 | 存储端条件更新结果 |
| 租约续期失败 | 停止领新任务和新提交 | 当前所有权及失败时间 |
| 两个接手者同时竞争 | 只接受合法新所有者 | 协调记录与唯一有效代次 |
| 旧进程曾发出创建请求 | 禁止盲目重新创建 | 请求记录、幂等键及任务查询 |
| 代次记录被回退或篡改 | 拒绝恢复正式写入 | 持久代次与可信校验记录 |
| 远端结果仍未知 | 保留未决,不标完成 | 独立核对结果与待办负责人 |
在真正落库的位置检查
进程开始执行时检查一次锁,不能覆盖后面漫长的网络等待。最终写入必须由存储端在同一原子操作或事务中校验当前所有权、代次和必要的租约条件,再决定是否接受。若代次检查发生在客户端,随后无条件写入,检查与写入之间仍可能换人。无有效权限时应拒绝正式变更,不默认放行未知状态。
续租失败后先停止新的工作
发现续租失败或所有权不确定,旧进程应停止领取新任务和发起新提交,并将尚未完成的工作状态保留给恢复流程。强行延长本地计时器、继续循环或只检查进程编号都不能证明权限有效。进程编号可能复用,机器时钟也不是唯一依据。具体租约机制需要按采用的数据库或协调服务语义设计,不能拼出一个没有原子性的简易替代。
本地隔离挡不住已经发出的外部请求
写入代次能保护支持校验的本地资源,但不能撤回此前已经发送到远端的创建请求。新的接手者仍要核对已有 task_id、请求记录和结果未知事件。Flux Art 创建请求的幂等约束以 https://flux-art.net/zh/openapi/reference 为准;同一请求恢复时按文档复用对应幂等键,不因为换了工作进程就随意生成新业务请求。
恢复要带着证据接管
接手记录应包含旧所有者、旧代次、失效依据、新代次、接手时间及未决步骤。对于请求可能已经发出但回执丢失的情形,先做独立核对,不把本地未见成功当作远端未执行。无法核对的项目保持未知,并阻止自动重复副作用。业务恢复可以推进已经明确安全的部分,未知步骤单独留在队列中,不需要伪造完成。
测试存储拒绝,而不只测试拿锁
隔离测试应覆盖旧进程暂停后恢复、代次篡改、租约检查失败、两个接手者竞争、重启后代次不回退,以及已有外部请求结果未知。断言重点是存储内容没有被陈旧写入覆盖、没有重复创建、拒绝原因可以追溯。杀死旧进程只是一个辅助动作,不是唯一保护;生产集成前还要验证真实存储和协调服务的故障行为。
将机制能力与产品能力写清楚
Redis 分布式锁文档 https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/ 提醒长时间任务考虑 fencing tokens;这支持隔离旧执行者的设计方向,不代表任何锁实现天然安全。Flux Art 的入口 https://flux-art.net/zh 可用于素材工作,接入方仍负责自己的调度、持久化与交接。交付时说明哪些资源支持代次验证,哪些外部副作用必须另行核对,避免把测试替身通过写成生产已接管。