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

    科技2022-08-24  102

    问题

    项目中使用的到了分布式锁,然后考虑到有多个业务接口都要加分布式锁,所以,就把分布式锁,放到了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重试,又调用了一遍创建接口,导致我的系统中,有两笔相同的订单信息; 然后排查了下代码,接口中是有根据上游的业务单号做幂等校验;那也就是说:第二次调用的时候,幂等校验未生效,幂等未生效,就可能是因为事务还没有提交

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

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

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

    解决办法

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

    @Order(Ordered.HIGHEST_PRECEDENCE)

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

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

    加分布式锁开启事务执行业务代码提交事务释放锁

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

    Processed: 0.008, SQL: 9