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/

相关推荐
搬码后生仔12 分钟前
SQLite 是一个轻量级的嵌入式数据库,不需要安装服务器,直接使用文件即可。
数据库·sqlite
码农君莫笑14 分钟前
Blazor项目中使用EF读写 SQLite 数据库
linux·数据库·sqlite·c#·.netcore·人机交互·visual studio
云空15 分钟前
《解锁 Python 数据挖掘的奥秘》
开发语言·python·数据挖掘
江上挽风&sty16 分钟前
【Django篇】--动手实践Django基础知识
数据库·django·sqlite
向阳121819 分钟前
mybatis 动态 SQL
数据库·sql·mybatis
胡图蛋.21 分钟前
什么是事务
数据库
小黄人软件23 分钟前
20241220流水的日报 mysql的between可以用于字符串 sql 所有老日期的,保留最新日期
数据库·sql·mysql
青莳吖26 分钟前
Java通过Map实现与SQL中的group by相同的逻辑
java·开发语言·sql
张声录128 分钟前
【ETCD】【实操篇(三)】【ETCDCTL】如何向集群中写入数据
数据库·chrome·etcd
Buleall33 分钟前
期末考学C
java·开发语言