一个仿照Redis开发的一个Mini-Redis数据库
1.什么是Redis?
Redis是一个开源的底层使用C语言编写的key-value存储数据库。可用于缓存、事件发布订阅、高速队列等场景。而且支持丰富的数据类型:string(字符串)、hash(哈希)、list(列表)、set(无序集合)、zset(sorted set:有序集合)
这里不做过多的解释,需要了解Reids的请自行查看一下连接
Redis内容详解.
2.项目前需要了解的知识
(1)Socket编程
import java.net.ServerSocket;
import java.net.Socket;
try (ServerSocket serverSocket = new ServerSocket(9989)) {
try (Socket socket = serverSocket.accept()) {
socket.getInputStream(); // InputStream 输入字节流
socket.getOutputStream(); // OutputStream 输出字节流
}
}
(2) 多线程编程中的固定线程池
import java.util.concurrent.Executors;
ExecutorService pool = Executors.newFixedThreadPool(6);
service.execute(new Runnable() {
@Override
public void run() {
}
});
pool.shutdown();
这里注意:
Redis本身是单线程的,这里是对Redis的功能进行了一定的扩充。
(3)Redis协议详细介绍
因为Redis有自己的数据类型和协议解析规则,所以在开发中,协议解析也成了最重要的一块内容,因此掌握Redis的数据类型非常重要。
官网详细介绍:Redis Protocol specification.
协议支持的数据类型
个人觉得简洁明了又清晰的Redis数据类型介绍:Redis数据类型.
有了这些知识储备就可以开始正式开发啦~
Mini-Redis
1.类设计图
2.代码
(1)协议处理
/**
* Created with IntelliJ IDEA
*
* @Description: 协议转换
* @Author: zhen
* @Date: 2019/8/2
* @Time: 11:30
*/
public class Protocol {
//将process封装
public static Object read(InputStream is) throws IOException, RemoteException {
return Process(is);
}
public static String readLine(InputStream is) throws IOException {
StringBuilder sb = new StringBuilder();
int b = -1;
while(true){
boolean needReed = true;
if(needReed == true) {
b = is.read();
if (b == -1) {
throw new RuntimeException("不应该读到的结尾");
}
}else{
needReed = true;
}
if(b == '\r'){
int c = is.read();
if(c == -1){
throw new RuntimeException("错误数据");
}
if(c == '\r'){
sb.append(b);
b = c;
needReed = false;
}
if(c == '\n'){
break;
}
sb.append((char)b);
sb.append((char)c);
}else{
sb.append((char)b);
}
}
return sb.toString();
}
public static long readInt(InputStream is) throws IOException {
boolean isNegative = false;
StringBuilder sb = new StringBuilder();
int b = is.read();
if(b == -1) {
throw new RuntimeException("不应该读到的结尾");
}
if(b == '-'){
isNegative = true;
}else{
sb.append((char)b);
}
while(true){
b = is.read();
if(b == -1){
throw new RuntimeException("错误数据");
}
if(b == '\r'){
int c = is.read();
if(c == '\n'){
break;
}
throw new RuntimeException("错误数据");
}else{
sb.append((char)b);
}
}
long v = Long.parseLong(sb.toString());
if(isNegative == true){
v = -v;
}
return v;
}
//读命令
public static Command readCommand(InputStream is) throws Exception, Exception {
Object o =read(is);
if(!(o instanceof List)){
throw new Exception("命令必须是Array类型");
}
List<Object> list = (List<Object>)o;
if(list.size() <= 1){
throw new Exception("命令元素个数必须大于1");
}
//获取命令名称
Object o2 =list.remove(0);
if(!(o2 instanceof byte[])){
throw new Exception("错误的命令类型");
}
byte[] array = (byte[])o2;
//将命令名称用String接受
String commandName = new String(array);
//根据类名得到类对象
String className = String.format("com.Commands.%sCommand",commandName.toUpperCase());
Class<?> cls = Class.forName(className);
//如果cls不属于command接口抛出异常
if(!Command.class.isAssignableFrom(cls)){
throw new Exception("错误的命令");
}
Command command = ((Command)cls.newInstance());
command.setArgs(list);
return command;
}
private static String processSimpleString(InputStream is) throws IOException {
// +OK\r\n
return readLine(is);
}
public static String processError(InputStream is) throws IOException {
// -ERROR\r\n
return readLine(is);
}
private static long processInteger(InputStream is) throws IOException {
//100\r\n
//-100r\n
return readInt(is);
}
public static byte[] processBulkString(InputStream is) throws IOException {
//'$6\r\nfoobar\r\n'
int len = (int) readInt(is);
if(len == -1){
// -1\r\n = null
return null;
}
byte[] by = new byte[len];
is.read(by,0,len);
is.read();
is.read();
return by;
}
private static List<Object> processArray(InputStream is ) throws IOException {
int len = (int)readInt(is);
if (len == -1) {
// "*-1\r\n" ==> null
return null;
}
List<Object> list = new ArrayList<>(len);
for (int i = 0; i < len; i++) {
try {
list.add(Process(is));
} catch (RemoteException e) {
list.add(e);
}
}
return list;
}
//public static String ReadCommand(ProtocolInputStream is){
//
//}
private static Object Process(InputStream is) throws IOException, RemoteException {
int a = is.read();
switch (a){
case '+':
return processSimpleString(is);
case '-':
throw new RemoteException(processError(is));
case':':
return processInteger(is);
case '$':
return processBulkString(is);
case '*':
return processArray(is);
default: throw new RuntimeException("出错");
}
}
public static void writeError(OutputStream os, String message) throws IOException {
//-message\r\n
os.write('-');
os.write(message.getBytes("GBK")); //字符转成字节流
os.write("\r\n".getBytes("GBK"));
}
public static void writeInteger(OutputStream os, long v) throws IOException {
//:v\r\n
os.write(':');
os.write(String.valueOf(v).getBytes("GBK"));
os.write("\r\n".getBytes("GBK"));
}
}
(2)命令相关(一个全局的Command接口,各种继承了Command接口的处理各种请求的类)
接口:
/**
* Created with IntelliJ IDEA
*
* @Description:Command接口
* @Author: zhen
* @Date: 2019/8/17
* @Time: 10:44
*/
public interface Command {
//处理参数
void setArgs(List<Object> args);
void run(OutputStream os) throws IOException;
}
Command类
(LPSHCommand)
/**
* Created with IntelliJ IDEA
*
* @Description:LPUSHCommand
* @Author: zhen
* @Date: 2019/8/17
* @Time: 10:44
*/
public class LPUSHCommand implements Command {
private static final Logger logger = LoggerFactory.getLogger(LPUSHCommand.class);
private List<Object> args;
@Override
public void setArgs(List<Object> args) {
this.args = args;
}
@Override
public void run(OutputStream os) throws IOException {
if (args.size() != 2) {
Protocol.writeError(os, "命令至少需要两个参数");
return;
}
String key = new String((byte[])args.get(0));
String value = new String((byte[])args.get(1));
logger.debug("运行的是 lpush 命令: {} {}", key, value);
// 这种方式不是一个很好的线程同步的方式
List<String> list = Database.getList(key);
list.add(0, value);
logger.debug("插入后数据共有 {} 个", list.size());
Protocol.writeInteger(os, list.size());
}
}
(LRANGECommand)
/**
* Created with IntelliJ IDEA
*
* @Description:LRANGECommand
* @Author: zhen
* @Date: 2019/8/17
* @Time: 10:44
*/
public class LRANGECommand implements Command {
private List<Object> args;
@Override
public void setArgs(List<Object> args) {
this.args = args;
}
@Override
public void run(OutputStream os) throws IOException {
String key = new String((byte[])args.get(0));
int start = Integer.parseInt(new String((byte[])args.get(1)));
int end = Integer.parseInt(new String((byte[])args.get(2)));
List<String> list = Database.getList(key);
if (end < 0) {
end = list.size() + end;
}
List<String> result = list.subList(start, end + 1);
try {
Protocol.writeArray(os, result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
(3)数据存储(Database)
import java.util.*;
/**
* Created with IntelliJ IDEA
*
* @Description: 存储所有数据
* @Author: zhen
* @Date: 2019/8/16
* @Time: 23:14
*/
public class Database {
/*
选用单例模式所有数据都存储在该类中
*/
private static Map<String, List<String>> lists = new HashMap<>();
public static List<String> getList(String key){
/*lamada表达式
List<String> list = lists.computeIfAbsent(key,k ->{
return new ArrayList<>();
});
*/
List<String> list = lists.get(key);
if(list == null){
list = new ArrayList<>();
lists.put(key,list);
}
return list;
}
}
(4)异常处理
因为Redis中存放在Array中的异常是需要打印出来而不需要报错的,所以需要将这种异常拿出来进行特殊处理。
/**
* Created with IntelliJ IDEA
*
* @Description: 处理list中的异常
* @Author: zhen
* @Date: 2019/8/8
* @Time: 21:39
*/
public class RemoteException extends Exception {
public RemoteException() {
}
public RemoteException(String s) {
super(s);
}
public RemoteException(String s, Throwable throwable) {
super(s, throwable);
}
public RemoteException(Throwable throwable) {
super(throwable);
}
public RemoteException(String s, Throwable throwable, boolean b, boolean b1) {
super(s, throwable, b, b1);
}
}