Redis 通信协议(protocol)

本文档翻译自: http://redis.io/topics/protocol 。

Redis 协议在以下三个目标之间进行折中:

  • 易于实现
  • 可以高效地被计算机分析(parse)
  • 可以很容易地被人类读懂

网络层

客户端和服务器通过 TCP 连接来进行数据交互, 服务器默认的端口号为 6379 。

客户端和服务器发送的命令或数据一律以 \r\n (CRLF)结尾。

请求

Redis 服务器接受命令以及命令的参数。

服务器会在接到命令之后,对命令进行处理,并将命令的回复传送回客户端。

新版统一请求协议

新版统一请求协议在 Redis 1.2 版本中引入, 并最终在 Redis 2.0 版本成为 Redis 服务器通信的标准方式。

你的 Redis 客户端应该按照这个新版协议来进行实现。

在这个协议中, 所有发送至 Redis 服务器的参数都是二进制安全(binary safe)的。

以下是这个协议的一般形式:

*<参数数量> CR LF
$<参数 1 的字节数量> CR LF
<参数 1 的数据> CR LF
...
$<参数 N 的字节数量> CR LF
<参数 N 的数据> CR LF
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

译注:命令本身也作为协议的其中一个参数来发送。

举个例子, 以下是一个命令协议的打印版本:

*3
$3
SET
$5
mykey
$7
myvalue
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这个命令的实际协议值如下:

"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"

稍后我们会看到, 这种格式除了用作命令请求协议之外, 也用在命令的回复协议中: 这种只有一个参数的回复格式被称为批量回复(Bulk Reply)。

统一协议请求原本是用在回复协议中, 用于将列表的多个项返回给客户端的, 这种回复格式被称为多条批量回复(Multi Bulk Reply)。

一个多条批量回复以 *<argc>\r\n 为前缀, 后跟多条不同的批量回复, 其中 argc 为这些批量回复的数量。

回复

Redis 命令会返回多种不同类型的回复。

通过检查服务器发回数据的第一个字节, 可以确定这个回复是什么类型:

  • 状态回复(status reply)的第一个字节是 “+”
  • 错误回复(error reply)的第一个字节是 “-“
  • 整数回复(integer reply)的第一个字节是 “:”
  • 批量回复(bulk reply)的第一个字节是 “$”
  • 多条批量回复(multi bulk reply)的第一个字节是 “*”

状态回复

一个状态回复(或者单行回复,single line reply)是一段以 “+” 开始、 “\r\n” 结尾的单行字符串。

以下是一个状态回复的例子:

 +OK
  • 1

客户端库应该返回 “+” 号之后的所有内容。 比如在在上面的这个例子中, 客户端就应该返回字符串 “OK” 。

状态回复通常由那些不需要返回数据的命令返回,这种回复不是二进制安全的,它也不能包含新行。

状态回复的额外开销非常少,只需要三个字节(开头的 “+” 和结尾的 CRLF)。

错误回复

错误回复和状态回复非常相似, 它们之间的唯一区别是, 错误回复的第一个字节是 “-” , 而状态回复的第一个字节是 “+” 。

错误回复只在某些地方出现问题时发送: 比如说, 当用户对不正确的数据类型执行命令, 或者执行一个不存在的命令, 等等。

一个客户端库应该在收到错误回复时产生一个异常。

以下是两个错误回复的例子:

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

在 “-” 之后,直到遇到第一个空格或新行为止,这中间的内容表示所返回错误的类型。

ERR 是一个通用错误,而 WRONGTYPE 则是一个更特定的错误。 一个客户端实现可以为不同类型的错误产生不同类型的异常, 或者提供一种通用的方式, 让调用者可以通过提供字符串形式的错误名来捕捉(trap)不同的错误。

不过这些特性用得并不多, 所以并不是特别重要, 一个受限的(limited)客户端可以通过简单地返回一个逻辑假(false)来表示一个通用的错误条件。

整数回复

