2.5 java面试题 :ZooKeeper
ZooKeeper 是分布式系统的协调中枢。在银行核心开发中,它通常不承载业务流量,但几乎所有关键分布式组件(如 Kafka、Dubbo、分布式锁)都依赖它。下面我从原理、协议、应用、对比和银行实践五个角度,给你一个能体现老练经验的深度回答。
一、ZooKeeper 是什么?
ZooKeeper 是一个开源的分布式协调服务,为分布式应用提供高可用、强一致的元数据管理。它的核心能力包括:
- 配置管理:集中式配置存储,动态变更推送。
- 命名服务:全局唯一 ID 生成。
- 分布式锁:跨进程互斥。
- 集群管理:成员发现、心跳保活、选主。
- 分布式队列/屏障:协调任务执行顺序。
类比:如果把分布式系统比作一个公司,ZooKeeper 就是 HR + 公告板,负责“谁在、谁负责什么、通知变革”。
二、核心概念与数据模型
1. 树形命名空间
ZooKeeper 的数据模型类似于文件系统,节点称为 ZNode,每个 ZNode 存储少量数据(默认最大 1MB),兼具文件和目录特性。
/app
/config
db-url: "jdbc:mysql://..."
/services
/deposit-service
/192.168.1.1:8080 (临时节点)
/192.168.1.2:8080 (临时节点)
/locks
/transfer-001
2. ZNode 类型
| 类型 | 持久性 | 顺序性 | 典型用途 |
|---|---|---|---|
| 持久节点(Persistent) | 客户端断开仍存在 | 否 | 配置存储 |
| 临时节点(Ephemeral) | 与 Session 同生命周期 | 否 | 服务健康检测 |
| 持久顺序节点 | 持久 | 是 | 分布式锁等需要唯一编号 |
| 临时顺序节点 | 临时 | 是 | 分布式锁(公平锁) |
3. 会话与 Watcher
- Session:客户端与服务端通过心跳维持会话(默认 2 秒一次),超时后临时节点自动删除,Watcher 被清理。
- Watcher:客户端可在 ZNode 上注册监听,当节点变化(创建、删除、数据变更、子节点变化)时服务端主动通知。一次性触发:通知后需重新注册。
三、ZAB 协议:ZooKeeper 的强一致性核心
ZooKeeper 通过 ZAB(ZooKeeper Atomic Broadcast) 协议保证集群数据的顺序一致性和原子性。
1. 两种模式
- 消息广播模式(正常状态):
Leader 将写请求转化为事务 Proposal,发送给所有 Follower。
当 过半 Follower 确认(ACK)后,Leader 发送 Commit 给所有节点执行。 - 崩溃恢复模式(选主或 Leader 故障):
集群重新选举 Leader(基于最大 ZXID),新 Leader 同步所有 Follower 的数据,确保没有已提交的事务丢失。
2. 关键保证
- 线性写入:所有写操作全局排序。
- 原子广播:事务要么在过半节点执行,要么都不执行。
- 顺序一致性:单个客户端操作的顺序与提交顺序一致。
对比 Raft:ZAB 与 Raft 类似,都是 Leader-based 的强一致性协议。ZAB 更强调事务 ID(ZXID)的连续性和崩溃恢复中数据同步的完整性。
四、ZooKeeper 在银行项目中的典型应用
1. 分布式锁(资金交易互斥)
需求:转账操作需要锁定付款账户和收款账户,防止并发导致余额不一致。
方案:基于 临时顺序节点 实现公平锁(避免惊群效应)。
// 使用 Curator 框架
InterProcessMutex lock = new InterProcessMutex(client, "/lock/transfer");
if (lock.acquire(10, TimeUnit.SECONDS)) {
try {
// 执行转账
} finally {
lock.release();
}
}
为什么不用 Redis?
- Redis 分布式锁(Redisson)在主从切换时可能丢失锁(异步复制),导致资金风险。
- ZooKeeper 基于 CP 模型,写入需过半确认,锁绝对安全。
2. 核心服务高可用与选主
场景:日切批处理任务只能由一个实例执行,防止重复记账。
方案:利用 临时节点 + 选主。
- 所有批处理实例启动时,在
/batch/master下创建临时节点。 - 创建成功的实例成为 Master,执行批处理任务。
- 若 Master 宕机,Session 超时,临时节点自动删除,其他实例通过 Watcher 获知并重新选举。
3. 动态配置推送
场景:利率、汇率、产品开关需要实时生效,无需重启服务。
方案:在 /config/loan-rate 存储配置,所有服务订阅该节点。
CuratorFramework client = ...;
TreeCache cache = new TreeCache(client, "/config");
cache.getListenable().addListener((curator, event) -> {
if (event.getType() == TreeCacheEvent.Type.NODE_UPDATED) {
String newRate = new String(event.getData().getData());
// 更新内存中的利率
}
});
cache.start();
4. 服务注册与发现(早期)
在 Nacos 流行前,我们用 ZooKeeper 做服务注册:
- 服务提供者启动时,创建临时节点
/services/deposit-service/192.168.1.1:8080。 - 消费者订阅
/services/deposit-service,通过 Watcher 感知实例变化。
为何后来迁移到 Nacos? ZooKeeper 的 Watcher 机制在高频变更下会引发惊群,且 CP 模型在服务发现场景下可用性较差(分区时可能拒绝写入,影响注册)。
五、ZooKeeper vs Redis vs etcd
| 维度 | ZooKeeper | Redis | etcd |
|---|---|---|---|
| 一致性模型 | CP(强一致) | AP(最终一致) | CP(强一致) |
| 数据模型 | 树形 ZNode | 键值对 | 键值对,带租约 |
| 典型用途 | 分布式锁、配置、选主 | 缓存、分布式锁(高性能) | K8s 服务发现、配置 |
| 性能 | 千级 TPS | 10万+ QPS | 万级 TPS |
| 客户端 | Curator(Java) | Redisson/Jedis | Jetcd(Java) |
| 部署复杂度 | 较高(奇数节点) | 低 | 低(Golang 二进制) |
选型建议:
- 资金锁、核心选主:用 ZooKeeper,安全第一。
- 高性能非资金锁:用 Redis Redisson。
- 容器化环境:如果有 K8s,用 etcd。
六、生产避坑与监控
1. 常见问题
- 脑裂:部署 3/5/7 奇数节点,过半存活才能服务。
- Session 超时配置不当:调优
tickTime和maxSessionTimeout,避免网络抖动导致误踢。 - 大规模 Watcher 导致惊群:使用 Curator 的
TreeCache或PathChildrenCache避免反复注册。 - 数据量大影响性能:ZNode 只存元数据,不存业务数据,单个节点 < 1MB。
2. 监控指标
zk_server_leader:是否存在唯一 Leader。zk_znode_count:ZNode 总数。zk_packets_received/sent:网络包吞吐。zk_avg_latency:平均延迟,过高需排查磁盘或网络。zk_outstanding_requests:排队请求数,过高表示性能瓶颈。
七、面试模板话术
“ZooKeeper 是分布式协调的核心中间件。我理解它的数据模型是树形 ZNode,有持久、临时、顺序三种节点,基于 ZAB 协议实现强一致。在实际银行项目中,我主要用它做两件事:第一是资金交易的分布式锁,利用临时顺序节点实现公平锁,避免 Redis 主从切换丢锁;第二是批处理任务的选主,保证日切逻辑只在一个实例执行。
配置上我使用 Curator 框架封装,避免原生 API 的复杂性。踩过的坑包括 Watcher 惊群和 Session 超时设置,后来都用
TreeCache和调整tickTime解决了。与 Redis 比,ZooKeeper 性能低但绝对安全,所以资金锁用 ZK,非资金锁用 Redis,这是我总结的选型原则。”
这样回答,既有原理、协议、应用,又有对比和实战踩坑,完整展现了你对 ZooKeeper 的全面掌握。
更多推荐
所有评论(0)