「黑马 Redis 原理」三、通信协议

RESP 协议

Redis 是一个 CS 架构的软件，通信一般分两步（不包括 pipeline 和 PubSub）

客户端 client 向服务端 server 发送一条命令

服务端解析并执行命令，返回响应结果给客户端

因此客户端发送命令的格式、服务端响应结果的格式必须有一个规范，这个规范就是通信协议

RESP：redis serialization protocol

学习 RESP2 协议，在 2.0 版本推出，6.0 升级为 RESP3 但因为变化过大仍默认使用 RESP2 协议

RESP 协议数据类型

对于单行字符串、错误、数据都是二进制不安全的，如果在内容中穿插了 \r\n 可能会误解成结尾

数组类型看上去其实和 AOF 文件挺像

模拟 Redis 客户端

跑下来 reader 收不到信息？

Copy
public class Main {
static Socket s;
static PrintWriter out;
static BufferedReader in;
public static void main(String[] args) {
try {
// 1. 建立连接
String host = "redis.orb.local";
int port = 6379;
s = new Socket(host, port);
// 2. 获取输入、输出流
out = new PrintWriter(
new OutputStreamWriter(s.getOutputStream(), StandardCharsets.UTF_8));
in = new BufferedReader(new InputStreamReader(s.getInputStream()));
// 3. 发出请求并解析
// 获取授权 auth TheBestWorkLoanapp1 sendRequest("auth", "TheBestWorkLoanapp1");
Object obj = handleResponse();
System.out.println(obj.toString());
// 设置键值对 set name Chanler sendRequest("set", "name", "Chanler");
obj = handleResponse();
System.out.println(obj.toString());
// 获取键值对 get name sendRequest("get", "name");
obj = handleResponse();
System.out.println(obj.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 释放链接
try {
if (s != null) {s.close();}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (out != null) {out.close();}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (in != null) {in.close();}
} catch (IOException e) {
e.printStackTrace();
}
}
}

private static Object handleResponse() throws IOException {
// 读取首字节判断数据类型
int prefix = in.read();
switch (prefix) {
case '+':
return in.readLine();
case '-':
throw new RuntimeException(in.readLine());
case ':':
return Long.parseLong(in.readLine());
case '$':
int len = Integer.parseInt(in.readLine());
if (len == -1) {
return null;
}
if (len == 0) {
return "";
}
// 这里应该读 len 个字节，但这里已经转换成了字符流，所以假设不存在特殊字符
return in.readLine();
case '*':
return readBulkString();
default:
throw new RuntimeException("Invalid prefix");
}
}

private static Object readBulkString() throws IOException {
int len = Integer.parseInt(in.readLine());
if (len == 0) {
return null;
}
List<Object> list = new ArrayList<>();
for (int i=0; i<len; i++) {
list.add(handleResponse());
}
return list;
}

// 发送 set name Chanler
private static void sendRequest(String ... args) {
// \r\n 就是换行符，这里用 println 直接附带了
out.println("*" + args.length);
for (String arg : args) {
out.println("$" + arg.getBytes(StandardCharsets.UTF_8).length);
out.println(arg);
}
out.flush();
}
}

此文由 Mix Space 同步更新至 xLog
原始链接为 https://blog.0xling.cyou/posts/redis/redis-5