Redis 做分布式锁,四个最常见的失效场景
用 Redis 做分布式锁,跑通不难,难的是在异常情况下还成立。下面四个场景是线上事故里出现频率最高的。
场景一,加锁和过期不是原子的。先 SETNX 再 EXPIRE 两段代码,中间进程挂了,锁永远不会过期,后续请求全部阻塞。正确写法是一条命令:
SET lock_key unique_value NX PX 30000
NX 保证互斥,PX 兜底过期时间,一条命令原子完成。
场景二,释放了别人的锁。业务处理超过过期时间,锁自动释放,另一个线程拿到同名锁,此时第一个线程处理完去 DEL,把别人的锁删了。解法是 value 存唯一标识(UUID 或 requestId),释放时先比对再删。比对和删除必须原子,用 Lua 脚本:
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
场景三,锁过期了业务还没做完。过期时间设长了怕死锁,设短了怕提前释放。生产做法是加看门狗:后台线程每隔三分之一过期时间检查锁是否还被自己持有,是就续期。Redisson 的 RLock 内置这个机制,别自己造。
场景四,主从切换丢锁。Redis 主从异步复制,主节点写入锁后没来得及同步就宕机,从节点升主,锁丢了,两个客户端同时觉得自己拿到锁。单实例解决不了,要上 Redlock(多主独立部署,过半成功才算加锁成功),或者改用 etcd、ZooKeeper 这类基于共识的组件。
选型建议:防重复提交这类互斥,单实例 Redis 加过期时间就够了。涉及资金、库存这种强一致要求,不要用 Redis 锁,用数据库行锁或乐观锁版本号。
最后一点容易忽略:锁的粒度要和事务边界一起设计。锁加在方法上、事务提交在方法外,锁释放了事务还没提交,读到的仍然是旧数据。
- 点赞
- 收藏
- 关注作者
评论(0)