struct timeval tv;
/* We need to initialize our libraries, and the server configuration. */
#ifdef INIT_SETPROCTITLE_REPLACEMENT
spt_init(argc, argv);
#endif
// <MM>
// 使用環境變量初始化 字符串比較
// </MM>
setlocale(LC_COLLATE,"");
zmalloc_enable_thread_safeness();
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
// <MM>
// 設置random的種子,之後會生成隨機的run id,所有加入進程id因素
// </MM>
srand(time(NULL)^getpid());
gettimeofday(&tv,NULL);
dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid());
server.sentinel_mode = checkForSentinelMode(argc,argv);
// <MM>
// 爲redisServer設置初始默認值
// </MM>
initServerConfig();
/* We need to init sentinel right now as parsing the configuration file
* in sentinel mode will have the effect of populating the sentinel
* data structures with master nodes to monitor. */
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}
在initServerConfig函數中,大部分是對server的屬性設置默認值,還有一部分是調用populateCommandTable函數對redis的命令表初始化。全局變量redisCommandTable是redisCommand類型的數組,保存redis支持的所有命令。server.commands是一個dict,保存命令名到redisCommand的映射。populateCommandTable函數會遍歷全局redisCommandTable表,把每條命令插入到server.commands中,根據每個命令的屬性設置其flags。struct redisCommand {
// 命令名稱,在server.commands命令表中,以命令名位key
char *name;
// 命令處理函數
redisCommandProc *proc;
// command的操作數,>0時表示確切的操作數,<0則表示至少有arity個操作數
int arity;
// 標記位的字符表示形式,主要用於命令表的初始化
char *sflags; /* Flags as string representation, one char per flag. */
// 屬性標記位,bitwise,指定command的類型
int flags; /* The actual flags, obtained from the 'sflags' field. */
// 下面的4個屬性都是用於從客戶端的一個請求解析出key,比如mset k1, v1, k2, v2 ...
/* Use a function to determine keys arguments in a command line. */
redisGetKeysProc *getkeys_proc;
/* What keys should be loaded in background when calling this command? */
int firstkey; /* The first argument that's a key (0 = no keys) */
int lastkey; /* The last argument that's a key */
int keystep; /* The step between first and last key */
// 統計信息
long long microseconds, calls;
};
redisCommand有屬性proc,表示命令處理函數。在處理客戶端請求時,獲取到命令名,從server.commands字典中獲取到redisCommand,然後回調其proc函數。 // <MM>
// 守護進程
// </MM>
if (server.daemonize) daemonize();
initServer();
// <MM>
// 創建pid文件
// </MM>
if (server.daemonize) createPidFile();
// <MM>
// 設置進程名字
// </MM>
redisSetProcTitle(argv[0]);
// <MM>
// 打印啓動日誌
// </MM>
redisAsciiArt();
if (!server.sentinel_mode) {
/* Things not needed when running in Sentinel mode. */
redisLog(REDIS_WARNING,"Server started, Redis version " REDIS_VERSION);
#ifdef __linux__
linuxOvercommitMemoryWarning();
#endif
// <MM>
// 從磁盤加載數據,rdb或aof
// </MM>
loadDataFromDisk();
if (server.ipfd_count > 0)
redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port);
if (server.sofd > 0)
redisLog(REDIS_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
} else {
sentinelIsRunning();
}
/* Warning the user about suspicious maxmemory setting. */
if (server.maxmemory > 0 && server.maxmemory < 1024*1024) {
redisLog(REDIS_WARNING,"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?", server.maxmemory);
}
initServerConfig中會對server的整型、字符串類型的屬性設置,initServer主要對複合數據結構list、dict等初始化,並創建事件循環,初始化監聽socket等。下面看下initServer函數:首先,註冊信號處理函數。
int j;
signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
setupSignalHandlers();
如果設置開啓syslog,則初始化。 if (server.syslog_enabled) {
openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT,
server.syslog_facility);
}
初始化客戶端相關的數據結構。redis會將所有連接的客戶端,slave以及monitor的客戶端組織成鏈表,此處主要是創建這些鏈表。 server.current_client = NULL;
server.clients = listCreate();
server.clients_to_close = listCreate();
server.slaves = listCreate();
server.monitors = listCreate();
server.slaveseldb = -1; /* Force to emit the first SELECT command. */
server.unblocked_clients = listCreate();
server.ready_keys = listCreate();
redis中會對經常使用的對象,創建常量池,以避免頻繁創建、回收的開銷。這些對象包括經常使用的響應內容以及小於10000的整數,還有表示bulk個數的響應頭以及bulk長度的響應頭,這兩個響應頭只包括長度小於32的。 // <MM>
// 創建常量池,避免頻繁創建
// </MM>
createSharedObjects();
調整打開描述符限制,需要調用getrlimit和setrlimit。 // <MM>
// 根據maxclients配置,調整打開文件描述符限制
// </MM>
adjustOpenFilesLimit();
初始化事件循環,具體過程在事件循環一節中講述。然後根據配置創建db數組。 // <MM>
// 初始化event loop,如是epoll實現,會調用epoll_create創建epoll的fd
// </MM>
server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);
server.db = zmalloc(sizeof(redisDb)*server.dbnum);
初始化監聽socket,就是調用socket、bind、listen等,並將socket設置爲非阻塞。如果配置了unix domain socket,也會進行相應的初始化。 /* Open the TCP listening socket for the user commands. */
if (server.port != 0 &&
listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR)
exit(1);
/* Open the listening Unix domain socket. */
if (server.unixsocket != NULL) {
unlink(server.unixsocket); /* don't care if this fails */
server.sofd = anetUnixServer(server.neterr,server.unixsocket,
server.unixsocketperm, server.tcp_backlog);
if (server.sofd == ANET_ERR) {
redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr);
exit(1);
}
anetNonBlock(NULL,server.sofd);
}
/* Abort if there are no listening sockets at all. */
if (server.ipfd_count == 0 && server.sofd < 0) {
redisLog(REDIS_WARNING, "Configured to not listen anywhere, exiting.");
exit(1);
}
// <MM>
// 註冊serverCrom時間事件,啓動event loop 1ms後執行
// </MM>
/* Create the serverCron() time event, that's our main way to process
* background operations. */
if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
redisPanic("Can't create the serverCron time event.");
exit(1);
}
將所有監聽socket添加到事件循環。可以看到會有acceptTcpHandler處理客戶端連接的建立。 // <MM>
// 爲所有的監聽socket添加file event,事件處理器是acceptTcpHandler用於處理連接的創建
// </MM>
/* Create an event handler for accepting new connections in TCP and Unix
* domain sockets. */
for (j = 0; j < server.ipfd_count; j++) {
if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
acceptTcpHandler,NULL) == AE_ERR)
{
redisPanic(
"Unrecoverable error creating server.ipfd file event.");
}
}
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
acceptUnixHandler,NULL) == AE_ERR) redisPanic("Unrecoverable error creating server.sofd file event.");
如果開啓aof,則創建aof文件。 /* Open the AOF file if needed. */
if (server.aof_state == REDIS_AOF_ON) {
server.aof_fd = open(server.aof_filename,
O_WRONLY|O_APPEND|O_CREAT,0644);
if (server.aof_fd == -1) {
redisLog(REDIS_WARNING, "Can't open the append-only file: %s",
strerror(errno));
exit(1);
}
}
最後部分,校驗並設置maxmemory配置,初始化lua腳本、slow log以及latency monitor,最後的bioInit初始化異步io線程。redis是單線程,對於重IO操作,比如aof的fsync的調用會由異步線程調用,避免阻塞主線程的事件循環。以上就是initServer函數。 /* 32 bit instances are limited to 4GB of address space, so if there is
* no explicit limit in the user provided configuration we set a limit
* at 3 GB using maxmemory with 'noeviction' policy'. This avoids
* useless crashes of the Redis instance for out of memory. */
if (server.arch_bits == 32 && server.maxmemory == 0) {
redisLog(REDIS_WARNING,"Warning: 32 bit instance detected but no memory limit set. Setting 3 GB maxmemory limit with 'noeviction' policy now.");
server.maxmemory = 3072LL*(1024*1024); /* 3 GB */
server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION;
}
replicationScriptCacheInit();
scriptingInit();
slowlogInit();
latencyMonitorInit();
// <MM>
// 初始化異步阻塞IO處理線程
// </MM>
bioInit();
(4)main函數的最後,是啓動事件循環。在事件循環的每次迭代,sleep之前會調用beforeSleep函數,進行一些異步處理。此處首先設置beforeSleep函數,然後啓動aeMain事件循環。當從事件循環退出後,清理事件循環,然後退出。 aeSetBeforeSleepProc(server.el,beforeSleep);
// <MM>
// 開啓event loop
// </MM>
aeMain(server.el);
aeDeleteEventLoop(server.el);
return 0;
以上便是redis的初始化、啓動過程。總體上,比較簡單,相比nginx的配置文件解析,模塊初始化,進程初始化要簡單不少,讀起來不會太費勁。下一篇介紹事件循環。