leveldb源码分析--Iterator遍历数据库
在DBImpl中有一个函数声明为Iterator* DBImpl::NewIterator(const ReadOptions& options) ,他返回一个可以遍历或者搜索数据库的迭代器句柄。
Iterator* DBImpl::NewIterator(const ReadOptions& options) {
SequenceNumber latest_snapshot;
uint32_t seed;
Iterator* iter = NewInternalIterator(options, &latest_snapshot, &seed);
return NewDBIterator(
this, user_comparator(), iter,
(options.snapshot != NULL
? reinterpret_cast<const SnapshotImpl*>(options.snapshot)->number_
: latest_snapshot),
可以看到这个函数就是获得一个内部迭代器句柄然后再用NewDBIterator包装返回一个DBIter,这个DBIter的目的就是作为内部迭代器的桥接封装的作用,方便用户调用。其接口函数大致有:
virtual bool Valid() const;
virtual Slice key() cons;
virtual Slice value() const;
virtual Status status() const ;
virtual void Next();
virtual void Prev();
virtual void Seek(const Slice& target);
virtual void SeekToFirst();
virtual void SeekToLast();
这些封装只是对InternalIterator的一个简单封装,他们都以依赖于一个这个InternalIterator。我们来看看InternalIterator的获取
Iterator* DBImpl::NewInternalIterator(const ReadOptions& options,
SequenceNumber* latest_snapshot,
uint32_t* seed) {
IterState* cleanup = new IterState;
mutex_.Lock();
*latest_snapshot = versions_->LastSequence(); // Collect together all needed child iterators
std::vector<Iterator*> list;
list.push_back(mem_->NewIterator());
mem_->Ref();
if (imm_ != NULL) {
list.push_back(imm_->NewIterator());
imm_->Ref();
}
versions_->current()->AddIterators(options, &list);
Iterator* internal_iter =
NewMergingIterator(&internal_comparator_, &list[0], list.size());
versions_->current()->Ref(); cleanup->mu = &mutex_;
cleanup->mem = mem_;
cleanup->imm = imm_;
cleanup->version = versions_->current();
internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, NULL); *seed = ++seed_;
mutex_.Unlock();
return internal_iter;
}
这里internal_iter的获取是从memTable、imm、还有version取得的所有与迭代器全部传入到一个MergingIterator中。在详细介绍这个MergingIterator前我们先来看看一个简化的例子,并且假设此时没有imm_table。这样我们现在有一个Memtable,SSTable中level0有两个文件,有一个level1的文件,里面的key大致如下(我们根据新旧程度排序,数据新旧依据请查阅Compaction章节):
Memtable: 1,2,3,4,5,6
level0-2: 3,4,7,6
level0-1: 2,3,4,6
level1: 1,3,6,7,9…
这样,如果我们开始从第一个key开始以递增(Next)的方式遍历整个数据库,那么我们可以见到如下的过程。首先是每个初始化一个指向当前文件(这里暂时将Memtable也当做一个文件)第一个位置的指针,如下红色表示当前指向的指针:
Memtable: 1,3,4,5,6
level0-2: 3,4,7,6
level0-1: 2,3,4,6
level1: 1,3,6,7,9…
这样,我们根据数据最新关系我们很容易判断第一个key应该为Memtable中的1,我们记该当前key为1。然后再调用Next,调用Next的时候就需要将Memtable和level1中的当前指针key为1的向后一个key(level1中的移动在leveldb中是在FindSmallest中进行的),得到如下:
Memtable: 1,3,4,5,6
level0-2: 3,4,7,6
level0-1: 2,3,4,6
level1: 1,2,6,7,9…
那么这个时候我们也很容以判断这里的next的值应该是level0-1中的2。那么我做出这个判断的过程是怎么样的呢?应该是找出当前每个文件中指针指向的值中最小的那个key,如果有多个文件中当前指针key相同的时候,那么就应该取最新的那个文件中。再继续Next,
Memtable: 1,3,4,5,6
level0-2: 3,4,7,6
level0-1: 2,3,4,6
level1: 1,2,6,7,9…
那么此时应该是Memtable中的3。那么此时我们需要先前查找当前3的前一个呢?很明显我们应该回到上面第三个图的状态,应该level1,level0-1都进行回溯,然后选择最小的那个。但是如何能回到该状态呢?如果这样的话我们必须记录每次移动的过程,这种过程性的记录在程序设计中是十分难以做到的。而在leveldb中也采用了另外一种方式,就是在我们的迭代器器中记录一个当前遍历的值比如此时的level0-1中的3进行一个Prev,然后再查找最大值,最大值方式的时候如果大于3就继续往前回溯,再找到最大的最新的。形成的状态如下:
Memtable: 1,3,4,5,6 //找到3,然后在prev到1
level0-2: 3,4,7,6 //此处其实应该为invalid,即找到3,prev到invalid
level0-1: 2,3,4,6 //找到3,prev到2
level1: 1,2,6,7,9… /找到6,prev到2
而查找的最大最新值也应该是level0-1中的2。
下面我们来看看代码Prev的实现:
virtual void Prev() {
if (direction_ != kReverse) {// 如果之前遍历方向向后
for (int i = 0; i < n_; i++) {
IteratorWrapper* child = &children_[i];
if (child != current_) {
child->Seek(key());// 查找遍历当前值,然后再往前回溯
if (child->Valid()) {
// Child is at first entry >= key(). Step back one to be < key()
child->Prev();
} else {
//没有>当前key值的key.
child->SeekToLast();
}
}
}
direction_ = kReverse;
}
current_->Prev();
FindLargest();
}
virtual void Next() {
if (direction_ != kForward) {
for (int i = 0; i < n_; i++) {
IteratorWrapper* child = &children_[i];
if (child != current_) {
child->Seek(key());
if (child->Valid() &&// 如果key为当前key相等,向后next
comparator_->Compare(key(), child->key()) == 0) {
child->Next();
}
}
}
direction_ = kForward;
}
current_->Next();
FindSmallest();
}
所以再调用Next的过程就为:查找 >= 2的,如果找到并且==2就Next,然后找最小的最新的一个位置。
Memtable: 1,3,4,5,6 //找到3
level0-2: 3,4,7,6 //找到3
level0-1: 2,3,4,6 //这里是先找到2,然后再Next
level1: 1,2,6,7,9… //同上
所以这里的操作就变成了查找
我们详细看看Next
void DBIter::Next() {
assert(valid_);
if (direction_ == kReverse) { // Switch directions?
direction_ = kForward;
// 如果上次已经到最后,回溯到第一个
if (!iter_->Valid()) {
iter_->SeekToFirst();
} else {
iter_->Next();
}
if (!iter_->Valid()) {
valid_ = false;
saved_key_.clear();
return;
}
// saved_key_ already contains the key to skip past.
} else {
// 存储当前key,以备下次为Prev时查找这个key.
SaveKey(ExtractUserKey(iter_->key()), &saved_key_);
}
FindNextUserEntry(true, &saved_key_);
}
这里由于leveldb遍历数据库时涉及到多个数据文件及内存中的Memtable,所以每次调用prev和next时会有比较复杂的处理。
void DBIter::FindNextUserEntry(bool skipping, std::string* skip) {
// Loop until we hit an acceptable entry to yield
assert(iter_->Valid());
assert(direction_ == kForward);
do {
ParsedInternalKey ikey;
if (ParseKey(&ikey) && ikey.sequence <= sequence_) {
switch (ikey.type) {
case kTypeDeletion:
// 如果为删除,标记后面的已删除的key应该跳过
// 保存跳过的key
SaveKey(ikey.user_key, skip);
skipping = true;
break;
case kTypeValue:
if (skipping &&
user_comparator_->Compare(ikey.user_key, *skip) <= 0) {
// 小于等于,跳过
} else {//找到值,返回
valid_ = true;
saved_key_.clear();
return;
}
break;
}
}
iter_->Next();
} while (iter_->Valid());
saved_key_.clear();
valid_ = false;
}
这里我们不再对DBIter中的其他函数进行一一介绍,比如Prev和这里也是一个类似的(但是比较相反)处理过程。稍微提一下的是我们在void DBIter::FindPrevUserEntry() 中有如下一段代码
if (saved_value_.capacity() > raw_value.size() + 1048576) {
std::string empty;
swap(empty, saved_value_);
}
这里当saved_value中的长度超过一定的值以后我们将其交换给一个零时变量,这样在超出其域以后析构时就可以对其内部的内存进行释放,而如果只是改变其大小内存则得不到释放。
leveldb源码分析--Iterator遍历数据库的更多相关文章
- leveldb源码分析--WriteBatch
从[leveldb源码分析--插入删除流程]和WriteBatch其名我们就很轻易的知道,这个是leveldb内部的一个批量写的结构,在leveldb为了提高插入和删除的效率,在其插入过程中都采用了批 ...
- leveldb源码分析--Key结构
[注]本文参考了sparkliang的专栏的Leveldb源码分析--3并进行了一定的重组和排版 经过上一篇文章的分析我们队leveldb的插入流程有了一定的认识,而该文设计最多的又是Batch的概念 ...
- leveldb源码分析--SSTable之block
在SSTable中主要存储数据的地方是data block,block_builder就是这个专门进行block的组织的地方,我们来详细看看其中的内容,其主要有Add,Finish和CurrentSi ...
- Leveldb源码分析--1
coming from http://blog.csdn.net/sparkliang/article/details/8567602 [前言:看了一点oceanbase,没有意志力继续坚持下去了,暂 ...
- leveldb源码分析--日志
我们知道在一个数据库系统中为了保证数据的可靠性,我们都会记录对系统的操作日志.日志的功能就是用来在系统down掉的时候对数据进行恢复,所以日志系统对一个要求可靠性的存储系统是极其重要的.接下来我们分析 ...
- leveldb源码分析之Slice
转自:http://luodw.cc/2015/10/15/leveldb-02/ leveldb和redis这样的优秀开源框架都没有使用C++自带的字符串string,redis自己写了个sds,l ...
- LevelDB源码分析--Cache及Get查找流程
本打算接下来分析version相关的概念,但是在准备的过程中看到了VersionSet的table_cache_这个变量才想起还有这样一个模块尚未分析,经过权衡觉得leveldb的version相对C ...
- leveldb源码分析--SSTable之TableBuilder
上一篇文章讲述了SSTable的格式以后,本文结合源码解析SSTable是如何生成的. void TableBuilder::Add(const Slice& key, const Slice ...
- leveldb源码分析之内存池Arena
转自:http://luodw.cc/2015/10/15/leveldb-04/ 这篇博客主要讲解下leveldb内存池,内存池很多地方都有用到,像linux内核也有个内存池.内存池的存在主要就是减 ...
随机推荐
- Direct3D11学习:(一)开发环境配置
转载请注明出处:http://www.cnblogs.com/Ray1024 从今天开始,开启一个新的系列:Direct3D11的学习教程. 因为一直对3D方面比较感兴趣,最近决定开始学习D3D知 ...
- 谈HTTP的KeepAlive
为什么要使用KeepAlive? 终极的原因就是需要加快客户端和服务端的访问请求速度.KeepAlive就是浏览器和服务端之间保持长连接,这个连接是可以复用的.当客户端发送一次请求,收到相应以后,第二 ...
- 专题——web.xml 中 url-pattern
一.映射什么? 一个请求发送到 servlet 容器,servlet 容器会将当前请求的 url 路径减去 协议.端口号.contextPath,剩下 servletPath 就是用来做 url-pa ...
- Linux各版本的本地root密码破解方法
(一)RedHat/CentOS/Fedora 系统密码破解 1.在grub选项菜单按E进入编辑模式 2.编辑kernel 那行最后加上S (或者Single) 3.按B,启动到single-user ...
- GridView如何实现双击行进行编辑,更新
虽然标题是原创,但是其实主要的思想呢还是接见了晓风残月的思路,今天在晓风残月的博客上看到了如何利用GridView来实现双击进行编辑.我决定动手实现一下,由于还没有实现双击进行更改操作,所以顺便就把这 ...
- C#为工作Sql而产生的字符串分割小工具(很实用,你值得拥有)
写在前面 为什么要写这个工具? 工作需要,拼接字符串头晕眼花拼接的,特别是in 查询,后面的参数太多,想在数据执行一些这个sql语句老费劲了. 看正文 工作所有的(后台)攻城狮们都会接触到sql语句, ...
- php中设定一个全局异常处理。全局catch。默认catch。默认异常处理
<?php function handleMissedException($e) { echo "Sorry, something is wrong. Please try again ...
- python学习笔记2(pycharm、数据类型)
Pycharm 的使用 IDE(Integrated Development Environ ment) :集成开发环境 Vim :经典的linux下的文本编辑器(菜鸟和大神喜欢使用) Emac ...
- If you insist running as root, then set the environment variable RUN_AS_USER=root...
版权声明:本文为博主原创文章,不经博主允许注明链接即可转载. If you insist running as root, then set theenvironment variable RUN_A ...
- Python参数组合
参数定义的顺序必须是:①必选参数.②默认参数.③可选参数.④命名关键字参数.⑤关键字参数 #a,b为必选参数:c为默认参数:args为可变参数:kw为关键字参数 def f1(a,b,c=0,*arg ...