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

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

1、介紹

            Redis客戶端使用RESP(Redis的序列化協議)協議與Redis的服務器端進行通訊。 雖然該協議是專門爲Redis設計的,可是該協議也能夠用於其餘 客戶端-服務器 (Client-Server)軟件項目。

            RESP是對如下幾件事情的折中實現:

                一、實現簡單

                二、解析快速

                三、人類可讀

            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集羣使用不一樣的二進制協議來交換節點之間的消息。


2、Redis協議的詳解

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

          一、網絡層(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).


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

                       Redis接受由不一樣參數組成的命令。 一旦接收到命令,它就會被處理而且發送響應回客戶端。

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

                             一、Redis支持管道操做(稍後會在本文檔中介紹)。因此客戶能夠一次發送多個命令,稍後等待回覆。

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

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


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

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

                        RESP其實是一個支持如下數據類型的序列化協議:簡單字符串(Simple Strings),錯誤(Errors),整數(Integers),塊字符串(Bulk Strings)和數組(Arrays)。

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

                            一、客戶端將命令做爲批量字符串的RESP數組發送到Redis服務器。

                            二、服務器(Server)根據命令執行的狀況返回一個具體的RESP類型做爲回覆。

                        在RESP協議中,有些的數據類型取決於第一個字節:

                            一、對於簡單字符串,回覆的第一個字節是「+」

                            二、對於錯誤,回覆的第一個字節是「 - 」

                            三、對於整數,回覆的第一個字節是「:」

                            四、對於批量字符串,回覆的第一個字節是「$」

                            五、對於數組,回覆的第一個字節是「*」

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

                    在RESP協議中,協議的不一樣部分始終以「\r\n」(CRLF)結尾。


      四、RESP簡單字符串(RESP Simple Strings)

                   簡單字符串按如下方式編碼:以+(加號字符)開始,後跟一個不能包含CR或LF字符的字符串(不容許換行符),以CRLF(即「\r\n」)結尾。

                   簡單字符串用於以最小開銷傳輸非二進制安全的字符串。例如,許多Redis命令在成功時回覆「OK」,由於RESP Simple String使用如下5個字節進行編碼:redis

"+OK\r\n"


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

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


      五、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 。
           

      六、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。


      七、RESP大容量字符串(RESP Bulk Strings)

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

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

                       一、一個以「$」字節開始,後面是組成字符串的字節數(前綴長度),由CRLF終止。

                        二、實際的字符串數據。

                       三、最終的CRLF。

                    因此字符串「foobar」被編碼以下:服務器

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


                   當一個空字符串只是:網絡

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


                 還可使用RESP Bulk Strings 的特殊格式來表示空值。在這種特殊的格式中,長度是-1,而且沒有數據,因此空值表示爲:socket

"$-1\r\n"


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

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


      八、RESP數組(RESP Arrays)

                 Redis客戶端使用RESP數組發送命令到Redis服務器。一樣,某些Redis命令使用 RESP數組 做爲回覆類型 將元素集合返回給客戶端。一個例子是返回列表元素的LRANGE命令。

                 RESP數組使用如下格式發送:

                        一、一個 * 字符做爲第一個字節,後面跟着一個十進制的數字,該數字是數組中元素的個數,而後是CRLF。

                        二、Array的每一個元素都有一個額外的RESP類型。

                  因此一個空的Array只是如下內容:工具

"*0\r\n"


                   雖然兩個RESP批量字符串「foo」和「bar」的數組編碼爲:性能

"*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"


                   正如您看到的那樣, * <count> CRLF 部分做爲數組的前綴,組成數組的其餘數據類型只是依次鏈接在一塊兒。例如,一個三個整數的數組編碼以下:ui

"*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的一個數組,另外一個是包含一個簡單字符串和一個錯誤組成的兩個元素的數組。


      九、數組中的空元素(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"]


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


        十、將命令發送到Redis服務器(Sending commands to a Redis Server)

                    如今您已經熟悉RESP序列化格式,編寫Redis客戶端庫的實現將很容易。咱們能夠進一步指定客戶端和服務器之間的交互如何工做:

                          一、客戶端向Redis服務器發送僅包含Bulk Strings的RESP數組。

                          二、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。


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

                     客戶端可使用相同的鏈接來發出多個命令。支持管道操做,所以客戶端可使用單個寫入操做發送多個命令,而無需在發出下一條命令以前讀取先前命令的服務器回覆。全部的答覆均可以在最後閱讀。

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


         十二、內聯命令

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

C: PING
                S: +PONG


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

C: EXISTS somekey
               S: :0


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


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


3、結束
      
            好了,今天就到這裏了。該文章也翻譯的差很少了,因爲英文水平有限,翻譯的過程當中可能會出現一些語義不通的地方,但願你們指出,我能夠修改錯誤,作的更好。好了,就說這麼多吧,若是你們想查看原文,請點擊這裏

天下國家,可均也;爵祿,可辭也;白刃,可蹈也;中庸不可能也

相關文章
相關標籤/搜索