一文以知 ZooKeeper 核心原理
ZooKeeper 应该是一个比较常见的开源工具,加上与分布式系统渊源很深,我们其实可以“望文生义”,大致猜测一下 ZooKeeper 的作用,因为 ZooKeeper 的英文直译是“动物园管理员”,动物园里的动物就像分布式系统里的各个子系统一样,混乱且难以管理,所以 ZooKeeper 大概就是拿来管理分布式系统的,让其可控可用。
事实上,我们的猜测是正确的,ZooKeeper 的主要作用就是拿来做分布式系统管理的。具体的作用主要包括:
- 分布式协调(系统间信息异步通信)
- 分布式锁(zk 分布式锁)
- 元数据/配置信息管理(注册中心)
- 高可用(主从选举)
ZooKeeper 能实现这么多的功能自然离不开底层数据结构的支持。ZooKeeper 是一个树形结构,每一个节点(znode)拥有唯一的路径 path,客户端基于 path 上传节点数据,ZooKeeper 收到后会实时通知对该路径进行监听的客户端。
znode 有 4 种类型,持久节点、持久序号节点、临时节点、临时序号节点。
节点类型英文 | 节点类型中文 |
---|---|
PERSISTENT | 持久节点 |
PERSISTENT_SEQUENTIAL | 持久序号节点 |
EPHEMERAL | 临时节点(不可在拥有子节点) |
EPHEMERAL_SEQUENTIAL | 临时序号节点(不可在拥有子节点) |
持久节点:哪怕客户端断开连接,也一直存在。
临时节点:只要客户端断开连接,节点就没了。
顺序节点:就是在创建节点的时候自动添加全局递增的序号
ZooKeeper 分布式锁的实现就是基于临时顺序节点来实现的,加锁的时候,创建一个临时顺序节点。
每一个 znode 结构包含如下属性:
- path:唯一路径
- childNode:子节点
- stat:状态属性
- type:节点类型
我们可以使用命令查看 znode 的属性:
stat /cluster
属性列表如下:
属性 | 描述 |
---|---|
cZxid = 0x75 | #创建节点的事务 ID |
ctime = Tue Oct 20 16:49:19 CST 2020 | #创建时间 |
mZxid = 0x75 | #修改节点的事务 ID |
mtime = Tue Oct 20 16:49:19 CST 2020 | #最后修改时间 |
pZxid = 0x76 | #子节点变更的事物ID |
cversion = 1 | #这表示对此znode的子节点进行的更改次数(不包括子节点) |
dataVersion = 0 | # 数据版本,变更次数 |
aclVersion = 0 | #权限版本,变更次数 |
ephemeralOwner = 0x0 | #临时节点所属会话ID |
dataLength = 0 | #数据长度 |
numChildren = 1 | #子节点数(不包括子子节点) |
ZooKeeper 特点:
- 顺序写:集群中只有一台机器可以写,所有机器都可以读,所有写请求都会分配一个zk集群全局的唯一递增编号,zxid,保证各种客户端发起的写请求都是有顺序的
- 数据一致性:任何一台zk机器收到了写请求之后都会同步给其他机器,保证数据的强一致,你连接到任何一台zk机器看到的数据都是一致的
- 高性能:每台zk机器都在内存维护数据,所以zk集群绝对是高并发高性能的,如果你让zk部署在高配置物理机上,一个3台机器的zk集群抗下每秒几万请求没有问题
- 高可用:哪怕集群中挂掉不超过一半的机器,都能保证可用,数据不会丢失,3台机器可以挂1台,5台机器可以挂2台
- 高并发:高性能决定的,只要基于纯内存数据结构来处理,并发能力是很高的,只有一台机器进行写,但是高配置的物理机,比如16核32G,写入几万QPS,读,所有机器都可以读,3台机器的话,起码可以支撑十几万QPS
ZooKeeper 3 种角色:
通常来说ZooKeeper集群里有三种角色的机器。
- Leader:集群启动自动选举一个Leader出来,只有Leader是可以写的。
- Follower:Follower 只能同步数据和提供数据的读取,Leader 挂了,Follower 可以继续选举出来Leader。
- Observer:只能读,但是Observer不参与选举
ZooKeeper最核心的一个机制:Watcher监听回调
就是客户端可以对 Znode 进行 Watcher 监听,然后 Znode 改变的时候回调客户端。
在整个zk的架构和工作原理中,有一个非常关键的环节,就是zk集群的数据同步是用什么协议做的?其实用的是特别设计的ZAB协议,ZooKeeper Atomic Broadcast,就是ZooKeeper原子广播协议。
ZAB的核心思想介绍:主从同步机制和崩溃恢复机制
两种角色:Leader 和 Follower,只有 Leader 可以接受写操作,Leader 和 Follower 都可以读。
流程:Leader 收到事务请求,转换为事务 Proposal(提议)同步给所有的 Follower,超过半数的 Follower 都说收到事务 Proposal 了(返回 ack),Leader 再给所有的 Follower 发一个Commit 消息,让所有 Follower 提交事务。
崩溃恢复:
zk集群启动的时候,进入恢复模式,选举一个leader出来,然后leader等待集群中过半的follower跟他进行数据同步,只要过半follower完成数据同步,接着就退出恢复模式,可以对外提供服务了
集群启动:恢复模式,leader选举(过半机器选举机制) + 数据同步
消息写入:消息广播模式,leader采用2PC模式的过半写机制,给follower进行同步
崩溃恢复:恢复模式,leader/follower宕机,只要剩余机器超过一半,集群宕机不超过一半的机器,就可以选举新的leader,数据同步
采用了2PC两阶段提交思想的ZAB消息广播流程:
每一个消息广播的时候,都是2PC思想走的,先是发起事务Proposal的广播,就是事务提议,仅仅只是个提议而已,各个follower返回ack,过半follower都ack了,就直接发起commit消息到全部follower上去,让大家提交。
发起一个事务proposal之前,leader会分配一个全局唯一递增的事务id,zxid,通过这个可以严格保证顺序。
leader会为每个follower创建一个队列,里面放入要发送给follower的事务proposal,这是保证了一个同步的顺序性。
每个follower收到一个事务proposal之后,就需要立即写入本地磁盘日志中,写入成功之后就可以保证数据不会丢失了,然后返回一个ack给leader,然后过半follower都返回了ack,leader推送commit消息给全部follower。
leader自己也会进行commit操作,commit之后,就意味这个数据可以被读取到了。
ZooKeeper到底是强一致性还是最终一致性:
明显,ZAB协议机制,zk一定不是强一致性,是最终一致,官方的说法是顺序一致性。因为leader一定会保证所有的proposal同步到follower上都是按照顺序来走的,起码顺序不会乱。
使用 Observer 节点来扩展 ZooKeeper 集群:
Observer节点是不参与leader选举的,他也不参与ZAB协议同步时候的过半follower ack的那个环节,他只是单纯的接收数据,同步数据,可能数据存在一定的不一致的问题,但是是只读的。
zk集群无论多少台机器,只能是一个leader进行写,单机写入最多每秒上万QPS,这是没法扩展的,所以zk是适合写少的场景。
但是读呢?follower起码有2个或者4个,读你起码可以有每秒几万QPS,没问题,那如果读请求更多呢?此时你可以引入Observer节点,他就只是同步数据,提供读服务,可以无限的扩展机器。