哨兵设置
根据前文所选择的模式,如果选择的是哨兵模式,这里就进入哨兵初始化流程。
是否需要 rdb/aof 校验
/* Check if we need to start in redis-check-rdb/aof mode. We just execute * the program main. However the program is part of the Redis executable * so that we can easily execute an RDB check on loading errors. */ if (strstr(argv[0],"redis-check-rdb") != NULL) redis_check_rdb_main(argc,argv,NULL); else if (strstr(argv[0],"redis-check-aof") != NULL) redis_check_aof_main(argc,argv);
解析命令行参数
根据命令行参数,如配置文件中已有设置,那就就覆盖默认参数设置
if (argc >= 2) { j = 1; /* First option to parse in argv[] */ sds options = sdsempty(); /* 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); } } /* Parse command line options * Precedence wise, File, stdin, explicit options -- last config is the one that matters. * * First argument is the config file name? */ if (argv[1][0] != '-') { /* Replace the config file in server.exec_argv with its absolute path. */ server.configfile = getAbsolutePath(argv[1]); zfree(server.exec_argv[1]); server.exec_argv[1] = zstrdup(server.configfile); j = 2; // Skip this arg when parsing options } while(j < argc) { /* Either first or last argument - Should we read config from stdin? */ if (argv[j][0] == '-' && argv[j][1] == '\0' && (j == 1 || j == argc-1)) { config_from_stdin = 1; } /* 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 config file * and stdin input are parsed (if they exist). */ else 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++; } // 读取配置文件 loadServerConfig(server.configfile, config_from_stdin, options); if (server.sentinel_mode) loadSentinelConfigFromQueue(); sdsfree(options); }
读取配置文件
主要是读取 redis.conf
配置参数。
/* Load the server configuration from the specified filename. * The function appends the additional configuration directives stored * in the 'options' string to the config file before loading. * * Both filename and options can be NULL, in such a case are considered * empty. This way loadServerConfig can be used to just load a file or * just load a string. */ void loadServerConfig(char *filename, char config_from_stdin, char *options) { sds config = sdsempty(); char buf[CONFIG_MAX_LINE+1]; FILE *fp; /* Load the file content */ if (filename) { if ((fp = fopen(filename,"r")) == NULL) { serverLog(LL_WARNING, "Fatal error, can't open config file '%s': %s", filename, strerror(errno)); exit(1); } while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL) config = sdscat(config,buf); fclose(fp); } /* Append content from stdin */ if (config_from_stdin) { serverLog(LL_WARNING,"Reading config from stdin"); fp = stdin; while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL) config = sdscat(config,buf); } /* Append the additional options */ if (options) { config = sdscat(config,"\n"); config = sdscat(config,options); } loadServerConfigFromString(config); sdsfree(config); }
是否后台运行
int background = server.daemonize && !server.supervised; // 是否是后台运行 if (background) daemonize(); // 执行后台运行的方法 void daemonize(void) { int fd; if (fork() != 0) exit(0); /* parent exits */ setsid(); /* create a new session */ /* Every output goes to /dev/null. If Redis is daemonized but * the 'logfile' is set to 'stdout' in the configuration file * it will not log at all. */ 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); } }
这里需要说一下(经常面试问到),当然 Redis 中台进程,貌似常见的问题比较简单,我们看来纯正的后台进程:
int daemon_init(const char *pname, int facility) { int i; pid_t pid; if ( (pid = Fork()) < 0) //创建一个子进程 return (-1); else if (pid) _exit(0); /* parent terminates 父进程退出*/ //子进程继续运行 //此进程是子进程,所以肯定不是组长进程 /* child 1 continues... */ if (setsid() < 0) /* become session leader 创建会话,成为会话首进程,新的进程组的组长进程*/ return (-1); Signal(SIGHUP, SIG_IGN); //把挂起信号设置为忽略 if ( (pid = Fork()) < 0) //再创建一个子进程 return (-1); else if (pid) //父进程退出 _exit(0); /* child 1 terminates */ //第二个子进程继续运行,因为第二个子进程已经不是会话首进程了,所以永远不会获得控制终端 /* child 2 continues... */ daemon_proc = 1; /* for err_XXX() functions 再error.c中定义的变量*/ chdir("/"); /* change working directory 调整工作目录到根目录 */ /* close off file descriptors */ for (i = 0; i < MAXFD; i++) //关闭所有文件描述符 close(i); /* redirect stdin, stdout, and stderr to /dev/null 定义标准输入,标准输出和标准错误到/dev/null */ open("/dev/null", O_RDONLY); open("/dev/null", O_RDWR); open("/dev/null", O_RDWR); openlog(pname, LOG_PID, facility); //打开日志文件 return (0); /* success 函数运行成功 */ }
- setsid(): 确保当前进程编程新绘画的会话头进程组的进程组头进程,从而不在受终端控制。
- 忽略 SIGHUP 信号之后再次 fork 目的是确保本守护京城来即使打开一个终端设备,也不会自动获取终端。当乜有控制终端的一个会话头进程打开一个控制终端时,该终端自动成为这个会话头进程的控制终端。然后再次 fork 之后,我们确保新的子进程不在是一个会话头进程,从而不能自动获取一控制器终端。这里必须要忽略 SIGHUP 信号,因为会话头进程(即首次 fork 产生的子进程)终止时,其会话中的所有进程(即再次 fork 的子进程)都会收到 SIGHUP 信号
- 更改跟路径
- 关闭继承来的所有套接字
- 重定向 stdin ,stdout 和 stderr ,否则他会输出到屏幕上。
初始化服务
信号量
服务端打交道最多的是 SIGHUP 和 SIGPIPE,这两个信号默认行为是终止。而服务器随便就终止那是不行的,我么需要进行忽略。
signal(SIGHUP, SIG_IGN); signal(SIGPIPE, SIG_IGN);
针对其他的信号,比如 SIGINT 信号,我们对其进行捕获后 redis 进行收尾工作,避免进程呗暴力 kill
void setupSignalHandlers(void) { struct sigaction act; /* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction is used. * Otherwise, sa_handler is used. */ sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_handler = sigShutdownHandler; sigaction(SIGTERM, &act, NULL); sigaction(SIGINT, &act, NULL); sigemptyset(&act.sa_mask); act.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; act.sa_sigaction = sigsegvHandler; if(server.crashlog_enabled) { sigaction(SIGSEGV, &act, NULL); sigaction(SIGBUS, &act, NULL); sigaction(SIGFPE, &act, NULL); sigaction(SIGILL, &act, NULL); sigaction(SIGABRT, &act, NULL); } return; }
syslog 设置
syslog 是 linux 系统自带的,主要是为了 daemon 进程提供日志服务,我们在前面讲过。deamon 进程无法打印到终端,那么如何方便的接受输出的日志呢?linux 提供了 syslog 服务,该服务支持打印各种级别的日志以及输出位置(本地或者远程均可)
if (server.syslog_enabled) { openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT, server.syslog_facility); }
其他常用参数设置
/* Initialization after setting defaults from the config system. */ server.aof_state = server.aof_enabled ? AOF_ON : AOF_OFF; server.hz = server.config_hz; server.pid = getpid(); server.in_fork_child = CHILD_TYPE_NONE; server.main_thread_id = pthread_self(); server.current_client = NULL; server.errors = raxNew(); server.fixed_time_expire = 0; server.clients = listCreate(); server.clients_index = raxNew(); server.clients_to_close = listCreate(); server.slaves = listCreate(); server.monitors = listCreate(); server.clients_pending_write = listCreate(); server.clients_pending_read = listCreate(); server.clients_timeout_table = raxNew(); server.replication_allowed = 1; server.slaveseldb = -1; /* Force to emit the first SELECT command. */ server.unblocked_clients = listCreate(); server.ready_keys = listCreate(); server.clients_waiting_acks = listCreate(); server.get_ack_from_slaves = 0; server.client_pause_type = 0; server.paused_clients = listCreate(); server.events_processed_while_blocked = 0; server.system_memory_size = zmalloc_get_memory_size(); server.blocked_last_cron = 0; server.blocking_op_nesting = 0;