leveldb 源码--总体架构分析

一 本文目的

对leveldb的总体设计框架分析(关于leveldb基本原理,此文不做阐述,读者可以自行检索文章阅读即可),对leveldb中底层数据存储数据格式,内存数据模型,compact,版本管理,快照等机制实现介绍以及整个leveldb实现源码中各文件的源码实现职责,方便快速对leveldb有个总体的掌握

二 各特性机制的实现

  1.leveldb的底层数据格式存储

leveldb底层数据格式,网上很多文章都有介绍,在此不做赘述,主要介绍一下上层怎么讲数据写入磁盘中。

   leveldb中k-v数据写入磁盘就是通过数据压缩写入的( CompactMemTable[内存压缩至sst] & BackgroundCompaction[层间sst文件压缩] ),上层都是通过TableBuilder对象来实现数据持久化的

主要的两个接口:

           # Add:将一行记录加入一块buffer中

#Finish:将k-v的记录,按照table的格式(生成filter,metaindex,index,footer 块),然后分配对各块写入到sst文件中

   compact的使用大致过程:

     1.压缩过程中使用迭代器模式,按序遍历输入需要压缩的sst文件中key-val键值对。

     2. 丢弃key过期记录(比如:一个key的多次修改,只保留最新的一条记录,另外还有快照,这是后话),对没有过期的记录,将会调用TableBuilder.Add接口添加到TableBuilder的缓存区中,

     3. 直到缓存区中数据大小达到阈值(用户指定)。将会调用TableBuilder.Finish 生成sst表格式数据并持久化。

2. 内存数据模型

    内存模型是使用跳表的数据结构的方式来进行管理维护的。

    # 跳表在leveldb中定义的是一个通用的数据结构,需要外部传入节点key的compare对象。

    #MemTable 使用组合的方式使用跳表,自定义compare对象。

    # 其中需要注意有意思的一点MemTable 存储到跳表结构中的节点是包含(key+val),跳表中key值得比较对象是通过外部定义传入的,MemTable使用的是下面的比较函数,从函数中可以看出需要对节点提取真正的userKey进行比较。

    

3 Compact过程: 

compact主要分为一下几步

1.选择需要compact的level,通过VersionSet.PickCompaction函数来决定需要compact哪一层,改层哪些文件需要compact

     #至于为什么由VersionSet来决定,是因为VersionSet管理当前整个leveldb的文件组织结构等信息,后面再版本管理中会进行详细说明

2.对需要compact的文件建立迭代器,迭代器按key的排序依次访问所有的记录

3.依次遍历所有的记录,判断需要丢弃记录,对需要保留的记录,会调用TableBuilder.Add接口加入sst缓存区中

    记录丢弃的判断条件 

#同一个key非第一次出现,并且记录对应的序列号小于最旧的快照的序列号(说明该记录不需要快照备份)

# 该key值插入一个删除操作,并且不需要快照备份

4.sst缓存区写满,就会生成一个完整的sst文件格式,然后持久化到磁盘上

     5.将压缩过程中涉及到的文件变更(例如:老sst删除,新sst生成)加入到版本管理中的version_edit中

#注:此处不涉及老sst文件的清理,只是记录当前一次compact操作导致哪些文件需要被删除,真正删除操作由其他过程执行

6.删除过期文件

# 当前compact出来的新sst文件 & 所有版本version管理的sst文件,都会加入livefiles集合中

#不在livefiles集合中的文件全部认为是过期文件,需要删除。

4. 版本管理

   版本管理是leveldb中极其重要的模块,要想理解整个leveldb必须理解其为什么需要 & 如何实现版本管理的

   4.1 为什么需要版本管理

1.假设一种场景:一个用户发起对某个sst文件读取操作,数据读取到了一半,此时compact完成,由于compact是独立的一个线程,此时sst文件会被清理掉了,此时用户读操作出错

    所以一句话就是:管理磁盘上的文件,保证leveldbdb数据的准确性