整数回复就是一个以 “:” 开头, CRLF 结尾的字符串表示的整数。

比如说, “:0\r\n” 和 “:1000\r\n” 都是整数回复。

返回整数回复的其中两个命令是 INCR 和 LASTSAVE 。 被返回的整数没有什么特殊的含义, INCR返回键的一个自增后的整数值, 而 LASTSAVE 则返回一个 UNIX 时间戳, 返回值的唯一限制是这些数必须能够用 64 位有符号整数表示。

整数回复也被广泛地用于表示逻辑真和逻辑假: 比如 EXISTS 和 SISMEMBER 都用返回值 1 表示真, 0 表示假。

其他一些命令, 比如 SADD 、 SREM 和 SETNX , 只在操作真正被执行了的时候, 才返回 1 , 否则返回 0 。

以下命令都返回整数回复: SETNX 、 DEL 、 EXISTS 、 INCR 、 INCRBY 、 DECR 、 DECRBY 、 DBSIZE 、 LASTSAVE 、 RENAMENX 、 MOVE 、 LLEN 、 SADD 、 SREM 、 SISMEMBER 、 SCARD 。

批量回复

服务器使用批量回复来返回二进制安全的字符串,字符串的最大长度为 512 MB 。

客户端:GET mykey
服务器:foobar
  • 1
  • 2

服务器发送的内容中:

  • 第一字节为 “$” 符号
  • 接下来跟着的是表示实际回复长度的数字值
  • 之后跟着一个 CRLF
  • 再后面跟着的是实际回复数据
  • 最末尾是另一个 CRLF

对于前面的 GET 命令,服务器实际发送的内容为: 
"$6\r\nfoobar\r\n"

如果被请求的值不存在, 那么批量回复会将特殊值 -1 用作回复的长度值, 就像这样:

客户端:GET non-existing-key
服务器:$-1
  • 1
  • 2

这种回复称为空批量回复(NULL Bulk Reply)。

当请求对象不存在时,客户端应该返回空对象,而不是空字符串: 比如 Ruby 库应该返回 nil , 而 C 库应该返回 NULL (或者在回复对象中设置一个特殊标志), 诸如此类。

多条批量回复

像 LRANGE 这样的命令需要返回多个值, 这一目标可以通过多条批量回复来完成。

多条批量回复是由多个回复组成的数组, 数组中的每个元素都可以是任意类型的回复, 包括多条批量回复本身。

多条批量回复的第一个字节为 “*” , 后跟一个字符串表示的整数值, 这个值记录了多条批量回复所包含的回复数量, 再后面是一个 CRLF 。

客户端: LRANGE mylist 0 3
服务器: *4
服务器: $3
服务器: foo
服务器: $3
服务器: bar
服务器: $5
服务器: Hello
服务器: $5
服务器: World
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在上面的示例中,服务器发送的所有字符串都由 CRLF 结尾。

正如你所见到的那样, 多条批量回复所使用的格式, 和客户端发送命令时使用的统一请求协议的格式一模一样。 它们之间的唯一区别是:

  • 统一请求协议只发送批量回复。
  • 而服务器应答命令时所发送的多条批量回复,则可以包含任意类型的回复。

以下例子展示了一个多条批量回复, 回复中包含四个整数值, 以及一个二进制安全字符串:

*5\r\n
:1\r\n
:2\r\n
:3\r\n
:4\r\n
$6\r\n
foobar\r\n
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在回复的第一行, 服务器发送 *5\r\n , 表示这个多条批量回复包含 5 条回复, 再后面跟着的则是 5 条回复的正文。

多条批量回复也可以是空白的(empty), 就像这样:

客户端: LRANGE nokey 0 1
服务器: *0\r\n
  • 1
  • 2

无内容的多条批量回复(null multi bulk reply)也是存在的, 比如当 BLPOP 命令的阻塞时间超过最大时限时, 它就返回一个无内容的多条批量回复, 这个回复的计数值为 -1 :

