【轉載】leveldb源碼分析—Recover和Repair
轉自 http://www.cnblogs.com/KevinT/p/3875572.html
leveldb作爲一個KV存儲引擎將數據持久化到磁盤,而對於一個存儲引擎來說在存儲過程中因爲一些其他原因導致程序down掉甚至數據文件被破壞等都會導致程序不能按正常流程再次啓動。那麼遇到這些狀況以後如何使程序最大程度的恢復數據就是非常重要的一項工作,leveldb也提供了這方面的工作。
首先來看recover,這是每一次啓動數據庫的時候都會唄調用到的流程。其功能是恢復數據庫在運行中突然因爲某些原因down掉而這個時候leveldb中的丟失的當前狀態,以及memtable甚至immtable中還未持久化到SSTable中的數據。我們知道leveldb採用的是WAL的方式進行的,那麼對於leveldb中的當前狀態存儲在manifest文件中,通過讀取已經持久化的狀態,然後在結合WAL總的信息就可以恢復到最新狀態;而memtable和immtable中的數據恢復的主要就是將未持久化到SSTable的數據從Log中讀取出來重做到memtable或者SSTable即可。整個恢復的流程如下:
Status DBImpl::Recover(VersionEdit* edit) { env_->CreateDir(dbname_); Status s = env_->LockFile(LockFileName(dbname_), &db_lock_); if (!env_->FileExists(CurrentFileName(dbname_))) { if (options_.create_if_missing) { s = NewDB(); //生成全新的manifest和current文件 return s; } } s = versions_->Recover(); // 恢復當前version信息 s = env_->GetChildren(dbname_, &filenames); // 獲取文件列表 versions_->AddLiveFiles(&expected); 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()) { //如果文件缺失 return Status::Corruption(buf); } std::sort(logs.begin(), logs.end()); //排序日誌文件 for (size_t i = 0; i < logs.size(); i++) { s = RecoverLogFile(logs[i], edit, &max_sequence); //重做日誌操作 versions_->MarkFileNumberUsed(logs[i]); } if (s.ok()) { if (versions_->LastSequence() < max_sequence) { versions_->SetLastSequence(max_sequence); } } }
下面我們首先來詳細看看從manifest文件中恢復version信息的流程
Status VersionSet::Recover() { // Read "CURRENT" file, which contains a pointer to the current manifest file Status s = ReadFileToString(env_, CurrentFileName(dbname_), ¤t); // 生成當前manifest的文件名 std::string dscname = dbname_ + "/" + current; SequentialFile* file; s = env_->NewSequentialFile(dscname, &file); if (!s.ok()) { return s; } // 各種初始化 { // 逐個讀取每個versionedit,並重建、記錄響應的log_num等 while (reader.ReadRecord(&record, &scratch) && s.ok()) { s = edit.DecodeFrom(record); if (s.ok()) { if (edit.has_comparator_ && edit.comparator_ != icmp_.user_comparator()->Name()) { } } 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()) { //恢復時獲取到的num,seq等的判斷和處理 MarkFileNumberUsed(prev_log_number); MarkFileNumberUsed(log_number); } if (s.ok()) { Version* v = new Version(this); builder.SaveTo(v); // 存儲一個全量的version // 計算Compaction相關的數據 Finalize(v); AppendVersion(v); // 將version加入version鏈表中,然後根據恢復得到的信息維護當前version的信息 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; } return s; }
恢復了當前version的基本信息以後進行日誌操作就可以恢復內存中的數據了,重做日誌操作即是將日誌中記錄的操作讀取出來,然後再將讀取到的操作重新寫入到leveldb中
Status DBImpl::RecoverLogFile(uint64_t log_number, VersionEdit* edit, SequenceNumber* max_sequence) { while (reader.ReadRecord(&record, &scratch) && status.ok()) { if (record.size() < 12) ; //size不對 WriteBatchInternal::SetContents(&batch, record); status = WriteBatchInternal::InsertInto(&batch, mem);//插入 if (last_seq > *max_sequence) { *max_sequence = last_seq; } if (mem->ApproximateMemoryUsage() > options_.write_buffer_size) { status = WriteLevel0Table(mem, edit, NULL); //寫成SSTable } } if (status.ok() && mem != NULL) { status = WriteLevel0Table(mem, edit, NULL); //寫成SSTable } return status; }
recover是leveldb中較爲輕量級的恢復,主要是恢復leveldb未持久化到磁盤的數據和狀態,每次啓動時都會被調用到。那麼如果我們的數據庫受到了更大程度的傷害呢?比如SSTable文件損壞甚至丟失,manifest文件丟失。那麼這個時候就必須使用下面的repair了,這個是當leveldb不能正常啓動的時候需要手動進行的。
repair主要包含了如下幾個流程來完成數據的修復:
FindFiles(); // 遍歷數據目錄下的文件解析文件名至manifest,日誌,SSTable ConvertLogFilesToTables(); // 解析日誌文件生成爲SSTble,主要用到了ConvertLogToTable, ExtractMetaData(); /* 逐個遍歷解析掃描到的SSTable並重新manifest的filemeta信息以供後面使用,
如果解析過程中有不能解析的數據,丟棄不能解析的數據並生成新的SSTable */ WriteDescriptor(); // 重置manifest文件號爲1並生成最新的manifest,將其記錄到current文件
ConvertLogFilesToTables主要用了一個和RecoverLogFile函數類似的ConvertLogToTable函數,他們的主要區別在RecoverLogFile在轉化日誌操作的時候使用的是leveldb的全局環境memtable和immtable,所以recover的數據有可能會持久化爲SSTable,而有一部分則會留在內存中的memtable中;而ConvertLogToTable則是自己新建了一個局部的memtable來進行操作,然後將數據持久化爲SSTable,這個過程從log恢復的數據一定會持久化爲SSTable。
然後我們着重看下ExtractMetaData函數對掃描到的SSTable文件的逐個進行ScanTable的流程:
void ScanTable(uint64_t number) { std::string fname = TableFileName(dbname_, number); //生成ldb文件名 Status status = env_->GetFileSize(fname, &t.meta.file_size); //獲取文件大小,否則嘗試sst文件後綴名 if (!status.ok()) { // 略,和上面類似 } if (!status.ok()) { // 如果都不能獲取到文件大小,這個文件恢復失敗,將其放入lost下 ArchiveFile(TableFileName(dbname_, number)); ArchiveFile(SSTTableFileName(dbname_, number)); return; } // 遍歷文件獲得metadata信息. Iterator* iter = NewTableIterator(t.meta); for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { Slice key = iter->key(); if (!ParseInternalKey(key, &parsed)) { continue; } counter++; if (empty) { // 第一個key記錄問文件最小key empty = false; t.meta.smallest.DecodeFrom(key); } //否則替換上一次設置的最大key,這樣遍歷到最後一個時就保留的是最後的一個 t.meta.largest.DecodeFrom(key); if (parsed.sequence > t.max_sequence) { t.max_sequence = parsed.sequence; } } // 如果遍歷過程中解析key無失敗,直接存儲metadata,否則遍歷SSTable生成新的SSTable,並刪除舊的 if (status.ok()) { tables_.push_back(t); } else { RepairTable(fname, t); // RepairTable archives input file. } }
如果數據有損壞,那麼就必須重建SSTable,其過程如下:
void RepairTable(const std::string& src, TableInfo t) { // 遍歷SSTable生成新的SSTable,並刪除舊的 // 生成新文件 std::string copy = TableFileName(dbname_, next_file_number_++); WritableFile* file; Status s = env_->NewWritableFile(copy, &file); if (!s.ok()) { return; } TableBuilder* builder = new TableBuilder(options_, file); // Copy data. Iterator* iter = NewTableIterator(t.meta); int counter = 0; for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { builder->Add(iter->key(), iter->value()); counter++; } delete iter; ArchiveFile(src); //將舊文件移到lost下 if (counter == 0) { builder->Abandon(); // Nothing to save } else { s = builder->Finish(); if (s.ok()) { t.meta.file_size = builder->FileSize(); } } delete builder; builder = NULL; if (s.ok()) { s = file->Close(); } delete file; file = NULL; if (counter > 0 && s.ok()) { std::string orig = TableFileName(dbname_, t.meta.number); s = env_->RenameFile(copy, orig); if (s.ok()) { tables_.push_back(t); } } if (!s.ok()) { env_->DeleteFile(copy); } }
在recover和repair的過程中我們發現了version的維護和管理十分重要,原本不打算再對這部分進行分析,但是現在看來對其進行一下細緻的梳理還是很有必要的,預計下一篇專門介紹這部分內容。