RESP协议解析需先用Peek(1)识别前导符号:+/-/:/$/*/对应简单字符串、错误、整数、批量字符串、数组,$-1和-1表示null,$N后读N字节+\r\n,N递归解析N项,须处理半包粘包及io.ErrUnexpectedEOF。

RESP 协议解析的核心是识别前导符号
Redis 使用 RESP(REdis Serialization Protocol)传输数据,不是 JSON 也不是纯文本。客户端必须能根据首字节区分类型:+(简单字符串)、-(错误)、:(整数)、$(批量字符串)、*(数组)。忽略这个规则,直接 bufio.ReadString('\n') 读取会卡死或解析错乱。
实操建议:
- 用
bufio.Reader.Peek(1)先看第一个字节,再决定后续读法 -
$-1表示 null bulk string,*-1表示 null array,必须支持,否则GET missing_key会解析失败 - 批量字符串长度后跟
\r\n,内容本身不包含\r\n,但可能含任意二进制字节——别用strings.Split处理
写入命令必须严格遵循 RESP 编码格式
Go 标准库没有内置 RESP 编码器,手写时容易漏掉 \r\n 或错用换行。比如 SET key value 对应的 wire format 是:
*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
常见错误:
- 用
\n替代\r\n→ Redis 返回Protocol error: expected '\r\n'. - 字符串长度没转成十进制 ASCII(如写
$3而非$0x3)→ 实际必须是$3,不是十六进制 - 数组项数量写错:比如传了 4 个参数却写
*3→ Redis 静默截断或报错
连接与读写需处理半包和粘包
网络 IO 不保证一次 Read() 拿到完整 RESP 帧。例如一个 *2\r\n\r\nGET\r\n\r\nfoo\r\n 可能被分两次到达。不能假设 reader.ReadString('\n') 就能拿到完整行——因为 $ 类型的内容里可能有换行(虽然 Redis 命令值一般不含,但协议允许)。
正确做法:
- 对
$N类型,先读出N,再调用io.ReadFull(reader, buf[:N])确保读满 - 对
*N类型,递归解析 N 个子项,每项都按自身前导符处理 - 用
net.Conn.SetReadDeadline()防止阻塞,尤其在解析$后等待内容时
支持基本命令只需实现 5 种响应类型解析
不需要一开始就兼容所有 Redis 类型。先跑通 SET/GET/PING 就够验证链路。对应 RESP 解析分支只有:
-
+→ 读到\r\n前为止,作为string(如+OK\r\n) -
-→ 同上,但存为error(如-ERR unknown command 'xxx'\r\n) -
:→ 读整数,如:1000\r\n→int64(1000) -
$N→ 若N == -1返回nil;否则读N字节 +\r\n -
*N→ 若N == -1返回nil;否则递归解析N个项,合并为[]interface{}
真正容易被忽略的是错误传播:比如 $ 后读不满 N 字节,io.ReadFull 返回 io.ErrUnexpectedEOF,这必须原样返回,不能吞掉或转成空字符串——否则上层无法区分“键不存在”和“网络中断”。










