服务器
命令的完整执行过程
以set key value为例,说明命令的完整执行过程。
发送命令请求
当用户在客户端中键入一个命令请求时,客户端会将这个命令请求转换成协议格式,然后通过连接到服务器的套接字,将协议格式的命令请求发送给服务器:
读取命令请求
连接套接字会因为客户端的写入而变得可读,服务器将调用命令请求处理器来执行:
1、将命令请求放入客户端状态的输入缓冲区中
2、对输出缓冲区中的内容进行分析,提出出参数赋值到客户端状态的argv属性和argc属性中
3、调用命令执行器来执行客户端指定的命令
执行命令:分4步
第一步:在命令表中查找argv[0]对应的命令,这是一个redisCommand结构,这个结构的各个主要属性如下:
其中sflags是标识值,它的意义如下:
以get和set命令为例,它们在命令表中示意图如下:
查找到之后将客户端状态的cmd指针指向该redisCommand结构。因为命令表使用的是大小写无关查找算法,所以在redis中输入命令是不分大小写的,都能找到正确的redisCommand并执行。
第二步:执行预备操作。预备操作包括:
1、检查cmd指针是否为null,如果为null就不执行后续。
2、检查对应的redisCommand结构中的arity属性(命令参数个数),结合真实参数查看能否对应,如果参数不正确就不执行后续步骤。
3、检查客户端是否通过身份验证。
4、如果服务器打开了maxmemory功能,就需要在执行命令之前检查内存是否可用,必要时进行内存回收。
等等
第三步:调用命令的实现函数
此时服务器已经把将要执行命令的实现保存到了客户端状态的cmd属性中,并将命令的参数和参数个数分别保存到了argv和argc中。执行命令产生的命令回复会被保存在客户端状态的输出缓冲区中。
第四步:执行后续工作。主要包括:
1、如果服务器开启了慢查询日志功能,那么慢查询日志模块会检查是否需要为刚刚执行完的命令请求添加一条新的慢查询日志。
2、根据刚刚执行命令所耗费的时长,更新被执行命令的redisCommand结构的milliseconds属性,并将calls计数器加1.
3、如果服务器开启了AOF持久化功能,那么AOF持久化模块会将刚刚执行的命令请求写入到AOF缓冲区中。
4、如果有其他服务器正在复制当前服务器,那么服务器会将刚刚执行的命令传播给所有从服务器。
回复并打印
命令回复存入客户端状态的输出缓冲区后,通过套接字传送给客户端,命令回复发送完毕后回复处理器会清空客户端状态的输出缓冲区,为处理下一个命令请求做好准备。
客户端收到协议格式的命令回复之后,会转换成可读格式打印出来。
serverCron函数的执行
redis中的serverCron函数默认每隔100毫秒就执行一次,接下来对它执行的操作进行比较完整的介绍。
更新服务器时间缓存
redis服务器状态中有unixtime和mstime两个属性,分别以秒级精度和毫秒级精度保存了当前的unix时间戳,serverCron函数默认会每次更新这两个属性,因为频繁获取系统时间需要执行系统调用,所以redis用这两个属性作为服务器时间缓存。
但是它们的精度并不高,因为默认是100毫秒更新一次的,所以服务器只在打印日志、更新服务器LRU时钟、决定是否执行持久化、计算服务器上线时间这类对时间精确度要求不高的功能上。
对于为键设置过期时间、添加慢查询日志这种需要高精确度时间的功能来说,redis还是会执行系统调用获得最准确的当前时间。
更新LRU时钟
服务器状态中还有一种时间缓存名为lruclock,每个对象都有一个lru属性,这个lru属性保存了对象最后一次被命令访问的时间,当服务器要计算一个键的空转时间时,就会用lruclock时间减去lru得到结果。
要求一个键的空转时间,需要执行以下命令:
object idletime key
serverCron函数默认10秒一次的频率更新lruclock,所以空转时间是一个模糊的估计值。
更新服务器每秒执行命令数
serverCron函数每100毫秒估计一次服务器在最近一秒钟内处理的命令请求数量,这个数量可以通过执行下列命令来查看:
info stats
然后会显示很多字段,其中的instantaneous_ops_per_sec字段就是服务器最近一秒内处理的命令请求数量。
redis服务器计算这个结果的逻辑如下:在redisServer结构中,有一个字段记录上一次抽样查询的时间,还有一个字段记录了上一次抽样时服务器已执行命令的数量,每次运行时都会根据这两个字段、服务器当前时间、服务器当前已执行的命令数量,做一个简单计算得出两次调用之间共处理了多少请求,然后将这个值平均到1秒,计算结果会被放入redisServer结构的一个环形数组中,这个环形数组的大小默认是16,每次执行查询时会循环遍历这个数组取平均值最后返回。所以综上所述,这个结果是一个估算值。
更新服务器内存峰值记录
服务器状态redisServer中的stat_peak_memory属性记录了服务器的内存峰值大小,每次执行serverCron函数时,该函数都会查看服务器当前使用的内存数量,然后更新这个最大值。
可以执行下列命令来查看内存峰值记录:
info memory
结果中的used_memory_peak和used_memory_peak_human就是服务器内存峰值。
处理sigterm信号
当服务器收到sigterm信号时,会打开服务器状态的shutdown_asap标识,让其变为1,每次serverCron函数运行时,都会对shutdown_asap标识进行检查,根据属性的值决定是否关闭服务器,如果是1就关闭。
服务器在收到sigterm信号并决定关闭服务器后,会先执行RDB持久化操作,然后再关闭。
管理客户端资源
serverCron函数每次执行都会调用clientsCron函数,这个函数会检查两个方面:
1、如果客户端和服务器之间的连接已经超时(很久都没有互动),那么程序释放这个客户端
2、如果客户端的输入缓冲区大小超过了一定的长度,那么程序会释放输入缓冲区,并重新创建一个默认大小的输入缓冲区,防止过大的输入缓冲区耗费了太多内存
管理数据库资源
serverCron函数每次执行都会调用databasesCron函数,这个函数会对服务器中一部分数据库进行检查,删除其中的过期键,有必要时还会进行字典收缩操作。
延迟执行bgrewriteaof
如果在执行bgsave的时候执行了bgrewriteaof,那么该命令会被延迟到bgsave执行完毕后执行。这个机制也是通过serverCron函数来实现的,具体做法是服务器状态有一个标识,这个标识记录了服务器是否延迟了bgrewriteaof命令,当serverCron函数执行时会检查这个标识,如果bgsave和bgrewriteaof都没有执行,且该标识为1,那么服务器就会执行bgrewriteaof命令。
检查持久化操作的运行状态
在服务器状态中有两个属性记录了执行bgsave和bgrewriteaof命令的子进程ID,如果服务器没有在执行这两个命令,那么这两个属性就会都被置为-1。每次执行serverCron函数时,程序都会检查这两个属性来确定持久化是否在进行。
1、如果这两个属性其中有一个不为-1,程序就会执行wait3函数,检查子进程是否有信号发到服务器进程,如果有信号到达说明新的RDB或AOF文件已经生成完毕,服务器需要进行文件替换操作。如果没有信号说明持久化未完成。
2、如果这两个属性都是-1,说明服务器此时没有进行持久化操作,此时服务器会检查是否有bgrewriteaof被延迟、检查启动持久化操作的条件是否满足、AOF重写条件是否满足,如果满足的话启动新的持久化操作。
将AOF缓冲区的内容写入AOF文件
如果服务器开启了AOF持久化操作,且AOF缓冲区里面有待写入的数据,那么serverCron函数会将AOF缓冲区的内容写入AOF文件。
关闭异步客户端
服务器会关闭那些输出缓冲区大小超出限制的客户端。
增加cronloops计数器的值
这个计数器是服务器状态的一个属性,记录了serverCron函数执行的次数,有些模块需要实现serverCron函数执行一定次数就需要执行的功能,这个时候就会用到cronloops计数器。
初始化服务器
初始化服务器分为几步:
1、初始化服务器状态结构redisServer
2、载入配置选项,我们可以执行下列命令载入配置文件:
redis-server redis.conf
如果没有手动配置,就会载入默认配置
3、初始化服务器数据结构,如clients链表、db数组、创建共享对象、开启监听端口
4、还原数据库状态:如果服务器启用了AOF持久化功能,那么服务器会用AOF文件来还原数据库状态,如果没有开启就会用RDB文件来还原。