Redis客户端和Redis服务器通过一个叫做RESP(REdis Serialization Protocol,Redis序列化协议)的协议进行通讯。虽然这个协议是为Redis设计的,但是它也能被用在其它的客户端-服务器软件项目。

RESP是以下几个方面妥协的结果:

  • 易于实现
  • 快速解析
  • 可读性好

RESP可以序列化不同的数据类型,比如整型,字符串,数组。另外还有特定的类型表示错误。请求由客户端以字符串数组(要执行的命令的参数)的形式发往Redis服务器。Redis回复一个和命令对应的数据类型。

RESP二进制安全的,并且不需要处理进程间传输的批量数据,因为它使用prefixed-length来传输批量数据。

Note:这里讨论的协议仅仅用于客户端-服务器通信。Redis集群使用一个不同的二进制协议来交换不同节点之间的消息。

网络层

一个客户端连接到一个Redis服务器,就是创建一个TCP连接。尽管RESP是和TCP独立的,到那时在Redis的上下文下面,我们仅仅和TCP(或者等价的像Unix套接字这样的面向流的连接)一起使用。

请求相应模型

Redis接收由不同参数组成的命令。一旦一个命令接收到,它被处理并且一个reply会被发送回客户端。这是可能的最简单的模型,然而有两个例外:

  • Redis支持管道(会在这个本章的后面提到)。也就是说,一个客户端可以一次性发送多个命令,然后等待replies。
  • 当一个Redis客户端订阅一个Pub/Sub channel的时候,这个协议改变语义,变成push协议。也就是说,客户端不再需要发送命令,因为服务器只要接受到信息就会自动给客户端发送消息(对于客户端订阅的channels)。

除了上述两个例外,Redis协议就是一个请求相应协议。

RESP协议描述

RESP协议在Redis1.2的时候开始引入,在Redis2.0的时候成为和Redis服务器通信的标准。这是一个你需要在你的Redis客户端中实现的协议。

RESP实际上是一个支持一下数据类型的序列化协议:Simple Strings,Errors,Integers,Bulk strings 和Arrays。

RESP在Redis下面被应用为请求相应协议的方式如下:

  • 客户端给Redis服务器发送的commands:一个RESP Bulk string数组。
  • 根据command的实现,服务器会回复一个RESP数据类型。

在RESP,数据的类型由第一个字节决定:

  • Simple string:“+”。
  • Errors:“-”。
  • Integer:“:”。 Bulk strings:“$”。 Arrays:“*”。 另外,RESP可以通过一个Bulk string或者Array的变体表示Null。在RESP,协议的不同部分都会用“\n\r”(CRLF)来表示结尾。

RESP simple strings

Simple strings 通过以下方式编码:一个加号,string(不能包含CR和LF),由CRLF终止。

Simple strings被用来以最小的overhead传输非二进制安全的字符串。比如,很多Redis命令会回复“OK”当执行成功的时候。这个时候就是RESP Simple strings:

“+OK\r\n”

如果要发送二进制安全的字符串就要使用RESP bulk strings。

当Redis用一个simple string回复的时候,一个客户端应该返回给调用者一个由“+”开始的字符串,但是省略CRLF。

RESP Errors

RESP有一个专门针对Errors的数据类型。事实上errors真的很像RESP simple string,但是第一个字符是一个“-”。errors和simple string真正的差异是对于客户端来说的,Errors被当作一种异常,其中的string被当作错误消息。基本的形式如下:

"-Error message\r\n"

Error只有当有错误出现的时候才会由Redis发送,比如你在一个错误的数据类型上进行一个操作,或者command不存在等等。客户端应当在收到Error回复的时候抛出异常。以下是Errors的例子:

-ERR unknown command 'foobar'
-WRONGTYPE Operation against a key holding the wrong kind of value

“-”后面第一个word表示错误的类型,这只是Redis的一个convention不是RESP error format。

比如,ERR是一个general的错误,WRONGTYPE是一个更加specific的错误(意味着客户端正在尝试对一个错误的数据类型进行某种操作)。这被叫做Error Prefix,可以帮助客户端理解错误。

一个客户端实现可能针对不同的错误实现不同的异常,也有可能仅仅是将Redis返回的信息作为一个字符串返回给调用者。however,这样的特性可能并不是很重要,因为没什么用,有些简单的客户端可能仅仅返回false给客户端。

RESP integers

这个类型就是一个CRLF终止的字符串,以“:”为前缀。比如“:0\r\n”,“:1000\r\n”就是整型回复。

很多Redis命令返回RESP整型,比如INCR,LLEN和LASTSAVE。

返回的整型数没有什么特殊的意义,对于INCR来说就是一个增量后的数据,对于LASTSAVE就是一个UNIX时间等等。但是返回的整型数据保证是一个64位的有符号整数。整数也被大量用于返回true和false。比如EXISTS或者SISMEMEBER会返回1表示true,返回0表示false。

其它像SADD,SREM,SETNX这些命令当成功的时候会返回1,失败返回0。

