LevelDB源碼解析11.文件序號

文章重要參考了: leveldb之文件 - CSDN博客

文件名

LevelDB在磁碟上是需要寫各種各樣的文件的。比如.log, .ldb, .sst, LOG, CURRENT, manifest等等。

.log: WAL LOG文件,主要是用來寫入用戶輸入的key:val.ldb: table cache文件.sst: 每一層的level文件LOG: level的LOG文件。用來輸出比如log.println("xxx");這種消息CURRENT: 裡面存放的是當前的manifest文件名。manifest:這個文件前面博文已經有所涉及了。就是指VersionEdit->Record的WAL LOG文件。

所有的這些文件都是需要遵首同一套編號系統的。如果文件名中帶數據。那麼文件名裡面的數字必然是沒有重複的。

生成文件名static std::string MakeFileName(const std::string& name, uint64_t number, const char* suffix) { char buf[100]; snprintf(buf, sizeof(buf), "/%06llu.%s",static_cast<unsigned long long>(number),suffix); return name + buf; }// 生成log文件示例std::string LogFileName(const std::string& name, uint64_t number) { return MakeFileName(name, number, "log"); }

序號

一個活躍的leveldb,實際上就是由versionset代表著。所以總體信息就是存放在versionset裡面。裡面關於序號的記錄信息如下:

log number(log編號)next file number(下一個文件編號)last sequence(單條write操作遞增該編號,可認為是版本號)prev log number(目前已棄用)

uint64_t next_file_number_; //用於生成系統下一個文件的編號 uint64_t manifest_file_number_;// 當前Manifest文件的編號,主要在Recover()時用到 uint64_t last_sequence_; // 寫入時會用到的seq.uint64_t log_number_; // 當前的log_number_uint64_t prev_log_number_; // 這個已經不用了,不看了。

