redis RESP协议

 

Redis协议规范

Redis客户端是通过一个称为RESP(Redis Serialization Protocol序列化协议)的协议与Redis服务器进行通信。 虽然这个协议是专门为Redis设计的,但它可以用于其他客户端-服务器软件项目。

RESP的设计主要遵循这几个原则:

  1. 易于实施
  2. 快速解析
  3. 人类可读

RESP能够序列化不同类型的数据,比如整形数,字符串,数组,以及错误类型 。客户端将请求以字符串数组的形式发送到Redis服务器,这些字符串表示要执行的命令的参数。Redis服务器会根据不同类型的命令返回特定类型的回复。

RESP是二进制安全的,并且不需要处理跨进程间的批量数据(bulk data),因为它使用前缀长度来传输批量数据。意思是说对于bulk data类型,只要通过prefixed-length知道数据的长度就行,并不需要解析具体得数据。

注意:此处概述的协议仅用于客户端-服务器通信。 Redis Cluster使用不同的二进制协议(gossip)以在节点之间交换消息。 

网络层 

客户端是通过TCP连接来和Redis进行通信的。尽管RESP在技术上不是特定于TCP的,但在Redis的上下文中,该协议仅用于TCP连接(或等效的面向流的连接,如Unix套接字)。 

请求-响应模型 

Redis接受由不同参数组成的命令。 收到命令后,将对其进行处理并将答复发送回客户端。 这是最简单的模型,但是有两个特殊场景:  

  1. Redis支持pipeline。 因此,客户端可以一次发送多个命令,并在以后等待答复。
  2. 当Redis客户端订阅Pub / Sub通道时,该协议会更改语义并成为推送协议,也就是说,客户端不再需要发送命令,因为服务器将自动向客户端订阅的频道发送新消息。

除了上述两个例外,Redis协议就是一种简单的请求-响应协议。  

RESP协议说明

RESP协议是在Redis 1.2中引入的,之后在Redis 2.0中成为客户端与Redis服务器通信的标准协议。 这是应该在Redis客户端中实现的协议。

RESP实际上是一种序列化协议,支持以下数据类型:简单字符串,错误类型,整数,批量字符串和数组。

在Redis中使用RESP作为请求-响应协议的方式如下:

  1. 客户端将命令以RESP批量字符串数组的形式发送到Redis服务器。
  2. 服务器执行命令并以RESP类型之一进行回复。

在RESP中,以字符串的第一个字节来表示数据类型:  

数据类型字符串第一个字节
简单字符串+
错误类型-
整数
批量字符串$
数组*

另外,RESP可以使用Bulk Strings或Array的特殊形式来表示Null值。 

在RESP中,协议的不同部分都是以“ \ r \ n”(CRLF)作为结尾的。

简单字符串

简单字符串的编码格式如下:一个“+”字符,后跟一个不能包含CR或LF字符的字符串(不允许换行),以CRLF终止( \ r \ n)。 简单字符串主要用于以最小的开销传输非二进制安全字符串。 例如,许多Redis命令在成功时仅回复“ OK”,这时RESP简单字符串编码只包含以下5个字节:  

“+OK/r/n”

如果要传输二进制安全的字符串,那就要用RESP Bulk Strings批量字符串替代。

当Redis用简单字符串答复时,客户端库应向调用者返回一个字符串,该字符串由'+'之后的第一个字符组成,直到字符串的末尾,不包括最后的CRLF字节。 例如上一个例子只返回“OK”.

错误类型

RESP有用于表示错误的特定数据类型。 实际上,错误与RESP简单字符串完全一样,但是第一个字符是减号“-”而不是加号。 RESP中的简单字符串和错误之间的真正区别是,客户端将错误视为异常,而组成错误类型的字符串是错误消息本身。 基本格式为: 

“-Error message\r\n” 

仅当发生错误时才发送错误答复,例如,对数据类型执行不支持的操作,或者命令不存在等等。 收到错误回复时,客户端库(hiredis等)应当抛出异常。

以下是一个错误回复的示例:

-ERR unknown command 'foobar'

-WRONGTYPE Operation against a key holding the wrong kind of value

从-”之后的第一个单词,到第一个空格或换行符,代表返回的错误类型。 这只是Redis使用的约定,不是RESP错误格式的一部分。 例如,ERR是一般错误,而WRONGTYPE是更具体的错误,它表示客户端尝试数据类型执行错误的操作。 这称为错误前缀,是一种使客户端能够知道服务器返回的错误类型的方法,而不必依赖于所给出的确切消息,该消息可能会随时间而变化。

客户端的实现可以针对不同的错误类型返回不同类型的异常,或者可以通过将错误名称作为字符串直接提供给调用方来提供捕获错误的通用方法。 但是这个功能并不是至关重要的,因为它很少有用,并且一个简单的客户端可能仅仅是简单地返回通用错误条件,例如false。

整数类型

整数类型只是一个以CRLF终止的字符串,它表示一个整数,以“:”字节为前缀。 例如,“:0 \ r \ n”或“:1000 \ r \ n”是整数类型的回复。有很多Redis命令返回RESP整数类型,比如INCR,LLEN,LASTSAVE.

返回整数类型并没有什么特殊的意义,可能仅仅是一个INCR命令增量之后的数值,或者LASTSAVE命令返回的UNIX时间戳等等。但是返回的整数必须保证是在64位有符整数的范围之内。

