一、介紹
Redis用戶端使用RESP(Redis的序列化協定)協定與Redis的伺服器端進行通信。 雖然該協定是專門為Redis設計的,但是該協定也可以用于其他 用戶端-伺服器 (Client-Server)軟體項目。
RESP是對以下幾件事情的折中實作:
1、實作簡單
2、解析快速
3、人類可讀
RESP可以序列化不同的資料類型,如整數(integers),字元串(strings),數組(arrays)。它還使用了一個特殊的類型來表示錯誤(errors)。請求以字元串數組的形式來表示要執行指令的參數從用戶端發送到Redis伺服器。Redis使用指令特有(command-specific)資料類型作為回複。
RESP協定是二進制安全的,并且不需要處理從一個程序傳輸到另一個程序的塊資料的大小,因為它使用字首長度(prefixed-length)的方式來傳輸塊資料的。
(RESP is binary-safe and does not require processing of bulk data transferred from one process to another, because it uses prefixed-length to transfer bulk data.)
注意:該文章所說的協定是僅用于 用戶端 - 伺服器(Client-Server)的通信。 Redis叢集使用不同的二進制協定來交換節點之間的消息。
二、Redis協定的詳解
要想更好的使用Redis,如果沒有對Redis的協定更深的了解,要想精通恐怕很難,現在我們就來看看Redis的協定是什麼。
1、網絡層(Networking layer)
用戶端連接配接到Redis的伺服器,建立到端口6379的TCP連接配接。
盡管,RESP協定是非TCP專用的技術,但在Redis的環境中,該協定僅用于TCP連接配接(或類似于Unix套接字的面向流的連接配接)。
While RESP is technically non-TCP specific, in the context of Redis the protocol is only used with TCP connections (or equivalent stream oriented connections like Unix sockets).
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協定中,協定的不同部分始終以“\r\n”(CRLF)結尾。
4、RESP簡單字元串(RESP Simple Strings)
簡單字元串按以下方式編碼:以+(加号字元)開始,後跟一個不能包含CR或LF字元的字元串(不允許換行符),以CRLF(即“\r\n”)結尾。
簡單字元串用于以最小開銷傳輸非二進制安全的字元串。例如,許多Redis指令在成功時回複“OK”,因為RESP Simple String使用以下5個位元組進行編碼:
為了發送二進制安全的字元串,需要使用RESP Bulk Strings。
當Redis以簡單字元串回複時,用戶端庫應該傳回給調用者一個由'+'後的第一個字元組成的字元串,直到字元串結尾,不包括最終的CRLF位元組。
5、RESP錯誤(RESP Errors)
RESP協定針對錯誤具有特定資料類型表示。實際上,錯誤與RESP Simple Strings完全相同,但第一個字元是減号' - '而不是加号。簡單字元串和RESP錯誤之間的真正差別在于錯誤被用戶端視為異常,而組成錯誤類型的字元串本身就是錯誤資訊。
基本格式是:
錯誤回複僅在發生錯誤時發送,例如,如果您嘗試針對錯誤的資料類型執行操作,或者指令不存在等等。 當收到錯誤應答時,用戶端就應該抛出一個異常。
以下是錯誤回複的示例:
“ - ”之後的第一個單詞,直到第一個空格或換行符,表示傳回的錯誤種類。這隻是Redis使用的一種約定,并不是RESP錯誤格式的一部分。
例如,ERR是通用錯誤,而WRONGTYPE是一個更具體的錯誤,意味着用戶端試圖針對錯誤的資料類型執行操作。 這被稱為錯誤字首,并且是一種允許用戶端了解伺服器傳回的錯誤類型而不依賴于給定的确切消息的方式,該消息可能随時間而改變。
用戶端實作可能會針對不同的錯誤傳回不同類型的異常,或者可能會提供一種通用方法來通過直接将錯誤名稱作為字元串提供給調用者來捕獲錯誤。
然而,這樣的功能不應該被認為是至關重要的,因為它很少有用,而針對用戶端有限的實作來說可能僅僅傳回一個通用錯誤條件,例如 false 。
6、RESP整數(RESP Integers)
這種以“:”位元組為字首,并且隻是以一個CRLF終止字元串的類型就表示是整數。 例如“:0\r\n”或“:1000\r\n”是整數回複。
許多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”被編碼如下:
當一個空字元串隻是:
還可以使用RESP Bulk Strings 的特殊格式來表示空值。在這種特殊的格式中,長度是-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隻是以下内容:
雖然兩個RESP批量字元串“foo”和“bar”的數組編碼為:
正如您看到的那樣, * <count> CRLF 部分作為數組的字首,組成數組的其他資料類型隻是依次連接配接在一起。例如,一個三個整數的數組編碼如下:
數組可以包含混合類型,元素之間不必是同一類型的。例如,一個四個整數和一個字元串塊的清單可以編碼如下:
(為了清楚起見,應答内容分為多行)。
伺服器發送的第一行是 *5\r\n,以指定接下來的五個回複。然後,構成多批量回複的項目的每個回複都被傳送。
Null數組的概念也存在,并且是指定Null值的替代方法(通常使用Null Bulk String,但由于曆史原因,我們有兩種格式)。
例如,當BLPOP指令逾時時,它會傳回一個空數組,其計數為-1,如下例所示:
當Redis使用空數組響應時,用戶端庫API應傳回空對象而不是空數組。這是區分空清單和不同條件(例如BLPOP指令的逾時條件)所必需的。
在RESP協定中也有可能存在數組的數組。例如,兩個數組的數組編碼如下:
(回複内容被分成多行,并加了空行,隻是為了閱讀友善)。
上述RESP資料類型的編碼表示了一個包含兩個數組元素的數組,一個是包含三個整數1,2,3的一個數組,另一個是包含一個簡單字元串和一個錯誤組成的兩個元素的數組。
9、數組中的空元素(Null elements in Arrays)
數組中的單個元素可能為空。這用于Redis回複中,以表示這些元素缺失并且不是空的字元串。當SORT指令使用GET模式選項時,如果缺少指定的鍵,可能會發生這種情況。 包含Null元素的Array回複的示例:
第二個元素是空值。 用戶端庫應該傳回如下所示的内容:
請注意,這不是前面章節中所述的異常情況,而隻是進一步指定協定的一個示例。
10、将指令發送到Redis伺服器(Sending commands to a Redis Server)
現在您已經熟悉RESP序列化格式,編寫Redis用戶端庫的實作将很容易。我們可以進一步指定用戶端和伺服器之間的互動如何工作:
1、用戶端向Redis伺服器發送僅包含Bulk Strings的RESP數組。
2、Redis伺服器回複發送任何有效的RESP資料類型作為用戶端的回複。
例如,一個典型的互動可能是如下這樣。
用戶端發送指令 LLEN mylist以擷取存儲在鍵名為mylist中的清單的長度,并且伺服器以如下例子回複一個整數應答(C:是用戶端,S:伺服器)。
通常我們将協定的不同部分用換行符分開,但實際的互動是用戶端作為一個整體發送 *2\r\n$4\r\nLLEN\r\n$6\r\nmylist\r\n。
11、多個指令和管道(Multiple commands and pipelining)
用戶端可以使用相同的連接配接來發出多個指令。支援管道操作,是以用戶端可以使用單個寫入操作發送多個指令,而無需在發出下一條指令之前讀取先前指令的伺服器回複。所有的答複都可以在最後閱讀。
12、内聯指令
有時在你的手中隻有telnet工具,并且你需要發送一個指令到Redis伺服器。雖然Redis協定易于實作,但在互動式會話中使用并不理想,而redis-cli可能并不總是可用。出于這個原因,Redis也以一種專門為人類設計的方式接受指令,并被稱為内聯指令格式。
以下是傳回整數的内聯指令的另一個示例:
基本上你隻需在telnet會話中編寫空格分隔的參數。由于沒有以統一請求協定中使用的 * 開始的指令,Redis 能夠檢測到這種情況并解析您的指令。
13、Redis協定的高性能分析器(High performance parser for the Redis protocol)
盡管Redis協定非常易于人工閱讀并且易于實作,但它也可以通過類似二進制協定的性能來實作。
RESP使用字首長度來傳輸批量資料,是以永遠不需要掃描特殊字元有效負載,例如使用JSON發生的情況,也不需要承擔發送到伺服器的有效負載。
批量和多批量的長度可以使用代碼進行計算,每個字元執行一次計算操作,同時掃描CR字元檢查,像下面的C代碼一樣:
在識别出第一個CR之後,可以在不進行任何處理的情況下将其與以下LF一起跳過。然後可以使用單個讀取操作讀取批量資料,該操作不會以任何方式檢查有效負載。最後,剩餘的 CR 和 LF 字元将被丢棄而不進行任何處理。
雖然在性能上與二進制協定相當,但Redis協定在大多數進階語言中實作起來要簡單得多,可減少在實作用戶端的軟體中的錯誤數量。
三、結束