Zookeeper基本架构
ZooKeeper 是分布式系统中的"大管家 "。它本质上是一个分布式协调服务,专门解决分布式环境下的配置管理、名字服务、分布式锁、集群管理等棘手问题。
1. 核心架构:ZAB 协议
ZooKeeper 并没有直接使用 Paxos 或 Raft,而是设计了一套专门的协议:ZAB (ZooKeeper Atomic Broadcast,原子广播协议)。
ZAB 协议主要分为两个阶段:
- 崩溃恢复(选举):当 Leader 挂掉时,集群进入选举模式,选出拥有最新提案(zxid 最大)的节点作为新 Leader。
- 消息广播(同步):Leader 接收写请求,通过两阶段提交(Propose + Commit)将数据同步给 Follower。只要超过半数节点回复,数据即视为写入成功。
2. 数据模型:类似文件系统的 ZNode
ZooKeeper 的数据存储在内存中,结构像一棵树,每个节点被称为 ZNode。
ZNode 有四种类型,这是实现各种功能的基石:
- 持久节点 (Persistent):除非手动删除,否则一直存在。
- 临时节点 (Ephemeral) :客户端断开连接(Session 失效),节点自动消失。(常用于心跳检测、存活监控)。
- 持久顺序节点 (Persistent_Sequential):名称后会自动带上递增数字。
- 临时顺序节点 (Ephemeral_Sequential) :(实现分布式锁的神器)。
3. 核心机制:监视器 (Watcher)
这是 ZooKeeper 最强大的功能。
- 客户端可以监听某个 ZNode 的变化(数据修改、子节点增减)。
- 当变化发生时,ZooKeeper 会给客户端发送一个一次性的通知。
- 应用场景:配置中心。你改了数据库密码,所有微服务立刻感知并更新。
4. ZooKeeper 能做什么?(常用场景)
A. 分布式锁:像排队领号一样的逻辑
分布式锁的核心矛盾是:多个进程同时想操作同一个资源(比如修改同一个账户余额),怎么保证只有一个能成功?
ZooKeeper 的做法不是让大家去"抢",而是让大家去**"排队"**。
详细步骤:
-
领号(创建临时顺序节点) :
假设大家都要抢一个名为
lock的锁。客户端 A、B、C 同时在/locks路径下创建节点。由于是"顺序节点",ZK 会自动给它们加编号:- 客户端 A 创建了
/locks/node-000001 - 客户端 B 创建了
/locks/node-000002 - 客户端 C 创建了
/locks/node-000003
- 客户端 A 创建了
-
判断自己是否是"最小号" :
每个客户端创建完后,都会看一眼
/locks目录下的所有子节点。- 客户端 A 发现自己是
000001,最小,于是成功获得锁,开始干活。 - 客户端 B 发现自己是
000002,前面有 A,拿不到锁。 - 客户端 C 发现自己是
000003,前面有 A 和 B,拿不到锁。
- 客户端 A 发现自己是
-
盯着前一个人(Watcher) :
关键点在这里:没拿到锁的人不会死循环去刷,而是只盯着比自己小的那一个号。
- 客户端 B 监听
node-000001的消失事件。 - 客户端 C 监听
node-000002的消失事件。
- 客户端 B 监听
-
释放与唤醒 :
当客户端 A 干完活(或者宕机了),
node-000001消失。此时 ZooKeeper 会通知客户端 B :"喂,你盯着的 01 没了!"。B 发现自己成了最小号,于是获得锁。
为什么要用"顺序"和"监听前一个"?
- 防止惊群效应:如果 1000 个节点同时盯着一个锁,锁一释放,ZK 要同时通知 1000 个人,网络瞬间爆炸。而"盯着前一个"确保了每次只唤醒一个人。
B. 服务注册与发现
像 Dubbo 等框架,Provider 启动时在 ZK 注册一个临时节点。Consumer 监听该目录。如果 Provider 挂了,临时节点消失,Consumer 立刻剔除该地址。
C. 集群选主:抢占"地盘"的逻辑
选主(Master Election)的核心矛盾是:一个集群有多个节点,必须选出一个"老大"来指挥,否则会乱套。
ZooKeeper 的做法是利用 "节点的唯一性" 和 "临时节点"。
详细步骤:
-
抢注(创建同名临时节点) :
集群启动时,所有节点(Node-A, Node-B, Node-C)都会尝试去 ZooKeeper 创建同一个节点:
/master。 -
谁快谁当选 :
由于 ZooKeeper 保证了路径的唯一性,同一时间只有一个节点能创建成功。
- 假设 Node-A 抢先创建了
/master,ZK 返回成功。Node-A 成为 Master。 - Node-B 和 Node-C 尝试创建时,ZK 会报错:"节点已存在"。它们就知道已经有老大产生了,于是变成 Follower。
- 假设 Node-A 抢先创建了
-
实时监控 :
虽然没抢到,但 Node-B 和 Node-C 会对
/master这个节点设置一个 Watcher(监视器)。 -
老大挂了怎么办? :
由于
/master是临时节点:- 如果 Node-A 挂了(机器宕机或断网),它与 ZK 的会话中断,
/master节点会被 ZK 自动删除。 - Node-B 和 Node-C 立刻收到通知。它们会再次发起"抢注"操作。
- 谁先抢到,谁就是下一任 Master。
- 如果 Node-A 挂了(机器宕机或断网),它与 ZK 的会话中断,
ZooKeeper 处于 TDSQL 的核心位置
在分布式数据库中,最怕的就是"信息不对称"。例如:
- SQL 引擎不知道该把请求发给哪个 MySQL 实例。
- 主库挂了,大家还没达成一致谁是新主库。
ZooKeeper 处于核心位置是因为它提供了 CP(强一致性) 保证。它像一个**"中央公告板"**,所有的关键组件(SQL 引擎、调度管理 OSS、监控采集等)都连接在它身上。只要 ZooKeeper 上的数据变了,所有组件都能立刻感知并达成共识。
ZooKeeper 在TDSQL架构中的主要作用
- 元数据管理:存储数据库的路由信息(哪个表在哪个分片上)、物理拓扑结构(主从关系、IP 地址)。
- 配置发布与订阅:当管理员修改了集群参数,通过 ZooKeeper 实时推送到所有 SQL 引擎和 Agent 节点。
- 存活监控与心跳:Agent 会在 ZooKeeper 上创建临时节点。如果 Agent 挂了,节点消失,ZooKeeper 会立刻通知调度器。
- 分布式锁:在执行自动化运维操作(如扩容、缩容)时,确保同一时间只有一个任务在运行,防止逻辑冲突。
具体场景举例:主从切换(故障自愈)
这是 TDSQL 最依赖 ZooKeeper 的场景之一。
场景描述:
假设 Set1 中的 主库(Agent+MySQL) 突然机房断电宕机了。
执行过程:
- 节点消失 :主库 Agent 在 ZooKeeper 上维护的是一个临时节点。主库宕机后,Session 断开,该节点自动删除。
- 触发警报 :OSS (Schedule Manager) 一直监听(Watch)着这个路径。发现主库节点没了,立刻意识到:"主库挂了!"
- 选主决策 :OSS 从 ZooKeeper 中读取 Set1 的备库信息,选出一个数据最全的从库。
- 更新真相:OSS 修改 ZooKeeper 里的元数据,将该从库标记为"新主库"。
- 路由感知 :前端的 SQL 引擎 也监听着 ZooKeeper。它发现"真相"变了,立刻更新自己的路由表,将后续的所有写请求转发给新的主库 IP。
结果:
整个过程在秒级完成。对于 APP 来说,只感觉到了一瞬间的闪断,而底层的切换逻辑完全由 ZooKeeper 配合 OSS 自动完成。
为什么要用Zookeeper
ZooKeeper 之所以能成为分布式架构(如你所见的 TDSQL)中的核心,是因为它在设计上提供了一套极其严苛的一致性保证 和独特的交互机制。
ZooKeeper核心特性
ZooKeeper 的设计目标是将复杂的、容易出错的分布式协调过程封装起来,只给用户提供简单易用的接口。其核心特性可以总结为以下几点:
1. 强一致性保证 (CP 属性)
- 顺序一致性:来自同一个客户端的更新将按其发送顺序依次应用。
- 原子性:更新操作要么成功,要么失败,没有中间状态。
- 单一视图:无论客户端连接到集群中的哪个服务器,看到的服务端数据模型都是一致的。
2. 独特的节点机制 (ZNode)
ZooKeeper 的数据结构类似文件系统,但它的节点(ZNode)具有特殊属性:
- 临时节点 (Ephemeral) :生命周期与客户端会话(Session)绑定。如果客户端宕机,节点自动删除。这是实现故障检测 和存活监控的关键。
- 顺序节点 (Sequential) :创建时自动在名后追加递增序列号。这是实现分布式锁 和分布式队列的基础。
3. 事件监听机制 (Watcher)
- 这是 ZooKeeper 最具魅力的特性。客户端可以对某个节点注册监听器,当节点数据变化或子节点增减时,ZooKeeper 会主动推送通知。
- 意义:它改变了传统的"轮训(Polling)"模式,大大降低了网络开销和响应延迟。
ZooKeeper解决的痛点
在分布式系统中,如果没有一个像 ZooKeeper 这样的"真相来源",会产生以下几个致命问题:
A. 解决"谁是老大"的问题 (Leader Election)
在 TDSQL 或 Kafka 集群中,必须选出一个主节点来负责调度。
- 不用 ZK:节点间需要通过复杂的相互投票,且极易出现"脑裂"(即两个节点都认为自己是主节点)。
- 用 ZK :大家抢着去创建一个名为
/master的临时节点,谁创建成功谁就是老大。由于 ZK 保证了节点的唯一性,绝不会出现两个主节点。
B. 解决"数据在哪"的问题 (Metadata Management)
- 分布式数据库有成千上万个分片,SQL 引擎需要实时知道每个分片在哪个 IP 上。
- 场景 :当发生扩容或迁移时,OSS 组件只需更新 ZooKeeper 里的路由表,所有 SQL 引擎通过 Watcher 瞬间就能更新本地缓存。
C. 解决"步调一致"的问题 (Distributed Barriers/Locks)
- 当多个进程需要同时完成某项任务,或者需要竞争同一份资源时。
- 场景 :防止多个管理后台同时对同一个数据库实例下发重启指令。通过 ZooKeeper 实现的分布式锁可以确保操作的互斥性。