前面已經(jīng)寫了幾篇文章介紹一些和 LevelDB 相關(guān)的內(nèi)容:
這篇文章凿试,介紹一下 LevelDB 的寫操作排宰。
寫操作接口
LevelDB 提供的寫操作接口有:
其中,Put 和 Delete 的實現(xiàn)都是通過封裝 Write 來實現(xiàn)的详炬,函數(shù)調(diào)用關(guān)系如下:
- leveldb::DBImpl::Put => leveldb::DB::Put => leveldb::DBImpl::Write
- leveldb::DBImpl::Delete => leveldb::DB::Delete => leveldb::DBImpl::Write
Write
接口
Write 的函數(shù)原型如下:
virtual Status Write(const WriteOptions& options, WriteBatch* updates) = 0;
leveldb::WriteOptions 是控制寫操作的參數(shù)盐类,目前只有一個成員變量 sync
表示是否每次寫完都要將日志 flush 到磁盤。
leveldb::WriteBatch 表示多個 Key-Value 數(shù)據(jù)的更新操作(Put呛谜、Delete)在跳。
具體實現(xiàn)是 leveldb::DBImpl::Write 。
實現(xiàn)
- LevelDB 通過傳入的參數(shù)構(gòu)造一個 Writer —— leveldb::DBImpl::Writer 對象來執(zhí)行本次批量寫操作:
struct DBImpl::Writer {
Status status; // 執(zhí)行結(jié)果
WriteBatch* batch; // 多個更新操作
bool sync; // 是否 flush 到磁盤隐岛,WriteOptions.sync
bool done; // 是否已經(jīng)執(zhí)行
port::CondVar cv; // 并發(fā)控制的條件變量
explicit Writer(port::Mutex* mu) : cv(mu) { }
};
獲取互斥鎖猫妙,將自己放入寫隊列后等待別人幫忙完成寫入或者成為隊首獲得執(zhí)行寫入的權(quán)限。這里涉及 LevelDB 寫操作的一個性能優(yōu)化:執(zhí)行寫入操作的線程聚凹,會根據(jù)一定的規(guī)則將隊列中的多個請求合并成一個請求割坠,然后執(zhí)行批量寫入齐帚,并更新各個 Writer 的狀態(tài)。
如果別人幫忙完成寫入了彼哼,直接返回結(jié)果对妄。下面開始執(zhí)行寫入數(shù)據(jù)。
-
調(diào)用 MakeRoomForWrite 在一個循環(huán)里面按照下面的流程進(jìn)行檢查敢朱,直到MemTable 的大小沒有達(dá)到閾值或者出錯剪菱。(MakeRoomForWrite 提供一個 force 參數(shù)表示是否強(qiáng)制切換新 MemTable,并觸發(fā) Compaction拴签。正常寫流程 force 為 false肥哎。)
MakeRoomForWrite.png -
調(diào)用 BuildBatchGroup 將從隊首開始的連續(xù)多個符合條件的 Writer 的寫請求合并到 tmp_batch_睛蛛。合并時主要考慮:
- 合并寫入的數(shù)據(jù)大小棒掠,默認(rèn) max_size 是 1MB (1 << 20)翰舌。如果第一個寫請求的 size 比較心鹫ā(小于128 KB, 128 << 10)状原,則 max_size 為 size + 128 KB菩收。這樣做是為了避免數(shù)據(jù)小的請求被其它請求給拖慢拨齐。
- 如果第一個寫請求 sync == false驰贷,那么就不要加入 sync == true 的寫請求盛嘿。
釋放互斥鎖括袒。這里代碼保證同一時刻只有一個線程會執(zhí)行寫入操作次兆。
寫日志(WAL) 。
根據(jù)參數(shù)決定是否 sync 日志锹锰。
更新 MemTable 芥炭。
獲取互斥鎖 。
如果 sync 失敗恃慧,設(shè)置 bg_error_园蝠,后續(xù)所有寫入都將失敗。
清空臨時合并的批量操作 痢士。
更新 LastSequence 彪薛。
通知寫隊列剩余的線程 怠蹂。
以上善延,便是 LevelDB 的寫入流程。
小結(jié)
本篇文章結(jié)合代碼簡單介紹了 LevelDB 寫操作的流程城侧,其中易遣,寫入隊列 + 合并寫操作 是 LevelDB 寫操作的一個設(shè)計亮點 —— 至少我個人覺得這個設(shè)計簡單又實用,對寫入性能的提升也應(yīng)該是立竿見影的赞庶。
當(dāng)然训挡,LevelDB 的寫操作也存在一些可以改進(jìn)的地方澳骤,比如整個寫入過程——包括寫日志和寫 MemTable,都是單線程的澜薄。