基于zookeeper实现领导选举和分布式锁

x33g5p2x  于2021-12-30 转载在 Zookeeper  
字(4.4k)|赞(0)|评价(0)|浏览(512)

上一篇博客讨论了基于zookeeper的分布式队列的机制,这个机制除了可以做分布式队列以外,稍加修改,还可以做更多的事情,例如接下来要讨论的领导选举和分布式锁的功能等。

领导选举

领导选举的应用场景可以理解为:多个节点同时想干一件事(都想当老大),但最终只有一个节点被授权(老大只可能有一个)

例如:一主多从模式下,如果主节点挂掉了,那么所有的从节点都要竞选成为主节点,但只有一个节点可以成为主节点。

领导选举的特点就是选举是一次性的,只要主节点不挂掉,他就一直是领导。

基于zookeeper的领导选举的实现方案如下:

提前准备一个znode作为一个队列。例如/election

  1. 每个节点都调用create()创建一个node,带上sequence和ephemeral标签。名字为/election/queue-
  2. /election节点调用getChildren(),不设置watch,拿到/election下的整个列表
  3. 如果在第1步创建的znode中自增长的数字,如果在列表中是最小的,则它就是leader了
  4. 如果发现自己不是leader,则删除在第1步创建的znode

此时,数字最小的那个znode成为了leader,其余的znode都被创建它的节点删除了。如果某个节点得到竞选通知比较晚,此时它再次调用create(),创建出来的znode的数字肯定会更大,按照上面的流程,它会直接删除自己创建的znode并退出选举。这就保证了任何时候只有一个leader。

领导选举的示例代码

我们用java的多线程来模拟一下多个节点,进行一次选举

首先,新建一个类实现Callable接口。这个类参与选举。如果成功竞选,则返回自己创建的znode地址,否则,返回空字符串。

  1. class LeaderElection implements Callable<String> {
  2. ZooKeeper zooKeeper;
  3. String znodePath;
  4. String name;
  5. String newNodeName = null;
  6. boolean isLeader = false;
  7. LeaderElection(ZooKeeper zooKeeper, String znodePath, String name) {
  8. this.zooKeeper = zooKeeper;
  9. this.znodePath = znodePath;
  10. this.name = name;
  11. }
  12. @Override
  13. public String call() throws Exception {
  14. String fullPath = znodePath + "/queue-";
  15. newNodeName = zooKeeper.create(fullPath, this.name.getBytes(),
  16. ZooDefs.Ids.OPEN_ACL_UNSAFE,
  17. CreateMode.EPHEMERAL_SEQUENTIAL);
  18. System.out.println(this.name + " created node:" + newNodeName);
  19. List<String> nodeList = zooKeeper.getChildren(this.znodePath, false);
  20. isLeader = checkIsLeader(nodeList, newNodeName);
  21. System.out.println(this.name + " is leader:" + isLeader);
  22. if (!isLeader) {
  23. zooKeeper.delete(newNodeName, zooKeeper.exists(newNodeName, false).getVersion());
  24. return "";
  25. } else {
  26. return newNodeName;
  27. }
  28. }
  29. private boolean checkIsLeader(List<String> nodeList, String newNodeName) {
  30. int curValue = getNumber(newNodeName);
  31. for (String node : nodeList) {
  32. if (getNumber(node) < curValue) {
  33. return false;
  34. }
  35. }
  36. return true;
  37. }
  38. private int getNumber(String queueNode) {
  39. queueNode = queueNode.substring(queueNode.length() - 10, queueNode.length());
  40. return Integer.parseInt(queueNode);
  41. }
  42. }

接下来,创建一个线程池,并启动10个选举线程,就可以了。

  1. @Test
  2. public void testLeaderSelection() throws IOException, InterruptedException, ExecutionException, KeeperException {
  3. ZooKeeper zooKeeper = zookeeperClient.connect();
  4. ExecutorService threadpool = Executors.newCachedThreadPool();
  5. List<Future<String>> futureList = new LinkedList<>();
  6. for (int i = 0; i < 10; i++) {
  7. Callable<String> callable = new LeaderElection(zooKeeper, zookeeperClient.getRestoreLockPath(), "election-" + i);
  8. Future<String> future = threadpool.submit(callable);
  9. futureList.add(future);
  10. }
  11. for (Future<String> future : futureList) {
  12. String leaderNode = future.get();
  13. if (!leaderNode.isEmpty()) {
  14. System.out.println("leader node is " + leaderNode);
  15. }
  16. }
  17. }

运行上面的测试,会打印出谁是领导,有且只有一个。输出如下:

  1. election-5 created node:/jobcenter/restorelocks/queue-0000000090
  2. election-4 created node:/jobcenter/restorelocks/queue-0000000091
  3. election-3 created node:/jobcenter/restorelocks/queue-0000000092
  4. election-1 created node:/jobcenter/restorelocks/queue-0000000093
  5. election-2 created node:/jobcenter/restorelocks/queue-0000000094
  6. election-0 created node:/jobcenter/restorelocks/queue-0000000095
  7. election-6 created node:/jobcenter/restorelocks/queue-0000000096
  8. election-7 created node:/jobcenter/restorelocks/queue-0000000097
  9. election-4 is leader:false
  10. election-5 is leader:true
  11. election-3 is leader:false
  12. election-9 created node:/jobcenter/restorelocks/queue-0000000098
  13. election-8 created node:/jobcenter/restorelocks/queue-0000000099
  14. election-1 is leader:false
  15. election-2 is leader:false
  16. election-0 is leader:false
  17. election-6 is leader:false
  18. election-7 is leader:false
  19. election-9 is leader:false
  20. election-8 is leader:false
  21. leader node is /jobcenter/restorelocks/queue-0000000090

分布式锁

这里所说的分布式锁,指的是:多个节点都需要做一件事,但这件事在任何一个时间点上只能有一个节点在做,如果多个节点同时做的话,会有并发问题,可能造成数据错误。

分布式锁的实现方案跟上面所说的领导选举十分相似,我还列一下这个过程吧

还是要提前准备一个znode作为一个队列。例如/distribute-lock

  1. 每个节点都调用create()创建一个node,带上sequence和ephemeral标签。名字为/distribute-lock/lock-
  2. /distribute-lock节点调用getChildren(),不设置watch,拿到/distribute-lock下的整个列表
  3. 如果在第1步创建的znode中自增长的数字,如果在列表中是最小的,则它就是leader了,就可以退出这个过程了。
  4. 如果发现自己不是leader,则调用exist()来监听比自己小一号的znode,这一步要设置watch。当watch触发时,goto step 2,来对比自己是不是leader(因为新的leader会在完事以后删掉自己创建的znode)。
  5. ……

怎么理解比自己小一号的znode:如果自己创建的znode是lock-0000000023的话,则小一号的不一定就是0000000022。正确的做法是,对整个列表从小到大进行排序,拿到自己的index,如果index是0,则自己就是leader,而比自己小一号的znode就是list[index - 1]。

如果在第3步发现自己是leader了,则表示自己拿到了分布式锁,就可以开始执行某个过程了,执行完了以后删除自己在第1步时创建的znode,表示释放了自己的分布式锁。这时,原本倒数第二小的znode成为了最小的znode,对应的节点就拿到了分布式锁。还有一种情况就是,如果老四监听老三,但老三在成为老大之前退出了,上面的机制会使老四去监听老二。这个过程会不断重复。

由于分布式锁的逻辑跟上面领导选举比较相似,读者可以自己用代码来实现一下玩玩。

参考资料

http://zookeeper.apache.org/doc/current/recipes.html

相关文章

最新文章

更多