目录
一、概述
ZooKeeper 是 Apache 软件基金会的一个软件项目,它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册;一个典型的分布式数据一致性的解决方案,分布式应用程序可以基于它实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
1.1 工作机制
一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper就将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应。
1.2 特点
- Zookeeper:一个领导者(Leader),多个跟随者(Follower)组成的集群
- 集群中只要有半数以上节点存活,Zookeeper集群就能正常服务(Zookeeper适合安装奇数台服务器)
- 全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的
- 更新请求顺序执行,来自同一个Client的更新请求按其发送顺序依次执行
- 数据更新原子性,一次数据更新要么成功,要么失败
- 实时性,在一定时间范围内,Client能读到最新数据
1.3 数据结构
ZooKeeper 数据模型的结构与Unix文件系统很类似,整体上可以看作是一棵树,每个节点称做一个ZNode。每一个ZNode默认能够存储1MB
的数据,每个ZNode都可以通过其路径唯一标识。
二、安装
2.1 本地安装(单机)
详细安装配置步骤参照 Zookeeper安装配置
2.2 docker安装(集群)
- 编写 zookeeper-cluster.yml 文件,具体内容如下:
version: '3.0'
services:
zookeeper1:
image: zookeeper:3.8.0
restart: always
hostname: zookeeper1
ports:
- 2181:2181
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=0.0.0.0:2888:3888;2181 server.2=zookeeper2:2888:3888;2181 server.3=zookeeper3:2888:3888;2181
zookeeper2:
image: zookeeper:3.8.0
restart: always
hostname: zookeeper2
ports:
- 2182:2181
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zookeeper1:2888:3888;2181 server.2=0.0.0.0:2888:3888;2181 server.3=zookeeper3:2888:3888;2181
zookeeper3:
image: zookeeper:3.8.0
restart: always
hostname: zookeeper3
ports:
- 2183:2181
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zookeeper1:2888:3888;2181 server.2=zookeeper2:2888:3888;2181 server.3=0.0.0.0:2888:3888;2181
- 启动集群容器:
COMPOSE_PROJECT_NAME=cluster docker-compose -f zookeeper-cluster.yml up -d
- 查看运行中的 Zookeeper 集群:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
15d7acedb767 zookeeper:3.8.0 "/docker-entrypoint.…" 6 seconds ago Up 4 seconds 2888/tcp, 3888/tcp, 8080/tcp, 0.0.0.0:2182->2181/tcp, :::2182->2181/tcp cluster-zookeeper2-1
3e5d406c05b6 zookeeper:3.8.0 "/docker-entrypoint.…" 6 seconds ago Up 4 seconds 2888/tcp, 3888/tcp, 8080/tcp, 0.0.0.0:2183->2181/tcp, :::2183->2181/tcp cluster-zookeeper3-1
f47df87b1ac9 zookeeper:3.8.0 "/docker-entrypoint.…" 6 seconds ago Up 4 seconds 2888/tcp, 3888/tcp, 0.0.0.0:2181->2181/tcp, :::2181->2181/tcp, 8080/tcp cluster-zookeeper1-1
非docker方式集群搭建可参考 Zookeeper linux 服务端集群搭建步骤
三、选举机制
3.1 重要参数及选举状态
三大参数:
- 服务器 ID(myid/sid):编号越大在选举算法中权重越大
- 事务 ID(zxid):值越大说明数据越新,权重越大
- 逻辑时钟(epoch-logicalclock):同一轮投票过程中的逻辑时钟值是相同的,每投完一次值会增加
四大选举状态:
- LOOKING: 竞选状态
- FOLLOWING: 随从状态,同步 leader 状态,参与投票
- OBSERVING: 观察状态,同步 leader 状态,不参与投票
- LEADING: 领导者状态
3.2 第一次启动
每个节点启动的时候都 LOOKING 观望状态,接下来就开始进行选举主流程。这里选取五台机器组成的集群为例。第一台服务器 server1启动时,无法进行 leader 选举,当第二台服务器 server2 启动时,两台机器可以相互通信,进入 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一样当小弟。
3.3 运行过程中
当集群中 leader 服务器出现宕机或者不可用情况时,整个集群无法对外提供服务,进入新一轮的 leader 选举。具体过程如下:
(1)变更状态。leader 挂后,其他非Oberver服务器将自身服务器状态变更为 LOOKING。
(2)每个 server 发出一个投票。在运行期间,每个服务器上 zxid 可能不同。
(3)处理投票。规则同启动过程。
(4)统计投票。与启动过程相同。
(5)改变服务器状态。与启动过程相同。
选举Leader规则:
EPOCH大的直接胜出;EPOCH相同,事务id(zxid)大的胜出;事务id相同,服务器id(sid)大的胜出
四、节点(znode)结构
zookeeper 中的所有存储的数据是由 znode 组成的,节点也称为 znode,并以 key/value 形式存储数据。整体结构类似于 linux 文件系统的模式以树形结构存储。其中根路径以 /
开头。节点类型可分为两大类:
(1)持久(Persistent)
:客户端和服务器端断开连接后,创建的节点不删除
- 持久化节点:客户端与Zookeeper断开连接后,该节点依旧存在
- 持久化顺序节点:客户端与Zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
(2)短暂(Ephemeral)
:客户端和服务器端断开连接后,创建的节点自己删除
- 临时节点:客户端与Zookeeper断开连接后,该节点被删除
- 临时顺序节点:客户端与 Zookeeper 断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号
五、客户端命令行操作
命令基本语法 | 功能描述 |
---|---|
help | 显示所有操作命令 |
ls path | 查看当前 znode 的子节点 [可监听](-w 监听子节点变化;-s 附加次级信息) |
create | 创建普通节点(-s 有序;-e 临时) |
get path | 获得节点的值 [可监听](-w 监听子节点变化;-s 附加次级信息) |
set | 设置(修改)节点的具体值 |
stat | 查看节点状态 |
delete | 删除节点 |
deleteall | 递归删除节点 |
Znode 的状态属性:
[zk: localhost:2181(CONNECTED) 1] ls /demo -s
[]cZxid = 0x100000004 -- 创建节点时的事务ID
ctime = Sat Oct 15 16:26:56 UTC 2022 -- 创建节点时的时间
mZxid = 0x100000004 -- 最后修改节点时的事务ID
mtime = Sat Oct 15 16:26:56 UTC 2022 -- 最后修改节点时的时间
pZxid = 0x100000004 -- 表示该节点的子节点列表最后一次修改的事务ID,添加子节点或删除子节点就会影响子节点列表,但是修改子节点的数据内容则不影响该ID
cversion = 0 -- 子节点版本号,子节点每次修改版本号加1
dataVersion = 0 -- 数据版本号,数据每次修改该版本号加1
aclVersion = 0 -- 权限版本号,权限每次修改该版本号加1
ephemeralOwner = 0x0 -- 创建该临时节点的会话的sessionID。(如果该节点是持久节点,那么这个属性值为0)
dataLength = 0 -- 该节点的数据长度
numChildren = 0 -- 该节点拥有子节点的数量
六、客户端API操作
Zookeeper 客户端连接常见两种方式:原生 API 和 Curator 两种方式
6.1 原生 API
建立maven项目及引入相关依赖:
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.8.0</version>
</dependency>
建立连接:
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
final CountDownLatch countDownLatch = new CountDownLatch(1);
ZooKeeper zooKeeper =
new ZooKeeper("127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183",
4000, event -> {
if (Watcher.Event.KeeperState.SyncConnected == event.getState()) {
// 如果收到了服务端的响应事件,连接成功
countDownLatch.countDown();
}
});
countDownLatch.await();
// CONNECTED
System.out.println(zooKeeper.getState());
// 创建持久接待您
zooKeeper.create("/demo","zookeeper-znodes-values".getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}
6.2 Curator
Curator 是 Netflix 公司开源的一套 zookeeper 客户端框架,解决了很多 Zookeeper 客户端非常底层的细节开发工作,包括连接重连、反复注册 Watcher 和 NodeExistsException 异常等。
建立maven项目及引入相关依赖:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.2.0</version>
</dependency>
建立连接:
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
CuratorFramework curatorFramework = CuratorFrameworkFactory.
builder().connectString("127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183")
.sessionTimeoutMs(4000)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.namespace("").build();
curatorFramework.start();
Stat stat = new Stat();
//查询节点数据
byte[] bytes = curatorFramework.getData().storingStatIn(stat).forPath("/demo");
System.out.println(new String(bytes));
curatorFramework.close();
}