《Go语言四十二章经》第四十章 LevelDB与BoltDB

栏目: Go · 发布时间: 7年前

内容简介:《Go语言四十二章经》第四十章 LevelDB与BoltDB作者:李骁LevelDB 和 BoltDB 都是k/v数据库。

《Go语言四十二章经》第四十章 LevelDB与BoltDB

作者:李骁

LevelDB 和 BoltDB 都是k/v数据库。

但LevelDB没有事务,LevelDB实现了一个日志结构化的merge tree。它将有序的key/value存储在不同文件的之中,通过db, _ := leveldb.OpenFile("db", nil),在db目录下有很多数据文件,并通过“层级”把它们分开,并且周期性地将小的文件merge为更大的文件。这让其在随机写的时候会很快,但是读的时候却很慢。

这也让LevelDB的性能不可预知:但数据量很小的时候,它可能性能很好,但是当随着数据量的增加,性能只会越来越糟糕。而且做merge的线程也会在服务器上出现问题。

LSM树而且通过批量存储技术规避磁盘随机写入问题。 LSM树的设计思想非常朴素,它的原理是把一颗大树拆分成N棵小树, 它首先写入到内存中(内存没有寻道速度的问题,随机写的性能得到大幅提升),在内存中构建一颗有序小树,随着小树越来越大,内存的小树会flush到磁盘上。磁盘中的树定期可以做merge操作,合并成一棵大树,以优化读性能。

BoltDB会在数据文件上获得一个文件锁,所以多个进程不能同时打开同一个数据库。BoltDB使用一个单独的内存映射的文件(.db),实现一个写入时拷贝的B+树,这能让读取更快。而且,BoltDB的载入时间很快,特别是在从crash恢复的时候,因为它不需要去通过读log去找到上次成功的事务,它仅仅从两个B+树的根节点读取ID。

BoltDB支持完全可序列化的ACID事务,让应用程序可以更简单的处理复杂操作。

BoltDB设计源于LMDB,具有以下特点:

  • 直接使用API存取数据,没有查询语句;
  • 支持完全可序列化的ACID事务,这个特性比LevelDB强;
  • 数据保存在内存映射的文件里。没有wal、线程压缩和垃圾回收;
  • 通过COW技术,可实现无锁的读写并发,但是无法实现无锁的写写并发,这就注定了读性能超高,但写性能一般,适合与读多写少的场景。
  • 最后,BoltDB使用Golang开发,而且被应用于influxDB项目作为底层存储。

LMDB的全称是Lightning Memory-Mapped Database(快如闪电的内存映射数据库),它的文件结构简单,包含一个数据文件和一个锁文件.

LMDB文件可以同时由多个进程打开,具有极高的数据存取速度,访问简单,不需要运行单独的数据库管理进程,只要在访问数据的代码里引用LMDB库,访问时给文件路径即可。

让系统访问大量小文件的开销很大,而LMDB使用内存映射的方式访问文件,使得文件内寻址的开销非常小,使用指针运算就能实现。数据库单文件还能减少数据集复制/传输过程的开销。

40.1 LevelDB

package kvdb

import (
    "fmt"

    "github.com/syndtr/goleveldb/leveldb"
    "github.com/syndtr/goleveldb/leveldb/util"
)

func Leveldb() {
    db, _ := leveldb.OpenFile("db", nil)

    defer db.Close()
    //读写数据库:
    _ = db.Put([]byte("key1"), []byte("好好检查"), nil)
    _ = db.Put([]byte("key2"), []byte("天天向上"), nil)
    _ = db.Put([]byte("key:3"), []byte("就会一个本事"), nil)

    data, _ := db.Get([]byte("key1"), nil)
    fmt.Println(string(data))

    //迭代数据库内容:
    iter := db.NewIterator(nil, nil)
    for iter.Next() {
        key := iter.Key()
        value := iter.Value()
        fmt.Println(string(key), string(value))

    }
    iter.Release()
    iter.Error()

    //Seek-then-Iterate:
    iter = db.NewIterator(nil, nil)
    for ok := iter.Seek([]byte("key:")); ok; ok = iter.Next() {
        // Use key/value.
        fmt.Println("Seek-then-Iterate:")
        fmt.Println(string(iter.Value()))
    }
    iter.Release()

    //Iterate over subset of database content:
    iter = db.NewIterator(&util.Range{Start: []byte("key:"), Limit: []byte("xoo")}, nil)
    for iter.Next() {
        // Use key/value.

        fmt.Println("Iterate over subset of database content:")
        fmt.Println(string(iter.Value()))
    }
    iter.Release()

    //Iterate over subset of database content with a particular prefix:
    iter = db.NewIterator(util.BytesPrefix([]byte("key")), nil)
    for iter.Next() {
        // Use key/value.

        fmt.Println("Iterate over subset of database content with a particular prefix:")
        fmt.Println(string(iter.Value()))
    }
    iter.Release()

    _ = iter.Error()

    //批量写:
    batch := new(leveldb.Batch)
    batch.Put([]byte("foo"), []byte("value"))
    batch.Put([]byte("bar"), []byte("another value"))
    batch.Delete([]byte("baz"))
    _ = db.Write(batch, nil)

    _ = db.Delete([]byte("key"), nil)
}

40.2 BoltDB

package kvdb

import (
    "fmt"
    "log"
    "time"

    "github.com/boltdb/bolt"
)

