Redis的通信协议-RESP
Redis的客户端使用的是RESP协议与Redis的服务器进行通信交互,这个协议不仅仅在Redis中使用,我们也可以用于其他的C-S软件项目,RESP主要的特点如下:①实现简单②解析快速③人类可读
接下来,我们围绕着RESP协议写几段代码来讲解它,这样应该能更深刻的理解RESP协议。
一、RESP协议传输的数据内容(结构)是怎样的?
RESP 底层采用的是 TCP 的连接方式,我们只需要使用Jedis作为客户端,自己模拟一个Socke作为服务端来接收Jedis发送过来的内容,就可以看到Jedis发送的内容是什么了。(如果没有网络编程基础知识的读者可以看我的网络编程与Netty专栏,有详细的学习讲解)
RedisClient
public class RedisClient {
public static void main(String[] args) {
//redis服务端地址和端口需要换成自己的
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.set("test-protocol", "hello world!"); //set "test-protocol" "hello world!"
}
}
RedisServer
public class RedisServer {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(6379);
Socket socket = serverSocket.accept();
byte[] arr = new byte[64];
socket.getInputStream().read(arr);
System.out.println(new String(arr));
}catch (Exception e){
e.printStackTrace();
}
}
}
运行结果:
把协议内容拿出来详细讲讲都代表什么意思
*3 //*<参数数量>/r/n
$3 //$<参数 1 的字节长度>
SET //<参数1的具体数据>
$13 //$<参数 2 的字节长度>
test-protocol //<参数2的具体数据>
$12 //$<参数 3 的字节长度>
hello world! //<参数3的具体数据>
二、自己手写一个简易Jedis
看完上面第一段代码,不知道是否各位读者是否想要自己手写一个Jedis作为客户端向服务端发送请求呢?下面笔者写一个简易的Jedis小案例,同时也是为了验证上面RESP协议的结构是否跟我们理解的一样。
MyJedis
public class MyJedis {
public static String auth(Socket socket,String password) throws IOException {
StringBuffer str = new StringBuffer();
str.append("*2").append("\r\n");
str.append("$4").append("\r\n");
str.append("auth").append("\r\n");
str.append("$").append(password.getBytes().length).append("\r\n");
str.append(password).append("\r\n");
socket.getOutputStream().write(str.toString().getBytes());
byte[] response = new byte[2048];
socket.getInputStream().read(response);
return new String(response);
}
public static String set(Socket socket,String key, String value) throws IOException {
StringBuffer str = new StringBuffer();
str.append("*3").append("\r\n");
str.append("$3").append("\r\n");
str.append("SET").append("\r\n");
str.append("$").append(key.getBytes().length).append("\r\n");
str.append(key).append("\r\n");
str.append("$").append(value.getBytes().length).append("\r\n");
str.append(value).append("\r\n");
socket.getOutputStream().write(str.toString().getBytes());
byte[] response = new byte[2048];
socket.getInputStream().read(response);
return new String(response);
}
public static String get(Socket socket,String key) throws IOException {
StringBuffer str = new StringBuffer();
str.append("*2").append("\r\n");
str.append("$3").append("\r\n");
str.append("GET").append("\r\n");
str.append("$").append(key.getBytes().length).append("\r\n");
str.append(key).append("\r\n");
socket.getOutputStream().write(str.toString().getBytes());
byte[] response = new byte[2048];
socket.getInputStream().read(response);
return new String(response);
}
public static void main(String[] args) throws IOException {
//需要换成自己的ip.端口.密码
Socket socket = new Socket("127.0.0.1",6379);
auth(socket,"123456");
set(socket,"MyJedis","good");
System.out.println(get(socket,"MyJedis"));
}
}
运行结果:
图中可以看出,确实是将MyJedis-good这个k-v对存入了Redis,并且Redis的服务器返回的数据也是使用RESP协议
三、使用pipeline批量删除,批量执行命令,并且对比不使用pipeline进行删除操作所需要花费的时间
UsePipeline
public class UsePipeline {
//删除10000个key
public static int arraylength = 10000;
public static String[] keys = new String[arraylength/2];
public static void initRedisData() {
//换成自己的ip 端口 密码
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.auth("123456");
String[] str = new String[arraylength];
int j = 0;
for(int i = 0 ; i < str.length/2; i ++ ){
str[j] = "key:"+i;
str[j+1] = "v"+i;
keys[i]= str[j];
j = j+2;
}
jedis.mset(str);
jedis.close();
}
public static void main(String[] args) {
initRedisData();
long t = System.currentTimeMillis();
//不使用pipeline
delNoPipe(keys);
//使用pipeline
//delByPipe(keys);
System.out.println(System.currentTimeMillis()-t);
}
public static void delNoPipe(String...keys){
//换成自己的ip 端口 密码
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.auth("123456");
for(String key:keys){
jedis.del(key);
}
jedis.close();
}
public static void delByPipe(String...keys){
//换成自己的ip 端口 密码
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.auth("123456");
Pipeline pipelined = jedis.pipelined();
for(String key:keys){
pipelined.del(key);
}
pipelined.sync();
jedis.close();
}
}
测试结果:
四、看完第三段代码,是否有想过自己手写一个简易的Pipeline?那就开始吧。
MyPipeline
public class MyPipeline {
private Socket socket;
public MyPipeline(Socket socket) {
this.socket = socket;
}
/**
* 传入数组KEY,批量删除
* String[]{"key:0","key:1","key:2","key:3","key:4"})
*/
public void mdel(String... keys) throws Exception {
StringBuffer str = new StringBuffer();
for(String key:keys){
str.append("*2").append("\r\n");
str.append("$3").append("\r\n");
str.append("del").append("\r\n");
str.append("$").append(key.getBytes().length).append("\r\n");
str.append(key).append("\r\n");
}
socket.getOutputStream().write(str.toString().getBytes());
}
public String auth(String password) throws IOException {
StringBuffer str = new StringBuffer();
str.append("*2").append("\r\n");
str.append("$4").append("\r\n");
str.append("auth").append("\r\n");
str.append("$").append(password.getBytes().length).append("\r\n");
str.append(password).append("\r\n");
socket.getOutputStream().write(str.toString().getBytes());
byte[] response = new byte[2048];
socket.getInputStream().read(response);
return new String(response);
}
}
public class UsePipeline {
public static int arraylength = 10000;
public static String[] keys = new String[arraylength/2];
public static void initRedisData() {
Jedis jedis = new Jedis(RedisUtils.ip, RedisUtils.port);
jedis.auth("12345678");
String[] str = new String[arraylength];
int j = 0;
for(int i = 0 ; i < str.length/2; i ++ ){
str[j] = "key:"+i;
str[j+1] = "v"+i;
keys[i]= str[j];
j = j+2;
}
jedis.mset(str);
jedis.close();
}
public static void main(String[] args) {
initRedisData();
long t = System.currentTimeMillis();
delByMyPipe(keys);
System.out.println(System.currentTimeMillis()-t);
}
public static void delByMyPipe(String...keys){
try {
//换成自己的ip 端口 密码
Socket socket = new Socket("127.0.0.1",6379);
MyPipeline myPipeline = new MyPipeline(socket);
myPipeline.auth("123456");
myPipeline.mdel(keys);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
可以看到,使用了pipeline的支持后,在批量删除时,可以大大的减少时间,它的原理也很简单,原本的删除操作一次请求只能删除一个key,而前面我们说过一次完整的redis请求主要包括网络传输和执行命令的过程,如果每删除一个key就需要发出一次网络请求,是比较浪费时间的,而pipeline则是将所有的删除命令打包在了一起,通过一次请求发送到服务器端进行删除,这样可以大大减少时间浪费。在案例三和四的结果中也很明显,删除同样的数据,消耗时间大大减少。