前言
日志就是记录数据库增删记录的文件。之所以需要记录这些东西,主要是为了防止万一数据库运行期间异常崩溃导致的数据丢失。而之所以会出现数据丢失,原因在于我们在往数据库中写数据时,并不是真的将数据库写入到了磁盘中,而可能只是将数据暂存到内存中而已。如果在数据flush到磁盘之前系统崩溃(数据库bug,操作系统bug,机器故障等等),那缓存在内存中的数据就会丢失,而用户以为这些数据已经成功写入数据库了(这也是插入数据时数据库告知用户已经成功插入了)。下次重启时,用户会因为在数据库中找不到自己插入的数据而迷惑不解。
日志系统的作用就是保证插入到缓存中的数据也不会丢失。
leveldb日志系统组成
leveldb的日志文件主要分为三种:
- 记录key-value的日志文件
- 记录版本信息变化(VersionEdit)的日志文件(manifest文件)
- CURRENT文件,它包含一个指向上面类型2日志文件的指针
类型1的日志是日志恢复的核心,因为只有把所有写到数据库中的key-value都用日志记录下来,后续才有可能进行故障恢复。类型2是为了在每次数据库启动时都可以还原历史版本信息。类型3的日志则只包含一行记录,这行记录就是manifest日志的文件名。
下面我们从代码层面跟踪日志系统的操作流程
日志系统的操作流程 —— key-value日志文件
用户通过DBImpl::Write函数向数据库中写入数据,根据之前的分析,这里的写入并不是真的写入磁盘,而是写入内存的memtable中。在DBImpl::Write函数中,我们可以看到在将数据写入memtable之前,leveldb先将数据写入log_中:
status = log_->AddRecord(WriteBatchInternal::Contents(updates)); // 写日志
而且如果我们深入到AddRecord函数中就会发现,它在将每个数据写入时都会flush,这样保证写入日志中的数据都实际地写入到了磁盘中。这里可能会觉得写日志会不会效率太低,其实不然,因为写日志是顺序写,和随机写相比,效率要高得多。
问题在于,这里的log_会记录历史上所有加入到数据库中的key-value吗?如果这样的话,日志文件实在就太大了。下面我们看到,其实每个memtable都会生成一个日志文件,而log_日志文件总是记录的是当前memtable中的key-value,也就是说当这个memtable转变成imm_等待被写入磁盘时,就会另生成一个log_,这个新的log_指向新的mem_。这个我们可以从DBImpl::MakeRoomForWrite函数中找到端倪:
logfile_number_ = new_log_number;
log_ = new log::Writer(lfile);
imm_ = mem_; //后台将启动对imm_ 进行写磁盘的过程
has_imm_