Go 每日一库之 nutsdb

简介nutsdb是一个完全由 Go 编写的简单、快速、可嵌入的持久化存储 。 nutsdb与我们之前介绍过的buntdb有些类似 , 但是支持List、Set、Sorted Set这些数据结构 。
快速使用先安装:
$ go get github.com/xujiajun/nutsdb后使用:
package mainimport ("fmt""log""github.com/xujiajun/nutsdb")func main() {opt := nutsdb.DefaultOptionsopt.Dir = "./nutsdb"db, err := nutsdb.Open(opt)if err != nil {log.Fatal(err)}defer db.Close()err = db.Update(func(tx *nutsdb.Tx) error {key := []byte("name")val := []byte("dj")if err := tx.Put("", key, val, 0); err != nil {return err}return nil})if err != nil {log.Fatal(err)}err = db.View(func(tx *nutsdb.Tx) error {key := []byte("name")if e, err := tx.Get("", key); err != nil {return err} else {fmt.Println(string(e.Value))}return nil})if err != nil {log.Fatal(err)}}看过前面介绍buntdb文章的小伙伴会发现 , nutsdb的简单使用与buntdb非常相似 。 首先打开数据库nutsdb.Open() , 通过选项指定数据库文件存放目录 。 数据的插入、修改和查找都是包装在一个事务方法中执行的 。 nutsdb允许同时存在多个读事务 。 但是有写事务存在时 , 其他事务不能并发执行 。 需要修改数据的操作在db.Update()的回调中执行 , 无副作用的操作在db.View()的回调中执行 。 上面代码先插入一个键值对 , 然后读取这个键 。
从代码我们可以看出 , 由于涉及数据库操作 , 需要大量的错误处理 。 为了简洁起见 , 本文后面的代码省略了错误处理 , 在实际使用中必须加上!
特性桶**桶(bucket)**有点像命名空间的概念 。 在同一个桶中的键不能重复 , 不同的桶可以包含相同的键 。 nutsdb提供的更新和查询接口都需要传入桶名 , 只是我们在最开始的例子中将桶名设置为空字符串了 。
func main() {opt := nutsdb.DefaultOptionsopt.Dir = "./nutsdb"db, _ := nutsdb.Open(opt)defer db.Close()key := []byte("name")val := []byte("dj")db.Update(func(tx *nutsdb.Tx) error {tx.Put("bucket1", key, val, 0)return nil})db.Update(func(tx *nutsdb.Tx) error {tx.Put("bucket2", key, val, 0)return nil})db.View(func(tx *nutsdb.Tx) error {e, _ := tx.Get("bucket1", key)fmt.Println("val1:", string(e.Value))e, _ = tx.Get("bucket2", key)fmt.Println("val2:", string(e.Value))return nil})}运行:
val1: djval2: dj我们可以将桶类比于 redis 中的 db 的概念 , redis 可以在不同的 db 中存储相同的键 , 但是同一个 db 的键是唯一的 。 通过 redis 客户端连接服务器后 , 使用命令select db切换不同的 db 。
更新和删除上面我们看到使用tx.Put()插入字段 , 其实tx.Put()也用来更新(如果键已存在) 。 tx.Delete()用来删除一个字段 。
func main() {opt := nutsdb.DefaultOptionsopt.Dir = "./nutsdb"db, _ := nutsdb.Open(opt)defer db.Close()key := []byte("name")val := []byte("dj")db.Update(func(tx *nutsdb.Tx) error {tx.Put("", key, val, 0)return nil})db.View(func(tx *nutsdb.Tx) error {e, _ := tx.Get("", key)fmt.Println(string(e.Value))return nil})db.Update(func(tx *nutsdb.Tx) error {tx.Delete("", key)return nil})db.View(func(tx *nutsdb.Tx) error {e, err := tx.Get("", key)if err != nil {log.Fatal(err)} else {fmt.Println(string(e.Value))}return nil})}删除后再次Get() , 返回err:
dj2020/04/27 22:28:19 key not found in the bucketexit status 1过期nutsdb支持在插入或更新键值对时设置一个过期时间 。 Put()的第四个参数即为过期时间 , 单位 s 。 传 0 表示不过期:
func main() {opt := nutsdb.DefaultOptionsopt.Dir = "./nutsdb"db, _ := nutsdb.Open(opt)defer db.Close()key := []byte("name")val := []byte("dj")db.Update(func(tx *nutsdb.Tx) error {tx.Put("", key, val, 10)return nil})db.View(func(tx *nutsdb.Tx) error {e, _ := tx.Get("", key)fmt.Println(string(e.Value))return nil})time.Sleep(10 * time.Second)db.View(func(tx *nutsdb.Tx) error {e, err := tx.Get("", key)if err != nil {log.Fatal(err)} else {fmt.Println(string(e.Value))}return nil})}插入一个数据 , 设置过期时间为 10s 。 等待 10s 之后返回err:
dj2020/04/27 22:31:16 key not found in the bucketexit status 1遍历在nutsdb的每个桶中 , 键是以字节顺序保存的 。 这使得顺序遍历异常迅速 。
前缀遍历我们可以使用PrefixScan()遍历具有特定前缀的键值对 。 它可以指定从第几个数据开始 , 返回多少条满足条件的数据 。 例如 , 每个玩家在nutsdb中保存在**user_ + 玩家id**的键中:
【Go 每日一库之 nutsdb】func main() {opt := nutsdb.DefaultOptionsopt.Dir = "./nutsdb"db, _ := nutsdb.Open(opt)defer db.Close()bucket := "user_list"prefix := "user_"db.Update(func(tx *nutsdb.Tx) error {for i := 1; i <= 300; i++ {key := []byte(prefix + strconv.FormatInt(int64(i), 10))val := []byte("dj" + strconv.FormatInt(int64(i), 10))tx.Put(bucket, key, val, 0)}return nil})db.View(func(tx *nutsdb.Tx) error {entries, _, _ := tx.PrefixScan(bucket, []byte(prefix), 25, 100)for _, entry := range entries {fmt.Println(string(entry.Key), string(entry.Value))}return nil})}