我曾跨入山巅,也曾步入低谷,两者都使我受益良多! I've been to the top, and I've fallen to the bottom, and I've learned a lot from both!
1、概述
在这个技术不断更新迭代的状况下,分布式这个概念,在企业中的权重愈来愈高!谈及分布式时,不可避免必定会提到分布式锁,现阶段分布式锁的实现方式主流的有三种实现方式, Zookeeper
、DB
、Redis
,咱们本篇文章以Redis为例!java
从咱们的角度来看,这三个属性是有效使用分布式锁所需的最低保证。web
-
安全特性:互斥。在任何给定时刻,只有一个客户端能够持有锁。 -
活力属性:无死锁。最终,即便锁定资源的客户端崩溃或分区,也始终能够得到锁。 -
活动性:容错能力。只要大多数Redis节点都处于运行状态,客户端就能够获取和释放锁。
2、redis多节点实现分布式锁带来的挑战
咱们使用Redis锁定资源的最简单方法是:redis
-
在实例中建立锁。 -
锁一般使用Redis过时功能在有限时间存在,所以最终将被释放,最终超过给按期限会被删除。 -
当客户端须要释放资源时,它将删除锁。
乍一看,彷佛并无什么问题。可是不妨咱们深究一下,这种实现方案在redis单机环境下彷佛并无什么问题!可是若是节点宕了呢?好吧,那么让咱们添加一个slave
节点!若是主服务器宕机了,就使用这个节点!可是咱们不妨来看看她真的能保证可用吗?算法
在谈论这个的致命缺陷时,咱们须要了解一个知识点,Redis复制是异步的。
数据库
-
客户端A获取主服务器中的锁。 -
在将锁复制传输到从机以前,主机崩溃。 -
slave
晋升为master
。 -
客户端B获取锁,由于从机并无该锁的对象,获取成功!
显然,这样是不对的,主节点由于没来得及同步数据就宕机了,因此从节点没有该数据,从而形成分布式锁的失效,那么做者antirez
的观点是如何解决这个呢?安全
3、Redlock算法
做者认为,咱们应该使用多个Redis
,这些节点是彻底独立的,不须要使用复制或者任何协调数据的系统,多个redis系统获取锁的过程就变成了以下步骤:服务器
-
以毫秒为单位获取当前的服务器时间 -
尝试使用相同的key和随机值来获取锁,对每个机器获取锁时都应该有一个超时时间,好比锁的过时时间为10s那么获取单个节点锁的超时时间就应该为5到50毫秒左右,他这样作的目的是为了保证客户端与故障的机器链接,耗费多余的时间!超时间时间内未获取数据就放弃该节点,从而去下一个节点获取,直至将全部节点所有获取一遍! -
获取完成后,获取当前时间减去步骤一获取的时间,当且仅当客户端半数以上获取成功且获取锁的时间小于锁额超时时间,则证实该锁生效! -
获取锁以后,锁的超时时间等于 设置的有效时间-获取锁花费的时间
-
若是 获取锁的机器不知足半数以上,或者锁的超时时间计算完毕后为负数 等异常操做,则系统会尝试解锁全部实例,即便有些实例没有获取锁成功,依旧会被尝试解锁! -
释放锁,只需在全部实例中释放锁,不管客户端是否定为它可以成功锁定给定的实例。
4、可是Redlock真可以解决问题吗?
Martin Kleppmann发表文章任务,Redlock并不能保证该锁的安全性!微信
他认为锁的用途无非两种网络
提高效率,用锁来保证一个任务没有必要被执行两次。好比(很昂贵的计算) 保证正确,使用锁来保证任务按照正常的步骤执行,防止两个节点同时操做一份数据,形成文件冲突,数据丢失。
对于第一种缘由,咱们对锁是有必定宽容度的,就算发生了两个节点同时工做,对系统的影响也仅仅是多付出了一些计算的成本,没什么额外的影响。这个时候 使用单点的 Redis 就能很好的解决问题,没有必要使用RedLock,维护那么多的Redis实例,提高系统的维护成本。app
1.分布式锁的超时性,所带来的缺点
可是对于第二种场景来讲,就比较慎重了,由于极可能涉及到一些金钱交易,若是锁定失败,而且两个节点同时处理同一数据,则结果将致使文件损坏,数据丢失,永久性不一致,或者金钱方面的损失!
咱们假设一种场景,咱们有两个客户端,每个客户端必须拿到锁以后才能去保存数据到数据库,咱们使用RedLock算法实现会出现什么问题呢?RedLock中,为了防止死锁,锁是具备过时时间的,可是Martin
认为这是不安全的!该流程图相似于这样!

