1. 程式人生 > 實用技巧 >【轉】最詳細的Redis通訊協議規範

【轉】最詳細的Redis通訊協議規範

理解Redis通訊協議是深入理解Redis的基礎內容之一,今天將官網關於Redis通訊協議RESP的內容搬到這裡,希望對大家有所幫助。

介紹

Redis客戶端使用RESP(Redis的序列化協議)協議與Redis的伺服器端進行通訊。 雖然該協議是專門為Redis設計的,但是該協議也可以用於其他 客戶端-伺服器 (Client-Server)軟體專案。RESP是對以下幾件事情的折中實現:

1、實現簡單

2、解析快速

3、人類可讀

RESP可以序列化不同的資料型別,如整數(integers),字串(strings),陣列(arrays)。它還使用了一個特殊的型別來表示錯誤(errors)。請求以字串陣列的形式來表示要執行命令的引數從客戶端傳送到Redis伺服器。Redis使用命令特有(command-specific)資料型別作為回覆。

RESP協議是二進位制安全的,並且不需要處理從一個程序傳輸到另一個程序的塊資料的大小,因為它使用字首長度(prefixed-length)的方式來傳輸塊資料的。

注意:該文章所說的協議是僅用於客戶端 - 伺服器(Client-Server)的通訊。 Redis叢集使用不同的二進位制協議來交換節點之間的訊息。

Redis協議的詳解

要想更好的使用Redis,如果沒有對Redis的協議更深的瞭解,要想精通恐怕很難,現在我們就來看看Redis的協議是什麼。

1、網路層(Networking layer)

客戶端連線到Redis的伺服器,建立到埠6379的TCP連線。

儘管,RESP協議是非TCP專用的技術,但在Redis的環境中,該協議僅用於TCP連線(或類似於Unix套接字的面向流的連線)。

2、請求-響應模型(Request-Response model)

Redis接受由不同引數組成的命令。 一旦接收到命令,它就會被處理並且傳送響應回客戶端。

這是最簡單的模式,但也有兩個例外的情況:

1、Redis支援管道操作(稍後會在本文件中介紹)。所以客戶可以一次傳送多個命令,稍後等待回覆。

2、當Redis客戶端訂閱 Pub/Sub模式的通道時,協議會改變語義變成推送協議,也就是說,客戶端不再需要傳送命令,因為伺服器一旦收到訊息就會自動向客戶端傳送該新訊息(對於訂閱了通道的客戶端)。

除了上述兩個例外,Redis協議就是一個簡單的 請求-響應 協議。

3、RESP協議描述(RESP protocol description)

RESP協議在Redis 1.2版本中引入,但它已成為在Redis 2.0版本中與Redis伺服器溝通的標準方式。這是您應該在Redis客戶端中實現的協議。

RESP實際上是一個支援以下資料型別的序列化協議:簡單字串(Simple Strings),錯誤(Errors),整數(Integers),塊字串(Bulk Strings)和陣列(Arrays)。

在Redis中,RESP用作 請求-響應 協議的方式如下:

1、客戶端將命令作為批量字串的RESP陣列傳送到Redis伺服器。

2、伺服器(Server)根據命令執行的情況返回一個具體的RESP型別作為回覆。

在RESP協議中,有些的資料型別取決於第一個位元組:

1、對於簡單字串,回覆的第一個位元組是“+”

2、對於錯誤,回覆的第一個位元組是“ - ”

3、對於整數,回覆的第一個位元組是“:”

4、對於批量字串,回覆的第一個位元組是“$”

5、對於陣列,回覆的第一個位元組是“*”

此外,稍後會講RESP協議能夠使用指定的 Bulk Strings 或Array 的特殊變數來表示空值。

在RESP協議中,協議的不同部分始終以“ ”(CRLF)結尾。

4、RESP簡單字串(RESP Simple Strings)

簡單字串按以下方式編碼:以+(加號字元)開始,後跟一個不能包含CR或LF字元的字串(不允許換行符),以CRLF(即“ ”)結尾。

簡單字串用於以最小開銷傳輸非二進位制安全的字串。例如,許多Redis命令在成功時回覆“OK”,因為RESP Simple String使用以下5個位元組進行編碼:

 "+OK"

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

當Redis以簡單字串回覆時,客戶端庫應該返回給呼叫者一個由'+'後的第一個字元組成的字串,直到字串結尾,不包括最終的CRLF位元組。

5、RESP錯誤(RESP Errors)

RESP協議針對錯誤具有特定資料型別表示。實際上,錯誤與RESP Simple Strings完全相同,但第一個字元是減號' - '而不是加號。簡單字串和RESP錯誤之間的真正區別在於錯誤被客戶端視為異常,而組成錯誤型別的字串本身就是錯誤資訊。

