大数跨境
0
0

一个注解完美实现分布式锁

一个注解完美实现分布式锁 Spring全家桶实战案例
2024-04-08
0
导读:分布式锁的完美实践:通过一个注解轻松实现
环境:SpringBoot2.7.16


1. 简介
本文将通过Redisson实现分布式锁。Redisson是一个具有内存数据网格特性的Redis Java客户端。它提供了更方便和最简单的方法来使用Redis。Redisson对象提供了关注点分离,允许您将重点放在数据建模和应用程序逻辑上。它基于高性能异步无锁Java Redis客户端和Netty框架。
JDK与Redis支持
JDK: 1.8~21
Redis: 3.0~7.2
Redisson分布式锁

基于Redis的Java分布式可重入锁对象,实现了Lock接口。使用pub/sub通道通知所有Redisson实例中等待获取锁的其他线程。

如果获得锁的Redisson实例崩溃,那么该锁将永远挂起,处于获得状态。为了避免这种情况,Redisson维护了锁看门狗,它在锁持有者Redisson实例活着的时候延长锁过期时间。默认情况下,锁看门狗超时时间为30秒,可通过配置修改。lockWatchdogTimeout设置。

基本使用

RLock lock = redisson.getLock("myLock");lock.lock();// 获得锁并在10秒后自动解锁lock.lock(10, TimeUnit.SECONDS);// 等待获取锁时间100秒,10秒后自动解锁boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);if (res) {  try {    // todo  } finally {    lock.unlock() ;  }}

异步接口使用

