Talent Plan TinyKV Project1 StandaloneKV

6.824做完了,代码写的乱糟糟,想着重写一遍,整理下思路,后来发现了tinykv,相比于6.824还多了个事务,就准备把tinykv也做一下。

文档翻译

在本项目中,实现一个单机的、支持Column Family的KV存储服务。Column Family表示Key的命名空间,不同Column Family间可以有相同的Key存在。

服务提供Put/Delete/Get/Scan四种基本操作。

本项目可以拆解为两步去实现:

  1. 实现单机的存储引擎。
  2. 实现原生的服务接口。

tinykvpb.proto和kvrpcpb.proto定义了rpc接口和请求响应消息,kv/main.go中注册了RPC服务。

proto文件由protocol-buffer生成,不需要修改。

Server底层由Storage抽象类支持,需要为StandAloneStorage实现Storage的所有接口。

type Storage interface {
    // Other stuffs
    Write(ctx *kvrpcpb.Context, batch []Modify) error
    Reader(ctx *kvrpcpb.Context) (StorageReader, error)
}
复制代码

Storage底层由badger(类似LevelDB或RocksDB的存储引擎)支持,StandAloneStorage即badger的简单封装。

目前不需要考虑kvrpcpb.Context的涵义。

一些提示。

  1. 使用badger.Txn来实现Reader()函数,即badger提供的事务支持。
  2. badger不支持Column Family,engine_util提供了一系列函数,利用前缀来实现Column Family,使用它们来实现Write()函数。
  3. 使用Connor1996/badger而不是dgraph-io/badger。
  4. 使用Discard()关闭badger.Txn,这之前需要关闭所有迭代器。

最后就是基于Storage实现RawGet/RawScan/RawPut/RawDelete,完成后通过make project1进行测试。

StandAloneStorage

engine_util封装了badger的接口,StandAloneStorage就是要在engine_util的基础上再封装一层。因此StandAloneStorage结构也很简单。

type StandAloneStorage struct {
   engine *engine_util.Engines
}
复制代码

StandAloneStorage为抽象类Storage的具体实现,因此需要实现Write和Reader两个函数,函数签名如下。

func (s *StandAloneStorage) Write(ctx *kvrpcpb.Context, batch []storage.Modify) error
func (s *StandAloneStorage) Reader(ctx *kvrpcpb.Context) (storage.StorageReader, error)
复制代码

Project1中,还用不到kvrpcpb.Context,storage.Modify对应Put或Delete两个写操作,storage.StorageReader同样是一个抽象类。

Reader

type StorageReader interface {
   GetCF(cf string, key []byte) ([]byte, error)
   IterCF(cf string) engine_util.DBIterator
   Close()
}
复制代码

可以看到StorageReader的两个函数,屏蔽了事务,简化了接口。实现StorageReader需要用到engine_util提供的GetCFFromTxn和NewCFIterator两个函数。

func GetCFFromTxn(txn *badger.Txn, cf string, key []byte) (val []byte, err error)
func NewCFIterator(cf string, txn *badger.Txn) *BadgerIterator
复制代码

获取badger.Txn的函数engine_util并未给出,需要直接调用badger.DB.NewTransaction函数。

func (db *DB) NewTransaction(update bool) *Txn
复制代码

update为真表示Put/Delete两个写操作,为假表示Get/Scan两个读操作。

Write

type Modify struct {
   Data interface{}
}

type Put struct {
   Key   []byte
   Value []byte
   Cf    string
}

type Delete struct {
   Key []byte
   Cf  string
}
复制代码

Modify表示一个Put/Delete操作,Write中通过断言确定是Put还是Delete,进而调用engine_util提供的PutCF和DeleteCF两个函数。

func PutCF(engine *badger.DB, cf string, key []byte, val []byte) error
func DeleteCF(engine *badger.DB, cf string, key []byte) error
复制代码

其实这两个函数内部实现依旧是用了事务的,相比于Reader,Write对事务的屏蔽并没有让我们自己实现。

Server

StandAloneStorage是Storage的实现,Server还要在他之上,后续还会有RaftStorage,这样Server底层的存储引擎就是可以更换的,分别对应单机的和分布式的存储服务。

type Server struct {
   storage storage.Storage
}
复制代码

Project1要求我们为Server实现原生的服务接口,这部分在raw_api.go中,需要实现的函数的签名如下。

func (server *Server) RawGet(_ context.Context, req *kvrpcpb.RawGetRequest) (*kvrpcpb.RawGetResponse, error)
func (server *Server) RawPut(_ context.Context, req *kvrpcpb.RawPutRequest) (*kvrpcpb.RawPutResponse, error)
func (server *Server) RawDelete(_ context.Context, req *kvrpcpb.RawDeleteRequest) (*kvrpcpb.RawDeleteResponse, error)
func (server *Server) RawScan(_ context.Context, req *kvrpcpb.RawScanRequest) (*kvrpcpb.RawScanResponse, error)
复制代码

实现这四个接口,需要调用之前实现的Write、Reader.GetCF、Reader.IterCF这三个函数,一个可能不太理解的是Scan函数,因为badgerDB内部是有序存储的,所以可以根据一个Key,向后获取N个Key,RawScanRequest.Limit就是N的涵义。

engine_util封装了DBIterator,提供了Item、Valid、Next、Seek用于实现RawScan函数。

Column Family

可能你还没理解CF是什么,其实本质就是字符串而已,用作Key的前缀,起到命名空间的作用。

const (
   CfDefault string = "default"
   CfWrite   string = "write"
   CfLock    string = "lock"
)

func KeyWithCF(cf string, key []byte) []byte {
   return append([]byte(cf+"_"), key...)
}
复制代码

例如默认的CF为"default",那么该CF下的名为"apple"的Key,实际存储为"default_apple"这个字符串。

上一篇:微服务学习 --Sentinel下载与配置


下一篇:Alpha版本发布说明