分布式锁租约:锁没过期不代表业务还安全

📅 2026/7/2 23:41:36
分布式锁租约:锁没过期不代表业务还安全
分布式锁租约锁没过期不代表业务还安全一、分布式锁最怕把时间当确定性分布式锁常用于控制任务互斥、主节点选举、资源抢占和批处理调度。很多实现依赖租约时间例如加锁成功后持有 30 秒超时自动释放。但租约并不等于绝对安全。GC 停顿、网络抖动、时钟漂移、进程假死和下游慢调用都可能让持锁者在自己以为安全时已经失去锁。真正危险的是旧持锁者恢复后继续执行写操作而新持锁者也已经开始执行。此时系统出现双主或重复任务。锁服务没有坏坏的是业务把“曾经拿到锁”理解成“现在仍然有权写”。二、租约模型持锁、续约和业务写入要分开sequenceDiagram participant A as Worker A participant L as Lock Service participant B as Worker B participant S as Storage A-L: acquire lease A-S: do work A--xA: GC pause L-B: lease expired, B acquire B-S: do work A-S: old worker writes again这个场景说明仅靠锁过期无法保护存储写入。需要引入 fencing token。锁服务每次授予锁时生成递增 token业务写入存储时携带 token存储层拒绝旧 token 的写入。这样即使旧持锁者恢复也不能覆盖新持锁者结果。如果底层存储不支持 fencing token就要谨慎使用分布式锁处理关键写入。可以把写入变成幂等任务或者在业务表中记录版本号和持有者 token通过条件更新保护顺序。锁只是互斥提示最终保护必须落在数据写入点。三、实现示例用版本条件保护写入下面是一个简化的条件更新思路。真正系统里 token 应由可靠锁服务生成并单调递增。UPDATE job_state SET owner worker-b, fencing_token 1024, status RUNNING WHERE job_id 1001 AND fencing_token 1024;后续写入也要携带 tokenUPDATE job_result SET result done WHERE job_id 1001 AND fencing_token 1024;这类条件写让数据层参与校验。即使旧 worker 还活着它的 token 也无法通过条件。没有数据层保护分布式锁只能降低并发概率不能证明安全。四、工程检查续约失败要立刻停手持锁任务要定期续约并在续约失败时停止执行。不要只是打印日志继续跑。续约线程和业务线程之间要有明确通信例如原子状态或取消信号。任务内部遇到长时间阻塞时也要检查锁状态避免失锁后继续写。租约时间设置要基于任务特征。太短容易误释放太长会拖慢故障恢复。对于长任务更好的方式是小步提交、可中断、可续跑而不是一次持锁跑到底。任务越长锁越不可靠。还要演练故障。模拟 GC 停顿、锁服务不可用、网络分区和进程重启观察是否出现双写。分布式锁的正确性不能靠文档声明需要故障注入证明。时钟也要检查。租约判断如果依赖本地时间NTP 抖动和虚拟机时间漂移会放大风险。更稳妥的做法是让锁服务统一授时或使用服务端过期时间并把关键状态持久化到可审计存储中。否则排查双写时连谁先失效都说不清。五、总结分布式锁租约只能说明某段时间内可能互斥不能单独保护关键写入。引入 fencing token、条件更新、幂等任务和续约失败停手才能降低双写风险。锁没过期不代表业务安全数据层校验才是最后防线。