文章目录
前言
在测试一个接口的时候,使用jmeter进行压测的时候,出现了多次调用成功的情景。
开始的时候以为是redis中的锁的问题或者是业务逻辑处理的问题,但是走查了一遍代码后,排除了。经过一通分析,发现是锁的获取和释放的位置出现了问题。
修改后也就在此做个记录~
问题代码
java
try{
boolean lockFlagRaise= JedisUtils.acquireLock(productListByIds.get(0).getProductId()+":createLock","1",20*60);
if(!lockFlagRaise){
throw new RuntimeException("该产品正在提取中,请稍后刷新再试。联系电话010-xxxxxxxx-1。");
}
---------业务逻辑处理,此处省略-----------
//释放锁
JedisUtils.del(productListByIds.get(0).getProductId()+":createLock");
} catch (Exception e) {
//释放锁
JedisUtils.del(productListByIds.get(0).getProductId()+":createLock");
logger.info("指令生成失败原因["+e.getMessage()+"]");
throw new RuntimeException(e.getMessage());
}
主要框架就是上边的内容,不知道大家发现问题没有?如果跟我一样没有发现,那么跟着下边的时间线来捋一遍就明了了。
时间线 :
T1: 请求A调用 acquireLock(),成功获取锁,返回 true
T2: 请求A进入业务处理(耗时操作)
T3: 请求B调用 acquireLock(),因为锁被A持有,返回 false
❌ 注意:这里返回 false,不是抛异常
T4: 请求B进入 if(!lockFlagRaise) 分支,抛出 RuntimeException
T5: 请求B被 catch 捕获,执行 JedisUtils.del(...)
⚠️ 关键问题:请求B释放了请求A持有的锁!
T6: 请求C调用 acquireLock(),发现锁已被释放,成功获取锁
T7: 请求C进入业务处理
T8: 请求A完成业务,执行 JedisUtils.del(...)
⚠️ 此时锁可能已被C持有,A释放了C的锁
结果:多个请求同时执行业务逻辑,造成重复生成
结合代码分析一下
java
try{
boolean lockFlagRaise= JedisUtils.acquireLock(...);
// 情况1:acquireLock锁竞争失败,返回false
// 情况2:acquireLock抛出异常,如redis宕机、网络异常
// 情况3:义务处理抛出异常
if(!lockFlagRaise){
// 获取锁失败,抛出的异常会被catch捕获
throw new RuntimeException("抛出异常");
}
} catch (Exception e) {
// 无论是哪种异常,都会执行这里
// 关键!!!情况1和情况2中,当前线程根本没获取到锁~ 然后就误删了其他线程持有的锁
JedisUtils.del(...);
}
压测时可能出现的异常情况:
1、Redis连接池耗尽:大量请求同时获取连接,部分请求超时抛异常
2、网络抖动:偶发的网络超时导致 acquireLock 抛异常
3、Redis慢查询:某个请求阻塞,导致其他请求超时
修改后的代码
java
boolean lockFlagRaise= JedisUtils.acquireLock(productListByIds.get(0).getProductId()+":createLock","1",20*60);
if(!lockFlagRaise){
throw new RuntimeException("该产品正在提取中,请稍后刷新再试。联系电话010-xxxxxxxx-1。");
}
try{
---------业务逻辑处理,此处省略-----------
//释放锁
JedisUtils.del(productListByIds.get(0).getProductId()+":createLock");
} catch (Exception e) {
//释放锁
JedisUtils.del(productListByIds.get(0).getProductId()+":createLock");
logger.info("指令生成失败原因["+e.getMessage()+"]");
throw new RuntimeException(e.getMessage());
}
T1: 请求A调用 acquireLock(),成功获取锁,返回 true
T2: 请求A进入业务处理
T3: 请求B调用 acquireLock(),返回 false
T4: 请求B执行 if(!lockFlagRaise),抛出 RuntimeException
✅ 注意:这里没有进入 try-catch,因为异常发生在try外部
✅ 不会执行任何 del 操作
T5: 请求A完成业务,执行 JedisUtils.del(...) 释放锁
T6: 请求C调用 acquireLock(),成功获取锁
T7: 请求C进入业务处理
结果:业务逻辑串行执行,不会重复生成
总结
1、第一段代码在压测时出现重复生成的根本原因:
(1)错误释放未持有的锁:锁获取失败的请求错误地释放了其他线程持有的锁
(2)锁失效的连锁反应:一次错误释放导致多个请求并发执行
(3)缺少锁持有者验证:没有通过唯一标识验证释放锁的合法性
2、第二段代码为什么不会:
(1)锁获取在 try-catch 外部,获取失败的请求不会进入异常处理
(2)异常处理只针对业务逻辑,不会误释放锁