bolt

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 27, 2022 License: Apache-2.0 Imports: 15 Imported by: 2

README

bolt

以下内容来自: https://www.bookstack.cn/read/jaydenwen123-boltdb_book/00fe39712cec954e.md

在用自己的话介绍boltdb之前,我们先看下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进行介绍:

boltdb是一个纯go编写的支持事务的文件型单机kv数据库。

下面对上述几个核心的关键词进行一一补充。

纯go:

意味着该项目只由golang语言开发,不涉及其他语言的调用。因为大部分的数据库基本上都是由c或者c++开发的,boltdb是一款难得的golang编写的数据库。

支持事务:

boltdb数据库支持两类事务:读写事务、只读事务。这一点就和其他kv数据库有很大区别。

文件型:

boltdb所有的数据都是存储在磁盘上的,所以它属于文件型数据库。这里补充一下个人的理解,在某种维度来看,boltdb很像一个简陋版的innodb存储引擎。底层数据都存储在文件上,同时数据都涉及数据在内存和磁盘的转换。但不同的是,innodb在事务上的支持比较强大。

单机:

boltdb不是分布式数据库,它是一款单机版的数据库。个人认为比较适合的场景是,用来做wal日志或者读多写少的存储场景。

kv数据库:

boltdb不是sql类型的关系型数据库,它和其他的kv组件类似,对外暴露的是kv的接口,不过boltdb支持的数据类型key和value都是[]byte。

其实boltdb的用法很简单,从其项目github的文档里面就可以看得出来。它本身的定位是key/value(后面简称为kv)存储的嵌入式数据库,因此那提到kv我们自然而然能想到的最常用的操作,就是set(k,v)和get(k)了。确实如此boltdb也就是这么简单。

不过在详细介绍boltdb使用之前,我们先以日常生活中的一些场景来作为切入点,引入一些在boltdb中抽象出来的专属名词(DB、Bucket、Cursor、k/v等),下面将进入正文,前面提到boltdb的使用确实很简单,就是set和get。但它还在此基础上还做了一些额外封装。下面通过现实生活对比来介绍这些概念。

boltdb本质就是存放数据的,那这和现实生活中的柜子就有点类似了,如果我们把boltdb看做是一个存放东西的柜子的话,它里面可以存放各种各样的东西,确实是的,但是我们想一想,所有东西都放在一起会不会有什么问题呢?

咦,如果我们把钢笔、铅笔、外套、毛衣、短袖、餐具这些都放在一个柜子里的话,会有啥问题呢?这对于哪些特别喜欢收拾屋子,东西归类放置的人而言,简直就是一个不可容忍的事情,因为所有的东西都存放在一起,当东西多了以后就会显得杂乱无章。

在生活中我们都有分类、归类的习惯,例如对功能类似的东西(钢笔、铅笔、圆珠笔等)放一起,或者同类型的东西(短袖、长袖等)放一起。把前面的柜子通过隔板来隔开,分为几个小的小柜子,第一个柜子可以放置衣服,第二个柜子可以放置书籍和笔等。当然了,这是很久以前的做法了,现在买的柜子,厂家都已经将其内部通过不同的存放东西的规格做好了分隔。大家也就不用为这些琐事操心了。既然这样,那把分类、归类这个概念往计算机中迁移过来,尤其是对于存放数据的数据库boltdb中,它也需要有分类、归类的思想,因为归根到底,它也是由人创造出来的嘛。

好了到这儿,我们引入我们的三大名词了“DB”、“Bucket”、“k/v”。

DB: 对应我们上面的柜子。

Bucket: 对应我们将柜子分隔后的小柜子或者抽屉了。

k/v: 对应我们放在抽屉里的每一件东西。为了方便我们后面使用的时候便捷,我们需要给每个东西都打上一个标记,这个标记是可以区分每件东西的,例如k可以是该物品的颜色、或者价格、或者购买日期等,v就对应具体的东西啦。这样当我们后面想用的时候,就很容易找到。尤其是女同胞们的衣服和包包,哈哈

