redis啟動初始化過程

azurelaker發表於2018-08-11

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協議命令.

相關文章