继续我们上一节的讨论。服务器启动了,客户端也发送命令了。接下来,就要到服务器“表演”的时刻了。

1 服务器处理

服务器读取到命令请求后,会进行一系列的处理。

1.1 读取命令请求

当客户端与服务器之间的套接字因客户端的写入变得可读时,服务器将调用命令请求处理器执行以下操作:

  1. 读取套接字中的命令请求,并将其保存到客户端状态的输入缓冲区。
  2. 对输入缓冲区的命令请求进行分析,提取出命令请求中包含的命令参数及参数个数,然后分别将参数和参数个数保存到客户端状态的 argv 属性和 argc 属性里。
  3. 调用命令执行器,执行客户端指定的命令。

上面的 SET 命令保存到客户端状态的输入缓存区之后,客户端状态如图 4。

之后,分析程序将对输入缓冲区中的协议进行分析,并将得出的结果保存的客户端的 argv 和 argc 属性中,如图 5 所示:

之后,服务器将通过调用命令执行器来完成执行命令的余下步骤。

1.2 查找命令实现

命令执行器要做的第一件事就是根据 argv[0] 参数,在命令表(commandtable)中查找参数所指定的命令,并将找到的命令保存到 cmd 属性中。

命令表是一个字典,字典的键是一个个命令名称,比如 "SET"、"GET" 等。而字典的值则是一个个 redisCommand 结构,每个 redisCommand 结构记录了 Redis 命令的实现信息。源码如下:

# server.h/redisCommand
struct redisCommand {
char *name; // 命令名称。如 "SET"
redisCommandProc *proc; // 对应函数指针,指向命令的实现函数。比如 SET 对应的 setCommand 函数
int arity; // 命令参数的格个数。用来检查命令请求的格式是否合法。
// 要注意的命令的名称也是一个参数。像我们上面的 SET KEY VALUE 命令,实际上有三个参数。
char *sflags; // 字符串形式的标识值。记录了命令的属性。
int flags; // 对 sflags 标识分析得出的二进制标识,由程序自动生成。检查命令时,实际上使用的是此字段
redisGetKeysProc *getkeys_proc; // 指针函数,通过此方法来指定 key 的位置。
int firstkey; // 第一个 key 的位置
int lastkey; // 最后一个 key 的位置
int keystep; // key 之间的间距
long long microseconds, calls; // 命令的总调用时间及调用次数
};

另外,对于 sflags 属性,可使用的标识值及含义如下表:

标识 意义 带有此标识的命令
w 这是一个写入命令,可能会修改数据库 SET、RPUSH、DEL 等
r 这是一个只读命令,不会修改数据库 GET、STRLEN 等
m 此命令可能会占用大量内存,执行器需先检查内存使用情况,如果内存紧缺就禁止执行此命令 SET、APPEND、RPUSH、SADD 等
a 这是一个管理命令 SAVE、BGSAVE 等
p 这是一个发布与订阅功能的命令 PUBLISH、SUBSRIBE 等
s 这个命令不可以在 lua 脚步中使用 BPOP、BLPOP 等
R 这是一个随机命令。对于相同的数据集和相同的参数,返回结果可能不同 SPOP、SRANDMEMBER 等
S 当在 lua 脚步中使用此命令时,对返回结果进行排序,使得结果有序 SINTER、SUNION 等
l 这个命令可以在服务器载入数据的过程中使用 INFO、PUBLISH 等
t 这个命令允许在从库有过期数据时使用 SLAVEOF、PING 等
M 这个命令在监视模式下,不会被自动传播 EXEC
k 集群模式下,如果对应槽点标记位“导入”,则接受此命令 restore-asking
F 这个命令在程序执行时应该立刻执行 SETNX、GET 等

命令表结构如图 6:

对于我们上面的 SET KEY VALUE 命令,当程序以图 5 中的 argv[0] 作为输入,在命令表中进行查找时,命令表返回 "set" 键对于的 redisCommand 结构,客户端状态的 cmd 指针会指向这个 redisCommand 结构。如图 7 所示:

要注意的是,对于 Redis 而言,命令名字的大小写不影响命令表的查找结果,也就是命令名称不区分大小写。执行 SET 和 set、Set 将获得相同结果。

1.3 执行预备操作

