自己動手寫一個能操作redis的客戶端
引言
redis大家在專案中經常會使用到。官網也提供了多語言的客戶端供大家操作redis,如下圖所示
但是,大家有思考過,這些語言操作redis背後的原理麼?其實,某些大神會說
只要按照redis的協議,傳送指定資料給redis,監聽返回值即可。
確實,本質原理就是如上面那句話所說。博主也是以這種思路,去看了一下JAVA端的開源元件jedis的原始碼,然後取其精華,寫了一個段能操作redis的demo,希望大家能有所收穫。 jedis的github地址為: https://github.com/xetorthio/jedis 有興趣的童鞋,也可以自行去閱讀。需要說明的是,這畢竟不是原始碼分析系列文章
正文
首先,我先說一下操作思路,如下圖所示
說明一下,上面的第四步,就是我們自己要寫的操作redis的小demo。
1、先寫一個socket監聽6379埠
這個程式很easy,度娘一下出來一大把
import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.net.ServerSocket; import java.net.Socket; public class SocketServer { public static void main(String[] args) throws IOException { ServerSocket server = new ServerSocket(6379); Socket socket = server.accept(); byte[] chars = new byte[64]; socket.getInputStream().read(chars); System.out.println(new String(chars)); } }
2、採用開源客戶端,操作一次redis
我這裡用的是JAVA語言的jedis,大家自己也可以用其他的任意語言元件,目的是為了採集客戶端在操作redis時,傳送出的資料
import redis.clients.jedis.Jedis;
public class RedisTest {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.set("eat", "I want to eat");
}
}
3、看看socket監聽到的資料
在這裡執行一下第二步的程式碼,檢視第一步的程式碼輸出的資料,如下所示
*3
$3
SET
$3
eat
$13
I want to eat
那麼,這組資料是什麼含義呢? 我們去官網進行查詢。原來,redis的客戶端和服務端採取了一種RESP協議。相應文件地址如下 https://redis.io/topics/protocol RESP設計巧妙,它的前景在於下面三個方面:
Simple to implement. Fast to parse. Human readable.
那麼+、-、*、:、$這些符號是什麼意思呢? 官網有這麼一段話
In RESP, the type of some data depends on the first byte: For Simple Strings the first byte of the reply is "+" For Errors the first byte of the reply is "-" For Integers the first byte of the reply is ":" For Bulk Strings the first byte of the reply is "$" For Arrays the first byte of the reply is "*" Additionally RESP is able to represent a Null value using a special variation of Bulk Strings or Array as specified later. In RESP different parts of the protocol are always terminated with "\r\n" (CRLF).
翻譯過來 (1)簡單字串Simple Strings, 以 "+"加號 開頭 (2)錯誤Errors, 以"-"減號 開頭 (3)整數型Integer, 以 ":" 冒號開頭 (4)大字串型別Bulk Strings, 以 "$"美元符號開頭,長度限制512M (5)組型別Arrays,以 "*"星號開頭 並且,協議的每部分都是以 "\r\n" (CRLF) 結尾的。
OK,那我們剛才的那一串的資料的意思就是(沒有看到""\r\n",是因為已經轉義了,所以無法看到):
*3 陣列包含3個元素,分別是SET、eat、I want to eat
$3 是一個字串,且字串長度為3
SET 字串的內容
$3 是一個字串,且字串長度為3
eat 字串的內容
$13 是一個字串,且字串長度為13
I want to eat 字串的內容
提問,如果是get命令,那麼傳輸的RESP的內容長什麼樣? 比如有一個命令get eat,那麼此時的內容如下所示
*2
$3
GET
$3
eat
沒有\r\n是因為已經轉義了,所以沒看到。其他的命令,可以自行測試。
4、嘗試構造一樣的資料操作redis
OK,經過上面的鋪墊。我們如果要對redis做一個set操作,則構造set命令的RESP協議內容,並且利用socket程式設計,將這串內容傳送給redis即可。這裡用java的socket程式設計實現,用其他語言也是一樣的。 我們有一個類 RedisClient.java 程式碼如下
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class RedisClient {
private Socket socket;
private OutputStream outputStream;
private InputStream inputStream;
public RedisClient(String host, int port){
try {
this.socket = new Socket(host,port);
this.outputStream = this.socket.getOutputStream();
this.inputStream = this.socket.getInputStream();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public String set(final String key, String value) {
StringBuilder sb = new StringBuilder();
//雖然輸出的時候,會被轉義,然而我們傳送的時候還是要帶上\r\n
sb.append("*3").append("\r\n");
sb.append("$3").append("\r\n");
sb.append("SET").append("\r\n");
sb.append("$").append(key.length()).append("\r\n");
sb.append(key).append("\r\n");
sb.append("$").append(value.length()).append("\r\n");
sb.append(value).append("\r\n");
byte[] bytes= new byte[1024];
try {
outputStream.write(sb.toString().getBytes());
inputStream.read(bytes);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return new String(bytes);
}
public static void main(String[] args) {
RedisClient redisClient = new RedisClient("127.0.0.1", 6379);
String result = redisClient.set("eat", "please eat");
System.out.println(result);
}
}
上面的public String set(final String key, String value)方法中,顯示了,我們假如需要對redis進行set操作,需要傳輸的RESP協議的內容。記住,一定要帶\r\n字元作為結尾 OK,執行上述程式碼,你會發現你可以往redis中set資料了,並且控制檯輸出如下
+OK
提問,你自己會封裝get命令麼?
總結
本文以一種循序漸進的方式帶領大家寫了一個能操作redis的demo,希望大家有所收穫。