理解 RESP:Redis 背后的协议

在我之前的文章中,我分享了使用 Node.js 构建的 Redis 克隆版的概览。现在,我想解释这个项目的一个关键部分:RESP,即 Redis Serialization Protocol(Redis 序列化协议)。

Redis 接收到的并不是整齐的 JavaScript 数组,而是通过 TCP socket 传输的原始字节。为了正常工作,这些字节需要一种结构。服务器必须知道一个命令从哪里开始,到哪里结束;必须知道有多少个参数,以及每个参数的长度是多少。

RESP 提供了这种结构。它是客户端和服务器之间进行通信的语言。

例如,命令 GET name 在 RESP 中看起来像这样: *2\r\n$3\r\nGET\r\n$4\r\nname\r\n

以下是详细分解:

  • *2 表示一个包含 2 个元素的数组。
  • $3 表示接下来的部分长度为 3 个字节:GET
  • $4 表示接下来的部分长度为 4 个字节:name

解析器将这些字节转换为 ["GET", "name"]。现在引擎就可以执行该命令了。

为什么不直接按空格拆分字符串呢? 简单的拆分在处理包含空格的值时会失败,例如 SET message "hello world"。它在处理二进制数据时也会失败。RESP 通过包含每个值的长度解决了这个问题。服务器会精确读取指定的字节数。这使得它具有二进制安全性(binary-safe)且非常可靠。

最难学到的一课是意识到 TCP 是基于流(stream-based)的,而不是基于消息(message-based)的。服务器并不总是每次接收到一个完整的命令。一个命令可能会分两次到达;或者,多个命令可能会在同一个数据块中同时到达。

这意味着解析器不能假设一次数据事件就等于一个命令。解析器必须使用内部缓冲区(buffer)。

我的 RESP 解析器工作流程如下:

  1. 从 socket 接收字节。
  2. 将字节添加到缓冲区。
  3. 尝试解析一个完整的命令。
  4. 如果命令不完整,等待更多数据。
  5. 如果命令已完整,则返回它。
  6. 如果剩余多余数据,将其保留在缓冲区中以供下次解析。

数据库始于协议层。在 Redis 存储数据之前,它必须理解命令;在返回一个值之前,它必须对响应进行编码。

实现 RESP 将一个简单的 TCP 服务器变成了一个真正的 Redis 兼容系统。它改变了我看待后端系统的方式。协议不仅仅是一种格式,它是两个系统之间的契约。

在我的下一篇文章中,我将介绍内存存储层。

仓库: https://github.com/Abhinov007/redis_clone 在线沙盒: https://abhinov007.github.io/Redis_Clone/ 来源: https://dev.to/abhinov007/understanding-resp-the-protocol-behind-redis-50p4