FullScan

FullScan诞生背景

fullscan命令是zankv独有的命令,其主要作用是用于备份的时候可以一次性scan出key以及对应的value,避免使用先使用scan命令然后再使用对应的get命令获取数据而产生的多次传输造成的性能浪费。

FullScan 设计思路

fullscan 在设计之初对于不同的命令采用了不同的实现,即kv,hash,list,set,zset使用了不同的函数来实现,在开发的过程中,发现有很多重用的部分,于是,经过梳理,将所有的共用代码抽取出一个单独的函数来处理,但是由于kv的处理流程相比其他的,又要简单,所以在大的流程上还是有所不同,这是最初的设计。后期经过不断的研磨,将各种类型的处理流程进行标准化之后发现,最终的不同只有在解析迭代器的时候是不同的,所以就将对迭代器解析,以及元素的提取采用一个func作为参数传进公共处理函数里面,这样,就大大简化了fullscan的设计逻辑以及代码量。

基于以上的思考与改进,则fullscan的主要逻辑集中在RockDB::fullScanCommon中,其函数原型如下:

func (db *RockDB) fullScanCommon(tp byte, key []byte, count int, match string, f itemFunc) *common.FullScanResult

其中tp 表示的是类型,f 是迭代器处理以及元素提取函数,原型如下:

type itemFunc func(*RangeLimitedIterator, glob.Glob)(*ItemContainer, error)

该函数通过传入一个迭代器,在不同的类型处理函数中来解析迭代器获取key,value等。 ItemContainer的定义如下:

type ItemContainer struct {
    table []byte
    key []byte
    item interface{}
    cursor []byte
}

fullScanCommon函数主要的逻辑是构建一个迭代器,然后调用各个类型的迭代器处理以及元素提取函数来获取一个结果,并把结果append到一个slice中。

Cursor组成

cursor由基础的cursor加上各个分区信息组成.

base64_encode(partionId1:basecursor1;pationId2:basecursor2)

其中basecursor是由key+cursor组成,对于不同的类型,格式不一样

KV

base64_encode(base64_encode(key):)

Hash

base64_encode(base64_encode(key):base64_encode(field))

List

base64_encode(base64_encode(key):base64_encode(sequence))

Set

base64_encode(base64_encode(key):base64_encode(member))

ZSet

base64_encode(base64_encode(key):base64_encode(member))

FullScan 传输格式

经过对fullscan流程的梳理,我们将fullscan对各种类型的处理逻辑进行了统一,这也涉及到对数据返回格式的统一,在这里简单介绍下fullscan对各种类型返回的格式。

所有返回的格式,都以数组的形式返回,数组的第一个元素即为key,下面分别介绍下各种类型:

KV

对于KV类型,因为一个Key对应一个Value,所以返回格式也是k,v的集合

[[key1,val1],[key2,val2],[key3,val3],...,[keyn, valn]]

Hash

Hash类型与KV类型不同的地方,是一个Key可以对应多个Filed & Value。

[[key1, [filed11, value11], [field12, value12]], [key2, [field21, value21], [field22, value22], [field23, value23]],...,[keyn, [fieldn1, valuen1], [fieldn2, valuen2],...,[fieldnn, valuenn]]]

List

List类型是一个Key后面跟n个元素,

[[key1, item11, item12, item13], [key2, item21, item22], ... ,[keyn, itemn1, itemn2,...,itemnn]]

Set

Set类型格式与List类型比较像,

[[key1, item11, item12, item13], [key2, item21, item22], ... ,[keyn, itemn1, itemn2,...,itemnn]]

ZSet

ZSet类型格式与Hash格式比较像

[[key1, [item11, score11], [item12, score12]], [key2, [item21, score21], [item22, score22], [item23, score23]], ... , [keyn, [itemn1, scoren1], [itemn2, scoren2], ..., [itemnn, scorenn]]]

FullScan 格式解析

在清楚fullscan传输格式之后,我们就很容易对其进行解析,下面以goredis为例,对各个类型进行解析,

