本文共 2574 字,大约阅读时间需要 8 分钟。
近期,我们的项目在 Redis 分布式锁实现中出现了一次严重的 NumberFormatException,导致业务数据部分错乱。这次问题的根源在于自定义 Redis 分布式锁的实现中存在严重的设计缺陷。以下将详细分析问题所在以及如何解决。
在运行过程中,系统报错信息如下:
redis setNX error java.lang.NumberFormatException: For input string: "null" at java.lang.Long.parseLong(Long.java:589) at java.lang.Long.parseLong(Long.java:631) ...
错误信息表明,系统尝试将一个空字符串 "null" 转换为 Long 时失败。经过定位发现,问题出在 Redis 锁的实现逻辑中。
错误信息显示,currentValue 的值为字符串 "null",这意味着在 Redis 中,某个锁的值为 null。在代码中,currentValue 的获取逻辑如下:
String currentValue = String.valueOf(objVal);
这里的 objVal 是从 Redis 中取出的值。如果 objVal 为 null,则 currentValue 也会是 "null"。
在分布式锁的加锁逻辑中,setNX 命令用于检查锁是否存在。如果 setNX 返回 false,则表示锁不存在,程序会继续执行后续逻辑。此时,程序会尝试从 Redis 中取出锁的值。如果锁不存在,objVal 也会是 null,进而导致 currentValue 为 "null"。
这种情况可能出现在以下两种情况下:
setNX 检查锁时发现锁不存在。自定义 Redis 分布式锁的实现方式存在以下问题:
setNX 和 expire 的原子性问题
在传统的 Redis 分布式锁实现中,setNX 和 expire 命令需要在同一个 Lua 脚本中执行,以确保原子性。否则,可能会出现 setNX 成功但 expire 未能及时更新的情况。 锁过期问题
如果加锁所需时间超过了 Redis 锁的过期时间,锁会自动过期,导致其他进程无法获取锁,甚至可能导致数据竞争。可重入问题
Redis 分布式锁通常支持可重入,但实现时需要确保每个线程都有唯一的标识符,以防止死锁。锁自旋问题
在高并发场景下,如果多个进程长时间等待锁,可能会导致自旋锁问题,影响系统性能。主从架构下的锁同步问题
Redis 主从复制中,主节点的锁可能会稍微滞后于从节点的锁状态,可能导致一致性问题。针对上述问题,我们可以采取以下改进措施:
Redisson 是一个功能强大的 Redis 客户端,提供了对 Redis 分布式锁的高级接口,可以避免传统实现中的诸多问题。以下是 Redisson 的简单使用示例:
public class RedisLockAspect { @Autowired private Redisson redisson; public void around(ProceedingJoinPoint pjp) { String key = "..."; Long waitTime = 3000L; // 等待时间 RLock lock = redisson.getLock(key); boolean lockSuccess = false; try { lockSuccess = lock.tryLock(waitTime); pjp.proceed(); } finally { if (lock.isLocked() && lock.isHeldByCurrentThread() && lockSuccess) { lock.unlock(); } } }} Redisson 提供了以下优势功能:
如果选择继续使用自定义 Redis 分布式锁,可以采取以下优化措施:
使用 Lua 脚本
在 Redis 中使用 Lua 脚本,确保setNX 和 expire 命令在同一个事务中执行。 例如:
local res = redis.call('setnx', key, expire_time)if res ~= false then redis.call('expire', key, timeout)end 锁过期时间管理
在加锁时,设置较长的过期时间,并在锁使用期间定期续期锁。线程唯一标识符
在锁的 key 中加入线程唯一标识符,避免死锁。异常处理
在获取锁时,增加异常捕获机制,确保锁不会因异常释放而导致其他进程无法获取锁。这次 NumberFormatException 的问题表明,我们对 Redis 分布式锁的实现存在严重的设计缺陷。解决这一问题需要从根本上改进 Redis 分布式锁的实现方式,而不是简单地修复表面问题。
推荐使用现成的 Redis 客户端如 Redisson,它能够显著提升 Redis 分布式锁的实现质量,避免类似问题的再次发生。同时,我们需要加强代码审查和技术培训,提升团队成员的技术水平,确保类似问题能够及时发现和解决。
转载地址:http://uiqfk.baihongyu.com/