Redis学习——Redisson 分布式锁集成及其简单使用

打印 上一主题 下一主题

主题 958|帖子 958|积分 2874


引言

在分布式系统中,经常需要对共享资源进行并发访问控制,以确保数据的一致性和完整性。分布式锁是一种用于在分布式情况中控制对共享资源访问的机制,它可以保证在同一时候只有一个客户端可以或许访问某些特定资源。
1. Redisson概述

1.1 Redisson的根本概念

Redisson是一个基于Redis的Java客户端,它不仅提供了对Redis的基础操纵支持,还封装了许多高级功能,如分布式锁、分布式聚集、分布式队列等。Redisson的设计目标是简化分布式系统的开辟,提高开辟效率和系统的可维护性。

1.2 Redisson的重要功能


  • 分布式锁:支持可重入锁、公平锁、读写锁、红锁等多种分布式锁机制,保证分布式情况下的资源访问控制。好比:在电商系统中,防止超卖征象;在订单系统中,防止同一订单被多次处理。
  • 分布式聚集:提供分布式Set、List、Map等聚集范例,支持高并发情况下的数据操纵。
  • 分布式队列:支持分布式阻塞队列、耽误队列等,适用于任务调度和消息传递场景。
  • 分布式对象:提供分布式AtomicLong、AtomicDouble、CountDownLatch、Semaphore等对象,简化分布式系统的开辟。
  • 分布式服务:支持分布式执行器、分布式调度器等服务,增强分布式系统的功能。
1.3 Redisson的优点

Redisson是一个基于Redis的Java客户端,提供了许多高级特性和分布式数据结构。相比其他Redis客户端,Redisson的优势在于:


  • 简便易用:提供了丰富的API,简化了分布式编程的复杂性。
  • 高可用性:支持多种Redis部署模式,包括单节点、主从复制和集群模式。
  • 分布式对象:提供了分布式锁、分布式聚集、分布式队列等高级数据结构,便于在分布式情况中使用。
  • 自动续期:Redisson的Watchdog机制可以自动续期分布式锁,避免锁超时题目。
2. 开辟情况



  • JDK版本:JDK 17
  • Spring Boot版本:Spring Boot 3.2.2
  • Redis版本:5.0.14.1
  • 构建工具:Maven
3. Redisson的安装与配置

3.1 添加依靠

  1. <dependency>
  2.     <groupId>org.redisson</groupId>
  3.     <artifactId>redisson</artifactId>
  4.     <version>3.24.3</version>
  5. </dependency>
复制代码
3.2 配置Redisson

   配置参考文档:2. Configuration · redisson/redisson Wiki (github.com)
  添加配置类RedissonConfig:
  1. /**
  2. * Redisson配置类,用于配置Redisson客户端。
  3. */
  4. @Configuration
  5. public class RedissonConfig {
  6.     /**
  7.      * 创建并配置RedissonClient Bean。
  8.      *
  9.      * @return 配置好的RedissonClient实例
  10.      */
  11.     @Bean
  12.     public RedissonClient redissonClient() {
  13.         // 创建Redisson配置对象
  14.         Config config = new Config();
  15.         // 配置单节点模式
  16.         config.useSingleServer()
  17.                 // 设置Redis服务器地址
  18.                 .setAddress("redis://127.0.0.1:6379")
  19.                 // 设置Redis服务器密码
  20.                 .setPassword("123321")
  21.                 // 设置连接池大小
  22.                 .setConnectionPoolSize(64)
  23.                 // 设置最小空闲连接数
  24.                 .setConnectionMinimumIdleSize(24)
  25.                 // 设置空闲连接超时时间(毫秒)
  26.                 .setIdleConnectionTimeout(10000)
  27.                 // 设置连接超时时间(毫秒)
  28.                 .setConnectTimeout(10000)
  29.                 // 设置命令等待超时时间(毫秒)
  30.                 .setTimeout(3000)
  31.                 // 设置命令重试次数
  32.                 .setRetryAttempts(3)
  33.                 // 设置命令重试间隔时间(毫秒)
  34.                 .setRetryInterval(1500);
  35.         // 创建并返回RedissonClient实例
  36.         return Redisson.create(config);
  37.     }
  38. }
复制代码
4. 使用Redisson

   官方wiki文档:8. Distributed locks and synchronizers · redisson/redisson Wiki (github.com)
  中文版wiki文档(已经有5年没有更新了,不建议看):8. 分布式锁和同步器 · redisson/redisson Wiki (github.com)
  4.1 可重入锁

