使用Spring AOP实现注解式的分布式锁

这里简单说一个springboot生态下基于redis实现的分布式锁方案。预期实现的效果是在要加锁的方法上添加一个注解,然后就能根据请求参数得到并加上锁,方法执行完后,也会自动释放锁。这样在实现方法时,开发者就可以只关注业务逻辑,不用考虑加锁解锁相关的事情。务求整个过程的丝滑程度类似Spring的 @Transactional 注解。

这个分布式锁暂时基于redis来实现,用来和redis交互的组件则是spring-redisson。当然,我也考虑过基于mysql来实现,以后有时间了也会写一个mysql的版本。不过不管是redis还是mysql,两者都只是实现分布式锁的一个基础中间件,对整体实现思路没有什么影响。

先来看下这个分布式锁的大致结构图:

 图中左侧的redis/redisson/RedisProperties是我们实现分布式锁的基础,它们的作用是不需要多说的;右下角的Business代表了各种业务需求及对应的方法,他们需要使用分布式锁来实现业务处理时的互斥性,这也不用解释;在下面的内容中会详细介绍下其他部分,也就是我们这个分布式锁的主要组成部分。

LockAdvisor

LockAdvisor 是分布式锁的基础组件,它由LockPointcut 和 LockAdvice 组成。当然,不只是当前这个分布式锁,spring aop的核心结构一般都是 AdvisorAdvisor中又有PointcutAdvice两个成员,两者作用大致如下图所示:

Pointcut发挥作用是在应用启动时Bean实例化的过程中,而Advice发挥作用是在目标方法被调用执行的过程中。

Spring在创建Bean实例时,会用所有的Advisor和Bean实例进行匹配,匹配成功了就会创建相应的代理。这其中,匹配是依赖Pointcut来实现的,创建出的代理要做什么则是由Advice决定的。可以说Advisor是Spring创建代理的一个起点。

spring创建代理的过程可以查看spring中的这个方法:AbstractAutoProxyCreator.wrapIfNecessary() 。

LockPointcut

前面也说了,Pointcut会根据方法定义中的信息决定要拦截哪些方法。在分布式锁这个case里, LockPointcut 会根据方法中是否存在 @RLock注解来完成拦截并创建代理。当然实际情况会比较复杂一点,这里在识别到@RLock注解后又对注解中的参数进行了解析和缓存以便进行复用,所以在实现的时候是继承的StaticMethodMatcherPointcut这个类。如果只是要匹配@RLock注解,完全可以依赖Spring提供的AnnotationMethodMatcher来实现。

在springboot中提供了多种PointcutMethodMatcher的实现类,在使用时可以根据自己的情况选择使用spring提供的实现,不必一定要自己造轮子。

LockAdvice

LockAdvice里记录了在目标方法执行前后获得锁及释放锁的详细过程。在这个分布式锁的实现中,我把LockAdvice分成了LockInterceptorLockAspectSupportSpelEvaluator三个部分,三者的关系如下图:

LockAdvice分成三部分的目的是为了划分职责,也就是遵循单一性原则。三部分的职责分别如下:

  1. LockInterceptor 只是简单定义了切面,仅是个入口,具体的锁处理还是需要父类LockAspectSupport来实现;
  2. LockAspectSupport 提供了锁能力的支持,在这个类里会完成与redis的通信从而实现锁获得和锁关闭的处理
  3. SpelEvaluator 提供了SpEL解析的能力,分布式锁的key一般不会是一个常量,需要根据方法参数动态组装,SpelEvaluator就提供了根据方法参数动态组装锁key的能力

LockAutoConfiguration

LockAutoConfiguration 看起来像是一个配置类,但实际上在这个类里完成的是分布式锁实现中需要的各种Bean的创建。这里我们我们可以直接看下代码:

从代码可以看到,LockAutoConfiguration中确实引入了一个配置类 RedisProperties,这个类对应着项目配置文件(application.yml)中redis的配置。通过@EnableConfigurationProperties(RedisProperties.class)可以获得redis的配置信息,从而创建RedisClient的Bean实例。

前面提到的几个类的实例都会在这里完成创建并最终被Spring得到并管理使用起来。

EnableRLock

前面提到在LockAutoConfiguration中创建了多个Bean,但是这些Bean该怎么注入到Spring容器中呢。

我们在写代码的时候应该都用过@ComponentScan注解,在启动类中通过@ComponentScan注解显示注入LockAutoConfiguration及其中的各种Bean确实是一个办法,但不好不优雅。

刷过springboot面试题的同学应该接触过spring.factories,这是一种springboot推荐的做法。springboot的应用在启动时会扫描所有依赖中的spring.factories文件,并自动注入文件中定义的AutoConfiguration类及类中定义的各种Bean。

在这个分布式锁的实现中采用的是另一种做法,是通过在项目启动类上定义的 @EnableRLock注解及@EnableRLock中的@Import注解注入LockAutoConfiguration及其中的各种Bean。体验上类似开启springboot事务支持能力的@EnbaleTransactional注解。(关于@Import注解可以参考 SpringBoot探索01 – @Import注解 )

这个分布式锁的具体实现可以看redis上的代码: zhyea / rlock-spring-boot-starter  

也可以直接添加如下依赖使用:

就这样。

END!!!

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据