說說 redis 的啓動流程。html
首先要找到啓動函數,咱們知道 C 程序從 main
函數開始,因此,就找到了「夢想」開始的地方 server.c
-> main
。
這裏主要講啓動過程當中的主要部分,因此並不會一一涉及到。node
整個代碼中最重要的結構體莫過於 struct redisServer server
,它以一個全局變量的形式出現。本函數主要是對它的成員進行賦值操做,這些成員基本上是能夠經過 redis.conf 文件來配置。redis
好比:數據庫
server 字段 | 含義 |
---|---|
runid | 節點標識佔用 40B |
port | 啓動端口默認爲 6379 |
tcp_backlog | 默認 511B |
aof_fsync | 默認 aof 每秒刷盤,可是 aof 默認關閉 |
aof_filename | 默認 aof 文件名爲 appendonly.aof |
rdb_filename | 默認 rdb 文件名爲 dump.rdb |
cluster_node_timeout | 默認 15s,默認 cluster 模式關閉 |
appendServerSaveParams(60 * 60,1); /* save after 1 hour and 1 change */ appendServerSaveParams(300,100); /* save after 5 minutes and 100 changes */ appendServerSaveParams(60,10000); /* save after 1 minute and 10000 changes */
包含對 backlog 的相關設置。安全
浮點數據精度設置。服務器
一共有三種類型,以下:app
clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = { {0, 0, 0}, /* normal */ {1024*1024*256, 1024*1024*64, 60}, /* slave */ {1024*1024*32, 1024*1024*8, 60} /* pubsub */ };
初始化 redis 命令表放到 server.commands
中,這主要是在 populateCommandTable
函數中完成的。tcp
注意:考慮到在 redis.conf 配置文件中可使用 rename-command 來對 Command 進行重命名(一般是爲了安全考慮而禁用某些命令),所以命令表保存了兩份,即 server.commands
和 server.orig_commands
。ide
同時還對一些常常查詢的命令單獨提出來,分別放到如下變量中,函數
struct redisCommand *delCommand, *multiCommand, *lpushCommand, *lpopCommand, *rpopCommand, *sremCommand, *execCommand;
默認時間爲 10ms。
如下方式進行該模式的開啓:
int checkForSentinelMode(int argc, char **argv) { int j; if (strstr(argv[0],"redis-sentinel") != NULL) return 1; for (j = 1; j < argc; j++) if (!strcmp(argv[j],"--sentinel")) return 1; return 0; }
使用命令行參數 --sentinel
,或者直接使用二進制文件 redis-sentinel
。
若是開啓了該模式,那麼進行相應的初始,沒開啓就跳過。
if (server.sentinel_mode) { initSentinelConfig(); // sentinel 默認端口 26379 initSentinel(); // sentinel 變量賦初值 }
主要仍是得到配置文件的絕對路徑 server.configfile = getAbsolutePath(configfile)
。
配置文件的載入有專門的函數
void loadServerConfig(char *filename, char *options){}
載入配置文件後,會覆蓋以前對於 server 的某些默認配置。實際上,當 redis-server 啓動後,一些配置能夠經過 config get
命令查看,也能夠經過 config set
命令進行修改,修改後 config rewrite
刷盤。
不一樣於 initServerConfig
函數,該函數主要初始化一些 redis-server 運行中的成員。
經過 redis 來複習下信號處理。
// 忽略SIGHUP和SIGPIPE信號 signal(SIGHUP, SIG_IGN); signal(SIGPIPE, SIG_IGN);
void setupSignalHandlers(void) { struct sigaction act; sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_handler = sigShutdownHandler; sigaction(SIGTERM, &act, NULL); sigaction(SIGINT, &act, NULL); return; }
主要是程序退出的善後工做。
if (server.syslog_enabled) { openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT, server.syslog_facility); }
前提是使用到了系統的 rsyslog。
該函數把一些經常使用的字符串保存起來,目的就是爲了減小不斷申請釋放時CPU時間,內存碎片等等。
好比 shared.ok = createObject(OBJ_STRING,sdsnew("+OK\r\n"))
。
額外說明的是,這裏還初始化了一個很大的共享數字對象,0 到 999。所以在設置 value 時可使用這些數字能夠減小內存的使用。
#define OBJ_SHARED_INTEGERS 10000 for (j = 0; j < OBJ_SHARED_INTEGERS; j++) { // 10000 個數字 shared.integers[j] = createObject(OBJ_STRING,(void*)(long)j); shared.integers[j]->encoding = OBJ_ENCODING_INT; }
struct sharedObjectsStruct shared
也是一個全局變量。
該函數根據配置文件中配置的最大 client 數量增大能夠打開的最多文件數。
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR)
這裏假設 io 多路複用使用的是 epoll,這也是用的最多的。
server.db = zmalloc(sizeof(redisDb)*server.dbnum);
數據庫對象 struct redisDb
,有 16 個。
if (server.port != 0 && listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR) exit(1);
監聽 server.port
,並把返回的 fd 存儲在 server.ipfd
中,有報錯就返回。
if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) { serverPanic("Can't create the serverCron time event."); exit(1); }
註冊定時時間,綁定回調函數 serverCron
,在該函數中咱們能夠看到,執行週期爲 1000/server.hz
ms,所以每秒會執行server.hz
(該值用戶可配)。
那爲何是這個頻率呢?redis 中對於事件處理在以前的一篇博客中寫過,能夠參考下 Redis 中的事件,這裏也能夠簡單回顧下。
時間事件處理函數 ae.c
-> processTimeEvents
中,會根據時間事件的回調返回值來決定這時一個週期事件仍是一次性事件,即
{ int retval; id = te->id; retval = te->timeProc(eventLoop, id, te->clientData); processed++; if (retval != AE_NOMORE) { aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms); } else { te->id = AE_DELETED_EVENT_ID; } }
for (j = 0; j < server.ipfd_count; j++) { if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, // 監聽可讀事件 acceptTcpHandler,NULL) == AE_ERR) { serverPanic( "Unrecoverable error creating server.ipfd file event."); } }
接收用戶請求(用戶鏈接會從這裏進來),監聽可讀事件,註冊回調函數 acceptTcpHandler
。
若是開啓了 cluster mode,會進行相應的初始化。
if (server.cluster_enabled) clusterInit();
replicationScriptCacheInit(); scriptingInit(1); slowlogInit(); latencyMonitorInit(); bioInit();
這個函數很實用的,方便 ps 看到良好格式的進程名。一塊兒來複習下。
void redisSetProcTitle(char *title) { #ifdef USE_SETPROCTITLE char *server_mode = ""; if (server.cluster_enabled) server_mode = " [cluster]"; else if (server.sentinel_mode) server_mode = " [sentinel]"; setproctitle("%s %s:%d%s", title, server.bindaddr_count ? server.bindaddr[0] : "*", server.port, server_mode); #else UNUSED(title); #endif }
若是不是以 sentinel 模式啓動的,那麼會加載持久化的數據,處理函數爲 loadDataFromDisk
。
若是開啓了 aof,那麼就加載 aof 文件,不然加載 rdb 文件。
該函數用來記載 aof 文件,主要流程就是建立一個僞客戶端,從 aof 文件中解析出來命令,讓 server 從新執行一遍。
if (buf[0] != '*') goto fmterr; // 判斷協議是否正確 if (buf[1] == '\0') goto readerr; // 判斷數據完整判斷 argc = atoi(buf+1); if (argc < 1) goto fmterr; argv = zmalloc(sizeof(robj*)*argc); // argc 個 robj 對象 fakeClient->argc = argc; fakeClient->argv = argv; for (j = 0; j < argc; j++) { if (fgets(buf,sizeof(buf),fp) == NULL) { // 每行最多 128B fakeClient->argc = j; /* Free up to j-1. */ freeFakeClientArgv(fakeClient); goto readerr; } if (buf[0] != '$') goto fmterr; len = strtol(buf+1,NULL,10); // 命令的長度 argsds = sdsnewlen(NULL,len); if (len && fread(argsds,len,1,fp) == 0) { sdsfree(argsds); fakeClient->argc = j; /* Free up to j-1. */ freeFakeClientArgv(fakeClient); goto readerr; } argv[j] = createObject(OBJ_STRING,argsds); if (fread(buf,2,1,fp) == 0) { // \r\n fakeClient->argc = j+1; /* Free up to j. */ freeFakeClientArgv(fakeClient); goto readerr; /* discard CRLF */ } } cmd = lookupCommand(argv[0]->ptr); if (!cmd) { serverLog(LL_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr); exit(1); } // 用 fakeClient 執行命令 cmd->proc(fakeClient);
以上函數就是 aof 文件解析過程。
附上一段 redis 協議數據,方便分析函數。
*3 $3 SET $2 xx $2 yy *3
注意:在加載 aof 文件過程當中,會暫時關閉 aof。
該函數用來加載 rdb 文件。與 aof 加載不一樣的是,解析 rdb 文件後直接放入內存中。
// 進入事件循環以前執行 beforeSleep() 函數 aeSetBeforeSleepProc(server.el,beforeSleep); // 開始事件循環 aeMain(server.el); // 服務器關閉,刪除事件循環 aeDeleteEventLoop(server.el);
畫了一個流程圖,能夠很好的體現以上流程。