redis客户端启动与交互简要分析(6)

上图

在这里插入图片描述



1.redis客户端启动主流程分析

目前研读的代码是redis5.0,启动流程在redis-cli.c的main函数中

先列一下main函数中,我认为比较重要的函数

main(){
	// 客户端默认配置设置
	config.hostip = sdsnew("127.0.0.1");
	....
	// 2.解析命令行携带的参数
  firstarg = parseOptions(argc,argv);
	// 3. 针对不同的模式进行处理
	CLUSTER_MANAGER_MODE()
  config.latency_mode
  config.latency_dist_mode

	// 4.当参数为0且没有任何命令提供时,则进入交互模式
	if (argc == 0 && !config.eval) {
        /* Ignore SIGPIPE in interactive mode to force a reconnect*/
        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. */
				//客户端连接,授权,选择默认的db0库
        cliConnect(0);
        /**交互响应模式处理*/
        repl();
    }
		// 后面就可以不用看了,如果进入了我们经常使用的交互模式后,会在上面进行循环,直到执行退出命令,
		// 网络断开连接啥的
}

从上面我们可以看出,重点内容其实就两点:

1.客户端连接

2.进入交互模式



2. 客户端连接 cliConnect(0)

函数功能:

  1. context = redisConnect(config.hostip,config.hostport); 进行redis server的tcp连接
  2. anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL); socket 保活
  3. cliAuth() 发送授权命令到redis server
  4. 发送选择db命令到redis server,默认是数据库0
static int cliConnect(int flags) {
    if (context == NULL || flags & CC_FORCE) {
        // 连接未初始化的情况
        if (context != NULL) {
            // 上线文释放
            redisFree(context);
        }

        //	没有连接
        if (config.hostsocket == NULL) {
        	//进行连接
            context = redisConnect(config.hostip,config.hostport);
        } else {
            // 连接unix的socket
            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;
        }

        /* Set aggressive KEEP_ALIVE socket option in the Redis context socket
         * in order to prevent timeouts caused by the execution of long
         * commands. At the same time this improves the detection of real
         * errors. socket保活机制*/
        anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);

        /* Do AUTH and select the right DB. 客户端授权*/
        if (cliAuth() != REDIS_OK)
            return REDIS_ERR;
        /** 客户端进行db选择 */
        if (cliSelect() != REDIS_OK)
            return REDIS_ERR;
    }
    return REDIS_OK;
}


3.进入交互模式repl(void)

核心内容:

  1. 初始化帮助信息cliInitHelp();
  2. cliRefreshPrompt(); 刷新输入框
  3. linenoise(context ? config.prompt : "not connected> ") 读取命令行
  4. argv = cliSplitArgs(line,&argc); 将读取的命令行进行拆分
  5. repeat = strtol(argv[0], &endptr, 10); 命令行重复命令检查
  6. 存储命令行到历史记录中去
  7. 判断是否为quit或者exit,是则退出
  8. restart 命令进行会话重启
  9. connect命令 进行连接
  10. clear命令进行屏幕内容清理
  11. 核心环节(issueCommandRepeat(argc-skipargs, argv+skipargs, repeat);
  12. cliReadReply(0); 客户端接收回复

其中从3步到第12步是一个循环的过程,阻塞是发生在监听用户输入。

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. 初始化帮助信息,就是怎么去用redis cli的命令*/
    //初始化帮助
    cliInitHelp();
    // 客户端整合帮助信息
    cliIntegrateHelp();

    config.interactive = 1;
    linenoiseSetMultiLine(1);
    //设置回调
    linenoiseSetCompletionCallback(completionCallback);
    linenoiseSetHintsCallback(hintsCallback);
    linenoiseSetFreeHintsCallback(freeHintsCallback);

    /* Only use history and load the rc file when stdin is a tty. 在控制台窗口中使用rc file来加载历史记录,其实就是加载历史输入*/
    if (isatty(fileno(stdin))) {
        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);
        }
        cliLoadPreferences();
    }

    //刷新输入框
    cliRefreshPrompt();
    //利用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;
                }
            }

            /*  不保存授权命令到 历史命令文件中去*/
            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) {
            	//判断是否为qiut,exit
                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);
}


4.命令重复发送issueCommandRepeat

这个部分主要是处理发送失败后重连发送的。

核心还是cliSendCommand 方法

static int issueCommandRepeat(int argc, char **argv, long repeat) {
    while (1) {
        config.cluster_reissue_command = 0;
        if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
						// 发送失败进行重连
            cliConnect(CC_FORCE);

            /* If we still cannot send the command print error.
             * We'll try to reconnect the next time. 重新发送 */
            if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
                cliPrintContextError();
                return REDIS_ERR;
            }
         }
         /* Issue the command again if we got redirected in cluster mode */
         if (config.cluster_mode && config.cluster_reissue_command) {
            cliConnect(CC_FORCE);
         } else {
             break;
        }
    }
    return REDIS_OK;
}


5.cliSendCommand 发送redis命令

函数的主要功能:

  1. 申请内存存储命令
  2. 命令行转化为resp协议redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
  3. 将resp协议命令传入cliReadReply 进行命令发送,并读取响应结果
  4. 至于后续的读取服务器返回信息redisBufferRead
  5. cliRefreshPrompt 刷新客户端交互窗口,进行返回结果输出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

欢谷悠扬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值