返回顶部
首页 > 资讯 > 后端开发 > Python >RedissonRedLock红锁加锁实现过程及原理
  • 718
分享到

RedissonRedLock红锁加锁实现过程及原理

RedissonRedLock红锁加锁RedissonRedLockRedisson红锁加锁 2023-02-11 12:02:45 718人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

目录一、主从Redis架构中分布式锁存在的问题二、红锁算法原理三、红锁算法的使用四、红锁加锁流程五、RedLock算法问题六、总结本篇文章基于redisson-3.17.6版本源码进

本篇文章基于redisson-3.17.6版本源码进行分析

一、主从redis架构中分布式锁存在的问题

1、线程A从主redis中请求一个分布式锁,获取锁成功;

2、从redis准备从主redis同步锁相关信息时,主redis突然发生宕机,锁丢失了;

3、触发从redis升级为新的主redis;

4、线程B从继任主redis的从redis上申请一个分布式锁,此时也能获取锁成功;

5、导致,同一个分布式锁,被两个客户端同时获取,没有保证独占使用特性;

为了解决这个问题,redis引入了红锁的概念。

二、红锁算法原理

需要准备多台redis实例,这些redis实例指的是完全互相独立的Redis节点,这些节点之间既没有主从,也没有集群关系。客户端申请分布式锁的时候,需要向所有的redis实例发出申请,只有超过半数的redis实例报告获取锁成功,才能算真正获取到锁。

具体的红锁算法主要包括如下步骤:

1、应用程序获取当前系统时间(单位是毫秒);

2、应用程序使用相同的key、value依次尝试从所有的redis实例申请分布式锁,这里获取锁的尝试时间要远远小于锁的超时时间,防止某个master Down了,我们还在不断的获取锁,而被阻塞过长的时间;

3、只有超过半数的redis实例反馈获取锁成功,并且获取锁的总耗时小于锁的超时时间,才认为锁获取成功;

4、如果锁获取成功了,锁的超时时间就是最初的锁超时时间减去获取锁的总耗时时间;

5、如果锁获取失败了,不管是因为获取成功的redis节点没有过半,还是因为获取锁的总耗时超过了锁的超时时间,都会向已经获取锁成功的redis实例发出删除对应key的请求,去释放锁;

三、红锁算法的使用

在Redisson框架中,实现了红锁的机制,Redisson的RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例。当红锁中超过半数的RLock加锁成功后,才会认为加锁是成功的,这就提高了分布式锁的高可用

使用的步骤如下:引入Redisson的Maven依赖

<!-- jdk 1.8+ compatible -->
<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.9.0</version>
</dependency> 

编写单元测试

@Test
public void testRedLock() {
    Config config = new Config();
    config.useSingleServer().setAddress("redis://127.0.0.1:6379");
    RedissonClient client1 = Redisson.create(config);
    RLock lock1 = client1.getLock("lock1");
    RLock lock2 = client1.getLock("lock2");
    RLock lock3 = client1.getLock("lock3");
    RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
    try {
        
        // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
        boolean res = redLock.tryLock(100, 10, TimeUnit.SECONDS);
        if (res) {
            //成功获得锁,在这里处理业务
            System.out.println("成功获取到锁...");
        }
    } catch (Exception e) {
        throw new RuntimeException("aquire lock fail");
    } finally {
        // 无论如何, 最后都要解锁
        redLock.unlock();
    }
}

四、红锁加锁流程

RedissonRedLock红锁继承自RedissonMultiLock联锁,简单介绍一下联锁:

基于Redis的Redisson分布式联锁RedissonMultiLock对象可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例,所有的锁都上锁成功才算成功。

RedissonRedLock的加锁、解锁代码都是使用RedissonMultiLock中的方法,只是其重写了一些方法,如:

failedLocksLimit():允许加锁失败节点个数限制。在RedissonRedLock中,必须超过半数加锁成功才能算成功,其实现为:

protected int failedLocksLimit() {
    return locks.size() - minLocksAmount(locks);
}
protected int minLocksAmount(final List<RLock> locks) {
    // 最小的获取锁成功数:n/2 + 1。 过半机制
    return locks.size()/2 + 1;
}

在RedissonMultiLock中,则必须全部都加锁成功才算成功,所以允许加锁失败节点个数为0,其实现为:

protected int failedLocksLimit() {
    return 0;
}

接下来,我们以tryLock()方法为例,详细分析红锁是如何加锁的,具体代码如下:

org.redisson.RedissonMultiLock#tryLock(long, long, java.util.concurrent.TimeUnit)

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
//        try {
//            return tryLockAsync(waitTime, leaseTime, unit).get();
//        } catch (ExecutionException e) {
//            throw new IllegalStateException(e);
//        }
    long newLeaseTime = -1;
    if (leaseTime > 0) {
        if (waitTime > 0) {
            newLeaseTime = unit.toMillis(waitTime)*2;
        } else {
            newLeaseTime = unit.toMillis(leaseTime);
        }
    }
    // 获取当前系统时间,单位:毫秒
    long time = System.currentTimeMillis();
    long remainTime = -1;
    if (waitTime > 0) {
        remainTime = unit.toMillis(waitTime);
    }
    long lockWaitTime = calcLockWaitTime(remainTime);
    // 允许加锁失败节点个数限制(N - ( N / 2 + 1 ))
    // 假设有三个redis节点,则failedLocksLimit = 1
    int failedLocksLimit = failedLocksLimit();
    // 存放调用tryLock()方法加锁成功的那些redis节点
    List<RLock> acquiredLocks = new ArrayList<>(locks.size());
    // 循环所有节点,通过EVAL命令执行lua脚本进行加锁
    for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {
        // 获取到其中一个redis实例
        RLock lock = iterator.next();
        String lockName = lock.getName();
        System.out.println("lockName = " + lockName + "正在尝试加锁...");
        boolean lockAcquired;
        try {
            // 未指定锁超时时间和获取锁等待时间的情况
            if (waitTime <= 0 && leaseTime <= 0) {
                // 调用tryLock()尝试加锁
                lockAcquired = lock.tryLock();
            } else {
                // 指定了超时时间的情况,重新计算获取锁的等待时间
                long awaitTime = Math.min(lockWaitTime, remainTime);
                // 调用tryLock()尝试加锁
                lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);
            }
        } catch (RedisResponseTimeoutException e) {
            // 如果抛出RedisResponseTimeoutException异常,为了防止加锁成功,但是响应失败,需要解锁所有节点
            unlockInner(Arrays.asList(lock));
            // 表示获取锁失败
            lockAcquired = false;
        } catch (Exception e) {
            // 表示获取锁失败
            lockAcquired = false;
        }
        if (lockAcquired) {
            // 如果当前redis节点加锁成功,则加入到acquiredLocks集合中
            acquiredLocks.add(lock);
        } else {
            // 计算已经申请锁失败的节点是否已经到达 允许加锁失败节点个数限制 (N-(N/2+1)), 如果已经到达,就认定最终申请锁失败,则没有必要继续从后面的节点申请了。因为 Redlock 算法要求至少N/2+1 个节点都加锁成功,才算最终的锁申请成功
            if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
                break;
            }
            if (failedLocksLimit == 0) {
                unlockInner(acquiredLocks);
                if (waitTime <= 0) {
                    return false;
                }
                failedLocksLimit = failedLocksLimit();
                acquiredLocks.clear();
                // reset iterator
                while (iterator.hasPrevious()) {
                    iterator.previous();
                }
            } else {
                failedLocksLimit--;
            }
        }
        // 计算 目前从各个节点获取锁已经消耗的总时间,如果已经等于最大等待时间,则认定最终申请锁失败,返回false
        if (remainTime > 0) {
            // remainTime: 锁剩余时间,这个时间是某个客户端向所有redis节点申请获取锁的总等待时间, 获取锁的中耗时时间不能大于这个时间。
            // System.currentTimeMillis() - time: 这个计算出来的就是当前redis节点获取锁消耗的时间
            remainTime -= System.currentTimeMillis() - time;
            // 重置time为当前时间,因为下一次循环的时候,方便计算下一个redis节点获取锁消耗的时间
            time = System.currentTimeMillis();
            // 锁剩余时间减到0了,说明达到最大等待时间,加锁超时,认为获取锁失败,需要对成功加锁集合 acquiredLocks 中的所有锁执行锁释放
            if (remainTime <= 0) {
                unlockInner(acquiredLocks);
                // 直接返回false,获取锁失败
                return false;
            }
        }
    }
    if (leaseTime > 0) {
        // 重置锁过期时间
        acquiredLocks.stream()
                .map(l -> (RedissonBaseLock) l)
                .map(l -> l.expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS))
                .forEach(f -> f.toCompletableFuture().join());
    }
    // 如果逻辑正常执行完则认为最终申请锁成功,返回true
    return true;
}

