首頁  >  文章  >  資料庫  >  redis協議是什麼意思

redis協議是什麼意思

尚
原創
2019-07-04 13:14:463950瀏覽

redis協議是什麼意思

Redis客戶端透過使用一種叫RESP(REdis Serialization Protocol, redis序列化協定)協定與Redis伺服器互動。雖然這個協定是為Redis設計的,但它也可以用於其他client-server架構的軟體系統。 (譯註: 從一些公開的資料來看,陌陌的IM協議設計就參考了Redis協議)

RESP 權衡了以下幾個方面:

實現要簡單解析要快方便人閱讀

RESP可以序列化不同的資料類型,像integers、strings、arrays,對於錯誤也設計了特殊的類型。客戶端以字串參數數組的請求形式傳送給Redis伺服器執行,Redis傳回命令相關的資料類型。

RESP是二進位安全性(binary-safe)的,並且不需要解析由一個進程發送給另一個進程的bulk 數據,因為它使用長度前綴來傳輸bulk 數據。

注意:這裡所說的協定只用於client-server的通訊。 Redis Cluster使用不同的二進位協定在node間進行訊息互動。

網路層

客戶端透過建立連接埠為6379的TCP連線與Redis通訊。

雖然RESP從技術上來說並不是TCP相關的,但對Redis來說該協定只用於TCP(或其他串流協定如Unix域協定)。 (譯註:反觀memcached, 既支持tcp, 也支持udp, 但實際上生產環境基本上只用tcp。我認為這是一種過度設計了,搞不好還可能被駭客利用來做memcached udp 反射攻擊。 。)

請求回應模型

Redis 接收不同參數構成的命令。當命令接收到後就會被處理,然後回應傳送給客戶端。

這是最簡單的模型了,但有兩點例外:

Redis支援 pipelining (後文會提及)。所以客戶端可以一次發送多個命令,然後等待回應。當客戶端訂閱了一個Pub/Sub channel, 該協議會改變語義而變成一個推送協議,也就是說客戶端不用發送命令,因為服務端在收到消息後會自動給客戶端發送新的消息(對客戶端訂閱了的channel)。

除了這兩點,Redis協定就是一個簡單的請求-回應協定。

RESP協定描述

RESP協定在Redis 1.2引入,但它現在成為Redis 2.0的標準互動協定。你應該實現Redis客戶端時採用該協定。

RESP事實上是一個支援以下類型的序列化協定:Simple Strings, Errors, Integers, Bulk Strings 和 Arrays。

RESP作為請求回應協議,在Redis中使用的方式如下:

客戶端發送命令到Redis伺服器,以RESP Bulk Strings數組的方式。伺服器根據不同的命令實現,返回相應的RESP實現。

 在RESP中,一些資料的類型由第一個位元組決定:

對於SImple Strings 回應的第一個位元組是「 」對於Errors 回應的第一個位元組是"-"對於Integers 回應第一個位元組是":"對於Bulk Strings 回應第一個位元組是"$" 對於Arrays 回應第一位元組是"*" 

#另外RESP可以用特殊的Bulk String或陣列來表示Null 值,後文會提及。

在RESP中協定不同部分總是用 "\r\n" (CRLF) 分隔。

RESP Simple Strings

Simple Strings透過以下方式編碼:一個加號,後面接著一個字串, 字串不包含CR或LF字元(不能有換行),以CRLF ("\ r\n")結束。

SImple Strings 以最小的代價來傳輸非二進位安全性的字串。例如許多Redis指令在成功時回應"OK",就是用RESP Simple String 編碼的5個位元組:

" OK\r\n"

為了傳送二進位安全的字串,要用RESP Bulk Strings。

當Redis回應一個Simple String時,客戶端程式庫應該會給呼叫者傳回從第一個" "字元到字串結尾的字串,不包括CRLF 位元組。 

RESP Errors

RESP 對於error有一種特殊的資料型態。實際上error就像RESP Simple String一樣,但第一個字串是"-"而不是加號。在RESP中 Simple Strings和Errors兩者的真正區別在於errors被客戶端作為異常,而構成Error類型的字串就是字串本身。基本格式是:

"-Error message\r\n"

Error回應只會在有錯誤發生時才會傳送,例如你操作了錯誤的資料類型,或命令不存在等。當收到Error回應時,客戶端應該拋出一個異常。

以下是error回應的範例:   

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

#"-

###"-######"-###### "到第一個空格或新行間的第一個字,表示傳回的錯誤類型。這只是Redis自己的一種約定,並不是RESP Error 所規定的格式。 ###

例如,ERR 是通用错误,而 WRONGTYPE 是一种更加具体的错误,表示客户端尝试操作错误的数据类型。这称为 Error Prefix (Error前缀),客户端可从此得知服务器返回错误的类型而不需依赖于那个确切的消息描述,后者会随时改变。

一个客户端的实现可能对不同的error返回不同类型的异常,或者向调用者返回代表错误的字符串。然而这种特性并不是必须的,因为这并没什么卵用,一些精简的客户端实现可能简单的返回一般的错误情况,例如 false。

RESP Integers 

这种类型就是一个代表整数的以CRLF结尾的字符串,并以“:”字节开头。例如 ":0\r\n", 或 ":1000\r\n" 都是整数响应。

很多Redis命令返回RESP Integers, 像 INCR, LLEN 和 LASTSAVE。

返回的整数并没什么特殊的含义,它就是 INCR 增加后的数字,LASTSAVE 的UNIX时间戳等。但返回的整数可以保证是在64位有符号整数的范围内。

整数响应也被大量的用于表示true或false。例如EXISTS和 SISMEMBER 等命令会返回1表示true, 0表示false。

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

RESP Bulk Strings

Bulk Strings 用于表示一个二进制安全的字符串,最大长度为512M。

Bulk Strings 的编码格式如下:

“$” 后跟字符串字节数(prefix 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。

当服务器返回Null Bulk String时,客户端API不应该返回空串,而是nil 对象。例如Ruby库应该返回 'nil' 而 C 库应该返回 NULL (或在返回的对象设置特殊的标记),等等。

RESP Arrays

客户端用RESP Arrays向Redis服务器发送命令。同样某些Redis命令要返回一个元素集合时也使用RESP Arrays作为返回的类型。一个例子是LRANGE 命令返回一个元素列表。

RESP Arrays使用以下格式发送:

“*” 为第一个字节,后跟数组的元素个数,然后CRLF。然后是数组中的每一个RESP类型表示的元素。

例如一个空数组表示为:

"*0\r\n"

而有两个RESP Bulk Strings "foo" 和 "bar" 的数组编码为:

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

正如你所见,在数组前面的 *570b78fce6f4d82c6d7725059f1f48f1CRLF 后,数组中的其他的数据类型一个接一个的连接在一起。例如一个有三个整数的数组编码如下:

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

数组可以包含混合类型,它不要求所有的元素都是相同的类型。例如,一个有4个interges和1个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个响应,然后每个代表元素的响应被发送。

Null 数组的概念同样存在,它是Null值的替代方式 (通常使用Null Bulk String,但由于历史原因我们有两种格式)。

例如当BLPOP命令超时,它返回一个长度为-1的Null 数组,如下所示:

"*-1\r\n"

在服务端返回Null数组时,客户端库API应该返回null对象而不是空数组。区分返回空的列表与其他的情况(如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

高效解析Redis协议

尽管Redis协议非常可读并且容易实现,它却可以兼得二进制协议的高效。

RESP使用长度前缀来传输bulk 数据,所以不需要像JSON一样扫描数据负载中的特殊符号,或者用引号括住数据负载。

Bulk和Multi Bulk长度的处理可以一次处理一个字符,同时可以扫描CR字符,像如下的C代码:

#include <stdio.h>

int main(void) {
    unsigned char *p = "$123\r\n";
    int len = 0;

    p++;
    while(*p != &#39;\r&#39;) {
        len = (len*10)+(*p - &#39;0&#39;);
        p++;
    }

    /* Now p points at &#39;\r&#39;, and the len is in bulk_len. */
    printf("%d\n", len);
    return 0;
}

当第一个CR被识别后,后面的LF可以忽略不处理。然后bulk数据可以一次读取而不需要分析 数据负载。最后剩下的CR和LF字符串可以丢弃不处理。

与二进制协议比较性能时,Redis协议在大部分的高级语言实现起来足够简单,减少了客户端软件的bug数量。

注:

1. 协议中的CR和LF相当于分割符,命令间存在多个CRLF不应影响后续解析,应为多个CRLF应被忽略掉。例如:

$> (printf "PING\r\nPING\r\nPING\r\n\r\n\rPING\r\n"; sleep 1) | nc localhost 6379
+PONG
+PONG
+PONG
+PONG

2. 对比一下memcached的协议,redis的协议确实设计得比较精简:

(1) 一致的请求形式。redis的请求都是以 Bluk String 数组发送,不同命令只是数组的元素个数不同,所有命令的处理可以先读取完整个数组再根据不同命令解析数组的参数;而不是像mc协议一样,不同请求的命令格式不同,那么在读取网络字节流的过程中就要对不同命令做不同的处理,增加了协议解析的难度。

(2) 长度前缀是高效解析协议的关键。字段长度信息并不是二进制协议的专利,文本协议也可以有。

更多Redis相关知识,请访问Redis使用教程栏目!

以上是redis協議是什麼意思的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn