Rocksdb?Memtable數(shù)據(jù)結(jié)構(gòu)源碼解析
一、什么是 Memtable?
Memtable 是 Rocksdb 在內(nèi)存中保存數(shù)據(jù)的一種數(shù)據(jù)結(jié)構(gòu),一個(gè) Memtable 的容量是固定的,在 Memtable 寫滿后,會(huì)轉(zhuǎn)換為 Immutable Memtable,Immutable Memtable 中的數(shù)據(jù)會(huì) Flush 到 SST File 中。
Memtable 和 Immutable Memtable 的唯一區(qū)別是 Memtable 可讀可寫,而 Immutable Memtable 是只讀且不允許寫入。Rocksdb 引入了 Column Family 的概念,在一個(gè) Column Family 中只有一個(gè) Memtable,但允許存在多個(gè) Immutable Memtable。Rocksdb 支持創(chuàng)建多數(shù)據(jù)結(jié)構(gòu)類型的 Memtable,默認(rèn)的是 SkipList,即跳躍表。
二、Memtable 的數(shù)據(jù)結(jié)構(gòu)
Rocksdb 中 Memtable 有多種實(shí)現(xiàn)方式 (SkipList / HashSkipList / HashLinkList / Vector),其中默認(rèn)的實(shí)現(xiàn)方式為 SkipList。
一個(gè) Memtable 中維護(hù)了兩個(gè) SkipList,其中范圍刪除插入 range_del_table_,其余的操作寫入 table_。
Memtable 定義的操作接口 Add () 如下:
bool MemTable::Add(SequenceNumber s, ValueType type, const Slice& key, /* user key */ const Slice& value, bool allow_concurrent, MemTablePostProcessInfo* post_process_info) { // 一條key-value Entry的數(shù)據(jù)格式 // key_size : varint32 of internal_key.size() // key bytes : char[internal_key.size()] // value_size : varint32 of value.size() // value bytes : char[value.size()] uint32_t key_size = static_cast<uint32_t>(key.size()); uint32_t val_size = static_cast<uint32_t>(value.size()); uint32_t internal_key_size = key_size + 8; const uint32_t encoded_len = VarintLength(internal_key_size) + internal_key_size + VarintLength(val_size) + val_size; char* buf = nullptr; // 通過判斷key-value的類型來選擇memtable, 范圍刪除的kv插入range_del_table_ std::unique_ptr<MemTableRep>& table = type == kTypeRangeDeletion ? range_del_table_ : table_; KeyHandle handle = table->Allocate(encoded_len, &buf); //... // 是否允許并發(fā)插入 if (!allow_concurrent) { // 是否制定了函數(shù)提取key的前綴 if (insert_with_hint_prefix_extractor_ != nullptr && insert_with_hint_prefix_extractor_->InDomain(key_slice)) { // ... bool res = table->InsertWithHint(handle, &insert_hints_[prefix]); } else { // 插入key-value pair bool res = table->Insert(handle); if (UNLIKELY(!res)) { return res; } } } else { // 插入key-value pair bool res = table->InsertConcurrently(handle); if (UNLIKELY(!res)) { return res; } } return true; }
Add () 函數(shù)將用戶的 key 和 value 封裝成一個(gè) buf,然后根據(jù)不同的條件調(diào)用 table->Insert () 插入至 Memtable。table 就是 Memtable 的工廠類實(shí)現(xiàn),默認(rèn) SkiplistRep, 即通過調(diào)用 SkipList 的 Insert () 完成 key 的插入。
Memtable 定義的操作接口 Get () 如下:
bool MemTable::Get(const LookupKey& key, std::string* value, Status* s, MergeContext* merge_context, RangeDelAggregator* range_del_agg, SequenceNumber* seq, const ReadOptions& read_opts, ReadCallback* callback, bool* is_blob_index) { // 在range_del_table_上初始化一個(gè)迭代器 std::unique_ptr<InternalIterator> range_del_iter( NewRangeTombstoneIterator(read_opts)); Status status = range_del_agg->AddTombstones(std::move(range_del_iter)); if (!status.ok()) { *s = status; return false; } Slice user_key = key.user_key(); // 利用前綴提取過濾判斷key是否存在 bool const may_contain = nullptr == prefix_bloom_
Memtable 的 Get () 調(diào)用了 SkipListRep 的 Get () 接口,最終是通過 SkipList 的 FindGreaterOrEqual () 來查找。查找出來的 key 會(huì)被傳入的回調(diào)函數(shù) SaveValu 并 e () 根據(jù) type 處理,例如 ktypeDeletion 就返回 NotFound ()。
三、什么是 SkipList?
SkipList 即跳躍表,在普通單向鏈表的基礎(chǔ)上增加了一些索引,而且這些索引是分層的,從而可以快速地查到數(shù)據(jù)。如下是一個(gè)典型的跳躍表構(gòu)建過程:
初始我們有個(gè)帶頭結(jié)點(diǎn)的有序鏈表 a,而后每相鄰兩個(gè)節(jié)點(diǎn)增加一個(gè)指針,讓指針指向下下個(gè)節(jié)點(diǎn),得到表 b。這樣所有新增指針連成了一個(gè)新的鏈表,但它包含的節(jié)點(diǎn)個(gè)數(shù)只有原來的一半。其后我們對(duì)第二層鏈表再次進(jìn)行此操作,得到表 c。重復(fù)這個(gè)過程,直到采樣出的節(jié)點(diǎn)只剩一個(gè),如圖 d。這樣便完成了跳躍表的構(gòu)建。跳躍表查找過程如下:
從 head 開始,head 的 level 為 4,判斷 head 后繼節(jié)點(diǎn)值小于 <12,此時(shí)當(dāng)前節(jié)點(diǎn)變?yōu)?6,繼續(xù)查找;
節(jié)點(diǎn) 6 的 level 為 3,判斷后繼節(jié)點(diǎn)值為 NIL,因此 level 降低到 2;
判斷 x -> forward [2] -> key (25) > 17,繼續(xù)降低 level 到 1;
判斷 x -> forward [1] -> key (9) < 17,此時(shí) x 變?yōu)?x ->forward [1],x 成為節(jié)點(diǎn) 9;
節(jié)點(diǎn) 9 的判斷 x -> forward [1] -> key 為 17,因此找到節(jié)點(diǎn),直接返回。
跳躍表插入過程如下:
我們以上圖為例,list -> leve=4,如果要插入節(jié)點(diǎn) 17,首先確定搜索路徑,與之前步驟類似。
創(chuàng)建新節(jié)點(diǎn) Node (17),并為其生成 level (隨機(jī)),該 level 可能值為 [1, MaxLevel],此時(shí)需要對(duì)比,如果 level < list -> level,需要先將突出部分從 header 指向它,這里新生成的節(jié)點(diǎn) Node (17) 的 level 為 5,超過了 list 當(dāng)前的最大 level,于是將 update [4] 設(shè)置為 header,后續(xù)直接將 Node (17) 作為 header 的后繼。
最后是設(shè)置搜索路徑上每個(gè)節(jié)點(diǎn)的后繼關(guān)系,這樣我們便完成了節(jié)點(diǎn)的插入。我們來看一下 SkipList 的具體代碼實(shí)現(xiàn):
InlineSkipList 數(shù)據(jù)結(jié)構(gòu) >>
class InlineSkipList { private: struct Node; struct Splice; public: using DecodedKey = \ typename std::remove_reference<Comparator>::type::DecodedType; … Allocator* const allocator_; Comparator const compare_; Node* const head_; std::atomic<int> max_height_; Splice* seq_splice_; };
Node 的數(shù)據(jù)結(jié)構(gòu) >>
template <class Comparator> struct InlineSkipList<Comparator>::Node { private: // 存放該節(jié)點(diǎn)的next_節(jié)點(diǎn)的數(shù)組 // 數(shù)組大小為該節(jié)點(diǎn)的height,當(dāng)調(diào)用NewNode()分配內(nèi)存初始化整個(gè)數(shù)組 std::atomic<Node*> next_[1]; };
Node 的數(shù)據(jù)結(jié)構(gòu)如圖,它將 key 和鏈表每層的指針連續(xù)存儲(chǔ),通過 next_[-n] 這種方式來訪問每層的 next 指針,此外在 new 新節(jié)點(diǎn)時(shí)會(huì)把該節(jié)點(diǎn)高度寫在 next_[0] 的前 4 個(gè)字節(jié)處,當(dāng)完成插入后,next_[0] 會(huì)恢復(fù)成指向同層的下一個(gè)節(jié)點(diǎn)的指針。
四、InlineSkipList 插入
Memtable 的 Add () 通過 SkipList 的 Insert () 來查找,下面是 Insert () 的具體實(shí)現(xiàn):
bool InlineSkipList<Comparator>::Insert(const char* key, Splice* splice, bool allow_partial_splice_fix) { Node* x = reinterpret_cast<Node*>(const_cast<char*>(key)) - 1; // x即為next_[0] const DecodedKey key_decoded = compare_.decode_key(key); int height = x->UnstashHeight(); assert(height >= 1 && height <= kMaxHeight_); int max_height = max_height_.load(std::memory_order_relaxed); // 更新max_height while (height > max_height) { if (max_height_.compare_exchange_weak(max_height, height)) { // successfully updated it max_height = height; break; } // 否則重試,可能因?yàn)槠渌嗽黾恿怂顺鲅h(huán) } assert(max_height <= kMaxPossibleHeight); // 插入節(jié)點(diǎn)的時(shí)候,需要借助一個(gè)Splice對(duì)象,該對(duì)象主要保存著最近一次插入的節(jié)點(diǎn)快照 // 它保存著一個(gè)prev和next的節(jié)點(diǎn)指針數(shù)組,由Level可以索引到對(duì)應(yīng)Level的節(jié)點(diǎn) int recompute_height = 0; if (splice->height_ < max_height) { // 當(dāng)重置splice splice->prev_[max_height] = head_; splice->next_[max_height] = nullptr; splice->height_ = max_height; recompute_height = max_height; } else { while (recompute_height < max_height) { if (splice->prev_[recompute_height]->Next(recompute_height) != splice->next_[recompute_height]) { //判斷該層的splice是否緊密,即prev_->Next是否等于next_ ++recompute_height; } else if (splice->prev_[recompute_height] != head_ && !KeyIsAfterNode(key_decoded, splice->prev_[recompute_height])) { //小于splice當(dāng)前層的prev_ // ... } else if (KeyIsAfterNode(key_decoded, splice->next_[recompute_height])) { //大于splice當(dāng)前層的prev_ // ... } else { // 找到了合適的level break; } } } assert(recompute_height <= max_height); if (recompute_height > 0) {//計(jì)算splice RecomputeSpliceLevels(key_decoded, splice, recompute_height); // 找到要插入的key合適的splice } bool splice_is_valid = true; if (UseCAS) {//CAS無鎖機(jī)制 //... } else { for (int i = 0; i < height; ++i) { if (i >= recompute_height && splice->prev_[i]->Next(i) != splice->next_[i]) { // 確保splice此Level有效,如果無效的話再查找一次 FindSpliceForLevel<false>(key_decoded, splice->prev_[i], nullptr, i, &splice->prev_[i], &splice->next_[i]); } // Checking for duplicate keys on the level 0 is sufficient if (UNLIKELY(i == 0 && splice->next_[i] != nullptr && compare_(x->Key(), splice->next_[i]->Key()) >= 0)) { // duplicate key return false; } if (UNLIKELY(i == 0 && splice->prev_[i] != head_ && compare_(splice->prev_[i]->Key(), x->Key()) >= 0)) { // duplicate key return false; } //… x->NoBarrier_SetNext(i, splice->next_[i]); //將新節(jié)點(diǎn)next指向?qū)?yīng)的next節(jié)點(diǎn) splice->prev_[i]->SetNext(i, x); //將splice的prev節(jié)點(diǎn)的next指向新節(jié)點(diǎn) } } if (splice_is_valid) {//將新節(jié)點(diǎn)Height下的prev節(jié)點(diǎn)都設(shè)置為該節(jié)點(diǎn),因?yàn)樵鹊膒rev和next之間已經(jīng)不連續(xù)了。 for (int i = 0; i < height; ++i) { splice->prev_[i] = x; } //... } else { splice->height_ = 0; } return true; }
五、InlineSkipList 查找
Memtable 的 Get () 通過 SkipList 的 FindGreaterOrEqual () 來查找,下面是 FindGreaterOrEqual () 的具體實(shí)現(xiàn):
InlineSkipList<Comparator>::FindGreaterOrEqual(const char* key) const { Node* x = head_; int level = GetMaxHeight() - 1;//從最高層開始查找 Node* last_bigger = nullptr; const DecodedKey key_decoded = compare_.decode_key(key); while (true) { Node* next = x->Next(level); if (next != nullptr) { PREFETCH(next->Next(level), 0, 1); } // Make sure the lists are sorted assert(x == head_ || next == nullptr || KeyIsAfterNode(next->Key(), x)); // Make sure we haven't overshot during our search assert(x == head_ || KeyIsAfterNode(key_decoded, x)); int cmp = (next == nullptr || next == last_bigger) ? 1 : compare_(next->Key(), key_decoded); if (cmp == 0 || (cmp > 0 && level == 0)) { // 找到相等的key或者查找的key不在此范圍內(nèi) return next; } else if (cmp < 0) { //待查找 key 比 next 大,則在該層繼續(xù)查找 x = next; } else { // 待查找 key 不大于 next,且沒到底,則繼續(xù)往下層查找 // Switch to next list, reuse compare_() result last_bigger = next; level--; } } }
以上就是Rocksdb Memtable數(shù)據(jù)結(jié)構(gòu)源碼解析的詳細(xì)內(nèi)容,更多關(guān)于Rocksdb Memtable數(shù)據(jù)結(jié)構(gòu)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android開發(fā)實(shí)現(xiàn)ListView點(diǎn)擊item改變顏色功能示例
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)ListView點(diǎn)擊item改變顏色功能,涉及Android布局及響應(yīng)事件動(dòng)態(tài)變換元素屬性相關(guān)操作技巧,需要的朋友可以參考下2017-11-11android中在Activity中響應(yīng)ListView內(nèi)部按鈕的點(diǎn)擊事件的兩種方法
本篇文章主要介紹了android中在Activity中響應(yīng)ListView內(nèi)部按鈕的點(diǎn)擊事件的兩種方法,有需要的可以了解一下。2016-11-11結(jié)合Windows窗口深入分析Android窗口的實(shí)現(xiàn)
在Android中,窗口是一個(gè)基本的圖形用戶界面元素,它提供了一個(gè)屏幕區(qū)域來放置應(yīng)用程序的用戶界面元素。窗口可以是全屏的,也可以是一個(gè)小的對(duì)話框。每個(gè)窗口都有一個(gè)特定的主題和樣式,可以根據(jù)應(yīng)用程序的需求進(jìn)行自定義2023-04-04淺析Kotlin使用infix函數(shù)構(gòu)建可讀語(yǔ)法流程講解
這篇文章主要介紹了淺析Kotlin使用infix函數(shù)構(gòu)建可讀語(yǔ)法,我們?cè)贙otlin中就多次使用A to B這樣的語(yǔ)法結(jié)構(gòu)構(gòu)建鍵值對(duì),包括Kotlin自帶的mapOf()函數(shù),這種語(yǔ)法結(jié)構(gòu)的優(yōu)點(diǎn)是可讀性強(qiáng)2023-01-01Android實(shí)現(xiàn)點(diǎn)擊獲取驗(yàn)證碼倒計(jì)時(shí)效果
這篇文章主要介紹了Android實(shí)現(xiàn)點(diǎn)擊獲取驗(yàn)證碼倒計(jì)時(shí)效果,這種效果大家經(jīng)常遇到,想知道如何實(shí)現(xiàn)的,請(qǐng)閱讀本文2016-08-08高仿網(wǎng)易新聞頂部滑動(dòng)條效果實(shí)現(xiàn)代碼
網(wǎng)易新聞的主界面頂部的滑動(dòng)條個(gè)人感覺還是比較漂亮的所以今天也模仿了下,網(wǎng)易頂部滑動(dòng)條的效果,由于初次模仿這種效果,可能有些地方還不夠完美,不過基本已經(jīng)實(shí)現(xiàn),希望大家能夠喜歡2013-01-01老生常談Android HapticFeedback(震動(dòng)反饋)
下面小編就為大家?guī)硪黄仙U凙ndroid HapticFeedback(震動(dòng)反饋)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04Android GestureDetector用戶手勢(shì)檢測(cè)實(shí)例講解
這篇文章主要為大家詳細(xì)介紹了Android GestureDetector用戶手勢(shì)檢測(cè)實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03