什么是Redisson?什么业务中用过Redis的分布式锁?在SpringBoot中怎么用分布式锁?用的是哪个工具类?怎么设这个分布式锁?
什么是Redisson?
Redisson 是一个用于 Java 的 Redis 客户端,它不仅提供了对 Redis 命令的访问,还实现了多种分布式对象、锁和同步工具。Redisson 的设计目标是简化在分布式系统中使用 Redis 的复杂度,并为开发者提供更高层次的抽象,以便更容易地实现诸如分布式锁、信号量、布隆过滤器等高级功能。
Redission 支持可重入锁,这意味着同一个线程可以在不释放之前持有的锁的情况下再次获取同一把锁。为了实现这一点,Redission 在内部维护了一个计数器来跟踪同一个客户端获取同一把锁的次数。每次获取锁时计数器增加,而每次解锁时计数器减少,直到计数器归零才真正释放锁
以下是 Redisson 的一些主要特点和提供的功能:
- 丰富的数据结构支持:
Redisson 实现了几乎所有的 Redis 数据类型(如 String, Map, Set, List 等),并且将它们包装成 Java 对象的形式,使得操作这些数据类型就像操作本地 Java 集合一样简单。
- 分布式集合:
提供了 RSet
, RList
, RMap
等接口,允许用户在多个 JVM 之间共享数据结构,保证数据的一致性和高可用性。
- 分布式锁与同步工具:
- 包含了多种类型的分布式锁,例如可重入锁 (
RLock
)、公平锁 (RFairLock
)、读写锁 (RReadWriteLock
) 和红锁 (RRedLock
)。 - 还有分布式信号量 (
RSemaphore
)、计数信号量 (RCountDownLatch
) 和闭锁 (RPermitExpirableSemaphore
) 等同步工具,帮助协调不同节点之间的并发访问。
- 消息队列:
支持发布/订阅模式 (RTopic
) 和阻塞队列 (RBlockingQueue
),可以用来构建事件驱动架构或异步任务处理系统。
- 任务调度:
内置的任务调度器 (RScheduledExecutorService
) 可以安排一次性或周期性的任务执行,适用于需要定时触发的操作。
- 分布式集合框架:
提供了 RBucket
, RScoredSortedSet
, RHyperLogLog
等高级数据结构的支持,满足更复杂的业务需求。
- 缓存解决方案:
提供了基于 Redis 的缓存机制,包括本地缓存、近缓存和多级缓存策略,提高了数据读取效率并降低了主数据库的压力。
- 易于集成:
Redisson 易于与其他 Java 应用程序和服务集成,可以通过 Spring Boot Starter 快速配置,也兼容其他常见的依赖注入容器。
- 性能优化:
Redisson 使用 Netty 框架进行网络通信,并且内部做了很多优化以确保高效的命令执行和数据传输。
- 集群模式支持:
全面支持 Redis 集群,能够自动发现和管理集群中的节点变化,保障服务的连续性和可靠性。
业务场景
在实际应用中,可能会在以下场景中使用 Redis 分布式锁:
- 库存管理
- 业务背景:电商平台在处理商品购买时,需要保证同一时间只能有一个请求更新库存数量,以防止超卖现象。
- 实现方式:当用户提交订单时,服务器尝试获取该商品对应的分布式锁。如果成功,则进行库存扣减操作,并释放锁;否则等待一段时间后重试。
- 秒杀活动
- 业务背景:在线促销活动中,如“双十一”或限时抢购,大量用户同时发起请求,可能导致数据库压力过大以及库存超卖问题。
- 实现方式:通过 Redis 分布式锁限制同一时刻只能有一个进程处理用户的秒杀请求。一旦某个进程获得了锁,它会检查是否有足够的库存,并立即减少库存数,然后释放锁。其他进程必须等待直到前一个进程完成并释放锁。
- 账务处理
- 业务背景:银行转账、支付网关等金融交易系统要求高度的一致性和准确性,不允许并发操作导致账户余额错误。
- 实现方式:在处理转账或支付请求时,先尝试获取涉及账户的分布式锁。成功后执行账务变更逻辑(如扣除金额),最后释放锁。这可以确保即使在网络延迟或异常情况下也不会发生重复扣款等问题。
- 缓存更新
- 业务背景:某些应用依赖缓存来加速数据读取速度,但当缓存失效时,所有客户端几乎同时去刷新缓存,可能会造成所谓的“缓存雪崩”效应。
- 实现方式:引入 Redis 分布式锁控制缓存重建过程,使得每次只有单一客户端负责从原始数据源加载最新数据到缓存中,其余客户端则等待直至新缓存生成完毕。
- 任务调度
- 业务背景:分布式环境中,定时任务或周期性作业可能由多台服务器共同承担,为了防止同一任务被多次执行,需要一种机制来同步这些任务。
- 实现方式:利用 Redis 分布式锁作为任务锁,确保每次只有一个实例执行特定的任务。例如,在每天凌晨两点清理日志文件的任务中,只有获得锁的那个节点才能开始清理工作。
- 配置管理
- 业务背景:微服务架构下的配置中心允许动态调整服务参数,但是为了避免不同服务实例之间相互覆盖配置信息,需要有锁定机制。
- 实现方式:当某个服务实例想要修改全局配置时,它首先要尝试获取与该配置相关的分布式锁。成功后更新配置,并通知其他服务实例重新加载新的配置值。
- 批量导入/导出
- 业务背景:企业级应用经常涉及到大量数据的批量导入或导出操作,这类操作通常是耗时且资源密集型的,不适合并发执行。
- 实现方式:采用 Redis 分布式锁来确保同一时间内只有一项批量任务在运行。这样可以避免因并发执行而引起的性能瓶颈或数据一致性问题。
- 用户注册与激活
- 业务背景:为了防止恶意注册行为,一些网站会在用户注册或激活过程中设置验证步骤,比如发送验证码短信。这里也需要确保每个手机号码仅能接收一次验证码。
- 实现方式:使用 Redis 分布式锁控制验证码的发放,确保同一个手机号码的验证码请求不会同时存在两个或更多未完成的状态。
Spring Boot 中使用Redisson
在 Spring Boot 中使用 Redis 实现分布式锁有多种方式,常用的是通过 Redisson
或者 spring-boot-starter-data-redis
结合 Lettuce
/Jedis
客户端库来实现。
使用 Redisson
Redisson 是一个流行的 Redis Java 客户端,它提供了许多高级功能,包括分布式锁。以下是设置和使用 Redisson 分布式锁的基本步骤:
添加依赖
在 pom.xml
文件中添加 Redisson 的依赖:
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.20.0</version> <!-- 确保版本号是最新的 -->
</dependency>
配置 Redisson 连接
在 application.properties
或 application.yml
文件中配置 Redisson 的连接信息。
获取锁对象并使用
使用 RedissonClient 来获取 RLock 对象,然后尝试加锁、解锁等操作。
@Autowired
private RedissonClient redissonClient;public void someServiceMethod() {RLock lock = redissonClient.getLock("myLock");try {// 尝试加锁,等待时间10秒,锁自动释放时间为30秒boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);if (isLocked) {// 执行需要互斥执行的代码块}} catch (InterruptedException e) {Thread.currentThread().interrupt();// 处理异常} finally {if (lock.isHeldByCurrentThread()) {lock.unlock(); // 确保在finally块中解锁}}
}
注意事项
- 锁的超时设置:设置合理的锁超时时间,以防止出现死锁的情况。
- 锁的唯一性:确保每个客户端持有的锁是唯一的,以便正确解锁。
- 异常处理:在发生异常时,确保锁被正确释放,否则可能导致其他客户端无法获取锁。
分布式锁实现原理
加锁(tryLock()
)
Redisson 使用 Redis 的 SETNX
(SET if Not eXists)命令来尝试获取锁。SETNX
是一种原子操作,只有在键不存在时才会设置成功。这意味着如果多个客户端同时尝试获取同一个锁,只有一个会成功。使用 SETNX
设置锁时,通常会结合 EXPIRE
或者直接使用 SETEX
命令为锁设置一个过期时间。这是为了防止死锁的发生,即如果持有锁的进程崩溃而没有主动释放锁,那么经过一定时间后锁也会自动释放。
骚戴理解:简单来说就是通过SETNX
+PEXPIRE
+ Lua 脚本实现分布式锁, Lua 脚本会在 Redis 中执行,它首先尝试使用 SETNX
设置键,如果成功,则通过 PEXPIRE
设置键的过期时间。Lua 脚本是为了保证这些操作的原子性
删除锁(unlock()
)
为了安全地删除锁,Redission 使用 Lua 脚本来检查当前锁是否由当前客户端持有( Redisson 在加锁的时候会关联一个唯一的标识符(lockId),解锁时会检查这个标识符是否匹配,避免了误删其他客户端持有的锁的问题)。如果是,则删除该锁;否则拒绝解锁请求。
锁续期与看门狗机制
Redission 提供了一种称为“看门狗”(Watchdog)的机制,用来自动延长锁的有效期。当客户端成功获取到锁后,看门狗会启动一个后台任务,周期性地检查锁的状态并在必要时更新其过期时间。这样即使处理时间超过了最初设定的锁有效期,只要任务还在进行中,锁就不会被其他客户端抢走,通过看门狗机制确保长时间运行的任务不会意外失去锁
- 续期逻辑:每当看门狗发现锁即将过期时,它会自动调用
expire
或者pexpire
命令延长锁的生存时间。续期的时间间隔通常是锁原始有效时间的一半左右。 - 看门狗触发条件:看门狗会在锁被获取后的第一次续期时启动,并在锁释放时停止。此外,如果锁已经因为某些原因提前释放,则看门狗也会随之终止。