理解 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 解析器工作流程如下:
- 从 socket 接收字节。
- 将字节添加到缓冲区。
- 尝试解析一个完整的命令。
- 如果命令不完整,等待更多数据。
- 如果命令已完整,则返回它。
- 如果剩余多余数据,将其保留在缓冲区中以供下次解析。
数据库始于协议层。在 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