Zookeeper 详解
核心定位:分布式协调服务
提供高可用、强一致性的核心基础设施,用于解决分布式系统中的协调难题:
- 服务注册与发现:服务动态上下线通知(如 Dubbo)
- 分布式锁:实现互斥访问共享资源
- 集群选主(Leader Election):确定主节点处理关键任务
- 配置管理:集中存储并动态推送配置
- 命名服务:维护全局唯一路径名
- 集群管理:监控节点状态
核心协议:ZAB (ZooKeeper Atomic Broadcast) 协议
保证分布式系统数据一致性的基石,包含两个核心阶段:
- Leader 选举:集群启动或 Leader 故障时,快速选出新 Leader(基于
epoch,zxid,sid比较)。 - 原子广播(消息广播):
- Leader 接收写请求,生成唯一递增事务 ID (
zxid)。 - Leader 将带
zxid的提案 (Proposal) 广播给所有 Follower。 - Follower 将提案持久化到事务日志,成功后返回
ACK。 - Leader 收到过半 Follower 的
ACK后,发送COMMIT命令。 - Follower 收到
COMMIT后,将提案应用到内存数据库 (DataTree)。
- Leader 接收写请求,生成唯一递增事务 ID (
核心特性:
- 顺序一致性:客户端请求按发送顺序执行。
- 原子性:事务要么全成功要么全失败。
- 单一系统映像:客户端无论连接哪个节点,看到相同数据视图。
- 可靠性:事务一旦提交,结果永久保存直到被覆盖。
- 最终一致性:客户端最终能看到最新提交的数据(读 Follower 可能有短暂延迟)。
- 高性能:读操作负载均衡到所有节点,极高吞吐;写操作由 Leader 处理,依赖网络和持久化速度。
选举机制详解 (面试重点)
核心目标:快速、公平选出 Leader,防止脑裂。
1. 首次启动选举 (以节点 sid=1,2,3 为例)
- 节点1启动:
- 状态:
LOOKING(寻找 Leader)。 - 投票:投给自己
(sid=1, zxid=0, epoch=0)。 - 结果:票数(1) < 半数(2),无 Leader。
- 状态:
- 节点2启动:
- 状态:
LOOKING。 - 投票:投给自己
(sid=2, zxid=0, epoch=0)。 - 通信: 节点2 向节点1 发送投票。节点1 发现节点2 的
sid(2) > 自己的 sid(1),更新投票为投给节点2(sid=2, zxid=0, epoch=0)并告知节点2。 - 节点2 统计:收到自己的票 + 节点1 的票 = 2票 >= 半数(2)。
- 结果:节点2 当选 Leader (状态变
LEADING)。 节点1 成为 Follower (状态变FOLLOWING)。
- 状态:
- 节点3启动:
- 状态:
LOOKING。 - 投票:投给自己
(sid=3, zxid=0, epoch=0)。 - 通信:向已存在的节点(1或2)询问。得知已有 Leader (节点2)。
- 结果:节点3 直接成为 Follower (
FOLLOWING)。
- 状态:
2. 运行期 Leader 故障选举 (核心三要素比较)
剩余 Follower 进入 LOOKING 状态发起投票。投票比较优先级(依次判断):
- Epoch (逻辑时钟): 最新选举轮次编号。
epoch大的优先。 (防止旧 Leader 的提案干扰新选举) - Zxid (事务ID): 节点最后提交的事务 ID。
zxid大的优先。 (保证数据最新的节点优先) - Sid (服务器ID): 配置文件中
myid的值。sid大的优先。 (唯一标识,保证选举结果唯一)
选举过程:
- 每个节点初始投票投给自己
(my_epoch, my_zxid, my_sid)。 - 节点交换投票信息。
- 收到他人投票时,按上述优先级比较:
- 若对方的投票“更优”(
epoch更大,或epoch相等时zxid更大,或epoch和zxid都相等时sid更大),则更新自己的投票为投给这个更优的节点。 - 否则,坚持自己当前的投票。
- 若对方的投票“更优”(
- 统计投票:当某个节点发现自己收到的投票中有过半票指向同一个节点(可能是自己或别人),则该节点当选 Leader。广播结果,其他节点成为 Follower。
3. 如何防止脑裂 (Split-Brain)
Zookeeper 通过 “过半机制” (Quorum) 从根本上防止脑裂:
- 写入要求过半: Leader 必须收到超过半数 Follower 的
ACK才能提交事务 (COMMIT)。 - 选举要求过半: 一个节点必须获得超过半数的投票才能当选 Leader。
- 网络分区后果:
- 如果一个分区拥有超过半数的节点,这个分区能正常选举新 Leader 并继续服务。
- 如果一个分区节点数不足半数,该分区无法选举出 Leader,也无法完成写请求(因为无法获得过半
ACK)。
- 结果: 整个集群中最多只有一个分区能正常工作(拥有过半节点的分区),其他分区处于不可用状态。保证了集群中最多只有一个有效的 Leader,避免脑裂导致的数据不一致。
节点类型 (ZNode Types)
Zookeeper 的数据模型是树形结构(类似文件系统),每个节点称为 ZNode。节点类型决定了其生命周期和命名规则:
-
持久节点 (Persistent Node) -
PERSISTENT- 特点: 创建后一直存在,除非显式删除 (
delete)。与客户端会话无关。 - 命令:
create /path data - 用途: 存储需要长期存在的配置信息、元数据等。
- 特点: 创建后一直存在,除非显式删除 (
-
持久顺序节点 (Persistent Sequential Node) -
PERSISTENT_SEQUENTIAL- 特点: 持久节点 + 创建时 ZK 自动在路径名后缀一个单调递增的序号 (10位数字,如
/path0000000001)。与客户端会话无关。 - 命令:
create -s /path data - 用途: 实现公平锁、任务队列等需要有序性的场景。
- 特点: 持久节点 + 创建时 ZK 自动在路径名后缀一个单调递增的序号 (10位数字,如
-
临时节点 (Ephemeral Node) -
EPHEMERAL- 特点: 生命周期绑定到客户端会话。 会话结束(主动断开或超时)时,节点自动被删除。不能有子节点。
- 命令:
create -e /path data - 用途: 服务注册与发现(服务在线则在特定路径下创建临时节点,下线则节点消失)、分布式锁持有者标识、集群成员在线状态。
-
临时顺序节点 (Ephemeral Sequential Node) -
EPHEMERAL_SEQUENTIAL- 特点: 临时节点 + 创建时 ZK 自动在路径名后缀一个单调递增的序号。会话结束自动删除。
- 命令:
create -e -s /path data - 用途: 实现分布式锁(如 Curator 的 InterProcessMutex)。多个客户端在锁节点下创建临时顺序节点,序号最小的获得锁。释放锁或会话结束节点删除后,下一个序号最小的获得锁。避免羊群效应。
-
容器节点 (Container Node) -
CONTAINER(3.5.3+ 引入)- 特点: 特殊持久节点。当它的最后一个子节点被删除后,容器节点会在未来的某个时刻被服务器自动删除(异步任务清理)。不能有子节点? (可以创建子节点,但空时会被删)
- 命令:
create -c /path data - 用途: 适合存放动态子节点列表的场景(如分组、队列),当组内成员全部退出时自动清理组节点。
-
带过期时间的持久节点 (Persistent Node with TTL) -
PERSISTENT_WITH_TTL(3.5.3+ 引入)- 特点: 持久节点,但如果在
TTL(Time-To-Live) 毫秒内没有被修改且没有子节点,则会被服务器自动删除。TTL 必须 > 0。 - 命令:
create -t <ttl> /path data(e.g.,create -t 30000 /path data表示 30 秒 TTL) - 用途: 需要在一定不活动时间后自动清理的持久化数据。
- 特点: 持久节点,但如果在
-
带过期时间和序号的持久节点 (Persistent Sequential Node with TTL) -
PERSISTENT_SEQUENTIAL_WITH_TTL(3.5.3+ 引入)- 特点: 带过期时间的持久节点 + 创建时自动添加序号后缀。
- 命令:
create -s -t <ttl> /path data - 用途: 需要有序性且需要自动清理的持久化数据。
总结关键点:
- 持久/临时: 区分节点生命周期是否依赖会话 (
-e)。 - 顺序: 节点名是否自动添加序号 (
-s)。 - 容器/TTL: 提供额外的自动清理机制 (
-c,-t),需注意版本要求 (>=3.5.3)。
掌握 Zookeeper 的核心概念、选举机制(尤其是三要素比较和过半原则防脑裂)以及不同节点类型的适用场景,是理解和应用分布式系统的关键,也是面试中的高频考点。