再此我们就可以得到一个大概的层次结构,一个柜子(DB)里面可以有多个小柜子(Bucket),每个小柜子里面存放的就是每个东西(k/v)啦。

那我们想一下,我们周末买了一件新衣服,回到家,我们要把衣服放在柜子里,那这时候需要怎么操作呢?

很简单啦,下面看看我们平常怎么做的。

第一步:如果家里没有柜子,那就得先买一个柜子;

第二步:在柜子里找找之前有没有放置衣服的小柜子,没有的话,那就分一块出来,总不能把新衣服和钢笔放在一块吧。

第三步:有了放衣服的柜子,那就里面找找,如果之前都没衣服,直接把衣服打上标签,然后丢进去就ok啦;如果之前有衣服,那我们就需要考虑要怎么放了,随便放还是按照一定的规则来放。这里我猜大部分人还是会和我一样吧。喜欢按照一定的规则放,比如按照衣服的新旧来摆放,或者按照衣服的颜色来摆放,或者按照季节来摆放,或者按照价格来摆放。哈哈

我们在多想一下,周一早上起来我们要找一件衣服穿着去上班,那这时候我们又该怎么操作呢?

第一步:去找家里存放东西的柜子,家里没柜子,那就连衣服都没了,尴尬…。所以我们肯定是有柜子的,对不对

第二步:找到柜子了,然后再去找放置衣服的小柜子,因为衣服在小柜子存放着。

第三步:找到衣服的柜子了,那就从里面找一件衣服了,找哪件呢!最新买的?最喜欢的?天气下雨了,穿厚一点的?天气升温了,穿薄一点的?今天没准可能要约会,穿最有气质的?…..

那这时候根据不同场景来确定了规则,明确了我们要找的衣服的标签,找起来就会很快了。我们一下子就能定位到要穿的衣服了。嗯哼,这就是排序、索引的威力了

如果之前放置的衣服没有按照这些规则来摆放。那这时候就很悲剧了,就得挨个挨个找,然后自己选了。哈哈,有点全表扫描的味道了

啰里啰嗦扯了一大堆,就是为了给大家科普清楚,一些boltdb中比较重要的概念,让大家对比理解。降低理解难度。下面开始介绍boltdb是如何简单使用的。


import "bolt"
func main(){
    // 我们的大柜子
    db, err := bolt.Open("./my.db", 0600, nil)
    if err != nil {
        panic(err)
    }
    defer db.Close()
    // 往db里面插入数据
    err = db.Update(func(tx *bolt.Tx) error {
       //我们的小柜子
        bucket, err := tx.CreateBucketIfNotExists([]byte("user"))
        if err != nil {
            log.Fatalf("CreateBucketIfNotExists err:%s", err.Error())
            return err
        }
        //放入东西
        if err = bucket.Put([]byte("hello"), []byte("world")); err != nil {
            log.Fatalf("bucket Put err:%s", err.Error())
            return err
        }
        return nil
    })
    if err != nil {
        log.Fatalf("db.Update err:%s", err.Error())
    }
    // 从db里面读取数据
    err = db.View(func(tx *bolt.Tx) error {
        //找到柜子
        bucket := tx.Bucket([]byte("user"))
        //找东西
        val := bucket.Get([]byte("hello"))
        log.Printf("the get val:%s", val)
        val = bucket.Get([]byte("hello2"))
        log.Printf("the get val2:%s", val)
        return nil
    })
    if err != nil {
        log.Fatalf("db.View err:%s", err.Error())
    }
}

组织结构

img

特点

  1. mmap

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

  1. b+树

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

  1. 嵌套bucket

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

核心数据结构

从一开始,boltdb的定位就是一款文件数据库,顾名思义它的数据都是存储在磁盘文件上的,目前我们大部分场景使用的磁盘还是机械磁盘。而我们又知道数据落磁盘其实是一个比较慢的操作(此处的快慢是和操作内存想对比而言)。所以怎么样在这种硬件条件无法改变的情况下,如何提升性能就成了一个恒定不变的话题。而提升性能就不得不提到它的数据组织方式了。所以这部分我们主要来分析boltdb的核心数据结构。

