天天看點

Redis protocol (redis通信協定)

Reids protocol, redis通信協定

以前隻會用redis -cli用戶端, 或者在java程式裡使用jedis來作為媒體與redis伺服器通信.這兩天正好在實習中接觸了一點這方面的内容. 五一放假就稍微翻了翻redis相關資料.

本篇部落格一來是給自己整理筆記,二來是給學校的同學們分享: 面向于使用過redis, 但隻是停留在基本set get的指令, 不了解其中原理的同學.

介紹redis的ping pong

首先介紹一下ping pong.. (既然有同學不懂..我就再加上ping pong的介紹吧)

登入redis cli用戶端後, 輸入ping, 伺服器會傳回pong, 來表示連接配接狀況是完好的, 也表示了伺服器大體上是正常運轉的.

Redis protocol (redis通信協定)

其中的第一行是我用docker 啟動的用戶端, 大家如果不是docker的話, 自己正常啟動redis -cli就行..

ping之後就會收到pong

(大家問了docker怎麼用...我這裡附上一個位址...http://www.runoob.com/docker/docker-install-redis.html )

使用Java socket 來實作 Redis 的ping pong

抄代碼的時候大家良心一點...不要去用我的ip去試...(非要拿我的試也沒關系...因為我已經偷偷改掉一位數了)

public static void main(String[] args) throws Exception {
        // socket
        Socket socket = new Socket("140.143.135.210", 6379);

        // oi流
        OutputStream os = socket.getOutputStream();
        InputStream is = socket.getInputStream();

        // 向redis伺服器寫
        os.write("PING\r\n".getBytes());

        //從redis伺服器讀,到bytes中
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);

        // to string 輸出一下
        System.out.println(new String(bytes,0,len));
    }
      

 傳回的結果如下:

Redis protocol (redis通信協定)

問: 為什麼會有一個 '+'符号 呢?  redis -cli裡是沒有這個加号的呀?  答:這個和通信協定有關, 一會兒再介紹具體的含義. 不過redis -cli隻是把這個'+'符号吞掉處理了, 沒顯示出來罷了. 如果這麼說還不了解的話......看下面代碼...

public static void main(String[] args) throws Exception {
        // socket
        Socket socket = new Socket("140.143.135.210", 6379);

        // oi流
        OutputStream os = socket.getOutputStream();
        InputStream is = socket.getInputStream();

        // 向redis伺服器寫
        os.write("PING\r\n".getBytes());

        //從redis伺服器讀,到bytes中
        byte[] bytes = new byte[1024];
        if(is.read()=='+'){
            // to string 輸出一下
            int len = is.read(bytes);
            System.out.println(new String(bytes,0,len));
        }
        // else if $
        // else if *
        // else
    }
      

 這樣就跟redis -cli裡的一樣啦.就隻是pong了

Redis protocol (redis通信協定)

ps: 不是我逗大家玩....jedis在在協定層也是類似于這樣的寫法, 把 $ * + 這幾個符号挨個判斷來确定傳輸内容的含義的...

實作一下SET 和 GET 吧

咱們先實作, 稍後再講解其中的協定内容

 set:

public static void main(String[] args) throws Exception {
        // socket
        Socket socket = new Socket("140.143.135.210", 6379);

        // oi流
        OutputStream os = socket.getOutputStream();
        InputStream is = socket.getInputStream();

        // 向redis伺服器寫
        os.write("set hello world123\r\n".getBytes());

        //從redis伺服器讀,到bytes中
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);

        // to string 輸出一下
        System.out.println(new String(bytes,0,len));
    }
      
Redis protocol (redis通信協定)

get:

public static void main(String[] args) throws Exception {
        // socket
        Socket socket = new Socket("140.143.135.310", 6379);

        // oi流
        OutputStream os = socket.getOutputStream();
        InputStream is = socket.getInputStream();

        // 向redis伺服器寫
        os.write("get hello\r\n".getBytes());

        //從redis伺服器讀,到bytes中
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);

        // to string 輸出一下
        System.out.println(new String(bytes,0,len));
    }
      
Redis protocol (redis通信協定)

解釋上面例子中的+和$符号

ps:本文隻是一個部落格,并非官方文檔, 是以不會一下子抛出很多概念, 隻是一點點引導着同學們看, 看的着急的同學可以直接去看官方文檔

https://redis.io/topics/protocol ,   這裡還有個中文版: http://doc.redisfans.com/topic/protocol.html

加号'+' 是來表示狀态回複的, 在redis服務端向用戶端傳回狀态資訊時, 就會先發送一個`+`符号來開頭.

接下來是相應的狀态資訊, 例如'OK'什麼的.

