zookeeper集群

介绍

Zookeeper 是一个开源的分布式协调服务【Google Chubby的开源实现】,提供了一个类型linux文件系统的树形结构,同时提供对每个节点的监控与通知机制。【是一个用于存储少量数据的基于内存的数据库,主要有如下两个核心的概念:文件系统数据结构+监听通知机制】。ZooKeeper 可以用于实现分布式系统中常见的发布/订阅、负载均衡、命令服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
在这里插入图片描述

  • ServerCnxnFactory

向外暴露TCP长链接,维护双向链接通道,分发请求。

  • LearnerCnxAcceptor

Leader服务Socket服务端,接收Follower、Observer服务的创建链接请求,创建LearnerHandler,用于内部数据交互。

  • FastLeaderElection

Leader内部选举,为避免双方重复创建链接,由myid大的向myid小的创建链接

  • SessionTracker

会话管理及独立线程会话超时清理

  • ZKDatabase

ZooKeeper的内存数据库,负责管理DataTree、事务日志,启动时从事务日志和快照中恢复到该内存数据库,运行期,将接收到的事务请求写入事务日志,当触发快照操作时,提供数据。

数据模型

在这里插入图片描述

ZooKeeper 的数据节点可以视为树状结构,树中的各节点被称为 Znode,一个 Znode 可以有多个子节点,ZooKeeper 中的所有存储的数据是由 znode 组成,并以 key/value 形式存储数据。整体结构类似于 linux 文件系统的模式以树形结构存储,其中根路径以 / 开头

数据节点类型

  • 持久节点【PERSISTENT】

数据节点被创建后,就会一直存在ZooKeeper服务器上,直到有删除操作主动清除节点数据。(默认创建)

  • 持久顺序节点【PERSISTENT_SEQUENTIAL】

在持久性的基础上,又具有了顺序性特性。每个父节点都会为它的第一级子节点维护一份顺序,用于记录每个子节点的先后顺序。在创建子节点过程中,ZooKeeper会自动为给定的节点名加上一个数字后缀。适用于分布式锁、分部署选择等场景

  • 临时节点【EPHEMERAL】

临时节点的生命周期和客户端的会话绑定在一起,如果客户端会话失效,该会话对应的临时节点就会被自动清理。临时节点不能创建子节点。

  • 临时顺序节点【EPHEMERAL_SEQUENTIAL】

在临时节点的特性上,添加了顺序性特点。适用于心跳、服务发现等场景

  • Container 节点

3 .5.3 版本新增,如果Container节点下面没有子节点,则Container节点在未来会被Zookeeper自动清除,定时任务默认60s 检查一次

集群安装

Zookeeper 3.5.0 以前,Zookeeper集群角色要发生改变的话,只能通过停掉所有的Zookeeper服务,修改集群配置,重启服务来完成,这样集群服务将有一段不可用的状态,为了应对高可用需求,Zookeeper 3.5.0 提供了支持动态扩容/缩容的 新特性。但是通过客户端API可以变更服务端集群状态是件很危险的事情,所以在zookeeper 3.5.3 版本要用动态配置,需要开启超级管理员身份验证模式 ACLs。如果是在一个安全的环境也可以通过配置 系统参数 -Dzookeeper.skipACL=yes 来避免配置维护acl 权限配置。

在这里插入图片描述

竖轴是每秒请求量,横轴是读请求的占比,不同颜色的曲线代表了不同数量的zookeeper节点,分别是3个节点,5个节点,7个节点,9个节点,13个节点。
随着读请求所占的比例越来越多,zookeeper所能处理的请求数量是指数级得提升。除了三个节点的zookeeper集群外,当读请求占比达到100%时,从曲线上看其他数量的集群能够处理的请求几乎无穷大。
当读请求达到80%时,由于follower数量的增大导致写请求的耗时增长,节点数量越多的zookeeper吞吐量越小(三个节点的集群是个例外)。吞吐量排序是:5>7>3>9>13。
正常情况下所有的请求都是读请求是不可能的,肯定含有写请求。所以建议zookeeper集群的读写比例为7:3或8:2最好,机器数量为5或者7台,不仅成本低也有良好的性能表现。

