赞
踩
在分布式系统中,经常需要对共享资源进行并发访问控制,以确保数据的一致性和完整性。分布式锁是一种用于在分布式环境中控制对共享资源访问的机制,它可以保证在同一时刻只有一个客户端能够访问某些特定资源。
Redisson是一个基于Redis的Java客户端,它不仅提供了对Redis的基础操作支持,还封装了许多高级功能,如分布式锁、分布式集合、分布式队列等。Redisson的设计目标是简化分布式系统的开发,提高开发效率和系统的可维护性。
Redisson是一个基于Redis的Java客户端,提供了许多高级特性和分布式数据结构。相比其他Redis客户端,Redisson的优势在于:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.24.3</version>
</dependency>
配置参考文档:2. Configuration · redisson/redisson Wiki (github.com)
添加配置类RedissonConfig
:
/** * Redisson配置类,用于配置Redisson客户端。 */ @Configuration public class RedissonConfig { /** * 创建并配置RedissonClient Bean。 * * @return 配置好的RedissonClient实例 */ @Bean public RedissonClient redissonClient() { // 创建Redisson配置对象 Config config = new Config(); // 配置单节点模式 config.useSingleServer() // 设置Redis服务器地址 .setAddress("redis://127.0.0.1:6379") // 设置Redis服务器密码 .setPassword("123321") // 设置连接池大小 .setConnectionPoolSize(64) // 设置最小空闲连接数 .setConnectionMinimumIdleSize(24) // 设置空闲连接超时时间(毫秒) .setIdleConnectionTimeout(10000) // 设置连接超时时间(毫秒) .setConnectTimeout(10000) // 设置命令等待超时时间(毫秒) .setTimeout(3000) // 设置命令重试次数 .setRetryAttempts(3) // 设置命令重试间隔时间(毫秒) .setRetryInterval(1500); // 创建并返回RedissonClient实例 return Redisson.create(config); } }
官方wiki文档:8. Distributed locks and synchronizers · redisson/redisson Wiki (github.com)
中文版wiki文档(已经有5年没有更新了,不建议看):8. 分布式锁和同步器 · redisson/redisson Wiki (github.com)
可重入锁(Reentrant Lock)是一种允许同一个线程多次获取同一把锁的锁机制。也就是说,当一个线程已经持有某个锁时,它可以再次获取该锁而不会被阻塞。这种锁机制能够避免死锁问题,并简化锁的使用。
可重入锁的主要特点是:
可重入锁的实现通常依赖于一个计数器和一个持有锁的线程标识。当一个线程第一次获取锁时,计数器加1,并记录持有锁的线程标识。当同一个线程再次获取锁时,只需将计数器加1,而不会阻塞线程。当线程释放锁时,计数器减1,当计数器为0时,锁才真正被释放,并允许其他线程获取锁。
在Redisson中,可重入锁的实现基于Redis的原子操作和Lua脚本。Redisson通过维护一个计数器和持有锁的线程标识,实现了可重入锁的功能。
import lombok.RequiredArgsConstructor; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; /** * 服务类示例 */ @RequiredArgsConstructor @Service public class XXXXService { private final RedissonClient redissonClient; /** * 使用 Redisson 可重入锁执行任务 */ public void performTaskWithLock() { // 获取可重入锁对象,指定锁的名称 RLock lock = redissonClient.getLock("myLock"); try { // 尝试获取锁,参数分别是:获取锁的最大等待时间,锁自动释放时间,时间单位,返回值为是否获取锁成功 boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS); // 判断获取锁成功 if (isLock) { try { System.out.println("执行业务"); // 在这里编写需要进行锁保护的业务逻辑 } finally { // 释放锁 lock.unlock(); } } else { // 获取锁失败,可以进行相应的处理,例如记录日志或返回错误信息 System.err.println("获取锁失败!"); } } catch (InterruptedException e) { // 处理中断异常 throw new RuntimeException(e); } } }
在上述代码中,我们使用
redissonClient.getLock("myLock")
获取一个分布式锁对象,然后使用lock.tryLock()
方法尝试获取锁,并在任务完成后释放锁。锁的获取和释放
- 获取锁:使用
RLock
对象的tryLock()
或lock()
方法来获取锁。tryLock()
方法允许设置等待时间和锁的自动释放时间。- 释放锁:使用
RLock
对象的unlock()
方法来释放锁。确保在finally
块中释放锁,以避免死锁。
公平锁(Fair Lock)是一种确保锁的获取顺序与请求顺序相同的锁机制。即先请求锁的线程优先获取锁,后请求的线程只能在前面的线程释放锁后才能获取锁。这种机制可以避免“饥饿”现象,确保每个线程都能公平地获取锁。
公平锁的实现通常依赖于一个队列来记录请求锁的顺序。每次有线程请求锁时,会将其添加到队列中,当锁被释放时,从队列中按照请求顺序依次唤醒等待的线程。
在Redisson中,公平锁的实现基于Redis的有序集合(Sorted Set)和Lua脚本。每次请求锁时,线程会被添加到一个有序集合中,并按照时间戳排序。当锁被释放时,按照有序集合中的顺序依次唤醒等待的线程。
public void performTaskWithFairLock() { // 1. 获取公平锁对象 RLock fairLock = redissonClient.getFairLock("myFairLock"); try { // 2. 尝试获取锁 boolean isLock = fairLock.tryLock(1, 10, TimeUnit.SECONDS); // 3. 判断是否获取到锁 if (isLock) { try { System.out.println("获得公平锁,正在执行任务..."); // 执行任务 } finally { // 4. 释放锁 fairLock.unlock(); System.out.println("释放公平锁。"); } } else { System.out.println("无法获取公平锁。"); } } catch (InterruptedException e) { e.printStackTrace(); } }
读写锁(Read-Write Lock)是一种允许多个读操作同时进行,但写操作必须独占的锁机制。读写锁分为两种锁:读锁和写锁。
读写锁的主要目的是提高并发性和性能。在读多写少的场景下,读写锁可以显著提高系统的并发处理能力。
读写锁的实现通常依赖于两个锁:一个读锁和一个写锁。读锁允许多个线程同时获取,而写锁只允许一个线程获取。在获取写锁时,需要确保没有线程持有读锁或写锁。
在Redisson中,读写锁的实现基于Redis的原子操作和Lua脚本。Redisson通过两个键来分别控制读锁和写锁,并使用Lua脚本确保锁操作的原子性。
public void performTaskWithReadWriteLock() { // 1. 获取读写锁对象 RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("myReadWriteLock"); // 2. 从读写锁对象中分别获取读锁和写锁 RLock readLock = readWriteLock.readLock(); RLock writeLock = readWriteLock.writeLock(); try { // 3. 尝试获取读锁 if (readLock.tryLock(10, 60, TimeUnit.SECONDS)) { try { System.out.println("获取读锁,正在执行读任务..."); // 执行读任务 } finally { // 4. 释放读锁 readLock.unlock(); System.out.println("释放读锁。"); } } // 5. 尝试获取写锁 if (writeLock.tryLock(10, 60, TimeUnit.SECONDS)) { try { System.out.println("获取写锁,正在执行写任务..."); // 执行写任务 } finally { // 6. 释放写锁 writeLock.unlock(); System.out.println("释放写锁。"); } } } catch (InterruptedException e) { e.printStackTrace(); } }
联锁(MultiLock)是一种允许将多个锁关联在一起,实现“全部获取”或“全部释放”的锁机制。
联锁适用于需要同时获取多个资源的场景,例如分布式事务中需要锁定多个数据表。
Redisson 的联锁基于 RedissonMultiLock
对象实现。RedissonMultiLock
对象可以将多个 RLock
对象关联在一起,并提供 tryLock()
和 unlock()
方法来统一管理这些锁。
在调用 tryLock()
方法时,RedissonMultiLock
会尝试依次获取所有参与联锁的锁。如果所有锁都获取成功,则返回 true
,否则释放已经获取到的锁,并返回 false
。
在调用 unlock()
方法时,RedissonMultiLock
会自动释放所有参与联锁的锁,无论这些锁是否被当前线程持有。
public void performTaskWithMultiLock() { // 获取多个锁对象 RLock lock1 = redissonClient.getLock("lock1"); RLock lock2 = redissonClient.getLock("lock2"); RLock lock3 = redissonClient.getLock("lock3"); // 创建联锁对象 RLock multiLock = redissonClient.getMultiLock(lock1, lock2, lock3); try { // 尝试获取联锁,等待 10 秒 if (multiLock.tryLock(10, TimeUnit.SECONDS)) { try { System.out.println("获取联锁成功,正在执行任务..."); // 执行需要所有锁的任务 } finally { // 释放联锁 multiLock.unlock(); System.out.println("释放联锁。"); } } else { System.out.println("获取联锁失败。"); } } catch (InterruptedException e) { e.printStackTrace(); } }
代码分析:
- 获取多个锁对象: 首先,获取需要参与联锁的多个
RLock
对象。- 创建联锁对象: 使用
redissonClient.getMultiLock(lock1, lock2, lock3)
创建一个RLock
对象,并将之前获取的多个锁对象作为参数传入。- 尝试获取联锁: 调用
multiLock.tryLock(10, TimeUnit.SECONDS)
尝试获取联锁,最多等待 10 秒。- 执行任务: 如果成功获取联锁,则执行需要所有锁保护的任务。
- 释放联锁: 最后,在
finally
块中调用multiLock.unlock()
释放联锁,这会自动释放所有参与联锁的锁。
想象一下,我们正在进行一场激烈的拔河比赛。我们队好不容易抓住了绳子,眼看就要赢了,结果突然有人手滑,绳子就被对方抢走了!
在分布式系统中,获取锁就好像抓住这根拔河绳。Redisson 分布式锁的租约时间就好像我们抓住绳子的时间。如果在租约时间内,我们没有完成任务,锁就自动释放了,其他线程就有机会获取锁,这就像拔河比赛中我们手滑绳子被抢走一样,可能会导致数据不一致的问题。
为了避免这种情况发生,Redisson 提供了 Watch Dog 机制,就像我们队伍里安排了一个“观察员”。这位观察员会每隔一段时间关注我们是否还抓着绳子,如果发现我们快坚持不住了,就会及时提醒我们,让我们重新握紧绳子,并延长我们抓住绳子的时间。
具体来说,Redisson 的Watch Dog 机制会在 Redisson 实例被关闭前,不断的延长锁的有效期,也就是说,如果一个拿到锁的线程一直没有完成逻辑,那么看门狗会帮助线程不断的延长锁超时时间,锁不会因为超时而被释放。
**开启方式:**在获取锁的时候,不能指定leaseTime
或者只能将leaseTime
设置为-1,这样才能开启看门狗机制。
public void test() throws Exception { RLock lock = redissonClient.getLock("myLock"); // 方式一: 不停重试,直到获取锁成功,具有 Watch Dog 自动延期机制,默认续约时间为 30 秒 lock.lock(); // 方式二: 尝试获取锁 10 秒,获取成功返回 true,否则返回 false,具有 Watch Dog 自动延期机制,默认续约时间为 30 秒 boolean res1 = lock.tryLock(10, TimeUnit.SECONDS); // 方式三: 尝试获取锁 10 秒,如果获取成功,则持有锁,否则抛出异常,leaseTime 为 10 秒,不会自动续约 try { lock.lock(10, TimeUnit.SECONDS); } catch (InterruptedException e) { // 处理异常 } // 方式四: 尝试获取锁 100 秒,如果获取成功,则持有锁 10 秒,leaseTime 为 10 秒,不会自动续约 boolean res2 = lock.tryLock(100, 10, TimeUnit.SECONDS); Thread.sleep(40000L); lock.unlock(); }
在本文中,我们简要介绍了Redisson及其优势,介绍了如何在Spring Boot项目中集成Redisson。通过代码示例展示了基本的分布式锁用法,以及高级用法如公平锁、可重入锁、读写锁和联锁。除此之外我们还简要介绍了Redisson 的Watch Dog 机制,希望本文对大家有所帮助
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。