使用golang写一个redis-cli的方法示例

2020-01-28 13:38:17于海丽

第四种批量回复:

批量回复的第一个字节为 "$",接下来是字符串表示的整数,它表示实际回复的长度,之后跟着一个 "rn",再后面跟着的是实际回复数据,最末尾是另一个 "rn"。如GET key 命令的返回值:"$7rnliangwtrn"

所以批量回复解析的实现:

读取第一行得到实际回复的长度 把字符串类型的长度转换成对应十进制整数 从第二行开始位置往下读对应长度

但是对于某些不存在的key,批量回复会将特殊值 -1 用作回复的长度值, 此时我们不需要继续往下读取实际回复。例如GET NOT_EXIST_KEY 返回值:"$-1", 所以我们需要对此特殊情况判断,让函数返回一个空对象(nil)而不是空值("")


func SingleUnMarshal(p []byte) ([]byte, int, error) {
 // ....
 case '$':
 n, err := ReadLine(p[1:])
 if err != nil {
  return []byte{}, 0, err
 }
 l, err := strconv.Atoi(string(n))
 if err != nil {
  return []byte{}, 0, err
 }
 if l == -1 {
  return nil, 0, nil
 }
 // +3 的原因 $ r n 三个字符
 result = p[len(n)+3 : len(n)+3+l]
 length = len(n) + 5 + l
 }
 return result, length, err
}

思考:

为什么redis要使用提前告知字节数,然后往下读取指定长度的方式,而不是直接读取第二行到rn为止?

答案很明显:此方式可以让redis读取返回值时不受具体的返回内容影响,在按行读取的情况下,无论使用任何分割符都有可能导致redis在解析具体内容时把内容中的分割符当作时结尾,导致解析错误。

思考一下这种情况:我们SET key "liangrnwt" ,那么当我们GET key时,服务端返回值为"$9rnliangrnwtrn" 完全规避了value中的rn影响

第五种多条批量回复:

多条批量回复是由多个回复组成的数组,它的第一个字节为"*", 后跟一个字符串表示的整数值, 这个值记录了多条批量回复所包含的回复数量, 再后面是一个"rn"。如LRANGE mylist 0 -1的返回值:"*3rn$1rn3rn$1rn2rn$1rn1"。

所以多条批量回复解析的实现:

解析第一行数据获得字符串类型的回复数量 把字符串类型的长度转换成对应十进制整数 按照单条回复依次逐个解析,一共解析成上面得到的数量

在这里我们用到了单条解析时返回的字节长度length,通过这个长度我们可以很方便的知道下次单条解析的开始位置为上一次位置+length

在解析多条批量回复时需要注意两点:

第一,多条批量回复也可以是空白的(empty)。例如执行LRANGE NOT_EXIST_KEY 0 -1 服务端返回值"*0rn"。此时客户端返回的应该空数组[][]byte

第二,多条批量回复也可以是无内容的(null multi bulk reply)。例如执行BLPOP key 1 服务端返回值"*-1rn"。此时客户端返回的应该是nil