首页 > PHP技术 > php高级 > Redis 之 RESP 协议
2020
08-27

Redis 之 RESP 协议


RESP 协议简介

Redis 的客户端和服务端之间在 TCP 协议的上层采用一种独立名为 RESP(REdis Serialization Protocol) 协议作为进行通讯的标准方式。


Redis 协议在以下几点之间做出了折衷:

  • 简单的实现
  • 快速地被计算机解析
  • 简单得可以能被人工解析

新的统一协议已在Redis 1.2中引入,但是在Redis 2.0中,这就成为了与Redis服务器通讯的标准方式。在这个统一协议里,发送给Redis服务端的所有参数都是二进制安全的。Redis用不同的回复类型回复命令。它可以从服务器发送的第一个字节开始校验回复类型:

  • 单行回复(单行字符串回复),回复的第一个字节将是“+”
  • 错误消息(单行字符串回复的另外展示形式),回复的第一个字节将是“-”
  • 整型回复(正整形数字回复),回复的第一个字节将是“:”
  • 批量回复(多行字符串回复),回复的第一个字节将是“$”
  • 多个批量回复(数组回复),回复的第一个字节将是“*”


传输层及网络层

传输层仍然是底层 TCP 传输。Redis在TCP端口6379上监听到来的连接,客户端连接到来时,Redis服务器为此创建一个TCP连接。在客户端与服务器端之间传输的每个Redis命令或者数据都以\r\n结尾。Redis接收由不同参数组成的命令。一旦收到命令,将会立刻被处理,并回复给客户端。


关于校验回复类型


单行回复(单行字符串回复):"+"

状态回复(或者单行回复)以“+”开始以“\r\n”结尾的单行字符串形式。例如:

> set name leeprince
+OK\r\n  # 服务端实际返回
---
OK # redis-cli 客户端显示

错误消息(单行字符串回复的另外展示形式):"-"

错误回复发送类似于状态回复。唯一的不同是第一个字节用“-”代替“+”。

错误回复仅仅在一些意料之外的事情发生时发送,例如:如果你试图执行一个操作来应付错误的数据类型,或者如果命令不存在等等。所以当收到一个错误回复时,客户端将会出现一个异常。例如:

> leeprince
-(error) ERR unknown command `leeprince`, with args beginning with:\r\n  # 服务端实际返回
---
(error) ERR unknown command `leeprince`, with args beginning with:  # redis-cli 客户端显示


整型回复(正整形数字回复):":"

这种回复类型只是用CRLF结尾字符串来表示整型,用一个字节的“:”作为前缀。例如:“:0\r\n”,或者“:1000\r\n”是整型回复。

像INCR或者LASTAVE命令用整型回复作为实际回复值,此时对于返回的整型没有特殊的意思。它仅仅是为INCR、LASTSAVE的UNIX时间等增加数值。

一些命令像EXISTS将为true返回1,为false返回0。

其它命令像SADD、SREM和SETNX如果操作实际完成了的话将返回1,否则返回0。例如:

>rpush intkey intvalue01 intvalue02 intvalue03
:3\r\n   # 服务端实际返回
---
(integer) 3 # redis-cli 客户端显示

以下命令都是回复一个整型:

SETNX、DEL、EXISTS、INCR、INCRBY、DECR、DECRBY、DBSIZE、LASTSAVE、RENAMENX、MOVE、LLEN、SADD、SREM、SISMEMBER、SCARD。


批量回复(多行字符串回复):”$“

批量回复被服务器用于返回一个单二进制安全字符串。服务器发送第一行回复,该行以“$”开始后面跟随实际要发送的字节数,随后是CRLF,然后发送实际数据,随后是2个字节的额外数据用于最后的CRLF。例如:

>get bulkkey
$9\r\nbulkvalue\r\n   # 服务端实际返回
---
"bulkvalue"  # redis-cli 客户端显示

如果请求的值不存在,批量回复将使用特殊的值-1来作为数据长度,例如:

>get bulkkey_
$-1 # 服务端实际返回
---
(nil) # redis-cli 客户端显示


多个批量回复(数组回复):”*“

像命令LRNGE需要返回多个值(列表的每个元素是一个值,而LRANGE需要返回多于一个单元素)。使用多批量写是有技巧的,用一个初始行作为前缀来指示多少个批量写紧随其后。批量回复的第一个字节总是*,例如:

127.0.0.1:6379> rpush arraykey a01 a0101
(integer) 2 # redis-cli 客户端显示
127.0.0.1:6379> lrange arraykey 0 -1
# 服务端实际返回
*2\r\n$3\r\na01$5\r\na0101\r\n  
---
# redis-cli 客户端显示    
1) "a01"
2) "a0101"

正如您可以看到的多批量回复是以完全相同的格式使用Redis统一协议将命令发送给服务器。

服务器发送的第一行是*4\r\n,用于指定紧随着4个批量回复。然后传送每个批量写。

如果指定的键不存在,则该键被认为是持有一个空的列表,且数值0被当作多批量计数值来发送,例如:

127.0.0.1:6379> LRANGE nokey 0 1
# 服务端实际返回
*0
---
# redis-cli 客户端显示    
(empty list or set)

当BLPOP命令超时时,它返回nil多批量回复。这种类型多批量回复的计数器是-1,且值被当作nil来解释。例如:

127.0.0.1:6379> BLPOP nokey 1
# 服务端实际返回
*-1
---
# redis-cli 客户端显示
(nil)
(1.09s)

当这种情况发生时,客户端库API将返回空nil对象,且不是一个空列表。这必须有别于空列表和错误条件(例如:BLPOP命令的超时条件)。


多命令和管道(pipelining)

客户端能使用同样条件为了发出多个命令。管道用于支持多命令能够被客户端用单写操作来发送,它不需要为了发送下一条命令而读取服务器的回复。所有回复都能在最后被读出。

通常Redis服务器和客户端拥有非常快速的连接,所以在客户端的实现中支持这个特性不是那么重要,如果一个应用需要在短时间内发出大量的命令,管道仍然会非常快。


管道(Pipelining) VS 脚本(Scripting)

大量 pipeline 应用场景可通过 Redis 脚本(Redis 版本 >= 2.6)得到更高效的处理,后者在服务器端执行大量工作。脚本的一大优势是可通过最小的延迟读写数据,让读、计算、写等操作变得非常快(pipeline 在这种情况下不能使用,因为客户端在写命令前需要读命令返回的结果)。

应用程序有时可能在 pipeline 中发送 EVAL 或 EVALSHA 命令。Redis 通过 SCRIPT LOAD 命令(保证 EVALSHA 成功被调用)明确支持这种情况。


Redis 大量数据插入


pipe mode的工作原理是什么?

难点是保证redis-cli在pipe mode模式下执行和netcat一样快的同时,如何能理解服务器发送的最后一个回复。

这是通过以下方式获得:

redis-cli –pipe试着尽可能快的发送数据到服务器。
读取数据的同时,解析它。
一旦没有更多的数据输入,它就会发送一个特殊的ECHO命令,后面跟着20个随机的字符。我们相信可以通过匹配回复相同的20个字符是同一个命令的行为。
一旦这个特殊命令发出,收到的答复就开始匹配这20个字符,当匹配时,就可以成功退出了。

同时,在分析回复的时候,我们会采用计数器的方法计数,以便在最后能够告诉我们大量插入数据的数据量。

扫码芷若 获取免费视频学习资料

编程学习

查 看2019高级编程视频教程免费获取