4.1.1 可重入锁的概念

可重入锁(Reentrant Lock)是一种允许同一个线程多次获取同一把锁的锁机制。也就是说,当一个线程已经持有某个锁时,它可以再次获取该锁而不会被阻塞。这种锁机制可以或许避免死锁题目,并简化锁的使用。
可重入锁的重要特点是:


  • 同一线程可多次获取:同一个线程可以多次获取同一把锁,而不会被阻塞。
  • 计数器维护:可重入锁内部维护一个计数器,每次获取锁时计数器加1,每次开释锁时计数器减1,当计数器为0时,锁才真正被开释。
4.1.2 可重入锁的实现原理

可重入锁的实现通常依靠于一个计数器和一个持有锁的线程标识。当一个线程第一次获取锁时,计数器加1,并记录持有锁的线程标识。当同一个线程再次获取锁时,只需将计数器加1,而不会阻塞线程。当线程开释锁时,计数器减1,当计数器为0时,锁才真正被开释,并允许其他线程获取锁。
在Redisson中,可重入锁的实现基于Redis的原子操纵和Lua脚本。Redisson通过维护一个计数器和持有锁的线程标识,实现了可重入锁的功能。
4.1.3 简单使用

  1. import lombok.RequiredArgsConstructor;
  2. import org.redisson.api.RLock;
  3. import org.redisson.api.RedissonClient;
  4. import org.springframework.stereotype.Service;
  5. import java.util.concurrent.TimeUnit;
  6. /**
  7. * 服务类示例
  8. */
  9. @RequiredArgsConstructor
  10. @Service
  11. public class XXXXService {
  12.     private final RedissonClient redissonClient;
  13.     /**
  14.      * 使用 Redisson 可重入锁执行任务
  15.      */
  16.     public void performTaskWithLock() {
  17.         // 获取可重入锁对象,指定锁的名称
  18.         RLock lock = redissonClient.getLock("myLock");
  19.         try {
  20.             // 尝试获取锁,参数分别是:获取锁的最大等待时间,锁自动释放时间,时间单位,返回值为是否获取锁成功
  21.             boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS);
  22.             
  23.             // 判断获取锁成功
  24.             if (isLock) {
  25.                 try {
  26.                     System.out.println("执行业务");
  27.                     // 在这里编写需要进行锁保护的业务逻辑
  28.                 } finally {
  29.                     // 释放锁
  30.                     lock.unlock();
  31.                 }
  32.             } else {
  33.                 // 获取锁失败,可以进行相应的处理,例如记录日志或返回错误信息
  34.                 System.err.println("获取锁失败!");
  35.             }
  36.         } catch (InterruptedException e) {
  37.             // 处理中断异常
  38.             throw new RuntimeException(e);
  39.         }
  40.     }
  41. }
复制代码
  在上述代码中,我们使用redissonClient.getLock("myLock")获取一个分布式锁对象,然后使用lock.tryLock()方法尝试获取锁,并在任务完成后开释锁。
  锁的获取和开释

  

  • 获取锁:使用RLock对象的tryLock()或lock()方法来获取锁。tryLock()方法允许设置等待时间和锁的自动开释时间。
  • 开释锁:使用RLock对象的unlock()方法来开释锁。确保在finally块中开释锁,以避免死锁。
  4.2 公平锁

4.2.1 公平锁的概念

公平锁(Fair Lock)是一种确保锁的获取次序与请求次序雷同的锁机制。即先请求锁的线程优先获取锁,后请求的线程只能在前面的线程开释锁后才能获取锁。这种机制可以避免“饥饿”征象,确保每个线程都能公平地获取锁。
4.2.2 公平锁的实现原理

公平锁的实现通常依靠于一个队列来记录请求锁的次序。每次有线程请求锁时,会将其添加到队列中,当锁被开释时,从队列中按照请求次序依次叫醒等待的线程。
在Redisson中,公平锁的实现基于Redis的有序聚集(Sorted Set)和Lua脚本。每次请求锁时,线程会被添加到一个有序聚集中,并按照时间戳排序。当锁被开释时,按照有序聚集中的次序依次叫醒等待的线程。
4.2.3 简单使用

  1. public void performTaskWithFairLock() {
  2.     // 1. 获取公平锁对象
  3.     RLock fairLock = redissonClient.getFairLock("myFairLock");
  4.     try {
  5.         // 2. 尝试获取锁
  6.         boolean isLock = fairLock.tryLock(1, 10, TimeUnit.SECONDS);
  7.         // 3. 判断是否获取到锁
  8.         if (isLock) {
  9.             try {
  10.                 System.out.println("获得公平锁,正在执行任务...");
  11.                 // 执行任务
  12.             } finally {
  13.                 // 4. 释放锁
  14.                 fairLock.unlock();
  15.                 System.out.println("释放公平锁。");
  16.             }
  17.         } else {
  18.             System.out.println("无法获取公平锁。");
  19.         }
  20.     } catch (InterruptedException e) {
  21.         e.printStackTrace();
  22.     }
  23. }
