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 服務是否啓動
2.字符串(String)
3.哈希(Hash)
4.列表(List)
5.集合(Set)
持久化的文件:
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}}從文件中讀數據
數據加載完成....
三、核心代碼
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();
}
}