redis协议解析

前段时间看了 redis cluster的一些原理,感觉真的很有意思,作者提到了目前的 cluster 客户端还不多,本着学习的目的决定试着从零用Go写一个 redis cluster client 试试。

  • 目标

    • 支持绝大多数 redis 命令
    • 支持 cluster 协议
    • 能够做简单的集群GUI管理
  • 需要做的考虑的有

    • 协议的解析
    • 连接的管理
    • 集群状态的管理
    • 其他功能的实现

redis 协议分析

想要实现客户端,第一步想的是先理解协议。本来以为需要去看 redis 的源码的,但检索后发现网上有很多分析,官方也给出了文档。看了后发下还是比较简单的。

  • redis client 和 server 端使用一种专门为 redis 设计的协议 RESP(REdis Serialization Protocol)交互。比较简单,解析较快,而且理解方便。
  • RESP 本身没有指定是 TCP 的, 但是 redis 上下文只使用 TCP 连接(或者使用类似 Unix sockets 的等效的面向流的连接 )

RESP 协议

  • 协议用 \r\n 做间隔
  • 对于简单的字符串,以 + 开头,例如 :

    +OK\r\n 
    
  • 对于错误消息,以 - 开头 ,例如 :

    -ERR unknown command 'foobar'\r\n
    -WRONGTYPE Operation against a key holding the wrong kind of value\r\n
    
  • 对于整数, 以 : 开头, 例如 :

    :100\r\n
    
  • 对于大字符串,以 $ 开头,接着跟上字符串长度的数字。 最长512MB 。 例如:

    $6\r\nfoobar\r\n      代表一个长6的字符串, foobar 
    $0\r\n\r\n            长度为0 的空字符串
    $-1\r\n                Null
    
  • 对于数组, 以 * 开头, 接上数组元素个数。 加数组元素

    *0\r\n    一个空的数组
    *2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n 一个有两个元素的数组   foo  bar 
       数组可以有更多复杂的用法,具体的建议去看官方文档。此处就不一一介绍了
    

协议的解析测试

看了上面的介绍,也许现在你还是一头雾水,没关系。 Talk is cheap,Show me the code!
我们要实现一个 redis cluster 的客户端,首先能执行 redis 的基本命令。这个命令的执行过程是

  • 输入命令->将命令编码成字节流->通过TCP发送到服务端->服务端解析字节流->服务端执行命令->将结果编码成字节流->通过TCP链接发送给客户端->解析字节流->得到执行结果

  • 现在来试着理解代码,我们来执行一条最简单的命令 set foo bar
    根据上面的协议,需要将 set foo bar 解析为

    *3\r\n
        $3\r\nset\r\n
        $3\r\nfoo\r\n
        $3\r\bbar\r\n
    
  • 上面是将协议解析后的字节流

    package main
    
    import (
        "fmt"
        "net"
        "time"
    )
    
    func main() {
        tcpAddr, err := net.ResolveTCPAddr("tcp4", "103.25.39.118:7000")
        conn, err := net.DialTCP("tcp", nil, tcpAddr)
        if err != nil {
            fmt.Println(err, conn)
            return
        }
        req := "*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"
    
        conn.Write([]byte(req))
    
        req = "*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n"
        buffer := make([]byte, 2048)
        conn.Write([]byte(req))
        time.Sleep(10 * time.Millisecond)
        conn.Read(buffer)
        fmt.Println(buffer)
    }
    
  • 上面代码执行了两条简单的命令, set foo bar 和 get foo, 将两条命令发送给服务端,从TCP 连接中读取结果。得到了

    • 43 79 75 13 10 36 51 13 10 98 97 114 13 10
    • 转化成对应的 ascii 数组 +OK\r\n$3\r\nbar\r\n
      可见,只要将命令按照REST格式编解码,在通过TCP连接和server端通信。就能正确的执行redis 的相关命令。
      理解了协议,下一步要做的是实现 client 的 reader 和 writer 。
如果你觉得本文对你有帮助,欢迎打赏!