本文默認你是使用過redis並瞭解redis的基礎概念,學習redis入門並不難,給你介紹各種API使用也沒啥意思。在這裏 不會給你堆各種專業詞彙,只有大白話
什麼是協議? 就是一種格式規範,使用者們都要遵循這種規範已達到數據的可傳遞性和通用性,比如在7層網絡模型中應用層的https協議,全世界的計算機都遵循它纔有了互聯網的快速發展,
現在基本每個架構在使用公用協議的基礎上都會有自己的小協議,這麼想吧 只要涉及到數據傳遞的都會使用協議。
我們的redis當然也有自己的小本本了。就是RESP協議(REdis Serialization Protocol),這個協議簡單說就是字符串。工作中大家都處理過日誌吧,在生成日誌時是不是我們會自定義一些格式已方便我們快速的定位問題,RESP 就可以理解成是一定規則(就當是分隔符吧)拼裝起來的字符串
我們知道redis的QPS號稱10W+,使用內存操作數據是最快的原因,試想一下 如果採用了一種非常複雜的協議作爲redis的數據載體,客戶端吧數據裝入,網絡中傳輸,服務端在解析都是一種耗時的操作,
作者在設計上已經考慮到這種原因,所以設計了RESP協議,他最大的優勢就是簡單,容易實現,解析快,人類可讀,
注意:RESP 雖然是爲 Redis 設計的,但是同樣也可以用於其他C/S架構,再次強調 重要的是 思想,思想 ,不要唄技術侷限了
一。協議說明
關於RESP的說明,中文文檔http://redisdoc.com的 功能文檔-> 通信協議 裏已經說的很明白了
引入文檔說明如下
-
狀態回覆(status reply)的第一個字節是
"+" (這幾個可以理解爲字符串的分隔符)
-
錯誤回覆(error reply)的第一個字節是
"-"
-
整數回覆(integer reply)的第一個字節是
":"
-
批量回復(bulk reply)的第一個字節是
"$"
-
多條批量回復(multi bulk reply)的第一個字節是
"*" (數組)
新版統一請求協議在 Redis 1.2 版本中引入, 並最終在 Redis 2.0 版本成爲 Redis 服務器通信的標準方式。
你的 Redis 客戶端應該按照這個新版協議來進行實現。
在這個協議中, 所有發送至 Redis 服務器的參數都是二進制安全(binary safe)的。
以下是這個協議的一般形式:
*<參數數量> CR LF $<參數 1 的字節數量> CR LF <參數 1 的數據> CR LF ... $<參數 N 的字節數量> CR LF <參數 N 的數據> CR LF
舉個例子, 以下是一個命令協議的打印版本:
*3 表示後面有三個命令字符 (set mykey myvalue這三個)/(也可以理解後面會出現三個 $) $3 表示後面的字符串長度是3 SET $5 表示後面的字符串長度是5 mykey $7 表示後面的字符串長度是7 myvalue
看上面的示例 爲什麼他們是豎着的,答案很簡單每一行的後面都會跟着一個結束的分隔符就是 \r\n
這個命令的實際協議值如下:
"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"
二。手寫redis客戶端
協議介紹清楚了,這就根據這協議實現我們自己的redis客戶端吧,
第一步要解決的問題,怎麼連接到redis, 如果你在你的IDEA中點進去看過就知道redis其實就是使用Socket連接到服務器的,
(不信可以比如jedis.set()點進去看看呦)
第二步要解決的問題,根據命令拼裝RESP協議
第三步就是編碼了,需求都理清楚了,編碼工作就是小意思了
package com.abc.demo;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/*****
* 手寫Redis客戶端,理解 RESP 協議
*
* 例如 set mads shuaige
* 轉義成RESP的寫法後
* *3
* $3
* set
* $4
* mads
* $7
* shuaige
*
* ps:注意每個回車代表了結束符 \r\n
* @author mads
*/
public class JedisDemo {
private Socket jedis = null;
private String CTRL = "\r\n";//結束語
private String FUNCTION_XING = "*";//數組
private String FUNCTION_DAOLE = "$";//字符 Bulk Strings 用於表示長度最大爲512MB的單個二進制安全字符串。
private String FUNCTION_NUM = ":";//數字
private String FUNCTION_ERROR = "-";//錯誤回覆
private String FUNCTION_STATUS = "+";//狀態回覆
private String API_SET = "SET";
private String API_GET = "GET";
private String API_INCRBY = "INCRBY";//
public static void main(String[] args) throws IOException {
JedisDemo jedis = new JedisDemo();
jedis.inintJedis();
// jedis.setString("mads", "googboy");
// jedis.getString("mads");
jedis.incrby("madsnum","1");
}
public void inintJedis() {
try {
jedis = new Socket("localhost", 6379);
} catch (IOException e) {
e.printStackTrace();
}
}
/*******
* 模擬 Redis 的GET 命令
* @param key
*/
public void incrby(String key,String inc) throws IOException {
//這裏使用了 阿里開發規範裏的提到的 try-with-resource的流處理方式,優雅的關閉流
try (OutputStream out = jedis.getOutputStream()) {
sendCommand(out, API_INCRBY, key.getBytes(),inc.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
/*******
* 模擬 Redis 的GET 命令
* @param key
*/
public void getString(String key) throws IOException {
//這裏使用了 阿里開發規範裏的提到的 try-with-resource的流處理方式,優雅的關閉流
try (OutputStream out = jedis.getOutputStream()) {
sendCommand(out, API_GET, key.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
/*******
* 模擬 Redis 的SET 命令
* @param key
* @param value
*/
public void setString(String key, String value) throws IOException {
//這裏使用了 阿里開發規範裏的提到的 try-with-resource的流處理方式,優雅的關閉流
try (OutputStream out = jedis.getOutputStream()) {
sendCommand(out, API_SET, key.getBytes(), value.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
/******
* 轉義成resp 協議 並執行
* @param out
* @param command redis 的api set,hget... ,這裏可以優化成枚舉
* @param args
* @throws IOException
*/
private void sendCommand(OutputStream out, String command, byte[]... args) throws IOException {
//比如 key ==mads value== shuaige
//在確定的字符串個數的情況下傳入大小。避免了後續的擴容操作,提升效率
StringBuilder ss = new StringBuilder(20);
// *3
ss.append(FUNCTION_XING).append(args.length + 1).append(CTRL);
//$3
ss.append(FUNCTION_DAOLE).append(command.length()).append(CTRL);
//SET
ss.append(command).append(CTRL);
//$4mads$7shuaige
for (byte[] arg : args) {
ss.append(FUNCTION_DAOLE).append(arg.length).append(CTRL);
ss.append(new String(arg)).append(CTRL);
}
System.out.println("自己客戶端拼裝成的RESP協議串->:" + ss);
out.write(ss.toString().getBytes());
out.flush();
try (InputStream inputStream = jedis.getInputStream()) {
byte[] buff = new byte[1024];
int len = inputStream.read(buff);
if (len > 0) {
String msg = new String(buff, 0, len);
System.out.println("自己客戶端收到Redis服務端返回的數據:" + msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}