命令请求的执行过程
- 客户端发送一个set命令到收到回复期间的步骤如下
- 1.客户端向服务端发生命令请求SET KEY VALUE
- 2.服务器接受并处理客户端发来的命令请求,并在相应的数据库中执行操作,并产生命令回复OK
- 3.服务器将命令回复OK发送给客户端
- 4.客户端接收到服务器返回的命令回复ok,并将这个回复打印给客户观看
发送命令请求
-
用户输入命令请求,客户端将请求转换成协议格式然后发送到服务器
image.png
读取命令请求
- 当客户端与服务器之间的链接socket因为客户端的写入而变得可读时,服务器将调用命令请求处理器来执行下列操作:
- 读取socket中协议格式的命令请求,并将其保存到客户端状态(客户端状态保存在服务端)的输入缓冲区
- 服务器对输入缓冲区中的命令请求进行分析,提取出命令请求中包含的命令参数,以及命令参数的个数,然后分别将其保存到客户端状态的argv属性和argc属性
- 调用命令执行器,执行客户端指定的命令
查找命令实现
- 命令执行器要做的第一件事就是根据客户端状态的argv[0]参数命令去命令表里面获取对应的命令,并把该命令保存到客户端状态的cmd属性里面
-
命令表如下
image.png -
其中sflags如下:
image.png -
命令表如下:
image.png -
客户端装状态的cmd指针
image.png
执行命令的预备操作
- 1.检测客户端状态的cmd指针是否指向Null,如果是说明找不到响应命令直接返回错误
- 2.根据redisCommand结构的arity属性检测命令请求所给定的参数个数是否正确,正确就继续执行,否则直接返回,值为负数代表大于等于该值,值为整数代表等于
- 3.检测客户端是否已经通过了身份验证,未通过身份验证的客户端只能执行AUTH命令
- 4.如果服务器还打开了maxmemory功能,那么在执行命令之前,先检查服务器的内存占用情况,并在有需要时候先进行内存回收,然后在执行命令。
- 5.如果上一次执行了BGSAVE命令时出错,且服务器打开了stop-writes-on-bgsave-error功能,且服务器要执行的命令是一个写命令,那么服务器将拒绝执行这个命令
- 6.如果客户端当前正在使用SUBSCRIBE命令订阅频道,或者使用PSUBSCRIBE命令订阅模式,那么服务器只会执行客户端发来的SUBSCRIBE,和PSUBSCRIBE,UNSUBSCRIBE,PUNSUBSCRIBE命令。其他命令都会被拒绝
- 7.如果服务器正在进行数据载入,那么客户端发送的命令必须带有l标识(比如info,shutdown,publish)才会被服务器执行,其他都会被拒绝
- 8.如果服务器因为执行lua脚本而超时并进入阻塞状态,那么服务器只会执行客户端的SHUTDOWN命令和SCRIPT KILL命令,其他命令会被拒绝
- 9.如果客户端正在执行事务,那么服务器只会执行客户端发来的EXEC,DISCARD,MULTI,WATCH四个命令,其他命令都会被放入事务队列中
- 10.如果服务器端打开了监视器功能,那么服务器会将要执行的命令和参数等信息发送给监视器,当完成了以上预备操作之后才会真正执行命令
执行命令的实现函数
- 真正的执行如下,根据client指针选择cmd,然后调用proc函数传入client对象本身
-
产生的回复会被保存在客户端对象的输出缓冲区,之后实现函数还会为客户端的socket关联命令回复处理器,该处理器会将命令回复返回给客户端
image.png
执行命令的后续操作
- 1.如果开启了慢查询,那么慢查询日志模块会检查是否需要为刚刚执行完的命令请求添加一条新的查询日志
- 2.更新rediscommand结构的milliseconds属性(记录耗时),更新calls+1
- 3.如果服务器开启了AOF持久化,那么AOF持久化模块会将刚执行的命令请求写入到AOF缓冲区
- 4.如果有其他从服务器正在复制当前这个服务器,那么服务器将会吧刚刚执行的命令传播给所有从服务器
将命令回复发送给客户端
- 把回复放入输出缓冲区,并为客户端的socket管理一个命令回复处理器,当客户的socket变为可写,服务器就会执行命令回复处理器,将缓冲区内容发送给客户端
- 发送完毕后输出缓冲区即被情况
客户端接受并打印命令回复
image.pngserverCron函数
-
服务器中的该函数默认是每隔100毫秒执行一次,其具体功能如下:
-
1.更新服务器时间缓存:该函数每隔一定时间更新unixtime(秒级别的时间戳)和mstime(毫秒级别的unix时间戳),这两个精度并不是很准
-
2.对于打印日志,更新服务器的LRU时钟,决定是否指向持久化任务,计算服务器上线时间等对精度要求不高的可以直接使用redisServer中的这两个属性
- 对于给key设置过期时间,添加慢查询日志等高精度功能来说,使用的时候还是需要让服务器再次执行系统调用获取最新时间
-
4.redisServer中的lruclock保存了服务器的LRU时钟,这个属性和上面介绍的unitime,mstime属性一样是服务器时间缓存的一种
-
5.lruclock默认每10秒更新一次(serverCron执行),其用来计算键的空转时间
-
6.每个Redis对象都有一个lru属性,这个lru属性保存了对象最后一次被命令访问的时间
-
7.计算空转时间主要是lruclock减去对象的lru属性记录的时间
-
8.更新服务器每秒执行命令的次数,该函数通过会以100毫秒一次的频率去抽样计算,并估算服务器在最近一秒钟处理的命令请求数量
-
9.更新服务器内存峰值记录,redisServer中的stat_peak_memorty记录了内存峰值大小,每次serverCron执行的时候都查询服务器当前的内存数量与stat_peak_memorty进行对比,保存大值。
-
10.处理sigterm信号:在服务器启动的时候会为sigterm信号关联处理器sigtermhandler函数,该函数负责处理服务器在接收到sigterm时候打开服务器状态的shutdown_asap标识
image.png -
11.每次serverCron执行都会检测shutdown_asap属性,并根据值决定是否关闭服务器,1关闭,0不做处理
-
12.每次关闭前会进行RDB持久化操作,所以这也是我们为什么要拦截sigterm信号的原因,不拦截就直接关闭了
-
13.serverCron每次执行都会调用clientsCron函数,该函数主要是检查客户端与服务器之间的链接是否超时,超时就释放这个客户端。如果客户端上一次执行命令请求后输入缓冲区的大小超过一定长度,那么程序会是否客户端当亲的输入缓冲区,并重新创建一个新的输入缓冲区。
-
14.管理数据库资源:serverCron每次执行都会调用databaseCron函数,该函数会删除过期键,并对字典进行收缩操作
-
15.执行被延迟的BGREWRITEAOF,一般我们重新命令会被BGSAVE命令给延迟
-
16.检测持久化操作的运行状态:服务器使用rdb_child_pid属性和aof_child_pid属性来记录执行bgsave和bgrewriteaof命令(AOF就是同步的)的子进程ID
-
17.只要上述两个pid有一个属性值不等于-1,那么程序就会执行一次wait3函数,改函数检测子进程是否有信号发来服务器进程。
-
18如果有信号到达,说明RDB文件以及生产完毕,或者AOF文件以及重写完毕,服务器要进行相应命令的后续操作,如更换RDB或者AOF文件
-
19.如果没有达到就不进行处理,另一方面pid都为-1代表没有持久化操作,那么程序需要执行以下三个检测:查看是否有AOF重写被延迟,如果有就重新启动一个新的AOF重新操作;检测服务器的自动保存条件是否满足,如果满足且此时没有执行其他持久化操作,那么服务器开始一次新的BGSAVE操作(这里不执行AOF重写操作,是因为我们AOF操作有可能被阻塞了,所以这边的检测也包含被阻塞的AOF重写操作);检测服务器设置的AOF重写条件是否满足,如果满足,且没有执行其他持久化操作,那么服务器开始执行AOF重写
image.png
- 20 .serverCron会将AOF缓冲区的内容吸入到AOF文件
- serverCron会关闭那些输出缓冲区大小超出限制的客户端
- 21.增加cronloop是的计数器值(记录了serverCron执行次数),该值目前主要作用就是在执行N此该函数后执行一次指定代码
初始化服务器
-
1,初始化服务器状态结构redisServer,具体是由函数initServerConfig函数执行
image.png -
改函数的主要目的是
image.png
初始化服务器只会就是载入配置选项
- 比如修改服务器的运行端口号
- 修改其他的属性参数
初始化服务器数据结构
- 我们还需要初始化server.clients
- server.db数组
- server.pibsub_channels字典用于保存频道订阅信息
- 初始化用于执行lua脚本的lua环境 server.lua
- 初始化用于保存慢查询日志的属性server.showlog
还原数据库状态
- 看看是否是执行RDB还是aof来还原
- 优先使用AOF
网友评论