Talent Plan( 路径二)Project 1

本文最后更新于 2024年4月9日 下午

Project1的主要内容是实现一个单机的K/V存储引擎,并通过gRPC为上层提供服务,通过RPC调用该存储引擎可以执行Put、Delete、Get、Scan四种操作。存储引擎的底层借助badger作为底层K/V数据库实现。

该项目的任务可划分为以下两个步骤,包括:

  1. 实现一个单机的存储引擎。
  2. 借助实现的存储引擎,实现K/V服务。

虽然在Project1阶段,涉及的代码量并不多,只要明确工作内容,然后在实现时注意细节便可通过全部测试点。但是Project1阶段实现的单机存储引擎与Project4实现的分布式存储引擎的数据结构有异曲同工之妙,所以对在完成Project1的同时,对StorageStorageReader的设计进行理解对后续任务的完成有一些帮助。

K/V存储引擎

TinyKV中对存储引擎进行了抽象,抽象为以下的接口:

定义在 kv/storage/storage.go

1
2
3
4
5
6
type Storage interface {
Start() error
Stop() error
Write(ctx *kvrpcpb.Context, batch []Modify) error
Reader(ctx *kvrpcpb.Context) (StorageReader, error)
}

本Project中需要实现的StandAloneStorage就是实现了以上接口的一个存储引擎。我们对StandAloneStorage的数据结构进行如下设计:

实现在 kv/storage/standalone_storage/standalone_storage.go

1
2
3
4
type StandAloneStorage struct {
conf *config.Config // 配置文件
KvDB *badger.DB // 底层数据库
}

conf字段保存存储引擎相关的配置信息对象的指针,KvDB字段保存底层数据库对象的指针。

StandAloneStorage相关的函数主要有以下几个,简要对其进行说明:

  • NewStandAloneStorage

    初始化存储引擎,函数执行时会创建StandAloneStorage对象,初始化conf字段,将KvDB字段暂时设置为nil

  • Start

    启动存储引擎,根据conf字段中保存的dbPath信息,设置badger相关的配置信息,根据设置好的配置新建badger数据库,并将数据保存在目标文件夹中。并将StandAloneStorage对象的KvDB字段设置为新建的数据库对象的指针。

  • Stop

    关闭底层数据库,根据StandAloneStorage对象conf字段中的信息,清空保存数据库的文件夹。

  • Reader

    该函数的名字并非为Read而为Reader,是因为该函数的作用并不是读取数据,而是向上层返回一个可用来读取数据的StorageReader对象,通过该reader上层在进行调用时可以根据需要实现想要的Get或Scan操作。

    StorageReader实际上也实现了一个定义好的接口:

    定义在 kv/storage/storage.go

    1
    2
    3
    4
    5
    6
    type StorageReader interface {
    // When the key doesn't exist, return nil for the value
    GetCF(cf string, key []byte) ([]byte, error)
    IterCF(cf string) engine_util.DBIterator
    Close()
    }

    为了实现StandAloneStorageReader函数,需要一个结构体实现了StorageReader接口,这个数据结构为StandaloneStorageReader

    StorageReader 定义在 /kv/storage/storage.go

    1
    2
    3
    4
    5
    6
    type StorageReader interface {
    // When the key doesn't exist, return nil for the value
    GetCF(cf string, key []byte) ([]byte, error)
    IterCF(cf string) engine_util.DBIterator
    Close()
    }

    storage.StorageReader定义在 kv/storage/standalone_storage/standalone_storage_reader.go

    1
    2
    3
    type StandaloneStorageReader struct {
    Txn *badger.Txn
    }

    该结构体实际上是对*badger.Txn的一个封装,实现接口的函数时实际上也是在通过*badger.Txn来调用badger引擎的方法来实现数据存取。

    需要注意的是:

    • GetCF方法,在调用engine_util.GetCFFromTxn方法时,可能返回 badger.ErrKeyNotFound的错误,若返回该错误,需要进行特判,将返回的val和err都设为nil。
    • IterCF方法,在调用engine_util.NewCFIterator初始化游标后,需要调用Rewind()将游标重置在初始位置。
    • Close方法,需要调用Discard()方法将连接关闭。
  • Write

    该方法接收的参数中,存在一个batch字段,对应一个storage.Modify类型的切片,该切片中保存多种修改,共有两种写操作类型:Put和Delete,需要根据每个修改的类型进行不同的操作,调用engine_util的相应方法,来实现数据写入。

K/V服务

即为单机存储引擎对外暴露的gRPC接口,来调用存储引擎进行Get、Put、Delete、Scan四种操作。

Get

  1. 调用server.storage.Reader方法初始化一个StorageReader对象。
  2. 调用该StorageReader对象的GetCF方法,返回valerr值。
  3. 根据val和err的取值情况,来进行返回对象的设置。
    • 如上文所说,在Get操作时,可能存在找不到该值的情况,这时返回的valerr都应该为nil。这时需要将返回对象的NotFound字段设置为true
    • err值不为nil,即可认为出现错误,将返回对象的Error字段设置为err
    • 其余情况为成功。

Put

初始化storage.Modify对象,将Data字段设置为storage.Put对象,根据操作类型,设置KeyValueCf三个字段。然后调用StandAloneStorageWrite方法将数据写入即可。

Delete

与Put操作类似,只是将Data字段设置为storage.Delete对象。

Scan

Scan操作同样需要用到我们上文提到的StorageReader对象。

  1. 调用server.storage.Reader方法初始化一个StorageReader对象。
  2. 调用IterCF方法返回一个游标。
  3. 使用迭代该游标,每次从游标中取出keyval
    • 由于Scan操作的请求时会携带一个limit字段,所以在迭代时我们需要对迭代次数进行判断。
    • 每次还需要对游标的有效性进行判断。

Talent Plan( 路径二)Project 1
https://siegelion.cn/2022/01/30/Talent Plan( 路径二)Project 1/
作者
siegelion
发布于
2022年1月30日
许可协议