其中c类型为*goredis.PoolConn

KV

KV类型比较简单:

ay, err := goredis.Value(c.Do("FULLSCAN", "default:test:", "KV", "count", 100))
if err != nil {
    fmt.Println(err)
    return
}
cursor := ay[0].([]byte);
fmt.Println("Cursor:", string(cursor))
a, err := goredis.MultiBulk(ay[1], nil)
if err != nil {
    fmt.Println(err)
    return
}

for idx, _ := range a {
    item := a[idx].([]interface{})
    //length must be 2
    if len(item) != 2 {
        fmt.Println("length is not 2")
        return
    }
    key := item[0].([]byte)
    value := item[1].([]byte)
    fmt.Println("Key:", string(key), "; Value:", string(value))
}

Hash

ay, err := goredis.Value(c.Do("FULLSCAN", "default:test:", "HASH", "count", 100))
if err != nil {
    fmt.Println(err)
    return
}
cursor := ay[0].([]byte);
fmt.Println("Cursor:", string(cursor))
a, err := goredis.MultiBulk(ay[1], nil)
if err != nil {
    fmt.Println(err)
    return
}

for idx, _ := range a {
    item := a[idx].([]interface{})
    length := len(item)
    key := item[0].([]byte)
    fmt.Println("Key:", string(key))
    for i := 1; i < length; i++ {
        fv := item[i].([]interface{})
        if len(fv) != 2 {
            fmt.Println("length is not 2")
            return
        }
        field := fv[0].([]byte)
        value := fv[1].([]byte)
        fmt.Println("       Field:", string(field), "; Value:", string(value))
    }
}

List

ay, err := goredis.Value(c.Do("FULLSCAN", "default:test:", "HASH", "count", 100))
if err != nil {
    fmt.Println(err)
    return
}
cursor := ay[0].([]byte);
fmt.Println("Cursor:", string(cursor))
a, err := goredis.MultiBulk(ay[1], nil)
if err != nil {
    fmt.Println(err)
    return
}

for idx, _ := range a {
    item := a[idx].([]interface{})
    length := len(item)
    key := item[0].([]byte)
    fmt.Println("Key:", string(key)
    fmt.Println("       ")
    for i := 1; i < length; i++ {
        value := item[i].([]byte)
        fmt.Print("  ", string(value))
    }
    fmt.Println("")
}

Set

ay, err := goredis.Value(c.Do("FULLSCAN", "default:test:", "HASH", "count", 100))
if err != nil {
    fmt.Println(err)
    return
}
cursor := ay[0].([]byte);
fmt.Println("Cursor:", string(cursor))
a, err := goredis.MultiBulk(ay[1], nil)
if err != nil {
    fmt.Println(err)
    return
}

for idx, _ := range a {
    item := a[idx].([]interface{})
    length := len(item)
    key := item[0].([]byte)
    fmt.Println("Key:", string(key)
    fmt.Println("       ")
    for i := 1; i < length; i++ {
        value := item[i].([]byte)
        fmt.Print("  ", string(value))
    }
    fmt.Println("")
}

ZSet

ay, err := goredis.Value(c.Do("FULLSCAN", "default:test:", "HASH", "count", 100))
if err != nil {
    fmt.Println(err)
    return
}
cursor := ay[0].([]byte);
fmt.Println("Cursor:", string(cursor))
a, err := goredis.MultiBulk(ay[1], nil)
if err != nil {
    fmt.Println(err)
    return
}

for idx, _ := range a {
    item := a[idx].([]interface{})
    length := len(item)
    key := item[0].([]byte)
    fmt.Println("Key:", string(key)
    for i = 1; i < length; i++ {
        pair := item[i].([]interface{})
        if len(pair) != 2 {
            fmt.Println("length is not 2")
            return
        }
        zvalue := pair[0].([]byte)
        zscore := pair[1].([]byte)
        fmt.Println("       Value:", string(zvalue), "; Score:", string(zscore))
    }
}