一. ZooKeeper 是啥
ZooKeeper 概念
ZooKeeper 是一个开源的分布式协调服务(a service for coordinating processes of distributed applications), 由雅虎公司创建, 是 Google Chubby 的开源实现(Google Chubby 是有名的分布式锁服务, GFS 和 Big Table 等大型系统都用它来解决分布式协调, Master 选举等一系列与分布式锁服务相关的问题). 分布式程序可以基于 ZooKeeper 实现负载均衡, 命名服务, 分布式锁等功能. ZooKeeper 将全量数据都存在内存中, 实现提高服务器吞吐, 减少延迟的目的.
上面的英文说的足够简而意赅了, a service for coordinating processes of distributed applications. 是为了协调分布式应用的, 到底解决什么样的问题呢. 相信大家的 Java 基础都不错, 个人觉得还是拿锁举例子比较好理解, 在单机式中, 我们想保证多个线程争抢, 最后只有一个线程争抢到执行权 (锁) 并执行你想要让他做的业务代码, 最简单的方式, 可以用:
- synchronized (obj){
- // 业务逻辑
- }
那么在分布式的情况下呢? 怎么保证, 同一个服务部署在不同机器上实现如上目标呢? 那么这就是分布式协调服务要干的事情. 分布式协调远远比同一个进程里的协调复杂得多, 所以类似 Zookeeper 这类分布式协调服务就应运而生. 在解决分布式数据一致性上, 除了 ZooKeeper, 目前没有一个成熟稳定且被大规模应用的开源方案. 且越来越多的大型分布式项目如 HBase,Storm 都已经使用 ZooKeeper 作为其核心组件, 用于分布式协调.
ZooKeeper 可以保证如下分布式一致性特性:
顺序一致性: 从同一客户端发起的事务请求, 最终将会严格地按照其发起顺序被应用到 ZooKeeper 中
原子性: 要么整个集群所有机器都成功应用了某个事务, 要么都没有应用
单系统镜像: 无论客户端连接的是哪个 ZooKeeper 服务器, 其看到的服务器数据模型都是一致的(当然, ZooKeeper 就是解决分布式数据一致性问题的)
可靠性: 一个服务端成功地应用了一个事务, 并完成对客户端的响应, 那么该事务所引起的服务端状态变更将会被一致保留下来, 除非有另一个事务又对其进行了变更
实时性: ZooKeeper 仅保证在一定时间段内, 客户端最终一定能从服务端上读取到最新的数据状态. 也就是说比方说一个 ZooKeeper 集群, 有一个时间点, 数据在集群中的每个服务器不是一致的, ZooKeeper 只保证最终一致性, 但是实时的一致性可以由客户端调用自己来保证, 通过调用 sync()方法
ZooKeeper 名字诞生
在立项初期, 考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的 Pig 项目), 雅虎的工程师希望给这个项目也取一个动物的名字. 时任研究院的首席科学家 Raghu Ramakrishnan 开玩笑地说:"在这样下去, 我们这儿就变成动物园了!" 此话一出, 大家纷纷表示就叫动物园管理员吧, 因为各个以动物命名的分布式组件放在一起, 雅虎的整个分布式系统看上去就像一个大型的动物园了. 而 Zookeeper 正好要用来进行分布式环境的协调, 于是, Zookeeper 的名字也就由此诞生了.
ZooKeeper 的核心概念
集群角色
在 ZooKeeper 中, 集群有 3 个角色: Leader,Follower 和 Observer 三种角色.
Leader:ZooKeeper 集群中所有机器通过选举过程选定集群中的一台机器为 Leader, 事务请求唯一调度者和处理者, 保证集群事务处理的顺序性
Follower: 为客户端提供读服务, 参与 Leader 选举过程, 参与写操作的 "过半写成功" 策略, 收到写事务请求直接转发给 Leader
Observer: 为客户端提供读服务, 在不影响集群事务处理能力的前提下提升集群的非事务处理能力
会话
Session 是 ZooKeeper 中的会话实体, 代表了一个客户端会话, 一个客户端连接指客户端和服务器之间的一个 TCP 长连接. 通过这个连接, 客户端能够做以下事情
- 向 ZooKeeper 服务器发送请求并接收响应
- 心跳检测
- 接收来自服务器的 Watch 事件
数据节点(Znode)
数据节点 (Znode) 是指数据模型中的数据单元, ZooKeeper 内存数据存储的核心是 DataTree, 是一个树的数据结构, 代表了内存中的一份完整的数据, 由斜杠 "/" 进行分割的路径, 就是一个 Znode, 每个 Znode 都保存自己的数据内容
版本
每个 Znode 都有三种类型的版本信息, 对节点数据变动会引起版本号变化
version: 当前数据节点数据内容的版本号
cversion: 当前数据节点子节点的版本号
aversion: 当前数据节点 ACL 变更版本号
Watcher
事件监听器 (Watcher) 是 ZooKeeper 非常重要的特性, 我们可以在节点上注册 Watcher, 并且在一些特性事件触发时候, 服务器将事件通知到客户端上
ACL
ZooKeeper 使用 ACL(Access Control Lists)权限控制机制保证数据安全, 有 5 个权限:
CREATE: 创建子节点的权限
READ: 获取节点数据和子节点列表的权限
WRITE: 更新节点数据的权限
DELETE: 删除子节点的权限
ADMIN: 设置节点 ACL 的权限
二. ZooKeeper 能做啥
我们可以回头看最上面的图, Hbase,Hadoop,Kafka 等已经被广泛应用在越来越多的大型分布式系统中, 用来解决诸如配置管理, 分布式通知 / 协调, 集群管理和 Master 选举等一系列分布式问题
ZooKeeper 在阿里的实践有 Dubbo, 消息中间件 Metamorphosis, 分布式数据库同步系统 Otter, 实时计算引擎 Jstorm 等
数据发布 / 订阅(配置中心)
负载均衡
分布式协调 / 通知
集群管理
Master 选举
分布式锁
三. ZAB 协议是啥
可以这么说, No ZAB,No ZooKeeper.ZAB 协议是整个 ZooKeeper 框架的核心所在.
ZooKeeper 是一个高可用的分布式数据管理与协调框架. 基于对 ZAB 算法的实现, ZooKeeper 成为了解决分布式环境中数据的一致性问题的利器. ZAB 协议的全称是 ZooKeeper Atomic Broadcast(ZooKeeper 原子消息广播协议).ZAB 协议是一种特别为 ZooKeeper 设计的崩溃可恢复的原子消息广播算法.
所有事务请求必须由唯一的 Leader 服务器来协调处理, 其他服务器则成为 Follower 服务器. Leader 服务器负责将事务请求转换成一个提议, 并将该提议分发到集群中的所有 Follower 服务器, 之后 Leader 服务器需要等待所有 Follower 的响应, 一旦超过半数的 Follower 服务器进行了正确的反馈后(不需要等待集群中所有的 Follower 服务器都反馈响应), 那么 Leader 就会再次向所有 Follower 服务器分发 Commit 消息, 要求对前一个提议进行提交.
术语解释
首先先来看一下选举算法出现的一些专有术语
SID: 服务器 ID
SID 是一个数据, 标识一台 ZooKeeper 集群中的机器, SID 不能重复, 和 myid 值一样.(集群的配置文件中, server.id=host:port:port, 这里的 id 就是 myid, 我们还需要在 dataDir 参数的目录创建 myid 文件, 就是这里的 id)
ZXID: 事务 ID
ZXID 是一个事务 ID, 标记唯一一次服务器状态的变更, 某一时刻, 集群中的每台机器的 ZXID 不一定都一致, 之前已经说过了. 它是一个 64 位的数字, 低 32 位可以看作递增计数器, 高 32 位代表 Leader 周期 epoch 的编号
Vote: 投票
我们可以看下 Vote 的数据结构:
接下来我们来解释一下每个字段的意思:
id: 被选举的 Leader 的 SID 值
zxid: 被选举的 Leader 的事务 ID
electionEpoch: 逻辑时钟
peerEpoch: 被选举的 Leader 的 epoch
state: 当前服务器的状态
Quorum: 过半机器数
quorum=(n/2+1), 假如集群总数是 3, 那么 quorum 就是 2
ZAB 协议三个阶段
阶段一: 发现
阶段一就是 Leader 选举过程(服务器启动期间或者服务器运行期间), 服务器的状态进入 LOCKING 状态. 进入选举 Leader 流程.
不要死记硬背具体规则, 总结简单来说, 哪台服务器上的数据较新, 也就是它的 ZXID 越大, 那么越有可能成为 Leader. 如果几个服务器具有相同的 ZXID, 那么 SID 较大的服务器成为 Leader.
规则 1: 如果收到投票的 ZXID 大于自身的 ZXID, 就认可收到的投票再次投出去
规则 2: 如果收到投票的 ZXID 小于自身的 ZXID, 则坚持自己的投票不做任何变更
规则 3: 如果收到投票的 ZXID 等于自身的 ZXID, 则对比两者 SID, 如果收到投票的 SID 大于自身的 SID 则认可收到的投票再次投出去
规则 4: 如果收到投票的 ZXID 等于自身的 ZXID, 并且收到投票的 SID 小于自身 SID 则坚持自己的投票不做任何变更
接下来举个例子来说明选举的过程:
过程 A: 我们假设 ZooKeeper 由 5 台机器组成, SID 分别为 1,2,3,4,5.ZXID 分别为 9,9,9,8,8. 此时 SID 为 2 的机器是 Leader 服务器, 某一时刻 SID 为 1 和 2 的机器出现故障, 因此集群开始进行 Leader 选举, state 切换到 LOCKING 状态.
过程 B: 第一次投票, 每台机器都选自己作为被选举的对象来进行投票, 所以 SID 为 3,4,5 的投票情况为(这里 Vote 做简化, 只有 SID 和 ZXID):(3,9),(4,8),(5,8).
过程 C:Server3 收到 (4,8) 和(5,8). 根据规则 2, 不做任何投票的变更.
Server4 收到 (3,9) 和(5,8). 根据规则 1, 需要变更投票为(3,9).
Server5 同样的变更投票为(3,9).
过程 D: 第二轮投票后, Server3 收到超过一半的票数, 成为 Leader
阶段二: 同步
选举完成后, Leader 服务器会为每一个 Follower 服务器都准备一个队列, 并将那些没有被各 Follower 服务器同步的事务以 Proposal 消息的形式逐个发送给 Follower 服务器, 然后在提议消息之后紧接发送一个 Commit 消息, 表示该事务被提交, 等到 Follower 服务器都将未同步的事务从 Leader 服务器同步过来并成功应用到本地数据库后, Leader 服务器会将该 Follower 服务器加入真正可用的 Follower 列表中
阶段三: 广播
Leader 服务器会给每个 Follower 分配一个 FIFO 的队列来分送事务, Follower 服务器收到事务 Proposal 之后以事务日志的形式写入本地磁盘, 写入成功会给 Leader 服务器回复一个 ACK.
当 Leader 服务器收到过半的 ACK 响应则广播发送 Commit 消息给所有 Follower, 然后所有服务器完成对事务的提交.
来源: https://www.cnblogs.com/GrimMjx/p/10922501.html