整数回复也广泛用于返回true或false。 例如,像EXISTS或SISMEMBER这样的命令将为true返回1,为false返回0。

其它像SADD,SREM和SETNX之类的命令,如果真正执行了操作就返回1,否则返回0.以SETNX key为例,SETNX命令只有在key不存在的时候才会去执行,否则不执行。

批量字符串(Bulk Strings)

批量字符串是用来表示二进制安全字符串,最大为512M.

批量字符串是按以下几种方式进行编码:

  1. 由“ $”字节开始,紧接着是一个前缀长度,表示组成字符串的字节数,由CRLF终止。
  2. 实际的字符串数据。
  3. 最后的CRLF。

比如“foobar"字符串是这样编码的:

"$6\r\nfoobar\r\n" 

一个空字符串的编码:

"$0\r\n\r\n"

RESP批量字符串用一种特殊格式来代表值的不存在,可以用来表示NULL值。 在这种特殊格式中,长度为-1,并且没有数据,因此Null表示为:

"$-1\r\n"

这种编码就是 Null Bulk String.

当服务器以Null Bulk String答复时,客户端库API不应返回空字符串,而应返回nil对象。 例如,Ruby库应返回“ nil”,而C库应返回NULL(或在Reply对象中设置特殊标志),依此类推。就是说客户端的实现要对RESP进行封装。

数组类型

客户端使用RESP数组来发送若干命令到Redis服务器。类似的,有些Redis命令也需要用RESP数组作为返回类型来发送元素集合到客户端。比如LEANGE命令会返回一个列表的所有元素。

RESP数组编码格式如下:

  • 一个*字符作为第一个字节,然后是数组中的元素数(作为十进制数),然后是CRLF。
  • 数组的每个元素都是某个RESP类型。

所以空数组是这样编码:

"*0\r\n"

一个包含两个RESP批量字符串(“foo”,“bar”)的数组是这样编码:

"*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"

编码的第一部分是*<count>CRLF,“*”和CRLF之间是一个十进制数,用来表示数组的长度。紧随其后是数组的元素,元素是一个挨一个串联在一起的。比如一个包含3个整数的的数组可以这样编码:

"*3\r\n:1\r\n:2\r\n:3\r\n"

RESP数组是可以包含混合类型的,就是说数组的元素可以是不同的类型。比如,一个包含4个整数和一个批量字符串的列表可以这样编码:

*5\r\n:1\r\n:2\r\n:3\r\n:4\r\n$6\r\nfoobar\r\n

为了看起来好看一点,拆分之后是这样:

*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个元素,或者说服务器发送的这行数据是要告诉客户端紧随其后的数据包含5个回复。

Null数组的概念也存在,并且是指定Null值的另一种方法(通常使用Null Bulk String,但出于历史原因,我们有两种格式)。例如,当BLPOP命令超时时,它将返回一个计数为-1的空数组,如以下示例所示:

"*-1\r\n"

 当Redis回复空数组时,客户端库API应该返回一个空对象而不是一个空数组。 并且区分空列表和其他条件(例如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

 为了便于阅读数据已经拆分成多行,实际上是一行数据。这个数据包含两个数组,其中一个包含3个整数(1,2,3),一个包含一个简单字符串和一个错误。

单个数组元素有可能是空值。这主要是在Redis回复中使用,是为了指明这些元素已经丢失了和非空字符串。这种情况可能发生在使用GET pattern选项的SORT命令,指定的值丢失的情况。一个例子:

*3\r\n

$3\r\n

foo\r\n

$-1\r\n

$3\r\n

bar\r\n

第二个元素就是空值,客户端库可能会返回:

["foo",nil,"bar"]

这个空值比不是前面所讲的异常,只是为了进一步说明协议的一个例子。

发送命令到服务器

熟悉了RESP序列化协议之后,编写Redis客户端库的实现将变得很容易。 我们可以进一步指定客户端和服务器之间的交互工作方式:

  1. 客户端向Redis服务器发送仅由批量字符串组成的RESP数组。
  2. Redis服务器将有效的RESP数据类型作为回复发送到客户端。

举一个典型的交互例子。 客户端发送命令LLEN mylist以获得存储在mylist上的链表的长度,服务器回复一个整数。

那么客户端会发送:"*2\r\n$4\r\nLLEN\r\n$6\r\nmylist\r\n".

服务器回复“:48293/r/n”.

为了方便阅读,进行拆分(C:是客户机,S:服务器):

C: *2\r\n

C: $4\r\n

C: LLEN\r\n

C: $6\r\n

C: mylist\r\n  

S: :48293\r\n

用于Redis协议的高性能解析器

 Redis协议非常易于阅读,并且易于实现,可以实现与二进制协议相似的性能。 RESP使用带前缀的长度来传输批量数据,因此不需要像json那样扫描数据来查找特殊字符,也不需要查询发送到服务器的数据。

对于Bulk 和 Multi Bulk的长度,可以对前缀长度的字符串进行扫描来计算 ,例如以下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之后,bulk的长度就已经计算出来了,所以LF不需要处理直接跳过。剩下的数据可以通过一个read调用来读取(因为数据长度已经知道了),不需要对数据做什么特殊处理。数据末尾的CRLF就可以直接忽略。

从性能上来讲,Redis协议和其它二进制协议都差不多,但它在大多数高级语言中的实现明显要简单得多,从而减少了客户端软件中的bug数量。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值