美文网首页
server.c分析

server.c分析

作者: 生命就是个Bug | 来源:发表于2019-04-03 14:39 被阅读0次

    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-serverredis-check-rdbredis-sentinelredis-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

    相关文章

      网友评论

          本文标题:server.c分析

          本文链接:https://www.haomeiwen.com/subject/xgqzbqtx.html