redis分布式锁+事务+AOP一起使用注意点

问题

项目中使用的到了分布式锁,然后考虑到有多个业务接口都要加分布式锁,所以,就把分布式锁,放到了aop来处理,通过环绕通知来实现,但是在使用的时候,出现了问题:
我加的切面是这样的

@Component
@Aspect
public class RedisLockAspect {
    
    

    @Around("@annotation(com.test.RedisLock)")
    public Object lockRedisLock(ProceedingJoinPoint pjp) throws Throwable {
    
    
        1.获取自定义注解中指定的RedisKey,获取到指定的锁超时时间等参数
        2.加锁
        3.加锁失败的话,return
        4.调用业务代码 pjp.proceed();
        5.释放锁
    }
}

这里是伪代码,其中com.test.RedisLock是我自定义的一个注解,在加锁之前会尝试获取自定义注解中配置的加锁信息;
这样的话,我哪个接口需要加锁,就只需要在接口上,加上@RedisLock注解即可

问题是这样的:
在外部系统调用创建订单接口orderCreate()的时候,由于网络的原因,导致接口响应时间超时,结果上游业务系统就进行了dubbo重试,又调用了一遍创建接口,导致我的系统中,有两笔相同的订单信息;
然后排查了下代码,接口中是有根据上游的业务单号做幂等校验;那也就是说:第二次调用的时候,幂等校验未生效,幂等未生效,就可能是因为事务还没有提交
图1

图2

按照我的想法,系统应该是这样的;但是实际查了下日志,发现了问题,代码在执行的时候,并不是先加锁,再开启事务的;而是正好反了过来
图3
这个模型就有问题了:

  1. 第一次调用接口的时候,超时了,但是业务代码还在执行
  2. 紧接着dubbo重试机制,导致发起了第二次接口调用,在调用的时候,先开启了事务,接口来尝试加锁,问题就是在这里,在第二次来尝试加锁的时候,第一次的调用已经执行完毕,释放了锁,但是还没有提交事务
  3. 第二次调用加锁成功,执行业务代码,业务代码中先进行幂等判断,由于此时第一次的事务还未提交,所以,数据库中还没有上游的业务单号信息,那第二次就幂等校验通过了,接着开始进行创建订单等其他的业务操作
  4. 此时第一次接口调用提交了事务;在第二次调用执行完毕之后,也提交了事务

这里之所以事务和Redis分布式锁对应的切面执行顺序错乱,是因为有一个点忽略了:
切面优先级
spring事务底层也是通过AOP来实现的,所以就涉及到了切面优先级的问题,从日志来分析,在都没有指定优先级的情况下,事务的切面优先级高,我自己定义的用来处理分布式锁的切面优先级低;知道了问题,就debug来验证一下
图4
从图中,可以看到,确实是先执行了事务,在开启事务之后,再来调用加锁的逻辑了

解决办法

解决办法有两个:
1.在处理Redis分布式锁的切面上,指定优先级为最高
2.将事务的范围缩小,幂等校验的逻辑放在事务外面、创建订单之后的其他非必要的业务逻辑放到事务外面;
对于第一种解决办法,只需要在处理分布式锁的切面加加上这个即可

@Order(Ordered.HIGHEST_PRECEDENCE)

因为在aop处理切面的时候,如果有多个切面,会根据order的大小来判断优先级,值越小,优先级越高
在这里插入图片描述

加上这个注解之后,就是笔记中第二个图片的模型了

  1. 加分布式锁
  2. 开启事务
  3. 执行业务代码
  4. 提交事务
  5. 释放锁

所以,就是一句话:让分布式锁切面的优先级高于事务即可

猜你喜欢

转载自blog.csdn.net/CPLASF_/article/details/108931549