• ZooKeeper分布式锁的实现原理(转)


    add by zhj:文章写得非常好,之前我以为用zookeeper实现分布式锁的方案跟Redis差不多,就是并发创建一个Znode节点,如果成功就获取锁,失败,就监听这个Znode节点的删除操作,当主动删除或因session断开而删除该Znode时,等待的机器节点再去获取锁。

    但这样做有一个问题:当Znode节点删除时,若等待的机器节点很多,同时去创建会增加Zookeeper和机器节点的负载,这称为“羊群效应”。好的解决办法是:删除Znode节点时,只通知一个等待机器。其实这跟Java Condition中的notify()和notifyAll()是一个道理。但Zookeeper没有类似这种notify()机制,只通知一个机器。但它的其它解决方案,将Znode定义为临时顺序类型,每个机器创建Znode前缀相同,但后缀序号递增,每个机器节点只监听它前面的那一个Znode节点。  

    另外,我理解当机器没有获取到锁时,会阻塞当前线程,比如用Object.await()等。然后,另起一个线程接收Zookeeper删除Znode通知,当收到时,去检查自己是否是最小Znode节点,如果是,那获取成功,然后notify()主线程,解除阻塞。是否这样实现要看Zookeeper客户端的源码了

    原文:https://juejin.cn/post/6844903729406148622#comment

    一、写在前面

    之前写过一篇文章(《拜托,面试请不要再问我Redis分布式锁的实现原理》),给大家说了一下Redisson这个开源框架是如何实现Redis分布式锁原理的,这篇文章再给大家聊一下ZooKeeper实现分布式锁的原理。

    同理,我是直接基于比较常用的Curator这个开源框架,聊一下这个框架对ZooKeeper(以下简称zk)分布式锁的实现。

    一般除了大公司是自行封装分布式锁框架之外,建议大家用这些开源框架封装好的分布式锁实现,这是一个比较快捷省事儿的方式。

    二、ZooKeeper分布式锁机制

    接下来我们一起来看看,多客户端获取及释放zk分布式锁的整个流程及背后的原理。

    首先大家看看下面的图,如果现在有两个客户端一起要争抢zk上的一把分布式锁,会是个什么场景?

    如果大家对zk还不太了解的话,建议先自行百度一下,简单了解点基本概念,比如zk有哪些节点类型等等。

    参见上图。zk里有一把锁,这个锁就是zk上的一个节点。然后呢,两个客户端都要来获取这个锁,具体是怎么来获取呢?

    咱们就假设客户端A抢先一步,对zk发起了加分布式锁的请求,这个加锁请求是用到了zk中的一个特殊的概念,叫做**“临时顺序节点”。**

    简单来说,就是直接在"my_lock"这个锁节点下,创建一个顺序节点,这个顺序节点有zk内部自行维护的一个节点序号。

    比如说,第一个客户端来搞一个顺序节点,zk内部会给起个名字叫做:xxx-000001。然后第二个客户端来搞一个顺序节点,zk可能会起个名字叫做:xxx-000002。大家注意一下,最后一个数字都是依次递增的,从1开始逐次递增。zk会维护这个顺序。

    所以这个时候,假如说客户端A先发起请求,就会搞出来一个顺序节点,大家看下面的图,Curator框架大概会弄成如下的样子:

    大家看,客户端A发起一个加锁请求,先会在你要加锁的node下搞一个临时顺序节点,这一大坨长长的名字都是Curator框架自己生成出来的。

    然后,那个最后一个数字是"1"。大家注意一下,因为客户端A是第一个发起请求的,所以给他搞出来的顺序节点的序号是"1"。

    接着客户端A创建完一个顺序节点。还没完,他会查一下"my_lock"这个锁节点下的所有子节点,并且这些子节点是按照序号排序的,这个时候他大概会拿到这么一个集合:

    接着客户端A会走一个关键性的判断,就是说:唉!兄弟,这个集合里,我创建的那个顺序节点,是不是排在第一个啊?

    如果是的话,那我就可以加锁了啊!因为明明我就是第一个来创建顺序节点的人,所以我就是第一个尝试加分布式锁的人啊!

    bingo!加锁成功!大家看下面的图,再来直观的感受一下整个过程。

    接着假如说,客户端A都加完锁了,客户端B过来想要加锁了,这个时候他会干一样的事儿:先是在"my_lock"这个锁节点下创建一个临时顺序节点,此时名字会变成类似于:

    大家看看下面的图:

    客户端B因为是第二个来创建顺序节点的,所以zk内部会维护序号为"2"。

    接着客户端B会走加锁判断逻辑,查询"my_lock"锁节点下的所有子节点,按序号顺序排列,此时他看到的类似于:

    同时检查自己创建的顺序节点,是不是集合中的第一个?

    明显不是啊,此时第一个是客户端A创建的那个顺序节点,序号为"01"的那个。所以加锁失败

    加锁失败了以后,客户端B就会通过ZK的API对他的顺序节点的**上一个顺序节点加一个监听器。**zk天然就可以实现对某个节点的监听。

    如果大家还不知道zk的基本用法,可以百度查阅,非常的简单。客户端B的顺序节点是:

    他的上一个顺序节点,不就是下面这个吗?

    即客户端A创建的那个顺序节点!

    所以,客户端B会对:

    这个节点加一个监听器,监听这个节点是否被删除等变化!大家看下面的图。

    接着,客户端A加锁之后,可能处理了一些代码逻辑,然后就会释放锁。那么,释放锁是个什么过程呢?

    其实很简单,就是把自己在zk里创建的那个顺序节点,也就是:

    这个节点给删除。

    删除了那个节点之后,zk会负责通知监听这个节点的监听器,也就是客户端B之前加的那个监听器,说:兄弟,你监听的那个节点被删除了,有人释放了锁

    此时客户端B的监听器感知到了上一个顺序节点被删除,也就是排在他之前的某个客户端释放了锁。

    此时,就会通知客户端B重新尝试去获取锁,也就是获取"my_lock"节点下的子节点集合,此时为:

    集合里此时只有客户端B创建的唯一的一个顺序节点了!

    然后呢,客户端B判断自己居然是集合中的第一个顺序节点,bingo!可以加锁了!直接完成加锁,运行后续的业务代码即可,运行完了之后再次释放锁。

     

    三、总结

    其实如果有客户端C、客户端D等N个客户端争抢一个zk分布式锁,原理都是类似的。

    • 大家都是上来直接创建一个锁节点下的一个接一个的临时顺序节点
    • 如果自己不是第一个节点,就对自己上一个节点加监听器
    • 只要上一个节点释放锁,自己就排到前面去了,相当于是一个排队机制。

    而且用临时顺序节点的另外一个用意就是,如果某个客户端创建临时顺序节点之后,不小心自己宕机了也没关系,zk感知到那个客户端宕机,会自动删除对应的临时顺序节点,相当于自动释放锁,或者是自动取消自己的排队。

    最后,咱们来看下用Curator框架进行加锁和释放锁的一个过程:

    其实用开源框架就是这点好,方便。这个Curator框架的zk分布式锁的加锁和释放锁的实现原理,其实就是上面我们说的那样子。

    但是如果你要手动实现一套那个代码的话。还是有点麻烦的,要考虑到各种细节,异常处理等等。所以大家如果考虑用zk分布式锁,可以参考下本文的思路。

    END

    如有收获,请帮忙转发,您的鼓励是作者最大的动力,谢谢!

    一大波微服务、分布式、高并发、高可用的原创系列文章正在路上

    欢迎扫描下方二维码,持续关注:

    石杉的架构笔记(id:shishan100)


    作者:石杉的架构笔记
    链接:https://juejin.cn/post/6844903729406148622
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    element ui 表单清空
    element ui 覆盖样式 方法
    element ui 修改表单值 提交无效
    element ui 抽屉里的表单输入框无法修改值
    element ui 抽屉首次显示 闪烁
    css 左侧高度 跟随右侧内容高度 自适应
    PICNUF框架
    elementui 抽屉组件标题 出现黑色边框
    vue 子组件跨多层调用父组件中方法
    vue 编辑table 数据 未点击提交,table里的数据就发生了改变(深拷贝处理)
  • 原文地址:https://www.cnblogs.com/ajianbeyourself/p/14068521.html
Copyright © 2020-2023  润新知