0%

bblot 入门

[TOC]

概述

本书是采用自底向上的方式来介绍boltdb内部的实现原理。其实我们经常都在采用自底向上或者自顶向下这两种方式来思考和求解问题。

例如:我们阅读源码时,通常都是从最顶层的接口点进去,然后层层深入内部。这其实本质上就是一种自顶向下的方式。

又比如我们平常做开发时,都是先将系统进行拆分、解耦。然后一般都会采用从下而上或者从上而下的方式来进行开发迭代。

回到最初的话题,为什么本书要采用自底向上的方式来写呢?

对于一个文件型数据库而言,所谓的上指的是暴露给用户侧的调用接口。所谓的下又指它的输出(数据)最终要落到磁盘这种 存储介质上。采用自底向上的方式的话,也就意味着我们先从磁盘这一层进行分析。然后逐步衍生到内存;再到用户接口这一层。层层之间是 被依赖的一种关系。这样的话,其实就比较好理解了。在本书中,本人采用自底向上的方式来介绍。希望阅读完后,有一种自己从0到1构建了 一块数据库的快感。

当然也可以采用自顶向下的方式来介绍,这时我们就需要在介绍最上层时,先假设它所依赖的底层都已经就绪了,我们只分析当层内容。然后层层 往下扩展。

之前和一位大佬进行过针对此问题的探讨,在不同的场景、不同的组件中。具体采用自底向上还是自顶向下来分析。见仁见智,也具体问题具体分析。当要达成的目标足够清晰时,通过自顶向下的方式可以倒推达成目标需要完成的几个阶段任务。然后再依次进行细分展开。

boltdb是什么

Bolt is a pure Go key/value store inspired by [Howard Chu’s][hyc_symas] [LMDB project][lmdb]. The goal of the project is to provide a simple, fast, and reliable database for projects that don’t require a full database server such as Postgres or MySQL.

Since Bolt is meant to be used as such a low-level piece of functionality, simplicity is key. The API will be small and only focus on getting values and setting values. That’s it.

boltdb的黑科技

1. mmap

在boltdb中所有的数据都是以page页为单位组织的,那这时候通常我们的理解是,当通过索引定位到具体存储数据在某一页时,然后就先在页缓存中找,如果页没有缓存,则打开数据库文件中开始读取那一页的数据就好了。 但这样的话性能会极低。boltdb中是通过mmap内存映射技术来解决这个问题。当数据库初始化时,就会进行内存映射,将文件中的数据映射到内存中的一段连续空间,后续再读取某一页的数据时,直接在内存中读取。性能大幅度提升。

2. b+树

在boltdb中,索引和数据时按照b+树来组织的。其中一个bucket对象对应一颗b+树,叶子节点存储具体的数据,非叶子节点只存储具体的索引信息,很类似mysql innodb中的主键索引结构。同时值得注意的是所有的bucket也构成了一颗树。但该树不是b+树。

3. 嵌套bucket

前面说到,在boltdb中,一个bucket对象是一颗b+树,它上面存储一批kv键值对。但同时它还有一个特性,一个bucket下面还可以有嵌套的subbucket。subbucket中还可以有subbucket。这个特性也很重要。

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
func first() {
// 在当前目录下打开 my.db 这个文件
// 如果文件不存在,将会自动创建
db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second})
if err != nil {
log.Fatal(err)
}
defer db.Close()

key := []byte("hello")
value := []byte("world")

// 创建一个 read-write 事务来进行写操作
err = db.Update(func(tx *bolt.Tx) error {
// 如果 bucket 不存在则,创建一个 bucket
bucket, err := tx.CreateBucketIfNotExists(testBucket)
if err != nil {
return err
}

// 将 key-value 写入到 bucket 中
err = bucket.Put(key, value)
if err != nil {
return err
}
return nil
})
if err != nil {
log.Fatal(err)
}

// 创建一个 read-only 事务来获取数据
err = db.View(func(tx *bolt.Tx) error {
// 获取对应的 bucket
bucket := tx.Bucket(testBucket)
// 如果 bucket 返回为 nil,则说明不存在对应 bucket
if bucket == nil {
return fmt.Errorf("bucket %q is not found", testBucket)
}
// 从 bucket 中获取对应的 key(即上面写入的 key-value)
val := bucket.Get(key)
fmt.Printf("%s: %s\n", string(key), string(val))
return nil
})
if err != nil {
log.Fatal(err)
}
}

参考

自底向上分析boltdb源码

boltdb 源码分析