func Boltdb() error {
    // Bolt 会在数据文件上获得一个文件锁,所以多个进程不能同时打开同一个数据库。
    // 打开一个已经打开的 Bolt 数据库将导致它挂起,直到另一个进程关闭它。
    // 为防止无限期等待,您可以将超时选项传递给Open()函数:
    db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second})
    defer db.Close()
    if err != nil {
        log.Fatal(err)
    }

    //  两种处理方式:读-写和只读操作,读-写方式开始于db.Update方法:
    //  Bolt 一次只允许一个读写事务,但是一次允许多个只读事务。 
// 每个事务处理都有一个始终如一的数据视图
    err = db.Update(func(tx *bolt.Tx) error {

         // 这里还有另外一层:k-v存储在bucket中,
// 可以将bucket当做一个key的集合或者是数据库中的表。
         //(顺便提一句,buckets中可以包含其他的buckets,这将会相当有用)
         // Buckets 是键值对在数据库中的集合.所有在bucket中的key必须唯一,
// 使用DB.CreateBucket() 函数建立buket
        //Tx.DeleteBucket() 删除bucket
        //b := tx.Bucket([]byte("MyBucket"))
        b, err := tx.CreateBucketIfNotExists([]byte("MyBucket"))


        //要将 key/value 对保存到 bucket,请使用 Bucket.Put() 函数:
        //这将在 MyBucket 的 bucket 中将 "answer" key的值设置为"42"。
        err = b.Put([]byte("answer"),[]byte("42"))
        return err
    })

    // 可以看到,传入db.update函数一个参数,在函数内部你可以get/set数据和处理error。
    // 如返回为nil,事务就会从数据库得到一个commit,但如果返回一个实际的错误,则会做回滚,
    // 你在函数中做的事情都不会commit。这很自然,因为你不需要人为地去关心事务的回滚,
    // 只需要返回一个错误,其他的由Bolt去帮你完成。
    // 只读事务 只读事务和读写事务不应该相互依赖,一般不应该在同一个例程中同时打开。
    // 这可能会导致死锁,因为读写事务需要定期重新映射数据文件,
// 但只有在只读事务处于打开状态时才能这样做。

    // 批量读写事务.每一次新的事物都需要等待上一次事物的结束,
// 可以通过DB.Batch()批处理来完
    err = db.Batch(func(tx *bolt.Tx) error {

        return nil
    })

    //只读事务在db.View函数之中:在函数中可以读取,但是不能做修改。
    db.View(func(tx *bolt.Tx) error {
        //要检索这个value,我们可以使用 Bucket.Get() 函数:
        //由于Get是有安全保障的,所有不会返回错误,不存在的key返回nil
        b := tx.Bucket([]byte("MyBucket"))
//tx.Bucket([]byte("MyBucket")).Cursor() 可这样写
        v := b.Get([]byte("answer"))
        id, _ := b.NextSequence()
        fmt.Printf("The answer is: %s %d \n", v, id)

        //游标遍历key
        c := b.Cursor()

        for k, v := c.First(); k != nil; k, v = c.Next() {
            fmt.Printf("key=%s, value=%s\n", k, v)
        }

        //游标上有以下函数:
        //First()  移动到第一个健.
        //Last()   移动到最后一个健.
        //Seek()   移动到特定的一个健.
        //Next()   移动到下一个健.
        //Prev()   移动到上一个健.

        //Prefix 前缀扫描
        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
    })

    //范围查找
    //另一个常见的用例是扫描范围,例如时间范围。如果你使用一个合适的时间编码,如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
    })

    //如果你知道所在桶中拥有键,你也可以使用ForEach()来迭代:
    db.View(func(tx *bolt.Tx) error {
        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
    })

    //事务处理
    // 开始事务
    tx, err := db.Begin(true)
    if err != nil {
        return err
    }
    defer tx.Rollback()

    // 使用事务...
    _, err = tx.CreateBucket([]byte("MyBucket"))
    if err != nil {
        return err
    }

    // 事务提交
    if err = tx.Commit(); err != nil {
        return err
    }
    return err

    //还可以在一个键中存储一个桶,以创建嵌套的桶:
    //func (*Bucket) CreateBucket(key []byte) (*Bucket, error)
    //func (*Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error)
    //func (*Bucket) DeleteBucket(key []byte) error
}

//备份 curl http://localhost/backup > my.db
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)
    }
}

//桶的自增
//利用nextsequence()功能,你可以让boltdb生成序列作为你键值对的唯一标识。见下面的示例。
func (s *Store) CreateUser(u *User) error {
    return s.db.Update(func(tx *bolt.Tx) error {
        // 创建users桶
        b := tx.Bucket([]byte("users"))

        // 生成自增序列
        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
}

本书《Go语言四十二章经》内容在github上同步地址: https://github.com/ffhelicopter/Go42

本书《Go语言四十二章经》内容在简书同步地址: https://www.jianshu.com/nb/29056963

虽然本书中例子都经过实际运行,但难免出现错误和不足之处,烦请您指出;如有建议也欢迎交流。

联系邮箱: roteman@163.com


以上所述就是小编给大家介绍的《《Go语言四十二章经》第四十章 LevelDB与BoltDB》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Art of Computer Programming, Volume 3

The Art of Computer Programming, Volume 3

Donald E. Knuth / Addison-Wesley Professional / 1998-05-04 / USD 74.99

Finally, after a wait of more than thirty-five years, the first part of Volume 4 is at last ready for publication. Check out the boxed set that brings together Volumes 1 - 4A in one elegant case, and ......一起来看看 《The Art of Computer Programming, Volume 3》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具