把数据库恢复到上次退出的状态,
Recover的基本功能:如果存在表数据,则Load表数据,并对日志进行恢复,否则,根据flag创建新表或者返回错误
Recover的基本流程是:首先是处理创建flag,比如存在就返回失败等等;然后是尝试从已存在的sstable文件恢复db;最后如果发现有大于manifest文件记录的log编号的log文件,则需要回放log(回放的是上一次db关闭时还存在于mem和immem的记录,因为这些记录并没有持久化到磁盘sst文件中),更新db数据。回放期间db可能会dump新的level 0文件,因此需要把db元信息的变动记录到edit中返回
Status DBImpl::Recover(VersionEdit* edit, bool *save_manifest) {
mutex_.AssertHeld();
// 创建DB目录,不关注错误
env_->CreateDir(dbname_);
// 在DB目录下打开或创建(如果不存在)LOCK文件并锁定它,防止其他进程打开此表
Status s = env_->LockFile(LockFileName(dbname_), &db_lock_);
if (!s.ok()) {
return s;
}
//判断CURRENT文件是否存在,current不存在说明数据库不存在
if (!env_->FileExists(CurrentFileName(dbname_))) {
//若CURRENT文件不存在,如果options选项设置了create_if_missing,则创建新的db
if (options_.create_if_missing) {
s = NewDB();
if (!s.ok()) {
return s;
}
} else {
//否则返回db不存在
return Status::InvalidArgument(
dbname_, "does not exist (create_if_missing is false)");
}
} else {
if (options_.error_if_exists) {
//如果数据库存在且设置了error_if_exists,则返回error_if_exists错误
return Status::InvalidArgument(
dbname_, "exists (error_if_exists is true)");
}
}
// 如果运行到此,表明数据库已经存在,需要load,第一步是从MANIFEST文件中恢复VersionSet
s = versions_->Recover(save_manifest);
if (!s.ok()) {
return s;
}
SequenceNumber max_sequence(0);
/*尝试从所有比manifest文件中记录的log要新的log文件中恢复(前一个版本可能会添加新的
log文件,却没有记录在manifest中)。这种情况出现在memtable或者immemtable还没来得
及写入sst文件db就挂掉了,因此需要从比manifest文件中记录的log要新的log文件中恢复*/
//prev_log是早前版本level_db使用的机制,现在以及不再使用,这里只是为了兼容
const uint64_t min_log = versions_->LogNumber();
const uint64_t prev_log = versions_->PrevLogNumber();
std::vector<std::string> filenames;
// 列出目录内的所有文件
s = env_->GetChildren(dbname_, &filenames);
if (!s.ok()) {
return s;
}
std::set expected;
// 这个函数实质是获取仍然存活(仍然有效)的文件
versions_->AddLiveFiles(&expected);
uint64_t number;
FileType type;
std::vector logs;
//这里先找出所有满足条件的log文件:比manifest文件记录的log编号更新。
for (size_t i = 0; i < filenames.size(); i++) {
if (ParseFileName(filenames[i], &number, &type)) {
// 从这里删除的目的是为了最后看看还有哪些文件名是不能够解析的
expected.erase(number);
if (type == kLogFile && ((number >= min_log) || (number == prev_log)))
logs.push_back(number);
}
}
// 如果这个数组不为空,那么表示有的文件名解析不了,出错!
if (!expected.empty()) {
char buf[50];
snprintf(buf, sizeof(buf), "%d missing files; e.g.",
static_cast<int>(expected.size()));
return Status::Corruption(buf, TableFileName(dbname_, *(expected.begin())));
}
//找到log文件后,首先排序,保证按照生成顺序,依次回放log。回放LOG时,记录被插入到memtable,如果超过write buffer,则还会dump出level 0的sst文件,
std::sort(logs.begin(), logs.end());
for (size_t i = 0; i < logs.size(); i++) {
// 此方法会将日志种每条记录的sequence num与max_sequence进行比较,以记录下最大的sequence num。 并把DB元信息的变动(sstable文件的变动)追加到edit中返回。
s = RecoverLogFile(logs[i], (i == logs.size() - 1), save_manifest, edit,
&max_sequence);
if (!s.ok()) {
return s;
}
// 记录哪些文件编号已经被使用(可能回放的日志文件编号大于versions_的当前最大文件编号)
versions_->MarkFileNumberUsed(logs[i]);
}
// 更新最大的全局事务序列号,因为log文件对应的memtable还没生成sst,便不会被写入到MANIFEST中。
if (versions_->LastSequence() < max_sequence) {
versions_->SetLastSequence(max_sequence);
}
return Status::OK();
}
该函数打开指定的log文件,回放日志,用于恢复db。期间可能会执行compaction,生产新的level 0sstable文件,记录文件变动到edit中。
它声明了一个局部类LogReporter以打印错误日志
Status DBImpl::RecoverLogFile(uint64_t log_number, bool last_log,
bool* save_manifest, VersionEdit* edit,
SequenceNumber* max_sequence) {
mutex_.AssertHeld();
//打开指定的log文件
std::string fname = LogFileName(dbname_, log_number);
SequentialFile* file;
Status status = env_->NewSequentialFile(fname, &file);
if (!status.ok()) {
MaybeIgnoreError(&status);
return status;
}
// Create the log reader.
LogReporter reporter;
//根据log文件句柄file创建log::Reader,准备读取log。
log::Reader reader(file, &reporter, true/*checksum*/,
0/*initial_offset*/);
Log(options_.info_log, "Recovering log #%llu",
(unsigned long long) log_number);
// Read all the records and add to a memtable
std::string scratch;
Slice record;
WriteBatch batch;
int compactions = 0;
MemTable* mem = NULL;
//依次读取所有的log记录,并插入到新生成的memtable中。这里使用到了批量更新接口WriteBatch,具体后面再分析。
while (reader.ReadRecord(&record, &scratch) &&
status.ok())
{
if (record.size() < 12) {// log数据错误,不满足最小长度12
reporter.Corruption(
record.size(), Status::Corruption("log record too small"));
continue;
}
WriteBatchInternal::SetContents(&batch, record);// log内容设置到WriteBatch中,因为写日志时一条record就是一个WriteBatch的内容。
if (mem == NULL) {
mem = new MemTable(internal_comparator_);
mem->Ref();
}
status = WriteBatchInternal::InsertInto(&batch, mem);// 插入到memtable中
MaybeIgnoreError(&status);
if (!status.ok()) {
break;
}
//恢复出kv对记录中最大的事务序列号
const SequenceNumber last_seq =
WriteBatchInternal::Sequence(&batch) +
WriteBatchInternal::Count(&batch) - 1;
if (last_seq > *max_sequence) {
*max_sequence = last_seq;
}
if (mem->ApproximateMemoryUsage() > options_.write_buffer_size)
{
// 如果mem的内存超过设置值write_buffer_size,则执行compaction
compactions++;
*save_manifest = true;
//一旦生成sst便会记录在edit,save_manifest设为ture告诉调用这edit有新的数据
status = WriteLevel0Table(mem, edit, NULL);
mem->Unref();
mem = NULL;
if (!status.ok()) {
// Reflect errors immediately so that conditions like full
// file-systems cause the DB::Open() to fail.
break;
}
}
}
delete file;
// See if we should keep reusing the last log file.
//是否应该继续使用上次剩下的日志文件
if (status.ok() && options_.reuse_logs && last_log && compactions == 0) {
//如果option设置可以以及是回放的最后一个日志文件以及回放过程中没有生成新的sst文件才行
assert(logfile_ == NULL);
assert(log_ == NULL);
assert(mem_ == NULL);
uint64_t lfile_size;
if (env_->GetFileSize(fname, &lfile_size).ok() &&
env_->NewAppendableFile(fname, &logfile_).ok()) {
Log(options_.info_log, "Reusing old log %s \n", fname.c_str());
log_ = new log::Writer(logfile_, lfile_size);//继续使用该日志文件
logfile_number_ = log_number;
if (mem != NULL) {
mem_ = mem;//如果刚才回放生成了memtable,就让db继续使用
mem = NULL;
} else {
// mem can be NULL if lognum exists but was empty.
//mem为空说明刚才的日志文件只是存在但没有存放记录。依然创建一个新的mem_
mem_ = new MemTable(internal_comparator_);
mem_->Ref();
}
}
}
if (mem != NULL) {
//扫尾工作,走到这说明不继续使用旧日志文件,因此需要mem刷到新的sstable文件中
if (status.ok()) {
*save_manifest = true;
status = WriteLevel0Table(mem, edit, NULL);
}
mem->Unref();
}
return status;
}
当正常运行期间,每当调用LogAndApply的时候,都会将VersionEdit作为一笔记录,追加写入到MANIFEST文件。我们知道VersionEdit就是记录数据库的一个版本到另一个版本间的sst文件变化情况以及各层合并点变化情况。
注意,VersionEdit可以序列化,存进MANIFEST文件,同样道理,MANIFEST中可以将VersionEdit一个一个的重放出来。这个重放的目的,是为了得到当前的Version 以及VersionSet。
一般来讲,当打开的DB的时候,需要获得这种信息,而这种信息的获得,靠的就是所有VersionEdit 按照次序一一回放,生成当前的Version。
Status VersionSet::Recover(bool *save_manifest) {
// Read "CURRENT" file, which contains a pointer to the current manifest file
//读取"CURRENT"文件的内容到current,该文件内容包含了最新的Manifest文件名。
std::string current;
Status s = ReadFileToString(env_, CurrentFileName(dbname_), ¤t);
if (!s.ok()) {
return s;
}
if (current.empty() || current[current.size()-1] != '\n') {
return Status::Corruption("CURRENT file does not end with newline");
}
//去掉文件名最后的'\n'
current.resize(current.size() - 1);
//获取完整的最新Manifest文件名
std::string dscname = dbname_ + "/" + current;
SequentialFile* file;
//打开该Manifest文件
s = env_->NewSequentialFile(dscname, &file);
if (!s.ok()) {
return s;
}
bool have_log_number = false;
bool have_prev_log_number = false;
bool have_next_file = false;
bool have_last_sequence = false;
uint64_t next_file = 0;
uint64_t last_sequence = 0;
uint64_t log_number = 0;
uint64_t prev_log_number = 0;
Builder builder(this, current_);
{
LogReporter reporter;
reporter.status = &s;
log::Reader reader(file, &reporter, true/*checksum*/, 0/*initial_offset*/);
Slice record;
std::string scratch;
/*读取MANIFEST内容,MANIFEST是以log的方式写入的,因此这里调用的是log::Reader来读取。
然后调用VersionEdit::DecodeFrom,从内容解析出VersionEdit对象,并将VersionEdit记录的改动应用到versionset中。
读取MANIFEST中的log number, prev log number, nextfile number, last sequence。*/
while (reader.ReadRecord(&record, &scratch) && s.ok()) {
VersionEdit edit;
s = edit.DecodeFrom(record);
if (s.ok()) {
//edit记录的user_comparator名一定要和当前打开数据库传入的user_comparator匹配
//否则会出错,因为原来数据库生成的sst文件是按user_comparator排序的
if (edit.has_comparator_ &&
edit.comparator_ != icmp_.user_comparator()->Name()) {
s = Status::InvalidArgument(
edit.comparator_ + " does not match existing comparator ",
icmp_.user_comparator()->Name());
}
}
/*按照次序,讲Verison的变化量层层回放,最重会得到最终版本的Version*/
if (s.ok()) {
builder.Apply(&edit);
}
if (edit.has_log_number_) {
log_number = edit.log_number_;
have_log_number = true;
}
if (edit.has_prev_log_number_) {
prev_log_number = edit.prev_log_number_;
have_prev_log_number = true;
}
if (edit.has_next_file_number_) {
next_file = edit.next_file_number_;
have_next_file = true;
}
if (edit.has_last_sequence_) {
last_sequence = edit.last_sequence_;
have_last_sequence = true;
}
}
}
delete file;
file = NULL;
if (s.ok()) {
if (!have_next_file) {
s = Status::Corruption("no meta-nextfile entry in descriptor");
} else if (!have_log_number) {
s = Status::Corruption("no meta-lognumber entry in descriptor");
} else if (!have_last_sequence) {
s = Status::Corruption("no last-sequence-number entry in descriptor");
}
if (!have_prev_log_number) {
prev_log_number = 0;
}
//将读取到的log number, prev log number标记为已使用。
MarkFileNumberUsed(prev_log_number);
MarkFileNumberUsed(log_number);
}
if (s.ok()) {
Version* v = new Version(this);
/*通过回放所有的VersionEdit,得到最终版本的Version,存入v*/
builder.SaveTo(v);
// Install recovered version
Finalize(v);
AppendVersion(v);
manifest_file_number_ = next_file;
next_file_number_ = next_file + 1;
last_sequence_ = last_sequence;
log_number_ = log_number;
prev_log_number_ = prev_log_number;
// 我们是否需要创建新的MANIFEST 文件
/*随着时间的流逝,发生Compact的机会越来越多,Version跃升的次数越多,自然VersionEdit出现的
次数越来越多,而每一个VersionEdit都会记录到MANIFEST,这必然会造成MANIFEST文件不断变大。*/
if (ReuseManifest(dscname, current)) {
// No need to save new manifest
} else {
*save_manifest = true;
}
}
return s;
}