前言
数据库中基本都有WAL功能,LevelDB也不例外,不过这里的WAL功能实现比较简单
Log文件初始化
在DbImpl的构造函数中,申请了一个新的FileNumber,然后对log对象进行了初始化。
1 | long logFileNumber = versions.getNextFileNumber(); |
所以这个Log文件每次启动都会创建一个新的。
写入
Log的写入也比较简单,每次进行put的时候,将KV序列化特定的格式,然后append进日志系统。
1 | public Snapshot writeInternal(WriteBatchImpl updates, WriteOptions options) { |
更迭
在正常的运行中,Log日志会更迭吗?
因为如果不更迭的话,日志会越来越多,单文件可能会越来越多。
同时我们知道Log格式的文件仅仅支持从头开始遍历的,在Recover的时候,从头到尾遍历一个大文件也是个问题。
在makeRoomForWrite
中,如果MemTable需要进行Compact的时候,就会强制关闭当前的Log,再创建一个新的Log。
1 | private void makeRoomForWrite(boolean force) { |
注意:makeRoomForWrite并不是每次调用都会走到这个逻辑的。
删除
什么时候删除呢?
前面我们看到每次启动时,会新生成一个新的Log,每次CompactMemTable的时候,也会新生成一个新的,那么旧的什么时候删除呢?
答案在deleteObsoleteFiles
方法中。
方法内容如名字,其实这个方法不止删除了旧的Log文件。
这个方法会遍历数据目录下的所有文件,如果是log文件,判断fileNumber是否小于当前的LogFileNumber,如果小于,就可以删除。
了解了内容,我们来想想这个方法会在什么时候调用呢?
前面提到过,每次Compact新的MemTable的时候,都会生成新的Log文件,也就是说,这个Log文件包含了当前MemTable中的内容,也就是未持久化的内容。
如果Compact后,持久化了,自然就不需要这个文件了。
所以在VersionSet::logAndApply后,都会清理旧的Log文件。
因为每次VersionEdit生成后,NewVersion的所有SSTable就已经确定了,新的KV记录则在新的Log日志中,也就不需要再知道旧的Log日志文件是啥了。
Recover
结合了WAL的性质,我们来了解下,recover的内容。
按照理解,Recover时,主要把有内容还是MemTable中,还没持久化到磁盘上,这个时候需要找到这个MemTable对于的Log文件,遍历这个文件内容,再Append一遍就行。
我们结合DbImpl的构造函数一起来看看:
1 | public DbImpl(Options options, File databaseDir) { |
- 读取CURRENT文件执行的Manifest文件,恢复出最后的LogNumber。
- 这里我不理解为什么Logs是个数组,讲道理每次MemTable都是串行Compact的话,那么Manifest中最后一个VersionEdit中的LogNumber,就是最新的MemTable的内容,不会出现多个文件的情况。
- 这里的recoverLogFile方法,就是顺序遍历,然后Put进去,但是这里和正常的Put流程不一样,虽然会生成新的SSTable,但是并不会调用VersionSet的logAndApply方法。
- 这里对Edit进行了LogAndApply,提交到了Manifest中,数据恢复成功
- 可以清理旧的文件了