30分钟手写Jedis

请相信我,你一定会更优秀!

一张图带你理清思路:

30分钟手写Jedis_第1张图片

请注意,所有CS架构的服务都离不开这三层。通俗点讲就是,CS之间约定一个协议,只有符合此协议的数据信息S才会处理,同样,S会遵循此协议回复给C。传输层即连接层,我们使用 socket连接redis-server即可;API层就是供C使用的操作命令;协议层即CS之间的协议,我们必须知道它们之间的协议。


目录

第一步,解密Jedis Protocol

第二步,手写Jedis传输层&API层

第三步,手写Jedis协议层

第四步,演示

第一步,解密Jedis Protocol

  1. 我们自己模拟 redis-server;
  2. 使用官方Jedis发送请求,抓取报文信息;
  3. 分析报文信息,解密 protocol;

 按照此思路,逐一实现:

1. 我们自己模拟 redis-server;

package com.haolin.java.demos.jedis;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;

public class MockRedisServer {

	@SuppressWarnings("resource")
	public static void main(String[] args) throws IOException {
		
		// 模拟redis-server服务
		ServerSocket serverSocket = new ServerSocket(6399);
		InputStream inputStream = serverSocket.accept().getInputStream();
		byte[] bs = new byte[1024];
		inputStream.read(bs);
		// 打印请求信息
		System.out.println(new String(bs, "UTF-8"));
		
	}
}

2. 使用官方Jedis发送请求,抓取报文信息;

@Test
public void testProtocol() {

	// 使用刚刚开启的模拟服务
	Jedis jedis = new Jedis("redis://localhost:6399");
	jedis.set("iname", "zhanghaolin");
	// jedis.get("iname");

}

3. 分析报文信息,解密 protocol;

// 控制台输出
// set("iname", "zhanghaolin") 
*3
$3
SET
$5
iname
$11
zhanghaolin
// 控制台输出
// get("iname")
*2
$3
GET
$5
iname

我们执行 set,get发送的这些报文信息里边,$, * 这些都代表什么呢?官网摘抄如下 redis官方文档地址

30分钟手写Jedis_第2张图片

结合官网帮助,不难分析出报文信息:

# set("iname", "zhanghaolin") 
*3        # *代表下边$行个数
$3        # $表示下边一行长度
SET       # 命令
$5
iname
$11
zhanghaolin

第二步,手写Jedis传输层&API层

package com.haolin.hedis;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;

import com.haolin.hedis.Protocol.Command;

/**
 * 
 * @Description: 传输层/API层
 * @author zhanghaolin
 * @date 2019年5月8日 上午9:40:47
 */
public class Hedis {

	private final String  DEFAULT_HOST 	= "127.0.0.1";
	private final Integer DEFAULT_PORT 	= 6379;
	
	private Socket 						connection;

	private String 						host;

	private Integer 					port;
	
	private InputStream 				inputStream;
	
	private OutputStream 				outputStream;

	public Hedis() {
		this.host = DEFAULT_HOST;
		this.port = DEFAULT_PORT;

		connect();
	}
	
	public Hedis(String host, Integer port) {
		this.host = host;
		this.port = port;

		connect();
	}

	public void set(String key, String value) {
		Protocol.sendMessage(outputStream, Command.SET, SafeEncoder.castValue(key), SafeEncoder.castValue(value));
	}
	
	public String get(String key) {
		Protocol.sendMessage(outputStream, Command.GET, SafeEncoder.castValue(key));
		String replyMessage = Protocol.getReplyMessage(inputStream);
		String temp1 = replyMessage.substring(replyMessage.indexOf(Protocol.LINEFLAG), replyMessage.lastIndexOf(Protocol.LINEFLAG));
		String temp2 = temp1.substring(replyMessage.indexOf(Protocol.LINEFLAG));
		String temp3 = temp2.substring(replyMessage.indexOf(Protocol.LINEFLAG)).replace(Protocol.LINEFLAG, Protocol.EMPTY);
		return temp3;
	}
	
	public String incr(String key) {
		Protocol.sendMessage(outputStream, Command.INCR, SafeEncoder.castValue(key));
		String replyMessage = Protocol.getReplyMessage(inputStream);
		return replyMessage.substring(replyMessage.indexOf(Protocol.MAOHAO), replyMessage.lastIndexOf(Protocol.LINEFLAG)).replace(Protocol.MAOHAO, Protocol.EMPTY);
	}
	
