前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Lmdb、Boltdb 和 mmap

Lmdb、Boltdb 和 mmap

原创
作者头像
王磊-字节跳动
修改2021-02-21 22:43:48
3.1K0
修改2021-02-21 22:43:48
举报
文章被收录于专栏:01ZOO

lmdb 简介

  1. LMDB 是 Lightning Memory-Mapped Database 的简称, 简单来说,就是使用 mmap 技术实现的一般是基于 b+ tree 的 kv 数据库
    • lmdb 的其他特点在 Howard Chu 的 talk 中还包括 acid 支持、mvcc 【reader 不阻塞】、高度压缩、使用 copy on write【不对历史值做修改】、无须 wal 等特点。
    • 读一般是直接使用 mmap【只读 mmap】, 操作简化,利用系统对 mmap 的cache 优化
    • 写可以使用 mmap【optional】, 无须 cache 层,极大的简化了实现逻辑
    • lmdb 使用 append-only B+ tree 实现,很多 db 如 CouchDB 等 也是基于这种结构。相比于普通的 B+ tree 区别主要是 mvcc + cow 特性,且没有脏写的可能性。【修改一个叶子节点会 copy 后修改,然后将所有的父节点也都 copy 修改指针,至于为什么不可能脏写,想象在修改时出现了 crash,那么根节点还没来得及修改,数据还是修改之前的数据,不会有 incomplete write 的情况】。同时为了更有效的使用磁盘空间,同时也避免了后台的 compaction/garbage collection 阶段【基于 lsm tree 的实现都会有这个阶段】:lmdb 只有保存最多两个版本的 version【更旧的版本会被回收】,并不是完全的 mvcc.
image.png
image.png
  1. bolt 可以看成是 lmdb 的一个 go 版本实现,目前活跃的是 etcd 的分支 bbolt,作为 etcd 的存储引擎使用。
    • Bolt 专注于简单性和易用性,例如,LMDB 允许执行一些不安全的操作,如直接写操作。 Bolt 选择禁止可能使数据库处于损坏状态的操作。
    • 读使用 mmap,写则通过文件读写【写时机可控】。

boltdb 学习

【以下大量内容参考自boltdb 1.3.0实现分析

  • boltdb中,db 代表一个数据库,对应一个 db 文件;而一个数据库中可能有多个表,对应的概念就是boltdb 中的 bucket
  • boltdb中以页面为单位来进行磁盘的读写操作,一个页面的大小一般而言与操作系统的页面一致,即 4K 大小。在boltdb中,分为以下几种类型的页面:
    • 存储 meta 元数据的页面
    • 存储freelist,即管理页面数据的页面
    • Branch页面,存储B+树索引节点,也就是内部节点的页面。
    • Leaf页面,存储B+树数据节点,也就是叶子节点的页面。
代码语言:txt
复制
 bolt db 数据库文件磁盘布局

# -------------|------------|--------------|--------------------|--------------------|----------
#  meta page 0 | meta page 1| freelist page| leaf/branch page 0 | leaf/branch page 1 | .....
# -------------|------------|--------------|--------------------|--------------------|----------
  • page 的结构主要包括以下几个部分
代码语言:txt
复制
type pgid uint64

type page struct {
	id       pgid   // 页面 id
	flags    uint16 // 标志位,区分 metaPageFlag、freelistPageFlag、branchPageFlag、leafPageFlag
	count    uint16 // 页面中存储的数据数量,仅在页面类型是branch以及leaf的时候起作用
	overflow uint32 // 当前页面如果还不够存放数据,就会有后续页面,这个字段表示后续页面的数量
}

# 根据这个结构如何获取 data 的地址 ?, 以 meta 为例: 
// meta returns a pointer to the metadata section of the page.
func (p *page) meta() *meta {
	return (*meta)(unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)))
}

# 结构如下
# --------------页表头-------------|----页数据------------|
# | id | flags | count | overflow | data............... | 
# --------------------------------|---------------------|
  • page 又分为以下几种,分别简要介绍结构:
    • meta 页面用于存储表示整个数据库信息的元数据
    • freelist 页面存储当前可以使用的页面 id,包括回收的页面【参考 how append only b+tree works】, 如果当前磁盘页面已经不够分配了,boltdb就需要扩大磁盘文件的大小,创建出更多可用的闲置页面供分配
      • 这里面比较核心的字段是 ids/pending/cache,实际存储时只会存储 ids 列表,freemaps/forwardMap/backwardMap 的含义参考 这篇文章, 是对于查询 freelist 里面的 page 性能的一个优化。
    • branch 页面
代码语言:txt
复制
# meta 页面结构
type meta struct {
	magic    uint32 // boltdb 的magic number
	version  uint32 // boltdb 的版本号
	pageSize uint32 // boltdb 文件的页面大小
	flags    uint32 // 保留字段
	root     bucket // 保存 boltdb 的根 bucket 的信息
	freelist pgid   // 保存freelist页面的页面ID
	pgid     pgid   // 保存当前总的页面数量,即最大页面号加一
	txid     txid   // 上一次写数据库的事务ID,可以看作是当前 boltdb 的修改版本号,每次写数据库时加1,只读时不改变
	checksum uint64 // 校验码,用于校验元数据页面是否出错的
}

# 结构如下
# --------------页表头-------------|----页数据----------------------------------------|
# | id | flags | count | overflow |pageSize|flags|root|freelist|pgid|txid| checksum | 
# --------------------------------|-------------------------------------------------|

freelist 页面结构

代码语言:txt
复制
# freelist 页面结构

// txPending holds a list of pgids and corresponding allocation txns
// that are pending to be freed.
type txPending struct {
	ids              []pgid
	alloctx          []txid // txids allocating the ids
	lastReleaseBegin txid   // beginning txid of last matching releaseRange
}
// freelist represents a list of all pages that are available for allocation.
// It also tracks pages that have been freed but are still in use by open transactions.
type freelist struct {
	freelistType   FreelistType                // freelist type
	ids            []pgid                      // all free and available free page ids.
	allocs         map[pgid]txid               // mapping of txid that allocated a pgid.
	pending        map[txid]*txPending         // mapping of soon-to-be free page ids by tx.
	cache          map[pgid]bool               // fast lookup of all free and pending page ids.
	freemaps       map[uint64]pidSet           // key is the size of continuous pages(span), value is a set which contains the starting pgids of same size
	forwardMap     map[pgid]uint64             // key is start pgid, value is its span size
	backwardMap    map[pgid]uint64             // key is end pgid, value is its span size
	allocate       func(txid txid, n int) pgid // the freelist allocate func
	free_count     func() int                  // the function which gives you free page number
	mergeSpans     func(ids pgids)             // the mergeSpan func
	getFreePageIDs func() []pgid               // get free pgids func
	readIDs        func(pgids []pgid)          // readIDs func reads list of pages and init the freelist
}


# 结构如下
# --------------页表头-------------|----页数据------------|
# | id | flags | count | overflow | free id 列表........ | 
# --------------------------------|---------------------|

branch 和 left 页面结构

代码语言:txt
复制
# branch 和 left 页面结构

// branchPageElement represents a node on a branch page.
type branchPageElement struct {
	pos   uint32 // 存储键相对于当前页面数据部分的偏移量
	ksize uint32 // 键的大小
	pgid  pgid   // 子节点的页面ID
}

// leafPageElement represents a node on a leaf page.
type leafPageElement struct {
	flags uint32 // 标志位,为0的时候表示就是普通的叶子节点,而为1的时候表示是子bucket
	pos   uint32 // 存储键相对于当前页面数据部分的偏移量
	ksize uint32 // 键的大小
	vsize uint32 // 存储数据的大小
}

结构见下图 【图片来自 https://youjiali1995.github.io/storage/boltdb/】
image
image
image
image
image
image

mmap

mmap,几乎是所有现代的存储系统使用的核心技术之一,比如 mongo, prometheus, rocketmq 等等。这部分将总结和学习 mmap 在各个项目中的使用。

mmap优点共有一下几点:

  • 对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。
  • 实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。
  • 提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。同时,如果进程A和进程B都映射了区域C,当A第一次读取C时通过缺页从磁盘复制文件页到内存中;但当B再读C的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可直接使用已经保存在内存中的文件数据。
  • 可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件I/O操作,极大影响效率。这个问题可以通过mmap映射很好的解决。换句话说,但凡是需要用磁盘空间代替内存的时候,mmap都可以发挥其功效

关于 mmap 的介绍和为什么有性能优势可以参考这篇文章

在 boltdb 中的使用

  • mmap 设置了选项 PROT_READ,表示只读
  • mmap 主要用来加速读的性能,在 boltdb 中抽象了存储结构为 4k 大小的 page,在 内存中 page 又被转化为 node 即 btree 的node,这样在读 btree 的时候,实际就使用了 mmap 按页读取数据,性能很高。
  • 而写数据的时候则使用 文件写操作,把一个 tx 里面的 dirty pages 里面的数据写入磁盘。
  • 在 boltdb 中将整个 db 都用一个 mmap 操作映射进内存, 同时没有 wal 保证恢复数据,同一个操作的 dirty page 可能比较多,write 操作通过 mmap 不太好控制 flush 时机,效率也不一定会很高。

在 prometheus 中的使用

  • prometheus 的原则是把 io 操作的优化留给系统:
    • 控制缓存很容易。Queried data can be rather aggressively cached in memory, yet under memory pressure the pages will be evicted. If the machine has unused memory, Prometheus will now happily cache the entire database, yet will immediately return it once another application needs it.
    • read-write => mmap总能提升性能 (Optimizing file I/O is hard, and most of the time, is better left to the kerner)

在 rocketmq 中的使用

  • 无论 commitlog、consumerQueue、indexFile 都是 mmap 来实现,单个文件都被设计成固定长度,比如 consumerQueue- 1G,写满之后就创建一个新文件,所以代码里面会看到一组 mmap 文件【mmapedFileQueue】
  • 通过 JDK NIO 的MappedByteBuffer 实现,即 mmap
  • 关于 mmap 的 flush 问题,rocketmq 的解决方式未提供了两种刷盘方式:同步和异步,一般使用异步刷盘,即后台线程定期做 flush 操作,将 pagecache 中内容刷盘。
  • 根据 rocketmq 的文档,异步刷盘情况,系统 crash,未刷盘的数据将全部丢失。根据性能压测结果,实际在内存未刷盘数据大概在几十 K。也就是说最糟糕的情况会有几十 K的消息丢失。【rocketmq 也没有 wal,rocketmq 的commitlog 实际是消息存储,并不是 wal】
  • rocketmq 在发送消息到 consumer 的时候通过 mmap 实现来零拷贝,没有使用 sendfile 操作。简单来说,原理是:将 pagecache 直接传输给 socket,在操作系统层面会做以下优化
    • 因为 pagecache 内存本身是内核与应用共享的内存,所以不需要用户态向内核态内存拷贝。
    • Socket 在发现是 pagecache 时,会将 pagecache 直接传输,不需要将数据拷贝到 socket 缓冲区。(TCP 协议栈优化)

在 mongodb 中的使用

  • MongoDB在3.0 之前使用 mmap 引擎作为默认存储引擎。【到MongoDb 3.2的时候默认的存储引擎已经变更为Wired Tiger】
  • 和 rocketmq 类似 mmap 引擎也是后端线程异步 flush,通过 wal 来保证数据的完整性。如果不打开 wal,和 rocketmq 一样,在 系统 crash 时,数据会丢失。

参考

  1. Bolt — an embedded key/value database for Go
  2. LDAP at Lightning Speed - Howard Chu's Talk at InfoQ
  3. bbolt 初体验
  4. boltdb 1.3.0实现分析
  5. boltdb 源码分析
  6. 认真分析mmap:是什么 为什么 怎么用
  7. rocketmq design

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • lmdb 简介
  • boltdb 学习
  • mmap
    • 在 boltdb 中的使用
      • 在 prometheus 中的使用
        • 在 rocketmq 中的使用
          • 在 mongodb 中的使用
          • 参考
          相关产品与服务
          数据库
          云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档