第八篇 - 手寫Redis(Java實現)

在這裏插入圖片描述
在這裏插入圖片描述
Github源碼下載:https://github.com/chenxingxing6/sourcecode/tree/master/code-redis


一、前言

Redis是一個開源的使用ANSI C語言編寫、遵守BSD協議、支持網絡、可基於內存亦可持久化的日誌型、key-value數據庫。它通常被稱爲數據結構服務器,因爲值(value)可以是字符串(String),哈希(Hash),列表(List),集合(Set)和有序集合(sorted Set)等類型。

1.1 實現內容

1.基本指令操作
2.多用戶併發
3.數據持久化

1.2 實現思路

1.將字節流依照Redis的協議反列化爲Java能夠識別的對象,識別指令。
2.然後通過反射機制,找到對於處理類進行處理。
3.最後將結果依照協議序列化爲字節流寫到客戶端。

在這裏插入圖片描述

Redis的RESP通信協議描述(二進制)

RESP實際上是一個支持以下數據類型的序列化協議:簡單字符串(Simple Strings),錯誤(Errors),整數(Integers), 塊字符串(Bulk Strings)和數組(Arrays)。

在Redis中,RESP用作 請求-響應 協議的方式如下:

1、客戶端將命令作爲批量字符串的RESP數組發送到Redis服務器。
2、服務器(Server)根據命令執行的情況返回一個具體的RESP類型作爲回覆。

在RESP協議中,有些的數據類型取決於第一個字節:

1、對於簡單字符串,回覆的第一個字節是“+”
2、對於錯誤,回覆的第一個字節是“ - ”
3、對於整數,回覆的第一個字節是“:”
4、對於批量字符串,回覆的第一個字節是“$”
5、對於數組,回覆的第一個字節是“*”

Redis在TCP端口6379上監聽到來的連接(本質就是socket),客戶端連接到來時,Redis服務器爲此創建一個TCP連接。在客戶端與服務器端之間傳輸的每個Redis命令或者數據都以\r\n結尾。
在這裏插入圖片描述


二、Redis客戶端測試

軟件自己去下載:

1.下載wget http://download.redis.io/redis-stable.tar.gz
2.解壓,編譯 cd redis-stable; make;
3.cd src/ 可以看到新生成redis-cli,redis-server
4.啓動服務 redis-cli
在這裏插入圖片描述


1.檢測 redis 服務是否啓動
avatar

2.字符串(String)
avatar

3.哈希(Hash)
avatar

4.列表(List)
avatar

5.集合(Set)
avatar

持久化的文件:
avatar


2.1 執行日誌
{key={field=update}}從文件中讀數據
數據加載完成....
執行命令:PINGCommand
執行命令:SETCommand
執行命令:GETCommand
執行命令:HSETCommand
向文件中寫入數據{mykey={field=value}, key={field=update}}
執行命令:HGETCommand
執行命令:HGETCommand
執行命令:HGETCommand
執行命令:LPUSHCommand
向文件中寫入數據{lxh=[value], key=[]}
執行命令:LPUSHCommand
向文件中寫入數據{lxh=[value1, value], key=[]}
執行命令:LPUSHCommand
向文件中寫入數據{lxh=[value2, value1, value], key=[]}
執行命令:LPOPCommand
向文件中寫入數據{lxh=[value1, value], key=[]}
執行命令:LPOPCommand
向文件中寫入數據{lxh=[value], key=[]}
執行命令:LPOPCommand
向文件中寫入數據{lxh=[], key=[]}
執行命令:SADDCommand
執行命令:SADDCommand
執行命令:SCARDCommand
執行命令:SADDCommand
執行命令:SCARDCommand

2.2 Jedis測試
import redis.clients.jedis.Jedis;
/**
 * @Author: cxx
 * @Date: 2019/11/3 23:28
 */