4.2 如何实现版本管理

     基本概念:

      version :一个version对应一次数据文件变更的记录,比如compact会导致文件发现变化(老的sst文件,新生成sst文件),所以需要记录当前compact过程结束后,在当前数据库状态下有哪些文件,

        #主要数据结构:std::vector<FileMetaData*> files_[config::kNumLevels];记录管理每一层sst文件

      versionSet:由于每一次compact后都会产生一个version,所以需要将这些version管理起来,采用双向链表version按先后生成的顺序管理起来

      versionEdit:变化增量,每次compact时记录增量,主要是增加了哪些文件,需要删除哪些文件,通过上一次Version+versionEdit就会得到当前的Version

        #主要数据结构:

#DeletedFileSet deleted_files_;

#std::vector<std::pair<int, FileMetaData>> new_files_;

实现大致思路是:

      每次在compact后生成versionEdit,然后通过调用versionSet.LogAndApply将versionEdit应用到当前版本生成最新的版本,然后加入versionSet链表中成为当前版本。

    

# 几个问题:  

      1.版本管理的使用场景

        在每次对文件的使用(比如读)都会获取当前版本,然后会将该版本的引用计数+1,(读)完成后会将引用计数-1

       2.版本什么时候会被删除

         当一个版本没有了引用时,系统会自动从versinSet的双向链表中摘除掉

       3.系统中每一次compact就会生成一个版本,是不是意味着compact多少次就会有多少次版本呢

         问题2中已经表明,一个版本的存活周期在于版本是否存在引用计数,如果没有了引用会立马被删除

    

  5.快照机制

     # 管理数据结构:快照双向链表,将所有的快照使用双向链表维护管理起来
     # 快照数据结构主要字段
          SnapshotImpl* prev_; //链表相关
           SnapshotImpl* next_; //链表相关
           const SequenceNumber sequence_number_; //快照的sequence_number_
      #实现的主要思想:
      对于一个快照,获取快照的sequence_number_,读取key时,可以获取key的多次插入的值,其中获取seq不大于sequence_number_的最大的seq的val。
                #如何保证快照的记录不被删除呢?
                     所有删除的过程只有compact时进行,compact逻辑中,遍历所有需要压缩的文件的key值,会判断该key能否丢弃
                     其中有一个条件是,如果当前key有快照引用,就不能compact掉,从而保证打了快照的数据不会被丢弃。

 三 源码结构

  主要通过源码的目录结构以及阐述关键目录和源文件的职责的方式来展示源码的整个架构。

    cmake:cmake的相关文件

    db:主要机制的实现,包括版本管理,compact,业务读写等功能机制实现;

    doc:文档;
    helpers/memenv:简单完全内存的文件系统,提供操作目录文件接口;
    include/leveldb:头文件,外部工程使用leveldb时引用的头文件;
    port:平台相关的实现,主要提供posix/android相关支持;
    table:定义了整个leveldb的持久化存储的数据结构
    util:通用功能实现。

主要介绍的是db & table,这两部分是整个leveldb的精髓

  table:完成了整个leveldb持久化层的数据格式的定义以及实现

    # block + block_builder  :定义了block格式以及block如何生成的实现,包括block块中重启点等技术细节,将block的访问抽象成对迭代器模式的访问

              # filter_block:定义了过滤器的实现

    # two_level_iterator & iterator_wrapper & iterator & merger & two_level_iterator:定义了各种迭代器,从而屏蔽底层数据访问细节

      #其中two_level_iterator:封装了索引迭代器和数据迭代器的操作,本质上是一个二重循环,来实现key的有序遍历

      for(遍历索引){

        for(遍历当前索引指向的block)

      }

      # table & table_build:定义了sst文件的数据格式以及如何生成sst的过程

  db:实现了包括上面提到的各种机制,主要包括版本管理,compact,容灾恢复等具体的实现细节

    #db_impl:定义个数据库的各种接口以及compact等数据库特性

    #log_format & log_reader & log_writer:定义了log文件格式和读写,此处的log文件用来实现备份容灾的功能的

#memtable & skiplist:定义了leveldb如何使用跳表来实现数据在内存中的有序存储

    #snapshot:负责管理快照,使用链表的方式对快照进行管理

    #version_edit & version_set:负责版本管理的操作

    

             

    

   

      

原文地址:https://www.cnblogs.com/chenhao-zsh/p/11616838.html

时间: 2024-08-27 10:57:06

leveldb 源码--总体架构分析的相关文章

leveldb源码分析--SSTable之逻辑结构

SSTable是leveldb 的核心模块,这也是其称为leveldb的原因,leveldb正是通过将数据分为不同level的数据分为对应的不同的数据文件存储到磁盘之中的.为了理解其机制,我们首先看看SSTable中的基本概念. 首先看看数据的整体存储结构: 可以从图中看到了几个概念:Datablock,Metablock, MetaIndex block, Indexblock, Footer.具体他们的含义可以大致解释如下: 1. Datablock,我们知道文件中的k/v对是有序存储的,他

LevelDB源码分析--Iterator

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

leveldb源码分析--Memtable

本节讲述内存中LevelDB的数据结构Memtable,Memtable义如其名即为内存中的KV Table,即LSM-Tree中的C0 Tree.我们知道在LSM-Tree中刚插入的的KV数据都是存储在内存中,当内存中存储的数据超过一定量以后再写到磁盘中.而对于leveldb来说这个过程演变为内存中的数据都是插入到MemTable中,当MemTable中的数据超过一定量(Options.write_buffer_size)以后MemTable就转化为Immutable Memtable等待du

leveldb源码分析--SSTable之TableBuilder

上一篇文章讲述了SSTable的格式以后,本文结合源码解析SSTable是如何生成的. void TableBuilder::Add(const Slice& key, const Slice& value) { //如果已经插入过数据,那么要保证当前插入的key > 之前最后一次插入的key, // SSTable必须是有序的插入数据 if (r->num_entries > 0) { assert(r->options.comparator->Compar

LevelDB源码分析--Cache及Get查找流程

本打算接下来分析version相关的概念,但是在准备的过程中看到了VersionSet的table_cache_这个变量才想起还有这样一个模块尚未分析,经过权衡觉得leveldb的version相对Cache来说相对复杂,而且version虽然对整个leveldb来说实现上跟其他功能十分紧密,但是从概念上来说却相对弱很多,有点感觉是附加的功能的感觉.所以从介绍系统首先应该注意的是整个系统概念的完整性的角度说还是先分析Cache相关的功能. 我们先来看Cache的基本框架结构数据: struct

leveldb源码分析--SSTable之Compaction

对于compaction是leveldb中体量最大的一部分,也应该是最为复杂的部分,为了便于理解我们首先从一些基本的概念开始.下面是一些从doc/impl.html中翻译和整理的内容: Level 0 当日志文件超过一定大小的阈值是 (默认为 1MB): 建立一个新的memtable和日志文件,以后的操作都是用新的memtable和日志文件 后台进行如下操作: 将旧的 memtable写到SSTable中(过程为先转为immtable_table,然后遍历写入) 废弃旧的 memtable 删除

leveldb源码分析--BloomFilter

bloomfilter是leveldb中的一大性能利器,所以为了文章的表现完整性这里新启这么一篇文章.leveldb中的bloomfilter的实现在bloom.cc中,是一个较为简单的实现,所以就不再具体进行分析.本文列出两个参考地址: 那些优雅的数据结构(1) : BloomFilter——大规模数据处理利器 是一个简单的bloomfilter的介绍乐和实现 Bloom Filter  则是一位专业的bloomfilter的研究人士的博客,内容及其的牛逼,对此感兴趣的阅读以下其文章应该有不小

leveldb源码分析--SSTable之block

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

leveldb源码分析--日志

我们知道在一个数据库系统中为了保证数据的可靠性,我们都会记录对系统的操作日志.日志的功能就是用来在系统down掉的时候对数据进行恢复,所以日志系统对一个要求可靠性的存储系统是极其重要的.接下来我们分析leveldb的日志,首先在leveldb源码目录中有doc/log_format.txt,这个文件详细的描述了leveldb的日志格式: record := checksum: uint32 // crc32c of type and data[] ; little-endian length: