
Redis分布式锁的核心挑战
分布式锁要解决的根本问题是跨进程的资源互斥访问。在单机环境下用synchronized或ReentrantLock就能搞定的事情,到了分布式环境就变得复杂起来。Redis之所以成为分布式锁的首选方案,主要因为它性能高、命令原子性好,但真正实现起来会遇到三个致命问题:
Redisson的解决方案是用UUID+线程ID作为锁标识,配合Lua脚本保证原子性。比如释放锁的Lua脚本长这样:
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1])
else
return 0
end
Watch Dog机制详解
Redisson最精妙的设计就是这个看门狗机制。当客户端获取锁成功后,会启动一个后台线程,每10秒检查一次是否还持有锁(默认锁有效期30秒),如果持有就延长锁有效期。这个设计解决了两个核心问题:
但要注意这个机制只在没有显式设置leaseTime时才生效。如果手动指定了锁超时时间,比如lock.lock(10, TimeUnit.SECONDS)
,那就相当于放弃了自动续约功能。
配置项 | 默认值 | 作用 |
---|---|---|
lockWatchdogTimeout | 30000ms | 锁自动续期间隔 |
expirationRenewalThreshold | 10000ms | 剩余时间阈值触发续约 |
高并发场景下的锁优化
当QPS超过5000时,简单的Redis分布式锁可能会遇到性能瓶颈。我们做过压测,单Redis节点在锁竞争激烈时,TPS会从20000骤降到3000左右。这时候需要做些特殊优化:
特别要注意的是红锁(RedLock)算法,虽然理论上能提高可用性,但在网络分区场景下反而可能更不可靠。我们生产环境更推荐用主从+哨兵模式,配合适当的重试策略。
常见坑点及解决方案
实际使用分布式锁时,90%的问题都出在异常处理上。这里列出我们踩过的三个典型深坑:
有个特别隐蔽的坑是Redis的主动过期策略。当内存不足时,Redis可能主动淘汰设置了过期时间的key,这会导致锁意外失效。解决方法是在redis.conf中设置maxmemory-policy noeviction
,或者改用内存更大的实例。
Redisson的Watch Dog机制其实是个很聪明的设计,它不会无脑地一直续约下去。这个后台线程每隔10秒就会检查一次,看看客户端是不是还活着、业务是不是还在跑。要是发现客户端挂了或者网络断了,续约线程立马就停了,这时候Redis里的锁key到了30秒默认过期时间就会自动清理掉,不会出现锁永远不释放的情况。有意思的是,如果你在代码里显式设置了leaseTime,比如lock.lock(10, TimeUnit.SECONDS)
,那就相当于告诉Redisson”别用看门狗了”,这时候锁说10秒到期就10秒到期,绝对不会自动续命。
实际用的时候得特别注意这个机制的触发条件。我们线上就遇到过坑,有个同事在锁代码块里写了死循环,结果因为没设置leaseTime,Watch Dog一直续约导致锁永远不释放,最后只能手动去Redis里删key。后来我们定了条规范:长时间任务必须显式设置合理的leaseTime,短时间任务才依赖默认的Watch Dog机制。还有个细节是续约时机,Redisson不是在锁快到期时才续约,而是锁还剩三分之一有效期(默认10秒)时就提前续上,这样就算续约操作有点延迟也不会出问题。
为什么Redis分布式锁需要设置过期时间?
设置过期时间主要是为了防止死锁。如果客户端获取锁后崩溃,没有显式释放锁,这个锁就会永远存在于Redis中,导致其他客户端永远无法获取锁。Redisson默认30秒的过期时间配合Watch Dog机制,既避免了死锁风险,又能保证长时间业务的锁持有。
Redisson的Watch Dog机制会无限续约吗?
不会。Watch Dog只在客户端保持活跃时续约,如果客户端进程崩溃或网络断开,续约线程会随之终止。当锁超过有效期未续约时,Redis会自动删除key释放锁。 如果显式指定了leaseTime参数,Watch Dog机制会被禁用。
Redis集群故障时分布式锁如何保证可用性?
生产环境 采用主从+哨兵模式,配合适当的重试策略。虽然RedLock算法理论上能提高可用性,但在网络分区场景下可能更不可靠。我们更推荐在客户端实现降级策略,比如Redis不可用时切换本地锁,并记录异常日志。
为什么推荐用Lua脚本实现分布式锁?
Lua脚本在Redis中具有原子性执行特性,可以避免多个命令执行过程中的竞态条件。例如判断锁标识和删除锁这两个操作必须原子性执行,否则可能出现锁误删。Redisson所有锁操作都通过Lua脚本实现,这是保证线程安全的关键。
如何处理Redis主动过期导致的锁失效?
当Redis内存不足时可能主动淘汰设置了过期时间的key。解决方案包括:1) 调整redis.conf的maxmemory-policy为noeviction;2) 升级Redis实例内存规格;3) 在业务层实现锁失效后的补偿机制,比如结合数据库版本号做二次校验。