redis通信協議(RESP )是什麼

什麼是RESP

RESP是REdis Serialization Protocol的簡稱,也就是專門爲redis設計的一套序列化協議. 這個協議其實在redis的1.2版本時就已經出現了,可是到了redis2.0才最終成爲redis通信協議的標準redis

這個序列化協議聽起來很高大上, 但實際上就是一個文本協議.根據官方的說法, 這個協議是基於如下幾點(而妥協)設計的:json

1. 實現簡單.能夠減低客戶端出現bug的機率
 2. 解析速度快.因爲RESP能知道返回數據的固定長度,因此不用像json那樣掃描整個payload去解析, 因此它的性能是能跟解析二進制數據的性能相媲美的.
 3. 可讀性好.
複製代碼

爲啥要理解RESP

其實RESP是個很簡單的東西,不用一天就能吃透. 可是我對它的認識一直都停留在一個很模糊的狀態, 以前只知道它返回的不一樣的類型是以不一樣的符號開始的,具體是什麼沒有仔細去深究.數組

直到前幾天遇到一個bug, 調試redis客戶端的時候發現對redis的返回內容特別陌生. 今天在看AOF文件時又遇到了它,才忽然悟到:書到用時方恨少啊bash

因而就有這一篇博客.併發

總結來講,RESP的應用場景有:socket

1. 開發定製化的客戶端. RESP設計成簡單的文本協議, 一大緣由就是爲了下降各類語言開發客戶端的複雜度
2. 理解RESP方便咱們分析AOF文件,瞭解redis的內部設計
3. 平時經過抓包軟件,能夠幫助快速定位redis的相關問題
4. 在沒有redis-cli的狀況下, 方便開發調試redis命令
複製代碼

RESP詳解

數據類型

通常來講,RESP只須要序列化三種數組便可: 字符串, 整數, 數組. 而在實際場景中, RESP又把字符串細化成了simple string, error string和bulk string三種.tcp

因此RESP一共涉及到5種數據類型:編輯器

1. simple string. 簡單的字符串
   2. error. 就是表示這是一個錯誤(異常)狀況
   3. integer 表示這是一個整數
   4. bulk string. 表示是長字符串,可是必須小於512M.
   5. arrays. 表示這是一個數組,數組元素能夠是上面的任意一種類型,也能夠是一個數組
複製代碼

像一些高級語言用int long等來表示不一樣數據類型同樣, RESP也有它本身標識不一樣數據類型的"語法", 就是用第一個字節的符號來表示不一樣的數據類型:性能

  1. simple string 的第一個字節是個"+"(加號), 後面接着的是字符串的內容, 最後以CRLF(\r\n)結尾.例如:
"+OK\r\n"
複製代碼
  1. error. error其實和string是相似的, 可是RESP爲了能讓不一樣客戶端把這種error和正常的返回結果區分開來對待 (例如redis返回error的話,就拋出異常),特地多設計了這個數據類型. error類型的第一個字節是"-"(減號), 後面接着的是錯誤的信息, 最後以CRLF(\r\n)結尾,例如:
"-ERR unknown command 'foobar'\r\n"
複製代碼
  1. integer 類型的第一個字節是":"(冒號), 後面接着的是整數,最後以CRLF(\r\n)結尾, 例如:
":1000\r\n"
複製代碼
  1. bulk string. 本質上也是字符串.跟普通字符串區分開來, 它的第一個字節是"$"(美圓符號),緊接着是一個整數,表示字符串的字節數,字節數後面接一個CRLF. CRLF後面是字符串的內容, 最後以一個CRLF結尾. 例如:
"$0\r\n"   --$後面的0表示這是一個空字符串

"$-1\r\n"  -- $後面的-1表示這是一個null字符串,Null Bulk String要求客戶端返回空對象,而不能簡單地返回個空字符串


"$6\r\nABCDEF\r\n"  -- ABCDEF是6個字節,因此$後面是6
複製代碼
  1. arrays的第一個字節是"*"(星號), 緊接着後面是一個數字,表示這個數組的長度,數字後面是一個CRLF. 須要注意的是這個CRLF以後纔是數組的真正內容, 並且數組內容能夠是任意類型, 包括arrays和bulk string, 每一個元素也要以CRLF結尾. 最後以CRLF(\r\n)結尾. 舉例:
"*0\r\n"   --*後面的0表示表示空的數組

"*-1\r\n"  --*後面的-1表示表示是null數組

"*5\r\n -- *5表示這是一個擁有5個元素的數組 +bar\r\n -- 第1個元素是簡單的字符串 -unknown command\r\n -- 第2個元素是個異常 :3\r\n -- 第3個元素是個整數 $3\r\n -- 第4個元素是長度爲3個字節的長字符串foo foo\r\n -- 第4個元素的內容 *3\r\n -- 第5個元素又是個數組 :1\r\n -- 第5個元素數組的第1元素 :2\r\n -- 第5個元素數組的第2元素 :3\r\n -- 第5個元素數組的第3元素 "   
複製代碼

request-response模型