从源码中可以看到,红锁的加锁,其实就是循环所有加锁的节点,挨个执行LUA脚本加锁,对于加锁成功的那些节点,会加入到acquiredLocks集合中保存起来;如果加锁失败的话,则会判断已经申请锁失败的节点是否已经到达允许加锁失败节点个数限制 (N-(N/2+1)), 如果已经到达,就认定最终申请锁失败,则没有必要继续从后面的节点申请了。

并且,每个节点执行完tryLock()尝试获取锁之后,无论是否获取锁成功,都会判断目前从各个节点获取锁已经消耗的总时间,如果已经等于最大等待时间,则认定最终申请锁失败,需要对成功加锁集合 acquiredLocks 中的所有锁执行锁释放,然后返回false。

五、RedLock算法问题

1、持久化问题

假设一共有5个Redis节点:A, B, C, D, E:

客户端1成功锁住了A, B, C,获取锁成功,但D和E没有锁住。

节点C崩溃重启了,但客户端1在C上加的锁没有持久化下来,丢失了。

节点C重启后,客户端2锁住了C, D, E,获取锁成功。

这样,客户端1和客户端2同时获得了锁(针对同一资源)。

2、客户端长时间阻塞,导致获得的锁释放,访问的共享资源不受保护的问题。

3、Redlock算法对时钟依赖性太强, 若某个节点中发生时间跳跃(系统时间戳不正确),也可能会引此而引发锁安全性问题。

六、总结

红锁其实也并不能解决根本问题,只是降低问题发生的概率。完全相互独立的redis,每一台至少也要保证高可用,还是会有主从节点。既然有主从节点,在持续的高并发下,master还是可能会宕机,从节点可能还没来得及同步锁的数据。很有可能多个主节点也发生这样的情况,那么问题还是回到一开始的问题,红锁只是降低了发生的概率。

其实,在实际场景中,红锁是很少使用的。这是因为使用了红锁后会影响高并发环境下的性能,使得程序的体验更差。所以,在实际场景中,我们一般都是要保证Redis集群的可靠性。同时,使用红锁后,当加锁成功的RLock个数不超过总数的一半时,会返回加锁失败,即使在业务层面任务加锁成功了,但是红锁也会返回加锁失败的结果。另外,使用红锁时,需要提供多套Redis的主从部署架构,同时,这多套Redis主从架构中的Master节点必须都是独立的,相互之间没有任何数据交互。

到此这篇关于Redisson RedLock红锁加锁实现过程及原理的文章就介绍到这了,更多相关Redisson RedLock内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: RedissonRedLock红锁加锁实现过程及原理

