levelDB Block

http://blog.csdn.net/sparkliang/article/details/8635821

BlockBuilder的接口

首先从Block的构建开始,这就是BlockBuilder类,来看下BlockBuilder的函数接口,一共有5个:

[cpp] view plaincopy

  1. void Reset(); // 重设内容,通常在Finish之后调用已构建新的block
  2. //添加k/v,要求:Reset()之后没有调用过Finish();Key > 任何已加入的key
  3. void Add(const Slice& key,const Slice& value);
  4. // 结束构建block,并返回指向block内容的指针
  5. Slice Finish();// 返回Slice的生存周期:Builder的生存周期,or直到Reset()被调用
  6. size_t CurrentSizeEstimate()const; // 返回正在构建block的未压缩大小—估计值
  7. bool empty() const { returnbuffer_.empty();} // 没有entry则返回true

主要成员变量如下:

[cpp] view plaincopy

  1. std::string            buffer_; // block的内容
  2. std::vector<uint32_t>  restarts_;  // 重启点-后面会分析到
  3. int                  counter_;  // 重启后生成的entry数
  4. std::string            last_key_; // 记录最后添加的key

6.3.2 BlockBuilder::Add()

调用Add函数向当前Block中新加入一个k/v对{key, value}。函数处理逻辑如下:

S1 保证新加入的key > 已加入的任何一个key;

[cpp] view plaincopy

  1. assert(!finished_);
  2. assert(counter_ <= options_->block_restart_interval);
  3. assert(buffer_.empty() || options_->comparator->Compare(key,last_key_piece) > 0);

S2 如果计数器counter < opions->block_restart_interval,则使用前缀算法压缩key,否则就把key作为一个重启点,无压缩存储;

[cpp] view plaincopy

  1. Slice last_key_piece(last_key_);
  2. if (counter_ < options_->block_restart_interval) { //前缀压缩
  3. // 计算key与last_key_的公共前缀
  4. const size_t min_length= std::min(last_key_piece.size(), key.size());
  5. while ((shared < min_length)&& (last_key_piece[shared] == key[shared])) {
  6. shared++;
  7. }else{ // 新的重启点
  8. restarts_.push_back(buffer_.size());
  9. counter_ = 0;
  10. }

S3根据上面的数据格式存储k/v对,追加到buffer中,并更新block状态。

[cpp] view plaincopy

  1. const size_t non_shared = key.size() - shared; // key前缀之后的字符串长度
  2. // append"<shared><non_shared><value_size>" 到buffer_
  3. PutVarint32(&buffer_, shared);
  4. PutVarint32(&buffer_, non_shared);
  5. PutVarint32(&buffer_, value.size());
  6. // 其后是前缀之后的字符串 + value
  7. buffer_.append(key.data() + shared, non_shared);
  8. buffer_.append(value.data(), value.size());
  9. // 更新状态 ,last_key_ = key及计数器counter_
  10. last_key_.resize(shared);   // 连一个string的赋值都要照顾到,使内存copy最小化
  11. last_key_.append(key.data() + shared, non_shared);
  12. assert(Slice(last_key_) == key);
  13. counter_++;

6.3.3 BlockBuilder::Finish()

调用该函数完成Block的构建,很简单,压入重启点信息,并返回buffer_,设置结束标记finished_:

[cpp] view plaincopy

  1. for (size_t i = 0; i < restarts_.size(); i++) {  // 重启点
  2. PutFixed32(&buffer_, restarts_[i]);
  3. }
  4. PutFixed32(&buffer_, restarts_.size());    // 重启点数量
  5. finished_ = true;
  6. return Slice(buffer_);

6.3.4 BlockBuilder::Reset() & 大小

还有Reset和CurrentSizeEstimate两个函数,Reset复位函数,清空各个信息;函数CurrentSizeEstimate返回block的预计大小,从函数实现来看,应该在调用Finish之前调用该函数。

[cpp] view plaincopy

  1. void BlockBuilder::Reset() {
  2. buffer_.clear();  restarts_.clear();  last_key_.clear();
  3. restarts_.push_back(0);       // 第一个重启点位置总是 0
  4. counter_ = 0;
  5. finished_ = false;
  6. }
  7. size_t BlockBuilder::CurrentSizeEstimate () const {
  8. // buffer大小 +重启点数组长度 + 重启点长度(uint32)
  9. return (buffer_.size() +  restarts_.size() * sizeof(uint32_t) + sizeof(uint32_t));
  10. }

Block的构建就这些内容了,下面开始分析Block的读取,就是类Block。

6.3.5 Block类接口

对Block的读取是由类Block完成的,先来看看其函数接口和关键成员变量。

Block只有两个函数接口,通过Iterator对象,调用者就可以遍历访问Block的存储的k/v对了;以及几个成员变量,如下:

[cpp] view plaincopy

  1. size_t size() const { returnsize_; }
  2. Iterator* NewIterator(constComparator* comparator);
  3. const char* data_; // block数据指针
  4. size_t size_;      // block数据大小
  5. uint32_t restart_offset_;     // 重启点数组在data_中的偏移
  6. bool owned_;              //data_[]是否是Block拥有的

6.3.6 Block初始化

Block的构造函数接受一个BlockContents对象contents初始化,BlockContents是一个有3个成员的结构体。

[cpp] view plaincopy

  1. >data = Slice();
  2. >cachable = false; // 无cache
  3. >heap_allocated = false; // 非heap分配
  4. 根据contents为成员赋值
  5. data_ = contents.data.data(), size_ =contents.data.size(),owned_ = contents.heap_allocated;

然后从data中解析出重启点数组,如果数据太小,或者重启点计算出错,就设置size_=0,表明该block data解析失败.

[cpp] view plaincopy

  1. if (size_ < sizeof(uint32_t)){
  2. size_ = 0;  // 出错了
  3. } else {
  4. restart_offset_ = size_ - (1 +NumRestarts()) * sizeof(uint32_t);
  5. if (restart_offset_ > size_- sizeof(uint32_t)) size_ = 0;
  6. }

NumRestarts()函数就是从最后的uint32解析出重启点的个数,并返回:

return DecodeFixed32(data_ +size_ - sizeof(uint32_t))

6.3.7 Block::Iter

这是一个用以遍历Block内部数据的内部类,它继承了Iterator接口。函数NewIterator返回Block::Iter对象:return new Iter(cmp, data_,restart_offset_, num_restarts);

下面我们就分析Iter的实现。

主要成员变量有:

[cpp] view plaincopy

  1. const Comparator* constcomparator_; // key比较器
  2. const char* const data_;      // block内容
  3. uint32_t const restarts_;     // 重启点(uint32数组)在data中的偏移
  4. uint32_t const num_restarts_; // 重启点个数
  5. uint32_t current_; // 当前entry在data中的偏移.  >= restarts_表明非法
  6. uint32_t restart_index_;  // current_所在的重启点的index

下面来看看对Iterator接口的实现,简单函数略过。

>首先是Next()函数,直接调用private函数ParseNextKey()跳到下一个k/v对,函数实现如下:

S1 跳到下一个entry,其位置紧邻在当前value_之后。如果已经是最后一个entry了,返回false,标记current_为invalid。

[cpp] view plaincopy

  1. current_ = NextEntryOffset(); // (value_.data() + value_.size()) - data_
  2. const char* p = data_ +current_;
  3. const char* limit = data_ +restarts_; // Restarts come right after data
  4. if (p >= limit) { // entry到头了,标记为invalid.
  5. current_ = restarts_;
  6. restart_index_ =num_restarts_;
  7. return false;
  8. }

S2 解析出entry,解析出错则设置错误状态,记录错误并返回false。解析成功则根据信息组成key和value,并更新重启点index。

[cpp] view plaincopy

  1. uint32_t shared, non_shared,value_length;
  2. p = DecodeEntry(p, limit,&shared, &non_shared, &value_length);
  3. if (p == NULL || key_.size()< shared) {
  4. CorruptionError();
  5. return false;
  6. } else { // 成功
  7. key_.resize(shared);
  8. key_.append(p, non_shared);
  9. value_ = Slice(p +non_shared, value_length);
  10. while (restart_index_ + 1< num_restarts_ && GetRestartPoint(restart_index_ + 1) < current_) {
  11. ++restart_index_; //更新重启点index
  12. }
  13. return true;
  14. }

函数DecodeEntry从字符串[p, limit)解析出key的前缀长度、key前缀之后的字符串长度和value的长度这三个vint32值,代码很简单。

函数CorruptionError将current_和restart_index_都设置为invalid状态,并在status中设置错误状态。

函数GetRestartPoint从data中读取指定restart index的偏移值restart[index],并返回:DecodeFixed32(data_ + restarts_ +index * sizeof(uint32_t);

>接下来看看Prev函数,Previous操作分为两步:首先回到current_之前的重启点,然后再向后直到current_,实现如下:

S1首先向前回跳到在current_前面的那个重启点,并定位到重启点的k/v对开始位置。

[cpp] view plaincopy

  1. const uint32_t original =current_;
  2. while (GetRestartPoint(restart_index_)>= original) {
  3. if (restart_index_ == 0) { // 到第一个entry了,标记invalid状态
  4. current_ = restarts_;
  5. restart_index_ =num_restarts_;
  6. return;
  7. }
  8. restart_index_--;
  9. }
  10. SeekToRestartPoint(restart_index_);//根据restart index定位到重启点的k/v对

S2 第二步,从重启点位置开始向后遍历,直到遇到original前面的那个k/v对。

do {} while (ParseNextKey() &&NextEntryOffset() < original);

说说上面遇到的SeekToRestartPoint函数,它只是设置了几个有限的状态,其它值将在函数ParseNextKey()中设置。感觉这有点tricky,这里的value_并不是k/v对的value,而只是一个指向k/v对起始位置的0长度指针,这样后面的ParseNextKey函数将会取出重启点的k/v值。

[cpp] view plaincopy

  1. void SeekToRestartPoint(uint32_tindex) {
  2. key_.clear();
  3. restart_index_ = index;
  4. // ParseNextKey()会设置current_;
  5. //ParseNextKey()从value_结尾开始, 因此需要相应的设置value_
  6. uint32_t offset =GetRestartPoint(index);
  7. value_ = Slice(data_ + offset,0); // value长度设置为0,字符串指针是data_+offset
  8. }

> SeekToFirst/Last,这两个函数都很简单,借助于前面的SeekToResartPoint函数就可以完成。

[cpp] view plaincopy

  1. virtual void SeekToFirst() {
  2. SeekToRestartPoint(0);
  3. ParseNextKey();
  4. }
  5. virtual void SeekToLast() {
  6. SeekToRestartPoint(num_restarts_ - 1);
  7. while (ParseNextKey()&& NextEntryOffset() < restarts_) {} //Keep skipping
  8. }

> 最后一个Seek函数,跳到指定的target(Slice),函数逻辑如下:

S1 二分查找,找到key < target的最后一个重启点,典型的二分查找算法,代码就不再贴了。

S2 找到后,跳转到重启点,其索引由left指定,这是前面二分查找到的结果。如前面所分析的,value_指向重启点的地址,而size_指定为0,这样ParseNextKey函数将会取出重启点的k/v值。

SeekToRestartPoint(left);

S3 自重启点线性向下,直到遇到key>= target的k/v对。

[cpp] view plaincopy

  1. while (true) {
  2. if (!ParseNextKey()) return;
  3. if (Compare(key_, target)>= 0) return;
  4. }

上面就是Block::Iter的全部实现逻辑,这样Block的创建和读取遍历都已经分析完毕。

levelDB Block

时间: 2024-10-15 12:25:20

levelDB Block的相关文章

leveldb源码分析--SSTable之block

在SSTable中主要存储数据的地方是data block,block_builder就是这个专门进行block的组织的地方,我们来详细看看其中的内容,其主要有Add,Finish和CurrentSizeEstimate三个函数.Finish的逻辑十分简单就是简单的将restart点信息和restart点个数分别以PutFixed32的格式写入数据最后:CurrentSizeEstimate则是简单的计算当前块需要的存储大小 = 已插入的KV对的大小 + 重启点个数 * 4 + 1 * 4(重启

Tair ldb(leveldb存储引擎)实现介绍

简介 tair 是淘宝自己开发的一个分布式 key/value 存储引擎. tair 分为持久化和非持久化两种使用方式. 非持久化的 tair 可以看成是一个分布式缓存. 持久化的 tair 将数据存放于磁盘中. 为了解决磁盘损坏导致数据丢失, tair 可以配置数据的备份数目, tair 自动将一份数据的不同备份放到不同的主机上, 当有主机发生异常, 无法正常提供服务的时候, 其于的备份会继续提供服务. tair 的总体结构 tair 作为一个分布式系统, 是由一个中心控制节点和一系列的服务节

LevelDB源码分析--Iterator

我们先来参考来至使用Iterator简化代码2-TwoLevelIterator的例子,略微修改希望能帮助更加容易立即,如果有不理解请各位看客阅读原文. 下面我们再来看一个例子,我们为一个书店写程序,书店里有许多书Book,每个书架(BookShelf)上有多本书. 类结构如下所示 class Book { private: string book_name_; }; class Shelf { private: vector<Book> books_; }; 如何遍历书架上所有的书呢?一种实

LevelDB Cache实现机制分析

几天前淘宝量子恒道在博客上分析了HBase的Cache机制,本篇文章,结合LevelDB 1.7.0版本的源码,分析下LevelDB的Cache机制. 概述 LevelDB是Google开源的持久化KV单机存储引擎,据称是HBase的鼻祖Bigtable的重要组件tablet的开源实现.针对存储面对的普遍随机IO问题,LevelDB采用merge-dump的方式,将逻辑场景的随机写请求转换成顺序写log和写memtable的操作,由后台线程根据策略将memtable持久化成分层的sstable.

leveldb与设计模式

Iterator模式: 提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示.leveldb中include/leveldb.h定义了iterator基类,访问某层sst.某个sst内部kv.某个memtable内部kv.整个DB内部kv都需要迭代器,都是通过继承iterator来实现自己的迭代器版本.这样做能将容器中遍历数据的职能和其他职能分离开来,遵守了单一职能原则:增加新的聚合类时,只需继承iterator基类实现新的迭代器版本,遵守了开闭原则.另外,SkipList

数据分析与处理之二(Leveldb 实现原理)

郑重声明:本篇博客是自己学习 Leveldb 实现原理时参考了郎格科技系列博客整理的,原文地址:http://www.samecity.com/blog/Index.asp?SortID=12,只是为了加深印象,本文的配图是自己重新绘制的,大部分内容与原文相似,大家可以浏览原始页面 :-),感兴趣的话可以一起讨论 Leveldb 的实现原理! LevelDb日知录之一:LevelDb 101 说起LevelDb也许您不清楚,但是如果作为IT工程师,不知道下面两位大神级别的工程师,那您的领导估计会

LevelDB源码之四LOG文件

“LOG文件在LevelDb中的主要作用是系统故障恢复时,能够保证不会丢失数据.因为在将记录写入内存的Memtable之前,会先写入Log文件,这样即使系统发生故障,Memtable中的数据没有来得及Dump到磁盘的SSTable文件,LevelDB也可以根据log文件恢复内存的Memtable数据结构内容,不会造成系统丢失数据,在这点上LevelDb和Bigtable是一致的.” (http://www.cnblogs.com/haippy/archive/2011/12/04/2276064

leveldb源码笔记

最近读了一下leveldb源码,leveldb最主要的操作就是get/set,因此从get/set的实现入手,了解一下实现机制. 之前也看过leveldb相关介绍以及别人的分析blog,已经有了一定了解.leveldb如其名,按照层级来组织数据,数据从内存到磁盘一层一层迁移.在内存中是通过skiplist来管理数据,而磁盘上则是一种名为SSTable(Sorted Strings Table)的结构来存储数据的. DB::Get实现 这个头文件include/leveldb/db.h定义了DB抽

[转载] leveldb日知录

原文: http://www.cnblogs.com/haippy/archive/2011/12/04/2276064.html 对leveldb非常好的一篇学习总结文章 郑重声明:本篇博客是自己学习 Leveldb 实现原理时参考了郎格科技系列博客整理的,原文地址:http://www.samecity.com/blog/Index.asp?SortID=12,只是为了加深印象,本文的配图是自己重新绘制的,大部分内容与原文相似,大家可以浏览原始页面 :-),感兴趣的话可以一起讨论 Level