redis 核心原理

RESP(REdis Serialization Protocol )协议:

redis客户端和服务端是通过RESP协议来进行交互的,RESP协议是基于TCP协议的,将客户端命令以某种形式传递给服务端,服务端接收后,进行命令的解析,并执行。这里我们先将redis持久化方式设置为AOF模式,准确查看RESP协议是怎么进行传输的。将appendonly.aof文件清空,然后使用set name xiaoming命令, 刷新文件,可以看到文件上如下字符,且每行以'\r\n结尾':

*3          //表明有几个参数
$3          //第一个参数的长度为3
SET         //第一个参数
$4          //第二个参数的字符为4
name        //第二个参数
$4          //第三个参数的长度
quan        //第三个参数
知道了RESP的大致格式后,我们可以使用虚拟服务来进行客户端请求的拦截。

首先先建虚拟服务器:

public static void main(String[] args) throws IOException {
	ServerSocket server = new ServerSocket(6378);
	Socket socket = server.accept();
	BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
	String data = null;
	while((data=reader.readLine()) != null) {
		System.out.println(data);
	}
}

然后使用Jedis新建客户端:

public static void main(String[] args) {
	Jedis jedis = new Jedis("localhost", 6378);
	jedis.set("name", "xiaoming");
	jedis.close();
}

控制台输出与appendonly.aof文件输出相同。

接下来我们自己写一个redis交互的类,仿照Jedis。这里我们使用的是长连接,即可以保持长时间的连接,而不是每请求一次就新建一个连接。

package jedis;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class CustomClient {
	InputStream is;
	OutputStream os;
	Socket socket;
	public CustomClient() throws IOException {
		socket = new Socket("localhost", 6379);
		is = socket.getInputStream();
		os = socket.getOutputStream();
	}
	public void set(String key ,String value) {
		StringBuilder builder = new StringBuilder();  
		builder.append("*3").append("\r\n");    
		builder.append("$3").append("\r\n");
		builder.append("SET").append("\r\n");
		builder.append("$").append(key.length()).append("\r\n");  
		builder.append(key).append("\r\n");
		builder.append("$").append(value.length()).append("\r\n");
		builder.append(value).append("\r\n"); 
		try {
			os.write(builder.toString().getBytes());
			byte[] data = new byte[1024];
			is.read(data);
			System.out.println(new String(data));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		CustomClient client;
		try {
			client = new CustomClient();
			client.set("name", "daming");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

查看键值对,可以看到name已经被修改为daming;

PipeLine的实现

TCP是请求响应模型,即发出一次请求,获取一次响应,但是这样数据间传输比较缓慢,在redis中使用pipeLine来解决这个难题。解决办法是:我们发起多次请求,不必关心响应结果,这样redis会将我们每次的响应结果缓存到内存中,等请求全部执行完成后,再从内存中获取响应结果。

首先我们定义一个PipeLine类,set方法或自定义客户端的set方法类似,只是没有了获取响应结果。response方法返回我们的响应结果。我们可以使用for循环来进行执行时间的比较,可以看到使用管道后速度明显加快。

public class PipeLine {
	InputStream is;
	OutputStream os;
	
	public PipeLine(InputStream is,OutputStream os) {
		this.is = is;
		this.os = os;
	}
	
	public void set(String key ,String value) {
		StringBuilder builder = new StringBuilder();
		builder.append("*3").append("\r\n");
		builder.append("$3").append("\r\n");
		builder.append("SET").append("\r\n");
		builder.append("$").append(key.length()).append("\r\n");
		builder.append(key).append("\r\n");
		builder.append("$").append(value.length()).append("\r\n");
		builder.append(value).append("\r\n");
		try {
			os.write(builder.toString().getBytes());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public String response() {
		byte[] data = new byte[1024*100];
		try {
			is.read(data);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return new String(data);
	}
}
订阅模式的实现

创建SubScribe类,来实现订阅。

public class SubScribe {
	InputStream is;
	OutputStream os;
	
	public SubScribe(InputStream is,OutputStream os) {
		this.is = is;
		this.os = os;
	}
	//模仿订阅通道
	public void sub(String channel) {
		StringBuilder builder = new StringBuilder();
		builder.append("*2").append("\r\n");
		builder.append("$9").append("\r\n");
		builder.append("SUBSCRIBE").append("\r\n");
		builder.append("$").append(channel.length()).append("\r\n");
		builder.append(channel).append("\r\n");
		try {
			os.write(builder.toString().getBytes());
			byte[] data = new byte[1024];
                        //使用for循环来进行订阅消息的接收
			while(true) { 
				is.read(data);
				System.out.println(new String(data));
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

redis代理

通常我们只有一个redis实例,但是会有局限性:不方便扩容;复制数据比较麻烦,因此我们可以使用多个实例,也就是集群。这里我们采用的负载均衡。负载均衡即按照负载策略均匀分布在不同的实例中。

负载策略是一种算法,将数据均匀分布的一种算法。

这里我们使用的负载策略是对key的长度进行取模。代理类代码如下:

通过创建虚拟服务器,监听该端口,对每个连接该服务的socket的输入流进行解析,获取key的长度,创建不同的客户端进行命令的执行,最后关闭客户端。

public class RedisProxy {
	private static List<Config> list;
	static {
		list = new ArrayList<>();
		list.add(new Config("localhost", 6379));
		list.add(new Config("localhost", 6380));
		list.add(new Config("localhost", 6381));
	}
	//负载算法实现
	public static void main(String[] args) throws IOException {
		ServerSocket server = new ServerSocket(9999);
                // 监听端口
                Socket socket ;
		while((socket = server.accept())!=null) {
			System.out.println("进行负载算法");
                        byte[] data = new byte[1024];
			InputStream is = socket.getInputStream();
			int length = is.read(data);
			String result = new String(data);
			//获取到key值的长度
                        String length = result.split("\r\n")[3].split("\\$")[1];
			int index = Integer.parseInt(key)%list.size();
			Config config = list.get(index);
			Socket client = new Socket(config.getHost(), config.getPort());
			client.getOutputStream().write(data,0,length);
			byte[] res = new byte[1024*10];
			client.getInputStream().read(res);
			System.out.println(new String(res));
			client.close();
                        socket.getOutputStream().write(res);   //需要将响应结果填充到jedis的输出域中,否则会报错。
		}
	}
	
	static class Config {
		private String host;
		private int port;
		public Config(String host, int port) {
			this.host = host;
			this.port = port;
		}
		public String getHost() {
			return host;
		}
		public int getPort() {
			return port;
		}
		
	}
}

测试类如下:

使用的是相通的端口9999。

public static void main(String[] args) {
		Jedis j1 = new Jedis("localhost", 9999);
		j1.set("a", "a");
		Jedis j2 = new Jedis("localhost", 9999);
		j2.set("ab", "ab");
		Jedis j3 = new Jedis("localhost", 9999);
		j3.set("abc", "abc");
}

当然,在实际开发中,我们是不会自己写代理的,难以维护,使用成熟的代理服务。例如:

Codis——豌豆荚开源的Redis分布式中间件


猜你喜欢

转载自blog.csdn.net/qq_39158142/article/details/80896627