通常來講,redis客戶端和服務端交互都是經過如下兩個步驟:測試

1. redis發送一個命令到服務端, 而後阻塞在socket.read()方法, 等待服務端的返回
 2. 服務端收到一個命令, 處理完成後將數據發送回去給客戶端
複製代碼

這個就被稱爲request/reponse模型. redis的大部分命令都是使用這種模型進行通信, 除了兩種狀況:

1. pipeline模式. 在pipeline模式下, 客戶端可能會把多個命令收集在一塊兒, 而後一併發送給服務端, 最後等待服務端把全部命令的執行響應一併發送回來
  2. pub/sub, 發佈訂閱模式下, redis客戶端只須要發送一次訂閱命令
複製代碼

RESP協議的request/response模型能夠總結爲如下兩個步驟

1. 客戶端發送命令, 通常組裝成bulk string的數組
 2. 服務端處理命令, 根據不一樣的命令,可能返回不一樣的數據類型
複製代碼

例如命令"set test1 1" 通常被序列化成

*3\r\n$3\r\nset\r\n$5\r\ntest1\r\n$1\r\n1\r\n


-- 爲了方便理解, 每一個CRLF咱們給它換一下行
*3\r\n        -- 這個命令包含3個(bulk)字符串
$3\r\n        -- 第一個bulk string有3個字節
set\r\n       -- 第一個bulk string是set
$5\r\n        -- 第二個bulk string有5個字節
test1\r\n     -- 第二個bulk string是test1
$1\r\n        -- 第三個bulk string有1個字節
1\r\n         -- 第三個bulk string是1
複製代碼

它的返回是:

+OK\r\n --一個簡單的字符串 
複製代碼

再例如命令"get test1":

*2\r\n$3\r\nget\r\n$5\r\ntest1\r\n
即:
*2\r\n     -- 這個命令是2個bulk字符串的數組
$3\r\n     -- 第一個bulk字符串有3個字節:  get
get\r\n
$5\r\n     -- 第二個bulk字符串有5個字節: test1
test1\r\n
   
複製代碼

這個命令的返回是:

$1\r\n   -- 只有一個字節的bulk string
1\r\n
複製代碼

再來看一個錯誤的命令"get ", 這裏咱們get的命令故意不傳參數

request:

*1\r\n
$3\r\n
get\r\n

response(跟咱們在redis-cli裏面獲取的提示是同樣的):

-ERR wrong number of arguments for 'get' command\r\n
複製代碼

測試和驗證

瞭解了RESP是什麼以後, 咱們一般都會想動手驗證一下,它實際的運行是否跟理論一致. 這個時候有兩種方法.

telnet方式

當咱們手上沒有redis-cli的時候, 有時候咱們想調試redis命令就顯得比較麻煩. 這點redis作得比較人性化, 當它發現它收到的數據不是以"*"開頭時, 它就會嘗試解析這個字符串, 把它當作一個命令來處理, 而後返回對應的RESP格式的響應.

來看一下用telnet執行咱們上面測試的3個命令:

lhh-Mac:~ lhh$ telnet localhost 6379
Trying ::1...
Connected to localhost.
Escape character is '^]'.

set test1 1
+OK

get test1
$1
1

get 
-ERR wrong number of arguments for 'get' command

quit
+OK
複製代碼

能夠看到,每一個命令返回的都是RESP格式(\r\n不可見,體現爲換行).

固然, 你也能夠發送RESP格式的命令, 可是要在本文編輯器裏面把\r\n換成換行符, 再複製過去,否則會報錯.

下面例如例子中, 我執行的命令是"get test1",RESP格式就是"*2\r\n$3get\r\n$5\r\ntets1".

返回的數據是"1", RESP格式就是"$1\r\n1\r\n"

因爲telnet窗口的緣由, request和response是連着的, 注意區分

使用telnet執行RESP格式的"get test1":

lhh-Mac:~ lhh$ telnet localhost 6379
Trying ::1...
Connected to localhost.
Escape character is '^]'.

*2
$3
get
$5
test1
$1
1

複製代碼
socket方式

在手上沒有寫代碼的條件時, 使用telnet確實很方便,當編輯起來不方便.當若是用IDE的話, 咱們仍是有更好的方式的, 就是寫代碼來測試驗證.

畢竟"talk is cheap, show me the code"嘛.

redis是基於tcp通信的, 因此簡單使用socket就好, 代碼以下:

public static void main(String[] args) throws IOException {
      Socket socket = new Socket("localhost", 6379);
      OutputStream outputStream = socket.getOutputStream();
      BufferedReader bufferedReader
              = new BufferedReader(new InputStreamReader(socket.getInputStream()));
      outputStream.write("*2\r\n$3\r\nget\r\n$5\r\ntest1\r\n".getBytes());
      int num = 0;
      char ch;
      while((num=bufferedReader.read()) != -1){
          ch = (char)num;
          System.out.print(ch);
      }
      socket.close();
  }
複製代碼

參考 redis.io/topics/prot…

相關文章
相關標籤/搜索