Redisson(二):分布式锁——加锁过程

本文详细解读了Redisson框架中的分布式锁使用,涉及源码分析,包括getLock过程、Watchdog机制、锁续命与renewExpiration等,以及RFuture接口的订阅与取消订阅操作。重点讲解了LUA脚本在加锁中的原子性保障和AQS在优先响应中断的应用。

Redisson

这个是一个Redis框架,提供了各种的分布式服务

Redisson的使用

使用也很简单

  • 注入Redisson
  • redisson.getLock:获取锁,获取锁还没有进行上锁,返回一个RLock
  • RLock.lock:进行加锁,本质上还是执行redis的setnx命令,默认30S过期时间
  • RLock.unlock:进行解锁

下面来一个小Demo

public class ControllerOne {
   
   

    private Redisson redisson;

    public String doSomething(){
   
   
        //锁的名字
        String lockName = "stock-name";
        //使用redisson来进行获取锁,这一步只是获取还没有进行加锁
        RLock lock = redisson.getLock(lockName);
        try{
   
   
            String result = "result";
            //进行加锁
            lock.lock();
            //do something
            return result;
        }finally {
   
   
            //进行解锁
            lock.unlock();
        }
    }
}

可以看到用起来十分简单,下面就分析一下源码

源码分析

首先认识一下整体的架构和原理是怎样的

其实整体的架构也就是解决了上一章留下的锁续命问题

  • 当一个线程去尝试获取锁的时候,会采用自旋的方式去一致尝试加锁
    • 加锁成功,就会开启一个后台线程,后台线程会每隔10S检查线程是否还持有锁(进行锁续命)
      • 如果还持有,则会延长锁的时间
      • 如果不持有,就不会进行延长
    • 加锁失败,自旋回去继续加锁

认识了原理之后,我们先认识一个概念,LUA脚本

LUA脚本其实是一门脚本语言,Redisson其实就是利用LUA脚本来执行redis命令的,并且Redis在执行LUA脚本时,会当作一个原子性命令去执行

getLock

先看第一条代码
在这里插入图片描述
这一行代码其实就是实例化想要获取的锁,比如锁的key,因为锁也是一个对象(RLock),在面向对象的语言中必须要先进行实例化才能使用

在这里插入图片描述
进入到Redisson里面,它其实就是实例化了一个RedissonLock,并且给了两个参数

  • CommandExecutor:命令执行器
  • name:锁的key

具体的构造方法如下

    public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {
   
   
        //进行实例化父类
        super(commandExecutor, name);
        //装入命令执行器
        this.commandExecutor = commandExecutor;
        //设置锁的默认过期时间,默认30S
        this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
        //获取订阅频道
        this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub();
    }

从构造方法可以看到,其实所有的配置都在Config类这里
在这里插入图片描述
至此,一把锁就被实例化了,产生了一个RedissonLock对象

但RedissonLock是一个具体实现,对外的接口却是RLock,所以先看看RLock的构造
在这里插入图片描述
可以看到Rlock多继承了Lock与RLockAsync

  • Lock:JDK的concurrent.locks包下的,也就是JDK自身的Lock,让RLock满足了JDK锁的规范,并且Lock在JDK的实现只有ReentrantLock也就是可重入锁,所以RLock是支持可重入的
  • RLockAsync:Redisson进行扩展的功能,添加了异步锁的功能,所以RLock也支持异步锁

再来看看其实现类
在这里插入图片描述
可以看到,Redisson支持的锁都实现了RLock,所以都有可重入、异步的功能

RedissonLock对象

我们先看一下它整体的架构
在这里插入图片描述
可以看到RedissonLock其实是一个继承的演化过程

  • RedissonObject:抽象类,最底层的与Redis进行交互的功能
  • RedissonExpirable:抽象类,拥有RedissonObject功能的同时,自身提供设置过期时间的一些支持
  • RedissonBaseLock:抽象类,拥有RedissonExpirable功能的同时,自身提供续锁续命功能(开启一个线程任务去不断续命)
  • RedissonLock:拥有BaseLock功能,实现了可重入锁(实现了),与服务器连接断开自动移除锁,并且提供一系列锁的服务,异步上锁等

在这里插入图片描述
对于RedissonLock其成员变量
在这里插入图片描述

  • CommandAsyncExecutor:命令异步执行器
  • LockPubSub:订阅的频道(之后会讲这个频道的作用)
  • internalLockLeaseTime:拥有锁的时间

对于RedissonLock的构造方法(tryLock的目的其实就是构造一把锁出来)

只有一个构造方法

    public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {
   
   
        //构造父类
        super(commandExecutor, name);
        //注入命令执行器
        this.commandExecutor = commandExecutor;
        //设置锁的过期时间,上面也截图显示这为30S
        this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
        //注入订阅服务(Redis的订阅功能)
        this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub();
    }

整体的结构已经看完了,下面就分析一下,lock方法的细节

lock

lock方法其实是重写RLock的

