1. 程式人生 > >redis協議規範

redis協議規範

好多年前看過redis的程式碼,那個時候還是2.6的版本,叢集和哨兵還沒加入正式程式碼,這幾年redis發展的好快。簡略翻譯一篇文章redis的https://redis.io/topics/protocol

redis的客戶端和伺服器通過一種叫RESP (REdis Serialization Protocol)協議進行通訊。雖然他是為redis設計的協議,但是也可以用到其他的CS架構軟體裡。

RESP的設計主要考慮了以下幾個要求:

  • 容易實現
  • 快速解析
  • 人類可讀

RESP能夠序列化不同的資料型別像整型,字串,陣列。還有一種專門為錯誤設計的型別。從客戶端發給伺服器的請求是一個代表命令引數的陣列。Redis返回一個該命令指定的資料型別。

RESP是二進位制安全的而且從一個程序發塊資料給另一個程序的時候不需要做轉換,因為他會在塊資料之前加上長度。

注意:這個協議只用於redis的客戶端和伺服器的資訊互動。redis叢集的node之間使用另一種二進位制協議來交換資訊。

網路層

redis的客戶端和伺服器通過6379埠建立連線。

雖然RESP是不依賴TCP協議的,但是這個協議只用在TCP連線上(或者是其他的流協議,比如unix套接字)。

請求-應答模型

 redis能夠接收帶各種型別引數的命令。處理收到的命令並且返回應答。這是最簡單的模型,但是有兩個例外:

  • redis支援管道,所以客戶端可能一次傳送多個命令然後等待應答。
  • 如果redis客戶端使用了釋出/訂閱模式(Pub/Sub),這個協議就變成了推送協議(push protocol),也就是說客戶端不用再發命令了,伺服器收到相應訊息的時候會自動發給客戶端。

除了以上兩種情況,redis協議是一個簡單的請求應答協議。

RESP協議描述

RESP協議是在redis的1.2版本引入的,之後在2.0版本成為標準協議,需要在客戶端實現。

RESP是一種序列化協議,支援以下型別:簡單字串(sample strings),錯誤(Errors),整型(integers),塊字串(bulk strings)和陣列(arrays)。

在RESP裡,資料型別是第一個位元組決定的:

  • 第一個位元組是“+”代表簡單字串型別。
  • 第一個位元組是“-”代表錯誤型別。
  • 第一個位元組是“:”代表整型。
  • 第一個位元組是“$”代表塊字串。
  • 第一個自己是“*”代表陣列。

RESP能用一個特殊的塊字串代表NULL。RESP協議不同的部分用“\r\n”(CRLF)結尾。

RESP簡單字串

簡單字串用如下方法編碼:一個“+”號後面跟字串,最後是“\r\n”,字串裡不能包含"\r\n"。簡單字串用來傳輸比較短的二進位制安全的字串。例如很多redis命令執行成功會返回“OK”,用RESP編碼就是5個位元組:

"+OK\r\n"

想要傳送二進位制安全的字串,需要用RESP的塊字串。當redis返回了一個簡單字串的時候,客戶端庫需要給呼叫者返回“+”號(不含)之後CRLF之前(不含)的字串。

RESP錯誤

RESP有一種專門為錯誤設計的型別。實際上錯誤型別很像RESP簡單字串型別,但是第一個字元是“-”。簡單字串型別和錯誤型別的區別是客戶端把錯誤型別當成一個異常,錯誤型別包含的字串是異常資訊。格式是:

"-Error message\r\n"

有錯誤發生的時候才會返回錯誤型別,例如你執行了一個對於某型別錯誤的操作,或者命令不存在等。當返回一個錯誤型別的時候客戶端庫應該發起一個異常。下面是一個錯誤型別的例子

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

“-”號之後空格或者換行符之前的字串代表返回的錯誤型別,這只是慣例,並不是RESP要求的格式。例如ERR是一般錯誤,WRONGTYPE是更具體的錯誤表示客戶端的試圖在錯誤的型別上執行某個操作。這個稱為錯誤字首,能讓客戶端更方便的識別錯誤型別。

