4. 数据与存储
ZkDatabase是ZK的内存数据库,其负责ZK的所有会话、DataTree
存储和事务日志。其会定时向磁盘 dump 快照数据,同时在 ZK 启动的时候,会通过磁盘上的事务日志和快照数据恢复内存数据。
ZK 中的数据存储分为两部分:内存数据存储、磁盘数据存储。其中内存数据中最核心的及时DataTree
,而磁盘数据包括 快照数据和事务日志。
内存数据
DataTree
是一个“树”形结构,按路径形成一个树形结构。其内部结构如下:
1 | ConcurrentHashMap<String, DataNode> nodes = |
nodes用来保存所有path到节点的映射;
ephemerals来保存所有零时节点集合;
dataWatches和childWatches分别是监听器,当数据节点发生变化时触发相应的watch;
aclCache保存所有的访问控制信息。
DataNode的数据结构如下:
1 | byte data[]; |
其创建节点的逻辑如下:
1 | public void createNode(final String path, byte data[], List<ACL> acl, long ephemeralOwner, int parentCVersion, long zxid, long time, Stat outputStat) { |
磁盘数据
ZK中,FileTxnSnapLog
是磁盘存储的核心类。
1 | public class FileTxnSnapLog { |
磁盘文件分为两部分:快照文件和事务日志文件。其中快照文件将定期保存内存数据库内容,事务日志则保存所有事务记录。
恢复时,首先选择一个可用的最近保存的快照文件进行恢复,其后再读取事务日志进行恢复。
下面重点来看看其 写入日志和数据快照的过程 和 服务启动恢复的过程:
日志写入
服务端负责日志写入的是SyncRequestProcessor。
其过程如下:
- 首先向事务日志中追加一条记录;
- 然后判断追加的记录数量是否大于
snapCount / 2 + randRoll
(其中snapCount
默认为100000,randRoll
为随机数,之所以这么做是为了确保所有服务器不是同一时间做快照),如果大于则首先滚动日志文件(将FileTxnLog.logStream
设置为null
),然后起异步线程snapInProcess
进行快照。 - 默认当积累的事务记录超过1000个则进行flush到磁盘。
下面分别来看追加事务记录和快照的过程:
- 追加事务记录:其最终会调用
FileTxnLog.append
方法,其内部执行过程如下:- 首先判断
logStream==null
,若是则根据请求的Zxid创建新的事务日志文件,并为文件预分配64MB
的空间,填充0
; - 其后序列化事务头和事务体,然后据此生成
CheckSum
,写入文件中。
- 首先判断
- 快照:其最终会调用
ZookeeperServer.takeSnapshot
方法:
1 | txnLogFactory.save(zkDb.getDataTree(), zkDb.getSessionWithTimeOuts(), syncSnap); |
跟据DataTree.lastProcessedZxid
在snapDir
目录下生成一个快照文件。然后序列化DataTree
和Session
存入文件中。
数据恢复
当服务器重启时,会调用ZkDatabase.loadDataBase
进行数据恢复。
其最终会调用FileTxnSnapLog.restore
方法:
- 首先找到最近可用的快照文件恢复
DataTree
和Session
;- 近可能多找出可用的100个快照文件,按文件名进行逆序排序;
- 选取最近的快照文件进行反序列化恢复
DataTree
和Session
,并做一次校验。
- 根据快照文件恢复后的
DataTree
对应的lastProcessZxod
获取事务记录,然后将事务应用到DataTree
和Session
上。(FileTxnSnapLog.fastForwardFromEdits
) - 需要特别提出的是,在事务记录恢复的过程中,会回调
ZKDatabase.commitProposalPlaybackListener
,将事务加入到committedLog
中,并保存minCommitedLog
和maxCommitedLog
,其在选举后Learner
与Leader
见的数据同步过程中起着重要作用。
数据同步
在前面《选举算法与实现》
章节中,提到完成选举后,Leader与各Learner
会进行一次数据同步,有三种模式:DIFF
、SNAP
、TURNC
。这里令Learner
发送到Leader
的Zxid
为peerLastZxid
。
- 在
peerLastZxid
在minCommitedLog
和maxCommitedLog
之间时,执行DIFF
操作,直接从Leader
的ZkDataabse.committedLog
中消费; - 当
peerLastZxid
小于minCommitedLog
,则进行全量数据同步; - 当
peerLastZxid
大于maxCommitedLog
,Learner
则需要进行回退操作。