【📕分布式锁通关指南 06】源码剖析redisson可重入锁之加锁

简介: 本文详细解析了Redisson可重入锁的加锁流程。首先从`RLock.lock()`方法入手,通过获取当前线程ID并调用`tryAcquire`尝试加锁。若加锁失败,则订阅锁释放通知并循环重试。核心逻辑由Lua脚本实现:检查锁是否存在,若不存在则创建并设置重入次数为1;若存在且为当前线程持有,则重入次数+1。否则返回锁的剩余过期时间。此过程展示了Redisson高效、可靠的分布式锁机制。

引言

在上篇中,我们基于spring boot整合redisson实现了分布式锁,接下来我会带领大家花一些时间来学习redisson如何实现各种锁,所以我们需要先从github上下载它的源码,本篇则先从可重入锁的相关实现开始来为大家做讲解。

加锁流程分析

这里我们按照步骤逐步分析Redisson 可重入锁的加锁流程。

1.首先从入口方法开始 (RLock.lock()):

// RLock 接口的默认实现类 RedissonLock
public void lock() {
   
    try {
   
        lock(-1, null, false);
    } catch (InterruptedException e) {
   
        throw new IllegalStateException();
    }
}

2.核心加锁逻辑实现:

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
   

    // 获取当前线程ID
    long threadId = Thread.currentThread().getId();

    // 尝试获取锁
    Long ttl = tryAcquire(leaseTime, unit, threadId);

    // 如果ttl为空,表示获取锁成功
    if (ttl == null) {
   
        return;
    }

    // 如果获取锁失败,订阅到对应的redisson锁channel,等待锁释放消息
    RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
    if (interruptibly) {
   
        subscribeFuture.syncUninterruptibly();
    } else {
   
        subscribeFuture.sync();
    }

    try {
   
        while (true) {
   
            // 再次尝试获取锁
            ttl = tryAcquire(leaseTime, unit, threadId);
            // 成功获取锁,直接返回
            if (ttl == null) {
   
                break;
            }

            // 等待锁释放通知
            if (ttl >= 0) {
   
                try {
   
                    await(ttl, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
   
                    if (interruptibly) {
   
                        throw e;
                    }
                }
            }
        }
    } finally {
   
        // 取消订阅
        unsubscribe(subscribeFuture, threadId);
    }
}

3.tryAcquire 方法实现(这里包含了可重入的核心逻辑):

private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
   

    // 根据传入的租约时间计算锁的过期时间
    long currentTime = System.currentTimeMillis();
    Long ttl = null;

    // 如果指定了租约时间
    if (leaseTime != -1) {
   
        ttl = tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    } 
    // 使用默认的过期时间
    else {
   
        ttl = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), 
            TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        // 启动看门狗定时续期
        scheduleExpirationRenewal(threadId);
    }

    return ttl;
}

梳理一下整个加锁流程:

1. 入口调用:

  • 用户调用 lock() 方法开始加锁
  • 默认使用无限等待时间,且不响应中断

2. 加锁尝试:

  • 首先获取当前线程 ID
  • 调用 tryAcquire 尝试获取锁
  • 如果获取成功(返回 null),则直接返回
  • 如果获取失败,进入等待流程

3. 锁等待流程:

  • 订阅锁释放的 Channel,等待通知
  • 进入循环,不断尝试获取锁
  • 获取成功则退出循环
  • 获取失败则等待指定时间后继续尝试

加锁Lua脚本分析

继续看tryLockInner方法 - 它是最核心的加锁 Lua 脚本:

<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
   
    // 这里执行 Lua 脚本
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
        // 判断锁是否存在
        "if (redis.call('exists', KEYS[1]) == 0) then " +
            // 不存在则创建锁,并设置重入次数为1
            "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
            "redis.call('pexpire', KEYS[1], ARGV[1]); " +
            "return nil; " +
        "end; " +
        // 锁已存在,判断是否是当前线程持有的锁
        "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
            // 是当前线程,则重入次数+1
            "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
            "redis.call('pexpire', KEYS[1], ARGV[1]); " +
            "return nil; " +
        "end; " +
        // 其他线程持有锁,返回锁的过期时间
        "return redis.call('pttl', KEYS[1]);",
        // 这里是参数
        Collections.singletonList(getName()), // KEYS[1] 锁名称
        unit.toMillis(leaseTime), // ARGV[1] 锁过期时间
        getLockName(threadId)); // ARGV[2] 线程标识
}

它的核心逻辑也很简单:首先检查锁是否存在,如果不存在,则直接加锁,且设置重入次数为1;如果存在,先检查是否是当前线程的锁,如果是,则重入次数+1,如果不是,则返回锁的剩余过期时间。

小结

本篇剖析了redisson可重入锁的加锁流程源码,其实这里读者应该可以发现我们前面通过redis手撸的时候的逻辑其实和这里几乎一致,这也是我们学习源码的意义,借鉴别人优秀的设计并为自己所用!

