文章摘要
- LevelDB的日志管理系統是怎么通過雙鏈表來進行數據管理
- 為什么LevelDB能夠在不鎖表的情況下進行日志新增
適用人群:
- 對版本管理機制有開發訴求,并且希望參考LevelDB的版本開發機制。
- 數據庫相關從業者的專業人士。
- 計算機狂熱愛好者,對計算機的存儲機制有強烈技術追求的同志。
閱讀建議:
- 作者本人功底有限不太可能考慮到所有讀者的閱讀細節,建議讀者先通盤閱讀下本文,先熟悉本文中會出現哪些關鍵概念和關鍵流程,并配合上AI工具對文章中個別流程進行細致理解。
LevelDB版本管理機制
核心抽象
主要分為
- 版本管理層:版本抽象相關的操作和邏輯。
- 文件管理層: 主要和文件磁盤上的物化數據打交道。
- 快照管理層:依托快照對外暴露固化查詢的服務。
抽象 | 職能 |
---|---|
Version | 版本管理的最小單位,維護特定時刻的數據庫狀態,管理文件集合 |
VersionSet | 版本集合管理器,負責管理所有版本,維護當前版本,處理版本切換 |
VersionEdit | 版本變更記錄,記錄版本間的差異,支持變更的序列化和反序列化 |
Builder | 版本構建器,負責構建新版本,應用版本變更 |
Compaction | 壓縮任務管理,處理文件壓縮,生成新的版本變更 |
FileMetaData | 文件元數據,記錄文件的基本信息(大小、范圍等) |
Manifest | 清單文件管理,持久化版本信息,支持數據庫恢復 |
Snapshot | 數據庫某一時刻的快照,提供一致性讀取視圖,基于序列號實現 |
SnapshotList | 快照列表管理,維護所有活躍的快照,管理快照的生命周期 |
這里面的重點是:
- Version是整個版本管理機制的最小單元抽象
- compaction 的機制非常復雜,本文不贅述,感興趣移步: [LevelDB]揭秘LevelDB暗藏的合并秘技,Compaction內部的超神操作讓工程師都驚呆了!
版本管理層(Version)核心邏輯
- 客戶端向VersionSet抽象請求版本變更操作
- VersionSet 通過創建VersionEdit來記錄一些當前操作的變更
- 使用Build模式來創建新版本并更新當前的版本
亮點設計:
- 使用新建Version的方式來實現無鎖讀取,簡化并發控制。
- 使用Builder(構建者模式)來進行構建,對代碼進行解耦。
- 使用VersionEdit進行增量更新,并且能夠通過VersionEdit來進行日志記錄。
源代碼細節說明
客戶端(通常理解是應用LevelDB的機器)調用LevelDB的LogAndApply接口來進行版本新增
// 調用入口
s = versions_->LogAndApply(&edit, &mutex_);
簡單來說,就是生成一個新的版本Version,并添加到當前的版本管理鏈表中。
LogAndApply方法主要有以下的核心階段(流程圖見下文)
- 初始化一些必要參數,如log_number_(日志編號: 用于后續清理WAL無用日志), file_number(文件編號-用于實現文件的唯一性),Sequence(用于實現序列號唯一性)
源碼參考:
// 日志版本號-用于實現WAL 預寫入機制-用于清理當時用不了的文件if (edit->has_log_number_) {assert(edit->log_number_ >= log_number_);assert(edit->log_number_ < next_file_number_);} else {edit->SetLogNumber(log_number_);}if (!edit->has_prev_log_number_) {edit->SetPrevLogNumber(prev_log_number_);}// file_number 文件編號-用于實現文件的唯一性 // sequence: 用于控制序列唯一性edit->SetNextFile(next_file_number_);edit->SetLastSequence(last_sequence_);
- 構建新版本(包括對版本的壓縮打分)
- 創建新版本 -亮點: 使用Builder模式來構建新版本
- Apply 方法 用于根據edit中的信息來生成對應的build構造器
- SaveTo 方法 用于將build構造器中的信息應用到新的version中
Version* v = new Version(this);{Builder builder(this, current_);builder.Apply(edit);builder.SaveTo(v);}// 計算 最佳壓縮層級Finalize(v);
- MANIFEST處理階段, 這里的MANIFEST
// MANIFEST 文件處理std::string new_manifest_file;Status s;if (descriptor_log_ == nullptr) {// 日志assert(descriptor_file_ == nullptr);// 底層文件操作new_manifest_file = DescriptorFileName(dbname_, manifest_file_number_);s = env_->NewWritableFile(new_manifest_file, &descriptor_file_);if (s.ok()) {descriptor_log_ = new log::Writer(descriptor_file_);// 寫入快照-本質上是向 Manifest 日志中寫入當前的文件狀態,防止記錄丟失s = WriteSnapshot(descriptor_log_);}}
- 文件同步階段
{mu->Unlock();if (s.ok()) {std::string record;edit->EncodeTo(&record);s = descriptor_log_->AddRecord(record);if (s.ok()) {s = descriptor_file_->Sync();}if (!s.ok()) {Log(options_->info_log, "MANIFEST write: %s\n", s.ToString().c_str());}}if (s.ok() && !new_manifest_file.empty()) {s = SetCurrentFile(env_, dbname_, manifest_file_number_);}// 重新獲取鎖mu->Lock();}
- 完成安裝階段
// 讓基于VersionEdit和老版本if (s.ok()) {// 將新版本添加到版本鏈表AppendVersion(v);// 更新日志文件號log_number_ = edit->log_number_;prev_log_number_ = edit->prev_log_number_;} else {// 快照寫入失敗delete v;if (!new_manifest_file.empty()) {// 清理新創建的 MANIFEST 文件相關資源delete descriptor_log_;delete descriptor_file_;descriptor_log_ = nullptr;descriptor_file_ = nullptr;env_->RemoveFile(new_manifest_file);}}return s;
}
猜你喜歡
C++多線程: https://blog.csdn.net/luog_aiyu/article/details/145548529
一文了解LevelDB數據庫讀取流程:https://blog.csdn.net/luog_aiyu/article/details/145946636
一文了解LevelDB數據庫寫入流程:https://blog.csdn.net/luog_aiyu/article/details/145917173
關于LevelDB存儲架構到底怎么設計的:https://blog.csdn.net/luog_aiyu/article/details/145965328?spm=1001.2014.3001.5502
PS
你的贊是我很大的鼓勵
我是darkchink,一個計算機相關從業者&一個摩托佬&AI狂熱愛好者
本職工作是某互聯網公司數據相關工作,歡迎來聊,內推或者交換信息
vx 二維碼見: https://www.cnblogs.com/DarkChink/p/18598402