jasper的技术小窝

关注DevOps、运维监控、Python、Golang、开源、大数据、web开发、互联网

基于Go的K/V数据库BoltDB简介

作者:jasper | 分类:Golang | 标签:       | 阅读 11092 次 | 发布:2015-07-26 11:52 a.m.

如果你想让你的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/goleveldbleveldb-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。


转载请注明出处:http://www.opscoder.info/boltdb_intro.html

其他分类: