本小节继续整合之前的所有模块, 实现迭代器。
代码仓库:https://github.com/ToniXWD/toni-lsm
欢迎点个Star
1 迭代器实现思路
实现整个LSM Tree
的迭代器会复杂一点, 迭代器本质上就是数据定位信息的汇总, 回顾一下我们的LSM Tree
引擎的定义头文件:
1 2 3 4 5 6 7 8 9 10
| class LSMEngine { public: std::string data_dir; MemTable memtable; std::list<size_t> l0_sst_ids; std::unordered_map<size_t, std::shared_ptr<SST>> ssts; std::shared_mutex ssts_mtx; std::shared_ptr<BlockCache> block_cache; ... }
|
因此基础的定位信息数据包括:
- 数据属于
memtable
还是sst
- 如果数据属于
memtable
, 其在哪个skiplist
中? 在这个skiplist
中的哪个位置?
- 如果数据属于
sst
, 其在哪个sst
中? 在这个sst
中的哪个block
? 在该block
的索引是多少?
乍一看挺复杂的, 但由于我们之前已经实现了SstIterator
和SkipListIterator
, 因此我们只需要将这些迭代器组合起来即可。组合逻辑就是:
- 遍历多个
SstIterator
形成一个HeapItearator
为h1
- 遍历多个
SkipListIterator
形成一个HeapItearator
为h2
- 将
h1
和h2
进行合并, 得到一个新的迭代器类似, 称为MergeIterator
, 记为m
这里用图示来说明一下:

2 MergeIterator
MergeIterator
需要包括2个Heaporator
, 其中一个由MemTable
整理而来, 另一个由SST
整理而来, 因此这2个Heaporator
由优先级之分, 对于相同的key
, 优先选择MemTable
的迭代器, 并跳过SST
中相同的迭代器。由于Heaporator
本质上都是小根堆, 因此相同的key
肯定会同时出现的, 跳过流程也很简单。这里先给出头文件定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #pragma once
#include "../iterator/iterator.h" #include "../sst/sst_iterator.h"
#include <memory>
class MergeIterator { using value_type = std::pair<std::string, std::string>; using pointer = value_type *;
private: HeapIterator it_a; HeapIterator it_b; bool choose_a = false; mutable std::shared_ptr<value_type> current;
void update_current() const;
public: MergeIterator(); MergeIterator(HeapIterator it_a, HeapIterator it_b); bool choose_it_a(); void skip_it_b(); bool is_end() const;
std::pair<std::string, std::string> operator*() const; MergeIterator &operator++(); bool operator==(const MergeIterator &other) const; bool operator!=(const MergeIterator &other) const; pointer operator->() const; };
|
接下来就是自增和*
运算符重载的实现:
自增
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void MergeIterator::skip_it_b() { if (!it_a.is_end() && !it_b.is_end() && it_a->first == it_b->first) { ++it_b; } }
MergeIterator &MergeIterator::operator++() { if (choose_a) { ++it_a; } else { ++it_b; } skip_it_b(); choose_a = choose_it_a(); return *this; }
|
这里的skip_it_b
就是判断如果a
和b
的key
相同, 那么就跳过b
的迭代器。a
就是由memtable
整理而来, b
就是由sst
整理而来。
*
运算符重载
1 2 3 4 5 6 7
| std::pair<std::string, std::string> MergeIterator::operator*() const { if (choose_a) { return *it_a; } else { return *it_b; } }
|
3 迭代器的构建流程
有了MergeIterator
, 我们就可以构建整个LSM
的迭代器了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| MergeIterator LSMEngine::begin() { std::vector<SearchItem> item_vec; std::shared_lock<std::shared_mutex> lock(ssts_mtx); for (auto &sst_id : l0_sst_ids) { auto sst = ssts[sst_id]; for (auto iter = sst->begin(); iter != sst->end(); ++iter) { item_vec.emplace_back(iter.key(), iter.value(), -sst_id); } } HeapIterator l0_iter(item_vec);
auto mem_iter = memtable.begin();
return MergeIterator(mem_iter, l0_iter); }
|
这里有一点需要注意, 我们的HeapIterator
是个小根堆, 我们先要堆顶的元素是最近写入存储引擎的话, 就需要其排序比较运算符更小, 但实际上sst_id
是越大表示越新, 因此这里有个小技巧, 就是对其取一个负值, 这样就让新的sst
排在堆顶了。
end()
函数构建一个2个迭代器都为空的迭代器就可以了:
1
| MergeIterator LSMEngine::end() { return MergeIterator{}; }
|