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

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

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

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

《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

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


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

查看所有标签

猜你喜欢:

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

周鸿祎自述

周鸿祎自述

周鸿祎 / 中信出版社 / 2014-8 / 45.00元

在很多方面,周鸿祎都是互联网领域的颠覆者。他重新定义了“微创新”,提出从细微之处着手,通过聚焦战略,以持续的创新,最终改变市场格局、为客户创造全新价值。他第一个提出了互联网免费安全的理念,也由此让奇虎360拥有了超过4亿的用户。 在《周鸿祎自述:我的互联网方法论》中,周鸿祎首次讲述了自己的互联网观、产品观和管理思想,厘清了互联网产品的本质特征和互联网时代的新趋势,列举了颠覆式创新在现实中的实......一起来看看 《周鸿祎自述》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

URL 编码/解码
URL 编码/解码

URL 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换