Chapter 14 - 服务器
Created by : Mr Dk.
2020 / 06 / 07 14:02
Nanjing, Jiangsu, China
命令请求的执行过程
客户端发送命令请求
客户端会将用户输入的命令转换为协议,然后发送给服务器。
服务器读取命令请求
- 服务器从 socket 中读取协议内容,并保存到输入缓冲区中
- 对输入缓冲区中的命令进行协议解析,将参数和参数个数保存到
argv
和argc
中 - 调用相应的命令执行器
命令执行器
命令执行器首先根据 argv[0]
参数,在命令表中查找参数指定的命令,并保存到客户端状态的 cmd
中。
redisCommand
结构体的主要属性:
char *name
命令名redisCommnadProc *proc
函数指针,指向命令的实现函数int arity
命令参数的个数char *sflags
命令属性int flags
对上述属性分析出的二进制 flaglong long calls
服务器执行命令的次数long long milliseconds
服务器执行命令耗费的总时长
在执行命令前,程序还需要进行一些预备操作:
- 检查
cmd
指针是否为NULL
(说明找不到命令的实现) - 检查参数个数
arity
和argc
是否一致 - 检查客户端是否经过了身份验证
- 检查服务器的内存占用情况,必要情况下,对内存进行回收
- 检查服务器是否正在进行数据载入、阻塞、事务等过程,一些命令不允许被执行
接下来,调用命令的实现函数:
client->cmd->proc(client);
命令的回复将会被保存在 client
结构的输出缓冲区中。
执行完命令实现函数后,服务器还要执行一些后续的动作:
- 添加日志
- 统计命令的执行次数与时长
- AOF 持久化
- 如果有从服务器,则刚执行的命令将传播给所有从服务器
之后,服务器将命令回复以协议的形式发送给客户端,客户端对协议进行解析后,向用户展示结果。
serverCron 函数
这个函数默认每 100ms 执行一次,主要是对服务器的资源进行管理,保持服务器自身的良好运转。
更新服务器的时间缓存
为了减少为获取时间而进行系统调用,服务器状态中缓存了时间,精度不高。
更新 LRU 时钟
服务器状态中时间缓存的一种,服务器的 LRU 时钟可用于计算对象或客户端的空闲时间。
更新服务器每秒执行的命令次数
服务器状态中保存了上一次抽样时,服务器的时间以及已执行的命令数量。下一次抽样时,根据两次抽样的时间差、命令数量差,取得这期间每毫秒执行的命令数量,并缓存到一个环形数组中。
struct redisServer {
// ...
long long ops_sec_last_sample_time; // 上一次抽样的时间
long long ops_sec_last_sample_ops; // 上一次抽样时服务器已执行命令的数量
long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES]; // 抽样结果环形数组
int ops_sec_index; // 数组当前索引
// ...
}
更新服务器的内存峰值
如果当前服务器内存使用量大于内存峰值,则更新这个属性。
处理 SIGTERM 信号
Redis 启动后会为 SIGTERM 信号注册一个处理函数,在收到该信号时,打开服务器状态的 shutdown_asap
标志。在 serverCron
函数中,会对这个标志进行检查,决定是否关闭服务器。在关闭服务器之前,服务器会先进行持久化操作。
管理客户端资源
- 客户端与服务器之间的连接已超时 (没有互动),则释放客户端
- 如果客户端的输入缓冲区超过一定的长度,则释放并重新分配一个默认大小的输入缓冲区
管理数据库资源
对服务器中的 一部分 数据库进行检查,删除其中的过期 key。
执行被延迟的 BGWRITEAOF
如果这个标志为 1,且 BGSAVE
和 BGREWRITEAOF
命令都没有在执行,那么执行这个命令。
struct redisServer {
// ...
int aof_rewrite_scheduled;
// ...
}
检查持久化操作的运行状态
检查自动保存或 AOF 重写的条件是否满足,如果满足且服务器没有正在进行持久化操作,则引发新的持久化操作。
将 AOF 缓冲区中的内容写入 AOF 文件。
关闭异步客户端
增加 cronloop 计数器的值
在服务器状态中记录了 serverCron
函数的执行次数。
服务器初始化
初始化服务器状态结构
初始化服务器的第一步 - 创建一个 struct redisServer
作为服务器的状态:
- 设置服务器的运行 ID
- 设置服务器的默认运行频率
- 设置服务器的默认配置文件路径
- 设置服务器的运行架构
- 设置服务器的默认端口号
- 设置服务器的默认 RDB / AOF 持久化条件
- 初始化服务器的 LRU 时钟
- 创建命令表
载入配置选项
由用户指定的覆盖默认配置的选项,对 struct redisServer
中已经初始化的默认值进行修改。
初始化服务器数据结构
在之前的初始化过程中,程序只创建了 命令表,其它的表不能提前创建,否则将无法应用用户指定的配置。在这一步中,用户自定义的配置已经在 struct redisServer
中了,此时可以进行数据结构的初始化:
- 初始化
client
链表 - 初始化
db
数组 (所有的数据库) - 初始化保存频道订阅信息的字典、链表
- 初始化执行 Lua 脚本的 Lua 环境
- 初始化保存慢查询日志的属性
实际上的主要工作就是用
struct redisServer
中的指针去开辟数据结构的内存,并初始化。
另外,做好与网络或磁盘 I/O 相关的一切准备:
- 为服务器设置信号处理器 (比如
SIGTERM
) - 创建共享的对象 (类比 Java 中的 Integer Cache)
- 打开服务器的监听端口,为 socket 关联 connect 处理器
- 为
serverCron
函数创建时间事件 - 如果 AOF 持久化功能开启,那么打开现有的 AOF 文件;否则新建并打开一个 AOF 文件
- 初始化服务器后台的 I/O 模块
还原数据库状态
载入 AOF 或 RDB 文件来还原数据库的状态,并在控制台上打印还原数据库耗费的时长。
执行时间循环
进入到服务器的事件循环中,初始化结束。接下来,服务器可以接受客户端的连接请求并执行命令了。