笔者:安大
网易游戏高级运维工程师,主要工作方向为网易游戏 Redis Saas 的开发与运维,也关注 Python 和 Rust 的最新进展。怕什么真理无穷,进一寸有进一寸的欢喜。
有任何疑问欢迎关注微信公众号:网易游戏运维平台。(长按识别上图二维码)
微信公众号原文链接:redis 应用层协议解析以及在 Python 客户端中的实现
Redis 的应用层协议设计
Redis 的通信协议设计得非常简单,具体可以参考 Redis Protocol specification,简称 RESP,在此进行一个大致的介绍。
RESP 本身并没有专门的字段标记整个请求的报文长度,它的设计思路整体针对于 命令管道(Pipeline)的需求,可以很方便地将多条命令封装在一次 tcp 报文发送中,比如:
当我们发送一个 GET A
命令,对应的报文如下:
*2\r\n$3\r\nGET\r\n$1\r\nA\r\n
而如果通过 Pipeline 发送 GET A
和 GET B
两条命令时,并不需要什么额外处理,仅仅是将两条命令按顺序发送:
*2\r\n$3\r\nGET\r\n$1\r\nA\r\n*2\r\n$3\r\nGET\r\n$1\r\nB\r\n
接下来我们进行一个较为详细完整的解析
RESP 规定了五种数据类型:
-
简单字符串(Simple Strings):以
+
作为开头,一般用于简单的字符串回复,比如set A B
这类命令的返回报文中的OK
,就是封装在简单字符串类型中。 -
错误(Errors):以
-
作为开头,用于返回错误信息,比如输入了一条不存在的命令,redis 服务会返回ERR unknown command 'xx'
,这条错误信息就封装在错误类型报文中。 -
整数(Integers):以
:
开头,用于返回整数结果,比如LLEN
命令,当我们用它统计某个列表长度时,返回的数字就封装在整数类型中。 -
二进制安全字符串(Bulk Strings):以
$
作为开头,用于承载携带数据,是最重要最常用的类型,当你向 Redis 发送命令时,命令中的字符串会被封装在二进制安全字符串,比如开篇的例子中GET
就被封装成了$3\r\nGET\r\n
这样一个二进制安全字符串报文,而一个正常 GET 命令的返回报文同样是一个二进制安全字符串。 -
数组(Arrays):以
*
开头,同样是最重要最常用的类型,开篇的例子中GET A
命令中的两个字符串GET
和A
分别被封装成了$3\r\nGET\r\n
和$1\r\nA\r\n
,然后被进一步封装成了一个数组类型*2\r\n...
,我们对 Redis 所有发送的命令都会被这样封装,先是子字符串被封装成二进制安全字符串,然后二进制安全字符串被封装成数组发往服务端。
以下是更为详细的示例:
1.1 简单字符串(Simple Strings)
简单字符串的回复通常是固定的,可以类似的理解为静态字符串,这种通常表达一种确定的、可预期的结果,比如最常见的就是 OK
和事务中返回的 QUEUED
127.0.0.1:6379> set a b
OK
127.0.0.1:6379>
OK
这个字符串不会有任何改变,也不需要携带可变的信息,它仅仅是标识这个操作成功了,不会包含其他任何可变的数据。它以 +
为开头,以 \r\n
为结尾,比如 OK
的报文就是
+OK\r\n
1.2 错误(Errors)
错误与简单字符串非常相似,不同的是它以 -
作为开头,其他并没有什么不同,它仅仅是显示一个错误信息,而这个信息在协议上并没有什么强制的规范,可以写入任意字符串信息,当然错误字符串中是不能写 \r\n
的
比如命令不存在的报错 ERR unknown command 'tt'
封装结果就是
-ERR unknown command 'tt'\r\n
1.3 整数(Integers)
整数类型也很简单,和前两种不同的是,它是可以携带数据的,类型为有符号64位整数,用于一些返回整数类型的命令,目前文档显示,会返回整数的有以下这些命令,
- SETNX
- DEL
- EXISTS
- INCR
- INCRBY
- DECR
- DECRBY
- DBSIZE
- LASTSAVE
- RENAMENX
- MOVE
- LLEN
- SADD
- SREM
- SISMEMBER
- SCARD
当然 Redis 的官方文档一直都不是很靠谱,RESP 很久没更新了,目前来看至少用于 Stream 功能的