问题
项目中使用的到了分布式锁,然后考虑到有多个业务接口都要加分布式锁,所以,就把分布式锁,放到了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的大小来判断优先级,值越小,优先级越高
加上这个注解之后,就是笔记中第二个图片的模型了
- 加分布式锁
- 开启事务
- 执行业务代码
- 提交事务
- 释放锁
所以,就是一句话:让分布式锁切面的优先级高于事务即可