以RocksDB為存儲引擎的MyRocks是近幾年流行起來的MySQL分支,在Facebook等公司廣泛使用,網易杭研的資料庫內核團隊從2018年初開始系統研究MyRocks,目前已在內部進行小範圍推廣使用,效果比較理想。

在調研、測試和試用期間,積累了一些經驗,包括技術實現等。本系列想跟大家分享我們組內小夥伴做的LevelDB調研輸出,這是第五篇,也是最後一篇,詳細介紹LevelDB MVCC機制、備份和恢復等場景。

第四篇鏈接:

溫正湖:LevelDB設計與實現 - Compaction?

zhuanlan.zhihu.com圖標

MVCC

Version相關的數據結構有3個,Version VersionEdit and VersionSet。其中VersionEdit顧名思義,是編輯或修改Version,它記錄的是兩個Version之間的差異。

sequence number 是一個由VersionSet直接持有的全局的編號,每次寫入(注意批量寫入時sequence number是相同的),就會遞增。根據我們之前對寫入操作的分析,當插入一條key的時候,實際參與排序的key和sequence number以及type組成的 InternalKey。

當我們進行Get操作時,我們只需要找到目標key,同時其sequence number 小於等於sequence number:

  • 普通的讀取,sepcific sequence number =last sequence number
  • snapshot讀取,sepcific sequenc number =snapshot sequence number

snapshot 其實就是一個sequence number,獲取snapshot,即獲取當前的last sequence number。

sstable級別的MVCC就是利用Version實現的。

  • 只有一個current version,持有最新的sstable集合。
  • VersionEdit 代表一次更新,新增了哪些sstable file,以及刪除了哪些sstable file
  • Version保存了各個level下每個sstable的FileMetaData

Version的結構如下圖所示:

Version數據成員如下:

VersionSet* vset_; // VersionSet to which this Version belongs
Version* next_; // Next version in linked list
Version* prev_; // Previous version in linked list
int refs_; // Number of live refs to this version
// List of files per level
std::vector<FileMetaData*> files_[config::kNumLevels];
// Next file to compact based on seek stats.
FileMetaData* file_to_compact_;
int file_to_compact_level_;
// Level that should be compacted next and its compaction score.
// Score < 1 means compaction is not strictly needed. These fields
// are initialized by Finalize().
double compaction_score_;
int compaction_level_;

next、prev分別指向前後版本,DB中所有的Version構成了一個環,此環中只有一個當前版本如圖13所示。file_保存了每層sstable的元數據FileMetaData,Version類還記錄了下一次參與Compaction的文件。

MANIFEST是跟版本變更有關的磁碟文件,MANIFEST文件的內容就是VersionEdit序列化後的內容,可用來恢復。MANIFEST中record存儲的方式跟log存儲方式一樣。VersionEdit類的主要成員如下:

std::string comparator_;
uint64_t log_number_;
uint64_t prev_log_number_;
uint64_t next_file_number_;
SequenceNumber last_sequence_;
bool has_comparator_;
bool has_log_number_;
bool has_prev_log_number_;
bool has_next_file_number_;
bool has_last_sequence_;

std::vector< std::pair<int, InternalKey> > compact_pointers_;
DeletedFileSet deleted_files_;
std::vector< std::pair<int, FileMetaData> > new_files_;

這些數據成員都會序列化後寫入MANIFEST文件。這些欄位不一定必然存在。

LevelDB在寫入每個欄位之前,都會先寫入一個varint型數字來標記後面的欄位類型。在讀取時,先讀取此欄位,根據類型解析後面的信息。一共有9種類型:

// Tag numbers for serialized VersionEdit.
enum Tag {
kComparator = 1,
kLogNumber = 2,
kNextFileNumber = 3,
kLastSequence = 4,
kCompactPointer = 5,
kDeletedFile = 6,
kNewFile = 7,
// 8 was used for large value refs
kPrevLogNumber = 9
};

MANIFEST的內容如下圖所示:

一次版本的變更信息保存在VersionEdit中,VersionEdit中的信息經過Encode後形成Record,一個Record有可能很大,MANIFEST存儲Record的方式與WAL日誌中存儲Record方式一樣,也分為:KFullType、KFirstType、KMiddleType、KLastType。隨著系統不斷的運行,發生版本變化的次數會越來越多,MANIFEST文件數也會變多,需要一個類似指針的東西指向當前使用的MANIFEST,CURRENT文件就充當這個指針的作用,它存儲了當前使用的MANIFEST的文件名。

Recovery and Repair

每次啟動資料庫都會調用一個Recover函數,其功能是當資料庫在運行中因為某些原因突然down掉後,恢復DB到宕機前的狀態,以及memtable甚至immutabl memtable中還未持久化到sstable中的數據。我們知道LevelDB採用的是WAL的方式存儲日誌,那麼對於LevelDB中的版本變化信息存儲在MANIFEST文件中,通過讀取MANIFEST中記錄的內容,然後在結合WAL中的信息就可以恢復到最新狀態;而memtable和immutable memtable中的數據恢復主要就是將未持久化到sstable的數據從log中讀取出來重做到memtable,在這個過程中也可能發生compaction操作,把immutable memtable持久化成sstable文件。

Recover的恢復是輕量級的恢復,如果MANIFEST文件損壞或者sstable損壞就需要Repair了,這個是當LevelDB不能正常啟動的時候需要手動進行的。整個Repair流程如圖16所示。

備份

LevelDB在調研的過程中沒有發現原生支持物理備份,RocksDB是加入了物理備份的,RocksDB備份的過程如下:

  1. Disable file deletions
  2. Get live files(包括:表文件、CURRENT文件、MANIFEST文件)
  3. Copy live files to the backup directory.如果在備份目錄已經存在了表文件,就不copy了,因為表文件是不可修改的而且文件名也是唯一的,CURRENT文件、MANIFEST文件還是需要都複製的。
  4. 如果flush_before_backup設置了false,還要copy日誌文件,GetSortedWalFiles()函數copy日誌文件到備份目錄。
  5. Re-enable file deletions。

RocksDB備份的實現在BackupEngine介面類和BackupEngineImpl實現類中,具體使用方法見以下鏈接:

github.com/facebook/roc

MyRocks提供了一個熱備的工具「myrocks_hotbackup」,原理和使用方法見以下鏈接:

github.com/facebook/mys


本系列結束。

參考資料:

1、RocksDB官方wiki;

2、網上公開的第三發資料,包括知乎及其他博客平台;

3、LevelDB、RocksDB源碼;


推薦閱讀:
相关文章