10 Version分析之1
Version不會修改其管理的sstable文件,只有讀取操作。
10.1 Version接口
先來看看Version類的接口函數,接下來再一一分析。
// 追加一系列iterator到 @*iters中,將在merge到一起時生成該Version的內容
// 要求: Version已經保存了(見VersionSet::SaveTo)
void AddIterators(constReadOptions&, std::vector<Iterator*>* iters);
// 給定@key查找value,如果找到保存在@*val並返回OK。
// 否則返回non-OK,設置@ *stats.
// 要求:沒有hold lock
struct GetStats {
FileMetaData* seek_file;
int seek_file_level;
};
Status Get(constReadOptions&, const LookupKey& key, std::string* val,GetStats* stats);
// 把@stats加入到當前狀態中,如果需要觸發新的compaction返回true
// 要求:hold lock
bool UpdateStats(constGetStats& stats);
void GetOverlappingInputs(intlevel,
const InternalKey*begin, // NULL 指在所有key之前
const InternalKey* end, // NULL指在所有key之後
std::vector<FileMetaData*>* inputs);
// 如果指定level中的某些文件和[*smallest_user_key,*largest_user_key]有重合就返回true。
// @smallest_user_key==NULL表示比DB中所有key都小的key.
// @largest_user_key==NULL表示比DB中所有key都大的key.
bool OverlapInLevel(int level,const Slice*smallest_user_key, const Slice* largest_user_key);
// 返回我們應該在哪個level上放置新的memtable compaction,
// 該compaction覆蓋了範圍[smallest_user_key,largest_user_key].
int PickLevelForMemTableOutput(const Slice& smallest_user_key,
const Slice& largest_user_key);
int NumFiles(int level) const {return files_[level].size(); } // 指定level的sstable個數
10.2 Version::AddIterators()
DBImpl::NewIterator()->DBImpl::NewInternalIterator()->Version::AddIterators()。
函數功能是爲該Version中的所有sstable都創建一個Two Level Iterator,以遍歷sstable的內容。
對於level=0級別的sstable文件,直接通過TableCache::NewIterator()接口創建,這會直接載入sstable文件到內存cache中。
對於level>0級別的sstable文件,通過函數NewTwoLevelIterator()創建一個TwoLevelIterator,這就使用了lazy open的機制。
下面來分析函數代碼:
S1 對於level=0級別的sstable文件,直接裝入cache,level0的sstable文件可能有重合,需要merge。
for (size_t i = 0; i <files_[0].size(); i++) {
iters->push_back(vset_->table_cache_->NewIterator(// versionset::table_cache_
options,files_[0][i]->number, files_[0][i]->file_size));
}
S2 對於level>0級別的sstable文件,lazy open機制,它們不會有重疊。
for (int ll = 1; ll <config::kNumLevels; ll++) {
if(!files_[ll].empty()) iters->push_back(NewConcatenatingIterator(options,level));
}
函數NewConcatenatingIterator()直接返回一個TwoLevelIterator對象:return NewTwoLevelIterator(new LevelFileNumIterator(vset_->icmp_,&files_[level]),
&GetFileIterator,vset_->table_cache_, options);
其第一級iterator是一個LevelFileNumIterator,第二級的迭代函數是GetFileIterator,下面就來分別分析之。
GetFileIterator是一個靜態函數,很簡單,直接返回TableCache::NewIterator()。函數聲明爲:
static Iterator* GetFileIterator(void* arg,const ReadOptions& options, constSlice& file_value)
TableCache* cache =reinterpret_cast<TableCache*>(arg);
if (file_value.size() != 16) { // 錯誤
returnNewErrorIterator(Status::Corruption("xxx"));
} else {
returncache->NewIterator(options,
DecodeFixed64(file_value.data()), // filenumber
DecodeFixed64(file_value.data() + 8)); // filesize
}
這裏的file_value是取自於LevelFileNumIterator的value,它的value()函數把file number和size以Fixed 8byte的方式壓縮成一個Slice對象並返回。10.3 Version::LevelFileNumIterator類
它的構造函數接受兩個參數:InternalKeyComparator&,用於key的比較;vector<FileMetaData*>*,指向version的所有sstable文件列表。
LevelFileNumIterator(const InternalKeyComparator& icmp,
const std::vector<FileMetaData*>* flist)
: icmp_(icmp), flist_(flist),index_(flist->size()) {} // Marks as invalid
來看看其接口實現,不限羅嗦,全部都列出來。
Valid函數、SeekToxx和Next/Prev函數都很簡單,畢竟容器是一個vector。Seek函數調用了FindFile,這個函數後面會分析。
virtual void Seek(constSlice& target) { index_ = FindFile(icmp_, *flist_, target);}
virtual void SeekToFirst() {index_ = 0; }
virtual void SeekToLast() {index_ = flist_->empty() ? 0 : flist_->size() - 1;}
virtual void Next() {
assert(Valid());
index_++;
}
virtual void Prev() {
assert(Valid());
if (index_ == 0) index_ =flist_->size(); // Marks as invalid
else index_--;
}
Slice key() const {
assert(Valid());
return(*flist_)[index_]->largest.Encode(); // 返回當前sstable包含的largest key
}
Slice value() const { // 根據|number|size|的格式Fixed int壓縮
assert(Valid());
EncodeFixed64(value_buf_,(*flist_)[index_]->number);
EncodeFixed64(value_buf_+8,(*flist_)[index_]->file_size);
return Slice(value_buf_,sizeof(value_buf_));
}
來看FindFile,這其實是一個二分查找函數,因爲傳入的sstable文件列表是有序的,因此可以使用二分查找算法。就不再列出代碼了。
10.4 Version::Get()
Status Version::Get(const ReadOptions& options, constLookupKey& k, std::string* value, GetStats* stats)
如果本次Get不止seek了一個文件(僅會發生在level 0的情況),就將搜索的第一個文件保存在stats中。如果stat有數據返回,表明本次讀取在搜索到包含key的sstable文件之前,還做了其它無謂的搜索。這個結果將用在UpdateStats()中。
這個函數邏輯還是有些複雜的,來看看代碼。
S1 首先,取得必要的信息,初始化幾個臨時變量
Slice ikey = k.internal_key();
Slice user_key = k.user_key();
const Comparator* ucmp =vset_->icmp_.user_comparator();
Status s;
stats->seek_file = NULL;
stats->seek_file_level = -1;
FileMetaData* last_file_read =NULL; // 在找到>1個文件時,讀取時記錄上一個
int last_file_read_level = -1; // 這僅發生在level 0的情況下
std::vector<FileMetaData*>tmp;
FileMetaData* tmp2;
S2 從0開始遍歷所有的level,依次查找。因爲entry不會跨越level,因此如果在某個level中找到了entry,那麼就無需在後面的level中查找了。
for (int level = 0; level <config::kNumLevels; level++) {
size_t num_files = files_[level].size();
if (num_files == 0) continue; // 本層沒有文件,則直接跳過
// 取得level下的所有sstable文件列表,搜索本層
FileMetaData* const* files = &files_[level][0];
後面的所有邏輯都在for循環體中。S3 遍歷level下的sstable文件列表,搜索,注意對於level=0和>0的sstable文件的處理,由於level 0文件之間的key可能有重疊,因此處理邏輯有別於>0的level。
S3.1 對於level 0,文件可能有重疊,找到所有和user_key有重疊的文件,然後根據時間順序從最新的文件依次處理。
tmp.reserve(num_files);
for (uint32_t i = 0; i <num_files; i++) { // 遍歷level 0下的所有sstable文件
FileMetaData* f =files[i];
if(ucmp->Compare(user_key, f->smallest.user_key()) >= 0 &&
ucmp->Compare(user_key, f->largest.user_key()) <= 0)
tmp.push_back(f); // sstable文件有user_key有重疊
}
if (tmp.empty()) continue;
std::sort(tmp.begin(),tmp.end(), NewestFirst); // 排序
files = &tmp[0]; num_files= tmp.size();// 指向tmp指針和大小
S3.2 對於level>0,leveldb保證sstable文件之間不會有重疊,所以處理邏輯有別於level 0,直接根據ikey定位到sstable文件即可。
//二分查找,找到第一個largest key >=ikey的file index
uint32_t index =FindFile(vset_->icmp_, files_[level], ikey);
if (index >= num_files) { // 未找到,文件不存在
files = NULL; num_files = 0;
} else {
tmp2 = files[index];
if(ucmp->Compare(user_key, tmp2->smallest.user_key()) < 0) {
// 找到的文件其所有key都大於user_key,等於文件不存在
files = NULL; num_files = 0;
} else {
files = &tmp2; num_files = 1;
}
}
S4 遍歷找到的文件,存在files中,其個數爲num_files。for (uint32_t i = 0; i <num_files; ++i) {
後面的邏輯都在這一層循環中,只要在某個文件中找到了k/v對,就跳出for循環。
S4.1 如果本次讀取不止搜索了一個文件,記錄之,這僅會發生在level 0的情況下。
if(last_file_read != NULL && stats->seek_file == NULL) {
// 本次讀取不止seek了一個文件,記錄第一個
stats->seek_file =last_file_read;
stats->seek_file_level= last_file_read_level;
}
FileMetaData* f = files[i];
last_file_read = f; // 記錄本次讀取的level和file
last_file_read_level =level;
S4.2 調用TableCache::Get()嘗試獲取{ikey, value},如果返回OK則進入S4.3,否則直接返回,傳遞的回調函數是SaveValue()。
Saver saver; // 初始化saver
saver.state = kNotFound;
saver.ucmp = ucmp;
saver.user_key = user_key;
saver.value = value;
s = vset_->table_cache_->Get(options,f->number, f->file_size,
ikey, &saver, SaveValue);
if (!s.ok()) return s;
S4.3 根據saver的狀態判斷,如果是Not Found則向下搜索下一個更早的sstable文件,其它值則返回。
switch (saver.state) {
case kNotFound: break; // 繼續搜索下一個更早的sstable文件
case kFound: return s; // 找到
case kDeleted: // 已刪除
s =Status::NotFound(Slice()); // 爲了效率,使用空的錯誤字符串
return s;
case kCorrupt: // 數據損壞
s =Status::Corruption("corrupted key for ", user_key);
return s;
}
以上就是Version::Get()的代碼邏輯,如果level 0的sstable文件太多的話,會影響讀取速度,這也是爲什麼進行compaction的原因。另外,還有一個傳遞給TableCache::Get()的saver函數,下面就來簡單分析下。這是一個靜態函數:static void SaveValue(void* arg,const Slice& ikey, const Slice& v)。它內部使用了結構體Saver:
struct Saver {
SaverState state;
const Comparator* ucmp; // user key比較器
Slice user_key;
std::string* value;
};
函數SaveValue的邏輯很簡單,首先解析Table傳入的InternalKey,然後根據指定的Comparator判斷user key是否是要查找的user key。如果是並且type是kTypeValue,則設置到Saver::*value中,並返回kFound,否則返回kDeleted。代碼如下:
Saver* s =reinterpret_cast<Saver*>(arg);
ParsedInternalKey parsed_key; // 解析ikey到ParsedInternalKey
if (!ParseInternalKey(ikey,&parsed_key)) s->state = kCorrupt; // 解析失敗
else {
if(s->ucmp->Compare(parsed_key.user_key, s->user_key) == 0) { // 比較user key
s->state =(parsed_key.type == kTypeValue) ? kFound : kDeleted;
if (s->state == kFound)s->value->assign(v.data(), v.size()); // 找到,保存結果
}
}
下面要分析的幾個函數,或多或少都和compaction相關。