一、关于分布式锁的两篇文章

文章1
文章2

二、redis分布式锁存在的问题

redis实现分布式锁有很多种方案,比较完善的方案应该是用setNx + lua进行实现。简单实现如下:

  • java代码-加锁,相当于
set lock_key_name unique_value NX PX 5000;
  • lua脚本-解锁,原子性操作
if redis.call("get", KEYS[1] == ARGV[1]) thenreturn redis.call("del", KEYS[1])
elsereturn 0
end
  • 解释
  • NX:仅在不存在 key 的时候才能被执行成功;
  • PX:失效时间,传入 30000,就是 30s 后自动释放锁;
  • unique_value:就是随机值,可以是线程号之类的。主要是为了更安全的释放锁,释放锁的时候使用脚本告诉 Redis: 只有 key 存在并且存储的值和我指定的值一样才能删除成功。
  • 为什么要设置随机值:主要是为了防止锁被其他客户端删除。
  1. 客户端 A 获得了锁,还没有执行结束,但是锁超时自动释放了;
  2. 客户端 B 此时过来,是可以获得锁的,加锁成功;
  3. 此时,客户端 A执行结束了,要去释放锁,如果不对比随机值,就会把客户端 B 的锁给释放了。
  4. 当然前面看过 Redisson 的处理,这个unique_value存放的是 UUID:ThreadId 组合成的一个类似 931573de-903e-42fd-baa7-428ebb7eda80:1 的字符串。

注意:

  1. value需要具有唯一性,可以采用时间戳、uuid或者自增id实现
  2. 客户端在解锁时,需要比较本地内存中的value和redis中的value是否一致,防止误解锁;(case:clientA获取锁lock1,由于clientA执行的时间比较久,导致key=lock1已经过期,redis实例会移除该key;clientB获取相同的锁lock1,clientB正在占有锁并执行业务,此时clientA业务已经执行完毕,准备释放锁;如果没有比较value的逻辑,那么clientA会把clientB持有的锁释放掉,这个显然不行的,由于value值不同,那么clientA释放锁的时候只会释放自己加的锁,不会误释放别的客户端加的锁)。

注意,不能一味认为锁过期的时间应该比key的expire要长,因为接下来要介绍的redisson框架中有续期机制(看门狗机制),该机制的核心就是:如果线程仍旧没有执行完,那么redisson会自动给redis中的目标key延长超时时间

在分布式系统中,为了避免单点故障,提高可靠性,redis都会采用主从架构,当主节点挂了后,从节点会作为主继续提供服务。该种方案能够满足大多数的业务场景,但是对于要求强一致性的场景如交易,该种方案还是有漏洞的,原因如下:

  1. redis主从架构采用的是异步复制,当master节点拿到了锁,但是锁还未同步到slave节点,此时master节点挂了,发生故障转移,slave节点被选举为master节点,丢失了锁。这样其他线程就能够获取到该锁,显然是有问题的。

因此,上述基于redis实现的分布式锁只是满足了AP,并没有满足C

三、redlock

  1. redlock出现的背景:当锁遇到故障转移
    单实例肯定不是很可靠吧?加锁成功之后,结果 Redis 服务宕机了,就凉了。这时候会提出来将 Redis 主从部署。即使是主从,也是存在巧合的!
  • 主从结构中存在明显的竞态:
  1. 客户端 A 从 master 获取到锁
  2. 在 master 将锁同步到 slave 之前,master 宕掉了。
  3. slave 节点被晋级为 master 节点
  4. 客户端 B 取得了同一个资源被客户端 A 已经获取到的另外一个锁。安全失效!

有时候程序就是这么巧,比如说正好一个节点挂掉的时候,多个客户端同时取到了锁。如果你可以接受这种小概率错误,那用这个基于复制的方案就完全没有问题。

  • 如果使用集群呢

我们知道对集群进行加锁的时候,其实是通过 CRC16 的 hash 函数来对 key 进行取模,将结果路由到预先分配过 slot 的相应节点上。发现其实还是发到单个节点上的!

  • 正是因为上述redis分布式锁存在的一致性问题,redis作者提出了一个更加高级的基于redis实现的分布式锁——RedLock。原文可参考 Distributed locks with Redis,也可以参考这篇文章。
  1. RedLock是什么

