Redis集群中Key提前消失致分布式锁失效,求排查方案
Redis集群下Spring Webflux自定义锁异常:锁提前失效问题排查
问题场景
基于Kotlin+Spring Boot Webflux实现自定义分布式锁,用于规避同一用户的并发事务处理。预期逻辑为:第二个事务循环等待,直到第一个事务完成并释放锁Key(未到TTL前锁应持续有效)。但实际测试发现,未达TTL时仅几秒后第二个进程就能获取锁;日志显示获取锁前查询锁Key为空,但通过TTL验证Key实际仍存在,且初始能查到Key值,随机一段时间后查询结果变为空。已确认代码采用Redis集群模式连接。
锁获取核心代码
redisTemplate .opsForValue() .get(lockKey) .flatMap { containValue -> redisTemplate.getExpire(lockKey).flatMap { ttl -> Mono.fromSupplier { // 打印锁状态日志 containValue.isNullOrEmpty() } } } .switchIfEmpty(Mono.just(true)) .flatMap { isUnlocked -> // 打印锁判定日志 if (isUnlocked) { redisTemplate .opsForValue() .setIfAbsent(lockKey, traceId, LOCK_EXPIRY) .defaultIfEmpty(false) } else { Mono.just(false) } }
排查发现与原因分析
- Redis集群Key迁移的客户端处理缺陷:Redis集群在扩容、缩容或槽位平衡时会触发Key迁移,此时针对该Key的读请求会收到
MOVE指令,要求客户端转向目标节点查询。若使用的Spring Data Redis Reactive客户端版本较旧,可能存在MOVE指令处理漏洞,直接返回空值而非转发请求到目标节点,导致误判锁已释放。 - 非原子性的锁状态检查:代码中
get(lockKey)和getExpire(lockKey)是两个独立异步操作,在Redis集群环境下,这两个请求可能被路由到不同节点(若Key正处于迁移过程中),出现get返回空但getExpire仍能查到TTL的状态不一致情况。 - 空值处理逻辑的误判:
switchIfEmpty(Mono.just(true))会在get(lockKey)返回空时直接判定锁未被持有,但未区分「Key真的不存在」和「查询失败(如MOVE指令导致的空)」两种场景。
解决方案
- 升级Spring Data Redis版本:建议升级到Spring Data Redis 2.7+(对应Spring Boot 2.7+),新版本修复了Reactive客户端在集群环境下的指令转发问题,能正确处理
MOVE指令。 - 原子化锁状态检查:用Lua脚本替代分开的
get和getExpire操作,保证锁状态查询的原子性,避免集群节点间的状态不一致。示例如下:
Webflux中调用Lua脚本的实现:local lockValue = redis.call('GET', KEYS[1]) local ttl = redis.call('TTL', KEYS[1]) return {lockValue, ttl}val lockCheckScript = DefaultRedisScript<List<Any?>>(""" local lockValue = redis.call('GET', KEYS[1]) local ttl = redis.call('TTL', KEYS[1]) return {lockValue, ttl} """, List::class.java) redisTemplate.execute(lockCheckScript, listOf(lockKey)) .flatMap { result -> val lockValue = result[0] val ttl = result[1] as Long? // 结合TTL判断锁是否真的不存在:若TTL>0则锁仍有效 Mono.just(lockValue.isNullOrEmpty() && (ttl == null || ttl <= 0)) } .flatMap { isUnlocked -> if (isUnlocked) { redisTemplate.opsForValue() .setIfAbsent(lockKey, traceId, LOCK_EXPIRY) .defaultIfEmpty(false) } else { Mono.just(false) } } - 检查Redis集群状态:通过
redis-cli cluster nodes查看集群节点状态和槽位分布,确认是否存在频繁的槽位迁移。若为临时迁移导致的问题,迁移完成后自动恢复;若频繁迁移需调整集群平衡策略。
内容的提问来源于stack exchange,提问作者DDDDDD
相关产品推荐
相关产品推荐

