锁与try catch的位置引发的思考

前言

在测试一个接口的时候,使用jmeter进行压测的时候,出现了多次调用成功的情景。
开始的时候以为是redis中的锁的问题或者是业务逻辑处理的问题,但是走查了一遍代码后,排除了。经过一通分析,发现是锁的获取和释放的位置出现了问题。
修改后也就在此做个记录~

问题代码

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的锁
结果:多个请求同时执行业务逻辑,造成重复生成

结合代码分析一下

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慢查询:某个请求阻塞,导致其他请求超时

修改后的代码

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)异常处理只针对业务逻辑,不会误释放锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值