我们都知道,操作磁盘之所以慢,是因为对磁盘的读写耗时主要包括:寻道时间+旋转时间+传输时间。而这儿的大头主要是在寻道时间上,因为寻道是需要移动磁头到对应的磁道上,通过马达驱动磁臂移动是一种机械运动,比较耗时。我们往往对磁盘的操作都是随机读写,简而言之,随机读写的话,需要频繁移动磁头到对应的磁道。这种方式性能比较低。还有一种和它对应的方式:顺序读写。顺序读写的性能要比随机读写高很多。

因此,所谓的提升性能,**无非就是尽可能的减少磁盘的随机读写,更大程度采用顺序读写的方式。**这是主要矛盾,不管是mysql的innodb还是boltdb他们都是围绕这个核心来展开的。**如何将用户写进来在内存中的数据尽可能采用顺序写的方式放进磁盘,同时在用户读时,将磁盘中保存的数据以尽可能少的IO调用次数加载到内存中,进而返回用户。**这里面就涉及到具体的数据在磁盘、内存中的组织结构以及相互转换了。下面我们就对这一块进行详细的分析

这里面主要包含几块内容:一个是它在磁盘上的数据组织结构page、一个是它在内存中的数据组织结构node、还有一个是page和node之间的相互转换关系。

这里先给大家直观的科普一点:

set操作: 本质上对应的是 set->node->page->file的过程

get操作: 本质上对应的是 file->page->node->get的过程

bolt的事务

  • 同一时间有且只能有一个读写事务执行;
  • 但同一个时间可以允许有多个只读事务执行
  • 每个事务都拥有自己的一套一致性视图

提到事务,我们不得不提大家烂熟于心的事务四个特性:ACID。为方便阅读后续的内容,下面再简单回顾一下:

  1. A(atomic)原子性: 只要事务一开始(Begin),那么事务要么执行成功(Commit),要么执行失败(Rollback), 上述过程只会出现两种状态,在事务执行过程中的中间状态以及数据是不可见的。
  2. C(consistency)一致性:事务开始前和事务提交后的数据都是一致的。
  3. I(isolation)隔离性: 不同事务之间是相互隔离、互不影响的。具体的隔离程度是由具体的事务隔离级别来控制。
  4. D(duration)持久性: 事务开始前和事务提交后的数据都是永久的。不会存在数据丢失或者篡改的风险。

实现

持久性

首先boltdb是一个文件数据库,所有的数据最终都保存在文件中。当事务结束(Commit)时,会将数据进行刷盘 同时,boltdb通过冗余一份元数据来做容错。当事务提交时,如果写入到一半机器挂了,此时数据就会有问题 而当boltdb再次恢复时,会对元数据进行校验和修复。这两点就保证事务中的持久性

隔离性

其次boltdb在上层支持多个进程以只读的方式打开数据库,一个进程以写的方式打开数据库 在数据库内部中事务支持两种,读写事务和只读事务。这两类事务是互斥的 同一时间可以有多个只读事务执行,或者只能有一个读写事务执行, 上述两类事务,在底层实现时,都是保留一整套完整的视图和元数据信息,彼此之间相互隔离。因此通过这两点就保证了隔离性

原子性

在boltdb中,数据先写内存,然后再提交时刷盘。如果其中有异常发生,事务就会回滚 同时再加上同一时间只有一个进行对数据执行写入操作。所以它要么写成功提交、要么写失败回滚。也就支持原子性了

一致性

通过以上的几个特性的保证,最终也就保证了一致性

Documentation

Index

Constants

View Source
const (
	MaxKeySize   = 32768
	MaxValueSize = (1 << 31) - 2
)
View Source
const (
	DefaultMaxBatchSize  int = 1000
	DefaultMaxBatchDelay     = 10 * time.Millisecond
	// 16k
	DefaultAllocSize = 16 * 1024 * 1024
)

