golang常用库之-KV数据库之pebble

文章目录

golang常用库之-KV数据库之pebble

rocksdb是一款由Facebook使用C/C++开发的嵌入式的持久化的KV数据库。

Pebble 是 Cockroach 参考 RocksDB 并用 Go 语言开发的高性能 KV 存储引擎。

pebble

github地址:https://github.com/cockroachdb/pebble

Pebble 是一个受 LevelDB/RocksDB 启发的键值存储,专注于 CockroachDB 的性能和内部使用。Pebble 继承了 RocksDB 文件格式和一些扩展名,例如范围删除逻辑删除、表级绽放过滤器和 MANIFEST 格式更新。

Pebble 有意不追求在 RocksDB 中包含所有功能,而是专门针对 CockroachDB 所需的用例和功能集, RocksDB 有大量 Pebble 中没有实现的功能。

Pebble 对 RocksDB 进行了多项改进!

Pebble 在 CockroachDB v20.1(2020 年 5 月发布)中作为 RocksDB 的替代存储引擎引入,并在当时成功用于生产。Pebble 在 CockroachDB v20.2(2020 年 11 月发布)中成为默认存储引擎。Pebble 正在被 CockroachDB 的用户大规模地用于生产,并且被认为是稳定且可用于生产的。

官方示例代码

go 复制代码
package main

import (
	"fmt"
	"log"

	"github.com/cockroachdb/pebble"
)

func main() {
	db, err := pebble.Open("demo", &pebble.Options{})
	if err != nil {
		log.Fatal(err)
	}
	key := []byte("hello")
	if err := db.Set(key, []byte("world"), pebble.Sync); err != nil {
		log.Fatal(err)
	}
	value, closer, err := db.Get(key)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s %s\n", key, value)
	if err := closer.Close(); err != nil {
		log.Fatal(err)
	}
	if err := db.Close(); err != nil {
		log.Fatal(err)
	}
}

实战

来自项目https://github.com/NethermindEth/juno
db/pebble/db.go

go 复制代码
package pebble

import (
	"sync"
	"testing"

	"github.com/NethermindEth/juno/db"
	"github.com/NethermindEth/juno/utils"
	"github.com/cockroachdb/pebble"
	"github.com/cockroachdb/pebble/vfs"
)

const (
	// minCache is the minimum amount of memory in megabytes to allocate to pebble read and write caching.
	minCache = 8
)

var _ db.DB = (*DB)(nil)

type DB struct {
	pebble   *pebble.DB
	wMutex   *sync.Mutex
	listener db.EventListener
}

// New opens a new database at the given path
func New(path string, cache uint, maxOpenFiles int, logger pebble.Logger) (db.DB, error) {
	// Ensure that the specified cache size meets a minimum threshold.
	cache = max(minCache, cache)
	pDB, err := newPebble(path, &pebble.Options{
		Logger:       logger,
		Cache:        pebble.NewCache(int64(cache * utils.Megabyte)),
		MaxOpenFiles: maxOpenFiles,
	})
	if err != nil {
		return nil, err
	}
	return pDB, nil
}

// NewMem opens a new in-memory database
func NewMem() (db.DB, error) {
	return newPebble("", &pebble.Options{
		FS: vfs.NewMem(),
	})
}

// NewMemTest opens a new in-memory database, panics on error
func NewMemTest(t *testing.T) db.DB {
	memDB, err := NewMem()
	if err != nil {
		t.Fatalf("create in-memory db: %v", err)
	}
	t.Cleanup(func() {
		if err := memDB.Close(); err != nil {
			t.Errorf("close in-memory db: %v", err)
		}
	})
	return memDB
}

func newPebble(path string, options *pebble.Options) (*DB, error) {
	pDB, err := pebble.Open(path, options)
	if err != nil {
		return nil, err
	}
	return &DB{pebble: pDB, wMutex: new(sync.Mutex), listener: &db.SelectiveListener{}}, nil
}

// WithListener registers an EventListener
func (d *DB) WithListener(listener db.EventListener) db.DB {
	d.listener = listener
	return d
}

// NewTransaction : see db.DB.NewTransaction
func (d *DB) NewTransaction(update bool) (db.Transaction, error) {
	txn := &Transaction{
		listener: d.listener,
	}
	if update {
		d.wMutex.Lock()
		txn.lock = d.wMutex
		txn.batch = d.pebble.NewIndexedBatch()
	} else {
		txn.snapshot = d.pebble.NewSnapshot()
	}

	return txn, nil
}

