Redis 客户端与服务端通信,使用 RESP
协议,RESP
协议全程: Redis Serialization Protocol
即 Redis 序列化协议,专为 Redis 设计。RESP 协议是非TCP 专用的技术,但在 Redis 的环境中,该协议仅用于 TCP 连接。
RESP 可以序列化不同的数据类型,如整数(integers),字符串(strings),数组(arrays)。它还使用了一个特殊的类型来表示错误(errors)。请求以字符串数组的形式来表示要执行命令的参数从客户端发送到Redis服务器。Redis使用命令特有(command-specific)数据类型作为回复。RESP协议是二进制安全的,并且不需要处理从一个进程传输到另一个进程的块数据的大小,因为它使用前缀长度(prefixed-length)的方式来传输块数据的。
一、RESP 协议原理
在RESP协议中,一共将传输的数据分为5类最小单元类型,单元结束统一使用换行符号 \r\n
(CRLF) 表示
- 对于简单字符串,回复的第一个字节是“+”
- 对于错误,回复的第一个字节是“ - ”
- 对于整数,回复的第一个字节是“:”
- 对于多行字符串,回复的第一个字节是“$”
- 对于数组,回复的第一个字节是“*”
demo:
1 | // 简单字符串hello world |
以上,可以用 telnet
和 redis-cli
来连接 redis server,对比看下返回值结构,telnet
显示的就是未解析的 resp
协议字符串(\r\n
会换行)redis-cli
显示的则是解析过的结果:
redis-cli | telnet |
---|---|
![]() |
![]() |
有意思的对比:hgetall v.s. scan |
|
![]() |
![]() |
可以看到 scan
命令返回的是个二维数组,第一个 *2
表示有两个返回值,然后 *10
表示列表数组的10个元素
Redis客户端向服务器发送的指令,只有一种格式,就是多行字符串数组
比如:set username tom
, 格式化之后的指令是:
1
2
3
4
5
6
7
8
9
10 *3\r\n$3\r\nset\r\n$8\r\nusername\r\n$3\r\ntom\r\n
格式解析(每一部分之间用\r\n间隔):
*3 => 参数数量为3
$3 => 第一个字符串长度为 3
set => 第一个字符串内容为 set
$8 => 第二个字符串长度为 8
username => 第二个字符串内容为 username
$3 => 第三个参数长度为 3
tom => 第三个参数内容为tom
二、RESP 编码解码的 GoLang 实现
1、RESP 解码(客户端消息解析)
前面说了 Redis 客户端向服务器发送的指令,只有一种格式,就是多行字符串数组。
先定义一个 Request
结构体,作为输入指令解析结果的标准化,返回给后续逻辑处理:
1 | type Request struct { |
为方便解析和校验参数,读取输入参数的方式由原来的
conn.Read(buf)
替换为使用bufio.NewReader(conn)
接下来开始解析(从 conn
开始)
1 | /** |
重点:
- 解析参数开头格式,参数数量
- 通过参数(含命令)长度读取参数内容
- 校验结束符号
CRLF(\r\n)
2、RESP编码(服务端返回信息)
服务端返回信息有类型区分,可根据前文提到的五种数据类型分别组织:
1 | // 错误信息 |
为便于阅读和维护,对消息返回可做如下封装:
1 | type Reply io.WriterTo // interface, WriteTo方法往 |
【参考文档】
2、Redis深度历险 - 核心原理与应用实践(钱文品 著)