前言
Redis 通信协议(RESP)是一种简单、高效、二进制安全的文本协议,核心是首字节标记类型 + 长度前缀 + CRLF 分隔,源码层面由网络 IO、协议解析、命令执行三部分协同完成。以下从协议规范、源码流程、核心函数与关键逻辑逐层解析。
核心设计思想
- 二进制安全:长度前缀 + CRLF,无需转义,支持任意字节数据。
- 解析高效:单线程循环解析,无锁,按长度直接读取,避免逐字符扫描。
- 兼容简单:支持内联协议(如
SET key val)与RESP 协议,自动识别。
原理
整体流程
Redis 基于单线程事件驱动 + I/O 多路复用(epoll/kqueue),请求处理链路:
客户端TCP连接 → acceptTcpHandler → 创建client结构体 → 注册读事件 → readQueryFromClient → processInputBuffer → 解析RESP → processCommand → 执行命令 → 序列化响应 → 写回客户端
源码解读
1. 连接接收(acceptTcpHandler)
// src/networking.c void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) { // 1. 接受TCP连接,获取客户端fd int cfd = accept(fd, (struct sockaddr*)&sa, &salen); // 2. 创建client结构体(存储fd、querybuf、argv等) client *c = createClient(cfd); // 3. 注册读事件:fd可读时触发readQueryFromClient aeCreateFileEvent(el, cfd, AE_READABLE, readQueryFromClient, c); }2. 数据读取(readQueryFromClient)
// src/networking.c void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) { client *c = (client*)privdata; // 1. 扩展querybuf(SDS动态字符串,默认16KB) c->querybuf = sdsMakeRoomFor(c->querybuf, PROTO_IOBUF_LEN); // 2. 从socket读取数据到querybuf末尾 int nread = read(fd, c->querybuf+sdslen(c->querybuf), PROTO_IOBUF_LEN); // 3. 处理缓冲区:解析RESP/内联协议 processInputBuffer(c); }关键:querybuf是 SDS(动态字符串),自动扩容,累积客户端数据,避免分包问题。
3. 协议解析(processInputBuffer)
// src/networking.c void processInputBuffer(client *c) { while (sdslen(c->querybuf) > c->qb_pos) { // 1. 判断协议类型:首字节是* → RESP数组;否则内联协议 if (c->querybuf[c->qb_pos] == '*') { processMultibulkBuffer(c); // 解析RESP数组(主流客户端) } else { processInlineBuffer(c); // 解析内联协议(如telnet) } // 2. 解析完成:调用processCommand执行命令 if (c->argc > 0) { if (processCommand(c) == C_OK) { resetClient(c); // 重置状态,等待下一个命令 } } } // 3. 清理已处理数据 sdsrange(c->querybuf, c->qb_pos, -1); }4. RESP 数组解析核心(processMultibulkBuffer)
客户端命令(如SET key val)序列化为 RESP 数组:*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\nval\r\n,解析逻辑:
// src/networking.c void processMultibulkBuffer(client *c) { char *p = c->querybuf + c->qb_pos; // 1. 读取数组元素个数:*后面的数字(如*3 → 3个元素) if (*p != '*') return; p++; c->multibulklen = strtol(p, &p, 10); // 解析数字 if (*p != '\r') return; p++; // 跳过\r if (*p != '\n') return; p++; // 跳过\n c->qb_pos = p - c->querybuf; // 2. 循环解析每个批量字符串元素 while (c->argc < c->multibulklen) { // 2.1 读取批量字符串长度:$后面的数字(如$3 → 3字节) if (*p != '$') return; p++; c->bulklen = strtol(p, &p, 10); if (*p != '\r') return; p++; if (*p != '\n') return; p++; c->qb_pos = p - c->querybuf; // 2.2 读取内容:长度为bulklen的字节 if (sdslen(c->querybuf) - c->qb_pos < c->bulklen + 2) return; c->argv[c->argc++] = sdsnewlen(p, c->bulklen); // 保存参数 p += c->bulklen + 2; // 跳过内容+\r\n c->qb_pos = p - c->querybuf; } }5. 命令执行(processCommand)
校验。路由。执行Redis命令
根据解析好的 argv[0](命令名),在 Redis 全局命令表中匹配对应的命令结构体 redisCommand。
通过 cmd->proc(c) 执行真正的命令逻辑(setCommand、getCommand、delCommand 等)。
命令执行完毕后,统一封装 RESP 格式结果,写入客户端reply缓冲区,等待网络层发送给客户端。
// src/server.c int processCommand(client *c) { // 1. 查找命令:通过argv[0]在commandTable中匹配 struct redisCommand *cmd = lookupCommand(c->argv[0]); // 2. 执行命令:调用cmd->proc(如setCommand、getCommand) cmd->proc(c); // 3. 序列化响应:将结果转为RESP格式,写入client->reply return C_OK; }6. 响应写回(sendReplyToClient)
- 响应数据存入
client->reply链表,由事件驱动触发写事件,调用sendReplyToClient序列化并写回客户端。 - 示例:
GET key返回$5\r\nhello\r\n,SET key val返回+OK\r\n。
7. 源码关键数据结构(src/server.h)
// 客户端结构体(核心) typedef struct client { int fd; // 客户端socket fd sds querybuf; // 输入缓冲区:存储客户端请求数据 sds *argv; // 命令参数数组(解析后) int argc; // 参数个数 list *reply; // 输出缓冲区:存储待发送响应 int multibulklen; // RESP数组元素个数 int bulklen; // 当前批量字符串长度 // ... 其他状态(认证、数据库、事务等) } client;8. RESP2 vs RESP3(源码差异)
RESP2:5 种类型,无空值类型,用$-1\r\n表示 null,主流客户端默认。
RESP3:新增空值、布尔、浮点数、映射、集合等类型,支持客户端缓存,兼容 RESP2。
源码适配:processMultibulkBuffer扩展支持 RESP3 类型解析,client结构体新增标记位