Redis進階實踐之十七 Redis協議的規范


一、介紹

            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個字節進行編碼:

                 "+OK\r\n"


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

                  當Redis以簡單字符串回復時,客戶端庫應該返回給調用者一個由'+'后的第一個字符組成的字符串,直到字符串結尾,不包括最終的CRLF字節。


      5、RESP錯誤(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 。
           

      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”被編碼如下:

               "$6\r\nfoobar\r\n"


                   當一個空字符串只是:

             "$0\r\n\r\n"


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

              "$-1\r\n"


                 這被稱為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\r\n"


                   雖然兩個RESP批量字符串“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,以指定接下來的五個回復。然后,構成多批量回復的項目的每個回復都被傳送。

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

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

               "*-1\r\n"


                   當Redis使用空數組響應時,客戶端庫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的一個數組,另一個是包含一個簡單字符串和一個錯誤組成的兩個元素的數組。


      9、數組中的空元素(Null elements in Arrays)

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

                *3\r\n
                $3\r\n
                foo\r\n
                $-1\r\n
                $3\r\n
                bar\r\n


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

                ["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:服務器)。

                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。


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

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

                      欲了解更多信息,請查看我們關於管道的頁面


         12、內聯命令

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

                C: PING
                S: +PONG


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

               C: EXISTS somekey
               S: :0


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


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

                     盡管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協議在大多數高級語言中實現起來要簡單得多,可減少在實現客戶端的軟件中的錯誤數量。


三、結束
      
            好了,今天就到這里了。該文章也翻譯的差不多了,由於英文水平有限,翻譯的過程中可能會出現一些語義不通的地方,希望大家指出,我可以修改錯誤,做的更好。好了,就說這么多吧,如果大家想查看原文,請點擊這里


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM