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/

相关推荐
秃头佛爷24 分钟前
Python学习大纲总结及注意事项
开发语言·python·学习
奶糖趣多多25 分钟前
Redis知识点
数据库·redis·缓存
待磨的钝刨26 分钟前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json
CoderIsArt1 小时前
Redis的三种模式:主从模式,哨兵与集群模式
数据库·redis·缓存
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
励志成为嵌入式工程师3 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
师太,答应老衲吧3 小时前
SQL实战训练之,力扣:2020. 无流量的帐户数(递归)
数据库·sql·leetcode
捕鲸叉4 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer4 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq4 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端