理解 etcd: 存储层

156 阅读2分钟

Bolt DB 是 etcd 的存储层, 理解 bolt db 是理解 etcd 的基础.

简介

Bolt DB 是一个用 golang 实现的简单,高效的 kv 数据库. 作者在 17 年认为 bolt db 已经处于一个稳定且成功的状态, 因此将 GitHub Repo GitHub - boltdb/bolt: An embedded key/value database for Go. 归档. Etcd 团队 fork 了一份 GitHub - etcd-io/bbolt: An embedded key/value database for Go. 并继续维护.

存储

文件读写

一个 db 的所有数据都存储在一个 file 中. 在调用 db.Open() 时, bolt db 把 file mmap 到内存中.

Bolt DB 基于这些内存中的数据构造出一个 page 列表, 并在此基础上构造出一系列的数据结构, 其中就包括 bucket.

DB 按照 page 来管理数据. Page 是操作系统管理内存的基本单位, 而为了优化文件的读写性能, 或者是为了使用 mmap 的功能, 许多数据库系统也会将 page 作为操作数据的基本单位.

当数据库读取 page 时, 可以直接基于内存中的 data 构造 page 并使用, 免去了重复读取文件的开销.

当对数据库的操作修改了 page 时, 实际上修改的是一个 page 在内存中的拷贝, 这些修改会在 transaction commit 时通过 file.WriteAt 写回到文件中, 并通过 mmap 自动反映到内存中的 data 中.

image-20231230204233849

B+ tree

Bolt DB 实现了十分标准的 B+ tree 来管理数据, 每一个 bucket 就是一颗 B+ 树, 而树中的 node 可以分为两类:

  1. Branch node: 分支节点并不保存数据, 而是保存了指向其他 child node 的信息.
  2. Leaf node: 叶子节点中保存着多个 key value 对.

每一个 node 对应一个 page, 因此在 branch node 中会存储一个 key, pgid 的列表, 作为其 child node 的列表.

image-20231230210057519 在 file 中, page 按照 id 顺序地存储, 这样 db 能够以 O(1) 的时间复杂度随机读取 mmap 到内存中的数据从而构造 page. 一个 page 以保存了page metadata 的 page header 作为开头, 根据 page 类型的不同, page header 后面的数据结构也不同.

一个 branch page 在内存和磁盘中的结构如下所示

|-------------|--------------------|--------------------|---------------------|---------------------|
| PageHeader  | branchElem 1       | branchElem 2       | key of branchElem 1 | key of branchElem 2 |
|-------------|--------------------|--------------------|---------------------|---------------------|
| ...         | pos | ksize | pgid | pos | ksize | pgid | ...                 | ...                 |
|-------------|--------------------|--------------------|---------------------|---------------------|

总结

不难看出 Bolt DB 很明显是一个适用于 read heavy 场景的 kv 数据库, 这也是 etcd 的使用场景. Bolt DB 还支持事务, 并且实现十分简洁明了, 本篇 blog 就不再赘述.