Boltdb 源码导读(一):Boltdb 数据组织(2)

简介: Boltdb 源码导读(一):Boltdb 数据组织(2)

中间节点页(branchPage)

中间节点页和叶子节点页的结构大体相同。不同之处在于,页中保存的数据的 value 是 page id,即该中间节点在哪些 key 上的分支分别指向的 page 。

image.pngbranchPageElement 中的 key 存的是其指向的页中的起始 key。

转换流程

boltdb 使用 mmap 将 db 文件映射到内存空间。在构建树并且访问过程中,按需将对应的页加载到内存里,并且利用操作系统的页缓存策略进行替换。

文件增长

当我们打开一个 db 时,如果发现该 db 文件为空,会在内存中初始化四个页(4*4k=16K),分别是两个元信息页、一个空的空闲列表页和一个空的叶子节点页,然后将其写入 db 文件,然后走正常打开流程。

func (db *DB) init() error {
  // 设置页大小与操作系统一致
  db.pageSize = os.Getpagesize()
  buf := make([]byte, db.pageSize*4)
  // 在 buffer 中创建两个元信息页.
  for i := 0; i < 2; i++ {
    p := db.pageInBuffer(buf[:], pgid(i))
    p.id = pgid(i)
    p.flags = metaPageFlag
    // 初始化元信息页.
    m := p.meta()
    m.magic = magic
    m.version = version
    m.pageSize = uint32(db.pageSize)
    m.freelist = 2
    m.root = bucket{root: 3}
    m.pgid = 4
    m.txid = txid(i)
    m.checksum = m.sum64()
  }
  // 在 pgid=2 的页写入一个空的空闲列表.
  p := db.pageInBuffer(buf[:], pgid(2))
  p.id = pgid(2)
  p.flags = freelistPageFlag
  p.count = 0
  // 在 pgid=3 的页写入一个空的叶子元素.
  p = db.pageInBuffer(buf[:], pgid(3))
  p.id = pgid(3)
  p.flags = leafPageFlag
  p.count = 0
  // 将 buffer 中的这四个页写入数据文件并刷盘
  if _, err := db.ops.writeAt(buf, 0); err != nil {
    return err
  }
  if err := fdatasync(db); err != nil {
    return err
  }
  return nil
}

随着数据的不断写入,需要申请新的页。boltdb 首先会去 freelist 中找有无可重复利用的页,如果没有,就只能进行 re-mmap(先 mumap 在 mmap),扩大 db 文件。每次扩大会进行倍增(因此从 16K * 2 = 32K 开始),到达 1G 后,再次扩大会每次新增 1G。

func (db *DB) mmapSize(size int) (int, error) {
  // 从 32KB 开始,直到 1GB.
  for i := uint(15); i <= 30; i++ {
    if size <= 1<<i {
      return 1 << i, nil
    }
  }
  // Verify the requested size is not above the maximum allowed.
  if size > maxMapSize {
    return 0, fmt.Errorf("mmap too large")
  }
  // 对齐到 maxMmapStep = 1G
  sz := int64(size)
  if remainder := sz % int64(maxMmapStep); remainder > 0 {
    sz += int64(maxMmapStep) - remainder
  }
  // 对齐到 db.pageSize
  pageSize := int64(db.pageSize)
  if (sz % pageSize) != 0 {
    sz = ((sz / pageSize) + 1) * pageSize
  }
  // 不能超过 maxMapSize
  if sz > maxMapSize {
    sz = maxMapSize
  }
  return int(sz), nil
}

在 32 位 机器上文件最大不能超过 maxMapSize = 2G;在 64 位机器上,文件上限为 256T。

读写流程

在打开一个已经存在的 db 时,会首先将 db 文件映射到内存空间,然后解析元信息页,最后加载空闲列表。

在 db 进行读取时,会按需将访问路径上的 page 加载到内存,并转换为 node,进行缓存。

在 db 进行修改时,使用 COW 原则,所有修改不在原地,而是在改动前先复制一份。如果叶子节点 node 需要修改,则 root bucket 到该 node 路径上所涉及的所有节点都需要修改。这些节点都需要新申请空间,然后持久化,这些和事务的实现息息相关,之后会在本系列事务文章中做详细说明。

小结

boltdb 在数据组织方面只使用了两个概念:页(page) 和节点 (node)。每个数据库对应一个文件,每个文件中包含一系列线性组织的页。页的大小固定,依其性质不同,分为四种类型:元信息页、空闲列表页、叶子节点页、中间节点页。打开数据库时,会渐次进行以下操作:

  1. 利用 mmap 将数据库文件映射到内存空间。
  2. 解析元信息页,获取空闲列表页 id 和 bucket 树页 id。
  3. 依据空闲列表页 id ,将所有空闲页列表载入内存。
  4. 依据 bucket 树起始地址,解析 bucket 树根节点。
  5. 根据读写需求,从树根开始遍历,按需将访问路径上的数据页(中间节点页和叶子节点页)载入内存成为节点(node)。

可以看出,节点分两种类型:中间节点(branch node)和叶子节点(leaf node)。

另外需要注意的是,多个子 Bucket 树和 Bucket 对应的 B+ 树复用了 bucket 这个数据结构,导致这一块稍微有点不好理解。在下一篇 boltdb 的索引设计中,将详细剖析 boltdb 是如何组织多个 bucket 以及单个 bucket 内的 B+ 树索引的。

参考

  1. github,boltdb repo
  2. 我叫尤加利,boltdb 源码分析

不妨一读

漫谈 LevelDB 数据结构(二):布隆过滤利器

golang Context 源码剖析


相关文章
|
存储 缓存 NoSQL
Leveldb学习笔记:leveldb的使用与原理探究
Leveldb学习笔记:leveldb的使用与原理探究
Leveldb学习笔记:leveldb的使用与原理探究
|
Java 测试技术 分布式数据库
从数据结构比较HBase的3种memstore实现方案
HBase的memstore目前存在3种实现:DefaultMemstore、CompactingMemstore、CCSMapMemStore,本文尝试从数据结构的角度对其进行比较。
1694 0
|
存储 算法 关系型数据库
innodb逻辑存储结构整理
从innodb存储引擎的逻辑存储来看,所有数据都被逻辑地存放在一个空间中,称之为表空间tablespace,表空间又由段segment、区extent、页page组成。
116 0
|
存储 Java 测试技术
Boltdb 源码导读(一):Boltdb 数据组织(1)
Boltdb 源码导读(一):Boltdb 数据组织(1)
200 0
Boltdb 源码导读(一):Boltdb 数据组织(1)
|
NoSQL 关系型数据库 MySQL
boltdb 源码导读(二):boltdb 索引设计(1)
boltdb 源码导读(二):boltdb 索引设计(1)
257 0
boltdb 源码导读(二):boltdb 索引设计(1)
|
存储 NoSQL 测试技术
boltdb 源码导读(三):boltdb 事务实现
boltdb 源码导读(三):boltdb 事务实现
289 0
boltdb 源码导读(三):boltdb 事务实现
|
缓存 API Go
boltdb 源码导读(二):boltdb 索引设计(2)
boltdb 源码导读(二):boltdb 索引设计(2)
136 0
|
存储 Linux Go
Boltdb学习笔记之一--存储管理
Boltdb学习笔记之一--存储管理
195 0
Boltdb学习笔记之一--存储管理
|
存储 缓存 关系型数据库
Boltdb学习笔记之二--数据结构
Boltdb学习笔记之二--数据结构
308 0
Boltdb学习笔记之二--数据结构
|
存储 SQL 缓存
如何基于LSM-tree架构实现一写多读
PolarDB是阿里巴巴自研的新一代云原生关系型数据库,在存储计算分离架构下,利用了软硬件结合的优势,为用户提供具备极致弹性、海量存储、高性能、低成本的数据库服务。X-Engine是阿里巴巴自研的新一代存储引擎,作为AliSQL的核心引擎之一已广泛用于阿里巴巴集团核心业务,包括交易历史库,钉钉历史库,图片空间等。PolarDB历史库是基于LSM-tree架构实现一写多读的新产品,在存储分离技术的加成下,利用X-Engine的高压缩特性,为用户带来极致的低成本体验。
708 1
如何基于LSM-tree架构实现一写多读

热门文章

最新文章