客戶端可能為不同的錯誤返回不同的異常,也可能只提供一個一般的方法來捕捉錯誤並提供錯誤名。但是不能依賴客戶端提供的這些特性,因為有的客戶端僅僅返回一般錯誤,比如false。

RESP整型

RESP只是用一個CRLF結尾的字串代表整型,第一個自己是“:”,例如":0\r\n"或者":1000\r\n"是整型返回。有很多redis命令返回整型,比如INCR,LLEN和LASTSVAE。返回的整型沒有特定的意義,對於INCR就是一個增長後的值,對於LASTSAVE就是一個UNIX時間戳。雖然沒什麼意義,但是還是給他分配了一個64位的有符號的空間。整型返回值還可以用於返回true或者false,比如命令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塊字串

塊字串用來代表二進位制安全的字元從,長度可達512M。塊字串用如下方式編碼:

  • “$”開頭,跟著數字代表字串長度,最後是CRLF。
  • 字串。
  • 最後CRLF。

所以“foobar”會編碼成如下方式:

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

空字串如下:

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

RESP塊字串還能用一個特殊的格式來表示一個不存在的值,代表NULL值。在這個特殊的格式里長度是-1,不帶資料。所以NULL用如下格式表示:

"$-1\r\n"

這個被稱為空塊字串(NULL BULK STRING)。客戶端庫API不能返回空字串,當服務端返回空塊字串的時候客戶端需要返回nil。例如RUBY庫需要返回‘nil’,C庫需要返回NULL(或者在返回值裡設定一個特殊的標誌)。

RESP陣列

redis客戶端用RESP陣列給伺服器發命令。一些命令的返回值也是RESP陣列型別,比如LRANGE。RESP陣列使用如下格式:

  • “*”號作為第一個字元,跟著一個數字代表元素個數,後面一個CRLF。
  • 每個元素是一個RESP型別。

空陣列如下:

"*0\r\n"

包含兩個塊字串“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代表後面跟著5個元素,之後傳送每一個元素。空陣列是存在的,也可以用來表示空值(一般情況下用空塊字串,但是因為歷史原因兩種都可以用)。例如BLPOP命令超時,他會返回一個空陣列,如下:

"*-1\r\n"

當伺服器返回空陣列以後客戶端的庫API應該返回一個空物件而不是空陣列,區分不同狀態下的空陣列是有必要的。在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

上面是一個包含三個整數的陣列和一個簡單字串陣列組成的陣列。

包含空元素的陣列

數組裡的一個元素可以為空。可以用在應答裡來表明這個元素丟失了。如下陣列包含一個空元素:

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

第二個元素是空,客戶端庫應該返回如下:

["foo",nil,"bar"]

注意這不是上一節說的異常,而是為了進一步說明RESP協議。

給REDIS伺服器發命令

現在你熟悉了RESP序列化,寫一個REDIS客戶端庫應該不難。我們來進一步說明客戶端和伺服器是怎樣互動的:

  • 客戶端給伺服器端傳送只包含塊字串的陣列。
  • 伺服器會給客戶端傳送任何合法的RESP資料型別。

一個典型的互動是下面這樣的,客戶端為了獲取mylist連結串列的長度傳送LLEN mylist命令給伺服器,伺服器返回一個整數作為應答:

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

多命令和管道

如果你需要給redis伺服器發命令,但是手頭只有telnet工具怎麼辦呢?儘管redis協議是很容易實現的,但是用這種互動型的工具實現redis協議也不是理想的工具。因此redis也可以用一種特殊的方式接受人類可讀的命令,稱為內聯格式(inline command)。如下是客戶端伺服器用內聯格式的例子:

C: PING
S: +PONG

下面是另一個內聯格式,返回一個整數:

C: EXISTS somekey
S: :0

其實就是簡單的把引數用空格分開,因為命令都不是“*”開頭的,redis就會檢測這種形式並且解析。

redis協議的高效能解析器

略吧,這一段沒什麼意思。