跟着大彬读源码 - Redis 3 - 服务器如何响应客户端请求?(下)
继续我们上一节的讨论。服务器启动了,客户端也发送命令了。接下来,就要到服务器“表演”的时刻了。
1 服务器处理
服务器读取到命令请求后,会进行一系列的处理。
1.1 读取命令请求
当客户端与服务器之间的套接字因客户端的写入变得可读时,服务器将调用命令请求处理器执行以下操作:
- 读取套接字中的命令请求,并将其保存到客户端状态的输入缓冲区。
- 对输入缓冲区的命令请求进行分析,提取出命令请求中包含的命令参数及参数个数,然后分别将参数和参数个数保存到客户端状态的 argv 属性和 argc 属性里。
- 调用命令执行器,执行客户端指定的命令。
上面的 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 属性)都初始化完毕。但在真正执行命令之前,程序还会进行一些预备操作,保证命令可以正确、顺利的被执行。预备操作包括:
- 检查客户端的 cmd 指针是否指向 NULL,如果是的话,说明用户输入的命令名称没有对应的函数,服务器将不再执行后续操作,并向客户端返回一个错误。
- 根据客户端 cmd 属性指向的 redisCommand 结果的 arity 属性,检查命令请求所给定的参数个数是否正确。
- 检查客户端是否已经通过了身份验证。未通过身份验证的客户端只能执行
AUTH命令。否则,将会向客户端返回一个错误。 - 如果服务器打开了 maxmemory 功能,在执行命令之前,会先检查服务器的内存占用情况,并在有需要时进行内存回收,从而使得接下来的命令可以顺利执行。如果内存回收失败,将不再执行后续步骤,向客户端返回一个错误。
- 如果服务器上一次执行
BGSAVE命令时出错,并且服务器打开了 stop-writes-on-bgsave-error 功能,而将要执行的命令是一个写命令,那么服务器将拒绝执行这个鞋命令,并向客户端返回一个错误。 - 如果客户端正在用
SUBSCRIBE和PSUBSCRIBE命令订阅频道或模式,那么服务器只会执行客户端发来的SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE、PUNSUBSCRIBE四个命令,其它命令都会被拒绝。 - 如果服务器正在进行数据载入,那么客户端发送是命令必须带有
l标识才会被服务器执行。 - 如果客户端正在执行事务,那么服务器只会执行
EXEC、DISCARD、MULTI、WATCH四个命令,其他命令都会被放进事务队列中。 - 如果服务器打开了监视器功能,那么服务器会将要执行的命令和参数等信息发送给监视器。
当完成了以上预备操作之后,服务器就开始真正的执行命令了。
要注意的是,上面列出的预备操作只是服务器在单机模式下的检查操作。如果在复制或者集群模式下,预备操作还会更多。
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 执行后续工作
实现函数执行完后,服务器还会执行一些后续工作,主要包括:
- 如果服务器开启了 slow-log 功能,那么慢查询日志模块将会检查是否需要将刚执行的命令添加到慢查询日志。
- 更新
redisCommand结构的 milliseconds 和 calls 属性。 - 如果服务器开启了 AOF 持久化功能,那么 AOF 持久化模块会将刚刚执行的命令请求写入到 AOF 缓冲区中。
- 如果有其它服务器正在复制当前这个服务器,那么服务器将会把刚刚执行的命令传播给所有从服务器。
以上后续操作执行完毕后,一条执行命令也就执行完成了。服务器可以继续处理后续的命令。
1.6 将命令回复发送给客户端
上面过程中,命令实现函数会将命令回复保存到客户端的输出缓冲区中,并为客户端的套接字关联命令回复处理器。当客户端套接字变为可写状态时,服务器就会执行命令回复处理器,将命令回复发送给客户端。
当命令回复发送完毕后,回复处理器会情况客户端的输出缓冲区,为处理下一个命令请求做好准备。
以图 9 所示的客户端状态为例,当客户端的套接字变为可写状态时,命令回复处理器会将协议格式的命令回复 "+OK\r\n" 发送给客户端。
1.7 源码解读
命令处理请求,函数调用堆栈信息如图 3-7-1:

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

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

至此,我们走完了从发起一个命令请求,到收到回复的所有过程。对于我们最开始提的问题,服务器如何响应客户端请求,你有答案了吗?
总结
- 服务器通过
networking.c/readQueryFromClient()读取和执行对应命令。 - 服务器通过
networking.c/writeToClient()将命令回复发送给客户端。
跟着大彬读源码 - Redis 3 - 服务器如何响应客户端请求?(下)的更多相关文章
- 跟着大彬读源码 - Redis 2 - 服务器如何响应客户端请求?(上)
上次我们通过问题"启动服务器,程序都干了什么?",跟着源码,深入了解了 Redis 服务器的启动过程. 既然启动了 Redis 服务器,那我们就要连上 Redis 服务干些事情.这 ...
- 跟着大彬读源码 - Redis 4 - 服务器的事件驱动有什么含义?(上)
众所周知,Redis 服务器是一个事件驱动程序.那么事件驱动对于 Redis 而言有什么含义?源码中又是如何实现事件驱动的呢?今天,我们一起来认识下 Redis 服务器的事件驱动. 对于 Redis ...
- 跟着大彬读源码 - Redis 1 - 启动服务,程序都干了什么?
一直很羡慕那些能读 Redis 源码的童鞋,也一直想自己解读一遍,但迫于 C 大魔王的压力,解读日期遥遥无期. 相信很多小伙伴应该也都对或曾对源码感兴趣,但一来觉得自己不会 C 语言,二来也不知从何入 ...
- 跟着大彬读源码 - Redis 6 - 对象和数据类型(下)
继续撸我们的对象和数据类型. 上节我们一起认识了字符串和列表,接下来还有哈希.集合和有序集合. 1 哈希对象 哈希对象的可选编码分别是:ziplist 和 hashtable. 1.1 ziplist ...
- 跟着大彬读源码 - Redis 7 - 对象编码之简单动态字符串
Redis 没有直接使用 C 语言传统的字符串表示(以空字符串结尾的字符数组),而是构建了一种名为简单动态字符串(simple dynamic string)的抽象类型,并将 SDS 用作 Redis ...
- 跟着大彬读源码 - Redis 8 - 对象编码之字典
目录 1 字典的实现 2 插入算法 3 rehash 与 渐进式 rehash 总结 字典,是一种用于保存键值对的抽象数据结构.由于 C 语言没有内置字典这种数据结构,因此 Redis 构建了自己的字 ...
- 跟着大彬读源码 - Redis 9 - 对象编码之 三种list
目录 1 ziplist 2 skiplist 3 quicklist 总结 Redis 底层使用了 ziplist.skiplist 和 quicklist 三种 list 结构来实现相关对象.顾名 ...
- 跟着大彬读源码 - Redis 10 - 对象编码之整数集合
[TOC] 整数集合是 Redis 集合键的底层实现之一.当一个集合只包含整数值元素,并且元素数量不多时,Redis 就会使用整数集合作为集合键的底层实现. 1 整数集合的实现 整数集合是 Redis ...
- 跟着大彬读源码 - Redis 5 - 对象和数据类型(上)
相信很多人应该都知道 Redis 有五种数据类型:字符串.列表.哈希.集合和有序集合.但这五种数据类型是什么含义?Redis 的数据又是怎样存储的?今天我们一起来认识下 Redis 这五种数据结构的含 ...
随机推荐
- 零元学Expression Blend 4 - Chapter 1 缘起
原文:零元学Expression Blend 4 - Chapter 1 缘起 本来都使用Adobe相关工具从事设计工作的我,因缘际会下,接触到了Expression Blend 4,让我完全的对微软 ...
- visual studio 2017 添加MSDN
原文:visual studio 2017 添加MSDN 1.启动VS2017的安装软件,点击更改,进行MSDN帮助组件添加安装. 2.在单个组件中找到"Help Viewer", ...
- C# ACCESS 向含有自动编码字段表中添加记录提示“查询值的数目与目标字段中的数目不同”
引发错误的SQL语句如下: sqlStr = "insert into tb_ReportLog values('" + DevSite + "','" + D ...
- 实现Qt日志功能并输出到文件(使用qInstallMsgHandler安装customMessageHandler)good
原文 http://www.cppblog.com/lauer3912/archive/2011/04/10/143870.html 一.基本分类:qDebug : 调试信息提示qWarning: 一 ...
- How To Compile Qt with Visual Studio
How To Compile Qt with Visual Studio FEBRUARY 1, 2011 This post is a step-by-step guide on how to co ...
- elasticsearch.yml 常用参数说明
cluster.name: 指定node所属的cluster. node.name: 本机的hostname. node.master: 是否可以被选举为master节点.(true or false ...
- NSmutableArray 的实现原理机制
古老的C数组: 优点:查询速度很快,直接通过下表找到对应的值 缺点:修改.删除数据很慢,需要移动基于所有的其他的元素 插入和删除一个元素,需要移动后面所有的元素 改进的目标:实现快速增加,删除 N ...
- [2017.02.21-22] 《Haskell趣学指南 —— Learning You a Haskell for Great Good!》
{- 2017.02.21-22 <Haskell趣学指南 -- Learning You a Haskell for Great Good!> 学习了Haskell的基本语法,并实现了一 ...
- EF Power Tool 代码生成器 反向生成
大致来说,这个工具有这样几个功能: 1) 按照现有数据库结构,生成Code First POCO class.DbContext class和相应的mapping class. 2) 以designe ...
- 发布一个Django项目
一.部署环境准备,准备python3和虚拟环境解释器,virtualenvwrapper 1.修改python3的环境变量 PATH=/opt/python36/bin:/usr/local/sbin ...