基本格式是:

 "-Error message"

錯誤回覆僅在發生錯誤時傳送,例如,如果您嘗試針對錯誤的資料型別執行操作,或者命令不存在等等。 當收到錯誤應答時,客戶端就應該丟擲一個異常。

以下是錯誤回覆的示例:

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

“ - ”之後的第一個單詞,直到第一個空格或換行符,表示返回的錯誤種類。這只是Redis使用的一種約定,並不是RESP錯誤格式的一部分。

例如,ERR是通用錯誤,而WRONGTYPE是一個更具體的錯誤,意味著客戶端試圖針對錯誤的資料型別執行操作。 這被稱為錯誤字首,並且是一種允許客戶端了解伺服器返回的錯誤型別而不依賴於給定的確切訊息的方式,該訊息可能隨時間而改變。

客戶端實現可能會針對不同的錯誤返回不同型別的異常,或者可能會提供一種通用方法來通過直接將錯誤名稱作為字串提供給呼叫者來捕獲錯誤。

然而,這樣的功能不應該被認為是至關重要的,因為它很少有用,而針對客戶端有限的實現來說可能僅僅返回一個通用錯誤條件,例如 false 。

6、RESP整數(RESP Integers)

這種以“:”位元組為字首,並且只是以一個CRLF終止字串的型別就表示是整數。 例如“:0 ”或“:1000 ”是整數回覆。

許多Redis命令返回RESP整數,如 INCR,LLEN 和 LASTSAVE。

返回的整數沒有特殊含義,它只是INCR的增量數,LASTSAVE的UNIX時間等等。但是,返回的整數保證位於有符號的64位整數範圍內。

整數回覆也廣泛用於返回true或false。例如像 EXISTS 或 SISMEMBER 這樣的命令將返回1為真,0為假。

其他命令如 SADD,SREM 和 SETNX 將在實際執行操作時返回1,否則返回0。

以下命令將回復一個整數回覆:SETNX,DEL,EXISTS,INCR,INCRBY,DECR,DECRBY,DBSIZE,LASTSAVE,REINENX,MOVE,LLEN,SADD,SREM,SISMEMBER,SCARD。

7、RESP大容量字串(RESP Bulk Strings)

大容量字串用於表示長達512 MB的單個二進位制安全字串。

大容量字串按以下方式編碼:

1、一個以“$”位元組開始,後面是組成字串的位元組數(字首長度),由CRLF終止。

2、實際的字串資料。

3、最終的CRLF。

所以字串“foobar”被編碼如下:

 "$6 foobar"

當一個空字串只是:

 "$0 "

還可以使用RESP Bulk Strings 的特殊格式來表示空值。在這種特殊的格式中,長度是-1,並且沒有資料,所以空值表示為:

 "$-1"

這被稱為Null Bulk String(空的大字串)。

當伺服器使用空字串進行回覆時,客戶端庫API不應該返回空字串,而是返回一個nil物件。例如,Ruby庫應該返回'nil',而C庫應該返回NULL值(或者在應答物件中設定一個特殊的標誌),等等。

8、RESP陣列(RESP Arrays)

Redis客戶端使用RESP陣列傳送命令到Redis伺服器。同樣,某些Redis命令使用 RESP陣列 作為回覆型別 將元素集合返回給客戶端。一個例子是返回列表元素的LRANGE命令。

RESP陣列使用以下格式傳送:

1、一個 * 字元作為第一個位元組,後面跟著一個十進位制的數字,該數字是陣列中元素的個數,然後是CRLF。

2、Array的每個元素都有一個額外的RESP型別。

所以一個空的Array只是以下內容:

 "*0"

雖然兩個RESP批量字串“foo”和“bar”的陣列編碼為:

  1. "*2
  2. $3
  3. foo
  4. $3
  5. bar
  6. "

正如您看到的那樣, * <count> CRLF 部分作為陣列的字首,組成陣列的其他資料型別只是依次連線在一起。例如,一個三個整數的陣列編碼如下:

  1. "*3
  2. :1
  3. :2
  4. :3
  5. "

陣列可以包含混合型別,元素之間不必是同一型別的。例如,一個四個整數和一個字串塊的列表可以編碼如下:

  1. *5
  2. :1
  3. :2
  4. :3
  5. :4
  6. $6
  7. foobar

(為了清楚起見,應答內容分為多行)。

伺服器傳送的第一行是 *5 ,以指定接下來的五個回覆。然後,構成多批量回復的專案的每個回覆都被傳送。

Null陣列的概念也存在,並且是指定Null值的替代方法(通常使用Null Bulk String,但由於歷史原因,我們有兩種格式)。

例如,當BLPOP命令超時時,它會返回一個空陣列,其計數為-1,如下例所示:

  1. "*-1
  2. "

