自顶向下redis4.0(1)启动
阅读原文时间:2023年07月09日阅读:2

redis4.0的启动流程

目录

redis 在接收客户端连接之前,大概做了以下几件事情:

  1. 初始化服务端配置
  2. 初始化服务器
  3. 进入事件主循环

全局server对象

在redis中,有一个全局的对象server保存了redis服务器对象的信息,redis服务器的操作都围绕着该对象展开。下文中当提及server对象,默认指redis的该全局server对象。

typedef struct redisServer {
    pid_t pid;                      /* Main process pid */
    redisDb *db;
    aeEventLoop *el;

    // networking
    int port;                       /* Tcp listening port */
    int tcp_backlog;
    int ipfd[CONFIG_BINDADDR_MAX];  /* TCP socket file descriptors */
    int ipfd_count;                 /* Used slots in ipfd[] */
    char *bindaddr[CONFIG_BINDADDR_MAX]; /* Addresses we should bind to */
    int bindaddr_count;             /* Number of addresses in server.bindaddr[] */
    char neterr[ANET_ERR_LEN];      /* Error buffer for anet.c */
    list *clients;              /* List of active clients */

    /* Limits */
    unsigned int maxclients;            /* Max number of simultaneous clients */
    uint64_t next_client_id;        

    int dbnum;                      /* Total number of configured DBs */
    int verbosity;                  /* Loglevel in redis.conf */
} redisServer;

初始化配置

redis的入口位于 server.cmain函数,main函数中最为重要的几个函数为initServerConfiginitServer 以及aeMain函数。

int main (int argc, char *argv){
    initServerConfig();
    initServer();
    aeMain(server.el);
}

initServerConfig函数为server对象设置了配置的默认值。这些默认值大多定义在文件server.h中。initServerConfig并不负责分配内存,需要分配内存的操作被放在initServer中执行。

void initServerConfig(void) {
    server.port = CONFIG_DEFAULT_SERVER_PORT;
    server.bindaddr_count = 0;
    server.dbnum = CONFIG_DEFAULT_DBNUM;
    server.ipfd_count = 0;
    server.maxclients = CONFIG_DEFAULT_MAX_CLIENTS;
    server.next_client_id = 1;
}

初始化服务器

initServer函数分配了server对象中clients链表,db数组所需的内存,设置了监听端口,将监听端口的文件描述符在多路复用API中注册。

void initServer(void) {
    int j;
    server.pid = getpid();

    server.clients = listCreate();

    // 创建server事件循环所需内存
    server.el = aeCreateEventLoop(server.maxclients+128);

    //监听端口, 此时只调用了 bind 和 listen函数,并将绑定的后的文件描述符传回给server.ipfd数组
    if(server.port !=0 &&
       listenToPort(server.port, server.ipfd, &server.ipfd_count)== C_ERR)
        exit(1);

    // 为db 分配内存,并为每个db创建对象的dict和过期时间的dict
    server.db = zmalloc(sizeof(redisDb)*server.dbnum);
    for(j = 0; j < server.dbnum; j++) {
         server.db[j].dict = dictCreate(&dbDictType,NULL);
         server.db[j].expires = dictCreate(&keyptrDictType,NULL);
         server.db[j].id = j;
    }

    for (j = 0; j < server.ipfd_count; j++) {
        // 对每个绑定的套接字创建文件事件,对于epoll,是将该文件描述符通过epoll_ctl进行注册
        if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
                              acceptTcpHandler,NULL) == AE_ERR){
            serverLog(LL_WARNING,
                      "Unrecoverable error creating server.ipfd file event.");
        }
    }

}

事件主循环

aeMain函数主要处理了多路复用事件的响应, 在没有接受客户端请求之前,服务器实际上一直在等待多路复用API中等待客户端连接TCP的请求。aeProcessEvents函数中调用了aeApiPoll函数,在4.0源码中,一共有4处地方定义了aeApiPoll,具体调用哪个函数,是在编译时提供的变量决定的。

#ifdef HAVE_EVPORT
#include "ae_evport.c"
#else
    #ifdef HAVE_EPOLL
    #include "ae_epoll.c"
    #else
        #ifdef HAVE_KQUEUE
        #include "ae_kqueue.c"
        #else
        #include "ae_select.c"
        #endif
    #endif
#endif

我们可以看作aeApiPoll是对系统函数的一层封装。aeApiPoll函数会将触发事件的文件描述符放入server.eventLoop.fired数组中,并返回触发事件的数量。如果没有时间事件,没有设置超时,并且没有客户端请求触发事件,redis服务器将会一直阻塞。

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    int processed = 0, numevents;

    int j;
    struct timeval tv, *tvp;

    tvp = NULL; /* wait forever */

    /* Call the multiplexing API, will return only on timeout or when
        * some event fires. */
    numevents = aeApiPoll(eventLoop, tvp);

    for (j = 0; j < numevents; j++) {
        aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
        int mask = eventLoop->fired[j].mask;
        int fd = eventLoop->fired[j].fd;
        int fired = 0; /* Number of events fired for current fd. */

        if (!invert && fe->mask & mask & AE_READABLE) {
            fe->rfileProc(eventLoop,fd,fe->clientData,mask);
            fired++;
        }

        processed++;
    }

    return processed; /* return the number of processed file/time events */
}

对于还未接受客户端请求的服务器,此时接受的是TCP请求连接事件,会接着调用注册在aeFileEvent->rfileProc中的acceptTcpHandler函数。

redis 文档