可以看出來。文件名都是從next_file_number_得來。seq主要是給寫入的item的。所以這裡不用去看。比如寫入item時,其seq的變化。

Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) { /* ...... */ uint64_t last_sequence = versions_->LastSequence();//首先獲得已經寫入的記錄總數 Writer* last_writer = &w; if (status.ok() && my_batch != NULL) { WriteBatch* updates = BuildBatchGroup(&last_writer); WriteBatchInternal::SetSequence(updates, last_sequence + 1);//對WriteBatch的序號+1 last_sequence += WriteBatchInternal::Count(updates);//加上此次寫入的記錄數,即為此時已經寫入的記錄總數 /* ...... */ versions_->SetLastSequence(last_sequence);//將其保存在VersionSet中 } /* ...... */ }

NewDB時的序號

當新生成一個資料庫的時候,這個序號是怎麼操作的呢?

Status DBImpl::NewDB() { // 這裡在NewDB的時候也是通過VersionEdit來執行的。 VersionEdit new_db; new_db.SetComparatorName(user_comparator()->Name()); // next_file_number_ = 0是給了log文件。 new_db.SetLogNumber(0); // next_file_number_ = 2 new_db.SetNextFile(2); // 這裡為什麼從2開始? // 由於是新的DB,那麼一開始的seq就可以從0開始。 new_db.SetLastSequence(0); // 寫入item:key->val的時候,會有相應的seq. // Descriptor就是指manifest. // 這裡可以發現,1號file_number是給了manifest文件。 // 所以next_file_number_設置為2是有原因的。 // 這麼一大段代碼,就是為了生成MANIFEST-000001文件。 const std::string manifest = DescriptorFileName(dbname_, 1); WritableFile* file; // file指向manifest_file Status s = env_->NewWritableFile(manifest, &file); if (!s.ok()) { return s; } // 把version_edit寫入到manifest_file裡面。 { log::Writer log(file); std::string record; new_db.EncodeTo(&record); s = log.AddRecord(record); if (s.ok()) { s = file->Close(); } } delete file; // 這裡設置current文件 // 因為current文件裡面記錄的是manifest文件名。 // 所以這裡寫的就是MANIFEST-000002 if (s.ok()) { // Make "CURRENT" file that points to the new manifest file. s = SetCurrentFile(env_, dbname_, 1); } else { // 如果失敗了。那麼就把manifest文件刪除掉。 // 就算就是後面再次打開DB的時候。 // 就會發現current裡面是空的。也不會說讀到一個寫到一半的 // manifest record開始操作。 env_->DeleteFile(manifest); } return s;}

有趣的是,NewDB裡面把manifest文件佔用到編號1。實際上是沒有反應到VersionSet裡面的。

VersionSet::VersionSet(...) : next_file_number_(2), manifest_file_number_(0), // Filled by Recover() ..這裡並沒有寫1 last_sequence_(0), log_number_(0), current_(NULL) { AppendVersion(new Version(this));}

這裡並沒有寫1的原因是因為。在NewDB中把VersionEdit作為一個Record寫入到了manifest文件中。

NewDB()調用完了之後,還會去調用VersionSet->Recorvery函數去執行恢複函數。就走到了與打開DB一樣的步驟裡面去。

所以VersionSet裡面的manifest_file_number_需要在Recovery裡面進行設置。

Status VersionSet::Recover(bool *save_manifest) { // .. other code. // 如果CURRENT文件裡面寫的是MANIFEST-00001 std::string dscname = dbname_ + "/" + current; // 那麼dscname = "MANIFEST-00001" SequentialFile* manifest_file; // 這裡生成一個新的SequentialFile. // 這裡只是生成一個新的內存結構體,指向已經有的文件 // 並且是按照只讀的方式來打開。 s = env_->NewSequentialFile(dscname, &manifest_file); 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; // VersionBuilder // current_指向當前的version. Builder builder(this, current_); { LogReporter reporter; reporter.status = &s; // 也就是前面的manifestfile. // 如果是NewDB生成的。那麼這裡讀出來的內容 // 就是等於前面代碼中的VersionEdit *new_db. log::Reader reader(manifest_file, &reporter, true/*checksum*/, 0/*initial_offset*/); Slice record; std::string scratch; while (reader.ReadRecord(&record, &scratch) && s.ok()) { // 如果恢復的內容是NewDB函數生成的。那麼這裡只會有一個Record. VersionEdit edit; s = edit.DecodeFrom(record); if (s.ok()) { builder.Apply(&edit); } // 根據new_db可知,這個=1 if (edit.has_log_number_) { log_number = edit.log_number_; have_log_number = true; } // ... // 根據new_db可知,這個=2 if (edit.has_next_file_number_) { next_file = edit.next_file_number_; have_next_file = true; } // 根據new_db可知,這個=0 if (edit.has_last_sequence_) { last_sequence = edit.last_sequence_; have_last_sequence = true; } } } if (s.ok()) { // .. other code. MarkFileNumberUsed(prev_log_number); MarkFileNumberUsed(log_number); } if (s.ok()) { // .. other code. .. manifest_file_number_ = next_file; next_file_number_ = next_file + 1; last_sequence_ = last_sequence; log_number_ = log_number; } return s;}

可以看出來。當recovery生成一個新的leveldb服務進程的時候,就會用掉這個next_file。把這個文件編號用來做manifest。

也就是說,當NewDB函數被調用的時候,生成的文件MANIFEST-000001。當恢復的時候,就會立馬用編號2。

當後再次打開leveldb的時候,就直接用掉上次leveldb沒有用掉的那個最大的編號。這樣做的目的,應該是為了避免去打開原來的manifest文件。造成破壞寫。

推薦閱讀:

c++ 子類能否使用父類的類內定義類型?
如何delete數組?
C++解析xml有什麼好用的輪子?
在 C++ 學習過程中,哪些書籍值得一看?
如何在 C++ 代碼中提示編譯器某個分支的執行概率高?

TAG:LevelDB | C | 源碼閱讀 |