介绍 LevelDB 的 WAL 的实现,以及如何实现故障恢复。从流程概览中分离出来。
此外,还介绍了下对文件的封装。
目录:
- LevelDB 之 Memtable 实现
- LevelDB 之 SSTable 实现
- LevelDB 之 Version
- LevelDB 之 Compaction
- LevelDB 之 WAL
- LevelDB 之流程概览
WAL 格式
在介绍故障恢复之前,先介绍 LevelDB 的 WAL 格式。
如下所示,WAL 的写入以 Block 为单位。
1 | enum RecordType { |
AddRecord 会写入一条记录,对应一个 Slice。写入的过程是:
- 如果这个 Block 剩余的空间,不能放下一个 header 了,就新建一个 Block。
- 否则比较 Slice 的大小,和 Block 的剩余空间
- 如果能放下,就写到这个 Block 里面,类型为 kFullType。
- 否则,就要写到多个 Block 里面,根据写入部分在 Slice 中的位置,类型分别是 kFirstType、kLastType、kMiddleType。只有在 Slice 非常大的时候,才会有 kMiddleType。
具体的写入,是通过 EmitPhysicalRecord 来做的。它是调用 PosixWritableFile 的 Append 接口来写入的。PosixWritableFile 这个类实现了 WritableFile 这个接口,具体实现可以看下面的介绍。写入最后,会调用 Flush,也就是 ::write
,但是不会调用 Sync 去强制刷盘。这里的刷盘,实际上是实现在外部的,由 options.sync
控制。如果外部不刷盘,那么 WAL 就可能会丢,从而造成数据丢失。
1 | Status DBImpl::Write(const WriteOptions& options, WriteBatch* updates) { |
对文件的封装
WritableFile
1 | class LEVELDB_EXPORT WritableFile { |
PosixWritableFile
这个类中有个 kWritableFileBufferSize 的 buffer,默认大小是 65536。写入的时候,会先写到这个 buffer 中。如果 buffer 满了,就需要 FlushBuffer
并清空 buffer。FlushBuffer
实际上就是调用 write
方法。这里不需要用 pwrite,因为是顺序写。
1 | ... |
然后,如果剩余的内容能够填入到新的 buffer 中,就写进去。否则直接调用 WriteUnbuffered
直接写到文件里面。
此外,另有 SyncFd 方法,用来确保数据已经写到持久化介质里面,而不是在 os 的缓存中。
1 | ... |
相比 fdatasync,fsync 更重,因为它可能要写两次盘。一次是写数据,另一次是写元数据。而 fdatasync 只同步文件的数据部分,除非元数据的变化是必要的(例如文件大小变化),否则不会同步元数据。