	public String decr(String key) {
		Protocol.sendMessage(outputStream, Command.DECR, SafeEncoder.castValue(key));
		String replyMessage = Protocol.getReplyMessage(inputStream);
		return replyMessage.substring(replyMessage.indexOf(Protocol.MAOHAO), replyMessage.lastIndexOf(Protocol.LINEFLAG)).replace(Protocol.MAOHAO, Protocol.EMPTY);
	}
	
	public void del(String key) {
		Protocol.sendMessage(outputStream, Command.DEL, SafeEncoder.castValue(key));
	}
	
	private void connect() {
		if (!isConnected()) {
			try {
				connection = new Socket(host, port);
				inputStream = connection.getInputStream();
				outputStream = connection.getOutputStream();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private boolean isConnected() {
		return connection != null && connection.isBound() && !connection.isClosed() && connection.isConnected()
				&& !connection.isInputShutdown() && !connection.isOutputShutdown();
	}

	public void close() {
		if (isConnected()) {
			try {
				outputStream.flush();
				connection.close();
			} catch (IOException ex) {
				ex.printStackTrace();
			} finally {
				finallyClose(connection);
			}
		}
	}

	private void finallyClose(Socket connection) {
		if (connection != null) {
			try {
				connection.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public static class SafeEncoder {
		
		public static byte[] castValue(String value) {
			if (value == null)
				value = Protocol.EMPTY;
			return value.getBytes();
		}
		
		public static String castValue(final byte[] bs) {
			try {
				return new String(bs, Protocol.CHARSET);
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
			return Protocol.EMPTY;
		}

	}
	
	// Setter Getter
	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public Integer getPort() {
		return port;
	}

	public void setPort(Integer port) {
		this.port = port;
	}

}

第三步,手写Jedis协议层

package com.haolin.hedis;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import com.haolin.hedis.Hedis.SafeEncoder;

/**
 * 
 * @Description: 协议层
 * @author zhanghaolin
 * @date 2019年5月8日 上午9:41:26
 */
public class Protocol {

	public static final String CHARSET  = "UTF-8";
	
	public static final String HEAD		= "*";
	public static final String DOLLARS 	= "$";
	public static final String MAOHAO 	= ":";
	public static final String LINEFLAG = "\r\n";
	public static final String EMPTY	= "";

	public enum Command {
		SET, GET, INCR, DECR, HSET, DEL
	}

	public static void sendMessage(OutputStream outputStream, Command commond, byte[]... bs) {
		
		String msg = buildSendMessage(commond, bs);
		try {
			outputStream.write(SafeEncoder.castValue(msg));
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	public static String getReplyMessage(InputStream inputStream) {
		
		byte[] reply = new byte[1024];
		try {
			inputStream.read(reply);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return new String(reply);
	}
	
	public static String buildSendMessage(Command commond, byte[]... bs) {
		
		StringBuilder msg = new StringBuilder();
		msg.append(HEAD).append(bs.length + 1).append(LINEFLAG);
		msg.append(DOLLARS).append(commond.toString().length()).append(LINEFLAG);
		msg.append(commond).append(LINEFLAG);
		for (int i = 0; i < bs.length; i++) {
			msg.append(DOLLARS).append(bs[i].length).append(LINEFLAG);
			msg.append(SafeEncoder.castValue(bs[i])).append(LINEFLAG);
		}
		return msg.toString();
		
	}

}

第四步,演示

为了更好的查看效果,我先进行清库

执行 set,get

package com.jedis.test;

import org.junit.Test;
import com.haolin.java.demos.jedis.Hedis;

public class HedisTest {

	@Test
	public void testJedis() {
		// 使用我们手写的Hedis
		Hedis hedis = new Hedis();
		hedis.set("name", "Hello uuimi");
		
		String value = hedis.get("name");
		System.out.println(value);

	}

}

控制台输出:

Hello uuimi

查看redis-server:

30分钟手写Jedis_第3张图片

完工。

 努力改变自己和身边人的生活。

特别希望本文可以对您有所帮助,转载请注明出处。感谢大家留言讨论交流。 

你可能感兴趣的:(Redis)