• 记录一次因执行时间过长锁已经释放导致finally块再次unlock引发的异常


    一、前言

    因为我的一个需求需要请求一个耗时比较长的接口(耗时长其实是对接方的锅),该接口交给了Spring事务管理,并且使用了分布式锁,但是在请求的时候,出现error,看日志发现是unlock的时候没有锁可以去解锁,才爆出的异常。然后又由于使用了事务,导致整个请求的数据被回滚。
    在这里插入图片描述
    然后就通过日志看请求时间和这次异常的时间,发现整整相差了1分钟,而分布式锁设置的时间只有5s

        @Override
    	@PostMapping(path = "xxxxx")
    	@Transactional(rollbackFor = Exception.class)
    	public Result create(@RequestBody Request request) {
    	// 通过缓存防止重复申请
    		String lockDate = new StringBuilder().append(request.getId()).toString();
    		DistributedLock lock = distributedLockService.getLock(LOCK_PREFIX + lockDate);
    		if (!lock.tryLock(0, 5, TimeUnit.SECONDS)) {
    			return Result.error(100, "请勿重复请求");
    		}
    		try{
    		    //业务代码
    		}catch (Exception e) {
    			TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    			logger.warn(e.getMessage());
    			return Result.error(100, "证书申请失败");
    		} finally {
    		     lock.unlock();
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    二、解决

    这就导致tryLock设置的时间已经超过,锁此时已自动释放,那么走到finally块时,又执行lock.unlock(),导致上述异常,而数据又回滚,就会产生好像执行了但实际上啥也没有的情景。
    那么我们的解决思路有两种:

    • 第一种延长锁的持续时间,但是这个我之前说过这个超时是因为对接方的锅,如果使用其他对接方请求这个接口还是很快的。那么这个想法就先pass
    • 那第二种解决办法也是最后采取的解决方式,我们知道其实这个bug引起的最不好的体验就是整个请求产生的数据因为发生Bug,被Spring事务回滚了,导致我们想知道发生了啥,去db中找也找不到,而且更为重要的是,如果采用上一种延长锁的持续时间,可能会导致锁的竞争更加激烈,会影响系统的性能,并且其实大部分请求并不需要那么久,只是个别请求产生的延迟导致的Bug.那么最终采取的解决方式也比较简单:就是在finally块处,对lock.unlock进行try/catch捕获,如果捕获到异常,什么也不处理,这样就算有时候出现超时,超过了锁的持续时间,就算锁已被释放,这个时候去执行finally块被捕获,也不会被Spring事务回滚
    @Override
    	@PostMapping(path = "xxxxx")
    	@Transactional(rollbackFor = Exception.class)
    	public Result create(@RequestBody Request request) {
    	// 通过缓存防止重复申请
    		String lockDate = new StringBuilder().append(request.getId()).toString();
    		DistributedLock lock = distributedLockService.getLock(LOCK_PREFIX + lockDate);
    		if (!lock.tryLock(0, 5, TimeUnit.SECONDS)) {
    			return Result.error(100, "请勿重复请求");
    		}
    		try{
    		    //业务代码
    		}catch (Exception e) {
    			TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    			logger.warn(e.getMessage());
    			return Result.error(100, "证书申请失败");
    		} finally {
    		   try{
    		     lock.unlock();
    		     }catch(Exception e){
    		     }
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    当然,唯一不好的,就是如果你的公司如果使用了sonarLint,会爆

    Unlock this lock along all executions paths of this method
    
    • 1

    但是这个并不会产生什么大问题,不去管它就好,如果大家有什么更好的解决方案,可以评论区一起讨论。

  • 相关阅读:
    【docker】Docker网络与iptables
    基于boost库的搜索引擎
    Go基础-2
    华为交换机:配置telnet和ssh、web访问
    基于ssm物业报修管理系统毕业设计源码111024
    【软件工程之美 - 专栏笔记】34 | 账号密码泄露成灾,应该怎样预防?
    webrtc优势与模块拆分
    2023-05-28 mysql列存储引擎-外表与两表内连接进行外连接处理-分析
    Vue前端框架基础+Element的使用
    Java集合的快速失败机制 fail-fast
  • 原文地址:https://blog.csdn.net/qq_44754515/article/details/127652114