前段时间看了 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 。