-
处理SIGTETM信号
-
管理客户端资源
-
管理数据库的资源
-
执行被延迟的BGREWRITEAOF
-
检查持久化操作的运行状态
-
将AOF缓冲区中的内容写入AOF文件
-
关闭异步客户端
-
增加cronloops计数器的值
serverCron是Redis中的一个周期时间事件,Redis服务器中的serverCron函数是默认每隔100毫秒执行一次,该函数负责管理服务器的资源
更新服务器时间缓存
Redis服务器中有不少命令都是需要获取系统的当前时间的(比如服务器那边判断客户端执行命令的总时长),而每次获取系统的当前时间都需要执行一次系统调用,为了减少系统调用的执行次数,服务器状态中的unixtime属性和mstime属性被用作当前时间的缓存。
struct redisServer(
//…
//保存了秒级精度的系统当前UNIX时间戳
time_t unixtime;
//保存了毫秒级精度的系统当前unix时间戳
long long mstime;
//…
);
因为serverCron函数默认会每100毫秒一次的频率更新unixtime属性和mstime属性(即serverCron的周期),所以这两个属性的时间的精确度其实并不高。
所以Redis只会在一些对于时间精确度其实并不高的操作上会使用这两个属性,但正在一些对于精确很高的操作,还是会直接获取系统时间。
比如,在打印日记,更新服务器的LRU时钟,决定是否执行持久化操作(触发RDB需要判断时间)、计算服务器的上线时间,这一类对时间精确度要求不高的功能上,会使用unixtime或者mstime。
但如果,对于给键设置过期时间、慢添加查询日志这样需要高精度确切时间的功能来说,服务器还是会再次执行系统调用,从而获得最准确的系统当前时间。
更新LRU时钟
LRU时钟也是服务器时间缓存的一种,服务器状态使用lruclock属性对其进行保存
struct
《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
redisServer(
time_t unixtime;
long long mstime;
//lruclock
unsigned lruclock:22;
//。。。
);
每个Redis对象都会有一个lru属性,这个lru属性保存了对象最后一次被命令访问的时间
struct redisObject(
//…
unsigned lru:22;
);
当服务器要计算一个数据库键的空转时间(注意这里是数据库键,也就是对应的键值对对象的空转时间),程序会用服务器的lruclock属性减去对象的lru属性记录的时间,得出的计算结果就是该对象的空转时间。
serverCron函数默认会以每10秒一次的频率更新lruclock属性的值(serverCron函数是100ms执行一次,即0.1秒执行一次,所以执行100次ServerCron才会更新lruclock)。
这是因为lruclock时钟是不现实的,所以根据在这个属性计算出来的LRU时间,实际上只是一个模糊的估算值。
更新服务器每秒执行次数
serverCron函数中的trackOperationsPerSecond函数会以每100毫秒一次的频率执行(即执行一次serverCron函数就发生一次),这个函数的功能是以抽样计算的方式,估算并记录服务器在每一秒钟处理的命令请求数量(这里是进行估算,并不一定准确)
该属性也是在服务器状态中的,属性名为ops_sec_last_sample_time
struct RedisServer(
//时间
time_t unixtime;
long long mstime;
//LRU时钟
unsigned lru:22;
//上一次的抽样时间
long long ops_sec_last_sample_time;
//上一次抽样时,服务器已经执行的命令个数
long long ops_sec_last_sample;
//REDIS_OPS_SEC_SAMPLES大小的环形数组(默认为16)
//数组中的每个元素都是一次抽样结果,即会保留前16次的抽样结果
long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES]
//ops_sec_samples的当前最大索引值
//每次抽样后,该值自增1,然后将抽样结果放入ops_sec_samples数组中
//在值自增后等于16就要重置为0,注意此时并不会将后面的数据清0
//也就是保存了上一组的记录,形成了一个环形数组
int ops_sec_idx;
//。。。
);
trackOperationsPerSecond函数每次运行后,都会根据ops_sec_last_sample_time记录的上一次抽样时间和服务器的当前时间,以及ops_sec_last_sample_ops记录的上一次抽样的已执行命令数量和服务器当前的已执行命令数量,计算出两次trackOperationsPerSecond调用之间,服务器平均每一毫秒处理了多少个命令请求,然后将该平均值乘上1000,这就得到了服务器在一秒钟内能处理多少个命令请求的估计值
即通过服务器当前时间减去ops_sec_last_sample_time,得出了两次trackOperationsPerSecond的抽样时间差,然后使用服务器的已执行命令数量减去上一次抽样的已经执行命令数量,得出了两次调用trackOperationsPerSecond之间的时间间隔总共执行命令数量,然后就可以求出每一秒的平均执行次数了。然后将此次结果记录到ops_sec_samples环形数组中去。
那么在查询这个时候的值时,计算的方法是叠加所有ops_sec_samples里面的记录,然后进行求平均
比如当客户端执行info命令时,服务器就会调用getOperationsPerSecond函数,根据ops_sec_samples环形数组这种的抽样结果,计算出instantaneout_ops_per_sec属性的值
实现代码如下
long long getOperations(void){
int j;
long long sum = 0;
//REDIS_OPS_SEC_SAMPLES是ops_sec_samples数组大小,默认为16
for(j = 0;j < REDIS_OPS_SEC_SAMPLES;j++){
sum += server.sps_sec_samples[j];
}
return sum/REDIS_OPS_SEC_SAMPLES;
}
可以看出,每一秒执行次数,仅仅只是前REDIS_OPS_SEC_SAMPLES次的抽样记录的平均值,是一个估算值,结果并不是很准确。
更新服务器内存峰值记录
服务器状态中,有一个名为stat_peak_memory(peak:山峰)的属性会记录服务器的内存峰值大小
struct redisServer(
//时间
time_t unixtime;
long long ctime;
unsigned lruclock:22;
//服务器每秒执行次数的属性
//上一次抽样的时间
long long ops_sec_stc_samples_time;
//上一次抽样的记录
long long ops_sec_stc_sample;
//结果的存储
long long ops_sec_stc_samples[REDIS_OPS_SEC_SAMPLES];
//数组当前的最大索引
int opc_sec_idx;
//已经使用的内存峰值
size_t stat_peak_memory;
//…
);
每次执行serverCron函数时,程序都是会查看服务器当前使用的内存大小,并且与stat_peak_memory保存的数值进行比较,如果当前使用的内存数量比stat_peak_memory属性记录的值要大时,那么程序就会更新stak_peak_memory属性,更新为当前的使用的内存大小。
总共有三种形式表达内存峰值大小(b,kb,百分比)
处理SIGTETM信号
在启动服务器时,Redis会为服务器进程的SIGTETM信号关联处理器sigtermHandler函数,这个信号处理器负责在服务器接过SIGTERM信号时,打开服务器状态的shutdown_asap标识,该标识的作用是用来确认服务器是否关机的。
首先先来看看sigtermHandler函数
static void sigtermHandler(int sig){
//打印日志
redisLogFromHandler(REDIS_WARNING,“Received …,scheduling shut down …”);
//打开服务器的关闭标识
server.shutdown_asap = 1;
}
每次当severCron函数运行时,程序都会对服务器状态的shutdown_asap属性进行检查,并且会根据属性的值决定是否关闭服务器(如果为1就进行关闭)
typedef struct RedisServer(
//时间
time_t unixtime;
long long ctime;
unsigned lruclock:22;
//更新服务器每秒执行次数的属性
//上一次抽样的时间
long long opc_sec_stc_samples_time;
//上一次抽样的结果
long long opc_sec_stc_sample;
//前面的记录
long long opc_sec_stc_samples[REDIS_OPC_SEC_STC_SAMPLES];
//当前数组的最大索引
int opc_sec_idx;
//内存的使用峰值
size stat_peak_memory;
//shutdown_asap标识
int shutdown_asap;
);
用SIGTETM信号去触发关机,是让Redis可以有时间去进行持久化准备,假如关机不是通过这种方式,而是直接关闭,那么就无法执行持久化操作了。
管理客户端资源
serverCron函数每次执行都会去调用clientsCron函数,clientsCron函数会对一定数量的客户端进行以下两个检查