共计 4614 个字符,预计需要花费 12 分钟才能阅读完成。
自动写代码机器人,免费开通
这篇文章主要介绍 redis 协议指的是什么,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!
Redis 客户端通过使用一种叫 RESP(REdis Serialization Protocol, redis 序列化协议)协议与 Redis 服务器交互。虽然这个协议是为 Redis 而设计的,但它也可以用于其他 client-server 架构的软件系统。(译注: 从一些公开的资料来看,陌陌的 IM 协议设计就参考了 Redis 协议)
RESP 权衡了以下几个方面:
实现要简单解析要快方便人阅读
RESP 可以序列化不同的数据类型,像 integers、strings、arrays,对于错误也设计了特殊的类型。客户端以字符串参数数组的请求形式发送给 Redis 服务器执行,Redis 返回命令相关的数据类型。
RESP 是二进制安全 (binary-safe) 的,并且不需要解析由一个进程发送给另一个进程的 bulk 数据,因为它使用长度前缀来传输 bulk 数据。
注意:这里所说的协议只用于 client-server 的通信。Redis Cluster 使用不同的二进制协议在 node 间进行消息交互。
网络层
客户端通过建立端口为 6379 的 TCP 连接与 Redis 通信。
虽然 RESP 从技术上来说并不是 TCP 相关的,但对 Redis 来说该协议只用于 TCP(或者其他流式协议如 Unix 域协议)。(译注:反观 memcached, 既支持 tcp, 也支持 udp, 但实际上生产环境基本只用 tcp。我认为这是一种过度设计了,搞不好还可能被骇客利用来做 memcached udp 反射攻击。。。)
请求响应模型
Redis 接收不同参数构成的命令。当命令接收到后就会被处理,然后响应发送给客户端。
这是最简单的模型了,但有两点例外:
Redis 支持 pipelining (后文会提及)。所以客户端可以一次发送多个命令,然后等待响应。当客户端订阅了一个 Pub/Sub channel, 该协议会改变语意而变成一个推送协议,也就是说客户端不用发送命令,因为服务端在收到消息后会自动给客户端发送新的消息(对客户端订阅了的 channel)。
除了这两点,Redis 协议就是一个简单的请求 - 响应协议。
RESP 协议描述
RESP 协议在 Redis 1.2 引入,但它现在成为 Redis 2.0 的标准交互协议。你应该实现 Redis 客户端时采用该协议。
RESP 事实上是一个支持以下类型的序列化协议:Simple Strings, Errors, Integers, Bulk Strings 和 Arrays。
RESP 作为一种请求响应协议,在 Redis 中使用的方式如下:
客户端发送命令到 Redis 服务器,以 RESP Bulk Strings 数组的方式。服务器根据不同的命令实现,返回相应的 RESP 实现。
在 RESP 中,一些数据的类型由第一个字节确定:
对于 SImple Strings 响应的第一个字节是“+”对于 Errors 响应的第一个字节是 – 对于 Integers 响应第一个字节是 : 对于 Bulk Strings 响应第一个字节是 $ 对于 Arrays 响应第一个字节是 *
另外 RESP 可以用特殊的 Bulk String 或数组来表示 Null 值,后文会提及。
在 RESP 中协议不同部分总是用 \r\n (CRLF) 分隔。
RESP Simple Strings
Simple Strings 通过以下方式编码:一个加号,后跟一个字符串, 字符串不包含 CR 或 LF 字符(不能有换行),以 CRLF (\r\n)结束。
SImple Strings 以最小的代价来传输非二进制安全的字符串。例如很多 Redis 命令在成功时响应 OK,就是用 RESP Simple String 来编码的 5 个字节:
+OK\r\n
为了传输二进制安全的字符串,要用 RESP Bulk Strings。
当 Redis 响应一个 Simple String 时,客户端库应该给调用者返回从第一个 + 字符到字符串结尾的字符串,不包括 CRLF 字节。
RESP Errors
RESP 对于 error 有一种特殊的数据类型。实际上 error 就像 RESP Simple String 一样,但第一个字符串是 – 而不是加号。在 RESP 中 Simple Strings 和 Errors 两者的真正区别在于 errors 被客户端作为异常,构成 Error 类型的字符串就是字符串本身。基本格式是:
-Error message\r\n
Error 响应只会在有错误发生时才会发送,例如你操作了错误的数据类型,或者命令不存在等。当收到 Error 响应时,客户端应该抛出一个异常。
以下是 error 响应的例子:
-ERR unknown command foobar -WRONGTYPE Operation against a key holding the wrong kind of value
在 – 到第一个空格或新行间的第一个词,表示返回的错误类型。这只是 Redis 自己的一种约定,并不是 RESP Error 所规定的格式。
例如,ERR 是通用错误,而 WRONGTYPE 是一种更加具体的错误,表示客户端尝试操作错误的数据类型。这称为 Error Prefix(Error 前缀),客户端可从此得知服务器返回错误的类型而不需依赖于那个确切的消息描述,后者会随时改变。
一个客户端的实现可能对不同的 error 返回不同类型的异常,或者向调用者返回代表错误的字符串。然而这种特性并不是必须的,因为这并没什么卵用,一些精简的客户端实现可能简单的返回一般的错误情况,例如 false。
RESP Integers
这种类型就是一个代表整数的以 CRLF 结尾的字符串,并以“:”字节开头。例如 :0\r\n , 或 :1000\r\n 都是整数响应。
很多 Redis 命令返回 RESP Integers, 像 INCR, LLEN 和 LASTSAVE。
返回的整数并没什么特殊的含义,它就是 INCR 增加后的数字,LASTSAVE 的 UNIX 时间戳等。但返回的整数可以保证是在 64 位有符号整数的范围内。
整数响应也被大量的用于表示 true 或 false。例如 EXISTS 和 SISMEMBER 等命令会返回 1 表示 true,0 表示 false。
以下命令会返回一个整数: SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD。
RESP Bulk Strings
Bulk Strings 用于表示一个二进制安全的字符串,最大长度为 512M。
Bulk Strings 的编码格式如下:
“$”后跟字符串字节数(prefix length),以 CRLF 结束实际的字符串 CRLF 结束
所以字符串 foobar 被编码成:
$6\r\nfoobar\r\n
空字符串:
$0\r\n\r\n
RESP Bulk String 也可以用一种代表 Null 值的特殊格式来表示不存在的值。这种特殊格式的长度值为 -1,并且没数据,所以 Null 表示为:
$-1\r\n
这称为 Null Bulk String。
当服务器返回 Null Bulk String 时,客户端 API 不应该返回空串,而是 nil 对象。例如 Ruby 库应该返回 nil 而 C 库应该返回 NULL (或在返回的对象设置特殊的标记),等等。
RESP Arrays
客户端用 RESP Arrays 向 Redis 服务器发送命令。同样某些 Redis 命令要返回一个元素集合时也使用 RESP Arrays 作为返回的类型。一个例子是 LRANGE 命令返回一个元素列表。
RESP Arrays 使用以下格式发送:
“*”为第一个字节,后跟数组的元素个数,然后 CRLF。然后是数组中的每一个 RESP 类型表示的元素。
例如一个空数组表示为:
*0\r\n
而有两个 RESP Bulk Strings foo 和 bar 的数组编码为:
*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n
正如你所见,在数组前面的 * count CRLF 后,数组中的其他的数据类型一个接一个的连接在一起。例如一个有三个整数的数组编码如下:
*3\r\n:1\r\n:2\r\n:3\r\n
数组可以包含混合类型,它不要求所有的元素都是相同的类型。例如,一个有 4 个 interges 和 1 个 bulk string 的数组可以编码为:
*5\r\n
:1\r\n
:2\r\n
:3\r\n
:4\r\n
$6\r\n
foobar\r\n
(为清晰起见响应被分为多行)。
服务器发送的第一行 *5\r\n 表示后跟有 5 个响应,然后每个代表元素的响应被发送。
Null 数组的概念同样存在,它是 Null 值的替代方式 (通常使用 Null Bulk String,但由于历史原因我们有两种格式)。
例如当 BLPOP 命令超时,它返回一个长度为 - 1 的 Null 数组,如下所示:
*-1\r\n
在服务端返回 Null 数组时,客户端库 API 应该返回 null 对象而不是空数组。区分返回空的列表与其他的情况 (如 BLPOP 命令超时的情况) 是有必要的。
RESP 允许数组的数组。例如一个含两个数组的数组编码如下:
*2\r\n
*3\r\n
:1\r\n
:2\r\n
:3\r\n
*2\r\n
+Foo\r\n
-Bar\r\n
高效解析 Redis 协议
尽管 Redis 协议非常可读并且容易实现,它却可以兼得二进制协议的高效。
RESP 使用长度前缀来传输 bulk 数据,所以不需要像 JSON 一样扫描数据负载中的特殊符号,或者用引号括住数据负载。
Bulk 和 Multi Bulk 长度的处理可以一次处理一个字符,同时可以扫描 CR 字符,像如下的 C 代码:
#include stdio.h
int main(void) {
unsigned char *p = $123\r\n
int len = 0;
p++;
while(*p != \r) {len = (len*10)+(*p - 0
p++;
/* Now p points at \r , and the len is in bulk_len. */
printf(%d\n , len);
return 0;
}
当第一个 CR 被识别后,后面的 LF 可以忽略不处理。然后 bulk 数据可以一次读取而不需要分析 数据负载。最后剩下的 CR 和 LF 字符串可以丢弃不处理。
与二进制协议比较性能时,Redis 协议在大部分的高级语言实现起来足够简单,减少了客户端软件的 bug 数量。
注:
1. 协议中的 CR 和 LF 相当于分割符,命令间存在多个 CRLF 不应影响后续解析,应为多个 CRLF 应被忽略掉。例如:
$ (printf PING\r\nPING\r\nPING\r\n\r\n\rPING\r\n sleep 1) | nc localhost 6379
+PONG
+PONG
+PONG
+PONG
2. 对比一下 memcached 的协议,redis 的协议确实设计得比较精简:
(1) 一致的请求形式。redis 的请求都是以 Bluk String 数组发送,不同命令只是数组的元素个数不同,所有命令的处理可以先读取完整个数组再根据不同命令解析数组的参数;而不是像 mc 协议一样,不同请求的命令格式不同,那么在读取网络字节流的过程中就要对不同命令做不同的处理,增加了协议解析的难度。
(2) 长度前缀是高效解析协议的关键。字段长度信息并不是二进制协议的专利,文本协议也可以有。
以上是 redis 协议指的是什么的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注丸趣 TV 行业资讯频道!
向 AI 问一下细节
丸趣 TV 网 – 提供最优质的资源集合!