这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战
前言
如果你稍微了解了一下 Zookeeper,你会发现有很多服务都依赖与 Zookeeper 来实现功能,比如 Dubbo 使用 Zookeeper 作为自己的注册中心,Kafka 基于 Zookeeper 实现来自己的功能,同时 Zookeeper 也可以用来实现分布式锁
因此系统的学习 Zookeeper 是必不可少的一环,也是必要的基础技能
简介
ZooKeeper 是一个开源的分布式协调服务,它的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用
ZooKeeper 为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案;同时,由于Zookeeper 将数据保存在内存中,性能非常的棒,尤其是在读多于写的场景下,因为写会导致服务集群进入同步状态
Zookeeper 有以下特点:
- 顺序一致性: 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
- 原子性: 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用
- 单一系统映像 : 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的
- 可靠性: 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖
Zookeeper 常见的应用常见:
- 分布式锁:通过创建唯一节点获得分布式锁,当获得锁的一方执行完相关代码或者是挂掉之后就释放锁
- 命名服务:可以通过 ZooKeeper 的顺序节点生成全局唯一 ID
- 数据的发布/订阅:通过 Watcher 机制 可以很方便地实现数据发布/订阅。当你将数据发布到 ZooKeeper 被监听的节点上,其他机器可通过监听 ZooKeeper 上节点的变化来实现配置的动态更新
1. Zookeeper 的数据模型
首先来看下一张模型图:
看到这个模型,是不是很熟悉,有 Linux 那味了!
Zookeeper 采用了类似于 Linux 文件系统路径的多叉树型结构:
- 最上层的节点根节点,用
/表示,在 Zookeeper 中每个节点被称为 ZNode,它的路径是唯一的,同时也是 Zookeeper 中最小的数据单元 - 所有节点都可以存放数据,这些数据可以是数组、字符串或二进制序列
- Zookeeper 主要用于协调服务,而不是存储数据,所以 ZNode 的存储上限被设定为 1M
2. ZNode 的四种类型
- 持久节点:一旦创建就一直存在,直到被手动删除
- 临时节点:临时节点与客户端的会话绑定,可以这么理解,谁把它搞出来,提裤子就走,那么这个节点跟着就自杀了;临时节点只能做叶子节点,不能创建子节点
- 持久顺序节点:除了拥有持久节点的特性,其子节点还拥有顺序性,如
/node1/001、/node2/002 - 临时顺序节点:除了拥有临时节点的特性之外,同名的临时顺序节点会自动在后面加上序号
3. ZNode 的主要构成
- stat:节点状态,在客户端可以通过
stat命令来获取节点的状态信息 - data:节点存放的数据内容,在客户端可以通过
get命令来获取节点里存的数据
例:
[zk: localhost:2181(CONNECTED) 5] create /abc
Created /abc
[zk: localhost:2181(CONNECTED) 6] get /abc
null
[zk: localhost:2181(CONNECTED) 7] stat /abc
cZxid = 0x21b
ctime = Wed Aug 18 15:00:19 CST 2021
mZxid = 0x21b
mtime = Wed Aug 18 15:00:19 CST 2021
pZxid = 0x21b
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 0
节点状态信息含义:
4. Zookeeper 集群
为了保证高可用,最好是以集群形态来部署 ZooKeeper,而且最好是奇数台(通常三台即可构建出一个集群),这样只要集群中大部分机器是可用的,那么 ZooKeeper 本身仍然是可用的
在上图,Zookeeper 的集群架构图中,每个 Server 代表以一个 Zookeeper 实例,集群间通过 ZAB 协议(ZooKeeper Atomic Broadcast)来保持数据的一致性
4.1 集群之间的角色
| 角色 | 说明 |
|---|---|
| Leader | 为客户端提供读和写的服务,负责投票的发起和决议,更新系统状态。 |
| Follower | 为客户端提供读服务,如果是写服务则转发给 Leader。参与选举过程中的投票。 |
| Observer | 为客户端提供读服务,如果是写服务则转发给 Leader。不参与选举过程中的投票,也不参与“过半写成功”策略。在不影响写性能的情况下提升集群的读性能。此角色于 ZooKeeper3.3 系列新增的角色。 |
4.2 集群 Leader 的选举过程
当集群中的 Leader 出现问题时,比如网络中断,服务器宕机等,就会触发 Leader 选举:
- 选举阶段:节点在一开始都处于选举阶段,当选准 Leader 的条件是某个节点得到超半数节点的票数,此时他还不是一个真正的 Leader
- 发现阶段:在这个阶段,Followers 跟准 Leader 进行通信,同步 followers 最近接收的事务提议
- 同步阶段:同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后准 Leader 才会成为真正的 Leader
- 广播阶段:当了领导,得喊啊,到了这个阶段,ZooKeeper 集群才能正式对外提供事务服务,并且 Leader 可以进行消息广播
4.3 为何集群的实例数最好是奇数台
Zookeeper 集群对外提供服务的机制是,只要存活的实例,大于 n/2 即可,这也叫做过半机制
假设有三台实例组成集群,其容忍度是挂掉一台,而四台实例的容忍度和三台是一样的,所以,为何要增加一台不必要的 Zookeeper 呢?
而过半机制可以防止脑裂的出现
什么是脑裂?
对于一个集群,通常多台机器会部署在不同机房,来提高这个集群的可用性。保证可用性的同时,会发生一种机房间网络线路故障,导致机房间网络不通,而集群被割裂成几个小集群。这时候子集群各自选主导致“脑裂”的情况
ZooKeeper 的过半机制导致不可能产生 2 个 leader,因为少于等于一半是不可能产生 leader 的,这就使得不论机房的机器如何分配都不可能发生脑裂
5. 总结
- Zookeeper 本身就是一个分布式服务,只要半数以上的节点存活,就能保证对外的服务
- Zookeeper 将数据保存在内存中,性能非常的棒,尤其是在读多于写的场景下,因为写会导致服务集群进入同步状态
- ZooKeeper 底层其实只提供了两个功能:其一是管理(存储、读取)用户程序提交的数据;其二是为用户程序提供数据节点监听服务
- Zookeeper 有临时节点的概念,且临时节点只能是叶子节点,不允许有子节点