如何在 Java 中优雅地使用 Redisson 实现分布式锁

分布式系统中,节点并发访问共享资源可能导致数据一致性问题。分布式锁是常见的解决方案,可确保操作原子性。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 {@Beanpublic 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 {@Autowiredprivate RedissonClient redissonClient;@Testpublic 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的分布式锁使用并不复杂,基本步骤包括:

  1. 创建锁对象

  2. 尝试获取锁

  3. 处理业务

  4. 释放锁

但是,除了第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();}}@Overridepublic int getOrder() {return 0;}
}

注意:

在实际开发中,很多业务方法上不仅会有分布式锁注解(如 @MyLock),还可能带有 Spring 的事务注解(如 @Transactional)。如果你希望:

  • 先获取锁
  • 再开启事务
  • 最后执行业务

就需要保证:加锁切面的 order 值小于事务切面的 order 值。

而 Spring 内置的事务切面默认的 orderInteger.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();}}@Overridepublic 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(){@Overridepublic boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {return lock.tryLock(0, prop.leaseTime(), prop.unit());}},FAIL_FAST(){@Overridepublic 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(){@Overridepublic boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {lock.lock( prop.leaseTime(), prop.unit());return true;}},SKIP_AFTER_RETRY_TIMEOUT(){@Overridepublic boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {return lock.tryLock(prop.waitTime(), prop.leaseTime(), prop.unit());}},FAIL_AFTER_RETRY_TIMEOUT(){@Overridepublic 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();}}@Overridepublic 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;}@Overridepublic 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 {@Beanpublic 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表达式,如 #{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(){@Overridepublic boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {return lock.tryLock(0, prop.leaseTime(), prop.unit());}},FAIL_FAST(){@Overridepublic 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(){@Overridepublic boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {lock.lock( prop.leaseTime(), prop.unit());return true;}},SKIP_AFTER_RETRY_TIMEOUT(){@Overridepublic boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {return lock.tryLock(prop.waitTime(), prop.leaseTime(), prop.unit());}},FAIL_AFTER_RETRY_TIMEOUT(){@Overridepublic 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;}@Overridepublic int getOrder() {return 0;}
}

7. 注意事项

  • 锁粒度:锁的粒度应尽可能小,以减少对系统性能的影响。
  • 异常处理:在使用锁时,务必处理好异常情况,确保锁能够正确释放。
  • 网络稳定性:由于 Redisson 依赖于 Redis,因此需要确保 Redis 的高可用性和网络稳定性。
  • 锁超时:合理设置锁的超时时间,避免因长时间持有锁而导致系统阻塞。

8. 总结

Redisson 的分布式锁提供了一种简单、高效的方式来管理分布式系统中的并发访问。它不仅支持常见的加锁和解锁操作,还提供了可重入性、自动续期、公平锁等高级特性,适用于多种复杂的分布式场景。通过合理使用 Redisson 的分布式锁,可以有效避免资源竞争、死锁等问题,提升系统的稳定性和可靠性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.tpcf.cn/web/83031.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pikachu靶场通关笔记20 SQL注入03-搜索型注入(GET)

目录 一、SQL注入 二、搜索型注入 三、源码分析 1、渗透思路1 2、渗透思路2 四、渗透实战 1、渗透准备 2、SQL注入探测 &#xff08;1&#xff09;输入百分号单引号 &#xff08;2&#xff09;万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取…

mac:大模型系列测试

0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何&#xff0c;是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试&#xff0c;是可以跑通文章里面的代码。训练速度也是很快的。 注意…

A Survey on the Memory Mechanism of Large Language Model based Agents

目录 摘要Abstract1. LLM-Based Agent的Memory1.1 基础概念1.2 用于解释Memory的例子1.3 智能体记忆的定义1.3.1 狭义定义(肯定不用这个定义)1.3.2 广义定义 1.4 记忆协助下智能体与环境的交互过程1.4.1 记忆写入1.4.2 记忆管理1.4.3 记忆读取1.4.4 总过程 2. 如何实现智能体记…

搭建 Serverless 架构

✅ 一、理解 Serverless 架构核心概念 核心理念&#xff1a; 无需管理服务器&#xff1a;只需编写业务逻辑&#xff0c;部署后由云平台托管运行环境。 事件驱动&#xff08;Event-driven&#xff09; 按需计费&#xff08;按调用次数/资源消耗&#xff09; 高可扩展性与自动…

Git仓库的创建

Git服务器准备 假设Git所在服务器为Ubuntu系统&#xff0c;IP地址10.17.1.5。 一. 准备运行git服务的git用户&#xff0c;这里用户名就直接设定为git。 1. 创建一个git用户组&#xff0c;并创建git用户。 sudo groupadd git sudo useradd git -g git 2. 创建git用户目录&…

电脑提示dll文件缺失怎么办 dll修复方法

当你在使用某些应用程序或启动电脑时&#xff0c;看到提示“DLL文件缺失”的错误信息&#xff0c;这通常意味着某个必要的动态链接库&#xff08;DLL&#xff09;文件无法被找到或加载&#xff0c;导致软件无法正常运行。本文将详细介绍如何排查和修复DLL文件缺失的问题&#x…

