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 }
首先进行初始化操作,通过调用initServerConfig
和initServer
设置全局对象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层次的连接空闲.字段verbosity
和logfile
控制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
管理两类事件对象, 包括文件事件对象和定时事件对象, 字段fileEventHead
和timeEventHead
分别指向这两类事件对象的链表头.
接着初始化指针数组server.dict
,每个指针指向函数dictCreate
动态分配的dict
对象.函数anetTcpServer
通过socket接口创建描述符,并绑定到之前的默认端口上, redis服务器就是通过这个socket接口来接收客户端的连接请求.字段cronloops
记录定时事件的触发次数.字段bgsaveinprogress
标识是否正在异步保存内存数据到磁盘文件.字段dirty
记录客户端更新操作的次数.字段lastsave
记录上次写磁盘的时间戳.redis服务器使用dirty
和lastsave
这两个字段来判断是否保存内存数据到磁盘.在函数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协议命令.