客户端: BLPOP key 1
服务器: *-1\r\n
  • 1
  • 2

客户端库应该区别对待空白多条回复和无内容多条回复: 当 Redis 返回一个无内容多条回复时, 客户端库应该返回一个 null 对象, 而不是一个空数组。

多条批量回复中的空元素

多条批量回复中的元素可以将自身的长度设置为 -1 , 从而表示该元素不存在, 并且也不是一个空白字符串(empty string)。

当 SORT 命令使用 GET pattern 选项对一个不存在的键进行操作时, 就会发生多条批量回复中带有空白元素的情况。

以下例子展示了一个包含空元素的多重批量回复:

服务器: *3
服务器: $3
服务器: foo
服务器: $-1
服务器: $3
服务器: bar
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

其中, 回复中的第二个元素为空。

对于这个回复, 客户端库应该返回类似于这样的回复:

["foo", nil, "bar"]

多命令和流水线

客户端可以通过流水线, 在一次写入操作中发送多个命令:

  • 在发送新命令之前, 无须阅读前一个命令的回复。
  • 多个命令的回复会在最后一并返回。

内联命令

当你需要和 Redis 服务器进行沟通, 但又找不到 redis-cli , 而手上只有 telnet 的时候, 你可以通过 Redis 特别为这种情形而设的内联命令格式来发送命令。

以下是一个客户端和服务器使用内联命令来进行交互的例子:

客户端: PING
服务器: +PONG
  • 1
  • 2

以下另一个返回整数值的内联命令的例子:

客户端: EXISTS somekey
服务器: :0
  • 1
  • 2

因为没有了统一请求协议中的 “*” 项来声明参数的数量, 所以在 telnet 会话输入命令的时候, 必须使用空格来分割各个参数, 服务器在接收到数据之后, 会按空格对用户的输入进行分析(parse), 并获取其中的命令参数。

高性能 Redis 协议分析器

尽管 Redis 的协议非常利于人类阅读, 定义也很简单, 但这个协议的实现性能仍然可以和二进制协议一样快。

因为 Redis 协议将数据的长度放在数据正文之前, 所以程序无须像 JSON 那样, 为了寻找某个特殊字符而扫描整个 payload , 也无须对发送至服务器的 payload 进行转义(quote)。

程序可以在对协议文本中的各个字符进行处理的同时, 查找 CR 字符, 并计算出批量回复或多条批量回复的长度, 就像这样:

#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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

得到了批量回复或多条批量回复的长度之后, 程序只需调用一次 read 函数, 就可以将回复的正文数据全部读入到内存中, 而无须对这些数据做任何的处理。

在回复最末尾的 CR 和 LF 不作处理,丢弃它们。

Redis 协议的实现性能可以和二进制协议的实现性能相媲美, 并且由于 Redis 协议的简单性, 大部分高级语言都可以轻易地实现这个协议, 这使得客户端软件的 bug 数量大大减少

[转]网络协议-redis协议的更多相关文章

  1. Redis协议详解

    smark Beetle可靠.高性能的.Net Socket Tcp通讯组件 支持flash amf3,protobuf,Silverlight,windows phone Redis协议详解 由于前 ...

  2. Netty 源码中对 Redis 协议的实现

    原文地址: haifeiWu的博客 博客地址:www.hchstudio.cn 欢迎转载,转载请注明作者及出处,谢谢! 近期一直在做网络协议相关的工作,所以博客也就与之相关的比较多,今天楼主结合 Re ...

  3. 网络协议-dubbo协议

    Dubbo支持dubbo.rmi.hessian.http.webservice.thrift.redis等多种协议,但是Dubbo官网是推荐我们使用Dubbo协议的. 下面我们就针对Dubbo的每种 ...

  4. KeeWiDB:兼容Redis协议,领跑NoSQL

    如果现在的我们离开了互联网,生活会是什么样子? 互联网++++,已经深刻渗透到人们的生活中. 不知道大家有没有想过?每一个互联网+结合的背后都是海量的存储需求.你查看的每一个商品.组建的每一个战队.阅 ...

  5. iOS开发网络篇—HTTP协议

    iOS开发网络篇—HTTP协议 说明:apache tomcat服务器必须占用8080端口 一.URL 1.基本介绍 URL的全称是Uniform Resource Locator(统一资源定位符) ...

  6. 02.iOS开发网络篇—HTTP协议

    iOS开发网络篇—HTTP协议 说明:apache tomcat服务器必须占用8080端口 一.URL 1.基本介绍 URL的全称是Uniform Resource Locator(统一资源定位符) ...

  7. NS2仿真:两个移动节点网络连接及协议性能分析

    NS2仿真实验报告2 实验名称:两个移动节点网络连接及协议性能分析 实验日期:2015年3月9日~2015年3月14日 实验报告日期:2015年3月15日 一.实验环境(网络平台,操作系统,网络拓扑图 ...

  8. 网络编程TCP协议-聊天室

    网络编程TCP协议-聊天室(客户端与服务端的交互); <span style="font-size:18px;">1.客户端发数据到服务端.</span> ...

  9. Redis进阶实践之十七 Redis协议的规范

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

随机推荐

  1. JavaScript中的typeof 和instanceof

    Js中的instanceof 和typeof的区别 演示1 var v5=new Number("22"); document.write(typeof v5+"< ...

  2. C语言:将ss所指字符串中所有下标为奇数位置的字母转换为大写-将该字符串中的所有字符按ASCII码值升序排序后输出。-将a所指的4*3矩阵第k行的元素与第0行元素交换。

    //函数fun:将ss所指字符串中所有下标为奇数位置的字母转换为大写,若不是字母,则不转换. #include<conio.h> #include<stdio.h> #incl ...

  3. 动态规划: 最大m子段和问题的详细解题思路(JAVA实现)

    这道最大m子段问题我是在课本<计算机算法分析与设计>上看到,课本也给出了相应的算法,也有解这题的算法的逻辑.但是,看完之后,我知道这样做可以解出正确答案,但是我如何能想到要这样做呢? 课本 ...

  4. 爬虫必备工具-chrome 开发者工具

    在某个网站上,分析和抓取数据,我们用的最多的工具就是 Chrome 开发者工具 01 元素面板 通过元素(Element)面板,我们能查看发哦想抓取页面渲染内容所在的标签.使用什么 CSS 属性(例如 ...

  5. h5页面判断移动端系统为Android或IOS

    最近遇到了一个需求,即所谓的 app+web 混合开发,需要将 h5 内嵌到 APP 中,这个时候因为要对 Android 和 IOS 有不同的处理逻辑,所以我们就需要判断一下,移动端的系统到时是哪一 ...

  6. Go语言经典库使用分析——高性能可扩展 HTTP 路由 httprouter(转载)

    转载自 飞雪无情的博客 Go语言(golang)的一个很大的优势,就是很容易的开发出网络后台服务,而且性能快,效率高.在开发后端HTTP网络应用服务的时候,我们需要处理很多HTTP的请求访问,比如常见 ...

  7. struct和class定义类的区别

    (1)struct定义的类.struct定义的类,其方法和属性都是公有的(public).因此,外部可以直接访问其内部数据. (2)class定义的类.class定义的类,默认情况下是私有的(priv ...

  8. leetCode练题——20. Valid Parentheses

    1.题目 20. Valid Parentheses——Easy  Given a string containing just the characters '(', ')', '{', '}',  ...

  9. Java中的基本数据类型语法补充

    变量要先赋值后使用 不给变量赋值代表什么 不赋值就使用会怎样 (会报错) 计算并赋值运算符 作用是为了让代码更加简洁.比如 a = a + 10,可以简化为 a+=10 += -= *= /= %= ...

  10. Flask - 运行APP

    from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return 'Hello, ...