// Close : see io.Closer.Close
func (d *DB) Close() error {
	return d.pebble.Close()
}

// View : see db.DB.View
func (d *DB) View(fn func(txn db.Transaction) error) error {
	return db.View(d, fn)
}

// Update : see db.DB.Update
func (d *DB) Update(fn func(txn db.Transaction) error) error {
	return db.Update(d, fn)
}

// Impl : see db.DB.Impl
func (d *DB) Impl() any {
	return d.pebble
}
  • DB 结构体:

    • pebble: Pebble 数据库的实例。
    • wMutex: 用于控制写操作的互斥锁。
    • listener: 数据库事件监听器。
  • New 函数:

    • 用于打开一个给定路径的数据库。
    • 确保指定的缓存大小满足最小阈值要求。
    • 创建一个新的 Pebble 实例,并返回数据库实例。
  • newPebble 函数: 用于创建一个新的 Pebble 实例

go 复制代码
func newPebble(path string, options *pebble.Options) (*DB, error) {
	pDB, err := pebble.Open(path, options)
	if err != nil {
		return nil, err
	}
	return &DB{pebble: pDB, wMutex: new(sync.Mutex), listener: &db.SelectiveListener{}}, nil
}

使用 pebble.Open 函数打开一个 Pebble 数据库,传入给定的路径和选项。

如果打开成功,则创建一个新的 DB 结构体实例,并初始化其中的字段:

pebble 字段使用刚刚打开的 Pebble 数据库实例。

wMutex 字段为一个新的互斥锁。

listener 字段为一个新的 db.SelectiveListener 实例的指针。

  • WithListener 方法:
    用于注册一个事件监听器。
  • Close: 关闭数据库。
  • View: 执行数据库的只读操作。
  • Update: 执行数据库的读写操作。
  • Impl: 返回底层的 Pebble 实例。
  • NewTransaction: 创建一个新的事务。
    NewTransaction 比较特殊是自定义业务有关的方法, "NewTransaction 返回一个在该数据库上的事务。如果请求创建一个更新事务,而另一个更新事务正在进行中,那么该方法应该会被阻塞。"
go 复制代码
func (d *DB) NewTransaction(update bool) (db.Transaction, error) {
	txn := &Transaction{
		listener: d.listener,
	}
	if update {
		d.wMutex.Lock()
		txn.lock = d.wMutex
		txn.batch = d.pebble.NewIndexedBatch()
	} else {
		txn.snapshot = d.pebble.NewSnapshot()
	}

	return txn, nil
}
  • 如果 update 为 true,则表示创建一个更新事务。此时,会获取 DB 对象的写入互斥锁 wMutex,将该互斥锁赋给事务对象的 lock 字段,并使用 Pebble 数据库的 NewIndexedBatch 方法创建一个新的批处理对象,并将其赋给事务对象的 batch 字段。
  • 如果 update 为 false,则表示创建一个只读事务。此时,会使用 Pebble 数据库的 NewSnapshot 方法创建一个新的快照对象,并将其赋给事务对象的 snapshot 字段。

这样做的目的是为了保证数据库的一致性和隔离性。如果多个更新事务同时进行,可能会导致数据库状态出现不一致的情况。通过阻塞方式,可以确保同一时间只有一个更新事务在进行。

db/pebble/transaction.go

go 复制代码
package pebble

import (
	"errors"
	"io"
	"sync"
	"time"

	"github.com/NethermindEth/juno/db"
	"github.com/NethermindEth/juno/utils"
	"github.com/cockroachdb/pebble"
)

var ErrDiscardedTransaction = errors.New("discarded txn")

var _ db.Transaction = (*Transaction)(nil)

type Transaction struct {
	batch    *pebble.Batch
	snapshot *pebble.Snapshot
	lock     *sync.Mutex
	listener db.EventListener
}

// Discard : see db.Transaction.Discard
func (t *Transaction) Discard() error {
	if t.batch != nil {
		if err := t.batch.Close(); err != nil {
			return err
		}
		t.batch = nil
	}
	if t.snapshot != nil {
		if err := t.snapshot.Close(); err != nil {
			return err
		}
		t.snapshot = nil
	}

	if t.lock != nil {
		t.lock.Unlock()
		t.lock = nil
	}
	return nil
}

