redis启动初始化过程

redis服务器实例的执行入口函数main(redis.c):

2064 /* =================================== Main! ================================ */
2065 
2066 int main(int argc, char **argv) {
2067     initServerConfig();
2068     initServer();
2069     if (argc == 2) {
2070         ResetServerSaveParams();
2071         loadServerConfig(argv[1]);
2072         redisLog(REDIS_NOTICE,"Configuration loaded");
2073     } else if (argc > 2) {
2074         fprintf(stderr,"Usage: ./redis-server [/path/to/redis.conf]\n");
2075         exit(1);
2076     }
2077     redisLog(REDIS_NOTICE,"Server started");
2078     if (loadDb("dump.rdb") == REDIS_OK)
2079         redisLog(REDIS_NOTICE,"DB loaded from disk");
2080     if (aeCreateFileEvent(server.el, server.fd, AE_READABLE,
2081         acceptHandler, NULL, NULL) == AE_ERR) oom("creating file event");
2082     redisLog(REDIS_NOTICE,"The server is now ready to accept connections");
2083     aeMain(server.el);
2084     aeDeleteEventLoop(server.el);
2085     return 0;
2086 }

首先进行初始化操作,通过调用initServerConfiginitServer 设置全局对象server的初始值,其类型为:

 93 /* Global server state structure */
 94 struct redisServer {
 95     int port;
 96     int fd;
 97     dict **dict;
 98     long long dirty;            /* changes to DB from the last save */
 99     list *clients;
100     char neterr[ANET_ERR_LEN];
101     aeEventLoop *el;
102     int verbosity;
103     int cronloops;
104     int maxidletime;
105     int dbnum;
106     list *objfreelist;          /* A list of freed objects to avoid malloc() */
107     int bgsaveinprogress;
108     time_t lastsave;
109     struct saveparam *saveparams;
110     int saveparamslen;
111     char *logfile;
112 };

下面是这个两个函数分别对该全局对象的初始化.
函数initServerConfig的实现(redis.c):

556 static void initServerConfig() {
557     server.dbnum = REDIS_DEFAULT_DBNUM;
558     server.port = REDIS_SERVERPORT;
559     server.verbosity = REDIS_DEBUG;
560     server.maxidletime = REDIS_MAXIDLETIME;
561     server.saveparams = NULL;
562     server.logfile = NULL; /* NULL = log on standard output */
563     ResetServerSaveParams();
564 
565     appendServerSaveParams(60*60,1);  /* save after 1 hour and 1 change */
566     appendServerSaveParams(300,100);  /* save after 5 minutes and 100 changes */
567     appendServerSaveParams(60,10000); /* save after 1 minute and 10000 changes */
568 }

全局变量server默认有16个数据库对象, 这个参数可以通过启动时加载的配置文件进行覆盖.redis底层通过TCP网络协议进行数据传输, 因此需要监听服务器端口,默认是端口6379.当已经连接到redis服务器的客户端处于空闲状态时, redis服务器有可能会关闭这个客户端的连接.这里的空闲指的是redis协议层面上的空闲,而不是TCP层次的连接空闲.字段verbositylogfile控制log输出, 可以输出到指定的文件,也可以输出到控制台.字段saveparams 在定时事件处理中使用, 定时事件有可能会把内存中的数据保存到磁盘, 判断的依据就是该字段的值,这里设置了三个标准, 满足其中任何一个,定时事件都会把内存数据保存到磁盘文件.

函数initServer的实现(redis.c):

570 static void initServer() {
571     int j;
572 
573     signal(SIGHUP, SIG_IGN);
574     signal(SIGPIPE, SIG_IGN);
575 
576     server.clients = listCreate();
577     server.objfreelist = listCreate();
578     createSharedObjects();
579     server.el = aeCreateEventLoop();
580     server.dict = malloc(sizeof(dict*)*server.dbnum);
581     if (!server.dict || !server.clients || !server.el || !server.objfreelist)
582         oom("server initialization"); /* Fatal OOM */
583     server.fd = anetTcpServer(server.neterr, server.port, NULL);
584     if (server.fd == -1) {
585         redisLog(REDIS_WARNING, "Opening TCP port: %s", server.neterr);
586         exit(1);
587     }
588     for (j = 0; j < server.dbnum; j++) {
589         server.dict[j] = dictCreate(&sdsDictType,NULL);
590         if (!server.dict[j])
591             oom("server initialization"); /* Fatal OOM */
592     }
593     server.cronloops = 0;
594     server.bgsaveinprogress = 0;
595     server.lastsave = time(NULL);
596     server.dirty = 0;
597     aeCreateTimeEvent(server.el, 1000, serverCron, NULL, NULL);
598 }

字段clients是个list类型,用于记录连接到redis服务器的客户端.字段objfreelist也是个list类型,用于保存空闲的robj类型的对象,objfreelist相当于一个robj类型的对象池, 在分配robj类型的对象时,如果该对象池中有空闲对象,则直接取走用, 否则需要动态分配robj类型的对象.

函数listCreate创建一个list类型的对象,其实现为(adlist.c):

 8 /* Create a new list. The created list can be freed with
 9  * AlFreeList(), but private value of every node need to be freed
10  * by the user before to call AlFreeList().
11  *
12  * On error, NULL is returned. Otherwise the pointer to the new list. */
13 list *listCreate(void)
14 {
15     struct list *list;
16 
17     if ((list = malloc(sizeof(*list))) == NULL)
18         return NULL;
19     list->head = list->tail = NULL;
20     list->len = 0;
21     list->dup = NULL;
22     list->free = NULL;
23     list->match = NULL;
24     return list;
25 }

list相关的结构定义为(adlist.h):