以下命令会产生一个整数回复:SETNX,DEL,EXISTS,INCR,INCRBY,DECR,DECRBY,DBSIZE,LASTSAVE,RENAMENX,MOVE,LLEN,SADD,SREM,SISMEMBER,SCARD。

RESP Bulk strings

bulk string是用来表示单个二进制安全的字符串(最大512MB)。Bulk string通过以下方式编码:

  • 一个“$”开始,然后跟着字符串长度(也就是prefixed 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。

当server回复一个Null Bulk string数据类型的时候,客户端API应该返回一个nil对象,而不是一个空字符串。比如一个Ruby客户端应该返回'nil',而一个C库应该返回NULL(或者在回复对象中设置一个特殊的标志)等等。

RESP数组(译者注:类似C中的结构体)

客户端通过RESP Arrays给Redis服务器发送命令。相似的,一些Redis命令会使用Arrays的形式从服务器得到返回值。比如,LARANGE命令。

RESP数组通过以下形式发送:

  • 一个“*”作为第一个字节,紧跟一个十进制数用于表示数组中元素的数量,以CRLF结尾。
  • 另外的RESP类型数据来表示数组中的元素。

所以一个空的数组可以表示为:

"*0\r\n"

而一个由2个bulk string,“foo”和“bar”组成的数组可以表示为:

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

正如你所看到的,除了前缀之外后面就是由其它数据类型一个一个连接而成的。比如一个由3个整数组成的数组编码为:

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

数组不需要每个数组元素都是同一类型的,可以包换混合类型的元素。比如,4个整数和一个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个元素会发送。然后发送这5个元素。

Null数组的概念也存在,并且是另一种方式来表示Null值。(Usually会用Null bulk string,但是由于历史原因我们保留两种形式的Null)。比如当BLPOP timeout的时候,它返回一个Null数组,它带有-1计数:

"*-1\r\n"

一个客户端得到一个Null数组的回复的时候,需要返回一个null对象,而不是一个空数组。当用于区分一个空字符串和其它事件(比如BLPOP的timeout)的时候这就变得很有必要。

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

上述RESP数据编码了一个由2个数组组成的数组,一个数组由整数1,2,3组成,另一个数组由一个simple string和一个errror组成。

字符串中的Null元素

数组中的某个元素可能是Null。这可以用来表示有一个元素缺失,但不是空元素。这个可以出现,比如SORT命令结合GET命令一起使用的时候。包含一个Null元素的数组的例子:

*3\r\n
$3\r\n
foo\r\n
$-1\r\n
$3\r\n
bar\r\n

第二个元素是一个Null,客户端应该这样返回:

["foo",nil,"bar"]

注意这不是前述的例外,只是一个用于进一步明确协议的例子。

给Redis服务器发送命令

现在你对RESP序列化的形式比较熟悉了,写一个Redis客户端应该是很容易的。我们可以进一步描述一下客户端和服务器之间的交互:

  • 客户端给Redis服务器发送一个仅仅由bulk string组成的RESP数组。
  • 一个服务器给客户端回复任意有效的RESP数据类型。

所以一个典型的交互是这样的:

客户端发送一个命令LLEN mylist,来获取存在key mylist的list的长度。服务器回复一个整数回复(C:客户端,S:服务器):

C: *2\r\n
C: $4\r\n
C: LLEN\r\n
C: $6\r\n
C: mylist\r\nS: :48293\r\n

还是一样,我们为了简单起见,将交互数据写成了一行一行,但是实际上的交互数据,客户端是发送*2\r\n$4\r\nLLEN\r\n$6\r\nmylist\r\n这样一个整体。

多个命令和管道

一个客户端可以使用相同的连接来发送多个命令。Redis支持管道,从而支持客户端通过一个写操作来发送多个命令,而不需要读取服务器对于上一个命令的回复再发送下一个命令。所有的回复都可以在最后读取。更堵的信息可以查阅page about Pipelining。

Inline命令

有时候,你手上只有Telnet,但是你想向Redis server发送命令。尽管Redis协议实现起来很简单,但是在交互式会话下面还是不理想。同事,redis-cli又不可用。出于这个原因,Redis也通过一个特殊的方式接收命令。这种方式是为人设计的,叫做inline 命令形式。

以下就是一个使用inline命令来进行server/client对话的例子。

C: PING
S: +PONG

以下是另一个例子:使用inline命令返回一个整数

C: EXISTS someky
S: :0

基本上,在telnet会话下面,你只是写了以空格分隔的参数。因为没有命令以*开始,所以Redis使用unified request protocol,因此redis有能力检测这个情况,并且解析你的命令。

Redis协议的高性能解释器

Redis协议是一个可读性好,而且可以很容易实现为像二进制协议那样快速的高性能协议。

RESP使用前缀长度来传输bulk数据,所以不需要像JSON那样扫描特殊字符,或者quote来确定payload。

Bulk和多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识别之后,后面可以在收到LF之后退出。然后bulk数据就可以只需要读一次就可以了,不需要再次检查payload的长度。最后的CR和LF将被直接discard。

在用用二进制协议一样的性能的前提下,Redis协议可以在大部分高级语言下面很方便的实现,因此减少客户端软件中的bug。

Redis协议spec(翻译)相关推荐

  1. simp服务器协议,Redis协议(RESP)规范

    本文由笔者手工翻译自Redis官网 Redis客户端使用称为RESP(REdis序列化协议)的协议与Redis服务器进行通信.虽然该协议是专为Redis设计的,但它可以用于其他C/S架构的软件项目. ...

  2. redis配置文件 谷歌翻译版本

    谷歌翻译版本的redis配置文件 # Redis配置文件示例. # # 注意,为了读取配置文件,Redis必须是以文件路径作为第一个参数开头: # # ./redis-server /path/to/ ...

  3. 腐蚀rust服务器命令_【使用 Rust 写 Parser】2. 解析Redis协议

    系列所有文章 https://zhuanlan.zhihu.com/p/115017849​zhuanlan.zhihu.com https://zhuanlan.zhihu.com/p/139387 ...

  4. AMBA协议之AXI协议——中文翻译

    AMBA协议之AXI协议--中文翻译 本规范的编写是为了帮助那些想要熟悉高级微控制器总线架构(AMBA)和设计与AXI协议兼容的系统和模块的硬件和软件工程师. 文章目录 AMBA协议之AXI协议--中 ...

  5. 【国产开源】兼容redis协议的内存数据库

    背景 jredis是一个高性能.高可用.低延迟的内存数据库,服务端源码请移步这里 编写目的 加深对底层网络传输,文件存储,文件索引的认知,同时也巩固自身的知识点. 协议特征 兼容redis原生协议 s ...

  6. SSRF - ctfhub -2【FastCGI协议、Redis协议、URL Bypass、数字IP Bypass、302跳转 Bypass、DNS重绑定Bypass】

    FastCGI协议 知识参考:CTFhub官方链接 首先介绍一下原理(这里简单介绍,详情请看官方附件) 如果说HTTP来完成浏览器到中间件的请求,那么FastCGI就是从中间件到某语言后端进行交换的协 ...

  7. 陈宝仪(Redis-replicator作者)详解Redis协议

    「 周四见 」公开课 We,知数堂 自我实力带盐-只分享干货 2018年8月16日,20:30-21:30 周四见 不见不散 分享嘉宾   分享主题 <Redis 协议解析> PPT页数  ...

  8. 陈宝仪(Redis-replicator作者)详解Redis协议,今晚直播

    「 周四见 」公开课 We,知数堂 自我实力带盐-只分享干货 2018年8月16日,20:30-21:30 周四见 不见不散 分享嘉宾   分享主题 <Redis 协议解析> PPT页数  ...

  9. Redis-replicator作者陈宝仪:详解Redis协议

    特邀嘉宾 陈 宝 仪 12年软件开发经验 Nextop高级软件工程师 Redis-replicator.Redis-rdb-cli等 Redis相关开源软件作者. 分享主题 周四见|知数堂公开课系列之 ...

最新文章

  1. 初等数论--同余--欧拉函数、欧拉定理、费马小定理
  2. counterfactual
  3. 定制圆角带背景色的矩形边框
  4. nginx优化配置选项
  5. 二分法查找 - python实现
  6. 【转】asp.net中bind()和eval()的区别
  7. php上传图片并显示代码,php图片上传代码(完整版已测试)
  8. 删除的文件如何恢复?一个技巧就解决
  9. java long类型赋值_Java语言编程第22讲——如何理解“Java是强类型语言”
  10. 计算机软件水平考试程序员之程序设计知识点汇总,计算机软件水平考试《程序员》复习知识点(5)...
  11. InnoDB之锁机制
  12. HEVC/H265编码原理
  13. OCR识别技术之—车牌识别
  14. Linux热插拔hotplug处理流程
  15. 雷电2接口_USB、Type-C、雷电3都是怎么一回事?
  16. 解读京东提出的第四次零售革命
  17. 配置汇编环境(王爽)
  18. Java 定义一个人类Person
  19. 日常英语单词 - 食物
  20. Android组件化开发详解

热门文章

  1. 开除AI伦理学家,谷歌如何从“不作恶”到“不宽容”?
  2. [033] 微信公众帐号开发教程第9篇-QQ表情的发送与接收
  3. 如何删除电脑浏览记录
  4. RTX 4080、RTX4070 Ti 相当于什么水平
  5. Mingle 2.0 发布了
  6. SSM框架超市进销存出库入库仓库管理系统(idea开发javaweb-javaee-j2ee-springboot) 退货管理 销售管理 供应商管理 客户管理 员工管理 以及库存统计和盘存统计
  7. python绘制分形图基础_Python 绘制分形图(曼德勃罗集、分形树叶、科赫曲线、分形龙、谢尔宾斯基三角等)附代码...
  8. 爬虫-招聘系列2----boss某直聘
  9. centos8升级centos stream 8
  10. 并发和并行的区别?一个很容易混淆,被忽略的问题