如果你想让你的Go应用中的数据持久化,大多数人会使用一些数据库。最简单最方便的选择是嵌入式数据库,有很多嵌入式数据库都是C写的,然而对于Go开发者来说,更希望使用纯粹的Golang的解决方案。
Bolt就是这么一个纯粹的Go语言版的嵌入式key/value的数据库,而且在Go的应用中很方便地去用作持久化。Bolt类似于LMDB,这个被认为是在现代kye/value存储中最好的。但是又不同于LevelDB,BoltDB支持完全可序列化的ACID事务,也不同于SQLlite,BoltDB没有查询语句,对于用户而言,更加易用。
BoltDB将数据保存在一个单独的内存映射的文件里。它没有wal、线程压缩和垃圾回收;它仅仅安全地处理一个文件。
LevelDB和BoltDB的不同
LevelDB是Google开发的,也是一个k/v的存储数据库,和BoltDB比起起来有很大的不同。对于使用者而言,最大的不同就是LevelDB没有事务。在其内部,也有很多的不同:LevelDB实现了一个日志结构化的merge tree。它将有序的key/value存储在不同文件的之中,并通过“层级”把它们分开,并且周期性地将小的文件merge为更大的文件。这让其在随机写的时候会很快,但是读的时候却很慢。这也让LevelDB的性能不可预知:但数据量很小的时候,它可能性能很好,但是当随着数据量的增加,性能只会越来越糟糕。而且做merge的线程也会在服务器上出现问题。LevelDB是C++写的,但是也有些Go的实现方式,如syndtr/goleveldb、leveldb-go。
BoltDB使用一个单独的内存映射的文件,实现一个写入时拷贝的B+树,这能让读取更快。而且,BoltDB的载入时间很快,特别是在从crash恢复的时候,因为它不需要去通过读log(其实它压根也没有)去找到上次成功的事务,它仅仅从两个B+树的根节点读取ID。
怎么install Bolt
很明显,可以用Go get的方式来install:
go get github.com/boltdb/bolt/...
使用Bolt
打开数据库并初始化事务
Bolt将所有数据都存储在一个文件中,这让它很容易使用和部署,用户肯定很高兴地发现他们根本不需要去配置数据库或是要DBA去维护它,我们需要对这文件所做的就是打开他们 ,如果你想要打开的文件不存在就会新建。
在这个例子中我们blog.db这个数据库在当前文件夹:
package main
import (
"log"
"github.com/boltdb/bolt"
)
func main() {
db, err := bolt.Open("blog.db", 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
// ...
}
在你打开之后,你有两种处理它的方式:读-写和只读操作,读-写方式开始于db.Update方法:
err := db.Update(func(tx *bolt.Tx) error {
// ...read or write...
return nil
})
可以看到,你传入了db.update函数一个参数,在函数内部你可以get/set数据和处理error。如果返回为nil,事务就会从数据库得到一个commit,但是如果你返回一个实际的错误,则会做回滚,你在函数中做的任何事情都不会commit到磁盘上。这很方便和自然,因为你不需要人为地去关心事务的回滚,只需要返回一个错误,其他的由Bolt去帮你完成。
只读事务在db.View函数之中:
err := db.View(func(tx *bolt.Tx) error {
// ...
return nil
})
在函数中你可以读取,但是不能做修改。
存储数据
Bolt是一个k/v的存储并提供了一个映射表,这意味着你可以通过name拿到值,就像Go原生的map,但是另外因为key是有序的,所以你可以通过key来遍历。
这里还有另外一层:k-v存储在bucket中,你可以将bucket当做一个key的集合或者是数据库中的表。(顺便提一句,buckets中可以包含其他的buckets,这将会相当有用)
你可以通过下面打方法update数据库;
db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte("posts"))
if err != nil {
return err
}
return b.Put([]byte("2015-01-01"), []byte("My New Year post"))
})
我们新建了一个名为“posts”的bucket,然后将key为“2014-01-01”的vaue置为“My New Year Post”。注意bucket的名字、key和value都是bytes的slices。
读取数据
在你存储了一些值到数据库之后,你可以这样读取他们:
db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("posts"))
v := b.Get([]byte("2015-01-01"))
fmt.Printf("%sn", v)
return nil
})
你可以在Bolt的Readme里读到更多处理事务和遍历key的例子。
Go中的简单持久化
Bolt存储bytes,但是如果我们想存储一些结构体呢?这很容易通过Go的标准库实现,我们可以存储Json或是Gob编码后的结构化数据。或者,可以不限你自己使用标准库,你也可以使用Protocal Buffer或是其他的序列化方法。
例如,如果你想要存储一个博客的描述:
type Post struct {
Created time.Time
Title string
Content string
}
我们可以先编码,例如使用Json,然后将编码后的bytes存储到BoltDB中:
post := &Post{
Created: time.Now(),
Title: "My first post",
Content: "Hello, this is my first post.",
}
db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte("posts"))
if err != nil {
return err
}
encoded, err := json.Marshal(post)
if err != nil {
return err
}
return b.Put([]byte(post.Created.Format(time.RFC3339)), encoded)
})
当读取的时候,只需要将bytes unmarshal为结构体。
使用Bolt的命令行
BoltDB提供了一个名叫bolt
的命令行工具,你可以列出buckets和keys、检索values、一致性检验。
用法可以使用--help查看。
例如,检查blog.db数据库的一致性,核对每个页面: bolt check blog.db
总结
Bolt真是一个简单易用的数据库,在Go的生态系统里将会有光明的未来。现在已经成熟并成功使用在一些项目之上,并且在大的读写中性能很好。比如现在这个不让我们省心的时间序列数据库Influxdb。
网友评论