曹工说Redis源码(5)-- redis server 启动过程解析,以及EventLoop每次处理事件前的前置工作解析(下)

曹工说Redis源码(5)-- redis server 动过程解析,eventLoop处理事件前的准备工作(下) 文章导航

Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读。由于我用c也是好几年以前了,些许错误在所难免,希望读者能不吝指出。

曹工说Redis源码(1)-- redis debug环境搭建,使用clion,达到和调试java一样的效果

曹工说Redis源码(2)-- redis server 动过程解析及简单c语言基础知识补充

曹工说Redis源码(3)-- redis server 启动过程完整解析(中)

曹工说Redis源码(4)-- 通过redis server源码来理解 listen 函数中的 backlog 参数

本讲主题

本讲将延续第三讲的主题,将启动过程的主体讲完。为了保证阅读体验,避免过于突兀,可以先阅读第三讲。本讲,主要讲解余下的部分:

创建pid文件

加载rdb、aof,获取数据

运行事件处理器,准备处理事件,EventLoop每次处理事件前的前置工作

创建pid文件

pid,也就是进程id,以后台模式运行时,redis会把自己的pid,写入到一个文件中,默认的文件路径和名称为:/var/run/redis.pid。

配置文件可配:

# When running daemonized, Redis writes a pid file in /var/run/redis.pid by # default. You can specify a custom pid file location here. pidfile /var/run/redis.pid

这部分代码非常简洁:

void createPidFile(void) { // 1 FILE *fp = fopen(server.pidfile, "w"); if (fp) { // 2 fprintf(fp, "%d\n", (int) getpid()); // 3 fclose(fp); } }

1,打开文件,这里的pidfile就是前面的文件名,/var/run/redis.pid,配置文件可以对其修改。模式为w,表示将对其写入。

2,调用pid,获取当前进程的pid,写入该文件描述符

3,关闭文件。

加载rdb、aof

在启动时,会检查aof和rdb选项是否打开,如果打开,则会去加载数据,这里要注意的是,redis总是先查看是否有 aof 开关是否打开;打开的话,则直接使用 aof;

如果 aof 没打开,则去加载 rdb 文件。

void loadDataFromDisk(void) { // 记录开始时间 long long start = ustime(); // AOF 持久化已打开 if (server.aof_state == REDIS_AOF_ON) { // 尝试载入 AOF 文件 if (loadAppendOnlyFile(server.aof_filename) == REDIS_OK) // 打印载入信息,并计算载入耗时长度 redisLog(REDIS_NOTICE, "DB loaded from append only file: %.3f seconds", (float) (ustime() - start) / 1000000); // AOF 持久化未打开 } else { // 尝试载入 RDB 文件 if (rdbLoad(server.rdb_filename) == REDIS_OK) { // 打印载入信息,并计算载入耗时长度 redisLog(REDIS_NOTICE, "DB loaded from disk: %.3f seconds", (float) (ustime() - start) / 1000000); } } }

加载的过程,现在来讲,不太合适,比如以aof为例,aof文件中存储了一条条的命令,加载 aof 文件的过程,其实就会在进程内部创建一个 fake client(源码中就是这样命名,也就是一个假的客户端),来一条条地发送 aof 文件中的命令进行执行。

这个命令执行的过程,现在讲会有点早,所以 aof 也放后面吧,讲了命令执行再回头看这块。

事件循环结构体讲解

核心流程如下:

// 1 aeSetBeforeSleepProc(server.el, beforeSleep); // 2 aeMain(server.el);

先看2处,这里传入server这个全局变量中的el属性,该属性就代表了当前事件处理器的状态,其定义如下:

// 事件状态 aeEventLoop *el;

el,实际就是EventLoop的简写;结构体 aeEventLoop,里面维护了:当前使用的多路复用库的函数、当前注册到多路复用库,在发生读写事件时,需要被通知的socket 文件描述符、以及其他一些东西。

typedef struct aeEventLoop { // 目前已注册的最大描述符 int maxfd; /* highest file descriptor currently registered */ // 目前已追踪的最大描述符 int setsize; /* max number of file descriptors tracked */ // 用于生成时间事件 id long long timeEventNextId; // 最后一次执行时间事件的时间 time_t lastTime; /* Used to detect system clock skew */ // 1 已注册的文件事件 aeFileEvent *events; /* Registered events */ // 2 已就绪的文件事件 aeFiredEvent *fired; /* Fired events */ // 3 时间事件 aeTimeEvent *timeEventHead; // 事件处理器的开关 int stop; // 4 多路复用库的私有数据 void *apidata; /* This is used for polling API specific data */ // 5 在处理事件前要执行的函数 aeBeforeSleepProc *beforesleep; } aeEventLoop;

1处,注册到多路复用库,需要监听的socket 文件描述符事件,比如,某socket的可读事件;

2处,以select或者epoll这类多路复用库为例,在一次 select 中,如果发现某些socket事件已经满足,则,这些ready的事件,会被存放到本属性中。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwzffd.html