Skip to content

命令执行与 RESP ​

一条 SET key value 从客户端发出到 Redis 返回 OK,中间经历了协议编码、网络传输、协议解析、命令查找、执行、响应写回。理解这条链路,才能解释命令是如何被可靠、高效地执行的。

1. RESP 协议 ​

RESP(REdis Serialization Protocol)是客户端与 Redis 通信的协议。它简单、易解析、二进制安全。

1.1 五种数据类型 ​

类型首字节含义示例
简单字符串+简单回复+OK\r\n
错误-错误信息-ERR unknown command\r\n
整数:整数回复:1000\r\n
批量字符串$二进制安全的字符串$3\r\nfoo\r\n
数组*多个元素*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n

1.2 命令编码格式 ​

客户端把命令编码为「数组 + 批量字符串」的格式发给 Redis:

txt
客户端发送:SET mykey myvalue
实际字节流:*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n
             ↑        ↑            ↑              ↑
             数组3     SET(长度3)    mykey(长度5)    myvalue(长度7)
  • *3:这是一个包含 3 个元素的数组
  • $3\r\nSET\r\n:第一个元素是长度 3 的字符串 "SET"
  • 依次类推

1.3 内联命令 ​

除了 RESP 格式,Redis 也支持内联命令(空格分隔的纯文本),例如直接发送 PING。这只用于 telnet 等手工调试场景,客户端库一般都用 RESP 格式。

1.4 RESP3(Redis 6.0+) ​

RESP2 只有 5 种类型,RESP3 新增了以下 10 种类型(完整清单):

新增类型首字节说明
双精度浮点,浮点数
布尔#true/false
空值_NULL
Blob 错误!带长度的错误信息
大整数(任意精度整数
Verbatim 字符串=带格式提示的字符串
属性|附加元数据
推送>服务端主动推送的数据
Map%键值对映射
Set~集合类型

其中 Push(推送)允许服务端在普通请求响应之外主动推送数据,常用于客户端缓存失效通知。

bash
# 启用 RESP3
HELLO 3

RESP3 的价值在于更丰富的类型系统,客户端可以更精确地反序列化响应,减少类型转换。

2. 命令解析 ​

Redis 收到网络字节流后,需要把它解析成内部可执行的命令结构。

解析流程:

解析的关键点:

环节说明
读缓冲区客户端连接有一个输入缓冲区(默认硬上限 1GB),读到的字节先暂存于此
协议判断首字节是 * 按 RESP 数组格式解析,否则尝试按内联命令格式解析
参数数组解析出 argv(参数数组)与 argc(参数个数)
二进制安全批量字符串按 $长度 精确读取,不依赖 \0 结尾

2.1 客户端输入缓冲区 ​

每个客户端连接都有一个输入缓冲区(querybuf),用于暂存从网络读到的数据:

bash
# 查看客户端缓冲区
redis-cli CLIENT LIST
# qbuf=1024 qbuf-free=32678 ...
字段含义
qbuf已使用的缓冲区大小
qbuf-free剩余可用空间

输入缓冲区用于累计客户端发来的命令。超大命令、批量事务或协议不同步可能使其增长;KEYS * 返回百万个 key 产生的大响应则占用服务端到客户端的输出缓冲区。两者应分别使用 client-query-buffer-limit 和 client-output-buffer-limit 检查。

3. 命令表查找 ​

解析出命令名后,Redis 需要找到对应的命令实现。它维护一个命令表——所有命令的哈希表。

txt
命令表(哈希表,O(1) 查找)
  "SET"   → redisCommand { 实现函数, 参数个数, 属性标志 }
  "GET"   → redisCommand { ... }
  "HSET"  → redisCommand { ... }
  ...

redisCommand 结构的关键字段:

字段作用
proc实现函数指针,指向真正执行命令的 C 函数
arity参数个数(含命令名本身),正数=精确个数,负数=最小个数
flags命令属性(写命令/只读命令/管理命令等)
microseconds累计执行耗时(用于 INFO commandstats 命令统计)
calls累计调用次数

查找特点:

  • O(1)(平均)哈希查找:命令名作为 key,哈希表直接命中
  • 大小写不敏感:SET 与 set 等价
  • 未知命令报错:找不到返回 -ERR unknown command

4. 执行流程与钩子 ​

找到命令后,执行过程会依次经过参数校验、键过期检查、命令执行、传播等环节,这些环节就像挂在命令执行链路上的钩子,承载了 Redis 的很多附加能力。

执行流程各环节:

环节说明
参数校验检查参数个数是否符合 arity 要求
键过期检查惰性删除:访问到已过期 Key 时先删除再执行
执行命令调用命令对应的实现函数,操作数据结构
慢查询日志记录执行耗时超过阈值的命令
AOF 传播AOF 启用时,写命令追加到 AOF 缓冲区(见 持久化)
复制传播按复制机制把需要同步的写操作传播给从库(见 主从复制)
返回响应把结果编码为 RESP 格式写回客户端

这种设计的价值:Redis 把「持久化」「复制」等能力内建在命令执行流程中,写命令执行后进入传播环节,由 Redis 根据 AOF 是否启用、复制是否配置来决定实际处理,无需命令实现自己操心。这是 Redis 架构简洁的关键设计之一。

5. 慢查询日志 ​

慢查询日志记录执行时间超过阈值的命令,是排查性能问题的第一手资料。它统计的是命令执行阶段的耗时,不包含客户端通信与响应发送等 I/O 时间,因此客户端请求延迟高但慢查询无记录时,还需检查网络与连接处理环节。

bash
# 配置
slowlog-log-slower-than 10000   # 超过 10ms 记录(微秒)
slowlog-max-len 128             # 最多保存 128 条

# 查看
SLOWLOG GET 10                  # 获取最近 10 条慢查询
SLOWLOG LEN                     # 慢查询总数
SLOWLOG RESET                   # 清空

慢查询日志输出示例:

txt
1) 1) (integer) 1           # 日志 ID
   2) (integer) 1712500000  # 时间戳
   3) (integer) 15000       # 耗时(微秒)
   4) 1) "KEYS"             # 命令
      2) "*"
   5) "127.0.0.1:54321"     # 客户端地址
   6) ""                    # 客户端名称
常见慢命令原因替代方案
KEYS *遍历所有 key,O(n)SCAN 分批迭代
HGETALL大 Hash 全量获取HSCAN 或只取需要的字段
SMEMBERS大 Set 全量获取SSCAN 分批迭代
DEL 大 Key同步删除阻塞UNLINK 异步删除
SORT排序开销大业务层排序,或提前用有序集合按 score 维护

慢查询日志是排查性能问题的第一步。如果发现慢命令,优先用替代方案;如果没有替代方案,考虑拆分数据或异步处理。

Released under the MIT License.