Zookeeper的一大应用就是分布式锁,分布式锁在集群中的应用是很普遍的,它可以促进资源的合理分配,防止顺序错乱。
分布式锁有分为排它锁和共享锁。
回归正题,这里主要讲共享锁机制会出现的羊群效应。
一般共享锁的实现逻辑
zookeeper中节点的创建类型有4类,这里我们重点关注下临时顺序节点。这种类型的节点有几下几个特性:
1. 节点的生命周期和客户端会话绑定,即创建节点的客户端会话一旦失效,那么这个节点也会被清除。
2. 每个父节点都会负责维护其子节点创建的先后顺序,并且如果创建的是顺序节点(SEQUENTIAL)的话,父节点会自动为这个节点分配一个整形数值,以后缀的形式自动追加到节点名中,作为这个节点终的节点名。
利用上面这两个特性,我们来看下获取实现分布式锁的基本逻辑:
1. 客户端调用create()方法创建名为“_locknode_/guid-lock-”的节点,需要注意的是,这里节点的创建类型需要设置为EPHEMERAL_SEQUENTIAL。
2. 客户端调用getChildren(“_locknode_”)方法来获取所有已经创建的子节点,同时在这个节点上注册上子节点变更通知的Watcher。
3. 客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点是所有节点中序号小的,那么就认为这个客户端获得了锁。
4. 如果在步骤3中发现自己并非是所有子节点中小的,说明自己还没有获取到锁,就开始等待,直到下次子节点变更通知的时候,再进行子节点的获取,判断是否获取锁。
羊群效应
上面这个分布式锁的实现中,大体能够满足了一般的分布式集群竞争锁的需求。这里说的一般性场景是指集群规模不大,一般在10台机器以内。
不过,细想上面的实现逻辑,我们很容易会发现一个问题,步骤4,“即获取所有的子点,判断自己创建的节点是否已经是序号小的节点”,这个过程,在整个分布式锁的竞争过程中,大量重复运行,并且绝大多数的运行结果都是判断出自己并非是序号小的节点,从而继续等待下一次通知——这个显然看起来不怎么科学。客户端无端的接受到过多的和自己不相关的事件通知,这如果在集群规模大的时候,会对Server造成很大的性能影响,并且如果一旦同一时间有多个节点的客户端断开连接,这个时候,服务器就会像其余客户端发送大量的事件通知——这就是所谓的羊群效应。而这个问题的根源在于,没有找准客户端真正的关注点。
我们再来回顾一下上面的分布式锁竞争过程,它的核心逻辑在于:判断自己是否是所有节点中序号小的。于是,很容易可以联想的到的是,每个节点的创建者只需要关注比自己序号小的那个节点。
改进后的共享锁逻辑
下面是改进后的分布式锁实现,和之前的实现方式不同之处在于,这里设计成每个锁竞争者,只需要关注”_locknode_”节点下序号比自己小的那个节点是否存在即可。实现如下:
1. 客户端调用create()方法创建名为“_locknode_/guid-lock-”的节点,需要注意的是,这里节点的创建类型需要设置为EPHEMERAL_SEQUENTIAL。
2. 客户端调用getChildren(“_locknode_”)方法来获取所有已经创建的子节点,注意,这里不注册任何Watcher。
3. 客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点序号小,那么就认为这个客户端获得了锁。
4. 如果在步骤3中发现自己并非所有子节点中小的,说明自己还没有获取到锁。此时客户端需要找到比自己小的那个节点,然后对其调用exist()方法,同时注册事件监听。
5. 之后当这个被关注的节点被移除了,客户端会收到相应的通知。这个时候客户端需要再次调用getChildren(“_locknode_”)方法来获取所有已经创建的子节点,确保自己确实是小的节点了,然后进入步骤3。
• 所有用户可根据关注领域订阅专区或所有专区
• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询
• 专区发布评论属默认订阅所评论专区(除付费小栈外)
作者
-
栈栈作者