天天看点

持久层和数据访问层_[LevelDB] 数据库1:台前幕后 —— 数据库层实现概览

持久层和数据访问层_[LevelDB] 数据库1:台前幕后 —— 数据库层实现概览

数据库1:台前幕后 —— 数据库层实现概览

我们人为将LevelDB分为了数据库层和存储引擎层,在前面部分介绍了存储引擎相关的组件,包括:

  • SSTable存储结构实现,提供了在一个磁盘文件里读取一个键的功能,并且可以迭代一个SSTable的所有键;
  • MemTable内存结构的实现,提供了读取一个键和写入一个键的功能,迭代MemTable里所有的键,以及可以将一个MemTable转换为一个SSTable;
  • Log将写入持久化到磁盘上面,将随机写入转换为顺序写入;
  • 迭代器对各种存储组件迭代,以及定位某一个键。

利用这些组件提供的功能,就可以实现数据库层。可以将数据库层的功能分为两个部分:

  • Get

    Put

    Delete

    接口实现,这里是LevelDB对外提供的操作接口,分别实现键值对的查找、插入和删除;
  • 版本管理和Compaction,随着数据的写入,不断的有MemTable转换为SSTable,当有些键不断的更新删除,有些Level的文件太多时,影响了读性能,需要进行Compaction,将低Level的SSTable Compaction到高Level的SSTable里去,提高读的效率。而Compaction是通过版本来管理的,当一次Compaction完成时,就会生成一个新版本。

接口实现

应用程序使用数据库其实就是操作这些接口,接口是看得见的部分,这些接口的实现反而是比较简单的部分。

先说

Put

操作,这个操作其实是最简单的,就是向MemTable插入一条记录,同时向Log插入一条记录作持久化。而

Put

不但是一个插入操作,也是一个更新操作,当想更新一个Kv时,只需要插入一个相同键的Kv即可,数据库里存在相同键的多个值,通过读取来找到正确的值。

Delete

操作和

Put

操作很像,当需要删除一个键,只需要写入包含这个键的一条记录,里面有一个标记,标记这个键为删除,当读取的时候读取到这条标识删除的记录时,就可以忽略之前的记录,返回这条记录不存在。

以上两个操作说明LevelDB并不会真的去更新或者删除一条记录,而只会插入记录,通过读取来合并多条记录。这样就不需要去修改一个SSTable,只需要顺序写数据,就减少了磁盘的随机读取和写入。

Put

Delete

数虽然很简单,

Get

的操作就相对复杂了。因为

Put

Delete

都是插入一条记录,所以可能同一个键有多条记录存在,但是只有最新的那条记录有效的。如果最新的记录是一条删除记录,说明这个键是不存在的,否则值就是最新记录的值。所以LevelDB在

Get

一个键时会按照从新到旧开始查询:

  • 首先查询MemTable里看键是否存在,因为MemTable里数据是最新的数据;
  • 如果一个MemTable写满了,会生成一个新的MemTable,而这个写满的MemTable的数据会转换为一个SSTable,但是在转换过程中这些数据可能被访问,这时候这个MemTable就会转换成一个Immutable MemTable,这里面的数据是次新的数据,如果Immutable MemTable存在,就要查找里面是否包含需要的Kv;
  • 如果上面两个步骤都都没查找键的话,就要查找SSTable了,SSTable先被写到Level 0,然后再被Compaction到更高的层,所以Level越低,数据越新,就从Level 0开始查找,直到找到对应的键。

版本管理和Compaction

这一部分实际上是后台线程干的事情,是看不到的部分。

随着数据的不断写入,不断地将MemTable转换为SSTable,而这些SSTable都在Level 0。而Level 0和其它Level相比,具有特殊性,其它Level里SSTable的数据都是不重叠的,也就是当需要查找一个键时,可以找到唯一的一个SSTable,这个键只有可能在这个SSTable里,而Level 0的SSTable的键是有重叠的,也就是查找一个键时,这个键可能在多个SSTable里,就需要搜索多个SSTable。所以如果Level 0的SSTable太多了,

Get

的效率就会非常低。

对于更新和删除,是写入一条记录,如果对同一个键作多次更新和删除,那么只有最新的数据是有用的,这样就会存在大量冗余数据,浪费磁盘空间,并且降低了读取的效率。

针对以上两个问题,需要Compaction,Compaction就是将低Level的SSTable做归并移到高的Level,并且消除冗余的键值对。

LevelDB是支持快照的,也就是可以在某个时刻访问数据时,做一个快照,然后只能看到那个时刻的数据,而在那个时刻后写入和删除都是看不见的。查找时,就是指定那一时刻的所有SSTable,只查询这些SSTable里的数据。然而随着Compaction的进行,SSTable的分布会发生变化,这时就需要版本管理了。当在访问一个旧的快照时,有一个版本对应这个快照,当Compaction进行时,生成新的版本,但是旧的版本还需要保留着,这样旧快照就可以访问旧的版本。每一个快照就对应自己的版本,当一个版本不被任何快照引用时,这个版本就可以被删除了。

LevelDB是一个函数库,自己是无法自动起后台线程的,这些后台线程是要依赖于调用方进程的。LevelDB的方式就是在调用方调用

Get

Put

Delete

时,会检测一些条件是否符合,是否需要Compaction,如果需要的话,就会启动后台线程开始Compaction,这个后台线程是属于数据库使用方的线程。

小结

LevelDB的数据库层分为看得见的接口实现,以及看不见的后台Compaction,接下来会详细介绍这两部分的内容。