【原創】自己動手寫一個能操作redis的客戶端


引言

redis大家在項目中經常會使用到。官網也提供了多語言的客戶端供大家操作redis,如下圖所示
image
但是,大家有思考過,這些語言操作redis背后的原理么?其實,某些大神會說

只要按照redis的協議,發送指定數據給redis,監聽返回值即可。

確實,本質原理就是如上面那句話所說。博主也是以這種思路,去看了一下JAVA端的開源組件jedis的源碼,然后取其精華,寫了一個段能操作redis的demo,希望大家能有所收獲。jedis的github地址為:https://github.com/xetorthio/jedis。
有興趣的童鞋,也可以自行去閱讀。需要說明的是,這畢竟不是源碼分析系列文章,不是帶你去看jedis的源碼。只是借鑒思路,寫一個能操作redis的程序。

正文

首先,我先說一下操作思路,如下圖所示
image
說明一下,上面的第四步,就是我們自己要寫的操作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,希望大家有所收獲。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM