【1】概述

① 简介

Zookeeper是一个开源的分布式的、为分布式应用提供协调服务的Apache项目。

Zookeeper从设计模式角度来理解,其实是一个基于观察者模式设计的分布式服务管理框架。它负责存储和管理大家都关心的数据,然后接受观察者的注册。一旦这些数据的状态发送变化,Zookeeper就将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应。从Zookeeper存储数据和通知机制来讲,可以说Zookeeper=文件系统+通知机制。

zookeeper实现服务器节点动态上下线过程描述如下:

  • 服务端启动时去注册信息(创建的都是临时节点);
  • 客户端获取到当前在线服务器列表,并且注册监听;
  • 服务器节点下线;
  • 服务器节点上下线事件通知(Zookeeper负责);
  • 客户端收到监听通知则做出相应反应,比如重新获取在线服务器列表注册监听。

② 集群特点

集群如下图所示:
在这里插入图片描述

  • Zookeeper集群是一个leader和多个follow组成的集群。
  • 集群中只要半数以上节点存活,Zookeeper集群就能正常服务。
  • 全局数据一致,每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的。
  • 更新请求顺序进行,来自同一个Client的更新请求按其发送顺序依次执行。
  • 数据更新原子性,一次数据更新要么成功,要么失败。
  • 实时性,在一定时间范围内,Client能读到最新数据。

③ 数据结构

Zookeeper的数据模型的结构与UNIX文件系统很类似,整体上可以看做是一棵树,每个节点称做一个ZNode。每一个ZNode默认能够存储1MB的数据,每个ZNode可以通过其路径唯一标识。

如下图所示:
在这里插入图片描述


④ 应用场景

Zookeeper有诸多应用场景,如统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等。

  • 统一命名服务

在分布式环境下,经常需要对应用/服务进行统一命名,便于区别。例如IP不容易记住而域名容易记住。

在这里插入图片描述

  • 统一配置管理

分布式环境下,配置文件同步非常常见。一般要求一个集群中,所有节点的配置信息是一致的,比如kafka集群。对配置文件修改后,希望能够快速同步到各个节点上。

配置管理可交由Zookeeper实现:可将配置信息写入Zookeeper上的一个ZNode;每个客户端服务器监听这个ZNode;一旦ZNode中的数据被修改,Zookeeper将通知各个客户端。

在这里插入图片描述


  • 统一集群管理

分布式环境中,实时掌握每个节点的状态是很必要的,以便可以根据节点实时状态做出一些调整。

Zookeeper可以实现实时监控节点状态的变化:可以将节点信息写入Zookeeper的一个ZNode;监听这个ZNode可获取它的实时状态变化。

在这里插入图片描述


  • 软负载均衡

在Zookeeper中记录每台服务器的访问数,让访问数最少的服务器去处理最新的客户端请求。


⑤ 节点类型

Zookeeper提供一个多层级的节点命名空间(节点称为znode),每个节点都用一个以斜杠(/)分隔的路径表示,而且每个节点都有父节点(根节点除外),非常类似于文件系统。并且每个节点都是唯一的。

znode节点有四种类型:

  • PERSISTENT:永久节点。客户端与zookeeper断开连接后,该节点依旧存在
  • EPHEMERAL:临时节点。客户端与zookeeper断开连接后,该节点被删除
  • PERSISTENT_SEQUENTIAL:永久节点、序列化。客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
  • EPHEMERAL_SEQUENTIAL:临时节点、序列化。客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

创建这四种节点:

[zk: localhost:2181(CONNECTED) 0] create /aa test  # 创建持久化节点
Created /aa
[zk: localhost:2181(CONNECTED) 1] create -s /bb test  # 创建持久序列化节点
Created /bb0000000001
[zk: localhost:2181(CONNECTED) 2] create -e /cc test  # 创建临时节点
Created /cc
[zk: localhost:2181(CONNECTED) 3] create -e -s /dd test  # 创建临时序列化节点
Created /dd0000000003
[zk: localhost:2181(CONNECTED) 4] ls /   # 查看某个节点下的子节点
[aa, bb0000000001, cc, dd0000000003, zookeeper]
[zk: localhost:2181(CONNECTED) 5] stat /  # 查看某个节点的状态
cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x5
cversion = 3
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 5
[zk: localhost:2181(CONNECTED) 6] get /aa  # 查看某个节点的内容
test
[zk: localhost:2181(CONNECTED) 11] delete /aa  # 删除某个节点
[zk: localhost:2181(CONNECTED) 7] ls /  # 再次查看
[bb0000000001, cc, dd0000000003, zookeeper]

⑥ 事件监听

事件监听:在读取数据时,我们可以同时对节点设置事件监听,当节点数据或结构变化时,zookeeper会通知客户端。当前zookeeper针对节点的监听有如下四种事件:

  1. 节点创建:stat -w /xx

    当/xx节点创建时:NodeCreated

  2. 节点删除:stat -w /xx

    当/xx节点删除时:NodeDeleted

  3. 节点数据修改:get -w /xx

    当/xx节点数据发生变化时:NodeDataChanged

  4. 子节点变更:ls -w /xx

    当/xx节点的子节点创建或者删除时:NodeChildChanged

【2】单击版Zookeeper下载与安装

官网地址:http://zookeeper.apache.org/

在这里插入图片描述

下载地址:https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/
在这里插入图片描述


① 本机模式安装Zookeeper

这里下载的是3.5.5版本,将其放到linux某个路径下(这里有个坑,请下载apache-zookeeper-3.5.5-bin.tar.gz,该包可以正常使用。apache-zookeeper-3.5.5.tar.gz是源码包):
在这里插入图片描述
注意,安装Zookeeper一定先安装好jdk,linux安装jdk可以参考如下博文:
Linux下源码安装jdk1.8CentOS7 下rpm安装jdk1.8

安装步骤如下:

  • 解压
tar -zxvf apache-zookeeper-3.5.5-bin.tar.gz
 //或者指定解压目录
 tar -zxvf apache-zookeeper-3.5.5-bin.tar.gz -C /home/softinstall
  • 修改配置文件(把conf中的zoo_sample.cfg重命名为zoo.cfg)
cd  apache-zookeeper-3.5.5/conf
mv zoo_sample.cfg zoo.cfg
//配置数据路径
mkdir -p /home/softinstall/apache-zookeeper-3.5.5/zkData
vim zoo.cfg

在这里插入图片描述


② 操作Zookeeper

  • 启动Zookeeper服务端
[root@localhost apache-zookeeper-3.5.5-bin]# bin/zkServer.sh  start
ZooKeeper JMX enabled by default
Using config: /opt/softinstall/apache-zookeeper-3.5.5-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

[root@localhost apache-zookeeper-3.5.5-bin]# jps
27368 QuorumPeerMain
27388 Jps
  • 关闭zkserver
 bin/zkServer.sh  stop
  • 查看服务状态
[root@localhost apache-zookeeper-3.5.5-bin]# bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/softinstall/apache-zookeeper-3.5.5-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: standalone    //表示单机模式
  • 重启Zookeeper服务
//进入目录下
 bin/zkServer.sh restart
  • 启动客户端
 bin/zkCli.sh

在这里插入图片描述

  • 查看根目录下内容
[zk: localhost:2181(CONNECTED) 2] ls /
[zookeeper]
  • 退出客户端
[zk: localhost:2181(CONNECTED) 6] quit

【3】Zookeeper配置文件参数解读

配置文件主要参数如下:

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/home/softinstall/apache-zookeeper-3.5.5/zkData
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1

① tickTime=2000

该参数含义为通信心跳时间,Zookeeper服务器端和客户端心跳时间,单位毫秒。2000表示2S一次心跳检测,也就是每个tickTime时间就会发送一个心跳。它用于心跳机制,并且设置最小的session超时时间为两倍心跳时间(session的最小超时时间=2*tickTime)。

② initLimit=10

这个参数指定了在集群初始化期间,跟随者(Follower)跟领导者(Leader)之间建立连接的最大超时时间,单位是 tickTime 。

集群中的follower与leader之间初始连接时能容忍的最多心跳数(tickTime的数量),用它来限定集群中Zookeeper服务器连接到leader的时限。

10个心跳帧,10*2=20s是最开始通信时leader和follower最大延时时间。如果超过该时间,则认为二者断开通信。

③ syncLimit=5

集群正常启动后集群中leader与follower之间的最大响应时间单位。假如响应时间超过syncLimit*tickTime,leader认为follower死掉,从服务器列表中删除follower。

  • 定义: 这个参数定义了跟随者处理领导者发送的信息的最大延迟时间,同样是以 tickTime 为单位。
  • 作用: 如果跟随者处理消息的时间超过了 syncLimit,那么领导者将认为该跟随者已断开连接,并将其标记为不可用。
  • 影响: 这个参数应该大于 tickTime,但小于 initLimit,以确保集群的稳定性。

④ clientPort=2181

客户端与服务端通信的端口号,默认为2181。

⑤ dataDir

zookeeper的数据目录。另外还有日志文件目录默认在../apache-zookeeper-3.5.5-bin/logs下。


【4】zookeeper常见命令

① ZK服务端命令

启动ZK服务

sh bin/zkServer.sh start

查看服务状态

sh bin/zkServer.sh status

停止ZK服务

 sh bin/zkServer.sh stop

重启ZK服务

sh bin/zkServer.sh restart

② zk客户端命令

ZooKeeper命令行工具类似于Linux的shell环境使用它我们可以简单的对ZooKeeper进行访问,数据创建,数据修改等操作. 使用 zkCli.sh -server 127.0.0.1:2181 连接到 ZooKeeper 服务,连接成功后,系统会输出 ZooKeeper 的相关环境以及配置信息。

命令行工具的一些简单操作如下:

  • 显示根目录下、文件: ls / 使用 ls 命令来查看当前 ZooKeeper 中所包含的内容
  • 显示根目录下、文件: ls2 / 查看当前节点数据并能看到更新次数等数据
  • 创建文件,并设置初始内容: create /zk “test” 创建一个新的 znode节点“ zk ”以及与它关联的字符串
  • 获取文件内容: get /zk 确认 znode 是否包含我们所创建的字符串
  • 修改文件内容: set /zk "zkbak" 对 zk 所关联的字符串进行设置
  • 删除文件: delete /zk 将刚才创建的 znode 删除
  • 退出客户端: quit
  • 帮助命令: help

③ ZooKeeper 常用四字命令

ZooKeeper 支持某些特定的四字命令字母与其的交互。它们大多是查询命令,用来获取 ZooKeeper 服务的当前状态及相关信息。用户在客户端可以通过 telnet 或 nc 向 ZooKeeper 提交相应的命令

  • 可以通过命令:echo stat|nc 127.0.0.1 2181 来查看哪个节点被选择作为follower或者leader
  • 使用echo ruok|nc 127.0.0.1 2181 测试是否启动了该Server,若回复imok表示已经启动。
  • echo dump| nc 127.0.0.1 2181 ,列出未经处理的会话和临时节点。
  • echo kill | nc 127.0.0.1 2181 ,关掉server
  • echo conf | nc 127.0.0.1 2181 ,输出相关服务配置的详细信息。
  • echo cons | nc 127.0.0.1 2181 ,列出所有连接到服务器的客户端的完全的连接 / 会话的详细信息。
  • echo envi |nc 127.0.0.1 2181 ,输出关于服务环境的详细信息(区别于 conf 命令)。
  • echo reqs | nc 127.0.0.1 2181 ,列出未经处理的请求。
  • echo wchs | nc 127.0.0.1 2181 ,列出服务器 watch 的详细信息。
  • echo wchc | nc 127.0.0.1 2181 ,通过 session 列出服务器 watch 的详细信息,它的输出是一个与 watch 相关的会话的列表。
  • echo wchp | nc 127.0.0.1 2181 ,通过路径列出服务器 watch 的详细信息。它输出一个与 session 相关的路径。

【5】常见API与用法

ZooKeeper的java客户端有:原生客户端、ZkClient、Curator框架(类似于redisson,有很多功能性封装)。

原生客户端依赖:

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.7.0</version>
</dependency>

常见API与用法:

public class ZkTest {

    public static void main(String[] args) throws KeeperException, InterruptedException {

        // 获取zookeeper链接
        CountDownLatch countDownLatch = new CountDownLatch(1);
        ZooKeeper zooKeeper = null;
        try {
            zooKeeper = new ZooKeeper("127.0.0.1:2181", 30000, new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    if (Event.KeeperState.SyncConnected.equals(event.getState()) 
                            && Event.EventType.None.equals(event.getType())) {
                        System.out.println("获取链接成功。。。。。。" + event);
                        countDownLatch.countDown();
                    }
                }
            });

            countDownLatch.await();
        } catch (Exception e) {
            e.printStackTrace();
        }

创建一个节点,1-节点路径 2-节点内容 3-节点的访问权限 4-节点类型:

// 创建一个节点,1-节点路径 2-节点内容 3-节点的访问权限 4-节点类型
// OPEN_ACL_UNSAFE:任何人可以操作该节点
// CREATOR_ALL_ACL:创建者拥有所有访问权限
// READ_ACL_UNSAFE: 任何人都可以读取该节点

//永久节点
// zooKeeper.create("/jane/aa", "haha~~".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

//临时节点
zooKeeper.create("/test", "haha~~".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);

//永久序列化节点
// zooKeeper.create("/jane/cc", "haha~~".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);

//临时序列化节点
// zooKeeper.create("/jane/dd", "haha~~".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

判断节点是否存在

        Stat stat = zooKeeper.exists("/test", true);
        if (stat != null){
            System.out.println("当前节点存在!" + stat.getVersion());
        } else {
            System.out.println("当前节点不存在!");
        }

判断节点是否存在,同时添加监听。需要注意的是,节点的监听事情是一次性事件。

        zooKeeper.exists("/test", event -> {
        });

获取一个节点的数据

        // 获取一个节点的数据
        byte[] data = zooKeeper.getData("/jane/ss0000000001", false, null);
        System.out.println(new String(data));

查询一个节点的所有子节点

        // 查询一个节点的所有子节点
        List<String> children = zooKeeper.getChildren("/test", false);
        System.out.println(children);

更新一个节点的内容

        // 更新
        zooKeeper.setData("/test", "wawa...".getBytes(), stat.getVersion());

删除一个节点

        // 删除一个节点
        //zooKeeper.delete("/test", -1);

断开链接

        if (zooKeeper != null){
            zooKeeper.close();
        }
    }
}

【6】监听服务器动态上下线实例

背景需求如下:某分布式系统中,主节点有多台,可能进行动态上下线,任意一台客户端都能实时感知到主节点服务器的上下线状态。

客户端代码

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

public class DistributeClient {

	public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
		
		DistributeClient client = new DistributeClient();
		
		// 1 获取zookeeper集群连接
		client.getConnect();
		
		// 2 注册监听
		client.getChlidren();
		
		// 3 业务逻辑处理
		client.business();
		
	}

	private void business() throws InterruptedException {
		Thread.sleep(Long.MAX_VALUE);
	}

	private void getChlidren() throws KeeperException, InterruptedException {
		
		List<String> children = zkClient.getChildren("/servers", true);
		
		// 存储服务器节点主机名称集合
		ArrayList<String> hosts = new ArrayList<String>();
		
		for (String child : children) {
			
			byte[] data = zkClient.getData("/servers/"+child, false, null);
			
			hosts.add(new String(data));
		}
		
		// 将所有在线主机名称打印到控制台
		System.out.println(hosts);
		
	}

	private String connectString="127.0.0.1:3181,127.0.0.1:3182,127.0.0.1:3183";
	private int sessionTimeout = 2000;
	private ZooKeeper zkClient;

	private void getConnect() throws IOException {
	
		zkClient = new ZooKeeper(connectString , sessionTimeout , new Watcher() {
			
			public void process(WatchedEvent event) {
				
				try {
					getChlidren();
				} catch (KeeperException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		
	}
}

服务端代码

import java.io.IOException;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooDefs.Ids;

public class DistributeServer {

	public static void main(String[] args) throws Exception {
		
		DistributeServer server = new DistributeServer();
		
		// 1 连接zookeeper集群
		server.getConnect();
		
		// 2 注册节点
		server.regist(args[0]);
		
		// 3 业务逻辑处理
		server.business();
	}

	private void business() throws InterruptedException {
	
		Thread.sleep(Long.MAX_VALUE);
	}

	private void regist(String hostname) throws KeeperException, InterruptedException {
		//注意,创建的是临时、有序号节点。这样服务器down了节点也就不存在了
		String path = zkClient.create("/servers/server", hostname.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
		
		System.out.println(hostname +"is online ");
		
	}

	private String connectString="127.0.0.1:3181,127.0.0.1:3182,127.0.0.1:3183";
	private int sessionTimeout = 2000;
	private ZooKeeper zkClient;

	private void getConnect() throws IOException {
		
		zkClient = new ZooKeeper(connectString , sessionTimeout , new Watcher() {
			
			public void process(WatchedEvent event) {
				// TODO Auto-generated method stub
				
			}
		});
	}
}

zookeeper更多使用参考博文:ZooKeeper学习之内部原理

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