10 typedef struct listNode {
11     struct listNode *prev;
12     struct listNode *next;
13     void *value;
14 } listNode;
15 
16 typedef struct list {
17     listNode *head;
18     listNode *tail;
19     void *(*dup)(void *ptr);
20     void (*free)(void *ptr);
21     int (*match)(void *ptr, void *key);
22     int len;
23 } list;

list链表结构图如下所示:

函数createSharedObjects创建了一些共享的robj类型的对象, 这些对象都是根据不同的客户端命令,响应客户端时发送的.函数aeCreateEventLoop创建了一个事件循环对象,并赋值给server.el, redis服务器的整个逻辑就不断的在事件循环中处理到来的事件.事件循环相关结构的定义为(ae.h):

11 /* File event structure */
12 typedef struct aeFileEvent {
13     int fd;
14     int mask; /* one of AE_(READABLE|WRITABLE|EXCEPTION) */
15     aeFileProc *fileProc;
16     aeEventFinalizerProc *finalizerProc;
17     void *clientData;
18     struct aeFileEvent *next;
19 } aeFileEvent;
20 
21 /* Time event structure */
22 typedef struct aeTimeEvent {
23     long long id; /* time event identifier. */
24     long when_sec; /* seconds */
25     long when_ms; /* milliseconds */
26     aeTimeProc *timeProc;
27     aeEventFinalizerProc *finalizerProc;
28     void *clientData;
29     struct aeTimeEvent *next;
30 } aeTimeEvent;
31 
32 /* State of an event based program */
33 typedef struct aeEventLoop {
34     long long timeEventNextId;
35     aeFileEvent *fileEventHead;
36     aeTimeEvent *timeEventHead;
37     int stop;
38 } aeEventLoop;

类型aeEventLoop管理两类事件对象, 包括文件事件对象和定时事件对象, 字段fileEventHeadtimeEventHead分别指向这两类事件对象的链表头.

接着初始化指针数组server.dict,每个指针指向函数dictCreate动态分配的dict对象.函数anetTcpServer通过socket接口创建描述符,并绑定到之前的默认端口上, redis服务器就是通过这个socket接口来接收客户端的连接请求.字段cronloops记录定时事件的触发次数.字段bgsaveinprogress标识是否正在异步保存内存数据到磁盘文件.字段dirty记录客户端更新操作的次数.字段lastsave记录上次写磁盘的时间戳.redis服务器使用dirtylastsave这两个字段来判断是否保存内存数据到磁盘.在函数initServer的最后,使用函数aeCreateTimeEvent创建一个定时事件对象.
函数aeDeleteTimeEvent的实现(ae.c):

100 long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
101         aeTimeProc *proc, void *clientData,
102         aeEventFinalizerProc *finalizerProc)
103 {
104     long long id = eventLoop->timeEventNextId++;
105     aeTimeEvent *te;
106 
107     te = malloc(sizeof(*te));
108     if (te == NULL) return AE_ERR;
109     te->id = id;
110     aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms);
111     te->timeProc = proc;
112     te->finalizerProc = finalizerProc;
113     te->clientData = clientData;
114     te->next = eventLoop->timeEventHead;
115     eventLoop->timeEventHead = te;
116     return id;
117 }

定时事件在当前时间+milliseconds毫秒后触发, 该参数传入的是1000,因此在1秒后触发.定时事件触发后将执行函数指针timeProc中的逻辑,该参数被传入的地址是serverCron.函数指针finalizerProc在定时事件被删除的时候调用,来处理客户端私有数据对象clientData.新创建的定时事件被放到事件循环对象eventLoop的链表timeEventHead中.

回到main函数, 如果启动redis时,指定了配置文件参数, 则读取配置文件中的数据,并覆盖全局对象server中相同字段的值.接着尝试装载之前保存到磁盘的数据库文件dump.rdb,函数loadDb的实现完全是根据写入磁盘数据库文件的saveDb函数来实现的, 后续我们在定时事件中再看saveDb实现,在此略过分析loadDb的实现.接下来调用函数aeCreateFileEvent创建一个文件事件对象, 用来监听客户端的连接请求,该对象只在可读时候被触发, 触发后的执行函数为acceptHandler,该函数获得连接客户端的socket描述符.

函数aeCreateFileEvent的实现(ae.c):

38 int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
39         aeFileProc *proc, void *clientData,
40         aeEventFinalizerProc *finalizerProc)
41 {
42     aeFileEvent *fe;
43 
44     fe = malloc(sizeof(*fe));
45     if (fe == NULL) return AE_ERR;
46     fe->fd = fd;
47     fe->mask = mask;
48     fe->fileProc = proc;
49     fe->finalizerProc = finalizerProc;
50     fe->clientData = clientData;
51     fe->next = eventLoop->fileEventHead;
52     eventLoop->fileEventHead = fe;
53     return AE_OK;
54 }

文件事件对象中有mask字段, 因为监听的文件描述符被触发时, 可能是可读,可写,或者带外消息, 其他字段含义类似定时事件结构.通过初始化过程, redis服务器实例中会存在一个定时事件对象和一个文件事件对象, 这两个对象一直存在. 当然随着系统运行, 当新的客户端连接到redis服务器时, 会动态的创建新的文件事件对象.

函数main中通过调用函数aeMain进入事件循环处理主流程.
函数aeMain的实现(ae.c):

313 void aeMain(aeEventLoop *eventLoop)
314 {
315     eventLoop->stop = 0;
316     while (!eventLoop->stop)
317         aeProcessEvents(eventLoop, AE_ALL_EVENTS);
318 }

通过调用aeProcessEvents监听并处理客户端的连接请求和redis协议命令.

猜你喜欢

转载自blog.csdn.net/azurelaker/article/details/81368112