介绍

BoltDB 是一个用 Go 语言编写的嵌入式键/值数据库。以下是关于 BoltDB 的一些基本介绍:

  • 键/值存储: BoltDB 为应用程序提供了简单的键/值存储接口。
  • 事务: BoltDB 支持完整的 ACID 事务。
  • 嵌入式: 与像 MySQL 或 PostgreSQL 这样的数据库系统不同,BoltDB 不运行在单独的服务器进程中。它作为一个库被直接嵌入到你的应用程序中。
  • 单文件存储: 所有的数据都存储在一个文件中,这使得备份和迁移变得简单。
  • 高效的二进制存储: 数据在磁盘上使用 B+ 树结构存储,这为随机读取提供了高性能。
  • 前缀扫描: 可以很容易地按键的前缀进行扫描,这使得它适用于范围查询。
  • 没有外部依赖: BoltDB 不依赖于任何外部系统或库。
  • 线程安全: BoltDB 是线程安全的,可以在多个 goroutines 中并发地使用。

BoltDB 特别适用于需要一个轻量级、高性能、易于部署和维护的数据库解决方案的场景。

虽然 BoltDB 非常有用,但它也有其局限性。例如,它不支持分布式存储,也不适用于需要多节点复制或分片的场景。但对于许多应用程序,它提供了一个简单且高性能的存储解决方案。

源码地址:

https://github.com/boltdb/bolt 这个项目已经不维护了, etcd 官方维护了一个 fork 库,api 是互通的: https://github.com/etcd-io/bbolt

谁在使用

etcdtidbinfluxdbconsul ......

架构图

引入

go get go.etcd.io/bbolt

使用

open a database

Bolt 中的顶级对象是 DB。它在你的硬盘上表示为一个单独的文件,并代表了你数据的一个一致性快照。

要打开你的数据库,只需使用 bolt.Open() 函数:

package main

import (
"log" bolt "go.etcd.io/bbolt"
) func main() {
db, err := bolt.Open("my.db", 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
}

请注意,Bolt 在数据文件上获得一个文件锁,因此多个进程不能同时打开同一个数据库。打开一个已经打开的 Bolt 数据库会导致它挂起,直到另一个进程关闭它。为了防止无限期的等待,你可以向 Open() 函数传递一个超时选项:

db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second})

Transactions

Bolt一次只允许一个读写 transactions ,但允许您一次进行多个只读 transactions 。每一个 transactions 在开始时都能看到数据的一致视图。

单独的 transactions 和从它们创建的所有对象(例如桶、键)都不是线程安全的。要在多个goroutines中处理数据,您必须为每一个goroutine启动一个 transactions ,或使用锁来确保一次只有一个goroutine访问一个 transactions 。从数据库创建 transactions 是线程安全的。

transactions 不应相互依赖,并且通常不应在同一goroutine中同时打开。这可能会导致死锁,因为读写 transactions 需要定期重新映射数据文件,但在任何只读 transactions 打开时都不能这样做。即使是嵌套的只读 transactions 也可能导致死锁,因为子 transactions 可能阻止父 transactions 释放其资源。

Read-write transactions

要开始一个读写 transactions ,可以使用DB.Update()函数:

err := db.Update(func(tx *bolt.Tx) error {
...
return nil
})

在闭包中,您可以看到数据库的一致视图。通过在最后返回nil来提交 transactions 。您还可以通过返回错误在任何时候回滚 transactions 。所有的数据库操作都允许在一个读写 transactions 中进行。

始终检查返回错误,因为它会报告任何可能导致您的 transactions 未完成的磁盘故障。如果您在闭包内返回错误,它将被传递。

Read-only transactions

要开始一个只读 transactions ,您可以使用DB.View()函数:

err := db.View(func(tx *bolt.Tx) error {
...
return nil
})

在这个闭包内,您也可以看到数据库的一致视图,但在只读 transactions 中不允许进行变动操作。在一个只读 transactions 中,您只能检索桶、检索值和复制数据库。

Batch read-write transactions

每个DB.Update()都会等待磁盘提交写入。这种开销可以通过使用DB.Batch()函数结合多个更新来最小化:

err := db.Batch(func(tx *bolt.Tx) error {
...
return nil
})

并发的Batch调用会被机会性地组合成更大的 transactions 。只有当有多个goroutines调用它时,Batch才是有用的。

权衡之处在于,Batch在 transactions 的部分失败时可以多次调用给定的函数。该函数必须是幂等的,且只有在成功从DB.Batch()返回后,副作用才会生效。

例如:不要从函数内部显示消息,而是在封闭范围内设置变量:

var id uint64
err := db.Batch(func(tx *bolt.Tx) error {
// 在桶中查找最后一个键,解码为bigendian uint64,增加
// 一个,编码回[]byte,并添加新键。
...
id = newValue
return nil
})
if err != nil {
return ...
}
fmt.Println("Allocated ID %d", id)

Managing transactions manually

DB.View()和DB.Update()函数是DB.Begin()函数的包装。这些助手函数将启动 transactions 、执行函数,然后在返回错误时安全地关闭您的 transactions 。这是使用Bolt transactions 的推荐方法。

然而,有时您可能希望手动开始和结束您的 transactions 。您可以直接使用DB.Begin()函数,但请确保关闭 transactions 。

// 开始一个可写的 transactions 。
tx, err := db.Begin(true)
if err != nil {
return err
}
defer tx.Rollback() // 使用 transactions ...
_, err := tx.CreateBucket([]byte("MyBucket"))
if err != nil {
return err
} // 提交 transactions 并检查错误。
if err := tx.Commit(); err != nil {
return err
}

DB.Begin()的第一个参数是一个布尔值,表示 transactions 是否应该是可写的。

Using buckets

桶是数据库中的键/值对集合。一个桶中的所有键都必须是唯一的。您可以使用Tx.CreateBucket()函数创建一个桶:

db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("MyBucket"))
if err != nil {
return fmt.Errorf("create bucket: %s", err)
}
return nil
})

您可以使用Tx.Bucket()函数检索现有的桶:

db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("MyBucket"))
if b == nil {
return fmt.Errorf("bucket does not exist")
}
return nil
})

您还可以使用Tx.CreateBucketIfNotExists()函数只在桶不存在时创建一个桶。打开数据库后,为所有的顶级桶调用此函数是一种常见的模式,这样您可以保证它们存在于未来的 transactions 中。

要删除一个桶,只需调用Tx.DeleteBucket()函数。

Using key/value pairs

要将键/值对保存到存储桶,使用Bucket.Put()函数:

db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("MyBucket"))
err := b.Put([]byte("answer"), []byte("42"))
return err
})

这会在MyBucket存储桶中将"answer"键的值设置为"42"。要检索此值,我们可以使用Bucket.Get()函数:

db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("MyBucket"))
v := b.Get([]byte("answer"))
fmt.Printf("The answer is: %s\n", v)
return nil
})

Get()函数不返回错误,因为其操作保证可以工作(除非有某种系统故障)。如果键存在,它将返回其字节切片值。如果不存在,则返回nil。重要的是要注意,您可以将零长度的值设置为一个键,这与键不存在是不同的。

使用Bucket.Delete()函数从存储桶中删除键:

db.Update(func (tx *bolt.Tx) error {
b := tx.Bucket([]byte("MyBucket"))
err := b.Delete([]byte("answer"))
return err
})

这将从MyBucket存储桶中删除答案键。

请注意,从Get()返回的值只在事务打开时有效。如果您需要在事务外部使用值,则必须使用copy()将其复制到另一个字节切片。

Autoincrementing integer for the bucket

通过使用NextSequence()函数,您可以让Bolt确定一个序列,该序列可以用作键/值对的唯一标识符。请参阅下面的示例。

// CreateUser saves u to the store. The new user ID is set on u once the data is persisted.
func (s *Store) CreateUser(u *User) error {
return s.db.Update(func(tx *bolt.Tx) error {
// Retrieve the users bucket.
// This should be created when the DB is first opened.
b := tx.Bucket([]byte("users")) // Generate ID for the user.
// This returns an error only if the Tx is closed or not writeable.
// That can't happen in an Update() call so I ignore the error check.
id, _ := b.NextSequence()
u.ID = int(id) // Marshal user data into bytes.
buf, err := json.Marshal(u)
if err != nil {
return err
} // Persist bytes to users bucket.
return b.Put(itob(u.ID), buf)
})
} // itob returns an 8-byte big endian representation of v.
func itob(v int) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(v))
return b
} type User struct {
ID int
...
}