在这里插入图片描述
对外提供的Lock方法有两种

  • 默认的无参lock方法,无参的就是使用默认的过期时间,也就是30S
  • 带有过期时间和时间单位参数的lock方法

具体调用的lock方法,可以看到这是一个private方法,需要给三个参数

  • leaseTime:过期时间
  • TimeUnit:时间单位
  • interruptiably:是否优先响应中断,线程是可以将其他线程设为中断状态(interrupted),而在分布式中,不同实例之间的线程不能通信,所以需要借助Redis的发布订阅功能来实现通信,也就是线程在频道中发布让那个线程interrupted(实现的本质是AQS)
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
   
   
    	//获取当前线程ID
        long threadId = Thread.currentThread().getId();
    	//尝试进行加锁
        Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
        // lock acquired
    	//如果加锁成功,return结束
        if (ttl == null) {
   
   
            return;
        }
    	//加锁失败,为了避免空转,通过订阅频道看能不能获取锁
    	//所以要先进行订阅频道
		//订阅频道
        RFuture<RedissonLockEntry> future = subscribe(threadId);
    	//判断是否优先响应中断
        if (interruptibly) {
   
   
            //优先响应中断并开启开门狗
            commandExecutor
### Redis 分布式锁Redisson 的实现原理 #### 1. **Redis 分布式锁** Redis 分布式锁的核心思想是通过 `SETNX` 和 `EXPIRE` 来实现加锁操作。在早期版本(Redis 2.6.12之前),由于 `SETNX` 不支持设置过期时间,因此需要分两步完成:先调用 `SETNX` 创建键值对表示锁定状态,再调用 `EXPIRE` 设置过期时间以防止死锁[^3]。 然而这种两步操作无法保证原子性,可能会因网络延迟或其他异常导致锁未成功创建却设置了过期时间。为此,Redis 2.6.12引入了新的命令 `SET key value NX PX milliseconds`,其中 `NX` 表示只有当键不存在时才执行设置操作,`PX` 则用于指定毫秒级的过期时间。这种方式能够在一个命令内完成加锁并设定超时,从而有效解决了上述问题。 #### 2. **Redisson 实现分布式锁** Redisson 是基于 Redis 开发的一个 Java 客户端库,它不仅实现了更高级别的抽象接口,还提供了多种类型的分布式对象和服务功能。对于分布式锁而言,Redisson 提供了一种更加健壮可靠的解决方案——`RLock` 接口及其子类实例化方式。 - **加锁逻辑** Redisson 使用 Lua 脚本来确保整个加锁过程具有原子性。该脚本会检查目标资源是否已被占用;如果未被占用,则尝试获取锁并将当前线程 ID 记录下来作为持有者的唯一标识符[^1]。 - **续命机制** 当某个客户端成功获得锁之后,Redisson 会在后台启动一个定时器任务定期向服务器发送续约请求延长锁的有效期限,直到显式解锁为止。此设计可以避免因长时间运行的任务而导致锁提前失效的情况发生[^2]。 - **自旋重试策略** 如果初次未能取得所需资源,则按照预定义间隔不断重复尝试直至达到最大等待时限或最终放弃争夺控制权。 - **公平性和可靠性保障措施** 在某些特殊情况下(比如网络分区), 可能会出现部分节点认为自己已经拿到了全局唯一的锁,但实际上其他地方也有竞争者存在的情形下, redisson 还特别考虑到了这一点并通过内部复杂的协调算法尽可能减少冲突概率[^4]. #### 性能对比分析 | 特性 | Redis 原生分布锁 | Redisson | |-------------------------|------------------------------------------|----------------------------------| | 加锁效率 | 较高 | 略低 | | 锁安全性 | 存在网络抖动等问题 | 更安全可靠 | | 功能扩展能力 | 单纯提供基础加解鎖功能 | 支持更多特性如自动续租、可重入等 | | 易用程度 | 需要开发者手动处理很多细节 | API 封装良好易于集成 | 从表中可以看出虽然原生态方法简单高效但在实际应用过程中往往面临诸多挑战;而借助第三方工具包则可以在一定程度上弥补这些不足之处. ```java // 示例代码展示如何利用Redisson进行分布式锁管理 import org.redisson.api.RLock; import org.redisson.api.RedissonClient; public class DistributedLockExample { private final RedissonClient redissonClient; public void acquireAndReleaseLock(String lockName) throws InterruptedException{ RLock lock = redissonClient.getLock(lockName); try { boolean isLocked = lock.tryLock(10, TimeUnit.SECONDS); // 尝试获取锁最长等待时间为10秒 if(isLocked){ System.out.println(Thread.currentThread().getName()+" acquired the lock."); Thread.sleep(5000L); // Simulate some work }else{ System.err.println("Failed to get lock after waiting..."); } } finally { if(lock.isHeldByCurrentThread()){ lock.unlock(); System.out.println(Thread.currentThread().getName()+ " released the lock."); } } } } ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值