溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

自己動(dòng)手寫(xiě)一個(gè)能操作redis的客戶(hù)端

發(fā)布時(shí)間:2020-08-12 17:44:05 來(lái)源:ITPUB博客 閱讀:180 作者:HitTwice 欄目:軟件技術(shù)

作者:孤獨(dú)煙 來(lái)源:微信訂閱號(hào)(程序員孤獨(dú)煙)

原文鏈接: https://mp.weixin.qq.com/s/IBynkex-FHhvJ3tmizvJhA


引言

redis大家在項(xiàng)目中經(jīng)常會(huì)使用到。官網(wǎng)也提供了多語(yǔ)言的客戶(hù)端供大家操作redis,如下圖所示


自己動(dòng)手寫(xiě)一個(gè)能操作redis的客戶(hù)端

但是,大家有思考過(guò),這些語(yǔ)言操作redis背后的原理么?其實(shí),某些大神會(huì)說(shuō)


只要按照redis的協(xié)議,發(fā)送指定數(shù)據(jù)給redis,監(jiān)聽(tīng)返回值即可。


確實(shí),本質(zhì)原理就是如上面那句話所說(shuō)。博主也是以這種思路,去看了一下JAVA端的開(kāi)源組件jedis的源碼,然后取其精華,寫(xiě)了一個(gè)段能操作redis的demo,希望大家能有所收獲。


jedis的github地址為:
https://github.com/xetorthio/jedis


有興趣的童鞋,也可以自行去閱讀。需要說(shuō)明的是,這畢竟不是源碼分析系列文章,不是帶你去看jedis的源碼。只是借鑒思路,寫(xiě)一個(gè)能操作redis的程序。

正文

首先,我先說(shuō)一下操作思路,如下圖所示


自己動(dòng)手寫(xiě)一個(gè)能操作redis的客戶(hù)端

說(shuō)明一下,上面的第四步,就是我們自己要寫(xiě)的操作redis的小demo。

1、先寫(xiě)一個(gè)socket監(jiān)聽(tīng)6379端口

這個(gè)程序很easy,度娘一下出來(lái)一大把

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、采用開(kāi)源客戶(hù)端,操作一次redis

我這里用的是JAVA語(yǔ)言的jedis,大家自己也可以用其他的任意語(yǔ)言組件,目的是為了采集客戶(hù)端在操作redis時(shí),發(fā)送出的數(shù)據(jù)

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監(jiān)聽(tīng)到的數(shù)據(jù)

在這里運(yùn)行一下第二步的代碼,查看第一步的代碼輸出的數(shù)據(jù),如下所示

*3
$3
SET
$3
eat
$13
I want to eat

那么,這組數(shù)據(jù)是什么含義呢?
我們?nèi)ス倬W(wǎng)進(jìn)行查詢(xún)。原來(lái),redis的客戶(hù)端和服務(wù)端采取了一種RESP協(xié)議。相應(yīng)文檔地址如下
https://redis.io/topics/protocol
RESP設(shè)計(jì)巧妙,它的前景在于下面三個(gè)方面:

  • Simple to implement.

  • Fast to parse.

  • Human readable.

那么+、-、*、:、$這些符號(hào)是什么意思呢?
官網(wǎng)有這么一段話

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).

翻譯過(guò)來(lái)
(1)簡(jiǎn)單字符串Simple Strings, 以 "+"加號(hào) 開(kāi)頭
(2)錯(cuò)誤Errors, 以"-"減號(hào) 開(kāi)頭
(3)整數(shù)型Integer, 以 ":" 冒號(hào)開(kāi)頭
(4)大字符串類(lèi)型Bulk Strings, 以 "$"美元符號(hào)開(kāi)頭,長(zhǎng)度限制512M
(5)組類(lèi)型Arrays,以 "*"星號(hào)開(kāi)頭
并且,協(xié)議的每部分都是以 "\r\n" (CRLF) 結(jié)尾的。

OK,那我們剛才的那一串的數(shù)據(jù)的意思就是(沒(méi)有看到""\r\n",是因?yàn)橐呀?jīng)轉(zhuǎn)義了,所以無(wú)法看到):

*3   數(shù)組包含3個(gè)元素,分別是SET、eat、I want to eat
$3   是一個(gè)字符串,且字符串長(zhǎng)度為3
SET  字符串的內(nèi)容
$3   是一個(gè)字符串,且字符串長(zhǎng)度為3
eat  字符串的內(nèi)容
$13  是一個(gè)字符串,且字符串長(zhǎng)度為13
I want to eat 字符串的內(nèi)容

提問(wèn),如果是get命令,那么傳輸?shù)腞ESP的內(nèi)容長(zhǎng)什么樣?
比如有一個(gè)命令get eat,那么此時(shí)的內(nèi)容如下所示

*2
$3
GET
$3
eat

沒(méi)有\(zhòng)r\n是因?yàn)橐呀?jīng)轉(zhuǎn)義了,所以沒(méi)看到。其他的命令,可以自行測(cè)試。

4、嘗試構(gòu)造一樣的數(shù)據(jù)操作redis

OK,經(jīng)過(guò)上面的鋪墊。我們?nèi)绻獙?duì)redis做一個(gè)set操作,則構(gòu)造set命令的RESP協(xié)議內(nèi)容,并且利用socket編程,將這串內(nèi)容發(fā)送給redis即可。這里用java的socket編程實(shí)現(xiàn),用其他語(yǔ)言也是一樣的。
我們有一個(gè)類(lèi) 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();
        //雖然輸出的時(shí)候,會(huì)被轉(zhuǎn)義,然而我們傳送的時(shí)候還是要帶上\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)方法中,顯示了,我們假如需要對(duì)redis進(jìn)行set操作,需要傳輸?shù)腞ESP協(xié)議的內(nèi)容。記住,一定要帶\r\n字符作為結(jié)尾
OK,運(yùn)行上述代碼,你會(huì)發(fā)現(xiàn)你可以往redis中set數(shù)據(jù)了,并且控制臺(tái)輸出如下

+OK

提問(wèn),你自己會(huì)封裝get命令么?

總結(jié)

本文以一種循序漸進(jìn)的方式帶領(lǐng)大家寫(xiě)了一個(gè)能操作redis的demo,希望大家有所收獲。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI