Redis クライアントは、RESP (REdis Serialization Protocol、redis シリアル化プロトコル) と呼ばれるプロトコルを使用して Redis サーバーと対話します。このプロトコルは Redis 用に設計されていますが、他のクライアント サーバー アーキテクチャ ソフトウェア システムでも使用できます。 (注釈: 公開情報から判断すると、Momo の IM プロトコル設計は Redis プロトコルを参照しています)
RESP は次の側面を考慮しています:
実装はシンプルでなければならず、解析は高速かつ便利でなければなりません。 read
RESP は、整数、文字列、配列などのさまざまなデータ型をシリアル化でき、エラー用の特別な型も設計できます。クライアントは、実行のために文字列パラメーター配列の形式でリクエストを Redis サーバーに送信し、Redis はコマンドに関連するデータ型を返します。
RESP はバイナリ セーフであり、バルク データの送信に長さのプレフィックスを使用するため、あるプロセスから別のプロセスに送信されたバルク データを解析する必要がありません。
注: ここで説明するプロトコルは、クライアントとサーバーの通信にのみ使用されます。 Redis クラスターは、ノード間のメッセージ交換に異なるバイナリ プロトコルを使用します。
ネットワーク層
クライアントは、ポート 6379 で TCP 接続を確立することで Redis と通信します。
RESP は技術的には TCP に関連していませんが、Redis の場合、このプロトコルは TCP (または Unix ドメイン プロトコルなどの他のストリーミング プロトコル) にのみ使用されます。 (翻訳注: 一方、Memcached は TCP と UDP の両方をサポートしていますが、実際には本番環境では基本的に TCP のみが使用されます。これは過剰設計だと思います。ハッカーが memcached UDP リフレクションを実行するために使用する可能性があります) .)
リクエスト応答モデル
Redis は、さまざまなパラメーターで構成されるコマンドを受け取ります。コマンドが受信されると処理され、応答がクライアントに送信されます。
これは最も単純なモデルですが、2 つの例外があります。
Redis はパイプラインをサポートしています (後述)。したがって、クライアントは複数のコマンドを一度に送信し、応答を待つことができます。クライアントが Pub/Sub チャネルにサブスクライブすると、プロトコルのセマンティクスが変更され、プッシュ プロトコルになります。これは、サーバーがメッセージの受信後に新しいメッセージをクライアントに自動的に送信するため、クライアントはコマンドを送信する必要がないことを意味します (クライアントがサブスクライブしているチャネル)。
これら 2 つの点に加えて、Redis プロトコルは単純な要求/応答プロトコルです。
RESP プロトコルの説明
RESP プロトコルは Redis 1.2 で導入されましたが、現在では Redis 2.0 の標準対話プロトコルとなっています。 Redis クライアントを実装する場合は、このプロトコルを使用する必要があります。
RESP は、実際には次のタイプをサポートするシリアル化プロトコルです: 単純な文字列、エラー、整数、バルク文字列、および配列。
RESP は、要求応答プロトコルとして、Redis で次のように使用されます。
クライアントは、RESP バルク文字列の配列の形式でコマンドを Redis サーバーに送信します。サーバーはさまざまなコマンドを実装し、対応する RESP 実装を返します。
RESP では、一部のデータのタイプは最初のバイトによって決まります。
単純な文字列の場合、応答の最初のバイトは " "、エラーの場合、応答の最初のバイト整数の場合は「-」、バルク文字列の場合は応答の最初のバイトは「:」、配列の場合は応答の最初のバイトは「*」です
さらに、RESP は特別なバルク文字列または配列を使用して Null 値を表すことができます。これについては後で説明します。
RESP では、プロトコルのさまざまな部分は常に「\r\n」 (CRLF) で区切られます。
RESP 単純な文字列
単純な文字列は次のようにエンコードされます: プラス記号の後に CR または LF 文字 (改行なし) を含まない文字列が続き、CRLF ("\ r\n ") が終了します。
SImple Strings は、最小限の労力で非バイナリセーフ文字列を送信します。たとえば、多くの Redis コマンドは成功すると「OK」と応答します。これは、RESP シンプル文字列でエンコードされた 5 バイトです:
" OK\r\n"
バイナリを送信するため安全な文字列を得るには、RESP Bulk Strings を使用してください。
Redis が単純な文字列に応答する場合、クライアント ライブラリは、CRLF バイトを除く、最初の " " 文字から文字列の末尾までの文字列を呼び出し元に返す必要があります。
RESP エラー
RESP エラーには特別なデータ型があります。実際、error は RESP Simple String と同じですが、最初の文字列がプラス記号ではなく "-" になっています。 RESP における単純文字列とエラーの本当の違いは、エラーはクライアントによって例外として扱われ、エラー タイプを構成する文字列は文字列そのものであることです。基本的な形式は次のとおりです:
"-エラー メッセージ\r\n"
エラー応答は、間違ったデータ型を操作したなど、エラーが発生した場合にのみ送信されます。 . またはコマンドが存在しないなど。エラー応答を受信した場合、クライアントは例外をスローする必要があります。
以下はエラー応答の例です:
-ERR 不明なコマンド 'foobar'-WRONGTYPE 間違った種類の値を保持するキーに対する操作です
新しい行の最初のスペースまたは最初の単語に「-」を置き、返されるエラーの種類を示します。これは 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 != '\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数据可以一次读取而不需要分析 数据负载。最后剩下的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 中国語 Web サイトの他の関連記事を参照してください。