Redis 分布式锁续期:锁还在,不代表业务安全
Redis 分布式锁续期:锁还在,不代表业务安全
一、分布式锁最怕误以为安全
Redis 分布式锁常用于防重复任务、定时调度、库存扣减和资源互斥。加锁成功后,很多代码就放心执行业务。但业务执行时间如果超过锁过期时间,或者续期线程异常,另一个实例可能拿到锁并发执行。
锁还在,不代表业务安全。锁的生命周期必须和业务状态一起设计。
二、锁要有唯一标识
flowchart TD A[请求加锁] --> B[写入锁值] B --> C[执行业务] C --> D[校验锁值] D --> E[释放锁]释放锁时必须校验锁值,不能只按 key 删除。否则 A 的锁过期后 B 获得锁,A 结束时可能把 B 的锁删掉。
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end锁值通常使用 requestId 或随机 token。
三、续期要看业务边界
如果业务可能超过锁 TTL,可以做 watchdog 续期。但续期不是万能药。续期线程卡住、进程 STW、网络抖动,都可能导致锁失效。
lock_policy: ttl_seconds: 30 renew_interval_seconds: 10 max_hold_seconds: 300要设置最大持有时间,避免业务卡死后锁一直续。
四、业务要能防重复
分布式锁只能降低并发概率,不能代替业务幂等。关键动作仍要有唯一约束、状态机或版本号兜底。
CREATE UNIQUE INDEX uk_job_day ON settlement_job(biz_date);如果锁失效导致重复执行,数据库约束至少能挡住结果污染。
最后,锁要有监控。加锁失败率、锁持有时间、续期失败次数、超时释放次数,都能反映系统是否接近危险边界。
还要区分锁失败和业务失败。拿不到锁不一定是错误,可能只是另一个实例正在处理;续期失败则更危险,说明当前实例可能已经失去互斥保障。日志级别和告警策略要分开。
lock_metrics: acquire_failed: info_or_warn renew_failed: alert hold_time_p99: monitored force_release_count: alert如果业务跨机房部署,还要谨慎使用单 Redis 锁。网络分区、主从切换、复制延迟都会影响锁语义。关键资金、库存或结算场景,不能只靠缓存锁兜底。
最后,锁粒度也要设计。锁太粗影响吞吐,锁太细容易漏互斥。按业务主键、租户、日期还是资源 ID 加锁,需要和并发冲突模型一致。
锁超时后的处理也要明确。业务执行到一半发现续期失败,不能假装什么都没发生继续写结果。可以设置中断标志,进入补偿流程,或者在最终提交前再次校验业务版本。
if (!lockService.stillOwner(lockKey, token)) { throw new LockLostException("lock ownership lost"); }对于定时任务,锁失效后要避免两个实例同时推进游标。游标更新应使用乐观锁或唯一版本号,确保只有一个实例能提交进度。
还要给锁操作设置超时。Redis 调用本身如果卡住,业务线程不能无限等待。加锁、续期、释放都应该有短超时和失败处理。
最后,评审分布式锁代码时,要把“锁保护了什么资源”写清楚。看不清保护对象的锁,通常也很难证明它正确。
五、总结
Redis 分布式锁续期要校验锁值、设置 TTL 和最大持有时间,并用业务幂等兜底。
锁还在,不代表业务安全。真正安全的是锁、状态和约束一起工作。