加入收藏 | 设为首页 | 会员中心 | 我要投稿 温州站长网 (https://www.0577zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 酷站推荐 > 酷站 > 正文

分布式之抉择分布式锁——多个方位比较

发布时间:2019-02-20 10:50:50 所属栏目:酷站 来源:谢涛
导读:引言 为什么写这篇文章? 目前网上大部分的基于zookpeer,和redis的分布式锁的文章都不够全面。要么就是特意避开集群的情况,要么就是考虑不全,读者看着还是一脸迷茫。坦白说,这种老题材,很难写出新创意,博主内心战战兢兢,如履薄冰,文中有什么不严谨

  引言

  为什么写这篇文章?

  目前网上大部分的基于zookpeer,和redis的分布式锁的文章都不够全面。要么就是特意避开集群的情况,要么就是考虑不全,读者看着还是一脸迷茫。坦白说,这种老题材,很难写出新创意,博主内心战战兢兢,如履薄冰,文中有什么不严谨之处,欢迎批评。博主的这篇文章,不上代码,只讲分析。

  (1)在redis方面,有开源redisson的jar包供你使用。

  (2)在zookpeer方面,有开源的curator的jar包供你使用。

  因为已经有开源jar包供你使用,没有必要再去自己封装一个,大家出门百度一个api即可,不需要再罗列一堆实现代码。需要说明的是,Google有一个名为Chubby的粗粒度分布锁的服务,然而,Google Chubby并不是开源的,我们只能通过其论文和其他相关的文档中了解具体的细节。值得庆幸的是,Yahoo!借鉴Chubby的设计思想开发了Zookeeper,并将其开源,因此本文不讨论Chubby。至于Tair,是阿里开源的一个分布式K-V存储方案。我们在工作中基本上redis使用的比较多,讨论Tair所实现的分布式锁,不具有代表性。

  因此,主要分析的还是redis和zookpper所实现的分布式锁。

  文章结构

  本文借鉴了两篇国外大神的文章,redis的作者antirez的《Is Redlock safe?》以及分布式系统专家Martin的《How to do distributed locking》,再加上自己微薄的见解从而形成这篇文章,文章的目录结构如下:

  (1)为什么使用分布式锁

  (2)单机情形比较

  (3)集群情形比较

  (4)锁的其他特性比较

  正文

  先上结论:zookpper可靠性比redis强太多,只是效率低了点,如果并发量不是特别大,追求可靠性,首选zookpeer。为了效率,则首选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] then

    return redis.call("del",KEYS[1])

  else

    return 0

  end

  在执行这段LUA脚本的时候,KEYS[1]的值为resource_name,ARGV[1]的值为my_random_value。原理就是先获取锁对应的value值,保证和客户端穿进去的my_random_value值相等,这样就能避免自己的锁被其他人释放。另外,采取Lua脚本操作保证了原子性.如果不是原子性操作,则有了下述情况出现

分布式之抉择分布式锁——多个方位比较

  分析:这套redis加解锁机制看起来很完美,然而有一个无法避免的硬伤,就是过期时间如何设置。如果客户端在操作共享资源的过程中,因为长期阻塞的原因,导致锁过期,那么接下来访问共享资源就不安全。可是,有的人会说

  那可以在客户端操作完共享资源后,判断锁是否依然归该客户端所有,如果依然归客户端所有,则提交资源,释放锁。若不归客户端所有,则不提交资源啊.

  OK,这么做,只能降低多个客户端操作共享资源发生的概率,并不能解决问题。为了方便读者理解,博主举一个业务场景。

  业务场景:我们有一个内容修改页面,为了避免出现多个客户端修改同一个页面的请求,采用分布式锁。只有获得锁的客户端,才能修改页面。那么正常修改一次页面的流程如下图所示

分布式之抉择分布式锁——多个方位比较

  注意看,上面的步骤(3)-->步骤(4.1)并不是原子性操作。也就说,你可能出现在步骤(3)的时候返回的是有效这个标志位,但是在传输过程中,因为延时等原因,在步骤(4.1)的时候,锁已经超时失效了。那么,这个时候锁就会被另一个客户端锁获得。就出现了两个客户端共同操作共享资源的情况。

  大家可以思考一下,无论你如何采用任何补偿手段,你都只能降低多个客户端操作共享资源的概率,而无法避免。例如,你在步骤(4.1)的时候也可能发生长时间GC停顿,然后在停顿的时候,锁超时失效,从而锁也有可能被其他客户端获得。这些大家可以自行思考推敲。

  (2)zookpeer

  先简单说下原理,根据网上文档描述,zookpeer的分布式锁原理是利用了临时节点(EPHEMERAL)的特性。

  ·当znode被声明为EPHEMERAL的后,如果创建znode的那个客户端崩溃了,那么相应的znode会被自动删除。这样就避免了设置过期时间的问题。

  ·客户端尝试创建一个znode节点,比如/lock。那么第一个客户端就创建成功了,相当于拿到了锁;而其它的客户端会创建失败(znode已存在),获取锁失败。

  分析:这种情况下,虽然避免了设置了有效时间问题,然而还是有可能出现多个客户端操作共享资源的。大家应该知道,Zookpeer如果长时间检测不到客户端的心跳的时候(Session时间),就会认为Session过期了,那么这个Session所创建的所有的ephemeral类型的znode节点都会被自动删除。这种时候会有如下情形出现

分布式之抉择分布式锁——多个方位比较

  如上图所示,客户端1发生GC停顿的时候,zookpeer检测不到心跳,也是有可能出现多个客户端同时操作共享资源的情形。当然,你可以说,我们可以通过JVM调优,避免GC停顿出现。但是注意了,我们所做的一切,只能尽可能避免多个客户端操作共享资源,无法完全消除。

  第二回合,集群情形比较

  我们在生产中,一般都是用集群情形,所以第一回合讨论的单机情形。算是给大家热热身。

  (1)redis

(编辑:温州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

推荐文章
    热点阅读