日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

leveldb:数据库recover机制

發布時間:2023/12/29 数据库 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 leveldb:数据库recover机制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

DBImpl::Recover

把數據庫恢復到上次退出的狀態,
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,則創建新的dbif (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<uint64_t> expected;// 這個函數實質是獲取仍然存活(仍然有效)的文件versions_->AddLiveFiles(&expected);uint64_t number;FileType type;std::vector<uint64_t> 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(); }

DBImpl::RecoverLogFile()

該函數打開指定的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 memtablestd::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數據錯誤,不滿足最小長度12reporter.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,則執行compactioncompactions++;*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; }

VersionSet::Recover

當正常運行期間,每當調用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_), &current);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 versionFinalize(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; }

總結

以上是生活随笔為你收集整理的leveldb:数据库recover机制的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。