redis-cli的實(shí)現(xiàn)原理

首先從源碼中找入口

redis源碼:src/redis-cli.c中找到main函數(shù),main函數(shù)中核心的處理就是以下部分

    /* Start interactive mode when no command is provided */
    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. */
        cliConnect(0);
        repl();
    }
  • cliConnect
    主要是與服務(wù)端建立連接,每一個(gè)連接都會(huì)創(chuàng)建一個(gè)redisContext結(jié)構(gòu)來保存
  • repl
    repl實(shí)現(xiàn)了發(fā)送命令并輸出Server返回結(jié)果的主要邏輯

RedisContext

redisContext結(jié)構(gòu)如下,重要的字段都進(jìn)行了注釋

typedef struct redisContext {
    int err; /* Error flags, 0 when there is no error */
    char errstr[128]; /* String representation of error when applicable */
    int fd; // socket句柄,用戶連接redis server
    int flags;
    char *obuf; /* Write buffer */ //主要存儲(chǔ)發(fā)送的命令,resp協(xié)議封裝后的sds
    redisReader *reader; /* Protocol reader */ // 存儲(chǔ)server返回的數(shù)據(jù)

    enum redisConnectionType connection_type;
    struct timeval *timeout;

    struct {
        char *host;
        char *source_addr;
        int port;
    } tcp;

    struct {
        char *path;
    } unix_sock;

} redisContext;

repl是做什么的

repl其實(shí)質(zhì)就是在不停的重復(fù)解析用戶輸入的命令和redis server返回的參數(shù)。repl中,實(shí)現(xiàn)這個(gè)核心操作的便是issueCommandRepeat方法。

issueCommandRepeat方法我們直觀來想,需要做3步操作

  • 從標(biāo)準(zhǔn)輸入獲取用戶輸入的命令和參數(shù),并按照resp協(xié)議封裝
  • 將封裝后的數(shù)據(jù)發(fā)送至服務(wù)器
  • 讀取從服務(wù)器返回的結(jié)果并解析輸出

cli如何封裝輸入的命令和參數(shù)

通過對(duì)issueCommandRepeat方法的分析,極其對(duì)它里邊調(diào)用關(guān)系的梳理,發(fā)現(xiàn)是redisAppendCommandArgv處理命令并將命令寫入context的obuf中,redisAppendCommandArgv的調(diào)用層級(jí)和源碼如下

issueCommandRepeat
    cliSendCommand
        redisAppendCommandArgv
            redisFormatSdsCommandArgv
                __redisAppendCommand 
源碼:
    sds cmd;
    int len;
    
    //redisFormatSdsCommandArgv是將命令極其跟隨的參數(shù),使用resp協(xié)議封裝后,存到一個(gè)sds結(jié)構(gòu)中。
    len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
    if (len == -1) {
        __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
        return REDIS_ERR;
    }

    //__redisAppendCommand是將sds保存的resp協(xié)議的數(shù)據(jù)存到redisContext中的obuf中
    if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
        sdsfree(cmd);
        return REDIS_ERR;
    }

    // 釋放sds結(jié)構(gòu)申請(qǐng)的內(nèi)存
    sdsfree(cmd);
    return REDIS_OK;

向服務(wù)器發(fā)送命令

有了封裝好的數(shù)據(jù),下一步就是可以向服務(wù)器發(fā)送命令了,還是issueCommandRepeat方法,看下述代碼

while(repeat-- > 0) {
        redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
        while (config.monitor_mode) {
            if (cliReadReply(output_raw) != REDIS_OK) exit(1);
            fflush(stdout);
        }

        if (config.pubsub_mode) {
            if (config.output != OUTPUT_RAW)
                printf("Reading messages... (press Ctrl-C to quit)\n");
            while (1) {
                if (cliReadReply(output_raw) != REDIS_OK) exit(1);
            }
        }

        if (config.slave_mode) {
            printf("Entering replica output mode...  (press Ctrl-C to quit)\n");
            slaveMode();
            config.slave_mode = 0;
            zfree(argvlen);
            return REDIS_ERR;  /* Error = slaveMode lost connection to master */
        }

        if (cliReadReply(output_raw) != REDIS_OK) {
            zfree(argvlen);
            return REDIS_ERR;
        } else {
            /* Store database number when SELECT was successfully executed. */
            if (!strcasecmp(command,"select") && argc == 2 && config.last_cmd_type != REDIS_REPLY_ERROR) {
                config.dbnum = atoi(argv[1]);
                cliRefreshPrompt();
            } else if (!strcasecmp(command,"auth") && argc == 2) {
                cliSelect();
            }
        }
        if (config.interval) usleep(config.interval);
        fflush(stdout); /* Make it grep friendly */
    }      

我們知道redisAppendCommandArgv只是組裝了命令,并沒有發(fā)送,cliReadReply看樣子是將結(jié)果讀取,最后fflush是將結(jié)果輸出到標(biāo)準(zhǔn)輸出。那么發(fā)送命令只可能藏在cliReadReply中,繼續(xù)分析cliReadyReply

cliReadyReply
    redisGetReply

在redisGetReply中發(fā)現(xiàn)特別隱藏的redisBufferWrite,這個(gè)實(shí)際是發(fā)送了請(qǐng)求,我們看具體代碼

    if (sdslen(c->obuf) > 0) {
        // 發(fā)送命令
        nwritten = write(c->fd,c->obuf,sdslen(c->obuf));
        if (nwritten == -1) {
            if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
                /* Try again later */
            } else {
                __redisSetError(c,REDIS_ERR_IO,NULL);
                return REDIS_ERR;
            }
        } else if (nwritten > 0) {
            // 發(fā)送成功,清理obuf
            if (nwritten == (signed)sdslen(c->obuf)) {
                sdsfree(c->obuf);
                c->obuf = sdsempty();
            } else {
                sdsrange(c->obuf,nwritten,-1);
            }
        }
    }

讀取服務(wù)器返回的結(jié)果

繼續(xù)在redisGetReply讀代碼,能夠看到redisBufferRead是獲取服務(wù)器返回的數(shù)據(jù)的方法。

int redisBufferRead(redisContext *c) {
    char buf[1024*16];
    int nread;

    /* Return early when the context has seen an error. */
    if (c->err)
        return REDIS_ERR;
        
    // 從fd讀取數(shù)據(jù)
    nread = read(c->fd,buf,sizeof(buf));
    if (nread == -1) {
        if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
            /* Try again later */
        } else {
            __redisSetError(c,REDIS_ERR_IO,NULL);
            return REDIS_ERR;
        }
    } else if (nread == 0) {
        __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
        return REDIS_ERR;
    } else {
        if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) {
            __redisSetError(c,c->reader->err,c->reader->errstr);
            return REDIS_ERR;
        }
    }
    return REDIS_OK;
}

從代碼可以看到如果讀取成功會(huì)調(diào)用redisReaderFeed將buf內(nèi)容寫入到redisContext中的reader里

redisGetReply中,又調(diào)用了redisGetReplyFromReader,redisReaderGetReply將返回的數(shù)據(jù)通過resp協(xié)議解析為字符串

int redisGetReplyFromReader(redisContext *c, void **reply) {
    if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) {
        __redisSetError(c,c->reader->err,c->reader->errstr);
        return REDIS_ERR;
    }
    return REDIS_OK;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容