0%

redis解析命令

Redis 命令解析流程:以 SET 命令为例

Redis 对命令的解析和执行是其核心功能之一,涉及协议解析、参数处理、命令路由等多个环节。本文以 SET 命令为例,详细解析 Redis 如何接收、解析并执行命令。

命令解析的整体流程

Redis 处理客户端命令的流程可概括为:
接收命令数据 → 协议解析 → 填充 client 结构体 → 路由到对应命令函数 → 执行命令 → 返回结果

其中,client 结构体是贯穿全程的核心载体,记录了命令的参数、客户端状态、连接信息等关键数据。

client 结构体:命令解析的核心载体

client 结构体是 Redis 描述客户端连接和命令请求的核心数据结构,与命令解析相关的关键字段如下:

字段名 作用
argc 命令参数的数量(包含命令名本身)。例如 SET key value 中,argc=3
argv 命令参数数组(robj* 类型,robj 是 Redis 通用对象结构)。argv[0] 为命令名(如 "SET"),argv[1...] 为命令参数(如 "key""value")。
querybuf 接收客户端命令的缓冲区(sds 类型,动态字符串),存储未解析的原始命令数据。
qb_pos querybuf 中已解析的位置,用于增量解析命令。
cmd 指向当前执行的命令结构体(redisCommand 类型),包含命令的处理函数、参数个数限制等元信息。

SET 命令解析:从代码看细节

setCommand 函数为例,解析 Redis 如何处理 SET 命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void setCommand(client *c) {
robj *expire = NULL; // 过期时间对象(如 EX 30 中的 30)
int unit = UNIT_SECONDS; // 时间单位(秒/毫秒,默认秒)
int flags = OBJ_NO_FLAGS; // 命令标志(如 NX、XX 等选项)

// 解析扩展参数(如 EX、PX、NX、XX 等)
if (parseExtendedStringArgumentsOrReply(c,&flags,&unit,&expire,COMMAND_SET) != C_OK) {
return; // 解析失败,返回错误
}

// 对 value 进行编码优化(如转为整数编码,节省内存)
c->argv[2] = tryObjectEncoding(c->argv[2]);

// 调用通用设置函数执行核心逻辑
setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
}

步骤 1:解析扩展参数(parseExtendedStringArgumentsOrReply

SET 命令支持丰富的扩展选项(如 SET key value EX 30 NX),该函数的作用是解析这些选项:

  • 选项解析:识别 EX(过期时间秒)、PX(过期时间毫秒)、NX(仅键不存在时设置)、XX(仅键存在时设置)等。
  • 参数校验:检查选项格式是否正确(如 EX 后必须跟数字),若错误则向客户端返回提示(通过 reply 机制)。
  • 结果存储:将解析出的过期时间(expire)、时间单位(unit)、标志(flags,如 NX 对应 CLIENT_SET_NX 标志)存入变量,供后续使用。

步骤 2:值对象编码优化(tryObjectEncoding

c->argv[2]SET 命令的 value 参数(robj 类型)。tryObjectEncoding 函数对其进行编码优化:

  • value 是字符串且可转为整数(如 "123"),则转为整数编码(OBJ_ENCODING_INT),节省内存。
  • 若字符串较短(如长度 ≤ 44 字节),则用 embstr 编码(紧凑存储);否则用 raw 编码。
  • 优化后的值对象替换原 c->argv[2],后续操作使用优化后的对象。

步骤 3:执行核心逻辑(setGenericCommand

setGenericCommandSET 命令的核心实现函数,负责:

  • 根据 flags 检查键是否存在(NX/XX 逻辑)。
  • 若设置了过期时间(expire 非空),计算绝对过期时间(当前时间 + 过期秒数 / 毫秒数)。
  • keyvalue 存入当前数据库(c->dbredisDb 类型,包含键空间哈希表)。
  • 若有过期时间,设置键的过期属性(setExpire 函数)。
  • 向客户端返回结果(如 OK)。

命令解析的前置步骤:从字节流到 argc/argv

setCommand 是命令执行的最后阶段,在此之前,Redis 需完成从原始字节流到 argc/argv 的解析:

接收命令数据(querybuf

客户端通过 TCP 发送命令(遵循 RESP 协议),例如 SET key value 的 RESP 格式为:

1
*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
  • *3 表示有 3 个参数,$3 表示下一个参数长度为 3(即 "SET"),以此类推。

Redis 将这些字节存入 client->querybuf

协议解析(生成 argc/argv

Redis 的事件循环(aeMain)会触发读事件处理器(readQueryFromClient),解析 querybuf

  • 调用 processInputBuffer 函数,按 RESP 协议解析 querybuf 中的数据。
  • 解析出的参数数量存入 client->argc,参数对象(robj)存入 client->argv
  • 解析完成后,qb_pos 更新为已解析的位置,未解析的数据保留在 querybuf 中。

命令路由(找到 setCommand

解析出 argc/argv 后,Redis 通过 argv[0](命令名)查找对应的 redisCommand 结构体:

1
2
3
4
5
6
7
// redisCommand 结构体示例(简化)
struct redisCommand {
char *name; // 命令名(如 "set")
redisCommandProc *proc; // 命令处理函数(如 setCommand)
int arity; // 参数个数要求(如 -3 表示至少 3 个参数)
// ... 其他元信息(如标志、ACL 权限等)
};

Redis 维护一个命令表(commands 字典),键为命令名(小写),值为 redisCommand 结构体。找到后,将 client->cmd 指向该结构体,随后调用 cmd->proc(client)(即 setCommand(c))。

欢迎关注我的其它发布渠道