使用 Rest-Assured 和 TestNG 进行购物车功能的 API 自动化测试

这段代码使用了 Rest-Assured 进行 API 测试&#xff0c;结合 TestNG 框架执行多个 HTTP 请求并进行断言验证。以下是对每个测试方法的详细解释&#xff0c;包括代码逻辑和测试目的。 1. test01() 方法 - 提取响应数据 Test public void test01() {String jsonData "{\&…

【设计模式-4.7】行为型——备忘录模式

说明&#xff1a;本文介绍行为型设计模式之一的备忘录模式 定义 备忘录模式&#xff08;Memento Pattern&#xff09;又叫作快照模式&#xff08;Snapshot Pattern&#xff09;或令牌模式&#xff08;Token Pattern&#xff09;指在不破坏封装的前提下&#xff0c;捕获一个对…

2025年渗透测试面试题总结-天融信[社招]渗透测试工程师(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 天融信[社招]渗透测试工程师 一、Java Spring Boot组件漏洞 1. CVE-2018-1270&#xff08;WebSocket RCE&…

华为OD机考-内存冷热标记-多条件排序

import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextInt();int[] arr new int[a];for(int…

PPT转图片拼贴工具 v3.0

软件介绍 这个软件就是将PPT文件转换为图片并且拼接起来。 这个代码支持导入单个文件也支持导入文件夹 但是目前还没有解决可视化界面问题。 效果展示 软件源码 import os import re import win32com.client from PIL import Image from typing import List, Uniondef con…

NLP学习路线图(三十):微调策略

在自然语言处理领域,预训练语言模型(如BERT、GPT、T5)已成为基础设施。但如何让这些“通才”模型蜕变为特定任务的“专家”?微调策略正是关键所在。本文将深入剖析七种核心微调技术及其演进逻辑。 一、基础概念:为什么需要微调? 预训练模型在海量语料上学习了通用语言表…

讲述我的plc自学之路 第十三章

我和lora都是那种理想主义者&#xff0c;这是我们的共同之处。但是lora比我要更文艺一些&#xff0c;她读的书毕竟比我多&#xff0c;上的又是名校。受北大人文气息的熏陶&#xff0c;她总是对爱情充满了太多幻想。 “说说你的过往吧&#xff0c;lora。”我给lora倒了一杯啤酒&…

GPU虚拟化

引言 现有如下环境&#xff08;注意相关配置&#xff1a;只有一个k8s节点&#xff0c;且该节点上只有一张GPU卡&#xff09;&#xff1a; // k8s版本 $ kubectl version Client Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.7&…

【免费数据】2005-2019年我国272个地级市的旅游竞争力多指标数据(33个指标)

旅游业是一个城市的重要产业构成。旅游竞争力是一个城市竞争力的重要构成部分。一个城市的旅游竞争力反映了其在旅游市场竞争中的比较优势。 今日我们分享的是2005-2019年我国272个地级市的旅游竞争力多指标数据&#xff01;该数据集源自2025年4月发表于《地理学报》的论文成果…

AI智能驱动浏览器工具Browser Use详解

前言 在之前关于 AI 测试相关的几篇文章中,我们分别介绍了通过 playwright-mcp,以及 midscene.js、magentic ui 等几个不同的 AI 浏览器工具,实现 AI 驱动自动化测试的方法介绍。而其实在这些不断涌现的新工具出现之前,还有一个更早推出(2024.11),也同样还在不断完善的…

新成果:GaN基VCSEL动态物理模型开发

作为高速数据传输与光电信号处理的核心器件&#xff0c;垂直腔面发射激光器&#xff08;VCSEL&#xff09;在高速光通信、激光雷达等领域应用广泛&#xff0c;其动态特性直接关联器件调制速率及稳定性等关键参数。近期&#xff0c;天津赛米卡尔科技有限公司技术团队开发了GaN基…

嵌入式学习笔记 - freeRTOS xTaskResumeAll( )函数解析

第一部分 移除挂起等待列表中的任务 while( listLIST_IS_EMPTY( &xPendingReadyList ) pdFALSE )//循环寻找直到为空&#xff0c;把全部任务扫描一遍 { pxTCB ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingR…

大数据+智能零售:数字化变革下的“智慧新零售”密码

大数据+智能零售:数字化变革下的“智慧新零售”密码 大家好,今天咱们聊聊一个火到不行的话题:大数据在智能零售中的应用。这个领域,不仅是技术的“硬核战场”,更是商业创新的风口浪尖。谁能玩转数据,谁就能掌控消费者心智,实现销售爆发。 咱们不搞枯燥学术,而是用最“…

react 常见的闭包陷阱深入解析

一、引子 先来看一段代码,你能说出这段代码的问题在哪吗? const [count, setCount] = useState(0); useEffect(() => {const timer = setTimeout(() => {setCount(count + 1);}, 1000);return () => clearTimeout(timer); }, []);正确答案: 这段代码存在闭包陷阱…