这里简单说一个springboot生态下基于redis实现的分布式锁方案。预期实现的效果是在要加锁的方法上添加一个注解,然后就能根据请求参数得到并加上锁,方法执行完后,也会自动释放锁。这样在实现方法时,开发者就可以只关注业务逻辑,不用考虑加锁解锁相关的事情。务求整个过程的丝滑程度类似Spring的 @Transactional
注解。
这个分布式锁暂时基于redis来实现,用来和redis交互的组件则是spring-redisson。当然,我也考虑过基于mysql来实现,以后有时间了也会写一个mysql的版本。不过不管是redis还是mysql,两者都只是实现分布式锁的一个基础中间件,对整体实现思路没有什么影响。
先来看下这个分布式锁的大致结构图:
图中左侧的redis/redisson/RedisProperties是我们实现分布式锁的基础,它们的作用是不需要多说的;右下角的Business代表了各种业务需求及对应的方法,他们需要使用分布式锁来实现业务处理时的互斥性,这也不用解释;在下面的内容中会详细介绍下其他部分,也就是我们这个分布式锁的主要组成部分。
LockAdvisor
LockAdvisor
是分布式锁的基础组件,它由LockPointcut
和 LockAdvice
组成。当然,不只是当前这个分布式锁,spring aop的核心结构一般都是 Advisor
,Advisor中
又有Pointcut
和Advice
两个成员,两者作用大致如下图所示:
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中提供了多种Pointcut
和MethodMatcher
的实现类,在使用时可以根据自己的情况选择使用spring提供的实现,不必一定要自己造轮子。
LockAdvice
在LockAdvice
里记录了在目标方法执行前后获得锁及释放锁的详细过程。在这个分布式锁的实现中,我把LockAdvice
分成了LockInterceptor
、LockAspectSupport
、SpelEvaluator
三个部分,三者的关系如下图:
把LockAdvice
分成三部分的目的是为了划分职责,也就是遵循单一性原则。三部分的职责分别如下:
- LockInterceptor 只是简单定义了切面,仅是个入口,具体的锁处理还是需要父类
LockAspectSupport
来实现; - LockAspectSupport 提供了锁能力的支持,在这个类里会完成与redis的通信从而实现锁获得和锁关闭的处理
- SpelEvaluator 提供了SpEL解析的能力,分布式锁的key一般不会是一个常量,需要根据方法参数动态组装,
SpelEvaluator
就提供了根据方法参数动态组装锁key的能力
LockAutoConfiguration
LockAutoConfiguration
看起来像是一个配置类,但实际上在这个类里完成的是分布式锁实现中需要的各种Bean的创建。这里我们我们可以直接看下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) @ConditionalOnClass({Redisson.class, RedissonClient.class, RedisProperties.class}) @EnableConfigurationProperties(RedisProperties.class) public class RLockConfiguration { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public RedissonClient redissonClient(RedisProperties properties) { ··· return Redisson.create(cfg); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public LockOperationSource lockOperationSource() { return new AnnotationLockOperationSource(); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public LockInterceptor lockInterceptor(LockOperationSource lockOperationSource, RedissonClient redissonClient) { LockInterceptor interceptor = new LockInterceptor(); interceptor.setLockOperationSource(lockOperationSource); interceptor.setRedissonClient(redissonClient); return interceptor; } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public LockAdvisor lockAdvisor(LockInterceptor lockInterceptor, LockOperationSource lockOperationSource) { LockAdvisor advisor = new LockAdvisor(); advisor.setLockOperationSource(lockOperationSource); advisor.setAdvice(lockInterceptor); return advisor; } } |
从代码可以看到,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
也可以直接添加如下依赖使用:
1 2 3 4 5 |
<dependency> <groupId>org.chobit.spring</groupId> <artifactId>rlock-spring-boot-starter</artifactId> <version>0.0.3</version> </dependency> |
就这样。
END!!!
发表评论