1. 阅读Redis的Makefile文件。
REDIS_SERVER_NAME=redis-server
REDIS_SENTINEL_NAME=redis-sentinel
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o
REDIS_CLI_NAME=redis-cli
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
REDIS_BENCHMARK_NAME=redis-benchmark
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o zmalloc.o redis-benchmark.o
REDIS_CHECK_RDB_NAME=redis-check-rdb
REDIS_CHECK_AOF_NAME=redis-check-aof
...
# redis-server
$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a $(FINAL_LIBS)
# redis-sentinel
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME)
# redis-check-rdb
$(REDIS_CHECK_RDB_NAME): $(REDIS_SERVER_NAME)
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_CHECK_RDB_NAME)
# redis-check-aof
$(REDIS_CHECK_AOF_NAME): $(REDIS_SERVER_NAME)
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME)
server.c
文件,被编译成redis-server
、redis-check-rdb
、redis-sentinel
、redis-check-aof
。
2. server.c的int main(int argc, char **argv)函数,redis-server的主入口
这个main函数的主要逻辑大概就是:各种初始化、加载配置、事件监听。
为了简单,我将这个文件分开来说:
- 初始化部分
- redis-check-rdb部分
- redis-check-aof部分
- redis-sentinel部分
- redis-cluster部分
- redis-server部分
- 其它部分
1. 初始化部分
//设置时区等信息
setlocale(LC_COLLATE,"");
tzset(); /* Populates 'timezone' global. */
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
srand(time(NULL)^getpid());
gettimeofday(&tv,NULL);
//通过/dev/urandom实现,生成非空随机
char hashseed[16];
//标识redis实例的时候用到
getRandomHexChars(hashseed,sizeof(hashseed));
dictSetHashFunctionSeed((uint8_t*)hashseed);
server.sentinel_mode = checkForSentinelMode(argc,argv);
//初始化默认配置
initServerConfig();
moduleInitModulesSystem();
//保存当前执行文件的绝对路径,执行restartServer的时候用到
server.executable = getAbsolutePath(argv[0]);
server.exec_argv = zmalloc(sizeof(char*)*(argc+1));
server.exec_argv[argc] = NULL;
for (j = 0; j < argc; j++) server.[exec_argvj] = zstrdup(argv[j]);
2. redis-check-rdb部分
if (strstr(argv[0],"redis-check-rdb") != NULL)
redis_check_rdb_main(argc,argv,NULL);
如果当前执行的文件是redis-check-rdb
,则执行redis-check-rdb.c
文件中的int redis_check_rdb_main(int argc, char **argv, FILE *fp)
方法。
int redis_check_rdb_main(int argc, char **argv, FILE *fp) {
//如果没传入 rdb文件路径的话,就会报错
if (argc != 2 && fp == NULL) {
fprintf(stderr, "Usage: %s <rdb-file-name>\n", argv[0]);
exit(1);
}
/* In order to call the loading functions we need to create the shared
* integer objects, however since this function may be called from
* an already initialized Redis instance, check if we really need to. */
if (shared.integers[0] == NULL)
createSharedObjects();
server.loading_process_events_interval_bytes = 0;
rdbCheckMode = 1;
rdbCheckInfo("Checking RDB file %s", argv[1]);
rdbCheckSetupSignals();
int retval = redis_check_rdb(argv[1],fp);
if (retval == 0) {
rdbCheckInfo("\\o/ RDB looks OK! \\o/");
rdbShowGenericInfo();
}
if (fp) return (retval == 0) ? C_OK : C_ERR;
exit(retval);
}
效果如图:
image.png
3. redis-check-aof部分
else if (strstr(argv[0],"redis-check-aof") != NULL)
redis_check_aof_main(argc,argv);
如果当前执行的文件是redis-check-aof,则执行redis-check-aof.c文件中的int redis_check_aof_main(int argc, char **argv)
方法。
int redis_check_aof_main(int argc, char **argv) {
char *filename;
int fix = 0;
//如果没传aof文件路径,则会报错
if (argc < 2) {
printf("Usage: %s [--fix] <file.aof>\n", argv[0]);
exit(1);
} else if (argc == 2) {
filename = argv[1];
} else if (argc == 3) {
//传入--fix可以对aof文件进行修复
if (strcmp(argv[1],"--fix") != 0) {
printf("Invalid argument: %s\n", argv[1]);
exit(1);
}
filename = argv[2];
fix = 1;
} else {
printf("Invalid arguments\n");
exit(1);
}
//打开aof文件
FILE *fp = fopen(filename,"r+");
if (fp == NULL) {
printf("Cannot open file: %s\n", filename);
exit(1);
}
struct redis_stat sb;
if (redis_fstat(fileno(fp),&sb) == -1) {
printf("Cannot stat file: %s\n", filename);
exit(1);
}
off_t size = sb.st_size;
//如果aof文件为空,则直接退出
if (size == 0) {
printf("Empty file: %s\n", filename);
exit(1);
}
/* This AOF file may have an RDB preamble. Check this to start, and if this
* is the case, start processing the RDB part. */
//这里做个测试,将rdb文件改为aof文件 cp ./dump.rdb ./dump.aof
if (size >= 8) { /* There must be at least room for the RDB header. */
char sig[5];
int has_preamble = fread(sig,sizeof(sig),1,fp) == 1 &&
memcmp(sig,"REDIS",sizeof(sig)) == 0;
rewind(fp);
//如果该aof文件内容包含了rdb格式的文件内容,则以RDB方式加载
if (has_preamble) {
printf("The AOF appears to start with an RDB preamble.\n"
"Checking the RDB preamble to start:\n");
if (redis_check_rdb_main(argc,argv,fp) == C_ERR) {
printf("RDB preamble of AOF file is not sane, aborting.\n");
exit(1);
} else {
printf("RDB preamble is OK, proceeding with AOF tail...\n");
}
}
}
//开始解析AOF文件
off_t pos = process(fp);
off_t diff = size-pos;
printf("AOF analyzed: size=%lld, ok_up_to=%lld, diff=%lld\n",
(long long) size, (long long) pos, (long long) diff);
if (diff > 0) {
if (fix) {
char buf[2];
printf("This will shrink the AOF from %lld bytes, with %lld bytes, to %lld bytes\n",(long long)size,(long long)diff,(long long)pos);
printf("Continue? [y/N]: ");
if (fgets(buf,sizeof(buf),stdin) == NULL ||
strncasecmp(buf,"y",1) != 0) {
printf("Aborting...\n");
exit(1);
}
if (ftruncate(fileno(fp), pos) == -1) {
printf("Failed to truncate AOF\n");
exit(1);
} else {
printf("Successfully truncated AOF\n");
}
} else {
printf("AOF is not valid. "
"Use the --fix option to try fixing it.\n");
exit(1);
}
} else {
printf("AOF is valid\n");
}
fclose(fp);
exit(0);
}
效果如图:
非正常aof文件.png
正常aof文件.png
4. redis-sentinel部分
我们启动redis-sentinel有两种方式。
一种是redis-sentinel /path/to/sentinel.conf
一种是redis-server --sentinel /path/to/sentinel.conf
因此,在server.c
中,会去判断这两种方式。
server.sentinel_mode = checkForSentinelMode(argc,argv);
/* Returns 1 if there is --sentinel among the arguments or if
* argv[0] contains "redis-sentinel". */
int checkForSentinelMode(int argc, char **argv) {
int j;
//当前执行文件为redis-sentinel
if (strstr(argv[0],"redis-sentinel") != NULL) return 1;
//启动参数列表中包含 --sentinel
for (j = 1; j < argc; j++)
if (!strcmp(argv[j],"--sentinel")) return 1;
return 0;
}
如果确定了是sentinel模式的话,将会去初始化sentinel及其配置。
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}
void initSentinelConfig(void) {
//默认端口26379
server.port = REDIS_SENTINEL_PORT;
server.protected_mode = 0; /* Sentinel must be exposed. */
}
/* Perform the Sentinel mode initialization. */
void initSentinel(void) {
//省略,一些初始值
}
完成上述操作之后,会去刷新sentinel的配置信息(rewriteConfig)
sentinelIsRunning();
/* This function gets called when the server is in Sentinel mode, started,
* loaded the configuration, and is ready for normal operations. */
void sentinelIsRunning(void) {
int j;
//sentinel配置文件没有指定则会报错
if (server.configfile == NULL) {
serverLog(LL_WARNING,
"Sentinel started without a config file. Exiting...");
exit(1);
} else if (access(server.configfile,W_OK) == -1) {
serverLog(LL_WARNING,
"Sentinel config file %s is not writable: %s. Exiting...",
server.configfile,strerror(errno));
exit(1);
}
/* If this Sentinel has yet no ID set in the configuration file, we
* pick a random one and persist the config on disk. From now on this
* will be this Sentinel ID across restarts. */
for (j = 0; j < CONFIG_RUN_ID_SIZE; j++)
if (sentinel.myid[j] != 0) break;
if (j == CONFIG_RUN_ID_SIZE) {
/* Pick ID and persist the config. */
getRandomHexChars(sentinel.myid,CONFIG_RUN_ID_SIZE);
sentinelFlushConfig();
}
/* Log its ID to make debugging of issues simpler. */
serverLog(LL_WARNING,"Sentinel ID is %s", sentinel.myid);
/* We want to generate a +monitor event for every configured master
* at startup. */
sentinelGenerateInitialMonitorEvents();
}
void sentinelFlushConfig(void) {
int fd = -1;
int saved_hz = server.hz;
int rewrite_status;
server.hz = CONFIG_DEFAULT_HZ;
// 刷新配置信息
rewrite_status = rewriteConfig(server.configfile);
server.hz = saved_hz;
if (rewrite_status == -1) goto werr;
if ((fd = open(server.configfile,O_RDONLY)) == -1) goto werr;
if (fsync(fd) == -1) goto werr;
if (close(fd) == EOF) goto werr;
return;
werr:
if (fd != -1) close(fd);
serverLog(LL_WARNING,"WARNING: Sentinel was not able to save the new configuration on disk!!!: %s", strerror(errno));
}
5. redis-cluster部分
moduleLoadFromQueue();
//从本地磁盘加载RDB、AOF
loadDataFromDisk();
if (server.cluster_enabled) {
//验证及加载配置、创建slot
if (verifyClusterConfigWithData() == C_ERR) {
serverLog(LL_WARNING,
"You can't have keys in a DB different than DB 0 when in "
"Cluster mode. Exiting.");
exit(1);
}
}
6. redis-server部分
除了以上的其它模式,剩下这个正常使用的模式。./redis-server
包含了三种功能:
- 带
-
的 - 带
--
的 - 什么也不带
效果如图:
image.png
if (argc >= 2) {
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];
server.configfile = getAbsolutePath(configfile);
/* Replace the config file in server.exec_argv with
* its absolute path. */
zfree(server.exec_argv[j]);
server.exec_argv[j] = zstrdup(server.configfile);
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 (!strcmp(argv[j], "--check-rdb")) {
/* Argument has no options, need to skip for parsing. */
j++;
continue;
}
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 (server.sentinel_mode && configfile && *configfile == '-') {
serverLog(LL_WARNING,
"Sentinel config from STDIN not allowed.");
serverLog(LL_WARNING,
"Sentinel needs config file on disk to save state. Exiting...");
exit(1);
}
resetServerSaveParams();
//加载redis.conf配置
loadServerConfig(configfile,options);
sdsfree(options);
}
7. 其它部分
监控
server.supervised = redisIsSupervised(server.supervised_mode);
int redisIsSupervised(int mode) {
if (mode == SUPERVISED_AUTODETECT) {
const char *upstart_job = getenv("UPSTART_JOB");
const char *notify_socket = getenv("NOTIFY_SOCKET");
if (upstart_job) {
//通过up_start方式来异步启动redis
redisSupervisedUpstart();
} else if (notify_socket) {
//通过systemd方式启动redis
redisSupervisedSystemd();
}
} else if (mode == SUPERVISED_UPSTART) {
return redisSupervisedUpstart();
} else if (mode == SUPERVISED_SYSTEMD) {
return redisSupervisedSystemd();
}
return 0;
}
守护进程,supervised模式下不可以使用,否则失效。
int background = server.daemonize && !server.supervised;
if (background) daemonize();
void daemonize(void) {
int fd;
//fork出子进程,exit 父进程
if (fork() != 0) exit(0); /* parent exits */
setsid(); /* create a new session */
if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO) close(fd);
}
}
image.png
image.png
可以看到,启动时显示的pid和启动后进程的pid不一致。
初始化Server
//初始化server
initServer();
//daemonize模式下,在/var/run目录下创建redis.pid文件
if (background || server.pidfile) createPidFile();
//设置进程名称
redisSetProcTitle(argv[0]);
//显示logo
redisAsciiArt();
//检查tcp的半连接队列配置
//查看/proc/sys/net/core/somaxconn中配置的值
checkTcpBacklogSettings();
事件驱动
aeSetBeforeSleepProc(server.el,beforeSleep);
aeSetAfterSleepProc(server.el,afterSleep);
//包括 select、epoll、kqueue,Redis高性能的原因
aeMain(server.el);
aeDeleteEventLoop(server.el);
执行aeMain,进入一个死循环,处理可被处理的文件事件、时间事件。
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
}
}
Redis.png
网友评论