redis源码阅读二-终于把redis的启动流程搞明白了

Posted 5ycode

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis源码阅读二-终于把redis的启动流程搞明白了相关的知识,希望对你有一定的参考价值。

阅读redis的源码永远也绕不过它的启动。我们来看下redis的启动流程。不想看代码可以直接看最后的流程图。

以下源码分析是redis的5.0分支 源码注释:https://github.com/yxkong/redis/commits/5.0

这是启动流程的核心代码。

int main(int argc, char **argv) 
    //申请空间oom后的处理器
    zmalloc_set_oom_handler(redisOutOfMemoryHandler);
        //哨兵模式
    server.sentinel_mode = checkForSentinelMode(argc,argv);
    //初始化服务器配置(默认值)
    initServerConfig();
    if (argc >= 2) 
        //将配置文件的内容填充到server中
        loadServerConfig(configfile,options);
    
    //守护进程
    int background = server.daemonize && !server.supervised;
    if (background) daemonize();
    //初始化server服务
    initServer();
    InitServerLast();
    aeMain(server.el); 

    

初始化服务配置

initServerConfig中只是给变量赋值了默认值

void initServerConfig(void) 
    //初始化锁
    pthread_mutex_init(&server.next_client_id_mutex,NULL);
    pthread_mutex_init(&server.lruclock_mutex,NULL);
    pthread_mutex_init(&server.unixtime_mutex,NULL);
    //数据库配置填充
    //淘汰策略
    //持久化配置
    //将预定义的命令填充到server.commands
    populateCommandTable();
    

加载配置文件

将配置文件的内容填充到server中覆盖初始化变量 在config.c中

void loadServerConfig(char *filename, char *options) 
   //读取配置到config
    while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
                config = sdscat(config,buf);
    //将配置填充到server,这里是各种if else逻辑     
    loadServerConfigFromString(config);


初始化server服务

这里是重头戏

void initServer(void) 
    //创建共享对象
    createSharedObjects();    
    //创建事件监听器,maxclients默认10000个
    server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
    //socket监听,绑定几个ip,就监听几个,默认监听ipv6和ipv4
    if (server.port != 0 &&
        listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
        exit(1);
    //初始化化数据库的属性
    for (j = 0; j < server.dbnum; j++) 
        server.db[j].dict = dictCreate(&dbDictType,NULL);
        server.db[j].expires = dictCreate(&keyptrDictType,NULL);
        server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
        server.db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
        server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
        server.db[j].id = j;
        server.db[j].avg_ttl = 0;
        server.db[j].defrag_later = listCreate();
    
    /**
     * @brief 创建时间处理器,并将serverCron放入处理器里(重要)
     * 在这里创建了aeTimeEvent并扔给了eventLoop->timeEventHead
     */
    if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) 
        serverPanic("Can't create event loop timers.");
        exit(1);
    
    /**
     * @brief 重点 ##########
     * 监听多少个tcp就创建多少个
     */
    for (j = 0; j < server.ipfd_count; j++) 
        //将acceptTcpHandler 放入文件监听器里,
        if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
            acceptTcpHandler,NULL) == AE_ERR)
            
                serverPanic(
                    "Unrecoverable error creating server.ipfd file event.");
            
    
    //慢日志初始化
    slowlogInit();
    //monitor初始化
    latencyMonitorInit();


在createSharedObjects()里主要创建了共享对象,包括一些异常提示,服务交互指令,共享int等

在ae.c中,我们看下aeCreateEventLoop

/**
 * @brief 创建事件监听器
 * 
 * @param setsize 比配置的最大链接数要多96,为了安全处理(比如有的处理完了,还没有释放,多创建的就相当于缓冲队列了)
 * @return aeEventLoop* 
 */
aeEventLoop *aeCreateEventLoop(int setsize) 
    aeEventLoop *eventLoop;
    int i;
    //分配空间
    if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err;
    //创建对应数量的aeFileEvent空间
    eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
    //创建对应数量的aeFiredEvent空间,此处承载的是触发事件(从epoll里读取后会放入这里)
    eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
    if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
    eventLoop->setsize = setsize;
    eventLoop->lastTime = time(NULL);
    //时间事件链表头节点(只有一个)
    eventLoop->timeEventHead = NULL;
    eventLoop->timeEventNextId = 0;
    eventLoop->stop = 0;
    eventLoop->maxfd = -1;
    eventLoop->beforesleep = NULL;
    eventLoop->aftersleep = NULL;
    //创建一个aeApiState 给eventLoop(不同的系统实现不同,aeApiState也不同)
    if (aeApiCreate(eventLoop) == -1) goto err;
    /* Events with mask == AE_NONE are not set. So let's initialize the
     * vector with it. */
    for (i = 0; i < setsize; i++)
        eventLoop->events[i].mask = AE_NONE;
    return eventLoop;

err:
    if (eventLoop) 
        zfree(eventLoop->events);
        zfree(eventLoop->fired);
        zfree(eventLoop);
    
    return NULL;


在ae.c中,看下aeCreateFileEvent

**
 * @brief 创建文件事件监听器并放入到eventLoop->events中
 *   注册acceptTcpHandler处理AE_READABLE和AE_WRITABLE
 * @param eventLoop 
 * @param fd 对应tcp socket的fd值
 * @param mask 
 * @param proc 处理器acceptTcpHandler
 * @param clientData 
 * @return int 
 */
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
        aeFileProc *proc, void *clientData)

    if (fd >= eventLoop->setsize) 
        errno = ERANGE;
        return AE_ERR;
    
    // 将创建的aeFileEvent 赋值给了&eventLoop->events[fd]
    aeFileEvent *fe = &eventLoop->events[fd];

    if (aeApiAddEvent(eventLoop, fd, mask) == -1)
        return AE_ERR;
    fe->mask |= mask;
    // 将处理器给rfileProc和wfileProc(后续会拿着这个直接执行)
    if (mask & AE_READABLE) fe->rfileProc = proc;
    if (mask & AE_WRITABLE) fe->wfileProc = proc;
    fe->clientData = clientData;
    if (fd > eventLoop->maxfd)
        eventLoop->maxfd = fd;
    return AE_OK;


