分布式系统中,节点并发访问共享资源可能导致数据一致性问题。分布式锁是常见的解决方案,可确保操作原子性。Redisson是基于Redis的Java分布式对象库,提供多种分布式同步工具,包括分布式锁。Redisson与Redis(实时数据平台)和Valkey兼容,是Java实现实时数据处理的理想选择。 Redis 官网:Redis - The Real-time Data Platform Redisson 官网:Redisson | Valkey & Redis Java client. Ultimate Real-Time Data Platform
1. 什么是 Redisson
Redisson 是一个开源的 Java 客户端,用于与 Redis 进行交互,并提供了一系列高级功能,如分布式集合、分布式锁、分布式服务等。Redisson 的设计目标是简化在分布式系统中的开发工作,提供简单易用的 API 来实现复杂的分布式协调任务。
2. Redisson 的特点
- 可重入锁:Redisson 提供的分布式锁是可重入的,意味着同一个线程可以多次获取同一把锁而不会造成死锁。
- 自动续期:如果持有锁的客户端在操作过程中崩溃或断开连接,Redisson 可以通过 Watchdog 机制自动续期锁,避免死锁问题。
- 公平锁与非公平锁:Redisson 支持公平锁(按照请求顺序分配锁)和非公平锁(允许插队)。默认情况下,锁是非公平的。
- 支持异步和响应式编程:除了传统的同步方法外,Redisson 还支持异步(RFuture)和响应式编程模型(如 Reactive 和 RxJava)。
- 多节点支持:Redisson 支持单机模式、集群模式、哨兵模式等多种 Redis 部署方式,确保在不同场景下的高可用性和扩展性。
3. Redisson 分布式锁的工作原理
Redisson 的分布式锁是基于 Redis 的 Lua 脚本实现的,确保了操作的原子性。以下是其核心工作流程:
3.1 加锁过程
当线程尝试获取锁时,Redisson 会向 Redis 发送一个 Lua 脚本,检查锁是否存在。
- 如果锁不存在,则设置锁,并记录当前线程 ID 和重入次数。
- 如果锁已经存在,并且是由当前线程持有的,则增加重入次数。
- 如果锁已经被其他线程持有,则当前线程进入等待状态。
3.2 解锁过程
解锁时,Redisson 同样使用 Lua 脚本来确保操作的原子性。
- 如果当前线程仍然持有锁,则减少重入次数。
- 如果重入次数为 0,则删除锁。
- 如果锁被意外释放(例如客户端崩溃),Watchdog 机制会检测到并释放锁。
3.3 Watchdog 机制
Redisson 提供了一个 Watchdog 看门狗机制,用于自动续期锁。
- 默认情况下,锁的有效期为 30 秒。
- 如果持有锁的客户端没有主动释放锁,并且锁即将过期,Watchdog 会自动延长锁的有效期。
- 这种机制可以有效防止因为客户端崩溃而导致的死锁问题。
4. Redisson 分布式锁的使用场景
Redisson 的分布式锁适用于以下几种典型的分布式系统场景:
4.1 资源竞争控制
在多个服务实例之间,确保对共享资源的访问是互斥的。例如:
- 更新数据库中的某个记录。
- 写入文件系统。
- 访问外部 API 或第三方服务。
4.2 任务调度
在分布式环境中,确保某个任务只在一个节点上执行。例如:
- 定时任务(如每天凌晨执行的数据清理任务)。
- 避免多个节点同时处理相同的任务。
4.3 幂等性保障
在分布式系统中,确保某些操作是幂等的。例如:
- 避免重复支付。
- 避免重复下单。
4.4 缓存更新
在缓存失效时,确保只有一个线程去重新加载数据,避免缓存击穿问题。
5. 快速入门
1. 引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
2. 配置 RedissonClient Bean
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedisConfig {
@Bean
public RedissonClient redissonClient() {
// 配置类
Config config = new Config();
// 添加redis地址,这里添加了单点的地址,也可以使用config.useClusterServers()添加集群地址
config.useSingleServer()
.setAddress("redis://localhost:6379")
.setPassword("password");
// 创建客户端
return Redisson.create(config);
}
}
3. 使用 Redisson 分布式锁
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.TimeUnit;
@Slf4j
@SpringBootTest
public class RedissonTests {
@Autowired
private RedissonClient redissonClient;
@Test
public void test() throws InterruptedException {
// 1.获取锁对象,指定锁名称
RLock lock = redissonClient.getLock("myLock");
try {
// 2.尝试获取锁,参数:waitTime、leaseTime、时间单位
boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS);
if (!isLock) {
// 获取锁失败处理 ..
log.error("获取锁失败");
} else {
// 获取锁成功处理
log.info("获取锁成功");
}
} finally {
// 4.释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
log.info("释放锁成功");
}
}
}
}
说明:
- tryLock(long waitTime, long leaseTime, TimeUnit unit) 方法用于尝试获取锁。
- waitTime:获取锁的等待时间。当获取锁失败后可以多次重试,直到waitTime时间耗尽。waitTime默认 -1 ,即失败后立刻返回,不重试。
- leaseTime:锁超时释放时间。默认是 30 ,同时会利用 WatchDog 来不断更新超时时间。需要注意的是,如果手动设置leaseTime值,会导致 WatchDog 失效。
- unit:时间单位。
- unlock() 方法用于手动释放锁。
- isHeldByCurrentThread() 方法用于判断当前线程是否持有锁,避免误释放其他线程的锁。
6. 通用分布式锁组件设计
Redisson的分布式锁使用并不复杂,基本步骤包括:
创建锁对象
尝试获取锁
处理业务
释放锁
但是,除了第3步以外,其它都是非业务代码,导致:
- 代码重复度高
- 对业务逻辑侵入性强
- 可维护性差
为了提升开发效率和代码整洁度,我们希望通过 AOP + 自定义注解 的方式,将加锁逻辑从业务代码中抽离出来,形成一个通用的分布式锁组件。
6.1 实现思路
6.1.1. 自定义注解 @MyLock
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLock {
String name(); // 锁名称
long waitTime() default 1; // 最大等待时间
long leaseTime() default -1; // 锁持有时间,-1表示默认由策略决定
TimeUnit unit() default TimeUnit.SECONDS; // 时间单位
}
6.1.2 定义切面
1. 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 编写切面代码
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
@Component
@Aspect
@RequiredArgsConstructor
public class MyLockAspect implements Ordered {
private final RedissonClient redissonClient;
@Around("@annotation(myLock)")
public Object tryLock(ProceedingJoinPoint pjp, MyLock myLock) throws Throwable {
// 1.创建锁对象
RLock lock = redissonClient.getLock(myLock.name());
// 2.尝试获取锁
boolean isLock = lock.tryLock(myLock.waitTime(), myLock.leaseTime(), myLock.unit());
// 3.判断是否成功
if(!isLock) {
// 3.1.失败,快速结束
throw new RuntimeException("请求太频繁");
}
try {
// 3.2.成功,执行业务
return pjp.proceed();
} finally {
// 4.释放锁
lock.unlock();
}
}
@Override
public int getOrder() {
return 0;
}
}
注意:
在实际开发中,很多业务方法上不仅会有分布式锁注解(如 @MyLock),还可能带有 Spring 的事务注解(如 @Transactional)。如果你希望:
- 先获取锁
- 再开启事务
- 最后执行业务
就需要保证:加锁切面的 order 值小于事务切面的 order 值。
而 Spring 内置的事务切面默认的 order 是 Integer.MAX_VALUE,也就是优先级最低。因此,你的分布式锁切面必须设置一个更小的 order 值,以确保它在事务之前执行。
层级
示例值
描述
最高优先级
Integer.MIN_VALUE
最先执行
较高优先级
0, 100, 1000
默认
Ordered.LOWEST_PRECEDENCE
默认值为 Integer.MAX_VALUE
事务切面
默认就是 Ordered.LOWEST_PRECEDENCE
最后执行
6.1.3 使用锁
@MyLock(name = "order_lock", waitTime = 3, leaseTime = 10)
public void createOrder(Long userId) {
// 业务逻辑
}
目前为止,业务中无需手动编写加锁、释放锁的逻辑了,没有任何业务侵入,使用起来也非常优雅。
不过呢,现在还存在几个问题:
- Redisson中锁的种类有很多,目前的代码中把锁的类型写死了;
- Redisson中获取锁的逻辑有多种,比如获取锁失败的重试策略,目前都没有设置;
- 锁的名称目前是写死的,并不能根据方法参数动态变化。
下面,要对锁的实现进行优化,解决上述问题。
6.2 工厂模式切换锁类型
Redisson 提供了多种分布式锁类型,比如:
锁类型 |
说明 |
RLock(默认) |
可重入锁 |
getFairLock() |
公平锁 |
getReadWriteLock().readLock() |
读锁 |
getReadWriteLock().writeLock() |
写锁 |
为了解耦业务逻辑与具体锁实现,采用工厂模式用于切换锁类型。
6.2.1 锁类型枚举
定义一个锁类型枚举:
public enum MyLockType {
RE_ENTRANT_LOCK, // 可重入锁
FAIR_LOCK, // 公平锁
READ_LOCK, // 读锁
WRITE_LOCK, // 写锁
}
然后在自定义注解中添加锁类型这个参数:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLock {
String name(); // 锁名称
long waitTime() default 1; // 最大等待时间
long leaseTime() default -1; // 锁持有时间,-1表示默认由策略决定
TimeUnit unit() default TimeUnit.SECONDS; // 时间单位
MyLockType lockType() default MyLockType.RE_ENTRANT_LOCK; // 锁类型
}
6.2.2 锁对象工厂
定义一个锁工厂,用于根据锁类型创建锁对象:
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import java.util.EnumMap;
import java.util.Map;
import java.util.function.Function;
import static com.zjp.redissondemo.enums.MyLockType.*;
@Component
public class MyLockFactory {
private final Map<MyLockType, Function<String, RLock>> lockHandlers;
public MyLockFactory(RedissonClient redissonClient) {
this.lockHandlers = new EnumMap<>(MyLockType.class);
this.lockHandlers.put(RE_ENTRANT_LOCK, redissonClient::getLock);
this.lockHandlers.put(FAIR_LOCK, redissonClient::getFairLock);
this.lockHandlers.put(READ_LOCK, name -> redissonClient.getReadWriteLock(name).readLock());
this.lockHandlers.put(WRITE_LOCK, name -> redissonClient.getReadWriteLock(name).writeLock());
}
public RLock getLock(MyLockType lockType, String name){
return lockHandlers.get(lockType).apply(name);
}
}
说明:
- MyLockFactory内部持有了一个Map,key是锁类型枚举,值是创建锁对象的Function。注意这里不是存锁对象,因为 锁是“有状态”的资源,不能在应用启动时就创建好并缓存起来。我们只需要保存“创建锁”的逻辑,而不是具体的锁实例。
- 锁对象(RLock)是有状态的:
- 每个 RLock 对象对应一个特定的锁名称(key)。
- 同一个锁类型(比如可重入锁)可以用于不同的业务场景(如订单锁、库存锁等),它们应该是不同的锁对象。
- 所以:锁必须根据传入的 name 动态创建,不能提前创建好缓存起来。
- 为什么用 EnumMap:
- 性能高:底层基于数组实现,通过枚举的 ordinal() 值直接索引,查找效率接近 O(1);
- 类型安全:只接受指定枚举类型的 key;
- 节省内存:相比 HashMap 更紧凑的数据结构。
6.2.3 改造切面代码
我们将锁对象工厂注入MyLockAspect,然后就可以利用工厂来获取锁对象了:
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
@Component
@Aspect
@RequiredArgsConstructor
public class MyLockAspect implements Ordered {
private final MyLockFactory lockFactory;
@Around("@annotation(myLock)")
public Object tryLock(ProceedingJoinPoint pjp, MyLock myLock) throws Throwable {
// 1.创建锁对象
RLock lock = lockFactory.getLock(myLock.lockType(), myLock.name());
// 2.尝试获取锁
boolean isLock = lock.tryLock(myLock.waitTime(), myLock.leaseTime(), myLock.unit());
// 3.判断是否成功
if (!isLock) {
// 3.1.失败,快速结束
throw new RuntimeException("请求太频繁");
}
try {
// 3.2.成功,执行业务
return pjp.proceed();
} finally {
// 4.释放锁
lock.unlock();
}
}
@Override
public int getOrder() {
return 0;
}
}
此时,在业务中,就能通过注解来指定自己要用的锁类型了:
@MyLock(name = "order_lock", lockType = MyLockType.FAIR_LOCK)
public void createOrder(Long userId) {
// 业务逻辑
}
6.3 锁失败策略
多线程争抢锁,大部分线程会获取锁失败,而失败后的处理方案和策略是多种多样的。目前,我们获取锁失败后就是直接抛出异常,没有其它策略,这与实际需求不一定相符。
6.3.1 策略分析
我们可以从两个维度来理解锁失败策略:
1. 是否重试?
- 不重试:立即返回或抛异常
- 固定次数/时间重试:尝试一段时间后放弃
- 无限重试:持续尝试直到获取锁
2. 失败后如何处理?
- 抛出异常:中断流程,由调用方处理
- 直接结束:表示未获得锁,不执行业务逻辑
重试策略 + 失败策略组合,总共以下几种情况:
由于锁失败策略种类有限、行为明确、不常变化,这里采用枚举策略模式。
枚举策略模式与策略+工厂模式的优缺点对比表:
对比项
枚举策略模式
策略+工厂模式
实现复杂度
简单,一个文件搞定
复杂,多个类+接口+工厂
扩展性
修改枚举即可扩展
新增类即可扩展,无需修改已有代码
可读性
所有策略集中查看
每个策略分散在不同类中
性能
无反射/代理,性能高
依赖 Spring 或工厂,性能略低
是否支持动态加载
否
是(如 SPI、BeanFactory)
使用便捷性
直接通过枚举调用
需要注入或工厂获取
是否符合开闭原则
符合(新增策略只需改枚举)
符合(完全解耦)
6.3.2 代码实现
import org.redisson.api.RLock;
public enum MyLockStrategy {
SKIP_FAST(){
@Override
public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
return lock.tryLock(0, prop.leaseTime(), prop.unit());
}
},
FAIL_FAST(){
@Override
public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
boolean isLock = lock.tryLock(0, prop.leaseTime(), prop.unit());
if (!isLock) {
throw new RuntimeException("请求太频繁");
}
return true;
}
},
KEEP_TRYING(){
@Override
public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
lock.lock( prop.leaseTime(), prop.unit());
return true;
}
},
SKIP_AFTER_RETRY_TIMEOUT(){
@Override
public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
return lock.tryLock(prop.waitTime(), prop.leaseTime(), prop.unit());
}
},
FAIL_AFTER_RETRY_TIMEOUT(){
@Override
public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
boolean isLock = lock.tryLock(prop.waitTime(), prop.leaseTime(), prop.unit());
if (!isLock) {
throw new RuntimeException("请求太频繁");
}
return true;
}
},
;
public abstract boolean tryLock(RLock lock, MyLock prop) throws InterruptedException;
}
然后,在MyLock注解中添加枚举参数:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLock {
String name(); // 锁名称
long waitTime() default 1; // 最大等待时间
long leaseTime() default -1; // 锁持有时间,-1表示默认由策略决定
TimeUnit unit() default TimeUnit.SECONDS; // 时间单位
MyLockType lockType() default MyLockType.RE_ENTRANT_LOCK; // 锁类型
MyLockStrategy strategy() default MyLockStrategy.FAIL_AFTER_RETRY_TIMEOUT; // 失败策略
}
最后,修改切面代码,基于用户选择的策略来处理:
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
@Component
@Aspect
@RequiredArgsConstructor
public class MyLockAspect implements Ordered {
private final MyLockFactory lockFactory;
@Around("@annotation(myLock)")
public Object tryLock(ProceedingJoinPoint pjp, MyLock myLock) throws Throwable {
// 1.创建锁对象
RLock lock = lockFactory.getLock(myLock.lockType(), myLock.name());
// 2.尝试获取锁
boolean isLock = myLock.strategy().tryLock(lock, myLock);
// 3.判断是否成功
if (!isLock) return null;
try {
// 3.2.成功,执行业务
return pjp.proceed();
} finally {
// 4.释放锁
lock.unlock();
}
}
@Override
public int getOrder() {
return 0;
}
}
这个时候,我们就可以在使用锁的时候自由选择锁类型、锁策略了。
6.4 基于SPEL的动态锁名
在分布式系统中,锁的粒度决定了并发控制的精确性。如果使用固定锁名,会导致:
- 所有用户的下单操作都串行化
- 并发性能下降
- 资源利用率低
而如果我们能根据业务参数动态生成锁名,就可以做到按需加锁、减少不必要的阻塞,并提升系统吞吐量。
关于 SPEL 表达式,可参考我之前写的:SpEL(Spring Expression Language)入门指南-CSDN博客
6.4.1 解析SPEL
@Component
@Aspect
@RequiredArgsConstructor
public class MyLockAspect implements Ordered {
private final MyLockFactory lockFactory;
/**
* SPEL的正则规则
*/
private static final Pattern pattern = Pattern.compile("\\#\\{([^\\}]*)\\}");
/**
* 方法参数解析器
*/
private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
@Around("@annotation(myLock)")
public Object tryLock(ProceedingJoinPoint pjp, MyLock myLock) throws Throwable {
// 1. 解析锁名(支持SPEL)
String lockName = getLockName(myLock.name(), pjp);
// 2. 创建锁对象
RLock lock = lockFactory.getLock(myLock.lockType(), lockName);
// 3. 尝试获取锁
boolean isLocked = myLock.strategy().tryLock(lock, myLock);
if (!isLocked) return null;
try {
// 4. 执行业务逻辑
return pjp.proceed();
} finally {
// 5. 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 解析锁名称
*
* @param name 原始锁名称
* @param pjp 切入点
* @return 解析后的锁名称
*/
private String getLockName(String name, ProceedingJoinPoint pjp) {
// 1.判断是否存在spel表达式
if (StringUtils.isBlank(name) || !name.contains("#")) {
// 不存在,直接返回
return name;
}
// 2.构建context,也就是SPEL表达式获取参数的上下文环境,这里上下文就是切入点的参数列表
EvaluationContext context = new MethodBasedEvaluationContext(
TypedValue.NULL, resolveMethod(pjp), pjp.getArgs(), parameterNameDiscoverer);
// 3.构建SPEL解析器
ExpressionParser parser = new SpelExpressionParser();
// 4.循环处理,因为表达式中可以包含多个表达式
Matcher matcher = pattern.matcher(name);
while (matcher.find()) {
// 4.1.获取表达式
String tmp = matcher.group();
String group = matcher.group(1);
// 4.2.这里要判断表达式是否以 T字符开头,这种属于解析静态方法,不走上下文
Expression expression = parser.parseExpression(group.charAt(0) == 'T' ? group : "#" + group);
// 4.3.解析出表达式对应的值
Object value = expression.getValue(context);
// 4.4.用值替换锁名称中的SPEL表达式
name = name.replace(tmp, ObjectUtils.nullSafeToString(value));
}
return name;
}
private Method resolveMethod(ProceedingJoinPoint pjp) {
// 1.获取方法签名
MethodSignature signature = (MethodSignature) pjp.getSignature();
// 2.获取字节码
Class<?> clazz = pjp.getTarget().getClass();
// 3.方法名称
String name = signature.getName();
// 4.方法参数列表
Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
return tryGetDeclaredMethod(clazz, name, parameterTypes);
}
private Method tryGetDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
try {
// 5.反射获取方法
return clazz.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
Class<?> superClass = clazz.getSuperclass();
if (superClass != null) {
// 尝试从父类寻找
return tryGetDeclaredMethod(superClass, name, parameterTypes);
}
}
return null;
}
@Override
public int getOrder() {
return 0;
}
}
6.5 最终代码
1. 添加依赖
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 配置 RedissonClient Bean
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedisConfig {
@Bean
public RedissonClient redissonClient() {
// 配置类
Config config = new Config();
// 添加redis地址,这里添加了单点的地址,也可以使用config.useClusterServers()添加集群地址
config.useSingleServer()
.setAddress("redis://localhost:6379")
.setPassword("password");
// 创建客户端
return Redisson.create(config);
}
}
3. 自定义注解 @MyLock
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLock {
String name(); // 锁名称,支持SPEL表达式,如 #userId 或 #{user.id}
long waitTime() default 1; // 最大等待时间
long leaseTime() default -1; // 锁持有时间,-1表示默认由策略决定
TimeUnit unit() default TimeUnit.SECONDS; // 时间单位
MyLockType lockType() default MyLockType.RE_ENTRANT_LOCK; // 锁类型
MyLockStrategy strategy() default MyLockStrategy.FAIL_AFTER_RETRY_TIMEOUT; // 失败策略
}
4. 锁类型枚举 MyLockType
public enum MyLockType {
RE_ENTRANT_LOCK, // 可重入锁
FAIR_LOCK, // 公平锁
READ_LOCK, // 读锁
WRITE_LOCK, // 写锁
}
5. 锁工厂 MyLockFactory
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import java.util.EnumMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class MyLockFactory {
private final Map<MyLockType, Function<String, RLock>> lockHandlers;
public MyLockFactory(RedissonClient redissonClient) {
this.lockHandlers = new EnumMap<>(MyLockType.class);
this.lockHandlers.put(RE_ENTRANT_LOCK, redissonClient::getLock);
this.lockHandlers.put(FAIR_LOCK, redissonClient::getFairLock);
this.lockHandlers.put(READ_LOCK, name -> redissonClient.getReadWriteLock(name).readLock());
this.lockHandlers.put(WRITE_LOCK, name -> redissonClient.getReadWriteLock(name).writeLock());
}
public RLock getLock(MyLockType lockType, String name){
return lockHandlers.get(lockType).apply(name);
}
}
6. 加锁失败策略枚举 MyLockStrategy
import org.redisson.api.RLock;
public enum MyLockStrategy {
SKIP_FAST(){
@Override
public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
return lock.tryLock(0, prop.leaseTime(), prop.unit());
}
},
FAIL_FAST(){
@Override
public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
boolean isLock = lock.tryLock(0, prop.leaseTime(), prop.unit());
if (!isLock) {
throw new RuntimeException("请求太频繁");
}
return true;
}
},
KEEP_TRYING(){
@Override
public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
lock.lock( prop.leaseTime(), prop.unit());
return true;
}
},
SKIP_AFTER_RETRY_TIMEOUT(){
@Override
public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
return lock.tryLock(prop.waitTime(), prop.leaseTime(), prop.unit());
}
},
FAIL_AFTER_RETRY_TIMEOUT(){
@Override
public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
boolean isLock = lock.tryLock(prop.waitTime(), prop.leaseTime(), prop.unit());
if (!isLock) {
throw new RuntimeException("请求太频繁");
}
return true;
}
},
;
public abstract boolean tryLock(RLock lock, MyLock prop) throws InterruptedException;
}
7. AOP切面 MyLockAspect
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Component
@Aspect
@RequiredArgsConstructor
public class MyLockAspect implements Ordered {
private final MyLockFactory lockFactory;
/**
* SPEL的正则规则
*/
private static final Pattern pattern = Pattern.compile("\\#\\{([^\\}]*)\\}");
/**
* 方法参数解析器
*/
private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
@Around("@annotation(myLock)")
public Object tryLock(ProceedingJoinPoint pjp, MyLock myLock) throws Throwable {
// 1. 解析锁名(支持SPEL)
String lockName = getLockName(myLock.name(), pjp);
// 2. 创建锁对象
RLock lock = lockFactory.getLock(myLock.lockType(), lockName);
// 3. 尝试获取锁
boolean isLocked = myLock.strategy().tryLock(lock, myLock);
if (!isLocked) return null;
try {
// 4. 执行业务逻辑
return pjp.proceed();
} finally {
// 5. 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 解析锁名称
*
* @param name 原始锁名称
* @param pjp 切入点
* @return 解析后的锁名称
*/
private String getLockName(String name, ProceedingJoinPoint pjp) {
// 1.判断是否存在spel表达式
if (StringUtils.isBlank(name) || !name.contains("#")) {
// 不存在,直接返回
return name;
}
// 2.构建context,也就是SPEL表达式获取参数的上下文环境,这里上下文就是切入点的参数列表
EvaluationContext context = new MethodBasedEvaluationContext(
TypedValue.NULL, resolveMethod(pjp), pjp.getArgs(), parameterNameDiscoverer);
// 3.构建SPEL解析器
ExpressionParser parser = new SpelExpressionParser();
// 4.循环处理,因为表达式中可以包含多个表达式
Matcher matcher = pattern.matcher(name);
while (matcher.find()) {
// 4.1.获取表达式
String tmp = matcher.group();
String group = matcher.group(1);
// 4.2.这里要判断表达式是否以 T字符开头,这种属于解析静态方法,不走上下文
Expression expression = parser.parseExpression(group.charAt(0) == 'T' ? group : "#" + group);
// 4.3.解析出表达式对应的值
Object value = expression.getValue(context);
// 4.4.用值替换锁名称中的SPEL表达式
name = name.replace(tmp, ObjectUtils.nullSafeToString(value));
}
return name;
}
private Method resolveMethod(ProceedingJoinPoint pjp) {
// 1.获取方法签名
MethodSignature signature = (MethodSignature) pjp.getSignature();
// 2.获取字节码
Class<?> clazz = pjp.getTarget().getClass();
// 3.方法名称
String name = signature.getName();
// 4.方法参数列表
Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
return tryGetDeclaredMethod(clazz, name, parameterTypes);
}
private Method tryGetDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
try {
// 5.反射获取方法
return clazz.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
Class<?> superClass = clazz.getSuperclass();
if (superClass != null) {
// 尝试从父类寻找
return tryGetDeclaredMethod(superClass, name, parameterTypes);
}
}
return null;
}
@Override
public int getOrder() {
return 0;
}
}
7. 注意事项
- 锁粒度:锁的粒度应尽可能小,以减少对系统性能的影响。
- 异常处理:在使用锁时,务必处理好异常情况,确保锁能够正确释放。
- 网络稳定性:由于 Redisson 依赖于 Redis,因此需要确保 Redis 的高可用性和网络稳定性。
- 锁超时:合理设置锁的超时时间,避免因长时间持有锁而导致系统阻塞。
8. 总结
Redisson 的分布式锁提供了一种简单、高效的方式来管理分布式系统中的并发访问。它不仅支持常见的加锁和解锁操作,还提供了可重入性、自动续期、公平锁等高级特性,适用于多种复杂的分布式场景。通过合理使用 Redisson 的分布式锁,可以有效避免资源竞争、死锁等问题,提升系统的稳定性和可靠性。