當Redis使用空陣列響應時,客戶端庫API應返回空物件而不是空陣列。這是區分空列表和不同條件(例如BLPOP命令的超時條件)所必需的。

在RESP協議中也有可能存在陣列的陣列。例如,兩個陣列的陣列編碼如下:

  1. *2
  2. *3
  3. :1
  4. :2
  5. :3
  6. *2
  7. +Foo
  8. -Bar

(回覆內容被分成多行,並加了空行,只是為了閱讀方便)。

上述RESP資料型別的編碼表示了一個包含兩個陣列元素的陣列,一個是包含三個整數1,2,3的一個數組,另一個是包含一個簡單字串和一個錯誤組成的兩個元素的陣列。

9、陣列中的空元素(Null elements in Arrays)

陣列中的單個元素可能為空。這用於Redis回覆中,以表示這些元素缺失並且不是空的字串。當SORT命令使用GET模式選項時,如果缺少指定的鍵,可能會發生這種情況。 包含Null元素的Array回覆的示例:

  1. *3
  2. $3
  3. foo
  4. $-1
  5. $3
  6. bar

第二個元素是空值。 客戶端庫應該返回如下所示的內容:

 ["foo",nil,"bar"]

請注意,這不是前面章節中所述的異常情況,而只是進一步指定協議的一個示例。

10、將命令傳送到Redis伺服器(Sending commands to a Redis Server)

現在您已經熟悉RESP序列化格式,編寫Redis客戶端庫的實現將很容易。我們可以進一步指定客戶端和伺服器之間的互動如何工作:

1、客戶端向Redis伺服器傳送僅包含Bulk Strings的RESP陣列。

2、Redis伺服器回覆傳送任何有效的RESP資料型別作為客戶端的回覆。

例如,一個典型的互動可能是如下這樣。

客戶端傳送命令 LLEN mylist以獲取儲存在鍵名為mylist中的列表的長度,並且伺服器以如下例子回覆一個整數應答(C:是客戶端,S:伺服器)。

  1. C: *2
  2. C: $4
  3. C: LLEN
  4. C: $6
  5. C: mylist
  6. S: :48293
  7. 通常我們將協議的不同部分用換行符分開,但實際的互動是客戶端作為一個整體傳送 *2 $4 LLEN $6 mylist 。

11、多個命令和管道(Multiple commands and pipelining)

客戶端可以使用相同的連線來發出多個命令。支援管道操作,因此客戶端可以使用單個寫入操作傳送多個命令,而無需在發出下一條命令之前讀取先前命令的伺服器回覆。所有的答覆都可以在最後閱讀。

欲瞭解更多資訊,請檢視我們關於管道的頁面。

12、內聯命令

有時在你的手中只有telnet工具,並且你需要傳送一個命令到Redis伺服器。雖然Redis協議易於實現,但在互動式會話中使用並不理想,而redis-cli可能並不總是可用。出於這個原因,Redis也以一種專門為人類設計的方式接受命令,並被稱為內聯命令格式。

  1. C: PING
  2. S: +PONG

以下是返回整數的內聯命令的另一個示例:

  1. C: EXISTS somekey
  2. S: :0

基本上你只需在telnet會話中編寫空格分隔的引數。由於沒有以統一請求協議中使用的 * 開始的命令,Redis 能夠檢測到這種情況並解析您的命令。

13、Redis協議的高效能分析器(High performance parser for the Redis protocol)

儘管Redis協議非常易於人工閱讀並且易於實現,但它也可以通過類似二進位制協議的效能來實現。

RESP使用字首長度來傳輸批量資料,因此永遠不需要掃描特殊字元有效負載,例如使用JSON發生的情況,也不需要承擔傳送到伺服器的有效負載。

批量和多批量的長度可以使用程式碼進行計算,每個字元執行一次計算操作,同時掃描CR字元檢查,像下面的C程式碼一樣:

  1. #include <stdio.h>
  2. int main(void) {
  3. unsigned char *p = "$123";
  4. int len = 0;
  5. p++;
  6. while(*p != '') {
  7. len = (len*10)+(*p - '0');
  8. p++;
  9. }
  10. /* Now p points at '', and the len is in bulk_len. */
  11. printf("%d", len);
  12. return 0;
  13. }

在識別出第一個CR之後,可以在不進行任何處理的情況下將其與以下LF一起跳過。然後可以使用單個讀取操作讀取批量資料,該操作不會以任何方式檢查有效負載。最後,剩餘的 CR 和 LF 字元將被丟棄而不進行任何處理。

雖然在效能上與二進位制協議相當,但Redis協議在大多數高階語言中實現起來要簡單得多,可減少在實現客戶端的軟體中的錯誤數量。