redis-cli.c分析

启动redis客户端的时候,会使用redis-cli,而这个二进制文件的入口为redis-cli.cint main(int argc, char **argv)函数。

int main(int argc, char **argv) {
    int firstarg;
    //配置初始化
    config.hostip = sdsnew("127.0.0.1");
    config.hostport = 6379;
    config.hostsocket = NULL;
    //省略一堆...
    config.cluster_manager_command.slots = 0;
    config.cluster_manager_command.timeout = CLUSTER_MANAGER_MIGRATE_TIMEOUT;
    config.cluster_manager_command.pipeline = CLUSTER_MANAGER_MIGRATE_PIPELINE;
    config.cluster_manager_command.threshold =
            CLUSTER_MANAGER_REBALANCE_THRESHOLD;
    pref.hints = 1;

    spectrum_palette = spectrum_palette_color;
    spectrum_palette_size = spectrum_palette_color_size;

    if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL))
        config.output = OUTPUT_RAW;
    else
        config.output = OUTPUT_STANDARD;
    config.mb_delim = sdsnew("\n");
    //解析命令行选项
    firstarg = parseOptions(argc, argv);
    argc -= firstarg;
    argv += firstarg;

    //解析环境变量
    parseEnv();

    /* Cluster Manager mode */
    //集群模式,redis5.0中不需要再使用redis-trib.rb来创建了
    //通过 redis-cli --cluster 即可
    if (CLUSTER_MANAGER_MODE()) {
        clusterManagerCommandProc *proc = validateClusterManagerCommand();
        if (!proc) {
            sdsfree(config.hostip);
            sdsfree(config.mb_delim);
            exit(1);
        }
        clusterManagerMode(proc);
    }

    /* Latency mode */
    //redis-cli --latency redis延迟时间测试
    if (config.latency_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        latencyMode();
    }

    /* Latency distribution mode */
    //redis-cli --latency-dist 以图表形式展示延迟时间测试
    if (config.latency_dist_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        latencyDistMode();
    }

    /* Slave mode */
    //redis-cli --slave 从模式
    if (config.slave_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        slaveMode();
    }

    /* Get RDB mode. */
    //redis-cli --rdb [rdb文件路径]
    if (config.getrdb_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        getRDB();
    }

    /* Pipe mode */
    //redis-cli --pipe 管道模式
    if (config.pipe_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        pipeMode();
    }

    /* Find big keys */
    //redis-cli --bigkeys 找大key
    if (config.bigkeys) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        findBigKeys(0, 0);
    }

    /* Find large keys */
    //redis-cli --memkeys 找大key
    if (config.memkeys) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        findBigKeys(1, config.memkeys_samples);
    }

    /* Find hot keys */
    //redis-cli --hotkeys 找热点key,需要设置LFU模式
    if (config.hotkeys) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        findHotKeys();
    }

    /* Stat mode */
    //redis-cli --stat 监控redis状态[key\men\clients\blocked\requests\connections]
    if (config.stat_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        if (config.interval == 0) config.interval = 1000000;
        statMode();
    }

    /* Scan mode */
    //redis-cli --scan 扫描所有的key
    if (config.scan_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        scanMode();
    }

    /* LRU test mode */
    //redis-cli --lru-test [key] 进行指定key的LRU测试,会狂set和get
    if (config.lru_test_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        LRUTestMode();
    }

    /* Intrinsic latency mode */
    //redis-cli --intrinsic-latency [seconds] 进行指定秒数的延迟测试
    if (config.intrinsic_latency_mode) intrinsicLatencyMode();

    /* Start interactive mode when no command is provided */
    if (argc == 0 && !config.eval) {
        /* Ignore SIGPIPE in interactive mode to force a reconnect */
        //非活跃状态,忽略SIGPIPE信号,当处于TCP半关闭状态,再进行write,TCP会回复一个RST重置。
        signal(SIGPIPE, SIG_IGN);

        /* Note that in repl mode we don't abort on connection error.
         * A new attempt will be performed for every command send. */
        //创建连接,通过host:ip或者unix_socket方式
        //并且会设置一个15秒的keepalive心跳检测,通过tcpdump可以看到
        //另外判断是否需要AUTH登录,及select db。
        cliConnect(0); //连接上Redis
        //命令行请求模式,request一条,response一条
        repl();
    }

    /* Otherwise, we have some arguments to execute */
    if (cliConnect(0) != REDIS_OK) exit(1);
    if (config.eval) {
        //解析lua脚本
        return evalMode(argc, argv);
    } else {
        return noninteractive(argc, convertToSds(argc, argv));
    }
}

再来看一下cliConnect函数的实现

/* Connect to the server. It is possible to pass certain flags to the function:
 *      CC_FORCE: The connection is performed even if there is already
 *                a connected socket.
 *      CC_QUIET: Don't print errors if connection fails. */
static int cliConnect(int flags) {
    if (context == NULL || flags & CC_FORCE) {
        if (context != NULL) {
            redisFree(context);
        }

        if (config.hostsocket == NULL) {
            //通过ip、端口连接  127.0.0.1:6379
            context = redisConnect(config.hostip, config.hostport);
        } else {
            //通过unix_socket连接  /tmp/idontexist.sock
            context = redisConnectUnix(config.hostsocket);
        }

        if (context->err) {
            if (!(flags & CC_QUIET)) {
                fprintf(stderr, "Could not connect to Redis at ");
                if (config.hostsocket == NULL)
                    fprintf(stderr, "%s:%d: %s\n",
                            config.hostip, config.hostport, context->errstr);
                else
                    fprintf(stderr, "%s: %s\n",
                            config.hostsocket, context->errstr);
            }
            redisFree(context);
            context = NULL;
            return REDIS_ERR;
        }

        //设置keepalive,默认15秒
        anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);

        //发送AUTH命令到服务端
        if (cliAuth() != REDIS_OK)
            return REDIS_ERR;
        //发送SELECT dbnum 命令到服务端
        if (cliSelect() != REDIS_OK)
            return REDIS_ERR;
    }
    return REDIS_OK;
}

tcpdump查看心跳检测

14:48:10.402103 IP localhost.43924 > localhost.6379: Flags [.], ack 11474, win 1365, options [nop,nop,TS val 9850688 ecr 9849152], length 0
14:48:10.402135 IP localhost.6379 > localhost.43924: Flags [.], ack 36, win 342, options [nop,nop,TS val 9850688 ecr 9840768], length 0
14:48:25.762022 IP localhost.43924 > localhost.6379: Flags [.], ack 11474, win 1365, options [nop,nop,TS val 9852224 ecr 9850688], length 0
14:48:25.762060 IP localhost.6379 > localhost.43924: Flags [.], ack 36, win 342, options [nop,nop,TS val 9852224 ecr 9840768], length 0

最后,看看repl()是如何实现的。
包括了命令提示、补齐、请求、响应等功能。
Redis使用了linenoise来实现的命令行交互。