Default values if not set in a DB instance.

View Source
const DefaultFillPercent = 0.5
View Source
const IgnoreNoSync = runtime.GOOS == "openbsd"

IgnoreNoSync specifies whether the NoSync field of a DB is ignored when syncing changes to a file. This is required as some operating systems, such as OpenBSD, do not have a unified buffer cache (UBC) and writes must be synchronized using the msync(2) syscall.

Variables

View Source
var (
	// ErrDatabaseNotOpen is returned when a DB instance is accessed before it
	// is opened or after it is closed.
	ErrDatabaseNotOpen = errors.New("database not open")

	// ErrDatabaseOpen is returned when opening a database that is
	// already open.
	ErrDatabaseOpen = errors.New("database already open")

	// ErrInvalid is returned when both meta pages on a database are invalid.
	// This typically occurs when a file is not a bolt database.
	ErrInvalid = errors.New("invalid database")

	// ErrVersionMismatch is returned when the data file was created with a
	// different version of Bolt.
	ErrVersionMismatch = errors.New("version mismatch")

	// ErrChecksum is returned when either meta page checksum does not match.
	ErrChecksum = errors.New("checksum error")

	// ErrTimeout is returned when a database cannot obtain an exclusive lock
	// on the data file after the timeout passed to Open().
	ErrTimeout = errors.New("timeout")
)

These errors can be returned when opening or calling methods on a DB.

View Source
var (
	// ErrTxNotWritable is returned when performing a write operation on a
	// read-only transaction.
	ErrTxNotWritable = errors.New("tx not writable")

	// ErrTxClosed is returned when committing or rolling back a transaction
	// that has already been committed or rolled back.
	ErrTxClosed = errors.New("tx closed")

	// ErrDatabaseReadOnly is returned when a mutating transaction is started on a
	// read-only database.
	ErrDatabaseReadOnly = errors.New("database is in read-only mode")
)

These errors can occur when beginning or committing a Tx.

View Source
var (
	// ErrBucketNotFound is returned when trying to access a bucket that has
	// not been created yet.
	ErrBucketNotFound = errors.New("bucket not found")

	// ErrBucketExists is returned when creating a bucket that already exists.
	ErrBucketExists = errors.New("bucket already exists")

	// ErrBucketNameRequired is returned when creating a bucket with a blank name.
	ErrBucketNameRequired = errors.New("bucket name required")

	// ErrKeyRequired is returned when inserting a zero-length key.
	ErrKeyRequired = errors.New("key required")

	// ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize.
	ErrKeyTooLarge = errors.New("key too large")

	// ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize.
	ErrValueTooLarge = errors.New("value too large")

	// ErrIncompatibleValue is returned when trying create or delete a bucket
	// on an existing non-bucket key or when trying to create or delete a
	// non-bucket key on an existing bucket key.
	ErrIncompatibleValue = errors.New("incompatible value")
)

These errors can occur when putting or deleting a value or a bucket.

View Source
var DefaultOptions = &Options{
	Timeout:    0,
	NoGrowSync: false,
}

DefaultOptions represent the options used if nil options are passed into Open(). No timeout is used which will cause Bolt to wait indefinitely for a lock.

View Source
var DefaultPageSize = os.Getpagesize()

default page size for db is set to the OS page size.

Functions

This section is empty.

Types

type Bucket

type Bucket struct {
	FillPercent float64 // 填充率
	// contains filtered or unexported fields
}

一组key/value的集合 也就是一个b+树 单个Bucket的结构: [bucketElem | b_key | b_val ] b_val = val + page page : [pageHeader|leafElem1|leafElem2|...|leafElemn|k1|v1|k2|v2|...|kn|vn]

当一个桶同时满足下面两个条件的时候 视作内联 1. 当前的桶没有其他嵌套子桶 2. 当前桶内的元素所占的总字节数小于 1/4 pageSize(4K)

