分布式安装部署

在hadoop102,hadoop103,hadoop104上分别安装ZooKeeper,参考​​本地模式安装​​,配置好一台后,其他2台通过xsync.sh命令进行同步,当然也可以安装3遍。

# 同步apache-zookeeper-3.6.3-bin/目录到hadoop102,hadoop103,hadoop104
[root@hadoop102 module]# xsync.sh apache-zookeeper-3.6.3-bin/

配置服务器编号

# 进入hadoop102的data目录下,创建myid文件,并填充值2
[root@hadoop102 module]# cd apache-zookeeper-3.6.3-bin/data/
[root@hadoop102 data]# echo 2 >> myid
# 同理,在hadoop103,hadoop104上也创建myid,分别填充3和4
[root@hadoop102 data]# ssh hadoop103
Last login: Sun Jun 27 11:07:50 2021 from 192.168.216.1
[root@hadoop103 ~]# cd /opt/module/apache-zookeeper-3.6.3-bin/data/
[root@hadoop103 data]# echo 3 >> myid
[root@hadoop103 data]# ssh hadoop104
Last login: Sun Jun 27 11:07:53 2021 from 192.168.216.1
[root@hadoop104 ~]# cd /opt/module/apache-zookeeper-3.6.3-bin/data/
[root@hadoop104 data]# echo 4 >> myid
[root@hadoop104 data]# exit
登出
Connection to hadoop104 closed.
[root@hadoop103 data]# exit
登出
Connection to hadoop103 closed.

配置zoo.cfg配置文件,集群模式,需要添加集群服务器的信息
参数解释:server.A=B:C:D。
其中,A是ZooKeeper里的myid,B是服务器的IP地址,C是Follower与Leader进行数据通信的端口,D是集群中用于选举通信的端口。

# 在hadoop102,hadoop103,hadoop104上添加如下内容
#######################cluster##########################
server.2=hadoop102:2888:3888
server.3=hadoop103:2888:3888
server.4=hadoop104:2888:3888

客户端命令行操作

因为这里使用的是ZooKeeper3.6.3版本,和视频里的版本不一样,命令也略有不同,官方文档在​​这里​​。

# 查看zkCli.sh支持的所有命令
[zk: localhost:2181(CONNECTED) 0] help
# 关闭zkCli.sh
[zk: localhost:2181(CONNECTED) 1] close
# 连接到hadoop103机器的ZooKeeper
[zk: localhost:2181(CONNECTED) 2] connect hadoop103:2181
# 创建一个持久结点,这个版本,可以不指定结点的值
[zk: localhost:2181(CONNECTED) 3] create /persistent_node
# 创建一个短暂结点,这个版本,可以不指定结点的值
[zk: localhost:2181(CONNECTED) 4] create -e /ephemeral_node
# 创建一个持久序列结点,这个版本,可以不指定结点的值
[zk: localhost:2181(CONNECTED) 5] create -s /persistent_sequential_node
# 创建一个短暂序列结点,这个版本,可以不指定结点的值
[zk: localhost:2181(CONNECTED) 6] create -s -e /ephemeral_sequential_node
# 删除结点
[zk: localhost:2181(CONNECTED) 7] delete /key
# 获取节点的值
[zk: localhost:2181(CONNECTED) 8] get /persistent_node
# 获取结点数
[zk: localhost:2181(CONNECTED) 9] getAllChildrenNumber /
# 查看子结点
[zk: localhost:2181(CONNECTED) 10] ls /
# 查看详细信息
[zk: localhost:2181(CONNECTED) 11] ls -s /persistent_node
# 递归查看子结点信息
[zk: localhost:2181(CONNECTED) 12] ls -R /
# 监听结点
[zk: localhost:2181(CONNECTED) 13] ls -w /
# 更新结点的值
[zk: localhost:2181(CONNECTED) 14] set /abc def
# 查看结点状态信息
[zk: localhost:2181(CONNECTED) 15] stat /
# 同步leader和follower之间数据信息
[zk: localhost:2181(CONNECTED) 16] sync /
# 查看ZooKeeper Client的版本信息
[zk: localhost:2181(CONNECTED) 17] version

API应用

新建一个Maven项目,添加pom.xml,这里ZooKeeper版本要和上面的保持一致。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.3</version>
</dependency>
</dependencies>
</project>

在src/main/resource下创建log4j.properties配置文件。

log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c]- %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c]- %m%n

创建ZooKeeper客户端连接

创建一个测试类。

package com.demo.zookeeper;

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.junit.Test;

import java.io.IOException;

public class TestZooKeeper {
// 这里一定是英文逗号分隔,在ConnectStringParser.ConnectStringParser()方法中
// 可以看到List<String> hostsList = StringUtils.split(connectString, ",");
private String connectionString = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
private int sessionTimeout = 2000;
private ZooKeeper zkClient;

@Test
public void init() throws IOException {
zkClient = new ZooKeeper(connectionString, sessionTimeout, new Watcher() {
public void process(WatchedEvent watchedEvent) {
}
});
}
}

看到这句话,说明连接成功了。

2021-06-28 07:20:55,594 INFO [org.apache.zookeeper.ZooKeeper]- Initiating client connection, connectString=hadoop102:2181,hadoop103:2181,hadoop104:2181 sessionTimeout=2000 watcher=com.demo.zookeeper.TestZooKeeper$1@2d6d8735