public class com.demo.JedisTest {
    public static void main(String[] args) {
        Jedis jedis=new Jedis("localhost", 6379);
        String result = jedis.ping();
        System.out.println(result);
        //關閉jedis
        jedis.close();
    }
}

開啓redis服務

{lxh=[], key=[]}從文件中讀數據
{mykey={field=value}, key={field=update}}從文件中讀數據
數據加載完成....

avatar


三、核心代碼

3.1 MyRedisService

package com.demo;

import com.demo.command.ICommand;
import com.demo.data.PermanentData;
import com.demo.procotol.MyDecode;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @Author: cxx
 * @Date: 2019/11/3 19:48
 */
public class MyRedisService {
    public static void main(String[] args) {
        new MyRedisService().start(6379);
    }
    public void start(int port){
        // 1.加載持久化數據
        loadData();
        ExecutorService executor = Executors.newFixedThreadPool(20000);
        ServerSocket serverSocket = null;
        try {
            // 循環接受客戶端連接
            serverSocket = new ServerSocket(port);
            System.out.println("等待客戶端連接....");
            while (true){
                final Socket socket = serverSocket.accept();
                executor.execute(() -> {
                    try {
                        // 持續提供業務服務
                        while (true){
                            InputStream is = socket.getInputStream();
                            OutputStream os = socket.getOutputStream();
                            // 解析命令
                            ICommand command = new MyDecode(is, os).getCommand();
                            if (command != null){
                                command.run(os);
                            }
                            TimeUnit.MILLISECONDS.sleep(10);
                        }
                    }catch (Exception e){
                        try {
                            socket.close();
                        }catch (Exception e1){
                        }
                    }
                });
            }
        }catch (Exception e){
            try {
                serverSocket.close();
            }catch (Exception e1){
            }
        }
    }
    public void loadData(){
        PermanentData.getInstance().readFromListProfile();
        PermanentData.getInstance().readFromMapProfile();
        System.out.println("數據加載完成....");
    }
}


3.2 ICommand

package com.demo.command;

import java.io.OutputStream;
import java.util.List;

/**
 * @Author: cxx
 * @Date: 2019/11/3 19:49
 */
public interface ICommand {
    public void run(OutputStream out);
    public void setArgs(List<Object> args);
}

實現類:exit,get,hget,hset,info,lpop,lpush,lrange,ping,sadd,scard,select,set


3.3 MyDecode解析命令

package com.demo.procotol;

import com.demo.command.ICommand;
import com.demo.exception.RedisException;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author: cxx
 * @Date: 2019/11/3 20:05
 */
public class MyDecode {
    private InputStream is;
    private OutputStream os;
    private ProtocolInputStream pis;

    public MyDecode(InputStream is, OutputStream os) {
        this.is = is;
        this.os = os;
        pis = new ProtocolInputStream(is);
    }

    public ICommand getCommand(){
        try {
            Object o = process();
            // 客戶端將命令作爲批量字符串的RESP數組發送到Redis服務器,如果解析出不是list就有問題了
            if (!(o instanceof List)){
                Protocolcode.writeBulkString(os, "Server too tired,please wait .....");
                throw new RedisException("內部解析錯誤,服務器故障");
            }

            List<Object> list = (List<Object>) o;
            if (list.size() < 1) {
                Protocolcode.writeBulkString(os, "Server too tired,please wait .....");
                throw new RedisException("內部解析錯誤,服務器故障");
            }
            Object o2 = list.remove(0);
            if (!(o2 instanceof byte[])) {
                Protocolcode.writeBulkString(os, "Server too tired,please wait .....");
                throw new RedisException("內部解析錯誤,服務器故障");
            }
            String commandName = String.format("%sCommand", new String((byte[]) (o2)).trim().toUpperCase());
            System.out.println("執行命令:" + commandName);

            Class<?> cls = null;
            ICommand command = null;
            cls = Class.forName("com.demo.command." + commandName);
            if (cls == null || !ICommand.class.isAssignableFrom(cls)){
                Protocolcode.writeError(os, "Wrong Input,Please try again");
            }else {
                command = (ICommand) cls.newInstance();
            }
            command.setArgs(list);
            return command;
        }catch (Exception e){
            try {
                Protocolcode.writeError(os, "Wrong Input,Please try again");
            }catch (Exception e1){
            }
        }
        return null;
    }