多个Bucket的结构:

[val1|child_inline_page1]

[bucketElem1 | bucketElem2 | bkey_1 | b_val1 | b_key2 | b_val2]

[val2|child_inline_page2]

func (*Bucket) Bucket

func (b *Bucket) Bucket(name []byte) *Bucket

Bucket 按名称检索嵌套的存储桶 根据指定的key来获取一个Bucket。如果找不到则返回nil。

func (*Bucket) CreateBucket

func (b *Bucket) CreateBucket(key []byte) (*Bucket, error)

根据 pgid 给指定的 parent 整个 child_node 用 key 创建 一个新桶

func (*Bucket) CreateBucketIfNotExists

func (b *Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error)

创建一个Bucket 根据指定的key来创建一个Bucket,如果指定key的Bucket已经存在,则会报错 如果指定的key之前有插入过元素,也会报错 否则的话,会在当前的Bucket中找到合适的位置,然后新建一个Bucket插入进去,最后返回给客户端

func (*Bucket) Cursor

func (b *Bucket) Cursor() *Cursor

既然一个Bucket逻辑上是一颗b+树,那就意味着我们可以对其进行遍历 前面提到的set、get操作,无非是要在Bucket上先找到合适的位置,然后再进行操作 而“找”这个操作就是交由Cursor(游标)来完成的 简而言之对Bucket这颗b+树的遍历工作由Cursor来执行, 一个Bucket对象关联一个Cursor。

func (*Bucket) Delete

func (b *Bucket) Delete(key []byte) error

删除一个key/value对

func (*Bucket) DeleteBucket

func (b *Bucket) DeleteBucket(key []byte) error

TODO 能不能删掉自己 删除一个Bucket

func (*Bucket) ForEach

func (b *Bucket) ForEach(fn func(k, v []byte) error) error

遍历Bucket中所有的键值对 ForEach executes a function for each key/value pair in a bucket. If the provided function returns an error then the iteration is stopped and the error is returned to the caller. The provided function must not modify the bucket; this will result in undefined behavior.

func (*Bucket) Get

func (b *Bucket) Get(key []byte) []byte

获取一个key/value对

func (*Bucket) NextSequence

func (b *Bucket) NextSequence() (uint64, error)

NextSequence 返回 Bucket 的自动递增整数

func (*Bucket) Put

func (b *Bucket) Put(key []byte, value []byte) error

插入一个key/value对

func (*Bucket) Root

func (b *Bucket) Root() pgid

返回桶的根节点(B+ Tree Root)

func (*Bucket) Sequence

func (b *Bucket) Sequence() uint64

func (*Bucket) SetSequence

func (b *Bucket) SetSequence(v uint64) error

SetSequence 更新 Bucket 的序列号

func (*Bucket) Stats

func (b *Bucket) Stats() BucketStats

Stat returns stats on a bucket.

func (*Bucket) Tx

func (b *Bucket) Tx() *Tx

返回桶当前持有的事务

func (*Bucket) Writable

func (b *Bucket) Writable() bool

判断bucket持有的事务是 读/写 事务

type BucketStats

type BucketStats struct {
	// Page count statistics.
	BranchPageN     int // number of logical branch pages
	BranchOverflowN int // number of physical branch overflow pages
	LeafPageN       int // number of logical leaf pages
	LeafOverflowN   int // number of physical leaf overflow pages

	// Tree statistics.
	KeyN  int // number of keys/value pairs
	Depth int // number of levels in B+tree

	// Page size utilization.
	BranchAlloc int // bytes allocated for physical branch pages
	BranchInuse int // bytes actually used for branch data
	LeafAlloc   int // bytes allocated for physical leaf pages
	LeafInuse   int // bytes actually used for leaf data

	// Bucket statistics
	BucketN           int // total number of buckets including the top bucket
	InlineBucketN     int // total number on inlined buckets
	InlineBucketInuse int // bytes used for inlined buckets (also accounted for in LeafInuse)
}