RedLock是基于redis实现的分布式锁,它能够保证以下特性:

  • 互斥性:在任何时候,只能有一个客户端能够持有锁;
  • 避免死锁:当客户端拿到锁后,即使发生了网络分区或者客户端宕机,也不会发生死锁;(利用key的存活时间)
  • 容错性:只要多数节点的redis实例正常运行,就能够对外提供服务,加锁或者释放锁;

而非redLock是无法满足互斥性的,上面已经阐述过了原因。

  1. RedLock算法

假设有N个redis的master节点,这些节点是相互独立的(不需要主从或者其他协调的系统)。N推荐为奇数。具体算法如下:

  • 注意:为什么N推荐为奇数呢
  • 原因1:本着最大容错的情况下,占用服务资源最少的原则,2N+1和2N+2的容灾能力是一样的,所以采用2N+1;比如,5台服务器允许2台宕机,容错性为2,6台服务器也只能允许2台宕机,容错性也是2,因为要求超过半数节点存活才OK。

  • 原因2:假设有6个redis节点,client1和client2同时向redis实例获取同一个锁资源,那么可能发生的结果是——client1获得了3把锁,client2获得了3把锁,由于都没有超过半数,那么client1和client2获取锁都失败,对于奇数节点是不会存在这个问题。

参考文章

  1. 失败时重试
  • 当客户端无法获取到锁时,应该随机延时后进行重试,防止多个客户端在同一时间抢夺同一资源的锁(会导致脑裂,最终都不能获取到锁)。客户端获得超过半数节点的锁花费的时间越短,那么脑裂的概率就越低。所以,理想的情况下,客户端最好能够同时(并发)向所有redis发出set命令。

  • 当客户端从多数节点获取锁失败时,应该尽快释放已经成功获取的锁,这样其他客户端不需要等待锁过期后再获取。(如果存在网络分区,客户端已经无法和redis进行通信,那么此时只能等待锁过期后自动释放)

不明白为什么会发生脑裂???

  1. 释放锁

向所有redis实例发送释放锁命令即可,不需要关心redis实例有没有成功上锁。

redisson在加锁的时候,key=lockName, value=uuid + threadID,采用set结构存储,并包含了上锁的次数(支持可重入);解锁的时候通过hexists判断key和value是否存在,存在则解锁;这里不会出现误解锁

  1. 性能、 崩溃恢复和redis同步
  • 如何提升分布式锁的性能?以每分钟执行多少次acquire/release操作作为性能指标,一方面通过增加redis实例可用降低响应延迟,另一方面,使用非阻塞模型,一次发送所有的命令,然后异步读取响应结果,这里假设客户端和redis之间的RTT差不多。

  • 如果redis没用使用备份,redis重启后,那么会丢失锁,导致多个客户端都能获取到锁。通过AOF持久化可以缓解这个问题。redis key过期是unix时间戳,即便是redis重启,那么时间依然是前进的。但是,如果是断电呢?redis在启动后,可能就会丢失这个key(在写入或者还未写入磁盘时断电了,取决于fsync的配置),如果采用fsync=always,那么会极大影响性能。如何解决这个问题呢?可以让redis节点重启后,在一个TTL时间段内,对客户端不可用即可。

四、redlock已经在最新本的redisson中被弃用了

看着 RedLock 好像是解决问题了:

  1. 客户端 A 锁住了集群的大多数(一半以上);
  2. 客户端 B 也要锁住大多数;
  3. 这里肯定会冲突,所以 客户端 B 加锁失败。

那实际解决问题了么?推荐大家阅读两篇文章:

  • Martin Kleppmann:How to do distributed locking

  • Salvatore(Redis 作者):Is Redlock safe?

最终,两方各持己见,没有得出结论。具体的源码分析弃用原因,可以参考这篇文章

五、结论

Redisson RedLock 是基于联锁 MultiLock 实现的,但是使用过程中需要自己判断 key 落在哪个节点上,对使用者不是很友好。Redisson RedLock 已经被弃用,直接使用普通的加锁即可,会基于 wait 机制将锁同步到从节点,但是也并不能保证一致性。仅仅是最大限度的保证一致性。

基于MultiLock 实现的分布式锁

