文章目录
- 0. 前言
- 大纲
- 1. Redis协议(RESP)
- 1. 简介
- 2. 协议设计
- 附加类型
- 3. 数据传输
- 请求和响应之间的交互模式
- 客户端与服务端交互
- 4. java实现 RESP协议
- 3. 总结
- 4.参考资料
0. 前言
当我们谈论 Redis 时,一般来说,我们讨论的核心是它用来存储和检索数据的多种数据结构:字符串、列表(Lists)、集合(Sets)、有序集合(Sorted Sets)和散列表(Hashes)等。但是,这些操作必须通过一定的协议来执行,这个协议就是Redis协议 (RESP, Redis Serialization Protocol)。
RESP 是一个简单而强大的协议,设计得非常易于理解和实现,几乎可以说是目前中间件中最简单的一种协议。这也是 Redis 受到广泛欢迎的一部分原因。这篇博客将详细介绍 RESP,包括其基本格式,以及如何使用 RESP 与 Redis 服务器进行交互。无论你是一个开发者,还是想了解更深层次的 Redis 工作原理的人,都可以从这篇文章get到一些点。
大纲
1. Redis协议(RESP)
1. 简介
Redis协议,也被称为 RESP (Redis Serialization Protocol),它是一种简单的文本协议,用于在客户端和服务器之间操作和传输数据。可以说是最简单的一种传输协议。
RESP 协议描述了不同类型的数据结构,并且定义了请求和响应之间如何以这些数据结构进行交互。
RESP 协议支持的数据类型:
- 简单字符串(Simple Strings): 以 “+” 开头,例如 “+OK\r\n” 表示一个成功的响应。
- 错误(Errors): 以 “-” 开头,例如 “-ERR unknown command\r\n” 表示一个错误响应。
- 整数(Integers): 以 “:” 开头,例如 “:1000\r\n” 表示整数1000。
- 批量字符串(Bulk Strings): 以 “$” 开头,例如 “$6\r\nfoobar\r\n” 表示一个长度为6的字符串 “foobar”。
- 数组(Arrays): 以 “*” 开头,例如 “*3\r\n:1\r\n:2\r\n:3\r\n” 表示包含3个整数的数组 [1, 2, 3]。
RESP 非常简单且人类可读,这使得 Redis 能够易于使用和调试。同时,RESP 也允许客户端和服务器以高效和低延迟的方式发送和接收数据。
例如,一个 Redis 客户端发送一个 “SET mykey myvalue” 命令,将转换为 RESP 协议如下:
*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n
Redis 服务器将解析这个 RESP 请求,并返回一个 RESP 响应,如 “+OK\r\n”,表示命令成功执行。
2. 协议设计
- 简单字符串(Simple Strings):以"+“字符开头,例如:”+OK\r\n"。
- 错误信息(Errors):以"-“字符开头,例如:”-Error message\r\n"。
- 整数(Integers):以":“字符开头,例如:”:1000\r\n"。
- 块字符串(Bulk Strings):以"$“字符开头,后跟字符串长度和字符串内容,例如:”$6\r\nfoobar\r\n"。
- 数组(Arrays):以"*“字符开头,后跟数组的长度和数组元素,例如:”*3\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$5\r\nhello\r\n"。
在RESP协议中,客户端向服务器发送命令请求,服务器接收并处理这些命令,然后返回一个响应。服务器能够一次处理多个命令,并且能够处理各种类型的数据,包括字符串、列表、集合、散列表等。
下面是一个实例,客户端发送一个GET mykey
命令:
*2\r\n$3\r\nGET\r\n$5\r\nmykey\r\n
如果mykey
存在且其值为myvalue
,那么服务器将返回:
$7\r\nmyvalue\r\n
如果mykey
不存在,那么服务器将返回:
$-1\r\n
RESP协议的设计使得Redis的客户端和服务器能够高效地进行通信。客户端和服务器都可以一次处理多个请求和响应,而不需要为每个请求/响应建立一个新的连接。这种设计使得Redis能够在处理大量数据时保持高性能和低延迟。
RESP 协议还有一个特点就是能够处理流式数据,这意味着客户端和服务器可以在数据发送和接收过程中开始处理数据,而不需要等待所有数据都发送或接收完毕。这对于处理大量数据或者大型集群非常有用。
在 RESP 协议中,一条命令或者响应的结束由 “\r\n” 标记。这种结构使得在数据流中识别消息的开始和结束变得非常简单。这也是 RESP 协议为什么能够做到在处理大量数据时仍然保持高效的一个重要原因。
另一个实用的特性是 RESP 协议对于二进制安全的支持。虽然 RESP 是一个基于文本的协议,但是它能够处理二进制数据。例如,你可以在 Redis 中存储一个图片或者视频文件,然后使用 RESP 协议来获取或者修改这些文件。
在处理二进制数据时,数据被当作一个批量字符串来处理。批量字符串的长度由一个数字表示,数字后面紧跟着的是字符串的内容。例如,表示一个长度为 5 的二进制数据 “hello”,在 RESP 协议中将会是 “$5\r\nhello\r\n”。
在 RESP 协议中,错误信息也是作为一种数据类型进行处理的。如果服务器在处理命令时遇到错误,那么它会返回一个以 “-” 开头的错误信息,像这样:“-ERR Unknown Command\r\n”。
总的来说,RESP 协议是一个非常强大且灵活的协议,能够适应各种不同类型的数据和应用场景。
附加类型
RESP协议的响应类型除了简单字符串、错误、整数、批量字符串和数组外,还有一种特殊的数据类型,那就是Null。在RESP协议中,Null值可以用于表示一个不存在或者未定义的值。
例如,当你尝试获取一个不存在的键的值时,Redis服务器会返回一个Null响应。在RESP协议中,Null响应可以是一个Null批量字符串($-1\r\n
),也可以是一个Null数组(*-1\r\n
)。
3. 数据传输
请求和响应之间的交互模式
RESP协议的数据传输采用简单的文本流,使用\r\n进行分隔符。
此外,RESP协议还定义了请求和响应之间的交互模式。在大多数情况下,交互模式是请求/响应模式,即客户端发送一个请求,然后服务器返回一个响应。然而,RESP协议也支持服务器推送模式(push messages),在这种模式下,服务器可以在没有接收到请求的情况下向客户端发送消息。这对于实现实时通信和事件驱动的应用场景非常有用。
客户端与服务端交互
客户端发送命令给Redis服务端时,将命令按RESP协议进行编码后发送。服务端接收到命令后进行解码,并根据命令进行相应操作后返回结果,也是按RESP协议进行编码后发送给客户端。
4. java实现 RESP协议
我用java 做了一个简单的实现,方便大家理解RESP协议。
package com.icepip.project.redis.resp;
import java.io.*;
import java.net.*;
/**
* Redis协议 RESP 学习和用java 原生简单实现
* 本代码示例 对应博客 《【高阶篇】Redis协议(RESP )详解》感谢大家指正
* @author 冰点
* @version 1.0.0
* @date 2023/9/7 17:42
*/
public class RedisRESP {
public static void main(String[] args) throws IOException {
// 创建一个 socket 连接
Socket socket = new Socket("127.0.0.1", 6379);
// 获取输出流,用于向服务器发送命令
BufferedWriter out = new BufferedWriter(
new OutputStreamWriter(
socket.getOutputStream(), "UTF-8"));
// 获取输入流,用于接收服务器的响应
BufferedReader in = new BufferedReader(
new InputStreamReader(
socket.getInputStream(), "UTF-8"));
// TODO 如果 Redis 服务器需要密码进行身份验证 如果不需要密码 则去掉这块代码验证
String password = "123456";
if (!password.isEmpty()) {
// 发送 AUTH 命令进行身份验证
out.write("AUTH " + password + "\r\n");
out.flush();
// 读取服务器响应
String response = in.readLine();
System.out.println("响应:"+response);
if (response.contains("OK")) {
// 身份验证成功
System.out.println("Authentication successful");
} else {
// 身份验证失败
System.out.println("Authentication failed");
// 进行错误处理或关闭连接等操作
return;
}
}
// 字符串数据类型
// 使用 SET 命令设置一个键值对
sendCommand(out, in, "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n");
// 使用 GET 命令获取键的值
sendCommand(out, in, "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n");
// 列表数据类型
// 使用 RPUSH 命令将一个元素添加到列表的右侧
sendCommand(out, in, "*3\r\n$5\r\nRPUSH\r\n$4\r\nlist\r\n$5\r\nvalue\r\n");
// 使用 LPOP 命令移除并获取列表的第一个元素
sendCommand(out, in, "*2\r\n$4\r\nLPOP\r\n$4\r\nlist\r\n");
// 集合数据类型
// 使用 SADD 命令添加一个元素到集合
sendCommand(out, in, "*3\r\n$4\r\nSADD\r\n$3\r\nset\r\n$5\r\nvalue\r\n");
// 使用 SPOP 命令随机移除并返回一个元素
sendCommand(out, in, "*2\r\n$4\r\nSPOP\r\n$3\r\nset\r\n");
// 有序集合数据类型
// 使用 ZADD 命令添加一个元素到有序集合,为元素设置分数为0
sendCommand(out, in, "*4\r\n$4\r\nZADD\r\n$4\r\nzset\r\n$1\r\n0\r\n$5\r\nvalue\r\n");
// 使用 ZRANGE 命令返回有序集合中所有元素,按分数从小到大排序
sendCommand(out, in, "*4\r\n$6\r\nZRANGE\r\n$4\r\nzset\r\n$1\r\n0\r\n$2\r\n-1\r\n");
// 散列表数据类型
// 使用 HSET 命令向散列表添加一个键值对
sendCommand(out, in, "*4\r\n$4\r\nHSET\r\n$4\r\nhash\r\n$5\r\nfield\r\n$5\r\nvalue\r\n");
// 使用 HGET 命令获取散列表中指定字段的值
sendCommand(out, in, "*3\r\n$4\r\nHGET\r\n$4\r\nhash\r\n$5\r\nfield\r\n");
// 关闭连接
in.close();
out.close();
socket.close();
}
/**
* 辅助方法,用于发送命令并打印响应
* @param out
* @param in
* @param command
* @throws IOException
*/
private static void sendCommand(BufferedWriter out, BufferedReader in, String command) throws IOException {
System.out.println("发送:"+command);
out.write(command); // 向服务器发送命令
out.flush(); // 清空缓冲区,确保命令立即发送
System.out.println("接收:"+in.readLine()); // 读取并打印服务器的响应
}
}
输出如下。里面一些转义字符 被IDEA控制台直接执行了。所以看到的是换行。但不影响大家理解。
3. 总结
Redis 协议主要分为 16 种,其中 8 种协议对应 8 种数据类型,你选择了使用什么数据类型,就使用对应的响应操作指令即可。剩下
8 种协议如下所示。
-
发布订阅协议(Pub-Sub protocol):此协议允许客户端订阅一个或多个频道(channels)以接收服务器发布的消息。这对于实现实时的事件驱动应用场景非常有用。
-
事务协议(Transaction protocol):Redis提供了
MULTI
,EXEC
,DISCARD
和WATCH
等事务命令来实现事务功能。当在MULTI
和EXEC
之间的一系列命令作为一个原子操作被执行。 -
脚本协议(Scripting protocol):使用
EVAL
命令,你可以执行Lua脚本。此外,EVALSHA
命令可以执行先前通过SCRIPT LOAD
命令加载的脚本。 -
连接协议(Connection protocol):可以通过
AUTH
,SELECT
,QUIT
等命令进行身份验证,切换数据库,关闭连接等。 -
复制协议(Replication protocol):
SLAVEOF
和SYNC
命令用于控制和实现Redis的主从复制功能。 -
配置协议(Configuration protocol):
CONFIG GET
,CONFIG SET
等命令可以用来获取和设置Redis服务器的配置参数。 -
调试统计(Debugging and statistics protocol):包括
MONITOR
,INFO
,SLOWLOG
等命令,这些命令提供调试和统计信息。 -
其他内部命令(Other internal commands):如
DUMP
,RESTORE
,MIGRATE
等用于数据迁移和持久化等操作。
4.参考资料
- Redis官方文档:https://redis.io/
- Redis实战(书籍)
- Redis设计与实现(书籍)
- https://www.toutiao.com/article/7088132873051488780/