Redis源码分析之基础流程

91 阅读4分钟

Redis系列文章

原理篇

源码篇

问题分析


Redis源码分析之基础流程

本文简要分析一下Redis源码的基本流程,为后续Redis源码分析做好基础。

1. main函数流程

main函数是程序的入口,会做配置去读,服务实例初始化,事件循环等操作。

//server.c#main
int main(int argc, char **argv) {
   ...
   initServerConfig();
   ...
   loadServerConfig(configfile,options);
   ...
   initServer();
   ...
   aeSetBeforeSleepProc(server.el,beforeSleep);
   aeSetAfterSleepProc(server.el,afterSleep);
   aeMain(server.el);
   aeDeleteEventLoop(server.el);
}

initServer主要做了一些server结构体的初始化;另外还会监听端口;创建数据库;把serverCron函数加入到事件循环中;为监听的端口注册事件。

//server.c#initServer
void initServer(void) {
	...
	listenToPort(server.port,server.ipfd,&server.ipfd_count)
	...
	aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL)
	...
	aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,acceptTcpHandler,NULL) 
}

2. 命令执行流程

在main函数中,会对监听端口进行事件监听,处理函数为:acceptTcpHandler。在该方法内部,会accept客户端的连接。然后调用acceptCommonHandler方法

//networking.c#acceptTcpHandler
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
    int cport, cfd, max = MAX_ACCEPTS_PER_CALL;
    while(max--) {
        cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
        acceptCommonHandler(cfd,0,cip);
    }
}

在acceptCommonHandler方法中,会调用createClient方法,进行创建client结构体的数据。

//network.c#acceptCommonHandler
static void acceptCommonHandler(int fd, int flags, char *ip) {
   createClient(fd);
}

在createClient方法中,除了client结构体的创建之外。还会对这个客户端连接进行绑定处理函数,为readQueryFromClient。

//network.c#createClient
client *createClient(int fd) {
	aeCreateFileEvent(server.el,fd,AE_READABLE,readQueryFromClient, c)
}

在该方法中,会读取数据;然后调用processInputBufferAndReplicate方法进行处理。

//network.c#readQueryFromClient
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
   nread = read(fd, c->querybuf+qblen, readlen);
   ...
   processInputBufferAndReplicate(c);
}

在该方法中,最主要是调用processInputBuffer方法。

//network.c#processInputBufferAndReplicate
void processInputBufferAndReplicate(client *c) {
    if (!(c->flags & CLIENT_MASTER)) {
        processInputBuffer(c);
    } else {
        size_t prev_offset = c->reploff;
        processInputBuffer(c);
        size_t applied = c->reploff - prev_offset;
        if (applied) {
            replicationFeedSlavesFromMasterStream(server.slaves,
                    c->pending_querybuf, applied);
            sdsrange(c->pending_querybuf,applied,-1);
        }
    }
}

在该方法中,最重要的是,会调用processCommand方法。

//network.c#processInputBuffer
void processInputBuffer(client *c) {
   ...
   if (processCommand(c) == C_OK) 
   ...
}

在processCommand方法中会以及通过参数,将本次的命令获取到(redisCommand结构体),然后有一系列的判断(后续遇到再说)。然后最重要的是会调用call方法。

//server.c#processCommand
int processCommand(client *c) {
    ...
    c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
	...
	call(c,CMD_CALL_FULL);
	...
}

在执行call方法中,最主要是调用命令对应方法。同时也会记录一下状态,如慢日志,AOF日志(如果有)之类的。

//server.c#call
void call(client *c, int flags) { 
   ...
   c->cmd->proc(c);
   ...
}

3. 事件循环

在main函数中,会在事件循环结构aeEventLoop上,添加beforeSleep、afterSleep的处理函数。主线程中调用aeMain方法。

//server.c#main
aeSetBeforeSleepProc(server.el,beforeSleep);
aeSetAfterSleepProc(server.el,afterSleep);
aeMain(server.el);

aeMain函数内部是一个死循环,处理beforesleep函数和aeProcessEvents

//ae.c#aeMain
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);
    }
}

在aeProcessEvents方法中,会读取事件,然后会执行aftersleep函数。然后遍历事件数,得到连接描述符,执行相应的处理函数。

//ae.c#aeProcessEvents
int aeProcessEvents(aeEventLoop *eventLoop, int flags) {
	...
	numevents = aeApiPoll(eventLoop, tvp);
	/* After sleep callback. */
	if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
	            eventLoop->aftersleep(eventLoop);
	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;
		fe->rfileProc(eventLoop,fd,fe->clientData,mask);
		//或者执行下面的方法,排版问题,去掉了条件判断
		fe->wfileProc(eventLoop,fd,fe->clientData,mask);
	}
    
    if (flags & AE_TIME_EVENTS)
        processed += processTimeEvents(eventLoop);
}

在对连接描述符添加处理函数的方法如下,aeCreateFileEvent。会设置fe->rfileProc或者fe->wfileProc的处理函数。

//ae.c#aeCreateFileEvent
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
        aeFileProc *proc, void *clientData)
{
    if (fd >= eventLoop->setsize) {
        errno = ERANGE;
        return AE_ERR;
    }
    aeFileEvent *fe = &eventLoop->events[fd];

    if (aeApiAddEvent(eventLoop, fd, mask) == -1)
        return AE_ERR;
    fe->mask |= mask;
    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;
}

在processTimeEvents方法中,会根据一些条件判断,执行timeProc对应的处理函数。(如果是Redis实例,其中会有serverCron的处理函数)

//ae.c#processTimeEvents
static int processTimeEvents(aeEventLoop *eventLoop) {
	...
	retval = te->timeProc(eventLoop, id, te->clientData);
	...
}

4. 参考资料

  1. 《Redis 5设计与源码分析》

  2. Redis 5.0.12 源码