Redis设计与实现第14章 -- 服务器 总结(命令执行器 serverCron函数 初始化)
14.1 命令请求的执行过程
一个命令请求从发送到获得回复的过程中,客户端和服务器都需要完成一系列操作。14.1.1 发送命令请求
当用户在客户端中输入一个命令请求的时候,客户端会把这个命令请求转换为协议格式,然后通过连接到服务器的套接字,将协议格式的命令请求发送给服务器。14.1.2 读取命令请求
当客户端与服务器之间的连接套接字因为客户端的写入而变得可读时,服务器将调用命令请求处理器来执行以下操作:- 读取套接字中协议格式的命令请求,并将其保存到客户端状态的输入缓冲区里面
- 对输入缓冲区中的命令请求进行分析,提取出命令请求中包含的命令参数和个数,分别保存到argv/argc属性里面。
- 调用命令执行器,执行客户端指定的命令
14.1.3 命令执行器1:查找命令实现
根据客户端状态的argv[0]参数,在命令表里查找参数所指定的命令,并将找到的命令保存到客户端状态的cmd属性里。命令表是一个字典,字典的键是一个个命令名字,字典的值是一个个redisCommend结构。
主要属性有:name 名字、proc 函数指针、arity 命令参数的个数、sflags 字符串形式的标识值、flags 二进制标识、calls 服务器总共执行了多少次这个命令、millseconds 服务器执行这个命令耗费的总时长
sflags属性可以使用的标识值如下:
- w:写入命令
- r:只读命令
- m:可能占用大量内存,内存紧张的话禁用
- a:管理命令
- p:发布订阅功能的命令
- s:不可以在lua脚本执行
- R:随机命令
- S:排序
- l:可以在服务器载入数据的过程中使用
- t:允许从服务器在带有过期数据时使用的命令
- M:监视器模式下不会自动被传播
14.1.4 命令执行器2:执行预备操作
+ 检查客户端状态的cmd指针是否指向NULL + 根据客户端cmd属性指向的redisCommand结构的arity属性,检查命令请求所给定的参数个数是否正确。 + 检查客户端是否通过了身份认证 + 如果服务器打开了maxmemory功能,执行命令前先检查服务器的内存占用情况,并在有需要时进行内存回收 + 如果服务器上一次执行BGSAVE命令时出错,并且服务器打开了stop-writes-on-bgsave-error功能,而且即将要执行的是写命令,那么服务器将拒绝执行 + 客户端正在用SUBSCRIBE命令订阅频道,或是正在用PSUBSCRIBE命令订阅模式,服务端只会执行客户端发来的SUBSCRIBE PSUBSCRIBE UNSUBSCRIBE PUNSUBSCRIBE 四个命令,其他命令都会被服务器拒绝 + 如果服务器正在进行数据载入,客户端发送的命令必须要带有l标识才会被执行 + 如果服务器因为执行Lua脚本而超时并进入阻塞状态,服务器只会执行客户端发来的SHUTDOWN nosave和SCRIPT KILL命令 + 如果客户端正在执行事务,只会执行EXEC DISCARD MULTI WATCH 四个命令 + 如果服务器打开了监视器功能,将要执行的命令和参数等信息发送给监视器14.1.5 命令执行器3:调用命令实现函数
被调用的命令实现函数会执行指定的操作,并产生相应的命令回复,这些回复会被保存在客户端状态的输出缓冲区里,buf属性和reply属性。14.1.6 命令执行器4:执行后续工作
+ 如果服务器开启了慢查询日志功能,会检查是否需要为刚刚执行完的命令请求添加一条新的慢查询日志 + 根据刚刚执行命令所耗费的时长,更新被执行命令的redisCommand结构的millseconds属性,并将命令的redisCommand结构的calls计数器的值加一 + 如果服务器开启了AOF持久化功能,会将刚刚执行的命令请求写入AOF缓冲区里 + 如果有其他从服务器正在复制当前这个服务器,那么服务器会将刚刚执行的命令传播给所有从服务器14.1.7 将命令回复发送给客户端
命令实现函数将命令回复保存到客户端的输出缓冲区里面,并为客户端的套接字关联命令回复处理器。当客户端套接字变成可写状态的时候,服务器就会执行命令回复处理器,将保存在客户端输出缓冲区的命令回复发送给客户端
14.1.8 客户端接收并打印命令回复
客户端收到协议格式的命令回复后,会转化为人类可读的格式。14.2 serverCron函数
每隔100ms执行一次14.2.1 更新服务器时间缓存
Redis服务器中有不少功能需要获取系统的当前时间,而每次获取系统的当前时间都需要执行一次系统调用,为了减少系统调用的执行次数,服务器状态中的unixtime属性和mstime属性被用作当前时间的缓存这两个属性是100ms更新一次的,所以精确度不高
- 当打印日志、更新服务器的LRU时钟、决定是否执行持久化任务、计算服务器上线时间这种对时间精确度要求不高的功能上才会用
- 对于为键设置过期时间、添加慢查询日志这种需要高精度时间的功能来说,服务器还是会再次执行系统调用
14.2.2 更新LRU时钟
服务器的lruclock属性保存了服务器的LRU时钟,每个redis对象都会有一个lru属性,保存了对象最后一次被命令访问的时间。当服务器计算数据库键的空转时间的时候,会用服务器的lruclock属性记录的时间减去对象lru属性记录的时间。serverCron函数默认以10秒一次的频率更新lruclock属性的值
14.2.3 更新服务器每秒执行命令次数
trackOperationsPerSecond函数以每100ms一次的频率执行,功能是以抽样计算的方式,估算并记录服务器在最后一秒钟处理的命令请求数量。可以通过INFO status命令的instantaneous_ops_per_sec域查看
14.2.4 更新服务器内存峰值记录
服务器状态的stat_peak_memory属性记录了服务器的内存峰值大小每次serverCron函数执行时,服务器都会查看当前使用的内存数量
14.2.5 处理SIGTERM信号
在启动服务器时,Redis会为服务器进程的SIGTERM信号关联处理器sigtermHandler函数,这个信号处理器负责在服务器接到SIGTERM信号时,打开服务器状态的shutdown_asap 标识每次serverCron函数运行时,程序都会对shutdown_asap 属性进行检查,并根据属性的值决定是否关闭服务器。值为1表示关闭服务器。
服务器在关闭自身前会进行RDB持久化操作,这也就是服务器拦截SIGTERM信号的原因
14.2.6 管理客户端资源
调用clientsCron函数,对一定数量的客户端进行检查:- 连接超时,很久没互动
- 上一次执行请求命令后,输入缓冲区的大小超过了一定长度,程序会释放客户端当前的输入缓冲区,重新创建一个默认大小的输入缓冲区
14.2.7 管理数据库资源
databasesCron函数,对服务器中的一部分数据库进行检查,1删除过期键,收缩字典等,详情见第9章14.2.8 执行被延迟的BGREWRITEAOF
在服务器执行BGSAVE命令的期间,如果客户端向服务器发来BGREWRITEAOF命令那么服务器会将 BGREWRITEAOF命令的执行时间延迟到 BGSAVE命令执行完毕之后。服务器的aof_rewrite_scheduled标识记录了服务器是否延迟了BGREWRITEAOF命令每次serverCron函数执行时,函数都会检查BGSAVE命令或者BGREWRITEAOF命令是否正在执行,如果这两个命令都没在执行,并且aof_rewrite_scheduled属性的值为1,那么服务器就会执行之前被推延的BGREWRITEAOF命令。
14.2.9 检查持久化操作的运行状态
服务器状态使用 rdb_child_pid属性和aof_child_pid属性记录执行 BGSAVE 命令和BGREWRITEAOF命令的子进程的ID,这两个属性也可以用于检查BGSAVE命令或者BGREWRITEAOF命令是否正在执行每次serverCron函数执行时,程序都会检查这两个属性的值,只要其中一个属性的值不为-1,程序就会执行一次wait3函数,检查子进程是否有信号发给服务器进程
- 如果有信号到达,那么表示新的RDB文件已经生成完成或是AOF文件已经重写完毕,服务器进行后续的替换操作
- 如果没有信号,不做动作
如果两个属性的值都是-1,说明没有进行持久化操作,检查:
- 查看是否有BGREWRITEAOF被延迟了
- 检查服务器的自动保存条件是否已经被满足,如果条件满足,而且服务器没有在执行其他持久化操作,那么服务器开始一次新的BGSAVE操作
- 检查服务器设置的AOF重写条件是否满足,如果满足并且没有进行其他持久化操作,服务器开始一次新的BHREWRITEAOF操作
14.2.10 将AOF缓冲区文件的内容写入AOF文件
详情见11章14.2.11 关闭异步客户端
输出缓冲区大小超出限制的客户端,详情见13章14.2.12 增加cronloops计数器的值
cronloops属性记录了serverCron函数执行的次数,目前唯一的作用就是在复制模块中实现:每执行serverCron函数N次就执行一次指定代码的功能14.3 初始化服务器
14.3.1 初始化服务器状态结构
创建一个redisServer类型的实例变量server作用服务器的状态,并为结构里的各个属性设置默认值,初始化函数为initServerConfig函数完成,主要工作为:设置服务器的运行ID、默认运行频率、默认配置文件路径、运行架构、默认端口号、默认RDB/AOF持久化条件、LRU时钟、创建命令表14.3.2 载入配置选项
载入用户给定的配置参数和配置文件,并根据用户设定的配置,对server变量相关属性的值进行修改14.3.3 初始化服务器数据结构
initServerConfig函数初始化server状态时,只创建了命令表一个数据结构,还有其他数据结构,比如:- server.clients链表,记录了和服务端相连的客户端的状态结构
- server.db数组,包含了服务器的所有数据库
- 保存频道订阅信息的pubsub_channels字典 保存模式订阅信息的pubsub_patterns链表
- 执行lua脚本的lua环境
- 用户保存慢查询日志的slowlog属性
这些都是在initServer函数里执行的,该函数负责初始化数据结构和设置操作,比如
- 设置进行信号处理器
- 设置共享对象,比如OK回复的字符串对象
- 打开服务器的监听端口,并为监听套接字关联连接应答事件处理器,等待服务器正式运行时接受客户端的连接
- 为serverCron函数创建时间事件,等待服务器正式运行时执行serverCron函数
- 如果AOF持久化功能打开,创建/打开AOF文件
- 初始化后台IO模块,bio
14.3.4 还原数据库状态
完成对服务器状态server变量的初始化之后,服务器需要载入RDB文件或AOF文件并根据文件记录的内容来还原服务器的数据库状态。- 如果开启了AOF持久化功能,用AOF文件还原
- 否则,用RDB文件还原