日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[leveldb] 3.put/delete操作

發(fā)布時(shí)間:2024/1/17 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [leveldb] 3.put/delete操作 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

0.導(dǎo)讀

LevelPut的流程:

Put操作首先將操作記錄寫入log文件,然后寫入memtable,返回寫成功。整體來看是這樣,但是會(huì)引發(fā)下面的問題:

1. 寫log的時(shí)候是實(shí)時(shí)刷到磁盤的嗎? 2. 寫入的時(shí)候memtable過大了咋辦? 3. 同時(shí)多個(gè)線程并發(fā)寫咋辦? ......

?

?

下面的分析中就會(huì)面對(duì)這些問題,有些答案很清晰,有些涉及到超級(jí)多細(xì)節(jié)。

step by step

在開始之前,我們知道Write操作是要記錄到log文件中的。那么一個(gè)記錄它的格式是怎樣的呢?看圖:

這里特別解釋一下,Delete操作也是通過Put實(shí)現(xiàn)的,只是圖中的類型字段是0,而正常Write的操作類型是1,由此區(qū)分寫操作和刪除操作!


熟悉了LevelDB整個(gè)脈絡(luò)之后, Put方法是相當(dāng)簡(jiǎn)單的, 一章就可以解決. 數(shù)據(jù)刪除和寫入是一個(gè)概念, 刪除就是寫入特殊deletion marker; 批量(batch)和單條寫入也是一個(gè)概念, 單條寫入就是只有一條記錄的batch. 整個(gè)流程很短, 基本上寫個(gè)log就好了, 因此速度很快.

step 1

Put interface

很簡(jiǎn)單吧。這里講解一下那三個(gè)參數(shù):

  • WriteOptions:提供一些寫操作的配置項(xiàng),例如要不要寫log的時(shí)候馬上flush磁盤
  • key和value就是對(duì)應(yīng)的keyValue,slice只是作者自己封裝的char數(shù)組存儲(chǔ)數(shù)據(jù)而已。<b>大牛喜歡把所有東西都封裝一下,賦予數(shù)據(jù)結(jié)構(gòu)意義!</b>這在大的工程里面是很有意義的,既方便操作,也方便思考(這樣就不用思考底層的真實(shí)的char數(shù)組還是啥)。
  • Put對(duì)于多線程的處理非常精妙, 主體在DBImpl::Write函數(shù)中.

    插入:

    Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) {WriteBatch batch; //leveldb中不管單個(gè)插入還是多個(gè)插入都是以WriteBatch的方式進(jìn)行的batch.Put(key, value);return Write(opt, &batch); }

    一條記錄包含如下內(nèi)容:?
    Type、Key、Value?
    當(dāng)要插入記錄時(shí),Type為kTypeValue,當(dāng)要?jiǎng)h除記錄時(shí),Type為kTypeDeletion,同時(shí)中每一個(gè)batch都有一個(gè)對(duì)當(dāng)前批處理記錄信息的統(tǒng)計(jì)(sequence(8字節(jié))和count(4字節(jié)),共12字節(jié))?
    由此可見,當(dāng)我們要?jiǎng)h除一個(gè)數(shù)據(jù)時(shí),并不是直接從內(nèi)存中刪除,而是插入一條帶有刪除標(biāo)志的記錄

    在本例中要插入數(shù)據(jù):key=”lili”; value=”hihi”;?
    由之前對(duì)WriterBatch的分析可知,得到的batch為:?
    01 00 00 00 00 00 00 00 01 00 00 00 (前8字節(jié)表示是第一個(gè)batch,后4字節(jié)表示此batch中只有一條記錄)?
    01(kTypeValue) 04(Key.size) 6C 69 6C 69(lili) 04(value.size) 68 69 68 69(hihi)?
    共12+1+1+4+1+4=23字節(jié)=0x17

    Delete也類似,只是調(diào)用了WriteBatch 的 Delete(key), 這樣再內(nèi)部會(huì)以不同的形式編碼傳遞至下一步進(jìn)行處理。具體的WriteBatch的實(shí)現(xiàn)和編碼方式在稍后的文章中進(jìn)行介紹。Delete和Put都調(diào)用了Write,,這里的Write是在DBImpl::Write中通過虛函數(shù)的形式實(shí)現(xiàn)對(duì)其調(diào)用的,我們接著看Write的流程

    1 Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) { 2 // A begin 3 Writer w(&mutex_); 4 w.batch = my_batch; 5 w.sync = options.sync; 6 w.done = false; 7 // A end 8 9 // B begin
    /*
    mutex l上鎖之后, 到了"w.cv.Wait()"的時(shí)候, 會(huì)先釋放鎖等待, 然后收到signal時(shí)再次上鎖. 這段代碼的作用就是多線程在提交任務(wù)的時(shí)候,
    一個(gè)接一個(gè)push_back進(jìn)隊(duì)列. 但只有位于隊(duì)首的線程有資格繼續(xù)運(yùn)行下去. 目的是把多個(gè)寫請(qǐng)求合并成一個(gè)大batch提升效率.
    */
    10 MutexLock l(&mutex_); 11 writers_.push_back(&w); 12 while (!w.done && &w != writers_.front()) { 13 w.cv.Wait(); 14 } 15 if (w.done) { 16 return w.status; 17 } 18 // B end 19 20 // May temporarily unlock and wait. 21 Status status = MakeRoomForWrite(my_batch == NULL); 22 uint64_t last_sequence = versions_->LastSequence(); 23 Writer* last_writer = &w; 24 if (status.ok() && my_batch != NULL) { // NULL batch is for compactions 25 WriteBatch* updates = BuildBatchGroup(&last_writer); 26 WriteBatchInternal::SetSequence(updates, last_sequence + 1); 27 last_sequence += WriteBatchInternal::Count(updates); 28 29 // Add to log and apply to memtable. We can release the lock 30 // during this phase since &w is currently responsible for logging 31 // and protects against concurrent loggers and concurrent writes 32 // into mem_. 33 { 34 mutex_.Unlock(); 35 status = log_->AddRecord(WriteBatchInternal::Contents(updates)); 36 bool sync_error = false; 37 if (status.ok() && options.sync) { 38 status = logfile_->Sync(); 39 if (!status.ok()) { 40 sync_error = true; 41 } 42 } 43 if (status.ok()) { 44 status = WriteBatchInternal::InsertInto(updates, mem_); 45 } 46 mutex_.Lock(); 47 if (sync_error) { 48 // The state of the log file is indeterminate: the log record we 49 // just added may or may not show up when the DB is re-opened. 50 // So we force the DB into a mode where all future writes fail. 51 RecordBackgroundError(status); 52 } 53 } 54 if (updates == tmp_batch_) tmp_batch_->Clear(); 55 56 versions_->SetLastSequence(last_sequence); 57 } 58 59 while (true) { 60 Writer* ready = writers_.front(); 61 writers_.pop_front(); 62 if (ready != &w) { 63 ready->status = status; 64 ready->done = true; 65 ready->cv.Signal(); 66 } 67 if (ready == last_writer) break; 68 } 69 70 // Notify new head of write queue 71 if (!writers_.empty()) { 72 writers_.front()->cv.Signal(); 73 } 74 75 return status; 76 }

    所以從流程可以清晰的看到插入刪除的流程主要為:

    1. 將這條KV記錄以順序?qū)懙姆绞阶芳拥絣og文件末尾;

    2. 將這條KV記錄插入內(nèi)存中的Memtable中,在插入過程中如果剛好后臺(tái)進(jìn)程在compaction會(huì)短暫停頓以為后臺(tái)進(jìn)程compaction騰出時(shí)間及cpu

    這里涉及到一次磁盤讀寫操作和內(nèi)存SkipList的插入操作,但是這里的磁盤寫時(shí)文件的順序追加寫入效率是很高的,所以并不會(huì)導(dǎo)致寫入速度的降低;

    而且從流程分析我們知道,在插入(刪除)過程中如果多線程同時(shí)進(jìn)行,那么這些操作將會(huì)將操作的同步設(shè)置相同的相鄰的操作合并為一個(gè)批插入,這樣可以使整個(gè)系統(tǒng)的總吞吐量更大。所以一次插入記錄操作只會(huì)等待一次磁盤文件追加寫和內(nèi)存SkipList插入操作,這是為何leveldb寫入速度如此高效的根本原因。

      假設(shè)同時(shí)有w1, w2, w3, w4, w5, w6 并發(fā)請(qǐng)求寫入。

      B部分代碼讓競(jìng)爭(zhēng)到mutex資源的w1獲取了鎖。w1將它要寫的數(shù)據(jù)添加到了writers_隊(duì)列里去,此時(shí)隊(duì)列只有一個(gè)w1, 從而其順利的進(jìn)行buildbatchgroup。當(dāng)運(yùn)行到34行時(shí)mutex_互斥鎖釋放,之所以這兒可以釋放mutex_,是因?yàn)槠渌膶懖僮鞫疾粷M足隊(duì)首條件,進(jìn)而不會(huì)進(jìn)入log和memtable寫入階段。這時(shí)(w2, w3, w4, w5, w6)會(huì)競(jìng)爭(zhēng)鎖,由于B段代碼中不滿足隊(duì)首條件,均等待并釋放鎖了。從而隊(duì)列可能會(huì)如(w3, w5, w2, w4).

      繼而w1進(jìn)行l(wèi)og寫入和memtable寫入。 當(dāng)w1完成log和memtable寫入后,進(jìn)入46行代碼,則mutex_又鎖住,這時(shí)B段代碼中隊(duì)列因?yàn)楂@取不到鎖則隊(duì)列不會(huì)修改。

      隨后59行開始,w1被pop出來,由于ready==w, 并且ready==last_writer,所以直接到71行代碼,喚醒了此時(shí)處于隊(duì)首的w3.

    ? ? ? w3喚醒時(shí),發(fā)現(xiàn)自己是隊(duì)首,可以順利的進(jìn)行進(jìn)入buildbatchgroup,在該函數(shù)中,遍歷了目前所有的隊(duì)列元素,形成一個(gè)update的batch,即將w3, w5, w2, w4合并為一個(gè)batch. 并將last_writer置為此時(shí)處于隊(duì)尾的最后一個(gè)元素w4,34行代碼運(yùn)行后,因?yàn)獒尫帕随i資源,隊(duì)列可能隨著dbimpl::write的調(diào)用而更改,如隊(duì)列狀況可能為(w3, w5, w2, w4, w6, w9, w8).

    ?  35-45行的代碼將w3, w5, w2, w4整個(gè)的batch寫入log和memtable. 到65行,分別對(duì)w5, w2, w4進(jìn)行了一次cond signal.當(dāng)判斷到完w4 == lastwriter時(shí),則退出循環(huán)。72行則對(duì)隊(duì)首的w6喚醒,從而按上述步驟依次進(jìn)行下去。

      這樣就形成了多個(gè)并發(fā)write 合并為一個(gè)batch寫入log和memtable的機(jī)制。

    轉(zhuǎn)載于:https://www.cnblogs.com/ym65536/p/7720105.html

    總結(jié)

    以上是生活随笔為你收集整理的[leveldb] 3.put/delete操作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。