下载
http://mirror.bit.edu.cn/apache/zookeeper/
1.修改conf目录下的 zoo_sample.cfg --》zoo.cfg 修改配置(如下):
在这里插入图片描述
2.在上面规划的dataDir目录下创建名为myid的文件(备注:无扩展名)里面写入server.id值
在这里插入图片描述
按上述步骤,修改节点2、3 【clientPort 也要修改】
3. 执行 bin\zkServer.cmd 依次启动三个节点
4. 通过管理工具管理(zktools)zk,【类似的管理工具很多…】
在这里插入图片描述

集群角色

在这里插入图片描述

#participant 可以不用写,默认就是participant
server.1=127.0.0.1:2001:3001:participant

#observer配置:只要在集群配置中加上observer后缀即可,示例如下:
server.3=127.0.0.1:2889:3889:observer

角色描述
Leader 领导者集群中最重要的角色。负责响应集群的所有对Zookeeper数据状态变更的请求。它会将每个状态更新请求进行顺序管理,以便保证整个集群内部消息处理的 FIFO,遵循了顺序一致性(Sequential Consistency)
Follower 跟随者具有选举权。负责提供给客户端读写服务,需要响应leader的提议
Observer观察者没有选举权。主要提供给客户端读服务,不提供写服务,也不需要响应leader的提议。也不需要日志文件,因为没有写服务,没有持久化的需要。

选举流程

第一次启动
(1)服务器1启动并发起一次选举。服务器1投自己一票,此时服务器1票数一票,不够半数以上(3票),则选举无法完成,服务器1状态保持为LOOKING。
(2)服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交换选票信息
服务器1发现服务器2的myid比自己目前投票推举的(服务器1) 大,更改选票为推举服务器2。
此时服务器1票数0票,服务器2票数2票,没有超过半数,选举无法完成,服务器1,2状态保持LOOKING。
(3)服务器3启动,发起一次选举。此时服务器1和2都会更改选票为服务器3。
服务器1为0票,服务器2为0票,服务器3为3票。此时服务器3的票数已经超过半数,服务器3当选Leader。
服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING。
(4)服务器4启动,发起一次选举。此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。
服务器3为3票,服务器4为1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为FOLLOWING。
(5)服务器5启动,同4一样更新状态为FOLLOWING。
非第一次启动
(1)当ZooKeeper集群中的一台服务器出现以下两种情况之一时,就会开始进入Leader选举:
服务器初始化启动。
服务器运行期间无法和Leader保持连接。
(2)而当一台机器进入Leader选举流程时,当前集群也可能会处于以下两种状态:
集群中本来就已经存在一个Leader【由于网络波动导致的暂时失联,重选leader的场景】。
对于第一种已经存在Leader的情况,机器试图去选举Leader时,会被告知当前服务器的Leader信息,对于该机器来说,仅仅需要和Leader机器建立连接,并进行状态同步即可。
(3)集群中确实不存在Leader,则遵循以下原则进行选举:
1️⃣EPOCH大的直接胜出
2️⃣EPOCH相同,事务id大的胜出
3️⃣事务id相同,服务器id大的胜出

在这里插入图片描述

消息广播流程

1、收到写请求的follower,将写请求发送给leader。
2、leader收到来自follower(或observer)的写请求后,首先计算这次写操作之后的状态,然后将这个写请求转换成带有各种状态的事务(如版本号、zxid等等)。
3、leader将这个事务以提议的方式广播出去(即发送proposal)。
4、所有follower收到proposal后,对这个提议进行投票,投票完成后返回ack给leader。follower的投票只有两种方式:(1)确认这次提议表示同意(2)丢弃这次提议表示不同意。
5、leader收集投票结果,只要投票数量超过半数,这次提议就通过。
6、提议通过后,leader向所有server发送一个提交通知。
7、所有节点将这次事务写入事务日志,并进行提交。
8、提交后,收到写请求的那个server向客户端返回成功信息。

在这里插入图片描述

常用命令

help 显示所有操作命令
ls path 使用 ls 命令来查看当前 znode 的子节点 [可监听]
	  -w 监听子节点变化
	  -s 附加次级信息
create 普通创建
	  -s 含有序列
	  -e 临时(重启或者超时消失)
get path 获得节点的值 [可监听]
	-w 监听节点内容变化
	-s 附加次级信息
set 设置节点的具体值
stat 查看节点状态
	 -w 监听子节点变化
delete 删除节点
deleteall 递归删除节点

