当前版本Apache IoTDB 0.11.2
1. 声明
当前内容主要为了解MultiFileLogNodeManager这个类的作用,再讨论Apache IoTDB的wal实现方式flush策略(这个类是由IoTDB这个类中使用RegisterManager进行注册的)
从名称看应该是多文件日志节点管理器(应该就是写Wal文件的管理器,这个类位于org\apache\iotdb\db\writelog\manager
包下面)
2. 分析源码
由于registerManager的register方法,所以必定调用start方法,现在看一下该类的字段和构造函数
这里在无参构造器的下面,构建了一个从测点映射到写日志的并发Map,并且字段中有ScheduledExecutorService表明是一个定时任务
继续查看start方法
@Override
public void start() throws StartupException {
try {
// 从配置(iotdb-engine.properties)中得到当前是否开启wal,这里一般都是true
if (!config.isEnableWal()) {
return;
}
// 判断当前执行wal的间隔时间是否有,该事件必须大于0
if (config.getForceWalPeriodInMs() > 0) {
executorService = Executors.newSingleThreadScheduledExecutor();
executorService.scheduleWithFixedDelay(this::forceTask, config.getForceWalPeriodInMs(),
config.getForceWalPeriodInMs(), TimeUnit.MILLISECONDS);
}
} catch (Exception e) {
throw new StartupException(this.getID().getName(), e.getMessage());
}
}
这里非常明显就是从配置中得到封口wal的间隔时间(毫秒),并周期性的执行当前类的forceTask方法,从配置文件中得到这个间隔时间为100ms
这里应该可以得到信息wal是定时任务写入的,应该和前面的一个并发Map有关(将内存中的日志写入到磁盘中的wal)
继续查看forceTask方法
private final void forceTask() {
if (IoTDBDescriptor.getInstance().getConfig().isReadOnly()) {
logger.warn("system mode is read-only, the force flush WAL task is stopped");
return;
}
if (Thread.interrupted()) {
logger.info("WAL force thread exits.");
return;
}
for (WriteLogNode node : nodeMap.values()) {
try {
node.forceSync();
} catch (IOException e) {
logger.error("Cannot force {}, because ", node, e);
}
}
}
果然这里就是将内存中的测点映射的WriteLogNode(将内存的写入到磁盘的wal),这里调用WriteLogNode的forceSync()方法
这里发现getNode中new了一个ExclusiveWriteLogNode,在通过查看源码发现这就是该wal日志的实现类(发现WriteLogNode就是一个接口)
转到ExclusiveWriteLogNode的forceSync方法中
@Override
public void forceSync() {
sync(); // 将内存中的byte写入到磁盘
forceWal();//进行封口
}
转到sync方法
private void sync() {
lock.writeLock().lock();
try {
if (bufferedLogNum == 0) {
return;
}
try {
// 得到文件写入器,将该buffer写入到文件中
getCurrentFileWriter().write(logBuffer);
} catch (IOException e) {
logger.error("Log node {} sync failed, change system mode to read-only", identifier, e);
IoTDBDescriptor.getInstance().getConfig().setReadOnly(true);
return;
}
logBuffer.clear();
bufferedLogNum = 0;
logger.debug("Log node {} ends sync.", identifier);
} finally {
lock.writeLock().unlock();
}
}
这里得到将内存的buffer写入到磁盘中,但是肯定有些buffer的方法(为此找到一个write的方法)
@Override
public void write(PhysicalPlan plan) throws IOException {
lock.writeLock().lock();
try {
putLog(plan); // 存放一个物理计划到内存中
if (bufferedLogNum >= config.getFlushWalThreshold()) {
sync();
}
} catch (BufferOverflowException e) {
throw new IOException(
"Log cannot fit into the buffer, please increase wal_buffer_size", e);
} finally {
lock.writeLock().unlock();
}
}
private void putLog(PhysicalPlan plan) {
logBuffer.mark();
try {
// 将物理计划序列化到buffer中
plan.serialize(logBuffer);
} catch (BufferOverflowException e) {
logger.info("WAL BufferOverflow !");
logBuffer.reset();
sync();
plan.serialize(logBuffer);
}
bufferedLogNum ++;
}
至此,所有线路全部通了,wal中就是一个物理计划类序列化并使用标记按各个标识进行存放的byte内容,所以是先有物理计划-->buffer-->wal
3. 观察写入时的wal
先保证所有的wal都已经flush了,然后在打开写入一个数据
观察服务器:
发现这个创建的是一个叫做root.test-1626312981377-1-0.tsfile
的文件夹,从文件夹中发现了一个叫做wal1的文件
回看data中的tsfile文件,发现了这个1626312981377-1-0.tsfile
文件 (这应该表示该wal1的前日志是对应的这个tsfile吗?)
上述中证明了的确是有wal文件的产生,并且类也是使用正确的,从上述中证明了IoTDB中的写前日志是先内存再写磁盘的
4. 总结
1. 总算解析完了这个类,证明这个MultiFileLogNodeManager就是定时将内存中的计划buffer写入到磁盘wal中
2. 这也解决了我在项目中的一个疑惑,为什么有的数据写着写着,当IoTDB进入只读后(此时是可以查询到数据的),重启IoTDB后之前写的数据会丢失的问题(查不到数据了)!(只读后,没有机会将内存中的计划写入磁盘wal中,不会有 node.forceSync();执行了!小心这个问题
)