1 Zookeeper简介
Zookeeper是一个开源的分布式的,为分布式应用程序提供高性能协调服务的Apache项目。
1.1 Zookeeper工作机制
Zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接收观察者的注册,一旦这些数据的状态发生变化,Zookeeper就将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应。
1.2 Zookeeper特点
- Zookeeper集群由一个Leader和多个Fllower组成。
- Zookeeper集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。
- 全局数据一致:客户端与单个Zookeeper服务器间维护着一个TCP连接,通过它发送请求,获取响应,获取监视事件并发送心跳,如果与服务器的TCP连接断开,则客户端将连接到其他的服务器,获取的数据是一致的。
- 更新请求顺序执行,来自同一Client的更新请求按其发送顺序依次执行。
- 数据更新原子性,一次数据更新要么成功,要么失败。
- 实时性,在一定时间范围内,Client能读到最新数据。
1.3 数据结构
Zookeeper数据模型的结构与Unix文件系统类似,整体上可以看作是一个树形结构,每个节点称作一个ZNode。每个ZNode默认能够存储1MB的数据,每个ZNode都可以通过其路径唯一标识。
1.4 应用场景
Zookeeper提供的服务包括:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等。
2 Zookeeper安装
2.1 本地模式安装部署
- 安装JDK
- copy Zookeeper安装包到linux系统下
- 解压到指定目录
#解压zookeeper-3.4.13.tar.gz [root@bme244 software]# tar -zxvf zookeeper-3.4.13.tar.gz -C /opt/module/
- 配置修改
#配置文件 [root@bme244 zookeeper-3.4.13]# mv conf/zoo_sample.cfg conf/zoo.cfg #修改数据存放目录 [root@bme244 zookeeper-3.4.13]# vim conf/zoo.cfg dataDir=../data #创建目录 [root@bme244 zookeeper-3.4.13]# mkdir data
- 操作Zookeeper
#{start|start-foreground|stop|restart|status|print-cmd} #Zookeeper启动 [root@bme244 zookeeper-3.4.13]# bin/zkServer.sh start #客户端连接 [root@bme244 zookeeper-3.4.13]# bin/zkCli.sh
2.2 配置参数
# 通信心跳数,Zookeeper服务器与客户端心跳间隔(ms),最小的session超时时间为2*tickTime。
tickTime=2000
# 初始通信时限,集群中Follower与Leader之间初始连接时能容忍的最多tickTime数,用它来限定集群中的Zookeeper服务器连接到Leader的时限。
initLimit=10
# 同步通信时限,集群中Leader与Follower之间的最大响应时间,假如响应超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。
syncLimit=5
# 数据文件目录+数据持久化路径
dataDir=../data
# 客户端连接端口
clientPort=2181
# 客户端最大连接数
#maxClientCnxns=60
# 保留到dataDir中的快照数
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
2.3 分布式安装部署
- 集群规划
在hadoop102、hadoop103和hadoop104三个节点上部署Zookeeper。 - 三个节点上分别解压安装
[yut@hadoop101 module]$ tar -zxvf ../software/zookeeper-3.5.4-beta.tar.gz -C ./
- 配置服务器编号
三个节点下相同操作,注意填写的myid不同,此处hadoop101、hadoop102、hadoop103分别是1、2、3#创建dataDir目录 [yut@hadoop101 zookeeper-3.5.4-beta]$ mkdir data #创建myid文件,并填写编号 [yut@hadoop101 zookeeper-3.5.4-beta]$ vim data/myid 1
- 配置zoo.cfg文件
#使用绝对路径,经测试集群模式下使用相对路径无法启动 dataDir=/opt/module/zookeeper-3.5.4-beta/data #集群配置,注意防火墙开启相应端口 server.1=hadoop101:2888:3888 server.2=hadoop102:2888:3888 server.3=hadoop103:2888:3888
server.A=B:C:D
A:myid,zookeeper启动时会读取myid文件并与zoo.cfg中配置信息比较确定server
B:服务器id地址
C:集群中Leader与Follower交换信息的端口
D:Leader服务器挂掉后,用于选举相互通信的端口 - 启动集群
[yut@hadoop101 zookeeper-3.5.4-beta]$ bin/zkServer.sh start [yut@hadoop102 zookeeper-3.5.4-beta]$ bin/zkServer.sh start [yut@hadoop103 zookeeper-3.5.4-beta]$ bin/zkServer.sh start
2.4 客户端操作
2.4.1 shell操作
主要命令:
命令基本语法 | 功能描述 |
---|---|
help | 显示所有操作命令 |
ls path [watch] | 使用 ls 命令来查看当前znode中所包含的内容 |
ls2 path [watch] | 查看当前节点数据并能看到更新次数等数据 |
create | 普通创建,-s 含有序列,-e 临时(重启或者超时消失) |
get path [watch] | 获得节点的值 |
set | 设置节点的具体值 |
stat | 查看节点状态 |
delete | 删除节点 |
rmr | 递归删除节点 |
2.4.2 Stat结构体
- cZxid:创建节点的事务zxid,每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前发生。在 ZAB ( ZooKeeper Atomic Broadcast , ZooKeeper 原子消息广播协议) 协议的事务编号 Zxid设计中,Zxid 是一个 64 位的数字,其中低 32 位是一个简单的单调递增的计数器,针对客户端每一个事务请求,计数器加 1;而高 32 位则代表 Leader 周期 epoch 的编号,每个当选产生一个新的 Leader 服务器,就会从这个 Leader 服务器上取出其本地日志中最大事务的 ZXID,并从中读取epoch 值,然后加 1,以此作为新的 epoch,并将低 32 位从 0 开始计数。Zxid(Transaction id)类似于 RDBMS 中的事务 ID,用于标识一次更新操作的 Proposal(提议)ID。为了保证顺序性,该 zkid 必须单调递增。
- ctime:znode被创建的毫秒数(从1970年开始)
- mZxid:znode最后更新的事务zxid
- mtime:znode最后修改的毫秒数(从1970年开始)
- pZxid:znode最后更新的子节点zxid
- cversion:znode子节点变化号,znode子节点修改次数
- dataversion:znode数据变化号
- aclVersion:znode访问控制列表的变化号
- ephemeralOwner:如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0。
- dataLength:znode的数据长度
- numChildren:znode子节点数量
3 Zookeeper内部原理
3.1 选举机制
半数机制:集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器。Zookeeper虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的。
zookeeper提供了三种方式:
- LeaderElection
- AuthFastLeaderElection
- FastLeaderElection (最新默认)
以下是默认的算法FastLeaderElection选举流程简述:
目前有5台服务器,每台服务器均没有数据,它们的编号分别是1,2,3,4,5,按编号依次启动,它们的选择举过程如下:
- 服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking(选举状态)。
- 服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。
- 服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为领导者,服务器1,2成为小弟。
- 服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为小弟。
- 服务器5启动,后面的逻辑同服务器4成为小弟。
https://www.cnblogs.com/shuaiandjun/p/9383655.html
3.2 节点类型
Zookeeper的节点根据生命周期可以分为二种类型:
- 持久(Persistent):客户端和服务端断开连接后,创建的节点不删除
- 短暂(Ephemeral):客户端和服务端断开连接后,创建的节点自己删除
创建znode时可以设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护。在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序
3.3 监听器
3.3.1 监听器原理
3.3.2 监听服务器节点上下线案例
- 创建maven工程,引入以下依赖
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.10</version>
</dependency>
- 配置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
- 编写服务端
public class DistributeServer {
public static void main(String[] args) throws Exception {
DistributeServer server = new DistributeServer();
//连接Zookeeper集群
server.getConnect();
//注册节点
server.register(args[0]);
//业务逻辑处理
server.business();
}
private void business() throws InterruptedException {
Thread.sleep(Long.MAX_VALUE);
}
private void register(String hostname) throws KeeperException, InterruptedException {
String path = zkClient.create("/servers/server", hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(hostname + " is online");
}
private String connectString="hadoop101:2181,hadoop102:2181,hadoop103:2181";
private int sessionTimeout = 2000;
private ZooKeeper zkClient;
private void getConnect() throws IOException {
zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
}
});
}
}
- 编写客户端
public class DistributeClient {
public static void main(String[] args) throws Exception {
DistributeClient client = new DistributeClient();
//获取Zookeeper连接
client.getConnect();
//注册监听
client.register();
//业务逻辑处理
client.business();
}
private void business() throws InterruptedException {
Thread.sleep(Long.MAX_VALUE);
}
private void register() throws KeeperException, InterruptedException {
List<String> children = zkClient.getChildren("/servers", true);
//存储服务器节点主机名称集合
ArrayList<String> hosts = new ArrayList<>();
for (String child : children) {
byte[] data = zkClient.getData("/servers/" + child, false, null);
hosts.add(new String(data));
}
//将所有在线主机名称打印到控制台
System.out.println(hosts);
}
private String connectString="hadoop101:2181,hadoop102:2181,hadoop103:2181";
private int sessionTimeout = 2000;
private ZooKeeper zkClient;
private void getConnect() throws IOException {
zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
try {
register();
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}