BucketStats records statistics about resources used by a bucket.

func (*BucketStats) Add

func (s *BucketStats) Add(other BucketStats)

type Cursor

type Cursor struct {
	// contains filtered or unexported fields
}

func (*Cursor) Bucket

func (c *Cursor) Bucket() *Bucket

返回持有当前游标的 Bucket对象

func (*Cursor) Delete

func (c *Cursor) Delete() error

Delete 从 Bucket 中删除当前 Cursor 下 K/V 如果当前键/值是存储桶或事务不可写,则删除失败

func (*Cursor) First

func (c *Cursor) First() (key []byte, value []byte)

定位到并返回该 bucket 第一个 KV

func (*Cursor) Last

func (c *Cursor) Last() (key []byte, value []byte)

定位到并返回该 bucket 最后一个 KV

func (*Cursor) Next

func (c *Cursor) Next() (key []byte, value []byte)

定位到并返回 当前Cursor所在位置的 下一个 KV 再此我们从当前位置查找前一个或者下一个时,需要注意一个问题: 如果当前节点中元素已经完了,那么此时需要回退到遍历路径的上一个节点

func (*Cursor) Prev

func (c *Cursor) Prev() (key []byte, value []byte)

定位到并返回 当前Cursor所在位置的 上一个 KV

func (*Cursor) Seek

func (c *Cursor) Seek(seek []byte) (key []byte, value []byte)

Seek 搜寻

type DB

type DB struct {
	// When enabled, the database will perform a Check() after every commit.
	// A panic is issued if the database is in an inconsistent state. This
	// flag has a large performance impact so it should only be used for
	// debugging purposes.
	StrictMode bool
	// Setting the NoSync flag will cause the database to skip fsync()
	// calls after each commit. This can be useful when bulk loading data
	// into a database and you can restart the bulk load in the event of
	// a system failure or database corruption. Do not set this flag for
	// normal use.
	//
	// If the package global IgnoreNoSync constant is true, this value is
	// ignored.  See the comment on that constant for more details.
	//
	// THIS IS UNSAFE. PLEASE USE WITH CAUTION.
	NoSync bool
	// When true, skips the truncate call when growing the database.
	// Setting this to true is only safe on non-ext3/ext4 systems.
	// Skipping truncation avoids preallocation of hard drive space and
	// bypasses a truncate() and fsync() syscall on remapping.
	//
	// https://github.com/boltdb/bolt/issues/284
	NoGrowSync bool
	// If you want to read the entire database fast, you can set MmapFlag to
	// syscall.MAP_POPULATE on Linux 2.6.23+ for sequential read-ahead.
	MmapFlags int
	// MaxBatchSize is the maximum size of a batch. Default value is
	// copied from DefaultMaxBatchSize in Open.
	//
	// If <=0, disables batching.
	//
	// Do not change concurrently with calls to Batch.
	MaxBatchSize int
	// MaxBatchDelay is the maximum delay before a batch starts.
	// Default value is copied from DefaultMaxBatchDelay in Open.
	//
	// If <=0, effectively disables batching.
	//
	// Do not change concurrently with calls to Batch.
	MaxBatchDelay time.Duration
	// AllocSize is the amount of space allocated when the database
	// needs to create new pages. This is done to amortize the cost
	// of truncate() and fsync() when growing the data file.
	AllocSize int
	// contains filtered or unexported fields
}

DB represents a collection of buckets persisted to a file on disk. All data access is performed through transactions which can be obtained through the DB. All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called.

func Open

func Open(path string, mode os.FileMode, options *Options) (*DB, error)

Open()创建数据库接口 Open()方法主要用来创建一个boltdb的DB对象,底层会执行新建或者打开存储数据的文件 当指定的文件不存在时, boltdb就会新建一个数据文件。否则的话,就直接加载指定的数据库文件内容 值的注意是,boltdb会根据Open时,options传递的参数来判断到底加互斥锁还是共享锁 新建时: 会调用init()方法,内部主要是新建一个文件

