1 Zookeeper
1.1 概述
Zookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。
Zookeeper工作机制:
1.2 特点
1)Zookeeper:一个领导者,多个跟随着组成的集群。
2)集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。
3)全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的。
4)更新请求数据进行,来自同一个Client的更新请求按照器发送次序依次执行。
5)数据更新原子性,一次数据更行要么成功,要么失败。
6)实时性,在一定时间范围内,Client能读到最新数据。
1.3 数据结构
Zookeeper数据模型的结构于Unix文件系统很类似,整体上可以看作一棵树,每个节点称作一个ZNode。每一个ZNode默认能够存储1MB的数据,每个ZNode都可以通过其路径唯一标识。
1.4 Zookeeper应用场景
Zookeeper提供的服务包括:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等。
1)统一命名服务:
2)统一配置管理:
3)统一集群管理
4)服务器动态上下线
5)软负载均衡
2 Zookeeper安装
1)将zookeeper安装包拷贝到 /opt/software
2)解压压缩文件
tar zxf apache-zookeeper-3.5.9-bin.tar.gz -C /opt/module/
3)配置环境变量
sudo vim /etc/profile.d/my_env.sh
添加:
#ZOOKEEPER_HOME
export ZOOKEEPER_HOME=/opt/module/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin
同步环境变量到集群
sudo xsync.sh /etc/profile.d
4)配置服务器编号
#在/opt/module/zookeeper下创建zkData文件夹
mkdir -p zkData
#在/opt/module/zookeeper/zkData下创建myid文件
touch myid
#编辑myid文件,填写对应server的编号
2
#分发集群上,同时修改对应的myid文件
xsync myid
5)修改配置文件
cd /opt/module/zookeeper/conf
mv zoo_sample.cfg zoo.cfg
vim zoo.cfg
dataDir=/opt/module/zookeeper/zkData #修改数据存储路径配置
#增加配置
server.2=hadoop102:2888:3888
server.3=hadoop103:2888:3888
server.4=hadoop104:2888:3888
同步配置文件
sudo xsync.sh zoo.cfg
配置参数解读:server.A=B:C:D
A是一个数字,表示这个是第几号服务器;
集群模式下配置一个文件myid,这个文件在dataDir目录下,这个文件里面有一个数据就是A的值,Zookeeper启动时读取此文件,拿到里面的数据与zoo.cfg里面的配置信息比较从而判断到底是哪个server。
B是这个服务器的地址;
C是这个服务器Follower与集群中的Leader服务器交换信息的端口;
D是万一集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口。
6)启动zookeeper
bin/zkServer.sh start #启动
bin/zkServer.sh status #查看状态
102:(follower)
103:(leader)
104:(follower)
3.Zookeeper操作
3.1 命令行操作
zkCli.sh #进入zookeeper命令行
命令基本语法 | 功能描述 |
---|---|
help | 显示所有操作命令 |
ls path | 使用 ls 命令来查看当前znode的子节点 |
-w | 监听子节点变化 |
-s | 附加次级信息 |
create | 普通创建 |
-s | 含有序列 |
-e | 临时(重启或者超时消失) |
get path | 获得节点的值 |
-w | 监听节点内容变化 |
-s | 附加次级信息 |
set | 设置节点的具体值 |
stat | 查看节点状态 |
delete | 删除节点 |
deleteall | 递归删除节点 |
3.2 Zookeeper API
3.2.1 环境搭建
1)创建Maven工程
2)pom文件中添加依赖
<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>
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.9</version>
</dependency>
</dependencies>
3)src/main/resources目录下创建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
3.2.2 实际操作(增删改查)
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
public class ZkClient {
private ZooKeeper zooKeeper;
/**
* 创建zookeeper对象,建立连接
*
* @throws IOException
*/
@Before
public void before() throws IOException {
String connectString = "hadoop102:2181,hadoop103:2181,hadoop104:2181";//zookeeper连接地址
int sessionTimeout = 2000;//会话超时时间
//1.创建对象
zooKeeper = new ZooKeeper(
connectString, //Zookeeeper地址
sessionTimeout,//超时时间
new Watcher() { //监听的回调函数 异步通信 返回的通过回调函数通知
@Override
public void process(WatchedEvent watchedEvent) {
}
});
}
/**
* 创建节点
*
* @throws IOException
* @throws KeeperException
* @throws InterruptedException
*/
@Test
public void create() throws IOException, KeeperException, InterruptedException {
//2.操作集群
//创建节点
zooKeeper.create(
"/testAPI", //节点路径
"123".getBytes(),//节点值
ZooDefs.Ids.OPEN_ACL_UNSAFE, //访问控制列表
CreateMode.PERSISTENT // 节点类型 (四种)
);
}
/**
* ls查看子节点
*
* @throws KeeperException
* @throws InterruptedException
*/
@Test
public void ls() throws KeeperException, InterruptedException {
List<String> children = zooKeeper.getChildren(
"/",//查询子节点的路径(根目录)
new Watcher() {//监听,如果根目录子节点有变化,则输出回调函数中的内容
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("自定义回调函数");
}
}
);
for (String child : children) {
System.out.println(child);
}
Thread.sleep(Long.MAX_VALUE);//阻塞主线程,让子线程能监听到请求
}
/**
* get 查询节点的值
*
* @throws KeeperException
* @throws InterruptedException
* @throws IOException
*/
@Test
public void get() throws KeeperException, InterruptedException, IOException {
Stat stat = new Stat();//节点状态 stat结构体
byte[] data = zooKeeper.getData("/testAPI", false, stat);
System.out.println(stat.getDataLength());
System.out.write(data);
System.out.println();
}
/**
* 查询节点状态
*
* @throws KeeperException
* @throws InterruptedException
*/
@Test
public void stat() throws KeeperException, InterruptedException {
Stat stat = zooKeeper.exists("/testAPI", false);
if (stat == null) {
System.out.println("节点不存在");
} else {
System.out.println(stat);
}
}
/**
* 修改节点
*
* @throws KeeperException
* @throws InterruptedException
*/
@Test
public void set() throws KeeperException, InterruptedException {
String node = "/testAPI";
Stat stat = zooKeeper.exists(node, false);
if (stat == null) {
System.out.println("节点不存在");
} else {
zooKeeper.setData(
"/testAPI",
"abcd".getBytes(),
stat.getVersion()//修改次数 乐观锁 保证要修改的与实际修改的是同一个东西
);
}
}
/**
* 删除节点
*
* @throws KeeperException
* @throws InterruptedException
*/
@Test
public void delete() throws KeeperException, InterruptedException {
zooKeeper.delete(
"/testAPI",
1
);
}
/**
* 关闭资源
* @throws InterruptedException
*/
@After
public void after() throws InterruptedException {
zooKeeper.close();
}
}
4 Zookeeper内部原理
4.1 节点类型
4.2 Stat结构体
(1)czxid-创建节点的事务zxid
每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。
事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前发生。
(2)ctime - znode被创建的毫秒数(从1970年开始)
(3)mzxid - znode最后更新的事务zxid
(4)mtime - znode最后修改的毫秒数(从1970年开始)
(5)pZxid-znode最后更新的子节点zxid
(6)cversion - znode子节点变化号,znode子节点修改次数
(7)dataversion - znode数据变化号
(8)aclVersion - znode访问控制列表的变化号
(9)ephemeralOwner- 如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0。
(10)dataLength- znode的数据长度
(11)numChildren - znode子节点数量
4.3 监听器原理
4.4 选举机制
(1)半数机制:集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器。
(2)Zookeeper虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的。
(3)以一个简单的例子来说明整个选举的过程。
假设有五台服务器组成的Zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动,来看看会发生什么。
(1)服务器1启动,发起一次选举。服务器1投自己一票。此时服务器1票数一票,不够半数以上(3票),选举无法完成,服务器1状态保持为LOOKING;
(2)服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交换选票信息:此时服务器1发现服务器2的ID比自己目前投票推举的(服务器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一样当小弟。