boltdb 是市面上为数不多的纯 go 语言开发的、单机 KV 库。boltdb 基于 Howard Chu's LMDB 项目 ,实现的比较清爽,去掉单元测试和适配代码,核心代码大概四千多行。 本系列计划分成三篇文章,依次围绕数据组织、索引设计、事务实现等三个主要方面对 boltdb 源码进行剖析。 boltdb buckets organised 相比普通 B+ 树,boltdb 的 B+ 树有几点特殊之处: 节点的分支个数不是一个固定范围,而是依据其所存元素大小之和来限制的,这个上限即为页大小。 按 boltdb 的设计,写事务只能串行进行。boltdb 使用 COW 的方式对节点进行修改,以保证不影响并发的读事务。 参考 boltdb repo:https://github.com/boltdb/bolt
boltdb 是市面上为数不多的纯 go 语言开发的、单机 KV 库。boltdb 基于 Howard Chu'sLMDB 项目 ,实现的比较清爽,去掉单元测试和适配代码,核心代码大概四千多行。 在文件系统上,boltdb 采用页(page)的组织方式,将一切数据都对齐到页;在内存中,boltdb 按 B+ 树组织数据,其基本单元是节点(node),一个内存中的树节点对应文件系统上一个或者多个连续的页 顶层组织 boltdb 的数据组织,自上而下来说: 每个 db 对应一个文件。 在下一篇 boltdb 的索引设计中,将详细剖析 boltdb 是如何组织多个 bucket 以及单个 bucket 内的 B+ 树索引的。 参考 github,boltdb repo 我叫尤加利,boltdb 源码分析
boltdb 是市面上为数不多的纯 go 语言开发的、单机 KV 库。boltdb 基于 Howard Chu’s LMDB 项目 ,实现的比较清爽,去掉单元测试和适配代码,核心代码大概四千多行。 本文仍然会按照这四个方面对 boltdb 对事务的支持进行剖析,但在每个小结开始,会先参考 Martin Kleppmann 的演讲[^2],试着从不同角度先阐释其内涵;然后在再分析 boltdb 对其实现 也因此,boltdb 适合读多写少的应用。 在读写事务进行到一半时,如果 boltdb 实例意外挂掉重启后,boltdb 如何保证事务的原子性? 对于 boltdb 来说,因为不允许并发写,可重复读和序列化在此含义就是一样的。总结来说,boltdb 实现隔离性的方法是: 增量写内存。 穿透读磁盘。
boltdb 学习 【以下大量内容参考自boltdb 1.3.0实现分析】 boltdb中,db 代表一个数据库,对应一个 db 文件;而一个数据库中可能有多个表,对应的概念就是boltdb 中的 bucket boltdb中以页面为单位来进行磁盘的读写操作,一个页面的大小一般而言与操作系统的页面一致,即 4K 大小。 在boltdb中,分为以下几种类型的页面: 存储 meta 元数据的页面 存储freelist,即管理页面数据的页面 Branch页面,存储B+树索引节点,也就是内部节点的页面。 / boltdb 的版本号 pageSize uint32 // boltdb 文件的页面大小 flags uint32 // 保留字段 root bucket // 保存 boltdb 1.3.0实现分析 boltdb 源码分析 认真分析mmap:是什么 为什么 怎么用 rocketmq design
本文要介绍的Bucket是boltdb中的桶,它是一个数据结构。boltdb中每个Bucket是一颗B+树,它里面存放一批key-value数据对。 一个boltdb中可以有多个Bucket, Bucket中可以嵌套Bucket. 在boltdb源码分析系列-内存结构文章中介绍了boltdb内存结构node,一个Bucket可以看做node的集合。 page描述的是boltdb的文件结构,即物理存储;node描述的是boltdb的内存结构,即逻辑结构。Bucket结构体中上面的两个字段分别从物理和逻辑层面描述了boltdb信息。 nodes缓存的是可能有影响的节点信息,当我们向Bucket中写入数据、删除数据或者更新数据的时候,并不是直接更新boltdb文件,而是更新它在内存中的node信息。 向boltdb中写入数据、修改数据或是删除数据,都是通过调用Bucket的方法,因为在逻辑上,Bucket是承载数据的地方。
可用的空闲页号信息存储在freelist中,具体位于freelist.go文件中,定义如下:
BoltDB内存结构 在boltdb源码分析系列-文件结构文章中讲述了BoltdDB文件是按page位单位进行物理存储的,当我们Open打开一个BoltDB文件之后,文件内容会被加载到内存。 文件中的page页在内存中以node来组织,如果说page是BoltDB物理存储单元,那么node是BoltDB的逻辑单元,是page在内存中的表示。 在boltdb源码分析系列-文件结构中介绍了BoltDB文件由meta page、freelist page、branch page和leaf page。 node与page相互转换 当我们打开一个BoltDB文件之后,它的信息(page)会被加载到内存以node表示。 BoltDB事务操作分为只读事务和读写事务,只读事务不会更新数据库内容,所以不会更新meta page, 只有读写事务会更新meta page.
Cursor是boltdb中的迭代器,它能够按顺序访问Bucket中的数据。在前面的文章中说过,一个Bucket是一颗B+Tree. Cursor是boltdb中的一个结构体,定义在cursor.go文件中。它只有2个字段,如下所示。 而boltdb中B+Tree的叶子节点并没有通过链表连接起来,这样做的原因小编觉得是为了减少了维持B+Tree平衡性质的难度,但在遍历的时候增加了一定的困难。 总结起来,因为boltdb中的B+Tree没有将叶子节点通过链表串联起来,为了能够方便对其访问,抽象处理了Cursor迭代器,来对其进行遍历操作。 因为boltdb中B+Tree叶子节点没有构成链表结构,所以需要回溯到当前节点的父节点,然后走到它的邻近右节点。
小编在阅读etcd源码的时候,看到它的存储storage使用了BoltDB数据库。 分析的BoltDB源码地址在https://github.com/boltdb/bolt。 readme介绍比较长,这里对BoltDB关键性概念做一个说明。 ,使用了写时复制技术(copy on write) B+树 通过使用B+树实现索引,提高了随机读操作的性能 BoltDB文件 boltDB是一个文件型数据库,写入的数据存储在磁盘的文件上。 总结 本文主要介绍了BoltDB物理存储,一个BoltDB文件是一系列page的集合,通过图示介绍了每种page的结构以及各个字段的含义,下篇将介绍BoltDB文件加载到内存之后,它的逻辑组织结构。
boltdb是golang实现的一个基于b+树的存储系统,通过mvcc实现了单个写事务和多个读事务并发。结构也很清晰,由于比较稳定,已经归档,确实是学习数据库的最佳选择。 boltdb是通过内存映射的方式实现持久化的,每一个db对象代表了一个存储实例。实例内部通过Bucket来管理命名空间的,类似于mysql的表名,不同点是Bucket是可以嵌套的。 package main import ( "log" "time" "github.com/boltdb/bolt" ) func main() { // Open the my.db : package main import ( "bytes" "encoding/json" "fmt" "log" "os" "time" "github.com/boltdb
在了解完boltDB如何使用后,我们开始详细分析boltdb的源码,我们从创建实例函数bolt.Open开始,它的源码位于db.go,它的第一个参数是文件名称,第二个参数是权限信息,第三个参数是创建数据库实例的可选参数 space and // bypasses a truncate() and fsync() syscall on remapping. // // https://github.com/boltdb
通过前面源码分析,我们差不多了解了boltdb的核心数据结构了,逻辑视图上是通过Bucket组建的嵌套结构来管理数据的,每一层都可以存储一一系列key和value,也是使用boltdb的用户需要关注的 先从磁盘上的存储结构开始,每一个boltdb对应一个文件,文件按照 page size(一般为 4096 Bytes) 划分为 page。 可以看出,和标准的B+树的区别是叶子节点没有指向兄弟页面的指针,因为这对于k/v类型的存储boltdb来说是不必要的。
在分析完核心数据结构后,我们结合使用boltdb的核心过程了解下上述数据结构建立的过程,总结下来核心过程如下: bolt.Open db.Update db.Begin tx.CreateBucket for _, fn := range tx.commitHandlers { fn() } 由于boltdb采用了mvcc模式,所以事务回滚很简单,直接重新加载meat信息,复原freelist
boltdb是如何实现事务原子性的? 事务的原子性即一组数据库操作,要么全部修改成功,要么全部撤销,不存在部分操作成功部分失败的情况。boltdb是如何实现事务原子性的,可以从两个方面来分析。 另一方面是在执行事务的过程中,例如在向磁盘写数据的过程中,出现设备掉电等导致boltdb实例挂掉,这是通过boltdb采用的shadow paging方法实现的,在tx.Commit的时候,元信息page boltdb是如何实现事务隔离性的? 对于boltdb来说,不存在并发的写操作,所以事务的隔离被限定在了多个只读事务的隔离性和1个写事务操作和读事务操作的隔离性上。 statlock是保护boltdb统计分析对象用的,这里不用过多关心。 type DB struct { ... 使用于读多写少的场景,对于写操作多并且性能要求高的情况,boltdb就不太适合。
读数据执行流程 读数据示例 读取boltdb数据有两种操作模式,一种是用户手动管理事务,另一种是通过boltdb提供的func (db *DB) View(fn func(*Tx) error) error 我们先来看手动管理事务的代码,分为以下流程: 通过Open操作,创建一个boltdb实例 对boltdb实例执行Begin操作,传入false, 开启一个只读事务tx 通过事务tx执行我们自己的逻辑,可以获取 在boltdb源码分析系列-Bucket文章中已详细介绍过func (b *Bucket) Get(key []byte) []byte操作,Cursor工作原理在boltdb源码分析系列-迭代器文章中已有介绍 写数据执行流程 写数据示例 类似读取数据,向boltdb中写入数据也有两种模式,一种是用户自己管理事务,另一种是通过boltdb提供的API接口,我们只需传入一个事务操作函数,像func (db *DB ,boltdb是如何实现事务的在下篇文章中详细介绍。
HashiCorp还在开发新版本的Consul,以取代BoltDB。 但是尽管如此,三大云提供商的销售代表还是没有从Roblox获得任何大笔的新业务。 BoltDB问题似乎直接归咎于糟糕的设计。需要空闲链表(freelist)很好,在每次追加后都需要将整个空闲链表同步到磁盘很可笑很荒唐。 我是BoltDB的开发者。是的,这是糟糕的设计。 为了防止BoltDB无限止地变大,Consul定期执行快照。快照操作将Consul的当前状态写入到磁盘,然后从BoltDB中删除最旧的日志条目。 但是,由于BoltDB的设计使然,即使明明已删除了最旧的日志条目,BoltDB在磁盘上使用的空间也不会缩小。 BoltDB在一种名为“空闲链表”(freelist)的结构中跟踪这些空闲页面。
而日志的索引则专门存储到索引服务当中,这里面包含Loki内置的BoltDB当中。其数据存储主要的思想也是让用对象存储负责廉价地存储压缩日志,而索引则负责以快速,有效的查询方式存储这些标签。 我们先来看下Loki默认情况下关于数据存储配置 schema_config: configs: - from: 2018-04-15 store: boltdb object_store : filesystem schema: v9 index: prefix: index_ period: 168h storage_config: boltdb ,当今天小白只拿filesystem、S3来做原始日志存储,boltdb和cassandra来做index存储 schema_config 这里面主要定义的是Loki数据存储的策略。 从默认的配置里面可以得到的信息是Loki里面保存的是2018年4月15日之后的数据,同时原始文件存在filesystem中,index存在boltdb当中且保存的周期是168小时 定义Schema享受丝滑般切换
这时候Loki将只创建一块PVC来做boltdb-shipper的数据持久化。 chunk: filesystem storageconfig: boltdb_shipper: shared_store: filesystem filesystem 这时候Loki会通过boltdb-shipper将index和chunk保存到S3对象存储当中,同时启用redis/memcached服务作为Loki的缓存,对于日志查询的体验比场景一更为优秀。 chunk: s3 storageconfig: boltdb_shipper: shared_store: s3 s3: address: x.x.x.x Loki之间通过Memberlist来同步哈希环,同时存储部分通过boltdb-shipper将index和chunk保存到S3对象存储当中,同时启用redis/memcached服务作为Loki的缓存
本文将从Raft内存存储、WAL持久化、MVCC逻辑存储、BoltDB物理存储四个层次,完整还原etcd的数据流转全貌。 磁盘B+tree(BoltDB):以(major, sub, type)三元组为Key,存储完整的mvccpb.KeyValue结构体。 四、BoltDB物理存储层:B+树的磁盘艺术 Etcd选择BoltDB作为底层存储引擎,这是一款纯Go实现的嵌入式KV数据库,其核心是B+树 + 内存映射文件(mmap)。 事务模型:BoltDB支持完全ACID的读写事务。 的分工,使查询与写入并行; BoltDB层只负责持久化,不负责共识——批量提交与mmap,使磁盘从瓶颈变为流水线的一环。
etcd内部 etcd存储层由内存中基于btree的索引层和基于boltdb的磁盘存储层两大部分组成。文档主要关注底层boltDB层,因为它是优化目标。 以下是对boltDB的介绍,引用自https://github.com/boltdb/bolt/blob/master/README.md Bolt最初是LMDB的一个移植,所以它在架构上是类似的。 例如,etcd内置了boltDB作为内部存储k/v数据的引擎。boltDB使用B+树存储数据,叶子节点存储真实的键/值。它将所有数据存储在一个文件中,使用mmap syscall将其映射到内存。 当页删除发生时,boltdb不会直接回收已删除页的存储。相反,它临时保存已删除的页,以形成一个空闲的页池供后续使用。这个自由页池在boltDB中称为freelist。 图2给出了一个boltDB页元数据的示例。 ? 图2. boltDB页元数据 红色页43、45、46、50正在使用,而42、44、47、48、49、51页以后可以供使用。