static void repl(void) {
    sds historyfile = NULL;
    int history = 0;
    char *line;
    int argc;
    sds *argv;

    /* Initialize the help and, if possible, use the COMMAND command in order
     * to retrieve missing entries. */
    //初始化帮助信息 help [command]
    cliInitHelp();
    //COMMAND命令
    cliIntegrateHelp();

    config.interactive = 1;
    //设置支持多行
    linenoiseSetMultiLine(1);
    //按Tab自动补齐命令
    linenoiseSetCompletionCallback(completionCallback);
    //命令提示
    linenoiseSetHintsCallback(hintsCallback);
    linenoiseSetFreeHintsCallback(freeHintsCallback);

    /* Only use history and load the rc file when stdin is a tty. */
    if (isatty(fileno(stdin))) {
        // ~/.rediscli_history文件,包含了所有通过redis-cli执行的命令
        historyfile = getDotfilePath(REDIS_CLI_HISTFILE_ENV, REDIS_CLI_HISTFILE_DEFAULT);
        //keep in-memory history always regardless if history file can be determined
        history = 1;
        if (historyfile != NULL) {
            linenoiseHistoryLoad(historyfile);
        }
        //加载偏好文件 ~/.redisclirc
        cliLoadPreferences();
    }

    //显示 127.0.0.1:6379> 或者 127.0.0.1:6379[1] 这个1代表db号 select dbNum
    cliRefreshPrompt();
    //linenoise是一个命令行编辑库,https://github.com/antirez/linenoise
    while ((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
        if (line[0] != '\0') {
            long repeat = 1;
            int skipargs = 0;
            char *endptr = NULL;

            argv = cliSplitArgs(line, &argc);

            /* check if we have a repeat command option and
             * need to skip the first arg */
            if (argv && argc > 0) {
                errno = 0;
                repeat = strtol(argv[0], &endptr, 10);
                if (argc > 1 && *endptr == '\0') {
                    if (errno == ERANGE || errno == EINVAL || repeat <= 0) {
                        fputs("Invalid redis-cli repeat command option value.\n", stdout);
                        sdsfreesplitres(argv, argc);
                        linenoiseFree(line);
                        continue;
                    }
                    skipargs = 1;
                } else {
                    repeat = 1;
                }
            }

            /* Won't save auth command in history file */
            //不把auth信息保存到history文件
            if (!(argv && argc > 0 && !strcasecmp(argv[0 + skipargs], "auth"))) {
                if (history) linenoiseHistoryAdd(line);
                if (historyfile) linenoiseHistorySave(historyfile);
            }

            if (argv == NULL) {
                printf("Invalid argument(s)\n");
                linenoiseFree(line);
                continue;
            } else if (argc > 0) {
                if (strcasecmp(argv[0], "quit") == 0 ||
                    strcasecmp(argv[0], "exit") == 0) {
                    exit(0);
                } else if (argv[0][0] == ':') {
                    cliSetPreferences(argv, argc, 1);
                    sdsfreesplitres(argv, argc);
                    linenoiseFree(line);
                    continue;
                } else if (strcasecmp(argv[0], "restart") == 0) {
                    if (config.eval) {
                        config.eval_ldb = 1;
                        config.output = OUTPUT_RAW;
                        return; /* Return to evalMode to restart the session. */
                    } else {
                        printf("Use 'restart' only in Lua debugging mode.");
                    }
                } else if (argc == 3 && !strcasecmp(argv[0], "connect")) {
                    sdsfree(config.hostip);
                    config.hostip = sdsnew(argv[1]);
                    config.hostport = atoi(argv[2]);
                    cliRefreshPrompt();
                    cliConnect(CC_FORCE);
                } else if (argc == 1 && !strcasecmp(argv[0], "clear")) {
                    linenoiseClearScreen();
                } else {
                    long long start_time = mstime(), elapsed;

                    issueCommandRepeat(argc - skipargs, argv + skipargs, repeat);

                    /* If our debugging session ended, show the EVAL final
                     * reply. */
                    if (config.eval_ldb_end) {
                        config.eval_ldb_end = 0;
                        cliReadReply(0);
                        printf("\n(Lua debugging session ended%s)\n\n",
                               config.eval_ldb_sync ? "" :
                               " -- dataset changes rolled back");
                    }

                    elapsed = mstime() - start_time;
                    if (elapsed >= 500 &&
                        config.output == OUTPUT_STANDARD) {
                        printf("(%.2fs)\n", (double) elapsed / 1000);
                    }
                }
            }
            /* Free the argument vector */
            sdsfreesplitres(argv, argc);
        }
        /* linenoise() returns malloc-ed lines like readline() */
        linenoiseFree(line);
    }
    exit(0);
}
repl.png
全部评论

相关推荐

不愿透露姓名的神秘牛友
10-12 10:48
已编辑
秋招之苟:邻居家老哥19届双2硕大厂开发offer拿遍了,前几天向他请教秋招,他给我看他当年的简历,0实习实验室项目技术栈跟开发基本不沾边😂,我跟他说这个放在现在中厂简历都过不了
点赞 评论 收藏
分享
尊嘟假嘟点击就送:加v细说,问题很大
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务