    public String processError() throws IOException {
        return pis.readLine();
    }

    public String processSimpleString() throws IOException {
        return pis.readLine();
    }

    public long processInteger() throws IOException {
        return pis.readInteger();
    }

    //'$6\r\nfoobar\r\n' 或者 '$-1\r\n'
    public byte[] processBulkString() {
        int len = 0;
        byte[] bytes;
        String str = null;
        try {
            len = (int) pis.readInteger();
            bytes = new byte[len];
            str = pis.readLine();

        } catch (IOException e) {
            e.printStackTrace();
        }
        return str.getBytes();
    }

    public List<byte[]> processArray() {
        int len = 0;
        List<byte[]> list = new ArrayList<byte[]>();
        try {
            len = (int) pis.readInteger();
            //"*5\r\n
            // 5\r\nlpush\r\n$3\r\nkey\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n";
            for (int i = 0; i < len; i++) {
                byte[] bytes = (byte[]) process();
                list.add(bytes);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return list;
    }

    public Object process() throws IOException {
        int b = 0;
        try {
            // 從輸入流中讀取數據的下一個字節,沒可用字節返回-1
            b = is.read();
        } catch (IOException e) {
        }
        if (b == -1) {
            throw new RuntimeException("程序錯誤..........");
        }

        // 數據類型取決於第一個字節,redis resp通信協議規定
        switch ((char) b) {
            case '+':
                return processSimpleString();
            case '-':
                return processError();
            case ':':
                return processInteger();
            case '$':
                return processBulkString();
            case '*':
                return processArray();
            default:
                Protocolcode.writeError(os, "Unresolve this commond");
        }
        return null;
    }
}


3.4 Protocolcode

package com.demo.procotol;

import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

/**
 * @Author: cxx
 * @Date: 2019/11/3 20:21
 */
public class Protocolcode {
    public static void writeInteger(OutputStream write, long length) throws Exception {
        write.write(':');
        write.write(String.valueOf(length).getBytes());
        write.write("\r\n".getBytes());
        write.flush();

    }

    //'+OK\r\n'
    public static void writeString(OutputStream write, String str) throws Exception {
        write.write('+');
        write.write(str.getBytes());
        write.write("\r\n".getBytes());
        write.flush();
    }

    //字節流'$6\r\nfoobar\r\n' 或者 '$-1\r\n'
    public static void writeBulkString(OutputStream write, String str) throws Exception {
        byte[] b = new byte[0];
        if (str != null){
            b = str.getBytes();
        }
        write.write('$');
        write.write(String.valueOf(b.length).getBytes());
        write.write("\r\n".getBytes());
        write.write(b);
        write.write("\r\n".getBytes());
        write.flush();
    }

    //'2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n'
    public static void writeArray(OutputStream write, List<?> list) throws Exception {
        write.write('*');
        write.write(String.valueOf(list.size()).getBytes());
        write.write("\r\n".getBytes());
        for (Object o : list) {
            if ((o instanceof String)) {
                writeBulkString(write, (String) o);
            } else if (o instanceof Integer) {
                writeInteger(write, (long) o);
            } else if (o instanceof Long) {
                writeInteger(write, (Long) o);
            } else if (o instanceof List<?>) {
                writeArray(write, (List<?>) o);
            }
        }
    }

    //'-WRONGTYPE Operation against a key holding the wrong kind of value'
    public static void writeError(OutputStream write, String message) throws IOException {
        write.write('-');
        write.write(message.getBytes());
        write.write("\r\n".getBytes());
        write.flush();
        write.close();
    }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章