分布式锁优先级问题

676 阅读3分钟

引入

由于项目中使用的到了分布式锁,然后考虑到有多个业务接口都要加分布式锁,所以,就把分布式锁,放到了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重试,又调用了一遍创建接口,导致我的系统中,有两笔相同的订单信息;

然后排查了下代码,接口中是有根据业务单号做幂等校验;那也就是说:第二次调用的时候,幂等校验未生效,幂等既然未生效,那么就可能是因为事务还没有提交。

clipboard.png

按照我的想法,系统应该是这样的:

clipboard.png

但是实际查了下日志,发现了问题,代码在执行的时候,并不是先加锁,再开启事务的;而是这样的:

clipboard.png

这个模型就有问题了:

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

问题分析

这是由于切面优先级产生的问题,pring事务底层也是通过AOP来实现的,所以就涉及到了切面优先级的问题,在都没有指定优先级的情况下,事务的切面优先级高,我自己定义的用来处理分布式锁的切面优先级低;

解决办法

在处理Redis分布式锁的切面上,指定优先级为最高,因为在aop处理切面的时候,如果有多个切面,会根据@order的大小来判断优先级,值越小,优先级越高。加上这个注解之后,

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