一、核心概述
Zookeeper 的持久化机制核心用于保障数据可靠存储,相关类集中在 org.apache.zookeeper.server.persistence 包下,核心围绕事务日志(TxnLog)和快照(Snapshot)展开,本次重点分析事务日志相关的 TxnLog 接口与实现类 FileTxnLog。
二、持久化总体框架(核心类 / 接口)
| 类 / 接口 | 类型 | 核心作用 |
| TxnLog | 接口 | 定义事务日志的核心操作规范(读写、回滚、获取最新 zxid 等) |
| FileTxnLog | 类(实现 TxnLog) | 事务日志的具体实现,提供访问事务日志的 API,处理日志的追加、读取、提交等 |
| Snapshot | 接口 | 定义快照持久化的规范 |
| FileSnap | 类(实现 Snapshot) | 快照的具体实现,负责快照的存储、序列化、反序列化与访问 |
| FileTxnSnapLog | 类 | 封装 TxnLog 和 Snapshot,统一提供持久化操作入口 |
| Util | 工具类 | 提供持久化所需的辅助 API(如数据序列化、解析等) |
| FileHeader | 类 | 日志文件头部结构定义 |
三、TxnLog 接口核心规范
TxnLog 是事务日志的顶层接口,定义了 4 个核心操作方法,同时包含日志读取迭代器 TxnIterator:
rollLog():回滚当前正在追加的日志。append(TxnHeader hdr, Record r):向事务日志中追加一条事务(包含事务头和事务体),返回是否追加成功。read(long zxid):从指定 zxid 开始读取事务日志,返回日志迭代器TxnIterator。getLastLoggedZxid():获取日志中记录的最后一个事务的 zxid(最新事务 ID)。
四、FileTxnLog 核心细节(实现类)
1. 日志文件(LogFile)格式
日志文件分为 3 部分,结构清晰:
plaintext
LogFile = FileHeader + TxnList + ZeroPad
- FileHeader(文件头部):4 字节 magic(标识 “ZKLG”) + 4 字节 version(版本) + 8 字节 dbid(数据库 ID)。
- TxnList(事务列表):多个事务按顺序排列,单个事务格式为
Txn = checksum(校验和) + Txnlen(事务长度,4字节) + TxnHeader(事务头) + Record(事务体) + 0x42(结束标识)。
- TxnHeader(事务头):8 字节 sessionid + 4 字节 cxid + 8 字节 zxid + 8 字节 time + 4 字节 type(事务类型)。
- ZeroPad(填充字段):文件预分配阶段,用 0 填充至文件大小为 64MB(不满时补 0)。
2. 核心函数功能与流程
(1)append 函数:追加事务日志
- 核心作用:将事务头(TxnHeader)和事务体(Record)写入日志文件。
- 关键步骤:
- 校验 TxnHeader 非空,否则返回失败。
- 初始化日志流(logStream)、FileHeader,序列化 FileHeader 写入文件。
- 强制刷新流,确保数据暂存磁盘。
- 填充 0 至文件达到 64MB(调用 padLog 函数)。
- 序列化事务头和事务体为 ByteBuffer(通过 Util.marshallTxnEntry)。
- 用 Checksum 算法校验 ByteBuffer,确保数据完整性。
- 将校验后的 ByteBuffer 写入磁盘,返回成功。
(2)getLogFiles 函数:获取并筛选日志文件
- 核心作用:筛选出 “zxid≤指定 snapshotZxid” 的所有日志文件,并排序。
- 关键步骤:
- 按 zxid 升序排序所有日志文件。
- 遍历找到最大的 “≤snapshotZxid” 的 logZxid。
- 筛选出 zxid≥logZxid 的所有日志文件,返回结果。
- 辅助函数:
sortDataDir(按 zxid 升 / 降序排序日志文件)、getZxidFromName(从文件名解析 zxid)。
(3)getLastLoggedZxid 函数:获取最新事务 zxid
- 核心作用:找到日志中最后一条事务的 zxid(最新事务 ID)。
- 关键步骤:
- 获取排序后的日志文件,取最后一个文件的 zxid 作为候选最大值。
- 读取该候选 zxid 之后的所有事务。
- 遍历事务提取 zxid,返回最大的那个(即最新 zxid)。
- 依赖:调用
read()生成FileTxnIterator(TxnIterator 子类),通过init()、goToNextLog()、next()迭代读取事务。
(4)commit 函数:提交事务日志至磁盘
- 核心作用:确保日志数据真正持久化到磁盘,避免丢失。
- 关键步骤:
- 强制刷新 logStream 至磁盘。
- 遍历所有待刷新流(streamsToFlush)并刷新。
- 若需强制同步,计算流耗时并告警。
- 移除并关闭所有流。
(5)truncate 函数:清空指定 zxid 之后的日志
- 核心作用:删除所有 zxid 大于 “给定 zxid” 的事务日志,用于数据回滚或清理。
(6)next 函数(FileTxnIterator 中):迭代读取下一条事务
- 关键步骤:
- 读取事务的 crcValue(校验值)。
- 用 CRC32 算法校验事务数据,与 crcValue 比对,不一致则抛异常。
- 反序列化事务头和事务体,确定事务类型并存储。
- 读取异常时关闭流,尝试读取下一条事务。
五、核心要点总结
- 事务日志核心流程:通过
append追加日志 →Checksum校验数据完整性 →commit提交至磁盘 →read迭代读取 →truncate清理过期日志。 - 关键保障:日志文件固定 64MB(ZeroPad 填充)、事务校验(crcValue + CRC32)、强制刷新流,确保数据可靠。
- 核心依赖:
TxnHeader(事务元信息)、Record(事务数据,需实现序列化)、zxid(事务唯一标识,用于日志排序和定位)。 - 核心函数作用:
append:日志写入;commit:日志持久化;getLastLoggedZxid:定位最新事务;truncate:日志清理;getLogFiles:日志筛选排序。