到目前为止,服务器已经将执行命令所需要的命令实现函数(客户端 cmd 属性)、参数(客户端 argv 属性)、参数个数(客户端 argc 属性)都初始化完毕。但在真正执行命令之前,程序还会进行一些预备操作,保证命令可以正确、顺利的被执行。预备操作包括:

  1. 检查客户端的 cmd 指针是否指向 NULL,如果是的话,说明用户输入的命令名称没有对应的函数,服务器将不再执行后续操作,并向客户端返回一个错误。
  2. 根据客户端 cmd 属性指向的 redisCommand 结果的 arity 属性,检查命令请求所给定的参数个数是否正确。
  3. 检查客户端是否已经通过了身份验证。未通过身份验证的客户端只能执行 AUTH 命令。否则,将会向客户端返回一个错误。
  4. 如果服务器打开了 maxmemory 功能,在执行命令之前,会先检查服务器的内存占用情况,并在有需要时进行内存回收,从而使得接下来的命令可以顺利执行。如果内存回收失败,将不再执行后续步骤,向客户端返回一个错误。
  5. 如果服务器上一次执行 BGSAVE 命令时出错,并且服务器打开了 stop-writes-on-bgsave-error 功能,而将要执行的命令是一个写命令,那么服务器将拒绝执行这个鞋命令,并向客户端返回一个错误。
  6. 如果客户端正在用 SUBSCRIBEPSUBSCRIBE 命令订阅频道或模式,那么服务器只会执行客户端发来的 SUBSCRIBEPSUBSCRIBEUNSUBSCRIBEPUNSUBSCRIBE 四个命令,其它命令都会被拒绝。
  7. 如果服务器正在进行数据载入,那么客户端发送是命令必须带有 l 标识才会被服务器执行。
  8. 如果客户端正在执行事务,那么服务器只会执行 EXECDISCARDMULTIWATCH 四个命令,其他命令都会被放进事务队列中。
  9. 如果服务器打开了监视器功能,那么服务器会将要执行的命令和参数等信息发送给监视器。

当完成了以上预备操作之后,服务器就开始真正的执行命令了。

要注意的是,上面列出的预备操作只是服务器在单机模式下的检查操作。如果在复制或者集群模式下,预备操作还会更多。

1.4 调用命令的实现函数

在前面的操作中 ,服务器已经将要执行的命令实现、参数、参数个数保存在客户端结构中。

对于我们上面的 SET KEY VALUE 命令,图 8 包含了命令实现、参数和参数个数结构:

当服务器决定要执行命令时,只要执行以下语句即可:

// client 是指向客户端状态的指针。server.c/call()
client->cmd->proc(client);

上面的执行语句实际上就是调用 setCommand 函数(t_string.c)。

被调用的命令实现函数会执行指定的操作,并产生相应的命令回复,这些回复会被保存在客户端状态的输出缓冲区中(bug 属性 和 reply 属性),之后实现函数会为客户端的套接字关联命令回复处理器,由命令回复处理器返回给客户端。

回到我们的示例,setCommand(client) 将产生一个 "+OK\r\n" 回复,这个回复被保存在客户端的 buf 属性中。如图 9 所示:

1.5 执行后续工作

实现函数执行完后,服务器还会执行一些后续工作,主要包括:

  1. 如果服务器开启了 slow-log 功能,那么慢查询日志模块将会检查是否需要将刚执行的命令添加到慢查询日志。
  2. 更新 redisCommand 结构的 milliseconds 和 calls 属性。
  3. 如果服务器开启了 AOF 持久化功能,那么 AOF 持久化模块会将刚刚执行的命令请求写入到 AOF 缓冲区中。
  4. 如果有其它服务器正在复制当前这个服务器,那么服务器将会把刚刚执行的命令传播给所有从服务器。

以上后续操作执行完毕后,一条执行命令也就执行完成了。服务器可以继续处理后续的命令。

1.6 将命令回复发送给客户端

上面过程中,命令实现函数会将命令回复保存到客户端的输出缓冲区中,并为客户端的套接字关联命令回复处理器。当客户端套接字变为可写状态时,服务器就会执行命令回复处理器,将命令回复发送给客户端。

当命令回复发送完毕后,回复处理器会情况客户端的输出缓冲区,为处理下一个命令请求做好准备。

以图 9 所示的客户端状态为例,当客户端的套接字变为可写状态时,命令回复处理器会将协议格式的命令回复 "+OK\r\n" 发送给客户端。

1.7 源码解读

命令处理请求,函数调用堆栈信息如图 3-7-1:

命令回复,函数调用堆栈信息如图 3-7-2:

2 客户端接收并打印回复

客户端接收到命令回复之后,会将回复转换成我们可读的格式,并打印在屏幕上(对于 redis-cli 客户端),如图 10 所示。

至此,我们走完了从发起一个命令请求,到收到回复的所有过程。对于我们最开始提的问题,服务器如何响应客户端请求,你有答案了吗?

总结

  1. 服务器通过 networking.c/readQueryFromClient() 读取和执行对应命令。
  2. 服务器通过 networking.c/writeToClient() 将命令回复发送给客户端。