复制代码
4.3 读写锁

4.3.1 读写锁的概念

读写锁(Read-Write Lock)是一种允许多个读操纵同时进行,但写操纵必须独占的锁机制。读写锁分为两种锁:读锁和写锁。


  • 读锁:允许多个线程同时获取读锁,只要没有线程持有写锁。读锁之间是共享的。
  • 写锁:只允许一个线程获取写锁,并且在写锁持有期间,其他线程不能获取读锁或写锁。写锁是独占的。
读写锁的重要目标是提高并发性和性能。在读多写少的场景下,读写锁可以明显提高系统的并发处理能力。
4.3.2 读写锁的实现原理

读写锁的实现通常依靠于两个锁:一个读锁和一个写锁。读锁允许多个线程同时获取,而写锁只允许一个线程获取。在获取写锁时,需要确保没有线程持有读锁或写锁。
在Redisson中,读写锁的实现基于Redis的原子操纵和Lua脚本。Redisson通过两个键来分别控制读锁和写锁,并使用Lua脚本确保锁操纵的原子性。
4.3.3 简单使用

  1. public void performTaskWithReadWriteLock() {
  2.     // 1. 获取读写锁对象
  3.     RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("myReadWriteLock");
  4.     // 2. 从读写锁对象中分别获取读锁和写锁
  5.     RLock readLock = readWriteLock.readLock();
  6.     RLock writeLock = readWriteLock.writeLock();
  7.     try {
  8.         // 3. 尝试获取读锁
  9.         if (readLock.tryLock(10, 60, TimeUnit.SECONDS)) {
  10.             try {
  11.                 System.out.println("获取读锁,正在执行读任务...");
  12.                 // 执行读任务
  13.             } finally {
  14.                 // 4. 释放读锁
  15.                 readLock.unlock();
  16.                 System.out.println("释放读锁。");
  17.             }
  18.         }
  19.         // 5. 尝试获取写锁
  20.         if (writeLock.tryLock(10, 60, TimeUnit.SECONDS)) {
  21.             try {
  22.                 System.out.println("获取写锁,正在执行写任务...");
  23.                 // 执行写任务
  24.             } finally {
  25.                 // 6. 释放写锁
  26.                 writeLock.unlock();
  27.                 System.out.println("释放写锁。");
  28.             }
  29.         }
  30.     } catch (InterruptedException e) {
  31.         e.printStackTrace();
  32.     }
  33. }
复制代码
4.4 联锁

4.4.1 联锁的概念

联锁(MultiLock)是一种允许将多个锁关联在一起,实现“全部获取”或“全部开释”的锁机制。


  • 全部获取: 只有当全部到场联锁的锁都被乐成获取后,才算乐成获取联锁。
  • 全部开释: 开释联锁时,会自动开释全部到场联锁的锁。
联锁适用于需要同时获取多个资源的场景,比方分布式事务中需要锁定多个数据表。
4.4.2 联锁的实现原理

Redisson 的联锁基于 RedissonMultiLock 对象实现。RedissonMultiLock 对象可以将多个 RLock 对象关联在一起,并提供 tryLock() 和 unlock() 方法来统一管理这些锁。
在调用 tryLock() 方法时,RedissonMultiLock 会尝试依次获取全部到场联锁的锁。如果全部锁都获取乐成,则返回 true,否则开释已经获取到的锁,并返回 false。
在调用 unlock() 方法时,RedissonMultiLock 会自动开释全部到场联锁的锁,无论这些锁是否被当前线程持有。
4.4.3 简单使用

  1. public void performTaskWithMultiLock() {
  2.     // 获取多个锁对象
  3.     RLock lock1 = redissonClient.getLock("lock1");
  4.     RLock lock2 = redissonClient.getLock("lock2");
  5.     RLock lock3 = redissonClient.getLock("lock3");
  6.     // 创建联锁对象
  7.     RLock multiLock = redissonClient.getMultiLock(lock1, lock2, lock3);
  8.     try {
  9.         // 尝试获取联锁,等待 10 秒
  10.         if (multiLock.tryLock(10, TimeUnit.SECONDS)) {
  11.             try {
  12.                 System.out.println("获取联锁成功,正在执行任务...");
  13.                 // 执行需要所有锁的任务
  14.             } finally {
  15.                 // 释放联锁
  16.                 multiLock.unlock();
  17.                 System.out.println("释放联锁。");
  18.             }
  19.         } else {
  20.             System.out.println("获取联锁失败。");
  21.         }
  22.     } catch (InterruptedException e) {
  23.         e.printStackTrace();
  24.     }
  25. }
复制代码
  代码分析:
  

  • 获取多个锁对象: 首先,获取需要到场联锁的多个 RLock 对象。
  • 创建联锁对象: 使用 redissonClient.getMultiLock(lock1, lock2, lock3) 创建一个 RLock 对象,并将之前获取的多个锁对象作为参数传入。
  • 尝试获取联锁: 调用 multiLock.tryLock(10, TimeUnit.SECONDS) 尝试获取联锁,最多等待 10 秒。
  • 执行任务: 如果乐成获取联锁,则执行需要全部锁保护的任务。
  • 开释联锁: 末了,在 finally 块中调用 multiLock.unlock() 开释联锁,这会自动开释全部到场联锁的锁。
  5. WatchDog机制

想象一下,我们正在进行一场猛烈的拔河角逐。我们队好不轻易抓住了绳子,眼看就要赢了,效果突然有人手滑,绳子就被对方抢走了!
在分布式系统中,获取锁就好像抓住这根拔河绳。Redisson 分布式锁的租约时间就好像我们抓住绳子的时间。如果在租约时间内,我们没有完成任务,锁就自动开释了,其他线程就偶然机获取锁,这就像拔河角逐中我们手滑绳子被抢走一样,大概会导致数据不一致的题目。
为了避免这种情况发生,Redisson 提供了 Watch Dog 机制,就像我们队伍里安排了一个“观察员”。这位观察员会每隔一段时间关注我们是否还抓着绳子,如果发现我们快坚持不住了,就会及时提醒我们,让我们重新握紧绳子,并延长我们抓住绳子的时间。
详细来说,Redisson 的Watch Dog 机制会在 Redisson 实例被关闭前,不断的延长锁的有效期,也就是说,如果一个拿到锁的线程一直没有完成逻辑,那么看门狗会帮助线程不断的延长锁超时时间,锁不会因为超时而被开释。
**开启方式:**在获取锁的时间,不能指定leaseTime或者只能将leaseTime设置为-1,这样才能开启看门狗机制。
  1. public void test() throws Exception {
  2.     RLock lock = redissonClient.getLock("myLock");
  3.     // 方式一: 不停重试,直到获取锁成功,具有 Watch Dog 自动延期机制,默认续约时间为 30 秒
  4.     lock.lock();
  5.     // 方式二: 尝试获取锁 10 秒,获取成功返回 true,否则返回 false,具有 Watch Dog 自动延期机制,默认续约时间为 30 秒
  6.     boolean res1 = lock.tryLock(10, TimeUnit.SECONDS);
  7.     // 方式三:  尝试获取锁 10 秒,如果获取成功,则持有锁,否则抛出异常,leaseTime 为 10 秒,不会自动续约
  8.     try {
  9.         lock.lock(10, TimeUnit.SECONDS);
  10.     } catch (InterruptedException e) {
  11.         // 处理异常
  12.     }
  13.     // 方式四: 尝试获取锁 100 秒,如果获取成功,则持有锁 10 秒,leaseTime 为 10 秒,不会自动续约
  14.     boolean res2 = lock.tryLock(100, 10, TimeUnit.SECONDS);
  15.     Thread.sleep(40000L);
  16.     lock.unlock();
  17. }
复制代码
6. 总结

在本文中,我们简要先容了Redisson及其优势,先容了如何在Spring Boot项目中集成Redisson。通过代码示例展示了根本的分布式锁用法,以及高级用法如公平锁、可重入锁、读写锁和联锁。除此之外我们还简要先容了Redisson 的Watch Dog 机制,盼望本文对各人有所帮助

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

大连密封材料

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表