专栏首页犀利豆的技术空间Redis 分布式锁的正确实现方式

Redis 分布式锁的正确实现方式

之前我们使用的定时任务都是只部署在了单台机器上,为了解决单点的问题,为了保证一个任务,只被一台机器执行,就需要考虑锁的问题,于是就花时间研究了这个问题。到底怎样实现一个分布式锁呢?

阅读这篇文章你可以了解到:

  • 单机版的实现
  • 分布式环境下RedLock实现

锁的本质就是互斥,保证任何时候能有一个客户端持有同一个锁,如果考虑使用redis来实现一个分布式锁,最简单的方案就是在实例里面创建一个键值,释放锁的时候,将键值删除。但是一个可靠完善的分布式锁需要考虑的细节比较多,我们就来看看如何写一个正确的分布式锁。

单机版分布式锁 SETNX

所以我们直接基于 redis 的 setNX (SET if Not eXists)命令,实现一个简单的锁。直接上伪码

锁的获取:

    SET resource_name my_random_value NX PX 30000

锁的释放:

    if redis.call("get",KEYS[1]) == ARGV[1] then
        return redis.call("del",KEYS[1])
    else
        return 0
    end

几个细节需要注意:

  • 首先在获取锁的时候我们需要设置设置超时时间。设置超时时间是为了,防止客户端崩溃,或者网络出现问题以后锁一直被持有。真个系统就死锁了。
  • 使用 setNX 命令,保证查询和写入两个步骤是原子的
  • 在锁释放的时候我们判断了KEYS[1]) == ARGV[1],在这里 KEYS[1]是从redis里面取出来的value,ARGV[1]是上文生成的my_random_value。之所以进行以上的判断,是为了保证锁被锁的持有者释放。我们假设不进行这一步校验: 造成这个问题的关键,在于客户端B持有的锁,被客户端A释放了。
    1. 客户端A获取锁,后发线程挂起了。时间大于锁的过期时间。
    2. 锁过期后,客户端B获取锁。
    3. 客户端A恢复以后,处理完相关事件,向redis发起 del命令。锁被释放
    4. 客户端C获取锁。这个时候一个系统中同时两个客户端持有锁。
  • 锁的释放必须使用lua脚本,保证操作的原子性。锁的释放包含了get,判断,del三个步骤。如果不能保证三个步骤的原子性,分布式锁就会有并发问题。

注意了以上细节,一个单redis节点的分布式锁就达成了。

在这个分布式锁中还是存在一个单点的redis。也许你会说,Redis是 master-slave的架构,发生故障的时候切换到slave就好,但是Redis的复制是异步的。

  • 如果在客户端A在master上拿到了锁。
  • 在master将数据同步到slave上之前,master宕机。
  • 客户端B就从slave上又一次拿到了锁。

这样由于Master的宕机,造成了同时多人持有锁。如果你的系统可用接受短时时间内,有多人持有锁。这个简单的方案就能解决问题。

但是如果解决这个问题。Redis的官方提供了一个Redlock的解决方案。

RedLock 的实现

为了解决,Redis单点的问题。Redis的作者提出了RedLock的解决方案。方案非常的巧妙和简洁。 RedLock的核心思想就是,同时使用多个Redis Master来冗余,且这些节点都是完全的独立的,也不需要对这些节点之间的数据进行同步。

假设我们有N个Redis节点,N应该是一个大于2的奇数。RedLock的实现步骤:

  1. 取得当前时间
  2. 使用上文提到的方法依次获取N个节点的Redis锁。
  3. 如果获取到的锁的数量大于 (N/2+1)个,且获取的时间小于锁的有效时间(lock validity time)就认为获取到了一个有效的锁。锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。
  4. 如果获取锁的数量小于 (N/2+1),或者在锁的有效时间(lock validity time)内没有获取到足够的说,就认为获取锁失败。这个时候需要向所有节点发送释放锁的消息。

对于释放锁的实现就很简单了。想所有的Redis节点发起释放的操作,无论之前是否获取锁成功。

同时需要注意几个细节:

  • 重试获取锁的间隔时间应当是一个随机范围而非一个固定时间。这样可以防止,多客户端同时一起向Redis集群发送获取锁的操作,避免同时竞争。同时获取相同数量锁的情况。(虽然概率很低)
  • 如果某master节点故障之后,回复的时间间隔应当大于锁的有效时间。 所以如果恢复的时间将大于锁的有效时间,就可以避免以上情况发生。同时如果性能要求不高,甚至可以开启Redis的持久化选项。
    1. 假设有A,B,C三个Redis节点。
    2. 客户端foo获取到了A、B两个锁。
    3. 这个时候B宕机,所有内存的数据丢失。
    4. B节点恢复。
    5. 这个时候客户端bar重新获取锁,获取到B,C两个节点。
    6. 此时又有两个客户端获取到锁了。

总结

了解了Redis分布式的实现以后,其实觉得大多数的分布式系统其实原理很简单,但是为了保证分布式系统的可靠性需要注意很多的细节,琐碎异常。 RedLock算法实现的分布式锁就是简单高效,思路相当巧妙。 但是RedLock就一定安全么?我还会写一篇文章来讨论这个问题。敬请大家期待。

本文分享自微信公众号 - 犀利豆的技术空间(xilidou1),作者:刁政欣

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-03-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Redis 的基础数据结构(二) 整数集合、跳跃表、压缩列表

    上篇文章写了 Redis 基础数据结构的可变字符串、链表、字典。大家可以点击链接查看。今天我们继续研究 Redis 的基础数据结构。

    用户2060079
  • Redis 的基础数据结构(一) 可变字符串、链表、字典

    这周开始学习 Redis,看看Redis是怎么实现的。所以会写一系列关于 Redis的文章。这篇文章关于 Redis 的基础数据。阅读这篇文章你可以了解:

    用户2060079
  • Redis 命令的执行过程

    之前写了一系列文章,已经很深入的探讨了 Redis 的数据结构,数据库的实现,key的过期策略以及 Redis 是怎么处理事件的。所以距离 Redis 的单机实...

    用户2060079
  • IT创业者如何突破困境

    这是一段关于互联网创业者如何突破困境,选择方向和传播的视频。是由斧子演示制作的,我转载过来的。值得分享!

    用户1631416
  • AI小知识-基于模型的聚类

    用户3578099
  • Hill加密法

    Hill加密法 hill密码是古典密码中常见的多码加密法,是使用数学方法实现的,Hill加密是1929年提出的一种密码体制。 需要的知识:线性代数基础(矩阵乘法...

    安恒网络空间安全讲武堂
  • Zookeeper实现参数的集中式管理【面试+工作】

    应用项目中都会有一些参数,一般的做法通常可以选择将其存储在本地配置文件或者内存变量中;对于集群机器规模不大、配置变更不是特别频繁的情况下,这两种方式都能很好的解...

    奋斗蒙
  • 小程序中rpx与px

    pt与px pt称为逻辑分辨率 pt大小与屏幕分辨率有关系,简单可以理解为长度与视觉单位 px是指物理分辨率,与屏幕尺寸没有关系 小程序中的rpx ? 屏幕分辨...

    听城
  • 九度OJ——1107搬水果

    题目描述: 在一个果园里,小明已经将所有的水果打了下来,并按水果的不同种类分成了若干堆,小明决定把所有的水果合成一堆。每一次合并,小明可以把两堆水果...

    AI那点小事
  • 图像融合之泊松融合(Possion Matting)

    一棹烟波

扫码关注云+社区

领取腾讯云代金券