最後, 要以'\r\n' 來結尾... 咱們看一下代碼就明白了

public static void main(String[] args) throws Exception {
        // socket
        Socket socket = new Socket("140.143.135.210", 6379);

        // oi流
        OutputStream os = socket.getOutputStream();
        InputStream is = socket.getInputStream();

        // 向redis伺服器寫
        os.write("set hello world123\r\n".getBytes());

        //從redis伺服器讀,到bytes中
        byte[] bytes = new byte[1024];
        if (is.read() == '+') {
            System.out.println("這是一個狀态回複哦! 怎麼知道的呢? `+` 号就表示 '狀态回複' 了");
            int len = is.read(bytes);
            System.out.println("回複的狀态是: " + new String(bytes, 0, len));
        }

        // 大家想不想看看bytes裡面到底有幾個字元嗎?
        System.out.println(Arrays.toString(bytes));
        // 輸出的是 [79, 75, 13, 10, 0, 0, 0, 0, 0,....]
        // 其中 79 75 是 `OK`
        // 其中 13 10 是 `\r\n`
        // 後面的一串0 是 表示沒有後續内容, 已經讀完.
    }
      

 $ 表示批量讀取, 一般格式是: $<數字>, 數字來表示正文的内容的位元組數 

Redis protocol (redis通信協定)

抓包後是這樣的, 用戶端向服務端發送了"get hello", 服務端向用戶端發送了藍色的這兩行.

public static void main(String[] args) throws Exception {
        // socket
        Socket socket = new Socket("140.143.135.210", 6379);

        // oi流
        OutputStream os = socket.getOutputStream();
        // 為了解析'\r\n'友善, 我就用改為字元流了
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        // 向redis伺服器寫
        os.write("get hello\r\n".getBytes());

        // 緩沖數組
        char[] chars = new char[1024];

        //從redis伺服器讀,到bytes中
        if (br.read() == '$') {
            System.out.println("這是一個批量回複哦! 怎麼知道的呢? `$` 号就表示 '批量回複' 了");
            System.out.println("$ 後面會跟一個數字, 來表示正文内容的大小");
            // readLine直接能判斷'\r' '\n'
            int len = Integer.parseInt(br.readLine());
            System.out.println("$後面跟着的數字是: " + len + ", 表示正文是" + len + "個位元組, 接下來隻要讀取" + len + "個位元組就好了");

            // 接下來隻讀取len個字元就ok了  (其實機關應該是位元組, 但是我中途為了readLine省事, 改用了字元流, 個數是不變的)
            br.read(chars, 0, len);
            System.out.println("get到的結果是: " + new String(chars, 0, len) + ", 數一數真的是" + len + "個字元");
        }
    }
      

Redis通信協定就隻是這樣?

no!!!剛才用戶端向服務端發送的 "get hello" , 這種隻是"内聯指令", 而不是Redis真正的通信協定.

問: 什麼意思呢? 答:  就是說你可以像之前那樣給服務端發, 伺服器端接受到後, 會周遊一遍你發送的内容, 最後根據空格來分析你所發的内容的含義.

問: 這樣有什麼不好的嗎?  答: 如果這樣的話, 你就把解析的工作交給了伺服器來做, 會加大伺服器的工作量.

問: 那怎麼樣才是符合規範的呢? 符合協定的話真的會提高伺服器的效率? 答: 首先看一下符合協定的用戶端和服務端之間的互動把.如下例子:

例: set java python ,抓到包之後是這樣的:

Redis protocol (redis通信協定)

紅色是用戶端發送的内容, 藍色是伺服器端傳回的内容.

咱們一起解析一下:

*3表示 , 用戶端即将發送3段内容

哪三段呢? 第一段: '$3 SET'  第二段: '$4 java'   第三段: '$6 python'

更嚴格地說: 第一段: '$3\r\nSET\r\n'  第二段:'$4\r\njava\r\n'  第三段:'$6\r\npython\r\n'

$符号的意思在上一小節就已經提到過了, 表示下文的内容的長度, 友善伺服器進行讀取.

例如: $6就已經把python的長度給彙報出來了, 伺服器隻需要截取區間[index, index+6]就好了, 不需要去找空格在什麼地方(找空格的時間複雜度是O(n), 而$6這種寫法是O(1) )

 Jedis呢?

其實Jedis做的工作大體就是把SET key value 這樣的格式轉化為下面這種格式, 然後發到Redis服務端:

*3\r\n
$3\r\n
SET\r\n
$3\r\n
key\r\n
$5\r\n
value\r\n
      

  

---------------------------------------------------------

學如不及,猶恐失之