然后第0页、第1页写入元数据信息;
第2页写入freelist信息;
第3页写入bucket leaf信息。
并最终刷盘

加载时: 会读取第0页内容,也就是元信息。

然后对其进行校验和校验,当校验通过后获取pageSize。
否则的话,读取操作系统默认的pagesize(一般4k)

上述操作完成后,会通过mmap来映射数据。最后再根据磁盘页中的freelist数据初始化db的freelist字段

func (*DB) Batch

func (db *DB) Batch(fn func(*Tx) error) error

Batch()批量更新接口 Batch的本质是: 将每次写、每次刷盘的操作转变成了多次写、一次刷盘,从而提升性能

func (*DB) Begin

func (db *DB) Begin(writable bool) (*Tx, error)

Begin()开启事务接口

func (*DB) Close

func (db *DB) Close() error

func (*DB) GoString

func (db *DB) GoString() string

func (*DB) Info

func (db *DB) Info() *Info

This is for internal access to the raw data bytes from the C cursor, use carefully, or not at all.

func (*DB) IsReadOnly

func (db *DB) IsReadOnly() bool

func (*DB) Path

func (db *DB) Path() string

func (*DB) Stats

func (db *DB) Stats() Stats

Stats retrieves ongoing performance stats for the database. This is only updated when a transaction closes.

func (*DB) String

func (db *DB) String() string

func (*DB) Sync

func (db *DB) Sync() error

Sync executes fdatasync() against the database file handle.

This is not necessary under normal operation, however, if you use NoSync then it allows you to force the database file to sync against the disk.

func (*DB) Update

func (db *DB) Update(fn func(*Tx) error) error

Update()更新接口

func (*DB) View

func (db *DB) View(fn func(*Tx) error) error

View()查询接口

type Info

type Info struct {
	Data     uintptr
	PageSize int
}

type Options

type Options struct {
	// Timeout is the amount of time to wait to obtain a file lock.
	// When set to zero it will wait indefinitely. This option is only
	// available on Darwin and Linux.
	Timeout time.Duration

	// Sets the DB.NoGrowSync flag before memory mapping the file.
	NoGrowSync bool

	// Open database in read-only mode. Uses flock(..., LOCK_SH |LOCK_NB) to
	// grab a shared lock (UNIX).
	ReadOnly bool

	// Sets the DB.MmapFlags flag before memory mapping the file.
	MmapFlags int

	// InitialMmapSize is the initial mmap size of the database
	// in bytes. Read transactions won't block write transaction
	// if the InitialMmapSize is large enough to hold database mmap
	// size. (See DB.Begin for more information)
	//
	// If <=0, the initial map size is 0.
	// If initialMmapSize is smaller than the previous database size,
	// it takes no effect.
	InitialMmapSize int
}

type PageInfo

type PageInfo struct {
	ID            int
	Type          string
	Count         int
	OverflowCount int
}

type Stats

type Stats struct {
	// Freelist stats
	FreePageN     int // total number of free pages on the freelist
	PendingPageN  int // total number of pending pages on the freelist
	FreeAlloc     int // total bytes allocated in free pages
	FreelistInuse int // total bytes used by the freelist

	// Transaction stats
	TxN     int // total number of started read transactions
	OpenTxN int // number of currently open read transactions

	TxStats TxStats // global, ongoing stats.
}

Stats represents statistics about the database.

func (*Stats) Sub

func (s *Stats) Sub(other *Stats) Stats

Sub calculates and returns the difference between two sets of database stats. This is useful when obtaining stats at two different points and time and you need the performance counters that occurred within that time span.

type Tx

type Tx struct {

	// WriteFlag specifies the flag for write-related methods like WriteTo().
	// Tx opens the database file with the specified flag to copy the data.
	//
	// By default, the flag is unset, which works well for mostly in-memory
	// workloads. For databases that are much larger than available RAM,
	// set the flag to syscall.O_DIRECT to avoid trashing the page cache.
	WriteFlag int
	// contains filtered or unexported fields
}

