Redis服务器负责与多个客户端建立网络连接,处理客户端发送的命令请求,在数据库中保存客户端执行命令所产生的数据,并通过资源管理来维持服务器自身的运转
Redis服务器的命令请求来自Redis客户端,当用户在客户端中键入一个命令请求时,客户端会将这个命令请求转换成协议格式,然后通过连接到服务器的套接字,将协议格式的命令请求发送给服务器
当客户端与服务器之间的连接套接字因为客户端的写入而变得可读时,服务器将调用命令请求处理器来执行以下操作:
之后,服务器将通过调用命令执行器来完成执行命令所需的余下步骤,以下几个小节将分别介绍命令执行器所执行的工作
命令执行器要做的第一件事就是根据客户端状态的argv[0]参数,在命令表(command table)中查找参数所指定的命令,并将找到的命令保存到客户端状态的cmd属性里面
命令表是一个字典,字典的键是命令名字,值是redisCommand结构
redisCommand结构的主要属性如图
sflags属性可以使用的标识值,以及这些标识的意义如图
客户端状态下的cmd指针例子如图
到目前为止,服务器已经将执行命令所需的命令实现函数(保存在客户端状态的cmd属性)、参数(保存在客户端状态的argv属性)、参数个数(保存在客户端状态的argc属性)都收集齐了,但是在真正执行命令之前,程序还需要进行一些预备操作,从而确保命令可以正确、顺利地被执行,这些操作包括:
检查客户端状态的cmd指针是否指向NULL
根据客户端cmd属性指向的redisCommand结构的arity 属性,检查命令请求所给定的参数个数是否正确
检查客户端是否已经通过了身份验证
。。。
执行client->cmd->proc(client)
产生的命令回复会被保存
在客户端状态的输出缓冲区里面(buf属性和reply 属性),之后实现函数还会为客户端的套接字关联命令回复处理器,这个处理器负责将命令回复返回给客户端
在执行完实现函数之后,服务器还需要执行一些后续工作:
当客户端套接字变为可写状态时,服务器就会执行命令回复处理器,将保存在客户端输出缓冲区中的命令回复发送给客户端。当命令回复发送完毕之后,回复处理器会清空客户端状态的输出缓冲区
当客户端接收到协议格式的命令回复之后,它会将这些回复转换成人类可读的格式,并打印给用户观看
Redis服务器中的serverCron函数默认每隔100毫秒执行一次,这个函数负责管理服务器的资源,并保持服务器自身的良好运转
struct redisServer {
// ...
time_t unixtime; // 保存了秒级精度的系统当前UNIX 时间戳
long long mstime; // 保存了毫秒级精度的系统当前UNIX 时间戳
unsigned lruclock:22; // 默认每10秒更新一次的时钟缓存,用于计算键的空转(idle )时长
long long ops_sec_last_sample_time; // 上一次进行抽样的时间
long long ops_sec_last_sample_ops; // 上一次抽样时,服务器已执行命令的数量
long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES]; // REDIS_OPS_SEC_SAMPLES 大小(默认值为16 )的环形数组,数组中的每个项都记录了一次抽样结果。
int ops_sec_idx; // ops_sec_samples 数组的索引值,每次抽样后将值自增一,在值等于16 时重置为0 ,让ops_sec_samples 数组构成一个环形数组。
size_t stat_peak_memory; // 已使用内存峰值
int shutdown_asap; // 关闭服务器的标识:值为1时,关闭服务器,值为0时,不做动作。
int aof_rewrite_scheduled; // 如果值为1 ,那么表示有 BGREWRITEAOF 命令被延迟了。
pid_t rdb_child_pid; // 记录执行BGSAVE 命令的子进程的ID:如果服务器没有在执行BGSAVE,那么这个属性的值为-1 /* PID of RDB saving child
pid_t aof_child_pid; // 记录执行BGREWRITEAOF 命令的子进程的ID,如果服务器没有在执行BGREWRITEAOF,那么这个属性的值为-1 /* PID if rewriting process */
int cronloops; // serverCron 函数的运行次数计数器,serverCron 函数每执行一次,这个属性的值就增一
};
serverCron函数默认会以每100毫秒一次的频率更新unixtime属性和mstime属性,所以这两个属性记录的时间的精确度并不高
每个Redis对象都会有一个lru属性,这个lru属性保存了对象最后一次被命令访问的时间:会以每10秒一次的频率更新lruclock属性的值
serverCron函数中的trackOperationsPerSecond函数会以每100毫秒一次的频率执行,这个函数的功能是以抽样计算的方式,估算并记录服务器在最近一秒钟处理的命令请求数量
服务器状态中的stat_peak_memory属性记录了服务器的内存峰值大小
在启动服务器时,Redis会为服务器进程的SIGTERM信号关联处理器sigtermHandler函数,这个信号处理器负责在服务器接到SIGTERM信号时,打开服务器状态的shutdown_asap标识
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函数执行的次数
唯一的作用的执行是每执行serverCron函数N次就执行一次指定代码
if cronloops % N == 0:
# 执行指定代码...
一个Redis服务器从启动到能够接受客户端的命令请求,需要经过一系列的初始化和设置过程,比如初始化服务器状态,接受用户指定的服务器配置,创建相应的数据结构和网络连接等等
初始化服务器的第一步就是创建一个struct redisServer类型的实例变量server作为服务器的状态,并为结构中的各个属性设置默认值。由redis.c/initServerConfig函数完成,其主要工作如下
在启动服务器时,用户可以通过给定配置参数或者指定配置文件来修改服务器的默认配置
创建其他数据结构,服务器必须先载入用户指定的配置选项,然后才能正确地对数据结构进行初始化。执行initServe函数
服务器选择了将server状态的初始化分为两步进行,initServerConfig函数主要负责初始化一般属性,而
initServer函数主要负责初始化数据结构
当initServer函数执行完毕之后,服务器将用ASCII字符在日志中打印出Redis的图标,以及Redis的版本号信息,即Redis图标logo
在完成了对服务器状态server变量的初始化之后,服务器需要载入RDB文件或者AOF文件,并根据文件记录的内容来还原服务器的数据库状态
如果启动了AOF持久化功能,使用AOF文件来还原数据库状态。否则使用RDB文件来还原数据库状态
开始执行服务器的事件循环(loop),接受客户端的连接请求,并处理客户端发来的命令请求
数;3)命令执行器根据参数查找命令的实现函数,然后执行实现函数并得出命令回复;4)服务器将命令回复返回给客户端