初始化服务器
初始化状态结构
初始化服务器的第一步就是创建一个struct redisServer类型的实例变量server作为服务器的状态,并为结构中的各个属性设置默认值。
初始化server变量的工作由redis.c/initServerConfig函数完成,以下是这个函数最开头的一部分代码:
void initServerConfig(void){
// 设置服务器的运行id
getRandomHexChars(server.runid,REDIS_RUN_ID_SIZE);
// 为运行id加上结尾字符
server.runid[REDIS_RUN_ID_SIZE] = '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;
// ...
}
';
// 设置默认配置文件路径
server.configfile = NULL;
// 设置默认服务器频率
server.hz = REDIS_DEFAULT_HZ;
// 设置服务器的运行架构
server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
// 设置默认服务器端口号
server.port = REDIS_SERVERPORT;
// ...
}
以下是initServerConfig函数完成的主要工作:
-
设置服务器的运行ID。
-
设置服务器的默认运行频率。
-
设置服务器的默认配置文件路径。
-
设置服务器的运行架构。
-
设置服务器的默认端口号。
-
设置服务器的默认RDB持久化条件和AOF持久化条件。
-
初始化服务器的LRU时钟。
-
创建命令表。
initServerConfig函数设置的服务器状态属性基本都是一些整数、浮点数、或者字符串属性,除了命令表之外,initServerConfig函数没有创建服务器状态的其他数据结构,数据库、慢查询日志、Lua环境、共享对象这些数据结构在之后的步骤才会被创建出来。
载入配置选项
当initServerConfig函数执行完毕之后,服务器就可以进入初始化的第二个阶段——载入配置选项。
设置方式
- 终端中输入参数:
redis-server --port 10086
- 终端指定配置文件路径:
redis-server redis.conf
服务器在用initServerConfig函数初始化完server变量之后,就会开始载入用户给定的配置参数和配置文件,并根据用户设定的配置,对server变量相关属性的值进行修改。
-
如果用户为这些属性的相应选项指定了新的值,那么服务器就使用用户指定的值来更新相应的属性。
-
如果用户没有为属性的相应选项设置新的值,那么服务器就沿用之前initServerConfig函数为属性设置的默认值。
初始化数据结构
服务器在载入用户指定的配置选项,并对server状态进行更新之后,服务器就可以进入初始化的第三个阶段——初始化服务器数据结构。
在之前执行initServerConfig函数初始化server状态时,程序只创建了命令表一个数据结构,不过除了命令表之外,服务器状态还包含其他数据结构,比如:
- server.clients链表,这个链表记录了所有与服务器相连的客户端的状态结构,链表的每个节点都包含了一个redisClient结构实例。
- server.db数组,数组中包含了服务器的所有数据库。
- 用于保存频道订阅信息的server.pubsub_channels字典,以及用于保存模式订阅信息的server.pubsub_patterns链表。
- 用于执行Lua脚本的Lua环境server.lua。
- 用于保存慢查询日志的server.slowlog属性。
服务器到现在才初始化数据结构的原因在于
- 服务器必须先载入用户指定的配置选项,然后才能正确地对数据结构进行初始化。如果在执行initServerConfig函数时就对数据结构进行初始化,那么一旦用户通过配置选项修改了和数据结构有关的服务器状态属性,服务器就要重新调整和修改已创建的数据结构。
- 为了避免出现这种麻烦的情况,服务器选择了将server状态的初始化分为两步进行,initServerConfig函数主要负责初始化一般属性,而initServer函数主要负责初始化数据结构。
除了初始化数据结构之外,initServer还进行了一些非常重要的设置操作,其中包括:
-
为服务器设置进程信号处理器。
-
创建共享对象:这些对象包含Redis服务器经常用到的一些值,比如包含”OK”回复的字符串对象,包含”ERR”回复的字符串对象,包含整数1到10000的字符串对象等等,服务器通过重用这些共享对象来避免反复创建相同的对象。
-
打开服务器的监听端口,并为监听套接字关联连接应答事件处理器,等待服务器正式运行时处理客户端的连接。
-
为serverCron函数创建时间事件,等待服务器正式运行时执行serverCron函数。
-
如果AOF持久化功能已经打开,那么打开现有的AOF文件,如果AOF文件不存在,那么创建并打开一个新的AOF文件,为AOF写入做好准备。
-
初始化服务器的后台I/O模块(bio),为将来的I/O操作做好准备。
当initServer函数执行完毕之后,服务器将用ASCII字符在日志中打印出Redis的图标,以及Redis的版本号信息:
还原数据库状态
在完成了对服务器状态server变量的初始化之后,服务器需要载入RDB文件或者AOF文件,并根据文件记录的内容来还原服务器的数据库状态。
根据服务器是否启用了AOF持久化功能,服务器载入数据时所使用的目标文件会有所不同:
-
如果服务器启用了AOF持久化功能,那么服务器使用AOF文件来还原数据库状态。
-
相反地,如果服务器没有启用AOF持久化功能,那么服务器使用RDB文件来还原数据库状态。
当服务器完成数据库状态还原工作之后,服务器将在日志中打印出载入文件并还原数据库状态所耗费的时长:
[5244] 21 Nov 22:43:49.084 * DB loaded from disk: 0.068 seconds
执行事件循环
在初始化的最后一步,服务器将打印出以下日志:
[5244] 21 Nov 22:43:49.084 * The server is now ready to accept connections on port 6379
并开始执行服务器的事件循环(loop)。
至此,服务器的初始化工作圆满完成,服务器现在开始可以接受客户端的连接请求,并处理客户端发来的命令请求了。
小结
一个命令请求从发送到完成主要包括以下步骤:
- 客户端将命令请求发送给服务器;
- 服务器读取命令请求,并分析出命令参数;
- 命令执行器根据参数查找命令的实现函数,然后执行实现函数并得出命令回复;
- 服务器将命令回复返回给客户端。
serverCron函数默认每隔100毫秒执行一次,它的工作主要包括
- 更新服务器状态信息,
- 处理服务器接收的SIGTERM信号,
- 管理客户端资源和数据库状态,
- 检查并执行持久化操作等等。
服务器从启动到能够处理客户端的命令请求需要执行以下步骤:
- 初始化服务器状态;
- 载入服务器配置;
- 初始化服务器数据结构;
- 还原数据库状态;
- 执行事件循环。
本文地址:https://blog.csdn.net/weixin_43934607/article/details/109591584