一、Zookeeper持久化
当一个zookeeper服务启动时,会将磁盘上的数据还原成DataTree,那么zookeeper在磁盘上是如何来保存数据的?分为两部分,一个是快照文件
,另一个是日志文件
。接下来结合源码来介绍一下这两类文件
1.1 快照文件
快照文件的文件名是以"snapshot"为前缀
的,如snapshot.40d4(40d4是十六进制的表示,代表该快照文件起始的zxid)。
何时生成快照?
当事务执行了5w-10w次(随机数)
,触发生成快照,具体逻辑在org.apache.zookeeper.server.SyncRequestProcessor类的run方法中,源码如下:
if (si != null) {
// 将请求追加到日志文件
if (zks.getZKDatabase().append(si)) {
logCount++;
// snapCount默认值是10w,randRoll初始值为5w之内的随机数
// 如果请求数 大于 5w-10w的随机数
if (logCount > (snapCount / 2 + randRoll)) {
randRoll = r.nextInt(snapCount/2);
// 滚动日志文件
zks.getZKDatabase().rollLog();
if (snapInProcess != null && snapInProcess.isAlive()) {
LOG.warn("Too busy to snap, skipping");
} else {
// 启动快照生成线程
snapInProcess = new Thread("Snapshot Thread") {
public void run() {
try {
zks.takeSnapshot();
} catch(Exception e) {
LOG.warn("Unexpected exception", e);
}
}
};
snapInProcess.start();
}
logCount = 0;
}
}
}
从上面源码可以看到,当写入到日志文件的请求数到达一个随机值(5w-10w),将会触发生成快照,zks.takeSnapshot()内部会调用FileTxnSnapLog类的save方法,将内存中的数据DataTree持久化到快照文件中,具体的序列化源码如下
public static void serializeSnapshot(DataTree dt,OutputArchive oa,
Map<Long, Integer> sessions) throws IOException {
HashMap<Long, Integer> sessSnap = new HashMap<Long, Integer>(sessions);
// 序列化sessions
oa.writeInt(sessSnap.size(), "count");
for (Entry<Long, Integer> entry : sessSnap.entrySet()) {
oa.writeLong(entry.getKey().longValue(), "id");
oa.writeInt(entry.getValue().intValue(), "timeout");
}
// 序列化DataTree
dt.serialize(oa, "tree");
}
1.2 日志文件
日志文件的文件名是以"log"为前缀
的,如log.40d4(40d4是十六进制的表示,代表该日志文件起始的zxid)。从上面也可以看到zookeeper处理请求时,会先将请求追加到日志文件中。
服务启动时如何使用日志文件还原到之前状态?
在zookeeper服务启动时,会先调用ZKDatabase.loadDataBase()将磁盘的数据还原到DataTree,源码如下
org.apache.zookeeper.server.ZKDatabase
public long loadDataBase() throws IOException {
PlayBackListener listener=new PlayBackListener(){
public void onTxnLoaded(TxnHeader hdr,Record txn){
Request r = new Request(null, 0, hdr.getCxid(),hdr.getType(),
null, null);
r.txn = txn;
r.hdr = hdr;
r.zxid = hdr.getZxid();
addCommittedProposal(r);
}
};
// 通过磁盘上的快照文件、日志文件,将数据还原到DataTree
long zxid = snapLog.restore(dataTree,sessionsWithTimeouts,listener);
initialized = true;
return zxid;
}
这里可以看到调用了FileTxnSnapLog的restore方法,看下该方法具体是如何来还原DataTree的?源码如下
public long restore(DataTree dt, Map<Long, Integer> sessions,
PlayBackListener listener) throws IOException {
// 先将快照文件的数据反序列化到DataTree中
snapLog.deserialize(dt, sessions);
FileTxnLog txnLog = new FileTxnLog(dataDir);
// 读取大于当前DataTree的zxid的日志数据
TxnIterator itr = txnLog.read(dt.lastProcessedZxid+1);
long highestZxid = dt.lastProcessedZxid;
TxnHeader hdr;
try {
while (true) {
hdr = itr.getHeader();
if (hdr == null) {
return dt.lastProcessedZxid;
}
if (hdr.getZxid() < highestZxid && highestZxid != 0) {
LOG.error("{}(higestZxid) > {}(next log) for type {}",
new Object[] { highestZxid, hdr.getZxid(),
hdr.getType() });
} else {
highestZxid = hdr.getZxid();
}
try {
// 处理单个事务请求,将数据还原到DataTree
processTransaction(hdr,dt,sessions, itr.getTxn());
} catch(KeeperException.NoNodeException e) {
throw new IOException("Failed to process transaction type: " +
hdr.getType() + " error: " + e.getMessage(), e);
}
listener.onTxnLoaded(hdr, itr.getTxn());
if (!itr.next())
break;
}
} finally {
if (itr != null) {
itr.close();
}
}
return highestZxid;
}
- 第一步,先将快照文件数据反序列化到DataTree
- 读取日志文件中
大于当前DataTree的zxid的日志数据
- 遍历日志数据,逐一将日志数据还原到DataTree中
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。