本章定位:理解 ZooKeeper 如何在节点宕机重启后恢复数据,以及新节点如何从 Leader 同步完整状态。
定义与作用
数据同步是 ZooKeeper 集群保证数据一致性的关键机制。它解决两个核心问题:
| 场景 | 同步解决什么 |
|---|---|
| Follower 宕机重启 | 从 Leader 获取宕机期间错过的事务 |
| 新节点加入集群 | 从 Leader 获取全量数据 |
| Leader 宕机恢复 | 从本地 Snapshot + 事务日志启动,再与 Follower 对齐 |
同步依赖两种持久化数据:
| 数据 | 文件 | 内容 |
|---|---|---|
| Snapshot(快照) | snapshot.<lastZXID> | 某个时间点的内存全量数据 |
| Transaction Log(事务日志) | log.<firstZXID> | 每次写操作的串行记录 |
核心原理
同步流程
四种同步类型:SNAP(全量快照同步)、DIFF(增量事务同步)、TRUNC(截断回滚)、TRUNC+DIFF(截断 + 增量)。
快照触发机制
完整示例
示例一:观察 Snapshot 和事务日志
场景说明:查看 dataDir 下的实际文件,理解快照和日志的对应关系。
ls /tmp/zk-data/1/version-2/
# 输出:
log.1 # 事务日志,记录 ZXID 范围 [1, xxx]
snapshot.0 # 初始快照(空状态)
snapshot.100 # ZXID=100 时的快照
# 使用 ZooKeeper 自带的工具查看快照
java -cp zookeeper-3.7.0.jar:lib/* org.apache.zookeeper.server.SnapshotFormatter \
/tmp/zk-data/1/version-2/snapshot.100
# 输出:
# /zookeeper/config
# /zookeeper/quota
# /app/config = port=8080
# /app/services (子节点:order-service-1)
# ...
# SessionDetails:
# 0x1000000001: timeout=30000
操作前后对比:
| 文件 | 含义 | 用途 |
|---|---|---|
| snapshot.0 | 初始空快照 | 服务启动基准 |
| snapshot.100 | ZXID=100 时内存状态 | 快速恢复到 ZXID=100 |
| log.1 ~ log.101 | 事务日志区间 | 从 ZXID=100 增量恢复到 ZXID=101 |
示例二:数据恢复验证
场景说明:模拟 Follower 宕机后重启,验证数据不丢失。
# 1. 在集群中写入数据
zkCli.sh -server 127.0.0.1:2182 # Leader
[zkshell: 0] create /recovery-test "before-crash"
Created /recovery-test
# 2. Follower 宕机
bin/zkServer.sh stop conf/zoo-3.cfg
# 3. 继续写入(Leader 和其他 Follower)
[zkshell: 0] create /recovery-test/node1 "after-crash"
Created /recovery-test/node1
# 4. Follower 重启
bin/zkServer.sh start conf/zoo-3.cfg
# 日志显示:
# USING SNAPSHOT snapshot.100
# FOLLOWING - LEADER ELECTION TOOK - 50 MS
# SYNCING WITH LEADER
# 5. Follower 恢复后验证
zkCli.sh -server 127.0.0.1:2183
[zkshell: 0] ls /recovery-test
[node1]
# 宕机期间的数据已同步!
操作前后对比:
| 节点 | 操作前 | 宕机期间变化 | 重启后 |
|---|---|---|---|
| Server 3 (2183) | 有 /recovery-test | 下线 | 有 /recovery-test/node1 |
| 恢复方式 | — | — | Snapshot + DIFF 增量同步 |
示例三:强制生成 Snapshot
场景说明:通过四字命令或 API 手动触发快照。
# 方法一:四字命令
echo "srst" | nc 127.0.0.1 2181
# 输出:Snapshot taken
# 方法二:JMX
# 通过 jconsole 连接,执行 MBean: org.apache.ZooKeeperService
# 调用 takeSnapshot()
执行前后对比:
| 时间点 | Snapshot 文件 | 说明 |
|---|---|---|
| SRST 前 | snapshot.100 | 最近一次自动快照 |
| SRST 后 | snapshot.150(新) | 手动触发即时快照 |
手动触发快照可在以下场景使用:备份前确保快照最新;大事务量后手动创建恢复点;运维窗口下的预操作。
易错场景与面试考点
易错场景
1. 事务日志磁盘满
当磁盘满时,事务日志无法写入,Leader 会停止接受写请求并 Crash。现象:
java.io.IOException: No space left on device
预防:配置 autopurge 自动清理,监控磁盘使用率。
2. dataDir 和 dataLogDir 放在同一块磁盘
快照写入(随机 I/O)和事务日志写入(顺序 I/O)相互干扰,导致 fsync 延迟增大,整体写性能下降。
3. SNAP 同步的时空代价
新节点加入集群或落后太多时触发全量 SNAP 同步。数据量大时可能占用数 GB 网络带宽和数十分钟时间。生产环境应避免频繁的 SNAP 同步(通过合理配置 snapCount 和保持节点稳定)。
面试高频题
Q:Snapshot 和事务日志的关系?
A:Snapshot 是某个 ZXID 时刻内存状态的完整镜像。事务日志记录了 Snapshot 之后的增量事务。重启时:
- 加载最近 Snapshot(快速恢复主体状态)
- 重放事务日志(增量恢复到最终状态)
这种设计平衡了恢复速度和存储开销。
Q:DIFF 和 SNAP 同步的触发条件?
A:Leader 根据 Follower 上报的最新 ZXID 判断:
- ZXID 在 Leader 事务日志范围内 → DIFF 增量同步(发送缺失的事务)
- ZXID 不在范围内(太老,日志已清理) → SNAP 全量同步
- ZXID 大于 Leader → TRUNC 截断(说明 Follower 有未提交事务需回滚)
Q:什么时候需要 TRUNC 截断?
A:Leader 宕机时可能有部分 Proposal 已发送但未 Commit。新 Leader 的 ZXID 可能小于某些 Follower 的 ZXID,此时需要 Follower 截断(删除)这些未提交的事务。
小结
| 要点 | 说明 |
|---|---|
| 两种持久化 | Snapshot(全量快照)+ Transaction Log(增量日志) |
| 三种同步方式 | SNAP(全量)、DIFF(增量)、TRUNC(截断) |
| snapCount | 控制快照触发频率 |
| autopurge | 自动清理旧快照和日志 |
| 恢复流程 | 加载 Snapshot → 重放日志 → 与 Leader 对齐 |
数据同步是 ZAB 协议崩溃恢复的具体实现。下一章进入 Leader 选举,理解集群如何选出主节点。