注:本文中的请求和事务是同一个含义,表示来自客户端的写请求
背景
Zookeeper虽然是内存数据库,但为了保证高可靠性,其同时提供了持久化功能,通过快照和事务日志将数据保存在磁盘中.
事务日志
- 每个执行的事务都会写入到事务日志中,其存储位置由
dataLogDir
配置,当未配置dataLogDir
时,使用dataDir
作为存储目录,由于事务日志的写入速度较为影响Zookeeper的性能,可以将dataLogDir
单独配置到一块磁盘上 - 由于事务日志要不断的写入,会触发底层磁盘I/O为文件开辟新的磁盘块,为了减少分配新磁盘块对写入的影响,Zookeeper使用预分配策略,默认每次分配新文件或扩容时,一次分配64MB
- 扩容事务日志文件时机:初始化事务日志文件时为其分配64MB,当写入事务日志的过程中,发现剩余可写入空间小于4KB时,进行扩容,依然是为事务日志文件增加64MB
- 生成新事务日志文件时机:即使当前事务日志文件可写空间较少,也只会进行扩容,不会生成新的事务日志文件.在经过
snapCount
次事务后,会生成快照文件,但同时将当前事务日志的输出流置null,这样下次写事务日志时自动创建新的事务日志文件 - 为了便于快速根据zxid找到存储该zxid对应事务的事务日志文件,事务日志文件的命名是有意义的,事务日志文件的命名为
log.{zxid}
,后缀是该日志文件存储的第一个事务的zxid
快照
- 生成快照文件时机:经过
snapCount
次事务后,会生成快照文件 - 和事务日志文件一样,快照文件的命名也是有意义的,命名为
snapShot.{zxid}
,后缀时该快照文件生成时已执行的最新的事务的zxid,即[1,zxid]的所有事务已应用到DataTree
相关类
- TxnLog:负责处理事务日志
- SnapShot:负责处理快照
- FileTxnSnapLog:组合TxnLog和SnapShot,是Zookeeper上层服务器和底层数据存储之间的对接层
FileTxnSnapLog可以完成数据恢复,持久化,日志截断等功能,下面则依次介绍何时执行这些操作以及如何执行.
数据恢复
总流程
在QuorumPeerMain
启动ZookeeperServer的过程中,需要从磁盘中恢复数据,恢复数据共有两个步骤
- 从快照中恢复
DataTree
,返回通过快照恢复的数据的最大zxid - 从事务日志中获取大于zxid的所有日志,将其应用到步骤1中初步恢复的
DataTree
中
/**
* this function restores the server database after reading from the snapshots and transaction logs
*
* @param dt the datatree to be restored
* @param sessions the sessions to be restored
* @param listener the playback listener to run on the
* database restoration
* @return the highest zxid restored
* @throws IOException
*/
public long restore(DataTree dt, Map<Long, Integer> sessions,
PlayBackListener listener) throws IOException {
//1.解析快照文件,同时更新dt.lastProcessedZxid
long deserializeResult = snapLog.deserialize(dt, sessions);
//2.处理事务日志
FileTxnLog txnLog = new FileTxnLog(dataDir);
boolean trustEmptyDB;
File initFile = new File(dataDir.getParent(), "initialize");
if (Files.deleteIfExists(initFile.toPath())) {
LOG.info("Initialize file found, an empty database will not block voting participation");
trustEmptyDB = true;
} else {
trustEmptyDB = autoCreateDB;
}
if (-1L == deserializeResult) {
/* this means that we couldn't find any snapshot, so we need to
* initialize an empty database (reported in ZOOKEEPER-2325) */
if (txnLog.getLastLoggedZxid() != -1) {
throw new IOException(
"No snapshot found, but there are log entries. " +
"Something is broken!");
}
if (trustEmptyDB) {
/* TODO: (br33d) we should either put a ConcurrentHashMap on restore()
* or use Map on save() */
save(dt, (ConcurrentHashMap<Long, Integer>) sessions, false);
/* return a zxid of 0, since we know the database is empty */
return 0L;
} else {
/* return a zxid of -1, since we are possibly missing data */
LOG.warn("Unexpected empty data tree, setting zxid to -1");
dt.lastProcessedZxid = -1L;
return -1L;
}
}
return fastForwardFromEdits(dt, sessions, listener);
}
上面是恢复DataTree
的总步骤,包含了一些错误处理代码,目前还不清楚何时会出现错误?自然不了解错误处理代码是如何处理错误的?因此只介绍正常情况下恢复数据的步骤
从快照中恢复
/**
* deserialize a data tree from the most recent snapshot
* 反序列化快照文件
* <p>
* 副作用:修改了{@link DataTree#lastProcessedZxid}
* <p>
* 若最新的有效的快照文件名为snapShot.n,则[1,n]的所有事务的执行结果都在快照文件中,此时返回n
*
* @return the zxid of the snapshot(快照数据保存的最后处理的zxid)
*/
@Override
public