RLock rLock = redisson.getLock(key) ;rLock.lock() ;RFuture<Boolean> lockFuture = rLock.tryLockAsync(10010, TimeUnit.SECONDS);lockFuture.whenComplete((res, exception) -> {  // todo  rLock.unlockAsync();});

以上是简单的使用示例,关于Redisson的详细使用请查看官方文档。

2. 实战案例
2.1 依赖管理
<dependency>  <groupId>org.redisson</groupId>  <artifactId>redisson-spring-boot-starter</artifactId>  <version>3.18.0</version></dependency>
2.2 配置文件
spring:  redis:    redisson:      file: classpath:redisson.yaml
这里指定redisson的配置文件,所有关于redisson的配置都通过redisson.yaml文件配置
---singleServerConfig:  idleConnectionTimeout: 10000  connectTimeout: 10000  timeout: 3000  retryAttempts: 3  retryInterval: 1500  password: xxxooo  subscriptionsPerConnection: 5  clientName: null  address: "redis://127.0.0.1:6379"  subscriptionConnectionMinimumIdleSize: 1  subscriptionConnectionPoolSize: 50  connectionMinimumIdleSize: 24  connectionPoolSize: 64  database: 14  dnsMonitoringInterval: 5000threads: 16nettyThreads: 32codec: !<org.redisson.codec.Kryo5Codec> {}transportMode: "NIO"
以上就是关于redisson的所有配置,接下来就是定义切面实现基于注解的分布式锁。
2.3 核心类定义
自定义注解
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface DLock {    /**分布式锁的key*/  String value() default "" ;}
value属性定义了分布式锁的key,默认为空,如果为空会根据当前注解的类及方法参数等生成key。
切面定义
@Component@Aspectpublic class DistributedLockAspect {    private final RedissonClient redisson ;  public DistributedLockAspect(RedissonClient redisson) {    this.redisson = redisson ;  }    @Pointcut("@annotation(lock)")  private void lockpc(DLock lock) {}    @Around("lockpc(lock)")  public Object lockAround(ProceedingJoinPoint pjp, DLock lock) throws Throwable {    MethodSignature signature = (MethodSignature) pjp.getSignature() ;    Method method = signature.getMethod() ;    Object[] args = pjp.getArgs() ;        String key = lock.value() ;    if ("".equals(key)) {      // 根据当前的类名+方法参数信息生成key      key = configKey(signature.getDeclaringType(), method).replaceAll("[^a-zA-Z0-9]", "") ;    } else {      // 支持SpEL表达式      StandardEvaluationContext context = new StandardEvaluationContext() ;      // 这里将当前执行的方法参数信息都存入到SpEL执行的上下文中      DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer() ;      String[] parameterNames = discoverer.getParameterNames(method) ;      for (int i = 0, len = parameterNames.length; i < len; i++) {        context.setVariable(parameterNames[i], args[i]) ;      }      ExpressionParser parser = new SpelExpressionParser() ;      Expression expression = parser.parseExpression(key) ;      key = expression.getValue(context, String.class) ;    }    RLock rLock = redisson.getLock(key) ;    rLock.lock() ;    try {      Object ret = pjp.proceed() ;      return ret ;    } finally {      rLock.unlock() ;     }  }  // 根据类,方法信息生成分布式锁的key  private String configKey(Class<?> targetType, Method method) {    StringBuilder builder = new StringBuilder();    builder.append(targetType.getSimpleName());    builder.append('#').append(method.getName()).append('(');    for (Class<?> param : method.getParameterTypes()) {      builder.append(param.getSimpleName()).append(',');    }    if (method.getParameterTypes().length > 0) {      builder.deleteCharAt(builder.length() - 1);    }    return builder.append(')').toString();  }  }
以上是基于注解实现分布式锁的核心类都定义完成了,接下来进行测试。
测试示例
// 通过SpEL表达式定义分布式锁的key,根据当前的请求参数// key统一添加前缀'person:'@DLock("'person:' + #person.id + ':' + #cateId")public void create(Person person, Integer cateId) {  System.out.printf("当前执行线程: %s%n", Thread.currentThread().getName()) ;  // todo  sleep(5) ;}
测试上面的方法
@Testpublic void testCreate() {  int size = 5 ;  CountDownLatch cdl = new CountDownLatch(size) ;  CountDownLatch nums = new CountDownLatch(size) ;  Thread[] threads = new Thread[size] ;  for (int i = 0; i < size; i++) {    final int n = i ;    threads[i] = new Thread(() -> {      try {        nums.countDown() ;        nums.await() ;      } catch (InterruptedException e) {        e.printStackTrace();      }      Person person = new Person();      person.setId(2L) ;      person.setName("张三") ;      lockService.create(person, 666) ;      cdl.countDown() ;    }, "T - " + n) ;  }  for (int i = 0; i < size; i++) {    threads[i].start() ;  }  try {    cdl.await() ;  } catch (InterruptedException e) {    e.printStackTrace();  }}
上面模拟的是同一个Person对象(相同参数的情况)
控制台会逐一的输出信息(排队)
当前时间戳: 1712041261076, 当前执行线程: T - 4当前时间戳: 1712041266080, 当前执行线程: T - 0当前时间戳: 1712041271090, 当前执行线程: T - 3当前时间戳: 1712041276102, 当前执行线程: T - 1当前时间戳: 1712041281119, 当前执行线程: T - 2

根据程序每隔5s后其它线程执行。

模拟不同的请求参数(请求参数不同的情况)

单元测试进行如下修改

Person person = new Person();person.setId(n + 0L) ;person.setName("张三 - " + n) ;lockService.create(person, n) ;

输出结果

当前时间戳: 1712041534160, 当前执行线程: T - 1当前时间戳: 1712041534160, 当前执行线程: T - 4当前时间戳: 1712041534160, 当前执行线程: T - 0当前时间戳: 1712041534160, 当前执行线程: T - 3当前时间戳: 1712041534160, 当前执行线程: T - 2

由于参数不同,所以这里同时输出。redis中的key如下:

每个线程都不同的key。

注意:在切面中并没有对SpEL执行异常进行处理。

以上是本篇文章的全部内容,希望对你有帮助。

完毕!!!

【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读38
粉丝0
内容832