Redis分布式锁实现原理

这篇具有很好参考价值的文章主要介绍了Redis分布式锁实现原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

为什么需要分布式锁

在早期互联网的架构中,一个应用都是单机进行部署,这种情况下,利用JDK提供的锁机制即可解决共享数据在多线程场景下的线程安全问题,但随着技术的发展,分布式系统架构逐渐普及,在分布式架构中,由于一个应用会进行多机部署,服务器实例之间的JVM是互相独立的, 这时候利用JDK提供的锁在这种场景下是没办法共享的 ,所以需要依靠一个中间件实现在分布式的场景下对锁的共享,典型的如 **Redis **。

为什么是Redis

  • 由于Redis中的数据是存放在内存中,读写速度很快,没有磁盘的IO,所以加锁和释放锁的速度会很快,性能很高。
  • Redis对客户端的请求都是单进程单线程进行处理的,也就意味着串行化去执行的,所以Redis的单个命令是支持原子性的,即使对于多个Redis指令,Redis也提供了将多个指令合并在一个Lua脚本中一起执行,从而保证多条指令的原子性操作。

分布式锁的加锁和解锁的要求必须是原子性的,而Redis就可以很好的支持这一特性。

如何利用Redis实现锁机制

用一句话概括的说,其实Redis实现锁机制其实就是在Redis中设置一个key-value,当key存在时,即上锁,删除key即解锁。
当然要想实现一个很健壮的锁机制,这其中还有很多细节不容忽视,所以下面,我们一步一步的跟着思路去思考如何使用Redis实现一个分布式的锁:

  1. 加锁保证互斥性,同一时间只能有一个客户端加锁成功。

    • 通过Redis的setnx命令实现,setnx即 set if not exists,当key不存在时才能设置成功

      SETNX key value
           summary: Set the value of a key, only if the key does not exist
           since: 1.0.0
           group: string
      
    • (推荐)通过set key value PX 3000 NX,PX指过期时间,NX即not exists,效果等同setnx,但是由于 setnx 不支持设置过期时间,所以需要拆分成两个两个命令setnx key valueexpire key 3,要保证原子性还需要将两个命令合并为一个lua脚本。

      SET key value [EX seconds|PX milliseconds|EXAT timestamp|PXAT milliseconds-timestamp|KEEPTTL] [NX|XX] [GET]
        summary: Set the string value of a key
        since: 1.0.0
        group: string
      

    前面也提到,由于Redis是单线程的,所以当大量请求过来时,这些请求是串行化执行的,所以一定只有一个请求才能设置成功,从而保证了加锁的互斥性。

  2. 防止死锁
    当客户端加锁之后,在释放锁之前如果Redis发生了宕机,那么Redis中的锁就无法自动释放,最终产生死锁,所以为了避免死锁,我们还需要给这个锁的key设置一个合理的过期时间,当锁占用的时间超过指定的过期时间,则自动删除该锁对应的key释放锁,让其他客户端能够有机会去争抢这个锁。

  3. 锁过期提前释放
    上一步由于为了避免死锁,所以在加锁时,指定了锁的有效期,但是这个有效期也是估算出来的,如果实际业务处理时间超过了锁的有效期,锁会被提前释放,就会导致其他客户端获得了锁,从而导致锁机制的失效。
    所以为了解决该问题,就需要一个机制去对锁进行续期,防止在加锁的业务还未处理完之前,被提前释放,我们可以利用一个子线程,在锁有效期到期之前,定期的去的给锁进行续期,即:增加key的过期时间。

  4. 释放锁
    释放锁,只需要将对应的锁的key从redis中删除即可,但是这里需要注意的是,在释放锁之前,必须判断只有是当前线程占用的锁才可以进行释放,所以锁的key对应的value我们就可以存放当前的客户端的身份标识,在释放锁之前,比对一下当前释放锁的客户端是否是当前加锁的客户端,如果匹配成功则可以正常删除对应的key释放锁,否则就不释放锁。

综合以上的对Redis实现锁的思路分析,其实市面上已经有了成熟的开源框架的实现,就是今天的主角 RedissonRedisson 不仅实现了基于Redis实现加锁,解锁,还提供了防死锁,锁续期,以及可重入的锁的功能,可以说能够满足大多数的场景了,下面我们就看下Redisson底层是如何实现Redis分布式锁的。

Redisson原理