// Commit : see db.Transaction.Commit
func (t *Transaction) Commit() error {
	start := time.Now()
	defer func() { t.listener.OnCommit(time.Since(start)) }()
	if t.batch != nil {
		return utils.RunAndWrapOnError(t.Discard, t.batch.Commit(pebble.Sync))
	}
	return utils.RunAndWrapOnError(t.Discard, ErrDiscardedTransaction)
}

// Set : see db.Transaction.Set
func (t *Transaction) Set(key, val []byte) error {
	start := time.Now()
	if t.batch == nil {
		return errors.New("read only transaction")
	}
	if len(key) == 0 {
		return errors.New("empty key")
	}

	defer func() { t.listener.OnIO(true, time.Since(start)) }()
	return t.batch.Set(key, val, pebble.Sync)
}

// Delete : see db.Transaction.Delete
func (t *Transaction) Delete(key []byte) error {
	start := time.Now()
	if t.batch == nil {
		return errors.New("read only transaction")
	}

	defer func() { t.listener.OnIO(true, time.Since(start)) }()
	return t.batch.Delete(key, pebble.Sync)
}

// Get : see db.Transaction.Get
func (t *Transaction) Get(key []byte, cb func([]byte) error) error {
	start := time.Now()
	var val []byte
	var closer io.Closer

	var err error
	if t.batch != nil {
		val, closer, err = t.batch.Get(key)
	} else if t.snapshot != nil {
		val, closer, err = t.snapshot.Get(key)
	} else {
		return ErrDiscardedTransaction
	}

	defer t.listener.OnIO(false, time.Since(start))
	if err != nil {
		if errors.Is(err, pebble.ErrNotFound) {
			return db.ErrKeyNotFound
		}

		return err
	}
	return utils.RunAndWrapOnError(closer.Close, cb(val))
}

// Impl : see db.Transaction.Impl
func (t *Transaction) Impl() any {
	if t.batch != nil {
		return t.batch
	}

	if t.snapshot != nil {
		return t.snapshot
	}
	return nil
}

// NewIterator : see db.Transaction.NewIterator
func (t *Transaction) NewIterator() (db.Iterator, error) {
	var (
		iter *pebble.Iterator
		err  error
	)
	if t.batch != nil {
		iter, err = t.batch.NewIter(nil)
		if err != nil {
			return nil, err
		}
	} else if t.snapshot != nil {
		iter, err = t.snapshot.NewIter(nil)
		if err != nil {
			return nil, err
		}
	} else {
		return nil, ErrDiscardedTransaction
	}

	return &iterator{iter: iter}, nil
}

pebble常用方法

NewSnapshot方法

NewSnapshot 方法,用于在 DB 类型上创建一个数据库的快照。

这个方法会返回一个 Snapshot 类型的对象,该对象代表了当前数据库状态的一个点时视图。

NewIndexedBatch 方法

NewIndexedBatch 方法,用于在 DB 类型上创建一个新的读写批处理对象。

参考

视频、推荐\]数据存储与检索(详解b+树存储引擎(innodb、boltdb、buntdb等)、lsm树存储引擎(bitcask、moss、pebble、leveldb等)) 参考URL: https://www.bilibili.com/video/BV1Zv411G7ty/

相关推荐
就改了几秒前
JUC小册——公平锁和非公平锁
java·开发语言
一粒沙白猫40 分钟前
Java综合练习04
java·开发语言·算法
哎呦你好44 分钟前
【CSS】Grid 布局基础知识及实例展示
开发语言·前端·css·css3
一入JAVA毁终身1 小时前
处理Lombok的一个小BUG
java·开发语言·bug
SailingCoder1 小时前
MongoDB Memory Server与完整的MongoDB的主要区别
数据库·mongodb
水木石画室1 小时前
MongoDB 常用增删改查方法及示例
数据库·mongodb
旷世奇才李先生1 小时前
MongoDB 安装使用教程
数据库·mongodb
qq_339282231 小时前
mongodb 中dbs 时,local代表的是什么
数据库·mongodb
Hellyc1 小时前
JAVA八股文:异常有哪些种类,可以举几个例子吗?Throwable类有哪些常见方法?
java·开发语言
2301_803554522 小时前
c++中的绑定器
开发语言·c++·算法