Iterating over keys

Bolt在存储桶内按字节排序存储其键。这使得对这些键进行顺序迭代非常快。要迭代键,我们将使用一个Cursor:

db.View(func(tx *bolt.Tx) error {
// Assume bucket exists and has keys
b := tx.Bucket([]byte("MyBucket")) c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() {
fmt.Printf("key=%s, value=%s\n", k, v)
} return nil
})

游标允许您移动到键列表中的特定点,并一次向前或向后移动一个键。

以下函数在游标上可用:

  • First() 移动到第一个键。
  • Last() 移动到最后一个键。
  • Seek() 移动到特定键。
  • Next() 移动到下一个键。
  • Prev() 移动到上一个键。 每个函数都有一个返回签名(key []byte, value []byte)。当您迭代到游标的末尾时,Next()将返回一个nil键。您必须使用First()Last()Seek()移动到某个位置,然后再调用Next()Prev()。如果您没有移动到某个位置,那么这些函数将返回一个nil键。

在迭代过程中,如果键不是nil,但值是nil,那么意味着键引用的是一个存储桶而不是一个值。使用Bucket.Bucket()来访问子存储桶。

Prefix scans

要迭代键前缀,您可以结合使用Seek()bytes.HasPrefix()

db.View(func(tx *bolt.Tx) error {
// Assume bucket exists and has keys
c := tx.Bucket([]byte("MyBucket")).Cursor() prefix := []byte("1234")
for k, v := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, v = c.Next() {
fmt.Printf("key=%s, value=%s\n", k, v)
} return nil
})

Range scans

另一个常见的用例是扫描范围,例如时间范围。如果您使用可排序的时间编码,例如RFC3339,那么您可以像这样查询特定的日期范围:

db.View(func(tx *bolt.Tx) error {
// Assume our events bucket exists and has RFC3339 encoded time keys.
c := tx.Bucket([]byte("Events")).Cursor() // Our time range spans the 90's decade.
min := []byte("1990-01-01T00:00:00Z")
max := []byte("2000-01-01T00:00:00Z") // Iterate over the 90's.
for k, v := c.Seek(min); k != nil && bytes.Compare(k, max) <= 0; k, v = c.Next() {
fmt.Printf("%s: %s\n", k, v)
} return nil
})

请注意,虽然RFC3339是可排序的,但Golang的RFC3339Nano实现不使用小数点后固定数量的数字,因此不可排序。

ForEach()

如果您知道要在存储桶中迭代所有键,也可以使用ForEach()函数:

db.View(func(tx *bolt.Tx) error {
// Assume bucket exists and has keys
b := tx.Bucket([]byte("MyBucket")) b.ForEach(func(k, v []byte) error {
fmt.Printf("key=%s, value=%s\n", k, v)
return nil
})
return nil
})

请注意,ForEach()中的键和值仅在事务打开时有效。如果您需要在事务之外使用键或值,您必须使用copy()将其复制到另一个字节切片。

Nested buckets

以下是给定文档的中文翻译:

请注意,ForEach()中的键和值仅在事务打开时有效。如果您需要在事务之外使用键或值,您必须使用copy()将其复制到另一个字节切片。

嵌套的桶 您还可以在键中存储一个桶,以创建嵌套的桶。该API与DB对象上的桶管理API相同:

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

假设您有一个多租户应用程序,其中根级别的桶是帐户桶。在此桶内是一系列自身为桶的帐户。在这个序列桶里,你可以拥有许多与账户自身相关的桶(如用户、笔记等),将信息隔离到逻辑分组中。