客户端1获取到锁成功后,开始执行,执行到一半系统发生Full GC ,系统服务被挂起,过段时间锁超时了。
客户端2等待客户端1的锁超时后,成功的获取到锁,开始执行入库操做,完成后,客户端1完成了Full GC,又作了一次入库操做!这是不安全的!如何解决呢?
Martin
提出来一种相似乐观锁的实现机制,示例图以下:

客户端1长时间被挂起后,客户端2获取到锁,开始写库操做,同时携带令牌 34
,写库完成后,客户端1苏醒,开始进行入库操做,可是由于携带的令牌为33 小于最新令牌,该次提交就被拒绝!
这个想法听起来彷佛时很完备的思路,这样即便系统由于某些缘由被挂起,数据也可以被正确的处理。可是仔细想一下:
-
若是仅当您的令牌大于全部过去的令牌时,数据存储区才能始终接受写入,则它是可线性化的存储区,至关与使用数据库来实现一个 分布式锁系统,那么RedLock的做用就变的微乎其微!甚至不在须要使用redis保证分布式锁!
2.RedLock对于系统时钟强依赖
回想一下Redlock算法
获取锁的几个步骤,你会发现锁的有效性是与当前的系统时钟强依赖,咱们假设:
咱们有,A B C D E 五个redis节点:
-
客户端1获取节点A,B,C的锁定。因为网络问题,没法访问D和E。 -
节点C上的时钟向前跳,致使锁过时。 -
客户端2获取节点C,D,E的锁定。因为网络问题,没法访问A和B。 -
如今,客户1和2都认为他们持有该锁。
若是C在将锁持久保存到磁盘以前崩溃并当即从新启动,则可能会发生相似的问题。
Martin认为系统时间的阶跃主要来自两个方面(以及做者给出的解决方案):
-
人为修改。 -
对于人为修改,能说啥呢?人要搞破坏没办法避免。 -
从NTP服务收到了一个跳跃时时钟更新。 -
NTP受到一个阶跃时钟更新,对于这个问题,须要经过运维来保证。须要将阶跃的时间更新到服务器的时候,应当采起小步快跑的方式。屡次修改,每次更新时间尽可能小。
3.基于程序语言弥补分布式锁的超时性所带来的缺点
咱们回顾 1 观点,深究抽象出现这个缺陷的根本缘由,就是为了解决因为系统宕机带来的锁失效而给锁强加了一个失效时间,异常状况下,程序(业务)执行的时间大于锁失效时间从而形成的一系列的问题,咱们可否从这方面去考虑,从而用程序来解决这个样一个死局 呢?
既然是由于锁的失效时间小于业务时间,那么咱们想办法保证业务程序执行时间绝对小于锁超时时间不久解决了?
java语言中redisson
实现了一种保证锁失效时间绝对大于业务程序执行时间的机制。官方叫作看门狗机制(Watchdog),他的主要原理是,在程序成功获取锁以后,会fork一条子线程去不断的给该锁续期,直至该锁释放为止!他的原理图大概以下所示:

redisson使用守护线程来进行锁的续期,(守护线程的做用:当主线程销毁,会和主线程一块儿销毁。)防止程序宕机后,线程依旧不断续命,形成死锁!
另外,Redisson还实现而且优化了 RedLock算法、公平锁、可重入锁、连锁等操做,使Redis分布式锁的实现方式更加简便高效!
才疏学浅,若是文章中理解有误,欢迎大佬们私聊指正!欢迎关注做者的公众号,一块儿进步,一块儿学习!
本文分享自微信公众号 - JAVA程序狗(javacxg)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。