那么服务器接收到执行命令后如何工作?服务器内的定时任务如何执行?服务端启动流程如何?这是我们接下来要了解的内容。
一个命令请求从发送到获得回复的过程中,客户端和服务器需要完成一系列操作。
从客户端发送命令到获得回复期间,客户端和服务器共需要
执行以下操作:
Redis服务器的命令请求来自Redis客户端,当用户在客户端中键人一个命令请求时,客户端会将这个命令请求转换成协议格式,然后通过连接到服务器的套接字,将协议格式的命令请求发送给服务器。
当客户端与服务器之间的连接套接字因为客户端的写人而变得可读时,服务器将调用命令请求处理器来执行以下操作:
命令执行器要做的第一件事就是根据客户端状态的argv[o]参数,在命令表(command table)中查找参数所指定的命令,并将找到的命令保存到客户端状态的 cmd属性里面。
服务器已经将执行命令所需的命令实现函数(保存在客户端状态的cmd属性)参数(保存在客户端状态的argv属性)参数个数(保存在客户端状态的argc属性)都收集齐了,但是在真正执行命令之前,程序还需要进行一些预备操作,从而确保命令可以正确、顺利地被执行,这些操作包括:
在前面的操作中,服务器已经将要执行命令的实现保存到了客户端状态的jcmd属性里面,并将命令的参数和参数个数分别保存到了客户端状态的argv属性和arqv属性里面,当服务器决定要执行命令时,它只要执行对应的函数就可以了。
被调用的命令实现函数会执行指定的操作,并产生相应的命令回复,这些回复会被保存在客户端状态的输出缓冲区里面(buf属性和repiy属性),之后实现函数还会为客户端的套接字关联命令回复处理器,这个处理器负责将命令回复返回给客户端。
在执行完实现函数之后,服务器还需要执行一些后续工作:
当以上操作都执行完了之后,服务器对于当前命令的执行到此就告一段落了,之后服务器就可以继续从文件事件处理器中取出并处理下一个命令请求了。
前面说过,命令实现函数会将命令回复保存到客户端的输出缓冲区里面,并为客户端的套接字关联命令回复处理器,当客户端套接字变为可写状态时,服务器就会执行命令回复处理器,将保存在客户端输出缓冲区中的命令回复发送给客户端。
当命令回复发送完毕之后,回复处理器会清空客户端状态的输出缓冲区,为处理下一个命令请求做好准备。
当客户端接收到协议格式的命令回复之后,它会将这些回复转换成人类可读的格式,并打印给用户观看。
Redis服务器中的jservercron函数默认每隔100毫秒执行一次,这个函数负责管理服务器的资源,并保持服务器自身的良好运转。
Redis服务器中有不少功能需要获取系统的当前时间,而每次获取系统的当前时间都需要执行一次系统调用,为了减少系统调用的执行次数,服务器状态中的unixtime属性和mstime属性被用作当前时间的缓存。
因为ServerCron函数默认会以每100毫秒一次的频率更新funixtime属性和mstime属性,所以这两个属性记录的时间的精确度并不高:
服务器状态中的1ruclock属性保存了服务器的LRU时钟,这个属性和上面介绍的unixtime属性、mstime)属性一样,都是服务器时间缓存的一种。
当服务器要计算一个数据库键的空转时间(也即是数据库键对应的值对象的空转时间)程序会用服务器的lruclock属性记录的时间减去对象的lru属性记录的时间,得出的计算结果就是这个对象的空转时间。
serverCron E函数默认会以每10秒一次的频率更新lruclock属性的值,因为这个时钟不是实时的,所以根据这个属性计算出来的LRU时间实际上只是一个模糊的估算值。
ServerCron函数中的trackoperationsPerSecond函数会以每100毫秒一次的频率执行,这个函数的功能是以抽样计算的方式,估算并记录服务器在最近一秒钟处理的命令请求数量,这个值可以通过INFOSuatus命令的instantaneous_ops_per_sec域查看。
服务器状态中的stat_peak_memory属性记录了服务器的内存峰值大小。
每次serverCron函数执行时,程序都会查看服务器当前使用的内存数量,并与stat_peak_memory 保存的数值进行比较,如果当前使用的内存数量比statpeakmemory属性记录的值要大,那么程序就将当前使用的内存数量记录到stat_peak_memory属性
里面。
在启动服务器时,Redis会为服务器进程的SIGTERM信号关联处理器sigtermHandler函数,这个信号处理器负责在服务器接到SIGTERM信号时,打开服务器状态的 shutdown_asap标识。
每次servercron函数运行时,程序都会对服务器状态的shutdown_asap属性进行检查,并根据属性的值决定是否关闭服务器。
服务器在关闭自身之前会进行RDB持久化操作,这也是服务器拦截SIGTERM信号的原因,如果服务器一接到SIGTERM信号就立即关闭,那么它就没办法执行持久化操作了。
serverCron函数每次执行都会调用clientscron函数,clientscron函数会对一定数量的客户端进行以下两个检查:
serverCron函数每次执行都会调用databasesCron函数,这个函数会对服务器中的一部分数据库进行检查,删除其中的过期键,并在有需要时,对字典进行收缩操作。
在服务器执行BGSAVE命令的期间,如果客户端向服务器发来BGREWRITEAOF命令那么服务器会将BGREWRITEAOF命令的执行时间延退到BGSAVE命令执行完毕之后。
服务器的aof_rewrite_scheduled标识记录了服务器是否延退了BGREWRITEAOF命令。
每次serverCron函数执行时,函数都会检查BGSAVE命令或者BGREWRITEAOF命令是否正在执行,如果这两个命令都没在执行,并且aof_rewrite_scheduled属性的值为1,那么服务器就会执行之前被推延的BGREWRITEAOF命令
服务器状态使用rdb_child_pid属性和aof_child_pid属性记录执行BGSAVE命令和BGREWRITEAOF命令的子进程的ID,这两个属性也可以用于检查BGSAVE命令或者BGREWRITEAOF命令是杏正在执行。
如果服务器开启了AOF持久化功能,并且AOF缓冲区里面还有待写人的数据,那么servercron函数会调用相应的程序,将AOF缓冲区中的内容写人到AOF文件里面。
在这一步,服务器会关闭那些输出缓冲区大小超出限制的客户端。
服务器状态的Cronloops属性记录了SerVerCron函数执行的次数。
Redis服务器从启动到能够接受客户端的命令请求,需要经过一系列的初始化和设置过程,比如初始化服务器状态,接受用户指定的服务器配置,创建相应的数据结构和网络连接等等。
初始化服务器的第一步就是创建一个structredisServer类型的实例变量server作为服务器的状态,并为结构中的各个属性设置默认值。初始化server变量的工作由redis.c/initServerconfig函数完成,有没觉得这种初始化方式和我们spring一个样,其实redis中间件归根结底就是一个框架,只是这个框架提供了接口,或者说redis就是个成熟的软件产品。
以下是initServerConfig函数完成的主要工作:
initserverConfig函数设置的服务器状态属性基本都是一些整数、浮点数、或者字符串属性,除了命令表之外,initServerconfig函数没有创建服务器状态的其他数据结构,数据库、慢查询日志、Lua环境、共享对象这些数据结构在之后的步骤才会被创建出来(这不就是懒加载吗)。
当initserverconfig函数执行完毕之后,服务器就可以进人初始化的第二个阶段——载人配置选项。
在启动服务器时,用户可以通过给定配置参数或者指定配置文件来修改服务器的默认配置。
服务器在用initServerConfig函数初始化完server变量之后,就会开始载人用户给定的配置参数和配置文件,并根据用户设定的配置,对server变量相关属性的值进行修改。
如果用户为这些属性的相应选项指定了新的值,那么服务器就使用用户指定的值来更新相应的属性。
如果用户没有为属性的相应选项设置新的值,那么服务器就沿用之前initserverConfig函数为属性设置的默认值。
服务器在载人用户指定的配置选项,并对server状态进行更新之后,服务器就可以进人初始化的第三个阶段-初始化服务器数据结构。
在之前执行initServerconfig函数初始化server状态时,程序只创建了命令表一个数据结构,不过除了命令表之外,服务器状态还包含其他数据结构,比如:
当初始化服务器进行到这一步,服务器将调用initServer函数,为以上提到的数据结构分配内存,并在有需要时,为这些数据结构设置或者关联初始化值。
服务器到现在才初始化数据结构的原因在于,服务器必须先载人用户指定的配置选项,然后才能正确地对数据结构进行初始化。如果在执行initServerconfig函数时就对数据结构进行初始化,那么一旦用户通过配置选项修改了和数据结构有关的服务器状态属性,服务器就要重新调整和修改已创建的数据结构。为了避免出现这种麻烦的情况,服务器选择了将server状态的初始化分为两步进行,initServerConfig函数主要负责初始化一般属性,而initserver区函数主要负责初始化数据结构。
除了初始化数据结构之外,initServer还进行了一些非常重要的设置操作,其中包括:
当initserver函数执行完毕之后,服务器将用ASCII字符在日志中打印出Redis的图标。
在完成了对服务器状态server变量的初始化之后,服务器需要载人RDB文件或者AOF文件,并根据文件记录的内容来还原服务器的数据库状态。
根据服务器是否启用了AOF持久化功能,服务器载人数据时所使用的目标文件会有所不同。
当服务器完成数据库状态还原工作之后,服务器将在日志中打印出载人文件并还原数据库状态所耗费的时长。
在初始化的最后一步,服务器将打印出日志,并开始执行服务器的事件循环(loop)。
至此,服务器的初始化工作圆满完成,服务器现在开始可以接受客户端的连接请求,并处理客户端发来的命令请求了。