在使用 zookeeper 监视点的时候,有一个问题需要注意:当所监视的 znode 节点发生变化时,ZooKeeper 将会触发这个 znode 节点中所拥有的所有监视点的集合,也就是说如果有 1000 个客户端在监视这个 znode 节点的话,那么当这个 znode 节点发生变化时就会一下子发送 1000 个通知出去,这就是监视点所带来的羊群效应
例如在 zookeeper 中实现锁功能的时候就可能会发生上述的羊群效应:
一般来说,zookeeper 可以通过如下的方式来实现锁功能:
假设有 n 个客户端在争相获取一个锁,为了获取到锁,每个客户端进程都可以尝试创建 /lock 节点,如果某个客户端成功创建了 /lock 节点,那就意味着这个客户端得到了锁,反之如果某个客户端在创建 /lock 节点的时候发现 /lock 节点已经存在了,就意味着这个客户端需要等待锁的释放,也就是去监视这个这个 /lock 节点的删除事件,当 /lock 被删除的时候,所有监视 /lock 节点的客户端都将会收到通知,也就表明这个锁已经被释放了,然后剩余的客户端就可以开始尝试再次去获取锁,如果有 1000 个客户端在争夺这个锁,那么就会造成监视点所带来的羊群效应
可以使用如下的方法来解决羊群效应:
让所有需要争夺锁的客户端都去创建一个有序的节点 /lock/lock-xxx ,其中 xxx 为序列号,然后客户端可以使用这个序列号来确定自己是否获得了锁:首先客户端通过 getChildren 方法来获取 /lock 节点下的所有子节点,然后再判断自己创建的 /lock/lock-xxx 节点中的序列号是否是最小的序列号,如果是最小的序列号就说明此客户端获取到了锁,反之就没有就没有获取到锁,如果客户端没有获取到锁,那么就可以根据序列号确定序列,并在前一个节点上设置监视点
例如:假设此时有三个节点:/lock/lock-001、/lock/lock-002 和 /lock/lock-003,那么:
- ·创建 /lock/lock-001 的客户端获得了锁
- ·创建 /lock/lock-002 的客户端没有获得锁,但可以通过监视 /lock/lock-001 节点的方式来等待锁的释放
- ·创建 /lock/lock-003 的客户端没有获得锁,但可以通过监视 /lock/lock-002 节点的方式来等待锁的释放
这样一来,每个节点上设置的监视点就可以最多只有一个客户端了