目录
相关文章
|
7天前
|
负载均衡 NoSQL 算法
Redisson分布式锁数据一致性解决方案
通过以上的设计和实现, Redisson能够有效地解决分布式环境下数据一致性问题。但是, 任何技术都不可能万无一失, 在使用过程中还需要根据实际业务需求进行逻辑屏障的设计和错误处理机制的建立。
98 48
|
1月前
|
安全
【📕分布式锁通关指南 07】源码剖析redisson利用看门狗机制异步维持客户端锁
Redisson 的看门狗机制是解决分布式锁续期问题的核心功能。当通过 `lock()` 方法加锁且未指定租约时间时,默认启用 30 秒的看门狗超时时间。其原理是在获取锁后创建一个定时任务,每隔 1/3 超时时间(默认 10 秒)通过 Lua 脚本检查锁状态并延长过期时间。续期操作异步执行,确保业务线程不被阻塞,同时仅当前持有锁的线程可成功续期。锁释放时自动清理看门狗任务,避免资源浪费。学习源码后需注意:避免使用带超时参数的加锁方法、控制业务执行时间、及时释放锁以优化性能。相比手动循环续期,Redisson 的定时任务方式更高效且安全。
97 24
【📕分布式锁通关指南 07】源码剖析redisson利用看门狗机制异步维持客户端锁
|
1天前
|
人工智能 安全 Java
智慧工地源码,Java语言开发,微服务架构,支持分布式和集群部署,多端覆盖
智慧工地是“互联网+建筑工地”的创新模式,基于物联网、移动互联网、BIM、大数据、人工智能等技术,实现对施工现场人员、设备、材料、安全等环节的智能化管理。其解决方案涵盖数据大屏、移动APP和PC管理端,采用高性能Java微服务架构,支持分布式与集群部署,结合Redis、消息队列等技术确保系统稳定高效。通过大数据驱动决策、物联网实时监测预警及AI智能视频监控,消除数据孤岛,提升项目可控性与安全性。智慧工地提供专家级远程管理服务,助力施工质量和安全管理升级,同时依托可扩展平台、多端应用和丰富设备接口,满足多样化需求,推动建筑行业数字化转型。
24 5
|
1月前
【📕分布式锁通关指南 08】源码剖析redisson可重入锁之释放及阻塞与非阻塞获取
本文深入剖析了Redisson中可重入锁的释放锁Lua脚本实现及其获取锁的两种方式(阻塞与非阻塞)。释放锁流程包括前置检查、重入计数处理、锁删除及消息发布等步骤。非阻塞获取锁(tryLock)通过有限时间等待返回布尔值,适合需快速反馈的场景;阻塞获取锁(lock)则无限等待直至成功,适用于必须获取锁的场景。两者在等待策略、返回值和中断处理上存在显著差异。本文为理解分布式锁实现提供了详实参考。
79 11
【📕分布式锁通关指南 08】源码剖析redisson可重入锁之释放及阻塞与非阻塞获取
|
25天前
|
数据采集 存储 数据可视化
分布式爬虫框架Scrapy-Redis实战指南
本文介绍如何使用Scrapy-Redis构建分布式爬虫系统,采集携程平台上热门城市的酒店价格与评价信息。通过代理IP、Cookie和User-Agent设置规避反爬策略,实现高效数据抓取。结合价格动态趋势分析,助力酒店业优化市场策略、提升服务质量。技术架构涵盖Scrapy-Redis核心调度、代理中间件及数据解析存储,提供完整的技术路线图与代码示例。
分布式爬虫框架Scrapy-Redis实战指南
|
2月前
|
NoSQL Java 中间件
【📕分布式锁通关指南 02】基于Redis实现的分布式锁
本文介绍了从单机锁到分布式锁的演变,重点探讨了使用Redis实现分布式锁的方法。分布式锁用于控制分布式系统中多个实例对共享资源的同步访问,需满足互斥性、可重入性、锁超时防死锁和锁释放正确防误删等特性。文章通过具体示例展示了如何利用Redis的`setnx`命令实现加锁,并分析了简化版分布式锁存在的问题,如锁超时和误删。为了解决这些问题,文中提出了设置锁过期时间和在解锁前验证持有锁的线程身份的优化方案。最后指出,尽管当前设计已解决部分问题,但仍存在进一步优化的空间,将在后续章节继续探讨。
509 131
【📕分布式锁通关指南 02】基于Redis实现的分布式锁
|
2月前
|
NoSQL Java Redis
Springboot使用Redis实现分布式锁
通过这些步骤和示例,您可以系统地了解如何在Spring Boot中使用Redis实现分布式锁,并在实际项目中应用。希望这些内容对您的学习和工作有所帮助。
218 83
|
6月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
29天前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁主要依靠一个SETNX指令实现的 , 这条命令的含义就是“SET if Not Exists”,即不存在的时候才会设置值。 只有在key不存在的情况下,将键key的值设置为value。如果key已经存在,则SETNX命令不做任何操作。 这个命令的返回值如下。 ● 命令在设置成功时返回1。 ● 命令在设置失败时返回0。 假设此时有线程A和线程B同时访问临界区代码,假设线程A首先执行了SETNX命令,并返回结果1,继续向下执行。而此时线程B再次执行SETNX命令时,返回的结果为0,则线程B不能继续向下执行。只有当线程A执行DELETE命令将设置的锁状态删除时,线程B才会成功执行S
|
2月前
|
缓存 NoSQL 搜索推荐
【📕分布式锁通关指南 03】通过Lua脚本保证redis操作的原子性
本文介绍了如何通过Lua脚本在Redis中实现分布式锁的原子性操作,避免并发问题。首先讲解了Lua脚本的基本概念及其在Redis中的使用方法,包括通过`eval`指令执行Lua脚本和通过`script load`指令缓存脚本。接着详细展示了如何用Lua脚本实现加锁、解锁及可重入锁的功能,确保同一线程可以多次获取锁而不发生死锁。最后,通过代码示例演示了如何在实际业务中调用这些Lua脚本,确保锁操作的原子性和安全性。
122 6
【📕分布式锁通关指南 03】通过Lua脚本保证redis操作的原子性