本文链接: https://lsjlt.com/news/195655.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • RedissonRedLock红锁加锁实现过程及原理
    目录一、主从redis架构中分布式锁存在的问题二、红锁算法原理三、红锁算法的使用四、红锁加锁流程五、RedLock算法问题六、总结本篇文章基于redisson-3.17.6版本源码进...
    99+
    2023-02-11
    Redisson RedLock红锁加锁 Redisson RedLock Redisson红锁加锁
  • Redisson RedLock红锁加锁实现过程及原理是什么
    本篇内容介绍了“Redisson RedLock红锁加锁实现过程及原理是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、主从...
    99+
    2023-07-05
  • redis锁实现原理
    redis锁是一种分布式锁机制,通过以下步骤实现:1. 获取锁(setnx);2. 释放锁(del);3. 设置过期时间(expire);4. 锁竞争。它优势在于分布式、简单、高效、可扩...
    99+
    2024-04-19
    redis 并发访问
  • MySQL InnoDB锁类型及锁原理实例解析
    目录锁共享锁排他锁意向锁记录锁间隙锁临键锁死锁死锁产生条件行锁发生死锁表锁发生死锁锁的释放事务阻塞死锁的避免锁的日志行锁的原理不带任何索引的表带主键索引的表带唯一索引的表结论1.表必定有索引2.唯一索引数据行加锁,主键索...
    99+
    2022-11-27
    MySQL InnoDB锁类型锁原理 MySQL InnoDB 锁
  • 分布式锁的原理及Redis怎么实现分布式锁
    这篇文章主要介绍“分布式锁的原理及Redis怎么实现分布式锁”,在日常操作中,相信很多人在分布式锁的原理及Redis怎么实现分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解...
    99+
    2023-02-02
    redis
  • Golang实现文件锁的原理及应用
    Golang实现文件锁的原理及应用 在操作系统中,文件锁是一种用于保护文件或资源不被多个进程同时访问或修改的机制。在Golang中,通过使用sync包提供的Mutex锁可以实现对内存中...
    99+
    2024-02-29
    应用 golang 文件锁
  • Golang锁原理如何实现
    这篇文章主要介绍了Golang锁原理如何实现的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Golang锁原理如何实现文章都会有所收获,下面我们一起来看看吧。什么是锁锁的本质,就是一种资源,是由操作系统维护的一种...
    99+
    2023-07-05
  • Java synchronized底层实现原理以及锁优化
    目录一、概述synchronized简介synchronized作用synchronized的使用二、实现原理三、理解Java对象头四、JVM对synchronized的锁优化1、偏...
    99+
    2024-04-02
  • Ajax的原理及实现过程
    这篇文章主要介绍“Ajax的原理及实现过程”,在日常操作中,相信很多人在Ajax的原理及实现过程问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Ajax的原理及实现过程”的疑惑...
    99+
    2024-04-02
  • redis分布式锁的实现原理
    小编给大家分享一下redis分布式锁的实现原理,希望大家阅读完这篇文章后大所收获,下面让我们一起去探讨吧!分布式锁其实可以理解为:控制分布式系统有序的去对共享资源进行操作,通过互斥来保持一致性。举个不太恰当...
    99+
    2024-04-02
  • InterProcessMutex实现zookeeper分布式锁原理
    原理简介: zookeeper实现分布式锁的原理就是多个节点同时在一个指定的节点下面创建临时会话顺序节点,谁创建的节点序号最小,谁就获得了锁,并且其他节点就会监听序号比自己小的节点,...
    99+
    2024-04-02
  • AndroidLock锁实现原理详细分析
    目录Lock简介synchronized和lock的区别写个Demolock源码总结Lock简介 Lock接口位于J.U.C下locks包内,其定义了Lock应该具备的方法。 Loc...
    99+
    2023-02-17
    Android Lock锁 Android Lock锁原理
  • golang锁的实现原理是什么
    golang锁的实现原理是通过互斥锁和读写锁来保护共享资源的访问。互斥锁是一种基本的锁机制,用于保护共享资源,使用一个标志位来表示资源是否被占用,当一个goroutine获取到互斥锁后,其他goroutine就会被阻塞,直到该gorouti...
    99+
    2023-12-12
    Golang
  • 如何实现Oracle11g用户修改密码及加锁解锁功能
    小编给大家分享一下如何实现Oracle11g用户修改密码及加锁解锁功能,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1.运行 c...
    99+
    2024-04-02
  • java Semaphore共享锁实现原理解析
    目录正文Semaphore内部类及继承关系Semaphore.acquire流程分析(以非公平锁为例)tryAcquireShareddoAcquireSharedInterrupt...
    99+
    2023-01-09
    Semaphore共享锁 java 锁
  • Spring Boot 实现Redis分布式锁原理
    目录分布式锁实现引入jar包封装工具类模拟秒杀扣减库存测试代码方案优化问题1:扣减库存逻辑无法保证原子性,问题2:如果加锁失败,则会直接访问,无法重入锁总结分布式锁实现 引入jar包...
    99+
    2022-11-13
    Spring Boot 实现Redis分布式锁 Spring Boot  Redis分布式锁
  • Javasynchronized重量级锁实现过程浅析
    目录一、什么是重量级锁二、重量级锁的演示三、重量级锁的原理四、锁的优缺点对比一、什么是重量级锁 当有大量的线程都在竞争同一把锁的时候,这个时候加的锁,就是重量级锁。 这个重量级锁...
    99+
    2023-02-11
    Java synchronized重量级锁 Java synchronized Java重量级锁
  • Go语言底层原理互斥锁的实现原理
    目录Go 互斥锁的实现原理?概念使用场景底层实现结构操作加锁解锁Go 互斥锁正常模式和饥饿模式的区别?正常模式(非公平锁)饥饿模式(公平锁)Go 互斥锁允许自旋的条件?Go 互斥锁的...
    99+
    2024-04-02
  • Android程序锁的实现以及逻辑
    本项目是一个比较有趣的项目源码,可以给其他项目加锁,程序锁的原理是一个“看门狗”的服务定时监视顶层activity,如果activity对应的包名是之前上锁的应用程序的,则弹出...
    99+
    2022-06-06
    Android
  • Java中锁的实现原理和实例用法
    这篇文章主要介绍“Java中锁的实现原理和实例用法”,在日常操作中,相信很多人在Java中锁的实现原理和实例用法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java中锁的实现原理和实例用法”的疑惑有所帮助!...
    99+
    2023-06-16
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作