acl

权限模式种类

模式描述
world这种模式方法的授权对象只有一个anyone,代表登录到服务器的所有客户端都能对该节点执行某种权限
ip对连接的客户端使用IP地址认证方式进行认证
auth以添加认证的用户进行认证
digest用户:密码方式验证

权限的类型

类型ACL简写描述
reade读取节点及显示子节点列表的权限
writew设置节点数据的权限
createc创建子节点的权限
delted删除子节点的权限
admina设置该节点ACL权限的权限

授权的命令

命令用法描述
getAclgetAcl path读取节点的ACL
setAclsetAcl path acl设置节点的ACL
createcreate path data acl创建节点时设置acl
addAuthaddAuth scheme auth添加认证用户,类似于登录操作

使用场景

分布式配置管理

Zookeeper 提供了一个简单且强大的配置管理机制。在分布式系统中,多个服务可能需要共享一些配置参数(如数据库连接信息、文件路径等),Zookeeper 通过节点存储配置,并通过 Watcher 机制通知所有客户端当配置发生变更时及时更新。

实现步骤:
将配置存储在 Zookeeper 的某个 ZNode 下。
客户端使用 Watcher 监控配置节点的变化。
当配置发生变化时,客户端通过 Watcher 获取新配置并更新。

# 配置存储节点:
$ zkCli.sh
[zk: localhost:2181(CONNECTED) 0] create /config/db '{"host": "localhost", "port": 3306}'
#Java 客户端监控配置变化:
import org.apache.zookeeper.*;
public class ZookeeperConfigWatcher implements Watcher {
    private static ZooKeeper zk;
    private static String configPath = "/config/db";

    public static void main(String[] args) throws Exception {
        // 初始化Zookeeper客户端
        zk = new ZooKeeper("localhost:2181", 3000, new ZookeeperConfigWatcher());
        
        // 获取当前配置
        String config = new String(zk.getData(configPath, true, null));
        System.out.println("Current config: " + config);
        
        // 在配置节点上设置Watcher监听配置变更
        zk.exists(configPath, true);
        
        // 保持程序运行,以便监听配置变化
        Thread.sleep(Long.MAX_VALUE);
    }

    @Override
    public void process(WatchedEvent event) {
        if (event.getType() == Event.EventType.NodeDataChanged) {
            try {
                // 配置变更时读取新数据
                String updatedConfig = new String(zk.getData(configPath, false, null));
                System.out.println("Updated config: " + updatedConfig);
                
                // 重新设置Watcher
                zk.exists(configPath, true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
使用说明:
上述代码会初始化一个 Zookeeper 客户端,连接到本地 Zookeeper 服务。
客户端读取 /config/db 路径下的配置信息,并设置 Watcher 监听该节点的变化。
当配置发生变化时,process 方法会被触发,客户端会自动获取新配置并更新。

分布式锁

Zookeeper 通过临时顺序节点可以实现分布式锁。临时顺序节点确保了在某个节点宕机后,其他节点能自动感知并重新争抢锁,避免了因节点宕机而导致的死锁情况。

实现步骤:
客户端创建一个临时顺序节点,节点名称中包含顺序编号。
客户端检查自己创建的节点是否是最小编号的节点。如果是,获取锁。
如果不是,客户端监视比自己编号小的节点,当它被删除时,客户端重新获取锁。

import org.apache.zookeeper.*;

import java.util.Collections;
import java.util.List;

public class ZookeeperDistributedLock {
    private static ZooKeeper zk;
    private static String lockPath = "/lock";
    private static String currentNode;

    public static void main(String[] args) throws Exception {
        // 初始化Zookeeper客户端
        zk = new ZooKeeper("localhost:2181", 3000, new Watcher() {
            public void process(WatchedEvent event) {
                // 处理Watcher事件
            }
        });
        
        // 创建临时顺序节点
        currentNode = zk.create(lockPath + "/lock-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        
        // 获取锁
        getLock();
        
        // 模拟持有锁的业务逻辑
        Thread.sleep(5000);
        
        // 释放锁
        releaseLock();
    }

    private static void getLock() throws Exception {
        // 获取锁路径下的所有子节点
        List<String> children = zk.getChildren(lockPath, false);
        Collections.sort(children); // 排序节点,最小的顺序节点获取锁

        String firstNode = children.get(0);
        if (currentNode.endsWith(firstNode)) {
            System.out.println("Lock acquired!");
        } else {
            String prevNode = children.get(children.indexOf(currentNode.substring(lockPath.length())) - 1);
            zk.exists(lockPath + "/" + prevNode, new Watcher() {
                public void process(WatchedEvent event) {
                    if (event.getType() == Event.EventType.NodeDeleted) {
                        try {
                            getLock(); // 前一个节点被删除时,尝试重新获取锁
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
        }
    }

    private static void releaseLock() throws Exception {
        zk.delete(currentNode, -1);
        System.out.println("Lock released!");
    }
}
使用说明:
客户端创建一个临时顺序节点,Zookeeper 会为每个客户端生成一个唯一的顺序节点名。
客户端获取锁时,会检查自己创建的节点是否是最小编号的节点。如果是,它就可以获取锁;如果不是,它将监视比自己顺序编号小的节点,等待其删除后尝试重新获取锁。
当任务完成后,客户端会删除自己创建的临时顺序节点,从而释放锁。

服务发现

Zookeeper 在分布式系统中广泛应用于服务发现机制。服务发现指的是在分布式环境中,动态地查询服务的可用实例并进行负载均衡。Zookeeper 可以通过其提供的节点存储和 Watcher 机制,动态管理服务实例。

实现步骤:
将所有服务实例注册到 Zookeeper 中,每个服务实例对应一个 ZNode。
客户端通过 Watcher 监控服务节点的变化,获取可用的服务实例列表。
当服务新增或删除时,客户端能够动态地发现新的服务。

$ zkCli.sh
#服务实例注册:
[zk: localhost:2181(CONNECTED) 0] create /services/myapp/service1 "Service 1"
[zk: localhost:2181(CONNECTED) 1] create /services/myapp/service2 "Service 2"
#Java 客户端获取服务实例:
import org.apache.zookeeper.*;
import java.util.List;
public class ZookeeperServiceDiscovery {
    private static ZooKeeper zk;
    private static String servicePath = "/services/myapp";

    public static void main(String[] args) throws Exception {
        zk = new ZooKeeper("localhost:2181", 3000, new Watcher() {
            public void process(WatchedEvent event) {
                // 监控服务实例变化
                if (event.getType() == Event.EventType.NodeChildrenChanged) {
                    try {
                        getServiceInstances(); // 获取更新后的服务实例列表
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        
        // 获取服务实例
        getServiceInstances();
        
        // 保持客户端运行
        Thread.sleep(Long.MAX_VALUE);
    }

    private static void getServiceInstances() throws Exception {
        List<String> serviceInstances = zk.getChildren(servicePath, true); // true 表示设置Watcher
        System.out.println("Current available services: " + serviceInstances);
    }
}
使用说明:

将服务实例(如 service1, service2)注册到 /services/myapp 路径下。
客户端通过 zk.getChildren() 获取该路径下所有的子节点,这些子节点代表了当前活跃的服务实例。
客户端通过设置 Watcher,当服务节点发生变化(如新增、删除)时,能够动态获取服务实例列表并更新。

分布式队列(高并发任务分发)

Zookeeper 还可以实现分布式队列。在这种场景下,多个消费者从队列中取任务并执行,Zookeeper 确保任务顺序和公平性。

实现步骤:
生产者将任务数据放入 Zookeeper 中,创建一个任务节点。
消费者获取最早的任务并处理,处理完成后删除该节点。
Zookeeper 保证任务的顺序性和节点的独占性。

import org.apache.zookeeper.*;

import java.util.List;
import java.util.Collections;

public class ZookeeperDistributedQueue {
    private static ZooKeeper zk;
    private static String queuePath = "/queue";

    public static void main(String[] args) throws Exception {
        zk = new ZooKeeper("localhost:2181", 3000, new Watcher() {
            public void process(WatchedEvent event) {
                // 处理事件
            }
        });
        
        // 创建队列路径
        createQueuePath();
        
        // 生产者入队
        enqueue("task1");
        enqueue("task2");
        
        // 消费者出队并处理
        dequeue();
    }

    private static void createQueuePath() throws Exception {
        if (zk.exists(queuePath, false) == null) {
            zk.create(queuePath, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
    }

    private static void enqueue(String task) throws Exception {
        String taskNode = zk.create(queuePath + "/task-", task.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
        System.out.println("Enqueued task: " + taskNode);
    }

    private static void dequeue() throws Exception {
        List<String> tasks = zk.getChildren(queuePath, false);
        Collections.sort(tasks);  // 确保任务顺序执行
        
        if (!tasks.isEmpty()) {
            String taskToProcess = tasks.get(0);
            byte[] taskData = zk.getData(queuePath + "/" + taskToProcess, false, null);
            System.out.println("Processing task: " + new String(taskData));
            
            // 删除已处理的任务
            zk.delete(queuePath + "/" + taskToProcess, -1);
            System.out.println("Processed and removed task: " + taskToProcess);
        }
    }
}
通过创建顺序节点,每个任务都会在队列中有一个唯一的位置。消费者根据任务顺序(即节点的编号)获取任务。
任务处理完成后,消费者删除任务节点,确保任务不会被重复消费。

Leader 选举

Zookeeper 常用于实现分布式系统中的 Leader 选举。Leader 选举用于确保在分布式环境中某一时刻只有一个节点被选举为 Leader,通常用于保证对资源的独占访问。其他节点(Follower)则等待 Leader 的指示,通常在数据库或任务调度系统中会使用 Leader 选举来避免竞争条件

实现步骤:
节点尝试创建一个临时顺序节点。
节点检查自己是否是最小的顺序节点。
如果是,成为 Leader。如果不是,则监听比自己顺序编号小的节点,等待它被删除后重新参与选举。

import org.apache.zookeeper.*;

import java.util.Collections;
import java.util.List;

public class ZookeeperLeaderElection {
    private static ZooKeeper zk;
    private static String electPath = "/leader";
    private static String currentNode;

    public static void main(String[] args) throws Exception {
        zk = new ZooKeeper("localhost:2181", 3000, new Watcher() {
            public void process(WatchedEvent event) {
                // 处理事件
            }
        });
        
        // 创建临时顺序节点
        currentNode = zk.create(electPath + "/node-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        
        // 选举Leader
        electLeader();
        
        // 模拟执行Leader任务
        Thread.sleep(5000);
        
        // 释放Leader
        releaseLeader();
    }

    private static void electLeader() throws Exception {
        List<String> children = zk.getChildren(electPath, false);
        Collections.sort(children); // 排序节点,最小编号的节点成为Leader

        String firstNode = children.get(0);
        if (currentNode.endsWith(firstNode)) {
            System.out.println("I am the Leader!");
        } else {
            String prevNode = children.get(children.indexOf(currentNode.substring(electPath.length())) - 1);
            zk.exists(electPath + "/" + prevNode, new Watcher() {
                public void process(WatchedEvent event) {
                    if (event.getType() == Event.EventType.NodeDeleted) {
                        try {
                            electLeader(); // 前一个节点被删除时,尝试重新选举Leader
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
        }
    }

    private static void releaseLeader() throws Exception {
        zk.delete(currentNode, -1);
        System.out.println("Released Leader role.");
    }
}
这个例子中,通过创建临时顺序节点(/leader/node-)来进行选举,节点会在节点列表中排序,最小编号的节点被选为 Leader。
如果当前节点不是最小的编号,它会监听比自己编号小的节点,当该节点被删除时,当前节点会尝试重新成为 Leader。

分布式计数器

Zookeeper 还可以用来实现分布式计数器。这通常用于在多个节点间共享一个递增的计数值。Zookeeper 提供的 临时顺序节点 可以帮助实现计数器,保证计数的顺序性和一致性。

实现步骤:
每个增量操作创建一个临时顺序节点。
节点的顺序值表示当前的计数值。

import org.apache.zookeeper.*;

public class ZookeeperDistributedCounter {
    private static ZooKeeper zk;
    private static String counterPath = "/counter";

    public static void main(String[] args) throws Exception {
        zk = new ZooKeeper("localhost:2181", 3000, new Watcher() {
            public void process(WatchedEvent event) {
                // 处理事件
            }
        });
        
        // 创建计数器
        createCounter();
        
        // 递增计数器
        incrementCounter();
        
        // 获取计数器值
        getCounterValue();
    }

    private static void createCounter() throws Exception {
        if (zk.exists(counterPath, false) == null) {
            zk.create(counterPath, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
    }

    private static void incrementCounter() throws Exception {
        String nodeName = zk.create(counterPath + "/node-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println("Incremented Counter: " + nodeName);
    }

    private static void getCounterValue() throws Exception {
        List<String> children = zk.getChildren(counterPath, false);
        System.out.println("Current Counter Value: " + children.size());
    }
}
该代码实现了一个分布式计数器,通过创建临时顺序节点 (/counter/node-) 来递增计数器。
每次创建新的临时顺序节点时,节点的顺序号自动递增。通过获取 /counter 路径下的所有子节点数量,获取当前计数器的值。

分布式任务调度

在分布式系统中,任务调度器常常需要协调不同节点的任务执行。Zookeeper 可用于任务的调度和管理,确保任务的分配是有序和高效的。例如,Zookeeper 可以用来确保每个任务只有一个节点在执行,从而避免重复执行。

实现步骤:
为每个任务创建一个 ZNode。
各个节点轮流获取任务并执行,确保任务的有序调度。
节点执行完任务后删除任务 ZNode,表示任务完成。

import org.apache.zookeeper.*;

public class ZookeeperTaskScheduler {
    private static ZooKeeper zk;
    private static String taskPath = "/tasks";

    public static void main(String[] args) throws Exception {
        zk = new ZooKeeper("localhost:2181", 3000, new Watcher() {
            public void process(WatchedEvent event) {
                // 处理事件
            }
        });
        
        // 创建任务路径
        createTaskPath();
        
        // 调度任务
        scheduleTask();
        
        // 保持客户端运行
        Thread.sleep(Long.MAX_VALUE);
    }

    private static void createTaskPath() throws Exception {
        if (zk.exists(taskPath, false) == null) {
            zk.create(taskPath, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
    }

    private static void scheduleTask() throws Exception {
        // 创建任务节点
        String taskNode = zk.create(taskPath + "/task-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println("Task created: " + taskNode);
        
        // 获取所有任务节点并排序
        List<String> tasks = zk.getChildren(taskPath, false);
        Collections.sort(tasks);  // 确保任务按照顺序执行
        
        String taskToExecute = taskPath + "/" + tasks.get(0);
        System.out.println("Executing task: " + taskToExecute);
        
        // 执行任务(这里只是打印任务信息,实际应用可以做更复杂的操作)
        zk.delete(taskToExecute, -1);
        System.out.println("Task completed and removed: " + taskToExecute);
    }
}
任务调度过程中,每个任务都被创建为一个临时顺序节点,节点顺序决定任务的执行顺序。
节点在执行完任务后会被删除,确保任务不会被重复执行。

全局唯一ID生成

在分布式系统中,经常需要生成全局唯一的 ID。Zookeeper 提供的临时顺序节点可以帮助生成全局唯一的 ID,通过创建顺序节点来实现 ID 的递增。

实现步骤:
在某个路径下创建临时顺序节点。
通过顺序节点的编号来生成唯一 ID。

import org.apache.zookeeper.*;

public class ZookeeperUniqueIDGenerator {
    private static ZooKeeper zk;
    private static String idPath = "/unique_id";

    public static void main(String[] args) throws Exception {
        zk = new ZooKeeper("localhost:2181", 3000, new Watcher() {
            public void process(WatchedEvent event) {
                // 处理事件
            }
        });
        
        // 创建唯一ID路径
        createIDPath();
        
        // 生成唯一ID
        generateUniqueID();
    }

    private static void createIDPath() throws Exception {
        if (zk.exists(idPath, false) == null) {
            zk.create(idPath, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
       

 }
    }

    private static void generateUniqueID() throws Exception {
        String idNode = zk.create(idPath + "/id-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println("Generated unique ID: " + idNode);
    }
}
每次生成 ID 时,都会在 /unique_id/id- 路径下创建一个临时顺序节点。通过节点的顺序号来生成全局唯一的 ID。

服务协调与健康检查

Zookeeper 作为一个高可用的分布式系统协调服务,能够帮助进行 服务协调 和 健康检查。在微服务架构中,Zookeeper 可以用于管理各个服务实例的健康状态,自动将健康服务加入到负载均衡池中。

实现步骤:
各服务节点定期更新自己的健康状态到 Zookeeper 中。
如果某个服务失效,它的健康状态会被 Zookeeper 监控并移除。
客户端或负载均衡器通过 Zookeeper 查询健康的服务实例。

import org.apache.zookeeper.*;
import java.util.List;

public class ZookeeperServiceHealthCheck {
    private static ZooKeeper zk;
    private static String servicePath = "/services";

    public static void main(String[] args) throws Exception {
        zk = new ZooKeeper("localhost:2181", 3000, new Watcher() {
            public void process(WatchedEvent event) {
                // 处理事件
            }
        });

        // 注册服务实例(健康检查)
        registerService("service1");
        registerService("service2");

        // 定期检查健康状态
        checkServiceHealth();
        
        // 保持客户端运行
        Thread.sleep(Long.MAX_VALUE);
    }

    private static void registerService(String serviceName) throws Exception {
        String serviceNode = zk.create(servicePath + "/" + serviceName, "healthy".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
        System.out.println(serviceName + " registered with Zookeeper");
    }

    private static void checkServiceHealth() throws Exception {
        List<String> services = zk.getChildren(servicePath, new Watcher() {
            public void process(WatchedEvent event) {
                // 重新检查健康状态
                try {
                    checkServiceHealth();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        
        System.out.println("Currently available services: " + services);
        
        for (String service : services) {
            byte[] status = zk.getData(servicePath + "/" + service, false, null);
            System.out.println(service + " status: " + new String(status));
        }
    }
}
服务实例通过 Zookeeper 注册,并设置为临时节点。
健康检查机制通过 getData 获取服务的健康状态数据,客户端实时监控服务状态变化。
如果某个服务挂掉,Zookeeper 会自动删除其临时节点,负载均衡器就可以检测到服务的失效。

分布式屏障(Barrier)

分布式屏障(Barrier)是一种同步原语,它可以确保所有参与者在达到某个条件后才能继续执行。例如,多个节点需要等待其他节点都准备好后才开始执行某个任务。Zookeeper 可以很好地实现这一功能,确保所有节点在同步点都达到一致,然后才能继续执行。

示例应用

Kafka集群管理:Kafka 使用 ZooKeeper 来管理其集群的元数据,如主题分区信息、集群成员信息等。
Hadoop生态系统:Hadoop的YARN资源管理器使用ZooKeeper来管理应用程序的生命周期和资源分配。

实现步骤:
每个节点在进入屏障时,会在 Zookeeper 中创建一个临时节点。
当所有节点都到达同步点(即所有临时节点都创建)后,才允许所有节点继续执行。
Zookeeper 会监控这些临时节点的数量,当数量达到预定值时,解除阻塞。

import org.apache.zookeeper.*;

import java.util.List;

public class ZookeeperDistributedBarrier {
    private static ZooKeeper zk;
    private static String barrierPath = "/barrier";
    private static int totalParticipants = 3;  // 假设有3个参与者

    public static void main(String[] args) throws Exception {
        zk = new ZooKeeper("localhost:2181", 3000, new Watcher() {
            public void process(WatchedEvent event) {
                // 处理事件
            }
        });

        // 创建屏障路径
        createBarrierPath();

        // 节点加入屏障
        joinBarrier("node1");
        joinBarrier("node2");
        joinBarrier("node3");

        // 等待所有节点加入屏障
        waitForAllNodes();
    }

    private static void createBarrierPath() throws Exception {
        if (zk.exists(barrierPath, false) == null) {
            zk.create(barrierPath, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
    }

    private static void joinBarrier(String nodeName) throws Exception {
        zk.create(barrierPath + "/" + nodeName, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
        System.out.println(nodeName + " reached the barrier");
    }

    private static void waitForAllNodes() throws Exception {
        List<String> children = zk.getChildren(barrierPath, false);
        
        while (children.size() < totalParticipants) {
            System.out.println("Waiting for other nodes...");
            Thread.sleep(1000);
            children = zk.getChildren(barrierPath, false);  // 重新获取子节点
        }

        System.out.println("All nodes reached the barrier, continuing execution...");
    }
}
每个节点通过在 Zookeeper 中创建临时节点加入到屏障路径 /barrier 下。
其他节点会检查当前屏障下的节点数量,直到达到预期的参与者数量,才解除阻塞,继续执行。

ZAB协议

​​​​​​​Zab( ZooKeeper Atomic Broadcast , ZooKeeper 原子消息广播协议)协议是一个分布式一致性算法,让ZK拥有了崩溃恢复和原子广播的能力,进而保证集群中的数据一致性。

协议的事务编号 Zxid 设计中,ZXID是一个长度64位的数字,其中低32位是按照数字递增,任何数据的变更都会导致,低32位的数字简单加1。高32位是leader周期编号,每当选举出一个新的leader时,新的leader就从本地事物日志中取出ZXID,然后解析出高32位的周期编号,进行加1,再将低32位的全部设置为0。这样就保证了每次新选举的leader后,保证了ZXID的唯一性而且是保证递增的。

Zab 协议有两种模式,它们分别是恢复模式广播模式:
当服务启动或者在领导者崩溃后,Zab 就进入了恢复模式,当领导者被选举出来,且大多数 server 的完成了和 leader 的状态同步以后,恢复模式就结束了,他就可以开始广播消息了,即进入广播状态

Zab协议分为四个核心步骤:
选举: 在ZooKeeper集群中选举中一个Leader
发现: Leader中会维护一个Follower的列表和Observer列表并与之通信
同步: Leader会把自己的数据同步给Follower和Observer, 做到多副本存储
广播: Leader接受Follower的事务Proposal,然后将这个事务性的proposal广播给其他learner

客户端

Curator是Netflix公司在原生zookeeper基础上开源的一个ZooKeeper Java客户端,现在是Apache下的一个开源项目。Curator解决了原生客户端存在的一系列问题,并提供了Fluent风格的操作API,针对业务场景,提供了如分布式锁、集群领导选举、分布式计数器、缓存机制、分布式队列等工具类

参考:https://gitee.com/wenwang2000/spring-multiple-datasource/tree/master/spring-boot-zookeeper

<!-- curator-framework -->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>版本号</version>
</dependency>
<!-- curator-recipes,业务场景解决方案 -->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>版本号</version>
</dependency>

运维命令

ZooKeeper响应少量命令。每个命令由四个字母组成。可通过telnet或nc向ZooKeeper发出命令。
这些命令默认是关闭的,需要配置4lw.commands.whitelist来打开,可打开部分或全部示例如下:

#打开指定命令
4lw.commands.whitelist=stat, ruok, conf, isro
#打开全部
4lw.commands.whitelist=*

# 安装Netcat工具,已使用nc命令   查看服务器及客户端连接状态
echo stat | nc localhost 2181

常用命令

[stat]   # 列出连接客户端列表、最大/平均/最小延迟数、数据接收/发送量、连接数、节点总数、模式    echo stat | nc 127.0.0.1 2181
[ruok]   # Are You Ok的缩写,测试服务器是否正在运行,如果在运行返回imok,否则返回空    echo ruok | nc 127.0.0.1 2181
[dump]   # 列出未经处理的会话和临时节点    echo dump | nc 127.0.0.1 2181
[conf]   # 输出Zookeeper相关服务的详细配置信息,如客户端端口,数据存储路径、最大连接数、日志路径、数据同步端口、主节点推举端口、session超时时间等等    echo conf | nc 127.0.0.1 2181
[cons]   #  输出连接到Zookeeper的服务器信息,包括队列、数据接收量、数据发送量、sessionID、操作延时、最后的操作信息等等    echo cons | nc 127.0.0.1 2181
[envi]   # 列出zookeeper的版本信息、主机名称、Java版本、java_home、class_path、服务器名称、当前登陆用户名、目录等等;    echo envi | nc 127.0.0.1 2181
[crst]   # 重置当前的所有连接、会话等等    echo crst | nc 127.0.0.1 2181
[srst ]   # 重置Zookeeper的所有统计信息    echo srst nc 127.0.0.1 2181
[srvr]   # 列出zookeeper的版本信息、数据接收/发送量、连接数、节点模式、Node数、最大/平均/最小延迟数    echo srvr | nc 127.0.0.1 2181
[wchs]   # 列出watch的总数,连接数    echo wchs | nc 127.0.0.1 2181
[wchp]   # 列出所有watch的路径及sessionID    echo wchp | nc 127.0.0.1 2181
[mntr]   # 列出集群的关键性能数据,包括zk的版本、最大/平均/最小延迟数、数据包接收/发送量、连接数、zk角色(Leader/Follower)、node数量、watch数量、临时节点数    echo mntr | nc 127.0.0.1 2181
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值