bigywater.tech
Redis · 105

Redis 协议解析

Redis 的协议简单到可以用 telnet 手敲。但服务器如何处理不规矩的输入?半条命令会怎样?多大的请求会被拒绝?这篇用原始 socket 发送各种正确与畸形的请求,把服务器的每一种回应记下来。

2 种格式
multibulk(标准)与 inline(telnet 风格,支持引号)
回错误后断开
Protocol error 一类的畸形请求,服务器发 -ERR 后关闭连接
两个上限
大 bulk 同时受 proto-max-bulk-len 与 client-query-buffer-limit 约束

两种请求格式

实测 · 本机执行输出
  multibulk PING                     发送: b'*1\r\n$4\r\nPING\r\n'                                              收到: +PONG⏎
  inline PING                        发送: b'PING\r\n'                                                          收到: +PONG⏎
  inline SET / GET(引号)               发送: b'SET k "hello world"\r\nGET k\r\n'                                  收到: +OK⏎$11⏎hello world⏎

标准的 multibulk 请求以 *<参数个数> 开头,每个参数 $<长度> + 内容。Inline 格式是直接的一行文本,以空白分隔参数,支持双引号和单引号,方便 telnet 调试。

协议错误

实测 · 本机执行输出
  multibulk 长度不是数字                   发送: b'*abc\r\n'                                                          收到: -ERR Protocol error: invalid multibulk length⏎<对端关闭连接>
  multibulk 长度为负                     发送: b'*-5\r\n'                                                           收到: 
  bulk 长度不是数字                        发送: b'*1\r\n$x\r\nfoo\r\n'                                               收到: -ERR Protocol error: invalid bulk length⏎<对端关闭连接>
  multibulk 元素前缀不是 $                 发送: b'*1\r\n:3\r\nfoo\r\n'                                               收到: -ERR Protocol error: expected '$', got ':'⏎<对端关闭连接>
  bulk 长度超过 proto-max-bulk-len       发送: b'*2\r\n$3\r\nSET\r\n$2000000\r\n'                                   收到: -ERR Protocol error: invalid bulk length⏎<对端关闭连接>
  bulk 长度声明 10 实际 3 + 结尾错            发送: b'*1\r\n$10\r\nabc\r\n'                                              收到: 
  multibulk 元素数超过 1024*1024          发送: b'*2000000\r\n'                                                      收到: 
  inline 引号未闭合                       发送: b'SET k "abc\r\n'                                                    收到: -ERR Protocol error: unbalanced quotes in request⏎<对端关闭连接>

服务器对畸形输入的处理不是忽略,而是回一条 -ERR Protocol error: … 并关闭连接(因为字节流已经失去同步,无法继续解析)。其中没有回复的几种情形(负的 multibulk 长度、bulk 数据没到齐、元素数很大)不是错误——服务器在等更多数据,或把负长度当作空请求跳过。

不完整请求

实测 · 本机执行输出
  发送到 "he" 就停: (0.5 秒内没有任何回复)  → 补上 "llo\r\n" 之后: b'+OK\r\n'

半条命令不会触发任何回复;补全后才执行。这是 TCP 字节流上的增量解析:一条命令可以被拆成多个包,服务器把未解析的部分放在客户端的查询缓冲里。

查询缓冲上限

实测 · 本机执行输出
  发送 3600000 字节后:ConnectionResetError;读:ConnectionResetError
  服务器日志里的相关行: ['Closing client that reached max query buffer length: id=18 addr=127.0.0.1:58805 laddr=127.0.0.1:8090 fd=9 name= age=0 idle=0 flags=A db=0 sub=0 psub=0 ssub=0 multi=-1 wat']

client-query-buffer-limit 限制单个客户端未处理的输入缓冲。我把它设成 2 MB、把 proto-max-bulk-len 放宽到 100 MB,发一个 5 MB 的 bulk:在约 3.6 MB 时连接被关闭,服务器日志是 Closing client that reached max query buffer length。要存大 value,两个参数都要调。

大 bulk 的解析速度

实测 · 本机执行输出
     1 MB:SET 往返 1 ms(930 MB/s)
    10 MB:SET 往返 9 ms(1164 MB/s)
   100 MB:SET 往返 61 ms(1631 MB/s)
   500 MB:SET 往返 438 ms(1143 MB/s)

SET 一个 1 / 10 / 100 / 500 MB 的 value,往返 1 / 9 / 61 / 438 ms,约每秒 1 GB(本机回环,客户端一次发送)。协议解析本身不是瓶颈;大 value 的问题在于它占用主线程的时间。

分步演示:服务器如何读一条请求

Redis 协议解析
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Redis 8.10.2 独立实例(本机回环端口,save 关闭),redis-cli 风格输出由自写的 RESP 客户端打印;计时用 time.perf_counter,数字每次运行会有小幅抖动。原始 socket;proto-max-bulk-len 与 client-query-buffer-limit 为演示临时调小,「⏎」表示 \r\n
    • 没有验证:未认证连接的参数个数限制、inline 请求的长度上限(64 KB 一说没有测)、TLS 下的解析、RESP3 下的差异(第 104 篇)
    京ICP备2026067502号-1京公网安备11010502063487号