关于Redis的源码分析,在Redis设计与实现
中分析的相当清楚,本人在学习的过程中也是阅读此书。
我们从redis的main函数着手,来分析整个redis的启动过程,main函数在redis.c/redis.h中的第2977行
#ifdef INIT_SETPROCTITLE_REPLACEMENT
spt_init(argc, argv);
#endif
此处用来修改进程名
setlocale(LC_COLLATE,"");
设置Redis所使用的字符串编码。
zmalloc_enable_thread_safeness();
设置线程安全zmalloc开关,函数存在于zmalloc.c中的第237行,作用是将zmalloc_thread_safe这个变量设置为1,该变量大多数情况下是用作是否采用加锁操作的布尔判断。
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
zmalloc_set_oom_handler
的函数在zmalloc.c中的241行,用来设置内存溢出之后的处理函数,而redisOutOfMemoryHandler
在redis.c的第2960行,在Redis内存溢出之后记录报警日志,然后退出整个程序。
dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid());
函数存在于dict.c中的第90行,使用gettimeofday(&tv,NULL);
所取到的精确时间和当前进程的pid异或来生成DICT中的哈希函数的种子。
server.sentinel_mode = checkForSentinelMode(argc,argv);
...
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}
根据输入的参数判断是否以sentinel
模式运行,Redis-sentinel
是Redis的集群管理工具,主要是中心节点用来监控其他节点的工作情况并进行故障恢复。
initServerConfig();
该函数在redis.c中的第1275行,以默认值初始化server的各项属性,具体属性可参见redis.h中第569行所定义的结构体的各项属性及其注释。
if (argc >= 2) {
int j = 1; /* First option to parse in argv[] */
sds options = sdsempty();
char *configfile = NULL;
/* Handle special options --help and --version */
if (strcmp(argv[1], "-v") == 0 ||
strcmp(argv[1], "--version") == 0) version();
if (strcmp(argv[1], "--help") == 0 ||
strcmp(argv[1], "-h") == 0) usage();
if (strcmp(argv[1], "--test-memory") == 0) {
if (argc == 3) {
memtest(atoi(argv[2]),50);
exit(0);
} else {
fprintf(stderr,"Please specify the amount of memory to test in megabytes.\n");
fprintf(stderr,"Example: ./redis-server --test-memory 4096\n\n");
exit(1);
}
}
/* First argument is the config file name? */
if (argv[j][0] != '-' || argv[j][1] != '-')
configfile = argv[j++];
/* All the other options are parsed and conceptually appended to the
* configuration file. For instance --port 6380 will generate the
* string "port 6380\n" to be parsed after the actual file name
* is parsed, if any. */
while(j != argc) {
if (argv[j][0] == '-' && argv[j][1] == '-') {
/* Option name */
if (sdslen(options)) options = sdscat(options,"\n");
options = sdscat(options,argv[j]+2);
options = sdscat(options," ");
} else {
/* Option argument */
options = sdscatrepr(options,argv[j],strlen(argv[j]));
options = sdscat(options," ");
}
j++;
}
if (configfile) server.configfile = getAbsolutePath(configfile);
resetServerSaveParams();
loadServerConfig(configfile,options);
sdsfree(options);
} else {
redisLog(REDIS_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "sentinel" : "redis");
}
这是redis.c中的第3001-3048行,主要是用来处理Redis-server命令行的一些可选参数选项以及读取参数配置文件内容并初始化server的全局变量的相关代码。
if (server.daemonize) daemonize();
根据server.daemonize的值决定是否以daemon的方式开启Redis,函数位于redis.c中的第2837行。
initServer();
初始化整个服务器,函数位于redis.c中的第1519行。主要是初始化各项属性的队列、建立对端口和UNIX域套接字的监听、生成AOF文件句柄等等。
if (server.daemonize) createPidFile();
redisSetProcTitle(argv[0]);
redisAsciiArt();
这三行分别是创建pid文件,设置程序名称以及打印启动时大家都会看到的LOGO。
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
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();
}
这里主要是处理sentinel模式和普通模式下,redis中的数据恢复问题。这里我们暂时只看非sentinel模式下的处理方式。其中,除了一些LOG函数之外,有两个特别的函数linuxOvercommitMemoryWarning();
和loadDataFromDisk();
。
linuxOvercommitMemoryWarning();
位于redis.c的第2821行,主要是用来检查在Linux下运行时,检测关于Linux内核对于内存分配方式策略的选择。该参数位于/proc/sys/vm/overcommit_memory
,其值可以是0、1、2。这三个值分别代表的意义是:
若Linux中该参数的值为0,则REDIS将LOG一条警告信息。
loadDataFromDisk();
函数位于redis.c中的第2944行,根据之前初始化配置信息的内容决定是采用AOF或是RDB方式读取磁盘数据。
/* 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);
}
如源码注释所说,告诉使用者是不是maxmemory这个配置项的数值写错了,因为只指定了不到1MB的空间。
aeSetBeforeSleepProc(server.el,beforeSleep);
aeMain(server.el);
aeSetBeforeSleepProc(server.el,beforeSleep);
用来设置每次进入事件处理函数之前所需要执行的函数。而aeMain(server.el);
函数,位于ae.c中的第450行,在经过了之前的一系列的操作之后,真正的开始Redis事件处理循环。
aeDeleteEventLoop(server.el);
return 0;
如果事件轮询结束,释放之前申请的资源,并且退出函数。
至此,整个Redis的启动流程大概分析完毕,中间很多地方只是大概说明了函数的作用,具体做法留待之后详细分析。