目录
1.关于AOF文件的解读
2.实现服务器命令记录
3.客户端命令的属性
3.redis事件的调度和执行
4.服务器的初始化
举个例子 set a1 hello
1.aof里面并不是直接存储set a1 hello
2.文件格式
trackOperationsPerSecond函数和服务器状态中四个ops_sec_开头的 属性有关
struct redisServer {
// ...
//
上一次进行抽样的时间
long long ops_sec_last_sample_time;
//
上一次抽样时,服务器已执行命令的数量
long long ops_sec_last_sample_ops;
// REDIS_OPS_SEC_SAMPLES
大小(默认值为16
)的环形数组,
//
数组中的每个项都记录了一次抽样结果。
long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES];
// ops_sec_samples
数组的索引值,
//
每次抽样后将值自增一,
//
在值等于16
时重置为0
,
//
让ops_sec_samples
数组构成一个环形数组。
int ops_sec_idx;
// ...
};
trackOperationsPerSecond函数每次运行,都会根据 ops_sec_last_sample_time记录的上一次抽样时间和服务器的当前时间, 以及ops_sec_last_sample_ops记录的上一次抽样的已执行命令数量和服 务器当前的已执行命令数量,计算出两次trackOperationsPerSecond调用 之间,服务器平均每一毫秒处理了多少个命令请求,然后将这个平均值 乘以1000,这就得到了服务器在一秒钟内能处理多少个命令请求的估计 值,这个估计值会被作为一个新的数组项被放进ops_sec_samples环形数 组里面
1.name char* 命令的名字
2.proc rediscommandProc 函数指针 指向命令的实现函数
3.arity int 命令参数的个数,用于检查命令请求格式是否正确
4.sflags char* 字符串形式的标识值,这个值记录了命令的属性
5.calls long long 累积执行多少次命令
6.millseconds long long 服务器执行这个所耗时长
关于标志:
举个例子来示例
SET命令的名字为"set",实现函数为setCommand;命令的参数个 数为-3,表示命令接受三个或以上数量的参数;命令的标识为"wm",表 示SET命令是一个写入命令,并且在执行这个命令之前,服务器应该对 占用内存状况进行检查,因为这个命令可能会占用大量内存。
·GET命令的名字为"get",实现函数为getCommand函数;命令的参 数个数为2,表示命令只接受两个参数;命令的标识为"r",表示这是一 个只读命令。
背景:
因为服务器中同时存在文件事件和时间事件两种事件类型,所以服 务器必须对这两种事件进行调度,决定何时应该处理文件事件,何时又 应该处理时间事件,以及花多少时间来处理它们等等
事件的调度和执行由ae.c/aeProcessEvents函数负责,以下是该函数 的伪代码表示:
def aeProcessEvents():
#
获取到达时间离当前时间最接近的时间事件
time_event = aeSearchNearestTimer()
#
计算最接近的时间事件距离到达还有多少毫秒
remaind_ms = time_event.when - unix_ts_now()
#
如果事件已到达,那么remaind_ms
的值可能为负数,将它设定为0
if remaind_ms < 0:
remaind_ms = 0
#
根据remaind_ms
的值,创建timeval
结构
timeval = create_timeval_with_ms(remaind_ms)
#
阻塞并等待文件事件产生,最大阻塞时间由传入的timeval
结构决定
#
如果remaind_ms
的值为0
,那么aeApiPoll
调用之后马上返回,不阻塞
aeApiPoll(timeval)
#
处理所有已产生的文件事件
processFileEvents()
#
处理所有已到达的时间事件
processTimeEvents()
图形示意:
解析:事件的调度和执行规则
1)aeApiPoll函数的最大阻塞时间由到达时间最接近当前时间的时 间事件决定,这个方法既可以避免服务器对时间事件进行频繁的轮询 (忙等待),也可以确保aeApiPoll函数不会阻塞过长时间。
2)因为文件事件是随机出现的,如果等待并处理完一次文件事件 之后,仍未有任何时间事件到达,那么服务器将再次等待并处理文件事 件。随着文件事件的不断执行,时间会逐渐向时间事件所设置的到达时 间逼近,并最终来到到达时间,这时服务器就可以开始处理到达的时间 事件了
3)对文件事件和时间事件的处理都是同步、有序、原子地执行 的,服务器不会中途中断事件处理,也不会对事件进行抢占,因此,不 管是文件事件的处理器,还是时间事件的处理器,它们都会尽可地减少 程序的阻塞时间,并在有需要时主动让出执行权,从而降低造成事件饥 饿的可能性。比如说,在命令回复处理器将一个命令回复写入到客户端 套接字时,如果写入字节数超过了一个预设常量的话,命令回复处理器 就会主动用break跳出写入循环,将余下的数据留到下次再写;另外, 时间事件也会将非常耗时的持久化操作放到子线程或者子进程执行。
4)因为时间事件在文件事件之后执行,并且事件之间不会出现抢占,所以时间事件的实际处理时间,通常会比时间事件设定的到达时间 稍晚一些。
一个Redis服务器从启动到能够接受客户端的命令请求,需要经过 一系列的初始化和设置过程,比如初始化服务器状态,接受用户指定的 服务器配置,创建相应的数据结构和网络连接等等一
1.iniserverConfig函数
初始化server变量的工作由redis.c/initServerConfig函数完成,以下是 这个函数最开头的一部分代码:‘
void initServerConfig(void){
//
设置服务器的运行id
getRandomHexChars(server.runid,REDIS_RUN_ID_SIZE);
//
为运行id
加上结尾字符
server.runid[REDIS_RUN_ID_SIZE] = '\0';
//
设置默认配置文件路径
server.configfile = NULL;
//
设置默认服务器频率
server.hz = REDIS_DEFAULT_HZ;
//
设置服务器的运行架构
server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
//
设置默认服务器端口号
server.port = REDIS_SERVERPORT;
// ...
}
解析:
·设置服务器的运行ID。
·设置服务器的默认运行频率。
·设置服务器的默认配置文件路径。
·设置服务器的运行架构。
·设置服务器的默认端口号。
·设置服务器的默认RDB持久化条件和AOF持久化条件。
·初始化服务器的LRU时钟。
·创建命令表
2.载入配置选项
在启动服务器时,用户可以通过给定配置参数或者指定配置文件来 修改服务器的默认配置。举个例子,如果我们在终端中输入
redis-server --port 10086
那么我们就通过给定配置参数的方式,修改了服务器的运行端口 号。
另外,如果我们在终端中输入
redis-server redis.conf
//并且redis.conf文件中包含以下内容
#
将服务器的数据库数量设置为 32
个
databases 32
#
关闭 RDB
文件的压缩功能
rdbcompression n
那么我们就通过指定配置文件的方式修改了服务器的数据库数量, 以及RDB持久化模块的压缩功能。
服务器在用initServerConfig函数初始化完server变量之后,就会开始载入用户给定的配置参数和配置文件,并根据用户设定的配置,对 server变量相关属性的值进行修改
note1:
不过,如果用户在启动服务器时为选项databases设置了值32,那么 server.dbnum属性的值就会被更新为32,这将使得服务器的数据库数量 从默认的16个变为用户指定的32个。
其他配置选项相关的服务器状态属性的情况与上面列举的port属性 和dbnum属性一样: ·如果用户为这些属性的相应选项指定了新的值,那么服务器就使 用用户指定的值来更新相应的属性。
·如果用户没有为属性的相应选项设置新的值,那么服务器就沿用 之前initServerConfig函数为属性设置的默认值
3.初始化服务器数据机构
在之前执行initServerConfig函数初始化server状态时,程序只创建了 命令表一个数据结构,不过除了命令表之外,服务器状态还包含其他数据结构
·server.clients链表,这个链表记录了所有与服务器相连的客户端的状态结构,链表的每个节点都包含了一个redisClient结构实例。
·server.db数组,数组中包含了服务器的所有数据库。
server.pubsub_channels·用于保存频道订阅信息的,以及用于保存模式订阅信息的server.pubsub_patterns链表。
·用于执行Lua脚本的Lua环境server.lua。
·用于保存慢查询日志的server.slowlog属性。
当初始化服务器进行到这一步,服务器将调用initServer函数,为以 上提到的数据结构分配内存,并在有需要时,为这些数据结构设置或者 关联初始化值。
4.还原数据库状态
在完成了对服务器状态server变量的初始化之后,服务器需要载入 RDB文件或者AOF文件,并根据文件记录的内容来还原服务器的数据库 状态。
根据服务器是否启用了AOF持久化功能,服务器载入数据时所使用 的目标文件会有所不同:
·如果服务器启用了AOF持久化功能,那么服务器使用AOF文件来 还原数据库状态。 ·
相反地,如果服务器没有启用AOF持久化功能,那么服务器使用 RDB文件来还原数据库状态
5.执行事件循环
在初始化的最后一步,服务器将打印出以下日志:
[5244] 21 Nov 22:43:49.084 * The server is now ready to accept connections on port 6
并开始执行服务器的事件循环(loop)。 至此,服务器的初始化工作圆满完成,服务器现在开始可以接受客 户端的连接请求,并处理客户端发来的命令请求了·