跟着大彬读源码 - Redis 3 - 服务器如何响应客户端请求?(下)的更多相关文章

  1. 跟着大彬读源码 - Redis 2 - 服务器如何响应客户端请求?(上)

    上次我们通过问题"启动服务器,程序都干了什么?",跟着源码,深入了解了 Redis 服务器的启动过程. 既然启动了 Redis 服务器,那我们就要连上 Redis 服务干些事情.这 ...

  2. 跟着大彬读源码 - Redis 4 - 服务器的事件驱动有什么含义?(上)

    众所周知,Redis 服务器是一个事件驱动程序.那么事件驱动对于 Redis 而言有什么含义?源码中又是如何实现事件驱动的呢?今天,我们一起来认识下 Redis 服务器的事件驱动. 对于 Redis ...

  3. 跟着大彬读源码 - Redis 1 - 启动服务,程序都干了什么?

    一直很羡慕那些能读 Redis 源码的童鞋,也一直想自己解读一遍,但迫于 C 大魔王的压力,解读日期遥遥无期. 相信很多小伙伴应该也都对或曾对源码感兴趣,但一来觉得自己不会 C 语言,二来也不知从何入 ...

  4. 跟着大彬读源码 - Redis 6 - 对象和数据类型(下)

    继续撸我们的对象和数据类型. 上节我们一起认识了字符串和列表,接下来还有哈希.集合和有序集合. 1 哈希对象 哈希对象的可选编码分别是:ziplist 和 hashtable. 1.1 ziplist ...

  5. 跟着大彬读源码 - Redis 7 - 对象编码之简单动态字符串

    Redis 没有直接使用 C 语言传统的字符串表示(以空字符串结尾的字符数组),而是构建了一种名为简单动态字符串(simple dynamic string)的抽象类型,并将 SDS 用作 Redis ...

  6. 跟着大彬读源码 - Redis 8 - 对象编码之字典

    目录 1 字典的实现 2 插入算法 3 rehash 与 渐进式 rehash 总结 字典,是一种用于保存键值对的抽象数据结构.由于 C 语言没有内置字典这种数据结构,因此 Redis 构建了自己的字 ...

  7. 跟着大彬读源码 - Redis 9 - 对象编码之 三种list

    目录 1 ziplist 2 skiplist 3 quicklist 总结 Redis 底层使用了 ziplist.skiplist 和 quicklist 三种 list 结构来实现相关对象.顾名 ...

  8. 跟着大彬读源码 - Redis 10 - 对象编码之整数集合

    [TOC] 整数集合是 Redis 集合键的底层实现之一.当一个集合只包含整数值元素,并且元素数量不多时,Redis 就会使用整数集合作为集合键的底层实现. 1 整数集合的实现 整数集合是 Redis ...

  9. 跟着大彬读源码 - Redis 5 - 对象和数据类型(上)

    相信很多人应该都知道 Redis 有五种数据类型:字符串.列表.哈希.集合和有序集合.但这五种数据类型是什么含义?Redis 的数据又是怎样存储的?今天我们一起来认识下 Redis 这五种数据结构的含 ...

随机推荐

  1. SQLServer 不允许保存更改的解决办法

    启动SQL Server  Management Studio 工具菜单----选项----Designers(设计器)----阻止保存要求重新创建表的更改 取消勾选即可.

  2. js 跨域访问 获取验证码图片 获取header 自定义属性

    1.net core web api 后端 /// <summary> /// 图形验证码 /// </summary> [HttpGet] public IActionRes ...

  3. 基于Spring开发

    1. XML Schema 1.1 最简单的标签 一个最简单的标签,形式如: <bf:head-routing key="1" value="1" to= ...

  4. [shell]流程控制----case语句

    Shell case语句为多选择语句.可以用case语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令.case语句格式如下: case 值 in 模式1) command1 command2 ...

  5. Android Contact 导入导出 vcf格式(不依赖第三方库)

    Android sdk 支持vcf处理的(忘记最低哪个版本开始支持的了,可以查一查) 备注:此代码来自Stack Overflow(原地址找不到了,o(╥﹏╥)o) 1. 导出联系人为vcf格式 Co ...

  6. .NET框架中系列专业技术术语和缩写

    CIL (Common Intermediate Language) 公共中间语言 : CIL最初是随着.NET由微软一起发布的,之前也叫MSIL(Microsoft Intermediate Lan ...

  7. Qt Resource系统概说(资源压缩不压缩都可以)

    什么是Qt Resource系统?简单的说,就是在可执行程序中存储binary文件,而且还是与平台无关的. 与Qt Resource系统密切相关的有三个法宝,分别是qmake.rcc.QFile. q ...

  8. sqlserver/mysql按天,按小时,按分钟统计连续时间段数据

    文 | 子龙 有技术,有干货,有故事的斜杠青年 一,写在前面的话 最近公司需要按天,按小时查看数据,可以直观的看到时间段的数据峰值.接到需求,就开始疯狂百度搜索,但是搜索到的资料有很多都不清楚,需要自 ...

  9. 基于Google Earth Engine的全国地表温度反演

    国内研究landsat8温度反演的人员很多,但是现有算法一般都是一景为例子,进行开展. 这有一个局限性,当研究的尺度很大时,就需要比较大的运算量了,例如全省温度,全国温度,全球温度,当然大家可能会说, ...

  10. Python 爬虫从入门到进阶之路(九)

    之前的文章我们介绍了一下 Python 中的正则表达式和与爬虫正则相关的 re 模块,本章我们就利用正则表达式和 re 模块来做一个案例,爬取<糗事百科>的糗事并存储到本地. 我们要爬取的 ...