serverCron函数
Redis服务器中的serverCron函数默认每隔100毫秒执行一次,这个函数负责管理服务器的资源,并保持服务器自身的良好运转。
更新服务器时间缓存
Redis服务器中有不少功能需要获取系统的当前时间,而每次获取系统的当前时间都需要执行一次系统调用,为了减少系统调用的执行次数,服务器状态中的unixtime属性和mstime属性被用作当前时间的缓存:
struct redisServer {
// ...
// 保存了秒级精度的系统当前UNIX时间戳
time_t unixtime;
// 保存了毫秒级精度的系统当前UNIX时间戳
long long mstime;
// ....
};
因为serverCron函数默认会以每100毫秒一次的频率更新unixtime属性和mstime属性,所以这两个属性记录的时间的精确度并不高:
- 1.服务器只会在打印日志、更新服务器的LRU时钟、决定是否执行持久化任务、计算服务器上线时间(uptime)这类对事件精确度要求不高的功能上"使用unixtime属性和mstime属性"。
- 2.对于为键设置过期事件、添加慢查询日志这种需要高精确度时间的功能来说,服务器还是会再执行系统调用,从而获得最准确的系统当前时间
更新LRU时钟。
服务器状态中的lruclock属性保存了服务器的LRU时钟,这个属性和unixtime属性、mstime属性一样,都是服务器时间缓存的一种:
struct redisServer {
// ...
// 默认每10秒更新一次的时钟缓存
// 用于计算键的空转(idle)时长
unsigned lruclock:22;
// ...
};
每个Redis对象都会有一个lru属性,这个lru属性保存了对象最后一次被命令访问的时间:
typedef struct redisObject {
// ...
unsigned lru:22;
//...
} robj;
当服务器要计算一个数据库键的空转时间(也即是数据库键对应的值对象的空转时间),程序会用服务器的lruclock属性记录的时间减去对象的lru属性记录的时间,得出的计算结果就是这个对象的空转时间:
127.0.0.1:6379> SET msg "hello world"
OK
# 等待一小段时间
127.0.0.1:6379> OBJECT IDLETIME msg
(integer) 13
# 等待一阵子
127.0.0.1:6379> OBJECT IDLETIME msg
(integer) 19
# 访问msg键的值
127.0.0.1:6379> GET msg
"hello world"
# 键处于活跃状态,空转时长为2
127.0.0.1:6379> OBJECT IDLETIME msg
(integer) 2
serverCron函数默认会以每10秒一次的频率更新lruclock属性的值,因为这个时钟不是实时的,所以根据这个属性计算出来的LRU时间实际上只是一个模糊的估算值。lruclock时钟的当前值可以通过INFO server命令的
lru_clock域查看:
```c
127.0.0.1:6379> info server
# Server
redis_version:3.0.504
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:a4f7a6e86f2d60b3
redis_mode:standalone
os:Windows
arch_bits:64
multiplexing_api:WinSock_IOCP
process_id:5512
run_id:87544bbfd0b6ddf6c7168be02719f23b94c97a96
tcp_port:6379
uptime_in_seconds:95307
uptime_in_days:1
hz:10
lru_clock:581331
config_file:E:\redis\redis.windows-service.conf
更新服务器每秒执行命令次数
serverCron函数中的trackOperationPerSecond函数会以每100毫秒一次的频率执行,这个函数的功能是以抽样计算的方式,估算并记录服务器在最近一秒钟处理的命令请求数量,这个值可以通过INFO stats命令 的
instantaneous_ops_per_sec域查看:
127.0.0.1:6379> info stats
# Stats
total_connections_received:3
total_commands_processed:16
instantaneous_ops_per_sec:0
total_net_input_bytes:542
total_net_output_bytes:2417
instantaneous_input_kbps:0.00
instantaneous_output_kbps:0.00
rejected_connections:0
sync_full:0
sync_partial_ok:0
sync_partial_err:0
expired_keys:0
evicted_keys:0
keyspace_hits:1
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:69502
migrate_cached_sockets:0
上面命令的结果显示中,在最近的一秒钟内,服务器没有处理命令。
trackOperationPerSecond函数和服务器状态中四个ops_sec开头的属性有关:
struct redisServer {
// ...
// 上一次进行抽样的时间
long long ops_sec_last_sample_time;
// 上一次抽样时,服务器已执行命令的数量
long long ops_sec_last_sample_ops;
// REDIS_OPS_SEC_SAMPLE 大小(默认值为16)的环形数组
long long ops_sec_sample[REDIS_OPS_SEC_SAMPLES];
// ops_sec_sample数组的索引值
// 每次抽样后将值增一
// 再值等于16时重置为0
// 让ops_sec_samples数组构成一个唤醒数组
int opts_sec_ids;
// ...
}
trackOperationsPerSecond函数
每次运行,都回根据ops_sec_last_sample_time记录的上一次抽样时间和服务器的当前时间,以及ops_sec_last_sample_ops记录的上一次抽样的已执行命令数量和服务器当前的已执行命令数量,计算出两次trackOperationsPerSecond调用之间,服务器平均每一毫秒处理了多少个命令请求,然后将这个平均值
乘以1000,这就得到了服务器在一秒钟内处理多少个命令请求的估计值,这个估计值会被作为一个新的数组项被放进ops_sec_samples唤醒数组里面。当客户端执行INFO命令时,服务器就会调用getOperationsPerSecond函数,根据ops_sec_samples唤醒数组中的抽样结果,计算出instantaneous_ops_per_sec属性的值,
getOperationsPerSecond函数
以下是getOperationsPerSecond函数的实现代码:
long long getOperationsPerSecond(void) {
int j;
long long sum = 0;
// 计算所有取样值综合
for (j = 0; j < REDIS_OPS_SEC_SAMPLES; j++) {
sum += server.ops_sec_samples[j];
}
// 计算取样的平均值
return sum / REDIS_OPS_SEC_SAMPLES;
}
根据getOperationsPerSeoncd函数的定义可以看出,instantaneous_ops_per_sec
属性的值是通过计算最近REDIS_OPS_SEC_SAMPLES次取样的平均值来计算得出的,它只是一个估算值。