引入
由于项目中使用的到了分布式锁,然后考虑到有多个业务接口都要加分布式锁,所以,就把分布式锁,放到了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()-->orderCreate();
5.释放锁
}
}
这里是伪代码,其中com.test.RedisLock是我自定义的一个注解,在加锁之前会尝试获取自定义注解中配置的加锁信息;这样的话,我哪个接口需要加锁,就只需要在接口上,加上@RedisLock注解即可。
问题:
在外部系统调用创建订单接口orderCreate()的时候,由于网络的原因,导致接口响应时间超时,结果上游业务系统就进行了dubbo重试,又调用了一遍创建接口,导致我的系统中,有两笔相同的订单信息;
然后排查了下代码,接口中是有根据业务单号做幂等校验;那也就是说:第二次调用的时候,幂等校验未生效,幂等既然未生效,那么就可能是因为事务还没有提交。
按照我的想法,系统应该是这样的:
但是实际查了下日志,发现了问题,代码在执行的时候,并不是先加锁,再开启事务的;而是这样的:
这个模型就有问题了:
- 第一次调用接口的时候,超时了,但是业务代码还在执行。
- 紧接着dubbo重试机制,导致发起了第二次接口调用,在调用的时候,先开启了事务,接口来尝试加锁,问题就是在这里,在第二次来尝试加锁的时候,第一次的调用已经执行完毕,释放了锁,但是还没有提交事务。
- 第二次调用加锁成功,执行业务代码,业务代码中先进行幂等判断,由于此时第一次的事务还未提交,所以,数据库中还没有上一次掉用的业务单号信息,那第二次就幂等校验通过了,接着开始进行创建订单等其他的业务操作。
- 此时第一次接口调用提交了事务;在第二次调用执行完毕之后,也提交了事务。
- 此时这就导致有了两笔相同的订单信息。
问题分析
这是由于切面优先级产生的问题,pring事务底层也是通过AOP来实现的,所以就涉及到了切面优先级的问题,在都没有指定优先级的情况下,事务的切面优先级高,我自己定义的用来处理分布式锁的切面优先级低;
解决办法
在处理Redis分布式锁的切面上,指定优先级为最高,因为在aop处理切面的时候,如果有多个切面,会根据@order的大小来判断优先级,值越小,优先级越高。加上这个注解之后,
- 加分布式锁
- 开启事务
- 执行业务代码
- 提交事务
- 释放锁