分享

Redis协议详解

PeersLee 发表于 2016-5-11 19:09:50 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 2 10199
本帖最后由 PeersLee 于 2016-5-11 19:32 编辑
问题导读:
1.RESP协议如何进行描述?
2.Simple Strings怎样进行响应?
3.RESP Errors如何进行响应?
4.RESP整型如何响应?
5.RESP Bulk Strings如何响应?
6.RESP 数组是什么样?
7.在Arrays中的NULL 元素是什么样的?
8.如何向Redis服务端发送命令?
9.多命令和管道处理如何工作?
10.Inline命令如何应用?
11.PHP如何实现Redis客户端?






解决方案:

《PHP操作redis两种方式》这篇文章中,我们介绍了如何使用PHP封装phpredis。但是我们对底层redis协议并不了解。本篇简单介绍一下Redis协议,方便以后大家在编程中使用。

Redis客户端和Redis服务端通信的协议被称作RESP(REdis Serialization Protocol)。虽然说RESP是特定为Redis所设计出来的,但是该协议同样也可以被用于其他的CS软件项目。

RESP是以下几种方案的一个折中的方案

· 容易实现
· 快速解析
· 阅读性高

RESP可以序列化不同的数据类型,比如:整型、字符串和数组。除了这些,RESP还特意为错误信息指定了一种数据类型。Redis客户端将要执行的命令的字符串参数数组作为请求发送给Redis服务端。Redis服务端将指定的命令的数据类型作为响应。

需要注意的是,这里所概括的Redis的协议仅仅适用于客户端和服务端通信。Redis集群使用不同的二进制协议在不同的节点之间传递信息。

网络层

Redis服务监听的端口是6379,在客户端和服务端会创建一个TCP连接,其连接端口就是6379。

RESP协议描述

RESP协议是在Redis1.2版本的时候开始被引入Redis的,但是却是在Redis2.0版本开始才成为和Redis服务端通信的标准的方式。RESP协议就是需要我们在Redis客户端实现的协议。

其实,RESP是一种序列化的协议。该协议支持以下几种数据类型:Simple String,Errors,Integers,Bulk Strings和数组。

RESP实现请求——响应协议的方式如下:

· 客户端将命令封装成复杂字符串数组的形式发送给Redis服务端。
· 服务端将接收到的命令的执行结果转化成RESP类型的数据返回给客户端。

在RESP中,我们将根据响应信息的第一个字节来判断数据的类型:

· + 表示是一个Simple Strings
· - 表示是错误信息Errors
· :表示是整型数据
· $ 表示是Bulk Strings
· * 表示是数组

另外,RESP还可以表示一个NULL值。这点将在下面介绍。在RESP中,协议内容的不同的部分都是以“\r\n”作为结束标识的。

Simple Strings响应

Simple Strings 回复内容是这样进行编码的:开头一个加号(+),后面跟着一个字符串——这个字符串不包含CR或者LF字符,最后使用CRLF(\r\n)结束。

Simple Strings用最低的负载来传输非二进制安全的字符串。比如说许多的Redis命令执行成功以后仅需要使用OK来告知客户端命令执行成功,这样响应字符串将按照RESP的Simple Strings的编码方式将OK进行如下编码:

[mw_shl_code=php,true]“+OK\r\n”[/mw_shl_code]

为了发送二进制安全的字符串,RESP Bulk Strings将取代Simple Strings的编码方式。

当Redis服务端使用Simple Strings进行响应的时候,客户端需要返回给调用者一个字符串,这个字符串是由加号(+)以后的字符串组成。在这个例子中就是OK。

RESP Errors响应

RESP为错误信息制定了特殊的类型。其实,错误类型和Simple Strings类型非常的相似。不同的是,第一个字节的标识不同。错误类型使用的是减号(-)作为开始。其实质的不同是错误信息在客户端是作为异常来处理的。由错误信息本身来标识是哪一种错误。

其基本的格式如下

[mw_shl_code=php,true]“-Error message\r\n”[/mw_shl_code]

错误信息只有在某些错误操作发生的时候才会被发送给客户端。比如说,如果你要处理一个错误的数据类型,抑或是发送了一个不存在的命令等等。这些情况客户端都会受到错误的信息。

下面是错误响应的例子:

[mw_shl_code=php,true]-ERR unknown command ‘onmpw’
-WRONGTYPE Operation against a key holding the wrong kind of value[/mw_shl_code]

减号(-)后面的第一个字符到第一个空格或者换行符之间的字符串,表示的是错误的种类。这只是为了方便使用Redis,它并不是RESP的一部分。

Redis客户端根据不同种类的错误返回给调用者不同的异常。或者是提供一个通用的方法来处理这些错误——通过将错误信息名称作为字符串提供给调用者。

不过错误类型不需要费太多的时间去考虑。因为它很少使用。并且一些功能并不是很强大的客户端对于错误信息可能仅仅返回一个false。

RESP整型响应

这种类型同样也是使用CRLF(\r\n)作为结束标志。其第一个字节是冒号(:)。它的格式如下

[mw_shl_code=php,true]:0\r\n
:1000\r\n[/mw_shl_code]

许多Redis命令的执行结果都会返回整型,像INCR,LLEN和LASTSAVE。

这些整型的数字并没有什么特殊的意义。对于INCR命令来说就是一个增长的数;对于LASTSAVE就是一个UNIX时间戳等等。但是,这里需要注意的是,整数的范围是一个带符号的64位的整数范围。

有时候,整型数的响应也被用来表示true和false。例如,EXISTS或者SISMEMBER命令会用1表示true,0表示false。

RESP Bulk Strings响应

Bulk Strings的使用是为了表示一个二进制安全的字符串。这个字符串的长度可以达到512MB。

Bulk Strings使用如下的形式进行编码

· 用$符号后面跟着一个整数的形式组成一个字符串(长度前缀),以CRLF结尾。
· 实际的字符串。
· 整个字符串后面使用CRLF结束。

字符串“onmpw”的编码形式如下:

[mw_shl_code=php,true]“$5\r\nonmpw\r\n”[/mw_shl_code]

空字符串的形式如下:

[mw_shl_code=php,true]“$0\r\n\r\n”[/mw_shl_code]

除此之外,Bulk Strings还可以使用一个特殊的标识来表示一个不存在的值。这个值就是上面我们提到过的NULL值。其表示形式如下:

[mw_shl_code=php,true]“$-1\r\n”[/mw_shl_code]

这被称为Null Bulk String。

当请求的对象不存在的时候,客户端的API不要返回一个空字符串,应该返回一个nil对象。就好像一个Ruby库应该返回一个nil,C库应该返回一个NULL等等。

RESP 数组

客户端想Redis服务端发送命令的时候,使用的是RESP 数组的形式。并且还有一些特定的命令的返回结果也是一个元素的集合。这些返回的结果也是用RESP数组的形式编码的。 比如说命令LRANGE就返回一些列的元素。

RESP Arrays使用如下的形式发送信息:

· 星号(*)作为第一个字节,后面跟着一个十进制的整数,接着是CRLF(\r\n)。
· 对每一个数组的元素都有一个额外的RESP类型。

空数组的形式如下:

[mw_shl_code=php,true]“*0\r\n”[/mw_shl_code]

对于有两个Bulk Strings元素foo和bar的Array的表示形式如下:

[mw_shl_code=php,true]“*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n”[/mw_shl_code]

Arrays除了包含有Bulk Strings元素外还可以包含整型元素

[mw_shl_code=php,true]“*3\r\n:1\r\n:2\r\n:3\r\n”[/mw_shl_code]

当然,Array中的元素就像PHP数组一样,里面的元素的类型可以是混合的。看下面的例子

[mw_shl_code=php,true]“*5\r\n:1\r\n:2\r\n:3\r\n:4\r\n$5\r\nonmpw\r\n”[/mw_shl_code]

为了看得清楚,我们将上面的字符串分成多行表示

[mw_shl_code=php,true]*5\r\n
:1\r\n
:2\r\n
:3\r\n
:4\r\n
$5\r\n
onmpw\r\n[/mw_shl_code]

服务端发送的第一行是*5\r\n,表示后面跟着有5个响应信息。每一条响应信息组成了一个Multi Bulk响应被传输到客户端。

在Bulk Strings类型中我们可以表示NULL值。同样的,在Arrays中也可以表示一个空Array。使用RESP Arrays的类型表示NULL的形式如下:

[mw_shl_code=php,true]“*-1\r\n”[/mw_shl_code]

注意,当Redis响应一个NULL Array的时候,客户端的API返回的不应该是一个空数组,而应该返回一个null对象。

和编程语言的数组一样,RESP Arrays是可以嵌套的。下面我们看嵌套两层的Array的形式——也就是对应我们编程语言中的二维数组。同样为了表示清楚,我们使用换行的方式表示。
[mw_shl_code=php,true]
*2\r\n
*3\r\n
:1\r\n
:2\r\n
:3\r\n
*2\r\n
+Foo\r\n
-Bar\r\n[/mw_shl_code]

在上面的数据中,*2\r\n表示的是有两个元素,从*3和下面的*2我们可以看出这两个元素都是RESP Arrays类型的。并且*3表示在该子数组中有三个元素,分别是整数1、2还有3;*2表示在该字数组中有两个元素,分别是Simple Strings Foo和错误信息 Bar。

在Arrays中的NULL 元素

一个元素的Array可能会是Null。包含多个元素的Array中也同样会有Null值。例如下面的Arrays数据

[mw_shl_code=php,true]*3\r\n
$3\r\n
foo\r\n
$-1\r\n
$3\r\n
bar\r\n[/mw_shl_code]

从上面我们可以看到,其中的第二个元素就是Null。客户端类库应该返回的信息如下

[mw_shl_code=php,true][“foo”,nil,”bar”][/mw_shl_code]

向Redis服务端发送命令

如果你已经对RESP序列化格式很熟悉了,接下来写一个Redis客户端应该是很容易的。

· 客户端想Redis服务端发送仅仅包含Bulk Strings元素的Array。
· Redis服务端会向客户端响应任何有效的RESP类型的数据。

举例来说

[mw_shl_code=php,true]C: *2\r\n$3\r\nget\r\n$5\r\nmykey\r\n
S: $5\r\nonmpw\r\n[/mw_shl_code]

C表示客户端发送给服务端的请求信息。本例中我们发送的命令是 get mykey。S表示是服务端响应的信息,本例中是返回了 onmpw字符串。至于各项的含义上面已经介绍了这里就不重复了。

多命令和管道处理

客户端可以在一个连接上面发送多个命令。流水线的处理方式支持在客户端只由一个写操作来发送多个命令。并且不用等待读取完上一个命令的返回结果就可以进行下一个命令的写操作。所有命令的响应信息都可以在最后进行读取。

关于这一点可以参考page about Pipeling

Inline命令

有些时候仅仅是telnet连接Redis服务,或者是仅仅向Redis服务发送一个命令进行检测。虽然Redis协议可以很容易的实现,但是使用Interactive sessions 并不理想,而且redis-cli也不总是可以使用。基于这些原因,Redis支持特殊的命令来实现上面描述的情况。这些命令的设计是很人性化的,被称作Inline 命令。

下面是一个Inline 命令的例子:
[mw_shl_code=php,true]
C: PING
S: +PONG
[/mw_shl_code]
S代表服务端响应,C代表客户端请求。

下面是另一个Inline命令的例子:

[mw_shl_code=php,true]C: EXISTS onmpw
S: :1[/mw_shl_code]

因为onmpw存在,所以服务端返回:1。这里EXISTS命令只带有一个参数,多个参数可以用空格进行分割。

在对Redis请求协议规范之前,客户端就是用这种用空格分割的命令发送请求。

代码实现

上面大概介绍了Redis的协议。现在我们通过一段PHP代码看一下如何实现Redis客户端。


[mw_shl_code=php,true]<?php
class Redis{
   
    private $handle;
   
    private $host;
    private $port;
    private $slient_fail;
    private $timeout;
   
    private $connect_timeout = 3;
   
    public function __construct($host,$port,$slient_fail = false,$timeout = 60){
        if($host && $port){
            $this->connect($host,$port,$slient_fail,$timeout);
        }
    }
   
    private function connect($host = '127.0.0.1',$port = 6379,$slient_fail = false,$timeout = 60){
        $this->host = $host;
        $this->port = $port;
        $this->slient_fail = $slient_fail;
        $this->timeout = $timeout;
        $this->handle = fsockopen($host,$port,$errno,$errstr,$this->connect_timeout);
    }
   
    public function get(){
        $nl = "\r\n";
        $cmd = '*2'.$nl.'$3'.$nl.'get'.$nl.'$5'.$nl.'mykey'.$nl;
        fwrite($this->handle, $cmd);
        $res = fgetc($this->handle);
        $res = trim(fgets($this->handle));
        $response = fread($this->handle,$res);
        fgets($this->handle);
        echo $response;
    }
}

$obj = new Redis('192.168.144.133',6379);
$obj->get();[/mw_shl_code]


转自:迹忆网 作者: 迹忆



已有(2)人评论

跳转到指定楼层
xuliang123789 发表于 2016-5-12 09:59:11
谢谢楼主,学习一下,辛苦啦,赞~~
回复

使用道具 举报

CM潜修 发表于 2016-5-12 14:17:41
不错,redis学习了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条