大数据开发之 zookeeper 的数据与存储
一、内存数据
zk 的数据模型是树结构,在内存数据库中,存储了整棵树的内容,包括所有的节点路径、节点数据、ACL 信息,zk 会定时将这个数据存储到磁盘上
1.1 DataTree
DataTree 是内存数据存储的核心,是一个树结构,代大数据培训表了内存中一份完整的数据。DataTree 不包含任何与网络、客户端连接及请求处理相关的业务逻辑,是一个独立的组件。
1.2 DataNode
DataNode 是数据存储的最小单元,其内部除了保存了结点的数据内容、ACL 列表、节点状态之外,还记录了父节点的引用和子节点列表两个属性,其也提供了对子节点列表进行操作的接口。
1.3 ZKDatabase
zk 的内存数据库,管理 zk 的所有会话、DataTree 存储和事务日志。ZKDatabase 会定时向磁盘 dump 快照数据,同时在 zk 启动时,会通过磁盘的事务日志和快照文件恢复成一个完整的内存数据库。
二、事务日志
2.1 日志写入
FileSnap 负责维护快照数据对外的接口,包括快照数据的写入和读取等,将内存数据库写入快照数据文件其实是一个序列化过程。针对客户端的每一次事务操作,zk 都会将他们记录到事务日志中,同时也会将数据变更应用到内存数据库中,zk 在进行若干次事务日志记录后,将内存数据库的全量数据 Dump 到本地文件中,这就是数据快照。其步骤如下;
1.确定是否需要进行数据快照。每进行一次事务日志记录之后,zk 都会检测当前是否需要进行数据快照,考虑到数据快照对于 zk 机器的影响,需要尽量避免 zk 集群中的所有机器在同一时刻进行数据快照。采用过半随机策略进行数据快照操作。
2.切换事务日志文件。表示当前的事务日志已经写满,需要重新创建一个新的事务日志。
3.创建数据快照异步线程。创建单独的异步线程来进行数据快照以避免影响 zk 主流程。
4.获取全量数据和会话信息。从 ZKDatabase 中获取到 DataTree 和会话信息。
5.生成快照数据文件名。zk 根据当前已经提交的最大 ZXID 来生成数据快照文件名。
6.数据序列化。首先序列化文件头信息,然后再对会话信息和 DataTree 分别进行序列化,同时生成一个 Checksum,一并写入快照数据文件中去。
四、数据初始化
在 zk 服务器启动期间,首先会进行数据初始化工作,用于将存储在磁盘上的数据文件加载到 zk 服务器内存中。
4.1 初始化流程
zk 的初始化过程如下图所示;
数据的初始化工作是从磁盘上加载数据的过程,主要包括了从快照文件中加载快照数据和根据实物日志进行数据修正两个过程。
初始化 FileTxnSnapLog 和
FileTxnSnapLog 是 zk 事务日志和快照数据访问层,用于衔接上层业务和底层数据存储,底层数据包含了事务日志和快照数据两部分。FileTxnSnapLog 中对应 FileTxnLog 和 FileSnap。
初始化 ZKDatabase。首先构建 DataTree,同时将 FileTxnSnapLog 交付 ZKDatabase,以便内存数据库能够对事务日志和快照数据进行访问。在 ZKDatabase 初始化时,DataTree 也会进行相应的初始化工作,如创建一些默认结点如/、/zookeeper、/zookeeper/quota 三个节点。
创建 PlayBackListener。其主要用来接收事务应用过程中的回调,在 zk 数据恢复后期,会有事务修正过程,此过程会回调 PlayBackListener 来进行对应的数据修正。
处理快照文件。此时可以从磁盘中恢复数据了,首先从快照文件开始加载。
获取最新的 100 个快照文件。更新时间最晚的快照文件包含了最新的全量数据。这里的 100 是硬编码,无参数配置。
解析快照文件。逐个解析快照文件,此时需要进行反序列化,生成 DataTree 和 sessionsWithTimeouts,同时还会校验 Checksum 及快照文件的正确性。对于 100 个快找文件,如果正确性校验通过时,通常只会解析最新的那个快照文件。只有最新快照文件不可用时,才会逐个进行解析,直至 100 个快照文件全部解析完。若将 100 个快照文件解析完后还是无法成功恢复一个完整的 DataTree 和 sessionWithTimeouts,此时服务器启动失败。
获取最新的 ZXID。此时根据快照文件的文件名即可解析出最新的 ZXID:zxid_for_snap。该 ZXID 代表了 zk 开始进行数据快照的时刻。
处理事务日志。此时服务器内存中已经有了一份近似全量的数据,现在开始通过事务日志来更新增量数据。
获取所有 zxid_for_snap 之后提交的事务。此时,已经可以获取快照数据的最新 ZXID。只需要从事务日志中获取所有 ZXID 比步骤 7 得到的 ZXID 大的事务操作。
10.事务应用。获取大于 zxid_for_snap 的事务后,将其逐个应用到之前基于快照数 据文件恢复出来的 DataTree 和 sessionsWithTimeouts。每当有一个事务被应用 到内存数据库中后,zk 同时会回调 PlayBackListener,将这事务操作记录转换成 Proposal,并保存到 ZKDatabase 的 committedLog 中,以便 Follower 进行快速 同步。
11.获取最新的 ZXID。待所有的事务都被完整地应用到内存数据库中后,也就基本 上完成了数据的初始化过程,此时再次获取 ZXID,用来标识上次服务器正常运行 时提交的最大事务 ID。
12.校验 epoch。epoch 标识了当前 Leader 周期,集群机器相互通信时,会带上这个 epoch 以确保彼此在同一个 Leader 周期中。完成数据加载后,zk 会从步骤 11 中 确定 ZXID 中解析出事务处理的 Leader 周期:epochOfZxid。同时也会从磁盘的 currentEpoch 和 acceptedEpoch 文件中读取上次记录的最新的 epoch 值,进行 校验。
五、数据同步
5.1 同步流程
整个集群完成 Leader 选举后,Learner 会向 Leader 进行注册,当 Learner 向 Leader 完成注册后,就进入数据同步环节,同步过程就是 Leader 将那些没有在 Learner 服务器上提交过的事务请求同步给 Learner 服务器,大体过程如下
获取 Learner 状态。在注册 Learner 的最后阶段,Learner 服务器会发送给 Leader 服务器一个 ACKEPOCH 数据包,Leader 会从这个数据包中解析出该 Learner 的 currentEpoch 和 lastZxid。
数据同步初始化。首先从 zk 内存数据库中提取出事务请求对应的提议缓存队列 proposals,同时完成 peerLastZxid(该 Learner 最后处理的 ZXID)、minCommittedLog(Leader 提议缓存队列 commitedLog 中最小的 ZXID)、maxCommittedLog(Leader 提议缓存队列 commitedLog 中的最大 ZXID)三个 ZXID 值的初始化。
对于集群数据同步而言,通常分为四类,直接差异化同步(DIFF 同步)、先回滚再差异化同步(TRUNC+DIFF 同步)、仅回滚同步(TRUNC 同步)、全量同步(SNAP 同步),在初始化阶段,Leader 会优先以全量同步方式来同步数据。同时,会根据 Leader 和 Learner 之间的数据差异情况来决定最终的数据同步方式。直接差异化同步(DIFF 同步,peerLastZxid 介于 minCommittedLog 和 maxCommittedLog 之间)。Leader 首先向这个 Learner 发送一个 DIFF 指令,用于通知 Learner 进入差异化数据同步阶段,Leader 即将把一些 Proposal 同步给自己,针对每个 Proposal,Leader 都会通过发送 PROPOSAL 内容数据包和 COMMIT 指令数据包来完成,
先回滚再差异化同步(TRUNC+DIFF 同步,Leader 已经将事务记录到本地事务日志中,但是没有成功发起 Proposal 流程)。当 Leader 发现某个 Learner 包含了一条自己没有的事务记录,那么就需要该 Learner 进行事务回滚,回滚到 Leader 服务器上存在的,同时也是最接近于 peerLastZxid 的 ZXID。· 仅回滚同步(TRUNC 同步,peerLastZxid 大于 maxCommittedLog)。Leader 要求 Learner 回滚到 ZXID 值为 maxCommittedLog 对应的事务操作。· 全量同步(SNAP 同步,peerLastZxid 小于 minCommittedLog 或 peerLastZxid 不等于 lastProcessedZxid)。Leader 无法直接使用提议缓存队列和 Learner 进行同步,因此只能进行全量同步。Leader 将本机的全量内存数据同步给 Learner。Leader 首先向 Learner 发送一个 SNAP 指令,通知 Learner 即将进行全量同步,随后,Leader 会从内存数据库中获取到全量的数据节点和会话超时时间记录器,将他们序列化后传输给 Learner。Learner 接收到该全量数据后,会对其反序列化后载入到内存数据库中。
原创作者:徐卖狼
评论