作用
FileTnxSnapLog封装了TxnLog和SnapShot,其在持久化过程中是一个帮助类。
源码
属性
private final File dataDir;//事务日志目录
//the directory containing the
//the snapshot directory
private final File snapDir;//快照目录
private TxnLog txnLog;
private SnapShot snapLog;
public final static int VERSION = 2;//版本号
public final static String version = "version-";
private static final Logger LOG = LoggerFactory.getLogger(FileTxnSnapLog.class);
内部类
//根据日志恢复dataTree,session时的回调函数
public interface PlayBackListener {
void onTxnLoaded(TxnHeader hdr, Record rec);
}
构造函数
public FileTxnSnapLog(File dataDir, File snapDir) throws IOException {
LOG.debug("Opening datadir:{} snapDir:{}", dataDir, snapDir);
//默认生成一个version-2的文件夹
this.dataDir = new File(dataDir, version + VERSION);
this.snapDir = new File(snapDir, version + VERSION);
if (!this.dataDir.exists()) {
if (!this.dataDir.mkdirs()) {
throw new IOException("Unable to create data directory "
+ this.dataDir);
}
}
if (!this.snapDir.exists()) {
if (!this.snapDir.mkdirs()) {
throw new IOException("Unable to create snap directory "
+ this.snapDir);
}
}
txnLog = new FileTxnLog(this.dataDir);
snapLog = new FileSnap(this.snapDir);
}
构造函数主要就是生成FileTxnLog和SnapLog的目录以及对象
其他函数
根据日志中恢复dataTree和session
//根据snapshot和txnlog,恢复datatree以及sessions,并配置回调函数
public long restore(DataTree dt, Map<Long, Integer> sessions,
PlayBackListener listener) throws IOException {
// 根据snap文件反序列化dt和sessions
snapLog.deserialize(dt, sessions);
FileTxnLog txnLog = new FileTxnLog(dataDir);
// 获取事务日志中zxid从dt.lastProcessedZxid+1开始的事务迭代器
TxnIterator itr = txnLog.read(dt.lastProcessedZxid+1);
// 目前快照中最大的zxid
long highestZxid = dt.lastProcessedZxid;
TxnHeader hdr;
try {
while (true) {
// iterator points to
// the first valid txn when initialized
hdr = itr.getHeader();
if (hdr == null) {
//empty logs
return dt.lastProcessedZxid;
}
//如果出现了事务迭代器的zxid比当前最大的zxid小的话,抛异常(就是snapshot是落后于txn的zxid的)
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 {
//在dt上处理事务
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());//利用PlayBackListener进行回调
if (!itr.next())
break;
}
} finally {
if (itr != null) {
itr.close();
}
}
return highestZxid;//返回最高的zxid
}
然后里面调用processTransaction
/*
根据事务日志来处理事务,更新dataTree,
主要就是sessions这个map的维护,再调用dt.processTxn(hdr, txn);
*/
public void processTransaction(TxnHeader hdr,DataTree dt,
Map<Long, Integer> sessions, Record txn)
throws KeeperException.NoNodeException {
ProcessTxnResult rc;
switch (hdr.getType()) {
case OpCode.createSession:
//如果是创建session,添加到sessions这个map里面去
sessions.put(hdr.getClientId(),
((CreateSessionTxn) txn).getTimeOut());
if (LOG.isTraceEnabled()) {
ZooTrace.logTraceMessage(LOG,ZooTrace.SESSION_TRACE_MASK,
"playLog --- create session in log: 0x"
+ Long.toHexString(hdr.getClientId())
+ " with timeout: "
+ ((CreateSessionTxn) txn).getTimeOut());
}
// give dataTree a chance to sync its lastProcessedZxid
rc = dt.processTxn(hdr, txn);//用dataTree处理事务
break;
case OpCode.closeSession:
//如果是关闭session,从sessions这个map里面删除
sessions.remove(hdr.getClientId());
if (LOG.isTraceEnabled()) {
ZooTrace.logTraceMessage(LOG,ZooTrace.SESSION_TRACE_MASK,
"playLog --- close session in log: 0x"
+ Long.toHexString(hdr.getClientId()));
}
rc = dt.processTxn(hdr, txn);//用dataTree处理事务
break;
default:
rc = dt.processTxn(hdr, txn);//用dataTree处理事务
}
其中dt.processTxn会在dataTree数据结构中讲解
将dataTree和session写入log
//将sessions和datatree保存至snapshot文件,是序列化的过程
public void save(DataTree dataTree,
ConcurrentHashMap<Long, Integer> sessionsWithTimeouts)
throws IOException {
long lastZxid = dataTree.lastProcessedZxid;
//按照lastZxid来命名,也就是当前最大的zxid
File snapshotFile = new File(snapDir, Util.makeSnapshotName(lastZxid));
LOG.info("Snapshotting: 0x{} to {}", Long.toHexString(lastZxid),
snapshotFile);
snapLog.serialize(dataTree, sessionsWithTimeouts, snapshotFile);
}
删掉指定zxid后面的所有事务日志
//删掉指定zxid后面的所有事务日志
public boolean truncateLog(long zxid) throws IOException {
// close the existing txnLog and snapLog
close();
// truncate it
FileTxnLog truncLog = new FileTxnLog(dataDir);
boolean truncated = truncLog.truncate(zxid);//用FileTxnLog删掉指定zxid后的所有事务日志
truncLog.close();
// re-open the txnLog and snapLog
// I'd rather just close/reopen this object itself, however that
// would have a big impact outside ZKDatabase as there are other
// objects holding a reference to this object.
txnLog = new FileTxnLog(dataDir);
snapLog = new FileSnap(snapDir);
return truncated;
}
回滚日志
//回滚事务日志
public void rollLog() throws IOException {
txnLog.rollLog();
}
总结
这个类就是FileTxnLog和FileSnap的封装类
restore负责根据日志恢复dataTree和session(反序列化)
save负责将dataTree和session写入log( 序列化)
其他函数基本就是FileTxnLog或者FileSnap的一个wrapper
思考
1.FileTxnLog的写入与事务的执行
在restore函数中可以看出,FileTxnLog是在事务真正执行之前,类似与"预提交"
之后restore函数调用时,才会根据FileTxnLog执行事务,写入dataTree和session
所以才会有rollback回滚日志之类的操作
2.snapLog文件的命名
在save函数中可以看到,用的是lastZxid,也就是一个snap文件的命名是它记录的最后一个zxid,不是第一个
问题
FileTxnLog文件的命名是怎样的
org.apache.zookeeper.server.persistence.Util#makeLogName 并没有看到调用