Redisson 分布式锁的正确使用

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 你会正确使用分布式锁吗?

背景介绍

前段时间,在写公司的一个项目的时候,用到了分布式锁,一个同事告诉我说,分布式锁解锁在高并发的时候会报错。

下面看下模拟代码:

simulate-lock

这里锁的时间是 5 秒,而业务执行的时间是 20 秒。这里模拟的是锁的时间少于业务执行的时间。

第二次执行的时候,就会报错,如下:

java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 660a38bb-c50b-4117-8ee2-67da7b4303c6 thread-id: 62

错误分析

通过这个错误信息,也知道该如何解决这个问题,我们只需要判断是当前线程再去解锁,就不会报错的。

可是为什么会报错呢?

所以,我们需要先去搞清楚具体的执行流程。

simulate-lock-marker

最开始,我以为,如果被锁住,运行到 ① 就会被返回,后面经过测试,实际上是会走到第 ② 步,尝试获取不到锁,就会返回,在返回之前呢,会执行 finally 的代码,因为 redisson 对锁有续租的功能,所以,这时候锁还是锁住的,解锁就会报错,也就是第 ③ 步。

实际上,我们的想法的,如果业务执行出错,我们在 finally 进行解锁,以防止程序死锁。

显然这样写代码,不是我们所期望的,并且代码也有问题。

优化代码

@RequestMapping("/try-lock")
public String tryLock() {
    
    RLock rLock = redissonClient.getLock("demo-spring-boot-redisson:try-lock");
    if (Objects.isNull(rLock)) {
        return "lock exception";
    }
    
    boolean tryLock;
    try {
        tryLock = rLock.tryLock(3, 60, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        return "get lock exception";
    }
    if (!tryLock) {
        return "get lock failed";
    }
    
    try {
        TimeUnit.SECONDS.sleep(20);
        return "success";
    } catch (Exception e) {
        return "business exception";
    } finally {
        if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
            rLock.unlock();
        }
    }
}

以上,便是优化后的代码,我们来一起分析一下。

分布式加锁主要分为三步。

第一步,主要是获取 RLock 对象,并且我们对它做了判空。

RLock rLock = redissonClient.getLock("demo-spring-boot-redisson:try-lock");
if (Objects.isNull(rLock)) {
    return "lock exception";
}

第二步,尝试加锁,加锁失败,返回加锁失败。

boolean tryLock;
try {
    tryLock = rLock.tryLock(3, 60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
    return "get lock exception";
}
if (!tryLock) {
    return "get lock failed";
}

这里我们用的是 tryLock,第一个参数 waitTime,意思是等待 5 秒,如果还没获取到,就不再等待。第二个参数是 leaseTime,意思是锁的释放时间。

第三步,就是我们业务代码。

try {
    TimeUnit.SECONDS.sleep(20);
    return "success";
} catch (Exception e) {
    return "business exception";
} finally {
    if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
        rLock.unlock();
    }
}

在 finally 里,我们做锁的释放操作,在释放之前,我们对锁的状态和是否是当前线程做了判断。

OK,如果你在实际的业务中如果遇到什么问题,欢迎留言探讨。

引申分析

    1. lock 和 tryLock 区别?

简单来说,lock 会一直阻塞,而 tryLock 加锁失败,会返回 false。

    1. 如果锁的时间少于业务的时间,会怎么样?

通过上面的分析,我们知道 tryLock 会加锁失败,而 lock,在锁到释放时间后,即便业务没有执行完,也会继续执行,并且不会报错。

@RequestMapping("/lock")
public String lock() {
    RLock rLock = redissonClient.getLock("demo-spring-boot-redisson:lock");
    if (Objects.isNull(rLock)) {
        return "exception";
    }
    try {
        rLock.lock(5, TimeUnit.SECONDS);
        System.out.println("execute business");
        TimeUnit.SECONDS.sleep(20);
        return "success";
    } catch (Exception e) {
        return "lock exception";
    } finally {
        if (rLock.isLocked()) {
            rLock.unlock();
        }
    }
}
    1. 在解锁的时候,不判断锁的状态,会报错吗,反正都会解锁?

tryLock 不会。

而 lock 会报错,报错信息如下:

java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 8b7b0374-506b-442f-9bc4-9e1c1cbf4d46 thread-id: 61
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
10月前
|
Java 编译器 数据库
Spring Boot 整合 redisson 实现分布式锁
Spring Boot 整合 redisson 实现分布式锁
164 1
|
11天前
|
安全
【📕分布式锁通关指南 07】源码剖析redisson利用看门狗机制异步维持客户端锁
Redisson 的看门狗机制是解决分布式锁续期问题的核心功能。当通过 `lock()` 方法加锁且未指定租约时间时,默认启用 30 秒的看门狗超时时间。其原理是在获取锁后创建一个定时任务,每隔 1/3 超时时间(默认 10 秒)通过 Lua 脚本检查锁状态并延长过期时间。续期操作异步执行,确保业务线程不被阻塞,同时仅当前持有锁的线程可成功续期。锁释放时自动清理看门狗任务,避免资源浪费。学习源码后需注意:避免使用带超时参数的加锁方法、控制业务执行时间、及时释放锁以优化性能。相比手动循环续期,Redisson 的定时任务方式更高效且安全。
60 24
【📕分布式锁通关指南 07】源码剖析redisson利用看门狗机制异步维持客户端锁
|
7天前
【📕分布式锁通关指南 08】源码剖析redisson可重入锁之释放及阻塞与非阻塞获取
本文深入剖析了Redisson中可重入锁的释放锁Lua脚本实现及其获取锁的两种方式(阻塞与非阻塞)。释放锁流程包括前置检查、重入计数处理、锁删除及消息发布等步骤。非阻塞获取锁(tryLock)通过有限时间等待返回布尔值,适合需快速反馈的场景;阻塞获取锁(lock)则无限等待直至成功,适用于必须获取锁的场景。两者在等待策略、返回值和中断处理上存在显著差异。本文为理解分布式锁实现提供了详实参考。
41 11
【📕分布式锁通关指南 08】源码剖析redisson可重入锁之释放及阻塞与非阻塞获取
|
23天前
|
NoSQL Java Redis
redisson分布式锁
Redisson 分布式锁提供了一种简单高效的方式来实现分布式系统中的锁机制。通过本文介绍的基本用法和高级用法,开发者可以根据具体的业务需求选择合适的锁类型来确保系统的稳定性和高并发性。希望本文能帮助读者更好地理解和使用 Redisson 分布式锁,提高系统的并发处理能力和可靠性。
71 10
|
20天前
|
NoSQL Java Redis
【📕分布式锁通关指南 06】源码剖析redisson可重入锁之加锁
本文详细解析了Redisson可重入锁的加锁流程。首先从`RLock.lock()`方法入手,通过获取当前线程ID并调用`tryAcquire`尝试加锁。若加锁失败,则订阅锁释放通知并循环重试。核心逻辑由Lua脚本实现:检查锁是否存在,若不存在则创建并设置重入次数为1;若存在且为当前线程持有,则重入次数+1。否则返回锁的剩余过期时间。此过程展示了Redisson高效、可靠的分布式锁机制。
41 0
【📕分布式锁通关指南 06】源码剖析redisson可重入锁之加锁
|
1月前
|
NoSQL Java API
Redisson分布式锁使用详解
通过以上内容,您可以全面了解如何在Java项目中使用Redisson实现分布式锁,并根据不同的业务需求选择合适的锁机制。
114 33
|
24天前
|
NoSQL Java 测试技术
【📕分布式锁通关指南 05】通过redisson实现分布式锁
本文介绍了如何使用Redisson框架在SpringBoot中实现分布式锁,简化了之前通过Redis手动实现分布式锁的复杂性和不完美之处。Redisson作为Redis的高性能客户端,封装了多种锁的实现,使得开发者只需关注业务逻辑。文中详细展示了引入依赖、配置Redisson客户端、实现扣减库存功能的代码示例,并通过JMeter压测验证了其正确性。后续篇章将深入解析Redisson锁实现的源码。
33 0
【📕分布式锁通关指南 05】通过redisson实现分布式锁
|
8月前
|
存储 缓存 NoSQL
Redis常见面试题(二):redis分布式锁、redisson、主从一致性、Redlock红锁;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
redis分布式锁、redisson、可重入、主从一致性、WatchDog、Redlock红锁、zookeeper;Redis集群、主从复制,全量同步、增量同步;哨兵,分片集群,Redis为什么这么快,I/O多路复用模型——用户空间和内核空间、阻塞IO、非阻塞IO、IO多路复用,Redis网络模型
Redis常见面试题(二):redis分布式锁、redisson、主从一致性、Redlock红锁;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
|
3月前
|
供应链 NoSQL Java
关于Redisson分布式锁的用法
Redisson分布式锁是实现分布式系统中资源同步的有效工具。通过合理配置和使用Redisson的各种锁机制,可以确保系统的高可用性和数据一致性。本文详细介绍了Redisson分布式锁的配置、基本用法和高级用法,并提供了实际应用示例,希望对您在实际项目中使用Redisson分布式锁有所帮助。c
347 10
|
9月前
|
NoSQL Redis
redis分布式锁redisson
底层会尝试去加锁,如果加锁失败,会睡眠,自旋加锁,直到获取到锁为止。
83 1

热门文章

最新文章