讀流程
當用戶調用Get方法從leveldb中讀取一個key的value時,leveldb按照memtable,imm,sst文件的順序,依次尋找key。其中,在sst文件中搜索是通過在當前version中Get來實現的,version是sst文件的一個snapshot,可以保證版本一致,如果沒有version機制,在get過程中,此key所在sst文件被合併到了其他文件中,則可能會get失敗。
關鍵代碼
Status DBImpl::Get(const ReadOptions& options,
const Slice& key,
std::string* value) {
Status s;
MutexLock l(&mutex_);
SequenceNumber snapshot;
if (options.snapshot != NULL) {
snapshot = reinterpret_cast<const SnapshotImpl*>(options.snapshot)->number_;
} else {
snapshot = versions_->LastSequence();
}
MemTable* mem = mem_;
MemTable* imm = imm_;
Version* current = versions_->current();
mem->Ref();
if (imm != NULL) imm->Ref();
current->Ref();
bool have_stat_update = false;
Version::GetStats stats;
// Unlock while reading from files and memtables
{
mutex_.Unlock();
// First look in the memtable, then in the immutable memtable (if any).
LookupKey lkey(key, snapshot);
//依次從mem,imm,current version中get對應的value
if (mem->Get(lkey, value, &s)) {
// Done
} else if (imm != NULL && imm->Get(lkey, value, &s)) {
// Done
} else {
s = current->Get(options, lkey, value, &stats);
have_stat_update = true;
}
mutex_.Lock();
}
if (have_stat_update && current->UpdateStats(stats)) {
MaybeScheduleCompaction();
}
mem->Unref();
if (imm != NULL) imm->Unref();
current->Unref();
return s;
}
需要注意的點
用戶通過user-key來查詢,在leveldb中首先會通過user-key和sequence num組成lookupkey,在三處查詢,均使用lookupkey
Get時會首先獲取鎖,在臨界區獲取mem,imm,current version,當前seq等信息,而從三處查詢時,不需要持有鎖,因爲對以上三處是隻讀
Get也可能會觸發compaction,每個get會導致對sst文件seek,leveldb會記錄對每個sst文件的seek次數,到達某個值之後,會將此文件compaction,其中的道理是,假設用戶查詢key是隨機的,那文件越大,其被seek的次數應該越多,leveldb假設16k對應一次查詢,比如文件大小爲160k,那麼它最多被seek10次,seek次數超過了10,就說明它的range太大了,應該被compaction(存疑?)
對sst文件的搜索,其實是按照level0,level1…的順序進行的,但是這些細節都被封裝在version->Get函數中,查看version->Get函數可以看到,首先根據key找到對應的file meta信息(version中保存了各個level的file meta信息),然後根據file meta信息(file number)到cache中找獲取對應的TableAndFile(如果沒有,則RandomAccess方式打開對應的文件),然後通過Table中的index block,filter block等信息來具體定位key在文件中的位置,期間,可能使用block cache。
MaybeSchedualCompaction函數會將一次compaction的判斷和compaction放到後臺任務隊列中,後臺會有一個bg線程消費此隊列
寫流程
大致上, 寫入一條k-v的流程爲
- 將k-v包裝成爲一個writer結構
- 放到一個writers 隊列中,等待,直到被喚醒(自己的任務被執行或者自己的任務在隊列頭)
- 如果線程發現自己的任務在隊列頭,則將隊列中目前所有的writer組合起來,先寫log,再寫入mem
- 如果線程發現自己的任務已經被執行,則返回
類比如下場景
一羣人要去一個窗口交表, 先排隊,如果發現自己不在隊首,就睡覺,如果自己在隊首,就把當前排隊的人羣中所有的表都收上來,代爲遞交,然後再挨個通知排隊的人他的表已經交上去了,可以回家了。
需要注意的是
- 系統中任意時刻, 只有一個線程在執行寫mem的操作,所以寫入mem不需要加鎖
- 每個線程都可能成爲執行寫入mem的線程
還沒有想通的問題
爲什麼這樣會提高效率?