自己怎么做免费网站,南通营销网站建设,网站建设采用的技术,常州建网站分布式锁#xff08;基于redis和zookeeper#xff09;详解 https://blog.csdn.net/a15835774652/article/details/81775044 为什么写这篇文章#xff1f; 目前网上大部分的基于zookeeper#xff0c;和redis的分布式锁的文章都不够全面。要么就是特意避开集群的情况#xf… 分布式锁基于redis和zookeeper详解 https://blog.csdn.net/a15835774652/article/details/81775044 为什么写这篇文章 目前网上大部分的基于zookeeper和redis的分布式锁的文章都不够全面。要么就是特意避开集群的情况要么就是考虑不全读者看着还是一脸迷茫。坦白说这种老题材很难写出新创意博主内心战战兢兢如履薄冰文中有什么不严谨之处欢迎批评。博主的这篇文章不上代码只讲分析。(1)在redis方面有开源redisson的jar包供你使用。(2)在zookeeper方面有开源的curator的jar包供你使用因为已经有开源jar包供你使用没有必要再去自己封装一个大家出门百度一个api即可不需要再罗列一堆实现代码。需要说明的是Google有一个名为Chubby的粗粒度分布锁的服务然而Google Chubby并不是开源的我们只能通过其论文和其他相关的文档中了解具体的细节。值得庆幸的是Yahoo借鉴Chubby的设计思想开发了zookeeper并将其开源因此本文不讨论Chubby。至于Tair是阿里开源的一个分布式K-V存储方案。我们在工作中基本上redis使用的比较多讨论Tair所实现的分布式锁不具有代表性。因此主要分析的还是redis和zookeeper所实现的分布式锁。 文章结构 本文借鉴了两篇国外大神的文章redis的作者antirez的《Is Redlock safe?》以及分布式系统专家Martin的《How to do distributed locking》再加上自己微薄的见解从而形成这篇文章文章的目录结构如下:(1)为什么使用分布式锁(2)单机情形比较(3)集群情形比较(4)锁的其他特性比较 正文 先上结论:zookeeper可靠性比redis强太多只是效率低了点如果并发量不是特别大追求可靠性首选zookeeper。为了效率则首选redis实现。 为什么使用分布式锁 使用分布式锁的目的无外乎就是保证同一时间只有一个客户端可以对共享资源进行操作。但是Martin指出根据锁的用途还可以细分为以下两类(1)允许多个客户端操作共享资源这种情况下对共享资源的操作一定是幂等性操作无论你操作多少次都不会出现不同结果。在这里使用锁无外乎就是为了避免重复操作共享资源从而提高效率。(2)只允许一个客户端操作共享资源这种情况下对共享资源的操作一般是非幂等性操作。在这种情况下如果出现多个客户端操作共享资源就可能意味着数据不一致数据丢失。 第一回合单机情形比较 (1)Redis先说加锁根据redis官网文档的描述使用下面的命令加锁 SET resource_name my_random_value NX PX 30000 my_random_value是由客户端生成的一个随机字符串相当于是客户端持有锁的标志 NX表示只有当resource_name对应的key值不存在的时候才能SET成功相当于只有第一个请求的客户端才能获得锁 PX 30000表示这个锁有一个30秒的自动过期时间。 至于解锁为了防止客户端1获得的锁被客户端2给释放,采用下面的Lua脚本来释放锁 if redis.call(get,KEYS[1]) ARGV[1] thenreturn redis.call(del,KEYS[1])
elsereturn 0
end在执行这段LUA脚本的时候KEYS[1]的值为resource_nameARGV[1]的值为my_random_value。原理就是先获取锁对应的value值保证和客户端穿进去的my_random_value值相等这样就能避免自己的锁被其他人释放。另外采取Lua脚本操作保证了原子性.如果不是原子性操作则有了下述情况出现 分析:这套redis加解锁机制看起来很完美然而有一个无法避免的硬伤就是过期时间如何设置。如果客户端在操作共享资源的过程中因为长期阻塞的原因导致锁过期那么接下来访问共享资源就不安全。可是有的人会说 那可以在客户端操作完共享资源后判断锁是否依然归该客户端所有如果依然归客户端所有则提交资源释放锁。若不归客户端所有则不提交资源啊. OK,这么做只能降低多个客户端操作共享资源发生的概率并不能解决问题。为了方便读者理解博主举一个业务场景。业务场景:我们有一个内容修改页面为了避免出现多个客户端修改同一个页面的请求采用分布式锁。只有获得锁的客户端才能修改页面。那么正常修改一次页面的流程如下图所示 注意看上面的步骤(3)--步骤(4.1)并不是原子性操作。也就说你可能出现在步骤(3)的时候返回的是有效这个标志位但是在传输过程中因为延时等原因在步骤(4.1)的时候锁已经超时失效了。那么这个时候锁就会被另一个客户端锁获得。就出现了两个客户端共同操作共享资源的情况。大家可以思考一下无论你如何采用任何补偿手段你都只能降低多个客户端操作共享资源的概率而无法避免。例如你在步骤(4.1)的时候也可能发生长时间GC停顿然后在停顿的时候锁超时失效从而锁也有可能被其他客户端获得。这些大家可以自行思考推敲。(2)zookeeper先简单说下原理根据网上文档描述zookeeper的分布式锁原理是利用了临时节点(EPHEMERAL)的特性。 当znode被声明为EPHEMERAL的后如果创建znode的那个客户端崩溃了那么相应的znode会被自动删除。这样就避免了设置过期时间的问题。 客户端尝试创建一个znode节点比如/lock。那么第一个客户端就创建成功了相当于拿到了锁而其它的客户端会创建失败znode已存在获取锁失败。 分析:这种情况下虽然避免了设置了有效时间问题然而还是有可能出现多个客户端操作共享资源的。大家应该知道zookeeper如果长时间检测不到客户端的心跳的时候(Session时间)就会认为Session过期了那么这个Session所创建的所有的ephemeral类型的znode节点都会被自动删除。这种时候会有如下情形出现 如上图所示客户端1发生GC停顿的时候zookeeper检测不到心跳也是有可能出现多个客户端同时操作共享资源的情形。当然你可以说我们可以通过JVM调优避免GC停顿出现。但是注意了我们所做的一切只能尽可能避免多个客户端操作共享资源无法完全消除。 第二回合集群情形比较 我们在生产中一般都是用集群情形所以第一回合讨论的单机情形。算是给大家热热身。(1)redis为了redis的高可用一般都会给redis的节点挂一个slave,然后采用哨兵模式进行主备切换。但由于Redis的主从复制replication是异步的这可能会出现在数据同步过程中master宕机slave来不及同步数据就被选为master从而数据丢失。具体流程如下所示: (1)客户端1从Master获取了锁。 (2)Master宕机了存储锁的key还没有来得及同步到Slave上。 (3)Slave升级为Master。 (4)客户端2从新的Master获取到了对应同一个资源的锁。 为了应对这个情形 redis的作者antirez提出了RedLock算法步骤如下(该流程出自官方文档)假设我们有N个master节点(官方文档里将N设置成5其实大等于3就行) (1)获取当前时间单位是毫秒。 (2)轮流用相同的key和随机值在N个节点上请求锁在这一步里客户端在每个master上请求锁时会有一个和总的锁释放时间相比小的多的超时时间。比如如果锁自动释放时间是10秒钟那每个节点锁请求的超时时间可能是5-50毫秒的范围这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间如果一个master节点不可用了我们应该尽快尝试下一个master节点。 (3)客户端计算第二步中获取锁所花的时间只有当客户端在大多数master节点上成功获取了锁在这里是3个而且总共消耗的时间不超过锁释放时间这个锁就认为是获取成功了。 (4)如果锁获取成功了那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。 (5)如果锁获取失败了不管是因为获取成功的锁不超过一半N/21)还是因为总消耗时间超过了锁释放时间客户端都会到每个master节点上释放锁即便是那些他认为没有获取成功的锁。 分析:RedLock算法细想一下还存在下面的问题节点崩溃重启会出现多个客户端持有锁假设一共有5个Redis节点A, B, C, D, E。设想发生了如下的事件序列(1)客户端1成功锁住了A, B, C获取锁成功但D和E没有锁住。(2)节点C崩溃重启了但客户端1在C上加的锁没有持久化下来丢失了。(3)节点C重启后客户端2锁住了C, D, E获取锁成功。这样客户端1和客户端2同时获得了锁针对同一资源。 为了应对节点重启引发的锁失效问题redis的作者antirez提出了延迟重启的概念即一个节点崩溃后先不立即重启它而是等待一段时间再重启等待的时间大于锁的有效时间。采用这种方式这个节点在重启前所参与的锁都会过期它在重启后就不会对现有的锁造成影响。这其实也是通过人为补偿措施降低不一致发生的概率。时间跳跃问题(1)假设一共有5个Redis节点A, B, C, D, E。设想发生了如下的事件序列(2)客户端1从Redis节点A, B, C成功获取了锁多数节点。由于网络问题与D和E通信失败。(3)节点C上的时钟发生了向前跳跃导致它上面维护的锁快速过期。客户端2从Redis节点C, D, E成功获取了同一个资源的锁多数节点。客户端1和客户端2现在都认为自己持有了锁。 为了应对始终跳跃引发的锁失效问题redis的作者antirez提出了应该禁止人为修改系统时间使用一个不会进行“跳跃”式调整系统时钟的ntpd程序。这也是通过人为补偿措施降低不一致发生的概率。超时导致锁失效问题RedLock算法并没有解决操作共享资源超时导致锁失效的问题。回忆一下RedLock算法的过程如下图所示 如图所示我们将其分为上下两个部分。对于上半部分框图里的步骤来说无论因为什么原因发生了延迟RedLock算法都能处理客户端不会拿到一个它认为有效实际却失效的锁。然而对于下半部分框图里的步骤来说如果发生了延迟导致锁失效都有可能使得客户端2拿到锁。因此RedLock算法并没有解决该问题。(2)zookeeperzookeeper在集群部署中zookeeper节点数量一般是奇数且一定大等于3。我们先回忆一下zookeeper的写数据的原理如图所示这张图懒得画直接搬其他文章的了。 那么写数据流程步骤如下1.在Client向Follwer发出一个写的请求2.Follwer把请求发送给Leader3.Leader接收到以后开始发起投票并通知Follwer进行投票4.Follwer把投票结果发送给Leader只要半数以上返回了ACK信息就认为通过5.Leader将结果汇总后如果需要写入则开始写入同时把写入操作通知给Leader然后commit;6.Follwer把请求结果返回给Client还有一点zookeeper采取的是全局串行化操作OK,现在开始分析集群同步client给Follwer写数据可是Follwer却宕机了会出现数据不一致问题么不可能这种时候client建立节点失败根本获取不到锁。client给Follwer写数据Follwer将请求转发给Leader,Leader宕机了会出现不一致的问题么不可能这种时候zookeeper会选取新的leader,继续上面的提到的写流程。总之采用zookeeper作为分布式锁你要么就获取不到锁一旦获取到了必定节点的数据是一致的不会出现redis那种异步同步导致数据丢失的问题。时间跳跃问题不依赖全局时间怎么会存在这种问题超时导致锁失效问题不依赖有效时间怎么会存在这种问题 第三回合锁的其他特性比较 (1)redis的读写性能比zookeeper强太多如果在高并发场景中使用zookeeper作为分布式锁那么会出现获取锁失败的情况存在性能瓶颈。(2)zookeeper可以实现读写锁redis不行。(3)zookeeper的watch机制,客户端试图创建znode的时候发现它已经存在了这时候创建失败,那么进入一种等待状态当znode节点被删除的时候zookeeper通过watch机制通知它这样它就可以继续完成创建操作获取锁。这可以让分布式锁在客户端用起来就像一个本地的锁一样加锁失败就阻塞住直到获取到锁为止。这套机制redis无法实现 总结 OK正文啰嗦了一大堆。其实只是想表明两个观点无论是redis还是zookeeper其实可靠性都存在一点问题。但是zookeeper的分布式锁的可靠性比redis强太多但是,zookeeper读写性能不如redis,存在着性能瓶颈。大家在生产上使用可自行进行评估使用。 转载于:https://www.cnblogs.com/handsome1013/p/11430009.html