Files
www/blogs/database/redis/redis.md
zhuhjay 22e48d9558 build(www): 添加 Drone CI 流水线配置
- 新增 .drone.yml 文件用于定义 CI/CD 流程
- 配置了基于 Docker 的部署步骤
- 设置了工作区和卷映射以支持持久化数据
- 添加了构建准备阶段和 Docker 部署阶段
- 定义了环境变量和代理设置
- 配置了 artifacts 目录的处理逻辑
- 添加了 timezone 映射以确保时间同步
- 设置了 docker.sock 映射以支持 Docker in Docker
2025-11-01 13:36:00 +08:00

178 lines
6.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 使用Java实现Redis客户端
date: 2022-11-11
sidebar: 'auto'
tags:
- Redis
categories:
- NoSQL
- Java
---
## Redis通信协议-RESP协议
Redis是一个CS架构的软件通信一般分两步不包括pipeline和PubSub
客户端client向服务端server发送一条命令
服务端解析并执行命令,返回响应结果给客户端
因此客户端发送命令的格式、服务端响应结果的格式必须有一个规范,这个规范就是通信协议。
而在Redis中采用的是RESPRedis Serialization Protocol协议
- Redis 1.2版本引入了RESP协议
- Redis 2.0版本中成为与Redis服务端通信的标准称为RESP2
- Redis 6.0版本中从RESP2升级到了RESP3协议增加了更多数据类型并且支持6.0的新特性--客户端缓存
但目前默认使用的依然是RESP2协议也是我们要学习的协议版本以下简称RESP
在RESP中通过首字节的字符来区分不同数据类型常用的数据类型包括5种
- 单行字符串:首字节是 + 后面跟上单行字符串以CRLF "\r\n" )结尾。例如返回"OK" "+OK\r\n"
- 错误Errors首字节是 - ,与单行字符串格式一样,只是字符串是异常信息,例如:"-Error message\r\n"
- 数值:首字节是 : 后面跟上数字格式的字符串以CRLF结尾。例如":10\r\n"
- 多行字符串:首字节是 $ 表示二进制安全的字符串最大支持512MB
- 如果大小为0则代表空字符串"$0\r\n\r\n"
- 如果大小为-1则代表不存在"$-1\r\n"
- 数组:首字节是 *’,后面跟上数组元素个数,再跟上元素,元素数据类型不限
Redis支持TCP通信因此我们可以使用Socket来模拟客户端与Redis服务端建立连接
```java
public class RedisClient {
/** Redis中 RESP 协议头 */
private final static char SIMPLE_STRING = '+';
private final static char ERRORS = '-';
private final static char NUMBER = ':';
private final static char MULTI_STRING = '$';
private final static char ARRAY = '*';
private final static String NEW_LINEAR = "\r\n";
/** Redis服务端连接信息 */
private final static String CONN_HOST = "localhost";
private final static Integer CONN_PORT = 6379;
private Socket socket;
private InputStream is;
private OutputStream os;
public RedisClient() {
try {
// 使用Socket进行远程连接
socket = new Socket(CONN_HOST, CONN_PORT);
is = socket.getInputStream();
os = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
close();
}
}
public void sendRequest(String msg) throws IOException {
// 解析命令
String[] commands = parseCommand(msg);
// 进行命令的拼接写出
StringBuilder sb = new StringBuilder();
// *3\r\n 命令数组头
sb.append(ARRAY).append(commands.length).append(NEW_LINEAR);
for (String command : commands) {
// $4\r\nname\r\n
sb.append(MULTI_STRING).append(command.length()).append(NEW_LINEAR).append(command).append(NEW_LINEAR);
}
os.write(sb.toString().getBytes(StandardCharsets.UTF_8));
os.flush();
}
private String[] parseCommand(String commandStr) {
return commandStr.split(" ");
}
public Object handlerResponse() throws IOException {
int read = is.read();
switch (read) {
case SIMPLE_STRING:
case ERRORS:
return new BufferedReader(new InputStreamReader(is)).readLine();
case NUMBER:
return readNumber();
case MULTI_STRING:
return readMulti();
case ARRAY:
return readArrays();
default:
throw new RuntimeException("结果有误");
}
}
private Object readMulti() throws IOException {
int count = readNumber().intValue();
if (count == -1) {
return null;
}
if (count == 0) {
return "";
}
return readLine(count);
}
private Long readNumber() throws IOException {
byte[] bytes = new byte[1024];
int temp;
int count = 0;
while ((temp = is.read()) != '\n') {
if (temp != '\r') {
bytes[count++] = (byte) temp;
}
}
return Long.parseLong(new String(bytes, 0, count));
}
private Object readArrays() throws IOException {
int count = readNumber().intValue();
List<Object> data = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
data.add(handlerResponse());
}
return data;
}
private String readLine(int count) throws IOException {
byte[] bytes = new byte[1024];
for (int i = 0; i < count + NEW_LINEAR.length(); i++) {
int temp = is.read();
if (temp != '\r' && temp != '\n') {
bytes[i] = (byte) temp;
}
}
return new String(bytes, 0, count, StandardCharsets.UTF_8);
}
public void close() {
try {
if (socket != null) {
socket.close();
}
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
RedisClient redisClient = new RedisClient();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while (true) {
System.out.print("[INFO] 请输入redis命令>>> ");
String command = reader.readLine();
if (command.equalsIgnoreCase("exit")) {
redisClient.close();
break;
}
redisClient.sendRequest(command);
System.out.println(redisClient.handlerResponse());
}
}
}
```