boltdb 介绍
介绍
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
谁在使用
etcd、tidb、influxdb、consul ......
架构图
引入
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)运用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等时刻序列存储办法的介 ...
- CSS3 background-image背景图片相关介绍
这里将会介绍如何通过background-image设置背景图片,以及背景图片的平铺.拉伸.偏移.设置大小等操作. 1. 背景图片样式分类 CSS中设置元素背景图片及其背景图片样式的属性主要以下几个: ...
- MySQL高级知识- MySQL的架构介绍
[TOC] 1.MySQL 简介 概述 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司. MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而 ...
- Windows Server 2012 NIC Teaming介绍及注意事项
Windows Server 2012 NIC Teaming介绍及注意事项 转载自:http://www.it165.net/os/html/201303/4799.html Windows Ser ...
- Linux下服务器端开发流程及相关工具介绍(C++)
去年刚毕业来公司后,做为新人,发现很多东西都没有文档,各种工具和地址都是口口相传的,而且很多时候都是不知道有哪些工具可以使用,所以当时就想把自己接触到的这些东西记录下来,为后来者提供参考,相当于一个路 ...
- JavaScript var关键字、变量的状态、异常处理、命名规范等介绍
本篇主要介绍var关键字.变量的undefined和null状态.异常处理.命名规范. 目录 1. var 关键字:介绍var关键字的使用. 2. 变量的状态:介绍变量的未定义.已定义未赋值.已定义已 ...
- HTML DOM 介绍
本篇主要介绍DOM内容.DOM 节点.节点属性以及获取HTML元素的方法. 目录 1. 介绍 DOM:介绍DOM,以及对DOM分类和功能的说明. 2. DOM 节点:介绍DOM节点分类和节点层次. 3 ...
- HTML 事件(一) 事件的介绍
本篇主要介绍HTML中的事件知识:事件相关术语.DOM事件规范.事件对象. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三 ...
- HTML5 介绍
本篇主要介绍HTML5规范的内容和页面上的架构变动. 目录 1. HTML5介绍 1.1 介绍 1.2 内容 1.3 浏览器支持情况 2. 创建HTML5页面 2.1 <!DOCTYPE> ...
- ExtJS 4.2 介绍
本篇介绍ExtJS相关知识,是以ExtJS4.2.1版本为基础进行说明,包括:ExtJS的特点.MVC模式.4.2.1GPL版本资源的下载和说明以及4种主题的演示. 目录 1. 介绍 1.1 说明 1 ...
随机推荐
- 让 js 失效 Chrome F12 右上角 settings - Preferences - Debugger - Disable JavaScript
说的可能比较长,实际上,F12 右上角 - 右小角 还是挺好找的.
- 用户不在 sudoers 文件中。此事将被报告
在终端,进入root模式 vim /etc/sudoers 在 sudo (ALL:ALL) ALL下 添加 用户名 (ALL:ALL) ALL
- python 读取txt并绘制波形图实例解析
一 用python绘图有很多方法,笔者找到了一种最简单的方法,使用非常便利,这里分享一下: import numpy as np import matplotlib.pyplot as plt a = ...
- Performance Improvements in .NET 8 & 7 & 6 -- Thread【翻译】
线程 .NET 的最近版本在线程.并行.并发和异步等方面做出了巨大的改进,例如 ThreadPool 的完全重写(在 .NET 6 和 .NET 7 中),异步方法基础设施的完全重写(在 .NET C ...
- 03.视频播放器Api说明
03.视频播放器Api说明 目录介绍 01.最简单的播放 02.如何切换视频内核 03.切换视频模式 04.切换视频清晰度 05.视频播放监听 06.列表中播放处理 07.悬浮窗口播放 08.其他重要 ...
- App启动页面优化
目录介绍 01.存在白屏问题 1.1 问题描述 1.2 问题分析 02.解决白屏的办法 2.1 解决方案分析 2.2 第一种解决方案 2.3 第二种解决方案 2.4 注意要点 03.Applicati ...
- 使用Channel传递数据
上次我们使用了事件异步传递数据,这次我们使用Channel在一个线程通信传递数据 直接上代码 public static class ChannelSample { private static re ...
- 记录--组件库的 Table 组件表头表体是如何实现同步滚动?
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前言 在使用 Vue 3 组件库 Naive UI 的数据表格组件 DataTable 时碰到的问题,NaiveUI 的数据表格组件 Da ...
- C# SM2 解密 对接兴业银行业务
下载地址
- ftp安装与配置 云服务器 CentOS7
1.FTP的安装 #安装 yum install -y vsftpd #设置开机启动 systemctl enable vsftpd.service #启动 systemctl start vsftp ...