Tx 主要封装了读事务和写事务。其中通过writable来区分是读事务还是写事务

func (*Tx) Bucket

func (tx *Tx) Bucket(name []byte) *Bucket

func (*Tx) Check

func (tx *Tx) Check() <-chan error

func (*Tx) Commit

func (tx *Tx) Commit() error

Commit()方法内部实现中,总体思路是: 1. 先判定节点要不要合并、分裂 2. 对空闲列表的判断,是否存在溢出的情况,溢出的话,需要重新分配空间 3. 将事务中涉及改动的页进行排序(保证尽可能的顺序IO),排序后循环写入到磁盘中,最后再执行刷盘 4. 当数据写入成功后,再将元信息页写到磁盘中,刷盘以保证持久化 5. 上述操作中,但凡有失败,当前事务都会进行回滚

func (*Tx) Copy

func (tx *Tx) Copy(w io.Writer) error

func (*Tx) CopyFile

func (tx *Tx) CopyFile(path string, mode os.FileMode) error

func (*Tx) CreateBucket

func (tx *Tx) CreateBucket(name []byte) (*Bucket, error)

func (*Tx) CreateBucketIfNotExists

func (tx *Tx) CreateBucketIfNotExists(name []byte) (*Bucket, error)

func (*Tx) Cursor

func (tx *Tx) Cursor() *Cursor

func (*Tx) DB

func (tx *Tx) DB() *DB

func (*Tx) DeleteBucket

func (tx *Tx) DeleteBucket(name []byte) error

func (*Tx) ForEach

func (tx *Tx) ForEach(fn func(name []byte, b *Bucket) error) error

ForEach 为 root 中的每个桶执行一个函数

func (*Tx) ID

func (tx *Tx) ID() int

func (*Tx) OnCommit

func (tx *Tx) OnCommit(fn func())

OnCommit 添加了事务成功提交后要执行的处理函数

func (*Tx) Page

func (tx *Tx) Page(id int) (*PageInfo, error)

Page returns page information for a given page number. This is only safe for concurrent use when used by a writable transaction.

func (*Tx) Rollback

func (tx *Tx) Rollback() error

Rollback()中,主要对不同事务进行不同操作: 1. 如果当前事务是只读事务,则只需要从db中的txs中找到当前事务,然后移除掉即可。 2. 如果当前事务是读写事务,则需要将空闲列表中和该事务关联的页释放掉,同时重新从freelist中加载空闲页。

func (*Tx) Size

func (tx *Tx) Size() int64

Size 返回此事务所见的当前数据库大小(以字节为单位)

func (*Tx) Writable

func (tx *Tx) Writable() bool

func (*Tx) WriteTo

func (tx *Tx) WriteTo(w io.Writer) (n int64, err error)

WriteTo 将整个数据库写入 writer

type TxStats

type TxStats struct {
	// Page statistics.
	PageCount int // number of page allocations
	PageAlloc int // total bytes allocated

	// Cursor statistics.
	CursorCount int // number of cursors created

	// Node statistics
	NodeCount int // number of node allocations
	NodeDeref int // number of node dereferences

	// Rebalance statistics.
	Rebalance     int           // number of node rebalances
	RebalanceTime time.Duration // total time spent rebalancing

	// Split/Spill statistics.
	Split     int           // number of nodes split
	Spill     int           // number of nodes spilled
	SpillTime time.Duration // total time spent spilling

	// Write statistics.
	Write     int           // number of writes performed
	WriteTime time.Duration // total time spent writing to disk
}

TxStats represents statistics about the actions performed by the transaction.

func (*TxStats) Sub

func (s *TxStats) Sub(other *TxStats) TxStats

Sub calculates and returns the difference between two sets of transaction stats. This is useful when obtaining stats at two different points and time and you need the performance counters that occurred within that time span.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL