作者:Jack47

上一篇SSTable 介绍(一)介绍了SSTable的适用场景和leveldb中SSTable的设计。本篇介绍SSTable文件的结构组成。

SSTable的特点

首先明确一下上文中提到的SSTable特点:

  1. 需要存储的格式的字节数据
  2. 键可以重复,键值对不需要对齐,即可以是任意长度的
  3. 需要支持高效的随机读取操作

读者们不妨想一想,如果让你设计一种数据结构来支持上述的设计目标,你会怎么设计呢?

SSTable文件的结构

一些辅助的类和结构体

在去看leveldb/table/table.cc文件内容时,经常会看到以下的几个类或者结构体,他们是对SSTable文件底层内容的抽象,它们使用起来像积木一样方便,能够帮助上层调用者关注底层的抽象,而且类型的名称取的也很到位,让代码阅读者很容易理解其用途。

BlockHandle:

BlockHandle的结构如下图所示:

BlockHandle的结构和其指向的文件数据

BlockHandle是指向文件中一部分连续数据的指针,顾名思义,可以指向一个数据块[data block]或者元信息块[meta block]。其实就是存储了两个成员变量:代表文件偏移的uint64_t offset_和代表数据长度的uint64_t size_。这样有了数据的起始地址和数据的长度,就可以定位到这片数据了。从其BlockHandle.Encode函数的实现可以知道总共需要占用16个字节就可以存储它序列化后的数据。

Slice:

Slice的结构如下图所示:

Slice和其指向的数据空间

Slice是一个简单的类,但是发挥的作用十分强大,可以用它来方便地指向一片连续存储的数据,可以认为指向的就是一个数组。它存储了指向的数据的起始地址和数据的字节长度。想想这样操作数据是不是就十分简洁了,再也不需要配套的两个变量来表示数据的起始地址和数据的大小了,使用 slice.data()拿到数组首地址,使用slice.size()拿到数组的长度,还有 slice.empty()来判断数组是否为空。需要注意的是这个类只是引用了一个数组,数组的存储空间由调用者管理,调用者必须保证在引用的数据被释放后,对应的slice对象不再使用。

BlockContents:

代表从文件中读取到的每一块[block]的数据的信息,包含实际的数据[Slice data],是否可以缓存[cachable]和是否是在堆上新申请的[heap_allocated]这三个属性。

SSTable存储到磁盘上的数据是支持压缩的,如果磁盘上的数据是经过压缩之后的,那就需要在从磁盘上读取的时候解压缩,此时BlockContents存储的是解压缩之后的数据,是在读取块数据时从堆上新申请的。heap_allocated就是用来标示这种情况,这片内存需要调用者负责最终释放。

SSTable文件的布局

从leveldb/doc/table_format.txt中可以看到,一个SSTable文件的内部数据的布局如下图所示:

SSTable文件的布局示意图

下文中提到的这些块数据是根据block_builder.cc中的代码来生成的。

  • 数据块[data block]

文件中键值对key/value是有序存储的,被划分成一系列的数据块[data block]。这些块在文件的开头一块紧挨一块地存储起来。可以选择开启压缩选项来压缩数据之后再存储到数据块里。

可见数据块的存储方式就跟数组一样,是连续存储

  • 元信息块[meta block]

在数据块之后,存储的是一些元信息块。每个元信息块数据也是由block_builder.cc中的代码生成的,也是支持压缩选项。可见元信息块的存储方式也跟数据块类似。

  • 元索引数据块[metaindex block]

为每个元信息块生成一条记录,记录的key是元信息块的名称,记录的值是指向元信息块的BlockHandle

可见每个元信息块中存储的数据用途不一样,而且一个块大小就完全能够存储的下。否则需要多个块,那记录的key就不能取元信息块的名称了。由于元信息有好几类,所以需要在这里存储给各个元信息块建立的索引。

  • 索引块[index block]

这个块包含了为每个数据块生成的索引记录,key是一个>=这数据块中最后一个key并小于后续数据块首个key的字符串,value是指向数据块的BlockHandle。

可以看到上述的每种类型的块数据,都可以用一个BlockHandle来表示。

  • Footer

每个文件尾部都包含一个固定长度[40 Bytes]的Footer,它包含了指向元索引块[metaindex block]和索引块[index block]的两个BlockHandle,以及一个8字节的magicNumber。

默认块的大小是大概是未压缩的4KB字节的数据。块的大小可以根据应用程序使用SSTable数据的方式来调整,例如主要是对数据库内容进行批量扫描的应用,可以适当调大块的大小;对于大量读取小数据的应用,可以调小块的大小来看看效率是否提升。

第一次接触这种类型的文件布局,可能很难理解上述的各个组成部分,可以尝试这样去理解:

  1. 由于机械磁盘本身是物理分块的[固态硬盘除外],所以SSTable也参照磁盘分块大小来进行数据分块,能够尽量减少访问磁盘的开销,而且即使有数据毁坏,也会局限在几个块上,不会影响其他块数据的读取。
  2. 为这些数据/元信息块建立索引,就类似给书籍制作目录页一样,便于读取某个key时,可以快速定位key落在哪个数据块上,而不需要顺序遍历SSTable的每个块。
  3. Footer相当于是最高一级的索引,从这里开始,就能够获取到SSTable所有的信息。因此把它设计成固定大小,这样从磁盘上反序列化SSTable时,首先把Footer读取出来,然后按图索骥,就可以把其他的块也加载到内存里。

上面提到了meta block,目前支持的meta block有以下两种:

  1. "过滤“元信息块["filter" Meta Block]

当数据库创建的时候指定了“过滤策略”,那么每个SSTable会存储一个过滤块[filter block][过滤策略所需的数据不超过一个块的大小]。“元索引”块包含一条记录,映射了从"filter."到指向过滤块的BlockHandle,这里""是过滤策略的"Name()"函数返回的字符串。

过滤块存储了一系列过滤器,其中filter i 包含了对一个文件偏移落于范围[ibase ... (i+1)base-1]上的数据块所存储的所有key执行FilterPolicy::CreateFilter()后的输出。

目前,“base”是2KB。所以举个例子,当块X和Y起始于范围[0KB .. 2KB-1],所有的X和Y的key都会通过调用FilterPolicy::CreateFilter()来被转换到同一个过滤器里,同时这个过滤器会被存储为过滤块里的第一个过滤器。
由于过滤串的长度不一,所以需要存储每个过滤器的起始地址。过滤块的格式见上图。

  1. “统计”元信息["stats" Meta Block]

这一块包含了一些统计信息。key是统计数据的名称。值是统计数字。
记录了如下统计信息:

数据大小

索引大小

键大小(未压缩)

值大小(未压缩)

记录的数量

数据块的数量

剩下还有一些细节没有介绍,例如索引块、数据块内部的格式,数据块的读取操作等,留待SSTable介绍系列的第三篇,也是最后一篇来介绍。

回到本系列目录:leveldb源码学习系列

SSTable 介绍(二)的更多相关文章

  1. Lucene.Net 2.3.1开发介绍 —— 二、分词(六)

    原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(六) Lucene.Net的上一个版本是2.1,而在2.3.1版本中才引入了Next(Token)方法重载,而ReusableStrin ...

  2. Lucene.Net 2.3.1开发介绍 —— 二、分词(五)

    原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(五) 2.1.3 二元分词 上一节通过变换查询表达式满足了需求,但是在实际应用中,如果那样查询,会出现另外一个问题,因为,那样搜索,是只 ...

  3. Lucene.Net 2.3.1开发介绍 —— 二、分词(三)

    原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(三) 1.3 分词器结构 1.3.1 分词器整体结构 从1.2节的分析,终于做到了管中窥豹,现在在Lucene.Net项目中添加一个类关 ...

  4. Lucene.Net 2.3.1开发介绍 —— 二、分词(四)

    原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(四) 2.1.2 可以使用的内置分词 简单的分词方式并不能满足需求.前文说过Lucene.Net内置分词中StandardAnalyze ...

  5. Lucene.Net 2.3.1开发介绍 —— 二、分词(二)

    原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(二) 1.2.分词的过程 1.2.1.分词器工作的过程 内置的分词器效果都不好,那怎么办?只能自己写了!在写之前当然是要先看看内置的分词 ...

  6. Lucene.Net 2.3.1开发介绍 —— 二、分词(一)

    原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(一) Lucene.Net中,分词是核心库之一,当然,也可以将它独立出来.目前Lucene.Net的分词库很不完善,实际应用价值不高.唯 ...

  7. {Django基础十之Form和ModelForm组件}一 Form介绍 二 Form常用字段和插件 三 From所有内置字段 四 字段校验 五 Hook钩子方法 六 进阶补充 七 ModelForm

    Django基础十之Form和ModelForm组件 本节目录 一 Form介绍 二 Form常用字段和插件 三 From所有内置字段 四 字段校验 五 Hook钩子方法 六 进阶补充 七 Model ...

  8. MySQL之多表查询一 介绍 二 多表连接查询 三 符合条件连接查询 四 子查询 五 综合练习

    MySQL之多表查询 阅读目录 一 介绍 二 多表连接查询 三 符合条件连接查询 四 子查询 五 综合练习 一 介绍 本节主题 多表连接查询 复合条件连接查询 子查询 首先说一下,我们写项目一般都会建 ...

  9. MySQL行(记录)的详细操作一 介绍 二 插入数据INSERT 三 更新数据UPDATE 四 删除数据DELETE 五 查询数据SELECT 六 权限管理

    MySQL行(记录)的详细操作 阅读目录 一 介绍 二 插入数据INSERT 三 更新数据UPDATE 四 删除数据DELETE 五 查询数据SELECT 六 权限管理 一 介绍 MySQL数据操作: ...

随机推荐

  1. [13]APUE:KQUEUE / FreeBSD

    [a] 概述 kqueue API 由两个函数(kqueue.kevent).一个辅助宏(EV_SET).一个结构体(struct kevent)构成,可以应用于 socket.FIFO.pipe.a ...

  2. vs2015 附加到进程找不到w3wp.exe

    vs2015 附加到进程找不到w3wp.exe 解决办法: 浏览器打开你访问的IIS地址后就出现了~!!!!!!!!!

  3. 如何辨别具体的一种SaaS是否安全?

    如何辨别具体的一种SaaS是否安全,需要把握以下几点: 1.传输协议加密 首先,要看SaaS产品提供使用的协议,是https://还是一般的http://,别小看这个s,这表明所有的数据在传输过程中都 ...

  4. 初识C语言

    C语言是开发iOS软件的基础. 一.C语言简介 1.      简史 1)          C语言于1972年发明,首次使用是用于重写UINX操作系统(UNIX以前主要是用汇编语言写的,它奠定了操作 ...

  5. ZOJ3795_Grouping

    告诉你某些人的年龄大小关系,问你把所有的人分成若干个组,最少需要多少组,使得组内任意两个人的年龄不可比. 首先考虑特殊情况,如果所有年龄关系构成了一个环,那么这个环中所有人的年龄都是相等,也就是可比的 ...

  6. 【转载】Java中的回车换行符/n /r /t

    source:http://hane00.blog.163.com/blog/static/1600615220126204446809/ '\r'是回车,'\n'是换行,前者使光标到行首,后者使光标 ...

  7. 查找原始MySQL死锁ID

    转载地址:http://yueliangdao0608.blog.51cto.com/397025/1180917 如果遇到死锁了,怎么解决呢?找到原始的锁ID,然后KILL掉一直持有的那个线程就可以 ...

  8. 编译器工具 Flex Bison for Windows 简单入门例子

    最近从事一个系统仿真软件的开发,里面定义了自己的描述性语言MSL, MSL语言经FlexBison转换成C语言,然后用C编译器来编译并计算仿真. 现在领域驱动开发比较热门,有机会定义自己的语言对程序员 ...

  9. First Missing Positive && missing number

    https://leetcode.com/problems/first-missing-positive/ 我原以为数组中不会有重复的数字,所以利用min.max分别记录给定数组中出现的最小正整数和最 ...

  10. ubuntu上搭建review board代码评审站点

    Reviewboard是一个开源个人可以免费使用的代码评审框架,貌似现在有越来越多的公司也开始使用reviewboard作为公司的代码评审工具. 今天早上试了一下,搭建过程非常方便简单,按照网页提示即 ...