原文: https://redis.io/topics/protocol
Reids客戶端和redis服務器交流使用RESP協議(redis序列化協議),這個協議是設計是服務於redis的,在其他客戶端-服務器軟件工程中也可以使用這個協議。
RESP特點:簡單實現,快速解析,人類可讀
RESP可以序列化不同的數據類型如integers/strings/arrays。同時也定義了錯誤的專用類型。Redis客戶端使用strings數組代表需要執行的命令,發送請求給服務端。Redis響應數據類型專用命令。
注意:協議只適用與客戶端服務端交流,redis cluster使用不同的二進制協議交換節點之間的數據。
- 網絡層
客戶端連接redis服務器在6379端口創建了一個TCP 連接
雖然RESP在技術上是非TCP特定的 , 但在Redis的上下文中,協議僅用於TCP連接(或者面向流的連接如unix套接字)
- 請求應答模型
Redis接受由不同參數組成的命令。 收到命令後,將對其進行處理,並將回覆發送回客戶端。
- RESP協議描述
RESP從Redis 1.2開始使用,Redis 2.0開始成爲Redis服務器標準的交互方式,需要在你的redis客戶端中實現。
RESP是一個序列化協議支持如下數據類型:Simple Srings/Errors/Integers/Bulk Strings/Arrays。
RESP在redis中是一個請求應答協議
--客戶端以Bulk Strings數組形式,發送命令給redis服務器
--服務器根據命令的實現回覆一種RESP 類型
在RESP中,數據的類型依賴於第一個字節
--簡單字符串(Simple Strings),第一個字節爲”+”
--錯誤(Errors), 第一個字節爲“-”
--整型(Integers), 第一個字節爲“:”
--Bulk Strings, 第一個字節爲“$”
--數組(Array),第一個字節爲“*”
RESP 使用特殊的Bulk Strings變量來表示空值
RESP的不同部分始終使用”\r\n”(CRLF)結束
- RESP簡單字符串(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具有特定的錯誤數據類型。實際上錯誤與RESP Simple Strings完全相同,但第一個字符是' - '字符而不是’+’。 RESP中簡單字符串和錯誤之間的真正區別在於客戶端將錯誤視爲異常,組成錯誤類型的字符串是錯誤消息本身。基本格式是:
"-Error message\r\n"
錯誤回覆僅在發生問題是時送,例如,嘗試對錯誤的數據類型執行操作,或者命令不存在等等。收到錯誤答覆時,庫客戶端應拋出異常。
以下是錯誤回覆的示例:
-ERR unknown command 'foobar'
-WRONGTYPE Operation against a key holding the wrong kind of value
“ - ”之後的第一個單詞,直到第一個空格或換行符,表示返回的錯誤類型。這只是Redis使用的約定,不是RESP錯誤格式的一部分。
例如,ERR是一般錯誤,而WRONGTYPE時一個更具體的錯誤,意味着客戶端嘗試對錯誤的數據類型執行操作。這稱爲錯誤前綴,是一種允許客戶端理解服務器返回的錯誤類型的方法,而不依賴於給定的確切消息,這可能隨時間而變化。
客戶端實現可以針對不同的錯誤返回不同類型的異常,或者直接將錯誤名稱作爲字符串提供給調用者,作爲提供捕獲錯誤的通用方法。
但是,這樣的特性不應該被認爲是至關重要的,因爲它很少有用,並且有限的客戶端實現可能只是返回一般的錯誤條件,例如false。
- RESP整數(Integers)
這個類型字符串,以“:”字節爲前綴,CRLF作爲終止符,如“:0\r\n”或“:1000\r\n”爲整數回覆。
許多Redis命令返回RESP整數,如: INCR,LLEN和LASTSAVE。
返回的整數沒有特殊含義,它只是INCR的增量數,LASTSAVE的UNIX時間等等。但是,返回的整數在有符號的64位整數範圍內。
整數回覆也被廣泛使用以返回真或假。例如,EXISTS或SISMEMBER之類的命令將返回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 Strings用於表示長度最大爲512 MB的單個二進制安全字符串。
Bulk Strings按以下方式編碼:
--一個“$”字節後跟組成字符串的字節數(一個前綴長度),由CRLF終止。
--實際的字符串數據。
--結尾的CRLF。
所以字符串“foobar”的編碼如下:
"$6\r\nfoobar\r\n"
當一個空字符串只是:
"$0\r\n\r\n"
RESP Bulk Strings爲了表示值不存在,使用特殊格式表示Null值。在這種特殊格式長度爲-1,沒有數據,因此Null表示爲:
"$-1\r\n"
這稱爲Null Bulk String。
當服務器使用Null Bulk String回覆時,客戶端庫API不應返回空字符串,而應返回nil對象。例如,Ruby庫應返回'nil',而C庫應返回NULL(或在reply對象中設置特殊標誌),依此類推。
- RESP Arrays
客戶端使用RESP Arrays將命令發送到Redis服務器。類似地,某些Redis命令將元素集合返回給客戶端,使用RESP Arrays作爲回覆類型。一個例子是LRANGE命令,它返回列表的元素。
RESP Arrays使用以下格式發送:
--一個*字符作爲第一個字節,後跟數組中的元素數作爲十進制數,後跟CRLF。
--Array的每個元素的附加RESP類型。
所以空數組如下:
"*0\r\n"
兩個RESP Bulk Strings “foo”和“bar”的數組編碼爲:
"*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"
數組前綴部分爲*<count>CRLF,組成數組的其他數據類型將一個接一個地連接起來。例如,三個整數的數組編碼如下:
"*3\r\n:1\r\n:2\r\n:3\r\n"
數組可以包含混合類型,元素不必是相同類型。例如,可以將包含四個整數和批量字符串的列表編碼如下:
*5\r\n
:1\r\n
:2\r\n
:3\r\n
:4\r\n
$6\r\n
foobar\r\n
(爲清楚起見,答覆分爲多行)。
服務器發送的第一行是*5\r\n爲了指定將跟隨五個回覆。然後每個回覆組成了Multi Bulk reply。
Null Array的概念也存在,並且是指定Null值的替代方法(通常使用Null Bulk String,但由於歷史原因,我們有兩種格式)。
例如,當BLPOP命令超時時,它返回一個Null數組,其計數-1如下例所示:
"*-1\r\n"
當Redis使用Null數組回覆時,客戶端庫API應返回空對象而不是空數組。這是區分空列表和不同條件(例如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
(格式分爲多行,以便於閱讀)。
上面的RESP數據類型編碼一個由兩個元素組成的數組,該數組包含三個整數1,2,3以及一個簡單字符串和一個錯誤。
第二個元素是Null。客戶端庫應返回如下內容:
["foo",nil,"bar"]
請注意,這不是前面部分中所述的例外,而只是進一步指定協議的示例。
將命令發送到Redis服務器
現在您熟悉了RESP序列化格式,編寫Redis客戶端庫的實現將很容易。我們可以進一步瞭解客戶端和服務器之間的交互如何工作:
--客戶端向Redis服務器發送一個僅由Bulk Strings組成的RESP Array。
--Redis服務器回覆發送任何有效RESP數據類型作爲回覆的客戶端。
因此,例如,典型的交互如下:
客戶端發送命令LLEN mylist以獲取存儲在密鑰mylist中的列表長度,服務器回覆一個Integer消息,如下例所示(C:是客戶端,S:服務器)。
C: *2\r\n
C: $4\r\n
C: LLEN\r\n
C: $6\r\n
C: mylist\r\n
S: :48293\r\n
通常我們將協議的不同部分與換行符分開以簡化,但實際的交互是客戶端*2\r\n$4\r\nLLEN\r\n$6\r\nmylist\r\n作爲一個整體發送。
- 多個命令和流水線操作
客戶端可以使用相同的連接來發出多個命令。支持流水線操作,因此客戶端可以通過單個寫入操作發送多個命令,而無需在發出下一個命令之前讀取上一個命令的服務器回覆。所有的回覆都可以在最後閱讀。
- 內聯命令
有時您只需要telnet並向Redis服務器發送命令。雖然Redis協議易於實現,但在交互式會話中使用並不理想,並且redis-cli可能並不總是可用。因此,Redis還以一種專爲人類設計的特殊方式接受命令,並稱爲內聯命令格式。
以下是使用內聯命令進行服務器/客戶端交互的示例(服務器聊天以S:開頭,客戶端與C聊天:)
C: PING
S: +PONG
以下是返回整數的內聯命令的另一個示例:
C: EXISTS somekey
S: :0
基本上,您只需在telnet會話中編寫以空格分隔的參數。由於沒有命令以*開頭,而是在統一請求協議中使用,因此Redis能夠檢測到這種情況並解析您的命令。
- Redis協議的高性能解析器
雖然Redis協議非常易讀且易於實現,但它可以用類似於二進制協議的性能來實現。
RESP使用前綴長度來傳輸批量數據,因此永遠不需要像json一樣,掃描有效負載以查找特殊字符,也不需要引用需要發送到服務器的有效負載。
可以使用對每個字符執行單個操作的代碼處理批量和多個批量長度,同時掃描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一起跳過而不進行任何處理。然後,可以使用不以任何方式檢查有效負載的單個讀取操作來讀取批量數據。最後,丟棄剩餘的CR和LF字符而不進行任何處理。
雖然性能與二進制協議相當,但Redis協議在大多數非常高級語言中實現起來要簡單得多,從而減少了客戶端軟件中的錯誤數量。
- AOF文件
格式如下:
*<參數數量> CR LF
$<參數 1 的字節數量> CR LF
<參數 1 的數據> CR LF
...
$<參數 N 的字節數量> CR LF
<參數 N 的數據> CR LF
例如:SET testkey testvalue
*3
$3
SET
$7
testkey
$9
testvalue
"*3\r\n$3\r\nSET\r\n$7\r\ntestkey\r\n$9\r\n testvalue\r\n"