它是来当裁判的,不是来干活的
给 ZooKeeper 定位,一句话:它不是数据库(不存海量数据),不是缓存(不做高并发读),它是分布式系统的裁判——专门回答「谁说了算」的问题:谁是主节点、这个配置当前生效哪个值、这把锁此刻归谁。Kafka 早期用它管 broker 元数据,HBase 用它选主,Dubbo 用它做注册中心,全是同一个逻辑:把「多个节点达成一致」这件难事,交给一个专职的、用一致性协议背书的中间人。理解它从数据模型开始。
ZNode:既是文件又是目录
ZooKeeper 的数据是一棵树,树的每个节点叫 ZNode。它打破了文件系统「文件不能有子目录」的规矩:每个 ZNode 既能存数据(默认上限 1MB,够放锁标记和元信息),又能有子节点——既是文件又是目录。路径从根开始:/services/order-service/provider1、/locks/card-8801。几个对锁场景关键的 ZNode 元信息:version(版本号,CAS 更新的依据,分布式事务系列第 10 篇的乐观锁思想)、zxid(创建/修改时的事务 ID,全局单调递增)、ephemeralOwner(临时节点的会话归属)。别被 1MB 上限吓到——裁判只记比分,不写比赛记录,大数据该进数据库。
四种节点,两种生死
按「创建方式」和「生死绑定」两个维度,ZNode 分四类,锁的世界主要用后两类:
| 类型 | 生死规则 | 典型用途 |
|---|---|---|
| 持久节点 | 创建后一直在,除非主动删 | 配置、元数据、锁的父目录 |
| 持久顺序节点 | 同上,但路径自动追加单调序号 | 队列、需要全局排序的事件 |
| 临时节点 | 绑定创建者会话,会话断开自动删除 | 服务注册、选主的领导标记 |
| 临时顺序节点 | 会话绑定 + 自动序号,双特性叠加 | 分布式锁排队(第 10 篇主角) |
顺序节点的序号是全局单调递增的:两个客户端在同一个父节点下各创建一个顺序节点,哪怕并发,ZooKeeper 保证一先一后绝不重号——「公平排队」的原材料白送。
会话:临时节点的生命线
临时节点绑定的「会话」是 ZooKeeper 灵魂设计。客户端与服务端建立 TCP 连接即产生会话,客户端周期性发心跳维持;超过 sessionTimeout 收不到心跳,服务端判定会话过期,该会话创建的所有临时节点被集中删除。对照 Redis 锁的 TTL 细品这个设计:Redis 的「持有人死了」靠锁过期来猜(猜 TTL 够不够长),ZooKeeper 的「持有人死了」靠心跳消失来断定(活性即契约)。进程崩溃,连接断,心跳停,临时节点蒸发,锁自动释放——防死锁不靠任何参数赌命,这是第 3 篇看门狗做不到的干净。当然它也有自己的软肋:网络抖动被误判死亡、会话过期后客户端复活(第 11 篇的坑)。
Watch:一次性的事件快递
Watch 解决「我怎么知道锁轮到我了」的问题,角色等同于第 6 篇的 pub/sub 唤醒。客户端对某个 ZNode 注册 Watch,该节点发生删除、修改、子节点增减时,服务端推送一个事件通知;客户端收到通知后再主动拉取最新数据(通知只说「变了」,不带货)。两个使用纪律:其一,Watch 是一次性的,触发即失效,想继续监听要重新注册(Curator 客户端已封装好续订);其二,事件可能丢失(会话抖动),通知只能当加速器,状态判断必须以拉取为准——「通知 + 主动检查」是永远的组合拳。
集群里的裁判怎么自己不吵架
ZooKeeper 自身是集群:一个 Leader 带一队 Follower,所有写请求转发给 Leader,Leader 发起投票,过半节点确认才算提交;读请求任意节点本地服务(读可能读到旧值,是 ZK 用一致性换性能的让步,细究见第 11 篇)。过半提交(quorum)是它的安全底线:新 Leader 上任时,所有已提交的写入必然都在——「锁已发给 A」这个事实一旦确认,换多少任 Leader 都不会翻供。这正是它面对第 7 篇 Redis 丢锁问题的底气来源。这份底气背后的投票协议叫 ZAB,下一篇拆开看它的两副面孔:崩溃恢复与消息广播。
评论 (0)