// createUser 在给定账户中创建一个新用户。
func createUser(accountID int, u *User) error {
// 开始事务。
tx, err := db.Begin(true)
if err != nil {
return err
}
defer tx.Rollback() // 检索帐户的根桶。
// 假设在设置帐户时已经创建了此桶。
root := tx.Bucket([]byte(strconv.FormatUint(accountID, 10))) // 设置用户桶。
bkt, err := root.CreateBucketIfNotExists([]byte("USERS"))
if err != nil {
return err
} // 为新用户生成ID。
userID, err := bkt.NextSequence()
if err != nil {
return err
}
u.ID = userID // 序列化并保存编码的用户。
if buf, err := json.Marshal(u); err != nil {
return err
} else if err := bkt.Put([]byte(strconv.FormatUint(u.ID, 10)), buf); err != nil {
return err
} // 提交事务。
if err := tx.Commit(); err != nil {
return err
} return nil
}

数据库备份

Bolt是一个单一的文件,所以备份很容易。您可以使用Tx.WriteTo()函数将数据库的一致视图写入一个写入器。如果您从只读事务中调用此函数,它将执行一个热备份,并且不会阻止您的其他数据库读取和写入。

默认情况下,它将使用一个常规的文件句柄,该句柄将利用操作系统的页面缓存。请查看Tx文档,了解有关优化大于RAM数据集的信息。

一个常见的用例是通过HTTP进行备份,这样您就可以使用像cURL这样的工具进行数据库备份:

func BackupHandleFunc(w http.ResponseWriter, req *http.Request) {
err := db.View(func(tx *bolt.Tx) error {
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", `attachment; filename="my.db"`)
w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size())))
_, err := tx.WriteTo(w)
return err
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}

然后你可以使用这个命令备份:

$ curl http://localhost/backup > my.db

或者您可以打开您的浏览器访问http://localhost/backup,它将自动下载。

如果您想备份到另一个文件,您可以使用Tx.CopyFile()辅助函数。

统计信息 数据库保持对其执行的许多内部操作的持续计数,这样您可以更好地了解正在发生的事情。通过在两个时间点获取这些统计的快照,我们可以看到在这个时间范围内执行了哪些操作。

例如,我们可以启动一个goroutine,每10秒记录一次统计信息:

go func() {
// 获取初始统计。
prev := db.Stats() for {
// 等待10秒。
time.Sleep(10 * time.Second) // 获取当前统计并对其进行差异处理。
stats := db.Stats()
diff := stats.Sub(&prev) // 将统计信息编码为JSON并打印到STDERR。
json.NewEncoder(os.Stderr).Encode(diff) // 为下一个循环保存统计。
prev = stats
}
}()

将这些统计信息管道到像statsd这样的服务进行监控,或者提供一个HTTP端点来执行固定长度的样本,也很有用。

只读模式

有时创建一个共享的、只读的Bolt数据库是很有用的。要做到这一点,打开数据库时设置Options.ReadOnly标志。只读模式使用共享锁,允许多个进程从数据库中读取,但它会阻止任何进程以读写模式打开数据库。

db, err := bolt.Open("my.db", 0600, &bolt.Options{ReadOnly: true})
if err != nil {
log.Fatal(err)
}

希望这可以帮助您理解给定的文档!如果您有任何问题或需要进一步的澄清,请告诉我。

referance

boltdb 介绍的更多相关文章

  1. 关于时间序列数据库的思考——(1)运用hash文件(例如:RRD,Whisper) (2)运用LSM树来备份(例如:LevelDB,RocksDB,Cassandra) (3)运用B-树排序和k/v存储(例如:BoltDB,LMDB)

    转自:http://0351slc.com/portal.php?mod=view&aid=12 近期网络上呈现了有关catena.benchmarking boltdb等时刻序列存储办法的介 ...

  2. CSS3 background-image背景图片相关介绍

    这里将会介绍如何通过background-image设置背景图片,以及背景图片的平铺.拉伸.偏移.设置大小等操作. 1. 背景图片样式分类 CSS中设置元素背景图片及其背景图片样式的属性主要以下几个: ...

  3. MySQL高级知识- MySQL的架构介绍

    [TOC] 1.MySQL 简介 概述 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司. MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而 ...

  4. Windows Server 2012 NIC Teaming介绍及注意事项

    Windows Server 2012 NIC Teaming介绍及注意事项 转载自:http://www.it165.net/os/html/201303/4799.html Windows Ser ...

  5. Linux下服务器端开发流程及相关工具介绍(C++)

    去年刚毕业来公司后,做为新人,发现很多东西都没有文档,各种工具和地址都是口口相传的,而且很多时候都是不知道有哪些工具可以使用,所以当时就想把自己接触到的这些东西记录下来,为后来者提供参考,相当于一个路 ...

  6. JavaScript var关键字、变量的状态、异常处理、命名规范等介绍

    本篇主要介绍var关键字.变量的undefined和null状态.异常处理.命名规范. 目录 1. var 关键字:介绍var关键字的使用. 2. 变量的状态:介绍变量的未定义.已定义未赋值.已定义已 ...

  7. HTML DOM 介绍

    本篇主要介绍DOM内容.DOM 节点.节点属性以及获取HTML元素的方法. 目录 1. 介绍 DOM:介绍DOM,以及对DOM分类和功能的说明. 2. DOM 节点:介绍DOM节点分类和节点层次. 3 ...

  8. HTML 事件(一) 事件的介绍

    本篇主要介绍HTML中的事件知识:事件相关术语.DOM事件规范.事件对象. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三 ...

  9. HTML5 介绍

    本篇主要介绍HTML5规范的内容和页面上的架构变动. 目录 1. HTML5介绍 1.1 介绍 1.2 内容 1.3 浏览器支持情况 2. 创建HTML5页面 2.1 <!DOCTYPE> ...

  10. ExtJS 4.2 介绍

    本篇介绍ExtJS相关知识,是以ExtJS4.2.1版本为基础进行说明,包括:ExtJS的特点.MVC模式.4.2.1GPL版本资源的下载和说明以及4种主题的演示. 目录 1. 介绍 1.1 说明 1 ...

随机推荐

  1. 让 js 失效 Chrome F12 右上角 settings - Preferences - Debugger - Disable JavaScript

    说的可能比较长,实际上,F12 右上角 - 右小角 还是挺好找的.

  2. 用户不在 sudoers 文件中。此事将被报告

    在终端,进入root模式 vim /etc/sudoers 在    sudo (ALL:ALL) ALL下 添加 用户名 (ALL:ALL) ALL

  3. python 读取txt并绘制波形图实例解析

    一 用python绘图有很多方法,笔者找到了一种最简单的方法,使用非常便利,这里分享一下: import numpy as np import matplotlib.pyplot as plt a = ...

  4. Performance Improvements in .NET 8 & 7 & 6 -- Thread【翻译】

    线程 .NET 的最近版本在线程.并行.并发和异步等方面做出了巨大的改进,例如 ThreadPool 的完全重写(在 .NET 6 和 .NET 7 中),异步方法基础设施的完全重写(在 .NET C ...

  5. 03.视频播放器Api说明

    03.视频播放器Api说明 目录介绍 01.最简单的播放 02.如何切换视频内核 03.切换视频模式 04.切换视频清晰度 05.视频播放监听 06.列表中播放处理 07.悬浮窗口播放 08.其他重要 ...

  6. App启动页面优化

    目录介绍 01.存在白屏问题 1.1 问题描述 1.2 问题分析 02.解决白屏的办法 2.1 解决方案分析 2.2 第一种解决方案 2.3 第二种解决方案 2.4 注意要点 03.Applicati ...

  7. 使用Channel传递数据

    上次我们使用了事件异步传递数据,这次我们使用Channel在一个线程通信传递数据 直接上代码 public static class ChannelSample { private static re ...

  8. 记录--组件库的 Table 组件表头表体是如何实现同步滚动?

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前言 在使用 Vue 3 组件库 Naive UI 的数据表格组件 DataTable 时碰到的问题,NaiveUI 的数据表格组件 Da ...

  9. C# SM2 解密 对接兴业银行业务

    下载地址

  10. ftp安装与配置 云服务器 CentOS7

    1.FTP的安装 #安装 yum install -y vsftpd #设置开机启动 systemctl enable vsftpd.service #启动 systemctl start vsftp ...