初始化后端线程

void InitServerLast() 
    bioInit();
    server.initial_memory_usage = zmalloc_used_memory();

bioInit() 在 bio.c中
/**
 * @brief 初始化后台线程
 * 
 */
void bioInit(void) 
    /**
     * @brief 根据后台线程的数量创建线程
     * 
     */
    for (j = 0; j < BIO_NUM_OPS; j++) 
        //初始化一个NUll 的互斥锁
        pthread_mutex_init(&bio_mutex[j],NULL);
        //新任务条件变量
        pthread_cond_init(&bio_newjob_cond[j],NULL);
        //下一个执行的条件变量
        pthread_cond_init(&bio_step_cond[j],NULL);
        //创建一个list
        bio_jobs[j] = listCreate();
        //待处理
        bio_pending[j] = 0;
    
   /**
     * @brief 创建后端线程,并放入线程组
     * 
     */
    for (j = 0; j < BIO_NUM_OPS; j++) 
        void *arg = (void*)(unsigned long) j;
        //创建线程,并启动bioProcessBackgroundJobs
        if (pthread_create(&thread,&attr,bioProcessBackgroundJobs,arg) != 0) 
            serverLog(LL_WARNING,"Fatal: Can't initialize Background Jobs.");
            exit(1);
        
        bio_threads[j] = thread;
    



最重要的 aeMain

在ae.c中

/**
 * @brief 执行eventLoop
 * 
 * @param eventLoop 
 */
void aeMain(aeEventLoop *eventLoop) 
    eventLoop->stop = 0;
    //只要没有停止,就循环执行,这个是主线程
    while (!eventLoop->stop) 
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
    


/**
 * @brief 处理eventLoop里等待的 定时任务,文件事件,过期事件
 * @param eventLoop 
 * @param flags 事件类型,
 * 从main中过来是所有的事件,
 * 从networking过来是文件事件
 * @return int 
 */
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
     //先从eventLoop取下一个要执行的定时器,如果下一次定时还有一定的时间间隔,那么就让epoll阻塞间隔的时间获取数据,否则则立即执行
        /**
         * @brief 从epoll里拿到numevents数量的数据
         * 有时间,就阻塞指定的时间,没有时间,直到有数据
         */
        numevents = aeApiPoll(eventLoop, tvp);
         /**
         * @brief 执行触发的事件
         * 
         */
        for (j = 0; j < numevents; j++) 
            //获取一个事件处理器
            aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];  
            //处理读事件,此处的rfileProc和wfileProc 可以直接理解为acceptTcpHandler
            if (!invert && fe->mask & mask & AE_READABLE) 
                fe->rfileProc(eventLoop,fd,fe->clientData,mask);
                fired++;
            

            /* Fire the writable event. */
            if (fe->mask & mask & AE_WRITABLE) 
                if (!fired || fe->wfileProc != fe->rfileProc) 
                    fe->wfileProc(eventLoop,fd,fe->clientData,mask);
                    fired++;
                
            
        
    //处理定时任务
    if (flags & AE_TIME_EVENTS)
        processed += processTimeEvents(eventLoop);
    //处理完以后进入另一次循环
    return processed; /* return the number of processed file/time events */



ae_kqueue.c中

/**
 * @brief 单位时间内获取事件数量
 * 
 * @param eventLoop 
 * @param tvp 在单位时间内
 * @return int 返回待处理的事件数量
 */
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) 
    aeApiState *state = eventLoop->apidata;
    int retval, numevents = 0;

    if (tvp != NULL) 
        struct timespec timeout;
        timeout.tv_sec = tvp->tv_sec;
        timeout.tv_nsec = tvp->tv_usec * 1000;
        retval = kevent(state->kqfd, NULL, 0, state->events, eventLoop->setsize, &timeout);
     else 
        /**
         * @brief 获取已经就绪的文件描述符数量
         * timeout指针为空,那么kevent()会永久阻塞,直到事件发生
         */
        retval = kevent(state->kqfd, NULL, 0, state->events, eventLoop->setsize, NULL);
    

    //将事件填充到eventLoop->fired中
    if (retval > 0) 
        int j;

        numevents = retval;
        for(j = 0; j < numevents; j++) 
            int mask = 0;
            struct kevent *e = state->events+j;

            if (e->filter == EVFILT_READ) mask |= AE_READABLE;
            if (e->filter == EVFILT_WRITE) mask |= AE_WRITABLE;
            eventLoop->fired[j].fd = e->ident;
            eventLoop->fired[j].mask = mask;
        
    
    return numevents;


终极图

以上是关于redis源码阅读二-终于把redis的启动流程搞明白了的主要内容,如果未能解决你的问题,请参考以下文章

redis源码阅读四-我把redis6里的io多线程执行流程梳理明白了

redis源码阅读五-为什么大量过期key会阻塞redis?

redis源码六-redis中的缓存淘汰策略处理分析

redis源码阅读-终于把内存占用算清楚了

redis集群源码阅读 之 集群握手

曹工说Redis源码-- redis server 主循环大体流程解析