创建子结点

首先将init()方法的@Test改成@Before,添加createNode()测试方法。

@Test
public void createNode() throws KeeperException, InterruptedException {
// path:结点的key
// data:结点的value
// acl:权限控制相关
// createMode:创建的结点类型
String result = zkClient.create("/wangshaoyang", "王劭阳".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(result);
}

获取子结点并监听结点变化

@Test
public void getChildren() throws KeeperException, InterruptedException {
List<String> children = zkClient.getChildren("/", true);
for (String child : children) {
System.out.println(child);
}
// 添加sleep是为了监听子节点变化的,如果线程停止了,就监听不到了
// 同时,还要修改init()方法,当watcher有变化的时候,执行一个回调方法,也就是再调用一遍getChildren()
Thread.sleep(Long.MAX_VALUE);
}

@Before
public void init() throws IOException {
// watcher:当监听事件获取到内容的时候,进行回调的方法
zkClient = new ZooKeeper(connectionString, sessionTimeout, watchedEvent -> {
List<String> children = new ArrayList<>();
try {
children = zkClient.getChildren("/", true);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
for (String child : children) {
System.out.println(child);
}
});
}

判断ZNode是否存在

@Test
public void exist() throws KeeperException, InterruptedException {
Stat exists = zkClient.exists("/wangshaoyang", false);
System.out.println(exists == null ? "not exist" : "exist");
}

案例实战

在一个分布式系统上,主节点有多台,可以动态上下线,任意一台客户端都想实时感知到主节点的上下线操作。
我们的集群有3台Server和若干台Client,我们将3台Server注册到ZooKeeper集群中,以结点的形式存在,Server的上下线,Client通过getChildren()来判断。
首先在集群上创建servers结点。

[zk: localhost:2181(CONNECTED) 7] create /servers

Server端

package com.demo.zookeeper;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;

import java.io.IOException;

public class DistributeServer {
private static String connectString = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
private static int sessionTimeout = 2000;
private ZooKeeper zk = null;
private String parentNode = "/servers";

// 创建到ZooKeeper客户端连接
public void getConnect() throws IOException {
zk = new ZooKeeper(connectString, sessionTimeout, event -> {
});
}

// 注册服务器
public void registServer(String hostname) throws Exception {
// 当服务端下线后,结点就会消失,这时候,我们才能观察到效果,所以这里创建EPHEMERAL类型的结点,为了避免path重复,所以创建SEQUENTIAL类型结点
String create = zk.create(parentNode + "/server", hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(hostname + " is online " + create);
}

// 业务功能
public void business(String hostname) throws Exception {
System.out.println(hostname + " is working ...");
Thread.sleep(Long.MAX_VALUE);
}

public static void main(String[] args) throws Exception {
// 获取ZooKeeper连接
DistributeServer server = new DistributeServer();
server.getConnect();
// 利用ZooKeeper连接注册服务器信息
server.registServer(args[0]);
// 启动业务功能
server.business(args[0]);
}
}

IDEA设置并行运行,通过Program Arguments传参,启动3次,参数分别为hadoop102,hadoop103,hadoop104。

ZooKeeper笔记04-ZooKeeper实战_结点


在服务端打开一个zkClient,可以看到/servers下面有3个结点了。

[zk: localhost:2181(CONNECTED) 0] ls /servers
[server0000000001, server0000000002, server0000000003]

Client端

package com.demo.zookeeper;

import org.apache.zookeeper.ZooKeeper;

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

public class DistributeClient {
private static String connectString = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
private static int sessionTimeout = 2000;
private ZooKeeper zk = null;
private String parentNode = "/servers";

// 创建到 zk 的客户端连接
public void getConnect() throws IOException {
zk = new ZooKeeper(connectString, sessionTimeout, event -> {
// 再次启动监听
try {
getServerList();
} catch (Exception e) {
e.printStackTrace();
}
});
}

// 获取服务器列表信息
public void getServerList() throws Exception {
// 1 获取服务器子节点信息,并且对父节点进行监听
List<String> children = zk.getChildren(parentNode, true);
// 2 存储服务器信息列表
ArrayList<String> servers = new ArrayList<>();
// 3 遍历所有节点,获取节点中的主机名称信息
for (String child : children) {
byte[] data = zk.getData(parentNode + "/" + child, false, null);
servers.add(new String(data));
}
// 4 打印服务器列表信息
System.out.println(servers);
}

// 业务功能
public void business() throws Exception {
System.out.println("client is working ...");
Thread.sleep(Long.MAX_VALUE);
}

public static void main(String[] args) throws Exception {
// 获取ZooKeeper连接
DistributeClient client = new DistributeClient();
client.getConnect();
// 获取/servers的子节点信息,从中获取服务器信息列表
client.getServerList();
// 业务进程启动
client.business();
}
}

此时3台Server都在运行,再启动一个Client,此时会自动打印3台Server的名称,依次关掉hadoop102,hadoop103,hadoop104,在Client的Console里,可以看到结点数在依次减少,直到最后都没有了,再把Server依次启动起来,也可以看到Client的Console里依次打印了上线的Server,说明Client可以实时观测到Server端的动态上下线。