Redisson提供RLock的接口,继承了JUC包下的java.util.concurrent.locks.Lock, 所以加锁的方式和JDK提供的ReentrantLock加锁方式很类似。
我们先来看一段利用Redisson加锁的代码:

        // 获取锁对象
        RLock lock = redissonClient.getLock("myLock");
        try {
            // 加锁, 5 代表锁过期自动释放的时间,单位为 秒
            boolean locked = lock.tryLock(5, TimeUnit.SECONDS);

            if (locked) {
                // 处理业务逻辑
            } else {
                // 未获取锁的逻辑
            }
        } finally {
            // 只有持有锁的线程才能释放锁
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
  1. 获取锁对象 RLock lock = redissonClient.getLock("myLock");

    这里myLock就是指定Redis中锁的唯一标识,关于key值需要根据实际业务来确定,一定要保证唯一性,而且key值也决定了锁粒度的大小。

  2. 加锁

    Redisson常用的加锁方式如:lock,tryLock,方式的区别如下:

    • lock方式,无返回值,如果锁已经被其他客户端持有,会利用Redis中的PUB/SUB机制,订阅Redisson解锁消息,并且当前线程会进行await阻塞,当监听到解锁消息,阻塞的客户端会被唤醒进行锁争夺。

      redis分布式锁实现原理,Redis,redis,分布式,数据库

    • tryLock 方式是有返回值的,当加锁失败会返回一个false,交由调用方决定后续的操作。

    对于以上两种加锁方式,最终都会调用下面这段Lua脚本的代码进行加锁操作:

redis分布式锁实现原理,Redis,redis,分布式,数据库

对于上述的Lua脚本解读如下:

a. 第一步通过 exists key 去判断锁的key是否存在.

b. 第二步,如果第一步中的判断返回0,表示 key 不存在,这时候可以加锁,利用hset key value 设置keyvalue,即ARGV[2].

  > hset 代表这个key对应的value是一个hash类型,类似于HashMap,其中field代表客户端,value是重入次数。
  >
  > ARGV[2] 代表了加锁客户端的唯一标识,由UUID和线程id组成,可以理解为某个客户端的某个线程加锁。

c. 第三步设置key的存活时间internalLockLeaseTime,这里 ARGV[1] 代表的是锁 key 的默认生存时间,默认 30 秒。

如果a步骤中的exists key判断key已经存在,即exists KEYS[1] 返回1,则利用hexists key field 判断当前的客户端ID(即ARG[2])在锁的key对应的hash数据结构中是否存在

  • 存在表明是当前客户端持有的锁,这时候就相当于锁重入,就利用hincrby key field increment去对锁重入进行 + 1,并通过pexpire key millseconds设置过期时间。
  • 不存在则表明当前持有锁是另外一个客户端,所以直接退出第二个if判断

如果上述两个if条件都未满足,则pttl key 返回当前锁的key的剩余存活时间。

  1. 从上述描述可以看出,锁的key其实可以设置过期时间的,key一旦过期,redis就会清除这个key,如果当业务处理的时间超出了锁的有效期,这时候锁就会被其他客户端获取成功,会造成锁失效,所以在Redisson中还存在一个WatchDog的机制去对去定期(默认10秒)去给锁续期,即Redisson会开启定时任务TimerTask去定时对锁的有效期进行延长。
    这里需要注意的是,WatchDog机制只有在我们未手工指定对应的锁过期时间leaseTime才会生效

    redis分布式锁实现原理,Redis,redis,分布式,数据库

  2. unlock 释放锁,释放锁的时候需要判断当前的客户端(UUID + threadId)是否持有锁,只有持有锁的客户端才能释放锁。

    redis分布式锁实现原理,Redis,redis,分布式,数据库

    unlock 其实最终也是通过Lua脚本进行解锁:

    a. 通过hexists lockKey clientId判断是否当前客户端持有锁,如果不是,则直接返回

    b. 如果是当前客户端持有锁,则对锁的重入次数counter进行-1,如果-1之后重入次数依旧大于0,说明锁被重入,需等待重入次数为0才可以解锁,这时候重新设置锁key的有效期。

    c. 如果重入次数已经为0,则开始释放锁,即del lockKey,并且发布一个锁释放的消息到channel中,通知其他等待锁的客户端进行加锁操作。


以上就是Redisson分布式锁的实现原理。

Redisson 单机模式下的缺点

事实上这类锁最大的缺点就是它加锁时只作用在一个Redis节点上,如果Redis挂了,那么就会产生单点故障的问题,
即使Redis通过sentinel哨兵机制保证高可用,当master节点发生故障后,可以故障转移,slaver升级为master,
但由于主从之间的数据同步是异步的, 如果在发生主从切换的时候,key 还没来得及同步到slaver上,那么就会出现锁丢失的情况:

  1. 在Redis的master节点上拿到了锁;
  2. 但是这个加锁的key还没有同步到slave节点;
  3. master故障,发生故障转移,slave节点升级为master节点;
  4. 导致锁丢失

所以Redis对于这种场景提供RedLock红锁,即对主节点master的Redis进行集群,多个master实例间互相独立,需要对N个实例进行上锁,这里假设有5个Redis集群,当获取锁的时候,当且仅当大多数的节点(即 N/2 + 1)都设置锁成功,整个获取锁的过程才算成功,如果没有满足该条件,就需要在向所有的Redis实例发送释放锁命令即可,不用关心之前有没有从Redis实例成功获取到锁.

这里顺手推荐一篇比较好的文章 https://segmentfault.com/a/1190000041172633文章来源地址https://www.toymoban.com/news/detail-569457.html

到了这里,关于Redis分布式锁实现原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java中利用Redis,ZooKeeper,数据库等实现分布式锁(遥遥领先)

    1.1 什么是分布式锁 在我们进行单机应用开发涉及并发同步的时候,我们往往采用synchronized或者ReentrantLock的方式来解决多线程间的代码同步问题。但是当我们的应用是在分布式集群工作的情况下,那么就需要一种更加高级的锁机制,来处理种跨机器的进程之间的数据同步问题

    2024年02月03日
    浏览(51)
  • 在Spring中,可以使用不同的方式来实现分布式锁,例如基于数据库、Redis、ZooKeeper等

    在Spring中,可以使用不同的方式来实现分布式锁,例如基于数据库、Redis、ZooKeeper等。下面是两种常见的实现方式: 使用Redis实现分布式锁: 使用自定义注解实现本地锁: 以上是两种常见的在Spring中实现分布式锁的方式。第一种方式使用Redis作为分布式锁的存储介质,通过

    2024年03月17日
    浏览(46)
  • Redis学习(三)分布式缓存、多级缓存、Redis实战经验、Redis底层原理

    单节点Redis存在着: 数据丢失问题:单节点宕机,数据就丢失了。 并发能力和存储能力问题:单节点能够满足的并发量、能够存储的数据量有限。 故障恢复问题:如果Redis宕机,服务不可用,需要一种自动的故障恢复手段。 RDB持久化 RDB(Redis database backup file,Redis数据库备份

    2024年02月16日
    浏览(41)
  • Redis集群(分布式缓存):详解持久化、主从同步原理、哨兵机制、Cluster分片集群,实现高并发高可用

            单机式Redis存在以下问题,因此需要Redis集群化来解决这些问题        Redis数据快照,简单来说就是 把内存中的所有数据都记录到磁盘中 。当Redis实例故障重启后,从 磁盘读取快照文件,恢复数据 。快照文件称为RDB文件,默认是保存在当前运行目录。     (1)

    2024年02月08日
    浏览(58)
  • 分布式天梯图算法在 Redis 图数据库中的应用

    Redis是一个高性能的键值对数据库,支持常用的数据结构和分布式操作,被广泛应用于缓存、消息队列和排行榜等场景。除了基本的数据结构,Redis还支持图数据结构并提供了一些算法支持。 天梯图算法是一种基于贪心的图搜索算法,在寻找最短路径问题中具有很高的效率。

    2024年02月14日
    浏览(34)
  • Redis——》实现分布式锁

    推荐链接:     总结——》【Java】     总结——》【Mysql】     总结——》【Redis】     总结——》【Kafka】     总结——》【Spring】     总结——》【SpringBoot】     总结——》【MyBatis、MyBatis-Plus】     总结——》【Linux】     总结——》【MongoDB】    

    2024年02月10日
    浏览(61)
  • redis实现分布式延时队列

    延时队列是一种特殊的消息队列,它允许将消息在一定的延迟时间后再进行消费。延时队列的主要特点是可以延迟消息的处理时间,以满足定时任务或者定时事件的需求。 总之,延时队列通过延迟消息的消费时间,提供了一种方便、可靠的方式来处理定时任务和定时事件。它

    2024年02月08日
    浏览(45)
  • 分布式锁之redis实现

    需要挂在的data和redis.conf自行创建即可 不要忘记开放端口6379 修改redis.conf配置文件,设置 requirepass xxxxx 如果直接使用RedisTemplate使用的序列化器是jdk的,存的是二进制,使用StringRedisTemplate默认初始化序列化器就是String类型 执行票数存入redis指令  编写代码演示超卖问题  500

    2024年02月10日
    浏览(47)
  • 使用redis实现分布式锁

    在一个分布式系统中,也会涉及多个节点访问同一个公共资源的情况,此时就需要通过锁来做互斥控制,避免出现类似于“线程安全”的问题,而java的synchronized这样的锁只能在当前进程中生效,在分布式的这种多个进程多个主机的场景无能为力,此时就需要分布式锁。 例如

    2024年02月07日
    浏览(56)
  • redis如何实现分布式锁?

    首先,“分布式锁”的概念,是相对“本地锁”而言。 本地锁比如java中的synchronized 这类 JDK 自带的 本地锁 ,来控制一个 JVM 进程内的多个线程对本地共享资源的访问。 同一时刻只有一个线程可以获取到本地锁访问共享资源。 分布式系统下,不同的服务/客户端通常运

    2024年02月06日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包