redisson redlock(基于redisson框架和redis集群使用分布式锁)相关推荐

  1. Redis集群及分布式锁

    1.无中心化集群 2.redis集群搭建 1.进入/root/myredis文件目录 cd /root/myredis 2.进入redis6378.conf,并添加一下内容 cluster-enabl ...

  2. 基于redis集群的分布式锁redlock

    Redis 作者为了解决因为主备切换.脑裂导致 Redis 单集群分布式锁不安全的问题,提出了 redlock 算法,下面是针对 文章 的翻译和一些自我理解. 一.安全性和可用性保证 用三个属性来建模 ...

  3. 分布式锁和数据一致性的讨论——redis集群做分布式锁的风险

    文章目录 写在前面 分布式锁的三个属性 分布式锁就⼀定要实现这三个属性吗? 实现容错性 方法一:基于多个 Redis 节点实现分布式锁 问题一:进程可能会被挂起,直到锁的 TTL 过期 问题二:墙上时 ...

  4. 基于虚拟机高可用redis集群搭建

    redis集群搭建 介绍 刚开始学习 redis集群搭建可能会被繁琐的配置劝退,但重复配置是我们敲代码路上最平坦的一条路了,希望晨雨和大家以后都有足够的耐心,学好技术,装好B格.本文基于redis-5 ...

  5. 基于Centos7系统搭建Redis集群之主从复制(新手教程)

    最近没多少事,就想着搭建个redis集群玩玩,毕竟听起来也是很高大上的东西,但是经过自己的断断续续的搭建,也感觉不是那么难,肯定也只是刚刚入门,搭建的东西也比较简单,并没有触及到里面更深入的内容: 因 ...

  6. ELK 集群 + Redis 集群 + Nginx ,分布式的实时日志(数据)搜集和分析的监控系统搭建,简单上手使用

    简述 ELK实际上是三个工具的集合,ElasticSearch + Logstash + Kibana,这三个工具组合形成了一套实用.易用的监控架构,很多公司利用它来搭建可视化的海量日志分析平台. 官 ...

  7. Redis官网——如何利用Redis做服务器集群的分布式锁

    2019独角兽企业重金招聘Python工程师标准>>> 链接:http://redis.io/topics/distlock 原理很简单,一段时间内轮询加锁的key 重点,不同语言的 ...

  8. 基于Codis的Redis集群部署

    Codis是基于代理的高性能Redis集群方案,使用Go语言进行开发,现在在在豌豆荚及其它公司内已经广泛使用,当然也包括我们公司. Codis与常见的Redis集群方案对比. 在搭建的时候,个人觉得R ...

  9. Redis集群搭建(基于6.2.6版本)

    前言 Redis 在我们工作中使用非常广泛,之前在掘金平台上写过一篇Redis集群的搭建,但是是基于4.0.14版本的,使用的是redis-trib.rb进行搭建的,Redis5之后 推荐使用 red ...

最新文章

  1. Socket程序从windows移植到linux下需要注意的
  2. 集成学习、Bagging算法、Bagging+Pasting、随机森林、极端随机树集成(Extra-trees)、特征重要度、包外评估
  3. xcode symbol(s) not found for architecture i386错误解决方法
  4. 图像和流媒体 -- 详解YUV数据格式
  5. 实时监控后台数据 vue_实时数据监控,快速掌握B站爆款视频热度走向
  6. 锁底层之内存屏障与原语指令
  7. 在博客中插入emoji表情
  8. 学习minix 3(未完成)
  9. HDU-3664 Permutation Counting(DP)
  10. 有关C#中的引用类型的内存问题
  11. Nginx ~模块详解~
  12. 第六章 第一个Linux驱动程序:统计单词个数
  13. ps新手秒变大师必备的Ps插件全在这!(mac版本)
  14. 极品飞车ol服务器连接不稳定,极品飞车OL常见客户端问题有哪些_客户端问题解决方法_3DM网游...
  15. JavaPDF文件转图片
  16. 判断二极管导通例题_如何判断开关电源变压器的好坏
  17. 网易2017校招合唱团
  18. Excel房贷计算表(商贷)1/2
  19. python数据挖掘14讲_python/pandas数据挖掘(十四)-groupby,聚合,分组级运算
  20. 建造者模式(二):游戏角色设计的建造者模式解决方案

热门文章

  1. 删除指定路径下的文件
  2. .NET实现应用程序登录Web页
  3. js面向对象编程:this究竟代表什么?第二篇
  4. zookeeper启动失败排查
  5. 【转】如何设计动态(不定)字段的产品数据库表?
  6. JTS基本概念和使用
  7. JQuery图片切换 Win8 Metro风格Banner
  8. 单击触发jquery.autocomplete的两种方法
  9. 读《程序设计实践》之一 风格
  10. 时频分析:短时傅里叶实现(3)