redis 服务器负责与各个客户端建立网络连接,处理客户端的各个命令请求。今个就来看看 redis 服务器从启动到接收客户端命令请求这段时间做了哪些准备工作。先看下本地机器运行的 redis 服务堆栈信息:
[root@localhost ~]# ps -ef | grep redis
redis 9270 1 0 2019 ? 2-10:36:23 /usr/bin/redis-server 10.xx.xx.xx:6379
root 31271 21025 0 14:48 pts/1 00:00:00 grep --color=auto redis
[root@localhost ~]# gdb attach 9270
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-94.el7
Copyright (C) 2013 Free Software Foundation, Inc.
...
(gdb) bt
#0 0x00007f41ecc7cd13 in epoll_wait () from /lib64/libc.so.6
#1 0x00007f41edae1130 in aeProcessEvents ()
#2 0x00007f41edae164b in aeMain ()
#3 0x00007f41edade315 in main ()
(gdb)
可以看到 redis 服务是从主方法 main() 开始运行,阻塞在 epoll_wait() 这。其中 main() 方法主要分为以下几个部分:
- 初始化服务器状态结构
- 载入配置选项
- 初始化服务器数据结构
- 还原数据库状态
- 执行事件循环
一、初始化服务器状态结构
初始化服务器的第一步就是初始化 struct redisServer 类型结构体,并定义为 server。初始化 server 变量的工作是 initServerConfig() 方法:
void initServerConfig(void) {
...
getRandomHexChars(server.runid,CONFIG_RUN_ID_SIZE); // 设置服务器的运行 id
server.runid[CONFIG_RUN_ID_SIZE] = '\0'; // 为运行 id 加上结尾字符
server.hz = CONFIG_DEFAULT_HZ; // 设置服务器默认频率
...
server.arch_bits = (sizeof(long) == 8) ? 64 : 32; // 设置服务器运行架构
...
}
部分代码如上,主要工作如下:
- 设置服务器的运行 id;
- 设置服务器的默认运行频率;
- 设置服务器的默认配置文件路径;
- 设置服务器默认端口号;
- 设置服务器默认的 rdb 持久化和 aof 持久化条件;
- 初始化服务器 lru 时钟;
- 创建命令表。
实际上 initServerConfig() 方法设置的服务器状态属性基本都是一些整数、浮点数或字符串属性,除了命令表之外,并没有创建服务器状态的其他数据结构。如数据库、慢查询日志、Lua 环境等在之后的步骤才会被创建。当 initServerConfig() 方法执行后,服务器就进入初始化的第二个阶段了。
二、载入配置选项
在启动服务器时,我们是可以通过给定配置参数或者指定配置文件来修改服务器的默认配置。如:
[root@localhost ~]# /usr/bin/redis-server 10.xxx.xx.xx:6379
这种就是指定启动对应的 ip 地址和 端口号。或者:
[root@localhost ~]# /usr/bin/redis-server redis.conf
这种就是通过指定配置文件的方式启动了,并根据用户的配置,对 server 变量的相关属性进行更新,然后服务器就进入第三个阶段了。
三、初始化服务器数据结构
在第一阶段初始化 server 状态时,程序只创建了命令表这一个数据结构,实际上还包含其他一些数据结构:
- server.clients 链表,该链表记录了与服务器相连的所有客户端的状态,链表中每个节点都包含了一个 redisClient 结构体,也就是客户端实例;
- server.db 数组,该数组中包含了服务器中所有数据库,一般默认为 16 个;
- 用于执行 Lua 脚本的 Lua 环境 server.lua;
- 用于保存慢查询日志的 server.slowlog。
初始化以上数据结构是在 initServer() 方法中进行的,并在需要时,为这些数据结构设置初始值。除此持外,还进行一些其他的重要操作,如:为服务器设置进程信号处理器、创建共享对象、打开服务器监听端口、创建时间事件等等。
服务器之所以才到现在才初始化数据结构是因为,必须等用户载入指定的配置选项,才能正确地对数据结构进行初始化。如果在 initServerConfig() 方法中就对所有的数据结构初始化,那么一旦用户同个配置选项修改了和数据结构有关的服务器状态,服务器就得重新调整和修改已创建的数据结构。为了避免这一状况,服务器就选择了初始化工作分两步就行:initServerConfig() 负责初始化一般属性,initServer() 负责初始化数据结构。
四、还原数据库状态
在完成了服务器状态 server 变量的初始化之后,服务器就需要载入 aof 文件或 rdb 文件来还原数据库状态了。并根据是否启用 aof 持久化功能,载入数据时所使用的目标文件也会有所不同:
- 如果服务器启用了 aof 持久化功能,那么服务器使用 aof 文件来还原数据库状态;
- 否则,使用 rdb 文件还原数据库状态。
五、执行事件循环
事件循环是在 aeMain() 方法中进行的,方法体如下:
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
aeProcessEvents(eventLoop, AE_ALL_EVENTS |
AE_CALL_BEFORE_SLEEP |
AE_CALL_AFTER_SLEEP);
}
}
通过 while 循环可以看到,服务器开始等待客户端连接请求,一旦请求到达,就可以对其进行处理了。
至此, redis 服务器初始化过程就算结束了,欢迎交流。