redis 命令執(zhí)行過(guò)程

系列

redis數(shù)據(jù)淘汰原理
redis過(guò)期數(shù)據(jù)刪除策略
redis server事件模型
redis cluster mget 引發(fā)的討論
redis 3.x windows 集群搭建
redis 命令執(zhí)行過(guò)程
redis string底層數(shù)據(jù)結(jié)構(gòu)
redis list底層數(shù)據(jù)結(jié)構(gòu)
redis hash底層數(shù)據(jù)結(jié)構(gòu)
redis set底層數(shù)據(jù)結(jié)構(gòu)
redis zset底層數(shù)據(jù)結(jié)構(gòu)
redis 客戶(hù)端管理
redis 主從同步-slave端
redis 主從同步-master端
redis 主從超時(shí)檢測(cè)
redis aof持久化
redis rdb持久化
redis 數(shù)據(jù)恢復(fù)過(guò)程
redis TTL實(shí)現(xiàn)原理
redis cluster集群建立
redis cluster集群選主

概述

?這篇文章的目的是為了描述redis server在處理client命令的執(zhí)行過(guò)程,大概包括流程圖、源碼、以及redis的命令格式說(shuō)明,redis的通信協(xié)議參考自redis的官網(wǎng)。


命令執(zhí)行過(guò)程

?整個(gè)redis的server端命令執(zhí)行過(guò)程就如下面這個(gè)流程圖:

  • nio層讀取數(shù)據(jù)
  • 解析數(shù)據(jù)到命令行格式
  • 查找命令對(duì)應(yīng)的執(zhí)行函數(shù)執(zhí)行命令
  • 同步數(shù)據(jù)到slave和aof
redis.png


命令執(zhí)行過(guò)程源碼


讀取命令

?nread = read(fd, c->querybuf+qblen, readlen);負(fù)責(zé)讀取命令數(shù),通過(guò)processInputBuffer進(jìn)行下一步處理。

void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
    redisClient *c = (redisClient*) privdata;
    int nread, readlen;
    size_t qblen;
    REDIS_NOTUSED(el);
    REDIS_NOTUSED(mask);

    // 設(shè)置服務(wù)器的當(dāng)前客戶(hù)端
    server.current_client = c;
    
    // 讀入長(zhǎng)度(默認(rèn)為 16 MB)
    readlen = REDIS_IOBUF_LEN;

    // 獲取查詢(xún)緩沖區(qū)當(dāng)前內(nèi)容的長(zhǎng)度
    // 如果讀取出現(xiàn) short read ,那么可能會(huì)有內(nèi)容滯留在讀取緩沖區(qū)里面
    // 這些滯留內(nèi)容也許不能完整構(gòu)成一個(gè)符合協(xié)議的命令,
    qblen = sdslen(c->querybuf);
    // 如果有需要,更新緩沖區(qū)內(nèi)容長(zhǎng)度的峰值(peak)
    if (c->querybuf_peak < qblen) c->querybuf_peak = qblen;
    // 為查詢(xún)緩沖區(qū)分配空間
    c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);
    // 讀入內(nèi)容到查詢(xún)緩存
    nread = read(fd, c->querybuf+qblen, readlen);

    if (nread) {
        // 根據(jù)內(nèi)容,更新查詢(xún)緩沖區(qū)(SDS) free 和 len 屬性
        // 并將 '\0' 正確地放到內(nèi)容的最后
        sdsIncrLen(c->querybuf,nread);
        // 記錄服務(wù)器和客戶(hù)端最后一次互動(dòng)的時(shí)間
        c->lastinteraction = server.unixtime;
        // 如果客戶(hù)端是 master 的話(huà),更新它的復(fù)制偏移量
        if (c->flags & REDIS_MASTER) c->reploff += nread;
    }

    // 查詢(xún)緩沖區(qū)長(zhǎng)度超出服務(wù)器最大緩沖區(qū)長(zhǎng)度
    // 清空緩沖區(qū)并釋放客戶(hù)端
    if (sdslen(c->querybuf) > server.client_max_querybuf_len) {
        sds ci = catClientInfoString(sdsempty(),c), bytes = sdsempty();

        bytes = sdscatrepr(bytes,c->querybuf,64);
        redisLog(REDIS_WARNING,"Closing client that reached max query buffer length: %s (qbuf initial bytes: %s)", ci, bytes);
        sdsfree(ci);
        sdsfree(bytes);
        freeClient(c);
        return;
    }

    // 從查詢(xún)緩存重讀取內(nèi)容,創(chuàng)建參數(shù),并執(zhí)行命令
    // 函數(shù)會(huì)執(zhí)行到緩存中的所有內(nèi)容都被處理完為止
    processInputBuffer(c);

    server.current_client = NULL;
}


解析命令

?核心在于processInlineBuffer處理內(nèi)聯(lián)命令,processMultibulkBuffer處理批量命令包括get/set等,核心的processCommand用于執(zhí)行命令。

// 處理客戶(hù)端輸入的命令內(nèi)容
void processInputBuffer(redisClient *c) {

    while(sdslen(c->querybuf)) {
        // 判斷請(qǐng)求的類(lèi)型
        // 兩種類(lèi)型的區(qū)別可以在 Redis 的通訊協(xié)議上查到:
        // http://redis.readthedocs.org/en/latest/topic/protocol.html
        // 簡(jiǎn)單來(lái)說(shuō),多條查詢(xún)是一般客戶(hù)端發(fā)送來(lái)的,
        // 而內(nèi)聯(lián)查詢(xún)則是 TELNET 發(fā)送來(lái)的
        if (!c->reqtype) {
            if (c->querybuf[0] == '*') {
                // 多條查詢(xún)
                c->reqtype = REDIS_REQ_MULTIBULK;
            } else {
                // 內(nèi)聯(lián)查詢(xún)
                c->reqtype = REDIS_REQ_INLINE;
            }
        }

        // 將緩沖區(qū)中的內(nèi)容轉(zhuǎn)換成命令,以及命令參數(shù)
        if (c->reqtype == REDIS_REQ_INLINE) {
            if (processInlineBuffer(c) != REDIS_OK) break;
        } else if (c->reqtype == REDIS_REQ_MULTIBULK) {
            if (processMultibulkBuffer(c) != REDIS_OK) break;
        } else {
            redisPanic("Unknown request type");
        }

        /* Multibulk processing could see a <= 0 length. */
        if (c->argc == 0) {
            resetClient(c);
        } else {
            /* Only reset the client when the command was executed. */
            // 執(zhí)行命令,并重置客戶(hù)端
            if (processCommand(c) == REDIS_OK)
                resetClient(c);
        }
    }
}


執(zhí)行命令前準(zhǔn)備

?執(zhí)行命令的過(guò)程其實(shí)主要是尋找命令對(duì)應(yīng)的執(zhí)行函數(shù),通過(guò)lookupCommand查找對(duì)應(yīng)的執(zhí)行命令,通過(guò)call執(zhí)行命令。

int processCommand(redisClient *c) {

    // 特別處理 quit 命令
    if (!strcasecmp(c->argv[0]->ptr,"quit")) {
        addReply(c,shared.ok);
        c->flags |= REDIS_CLOSE_AFTER_REPLY;
        return REDIS_ERR;
    }


    // 查找命令,并進(jìn)行命令合法性檢查,以及命令參數(shù)個(gè)數(shù)檢查
    c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
  
    // 中間省略了很多異常情況的檢測(cè),包括是否超過(guò)內(nèi)存限制、是否合法命令等
    
    if (c->flags & REDIS_MULTI &&
        c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
        c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
    {
        // 在事務(wù)上下文中
        // 除 EXEC 、 DISCARD 、 MULTI 和 WATCH 命令之外
        // 其他所有命令都會(huì)被入隊(duì)到事務(wù)隊(duì)列中
        queueMultiCommand(c);
        addReply(c,shared.queued);
    } else {
        // 執(zhí)行命令
        call(c,REDIS_CALL_FULL);

        c->woff = server.master_repl_offset;
        // 處理那些解除了阻塞的鍵
        if (listLength(server.ready_keys))
            handleClientsBlockedOnLists();
    }

    return REDIS_OK;
}


執(zhí)行命令

?負(fù)責(zé)執(zhí)行命令c->cmd->proc并更新統(tǒng)計(jì)信息,執(zhí)行完成后負(fù)責(zé)同步數(shù)據(jù)propagate。

// 調(diào)用命令的實(shí)現(xiàn)函數(shù),執(zhí)行命令
void call(redisClient *c, int flags) {

    // 執(zhí)行實(shí)現(xiàn)函數(shù)
    c->cmd->proc(c);

    // 將命令復(fù)制到 AOF 和 slave 節(jié)點(diǎn)
    if (flags & REDIS_CALL_PROPAGATE) {
        int flags = REDIS_PROPAGATE_NONE;

        // 強(qiáng)制 REPL 傳播
        if (c->flags & REDIS_FORCE_REPL) flags |= REDIS_PROPAGATE_REPL;

        // 強(qiáng)制 AOF 傳播
        if (c->flags & REDIS_FORCE_AOF) flags |= REDIS_PROPAGATE_AOF;

        // 如果數(shù)據(jù)庫(kù)有被修改,那么啟用 REPL 和 AOF 傳播
        if (dirty)
            flags |= (REDIS_PROPAGATE_REPL | REDIS_PROPAGATE_AOF);

        if (flags != REDIS_PROPAGATE_NONE)
            propagate(c->cmd,c->db->id,c->argv,c->argc,flags);
    }


同步執(zhí)行命令

?主要是負(fù)責(zé)同步數(shù)據(jù)到AOF文件和slave節(jié)點(diǎn),feedAppendOnlyFile負(fù)責(zé)同步到AOF文件,replicationFeedSlaves負(fù)責(zé)同步

/* Propagate the specified command (in the context of the specified database id)
 * to AOF and Slaves.
 *
 * 將指定命令(以及執(zhí)行該命令的上下文,比如數(shù)據(jù)庫(kù) id 等信息)傳播到 AOF 和 slave 。
 *
 * flags are an xor between:
 * FLAG 可以是以下標(biāo)識(shí)的 xor :
 *
 * + REDIS_PROPAGATE_NONE (no propagation of command at all)
 *   不傳播
 *
 * + REDIS_PROPAGATE_AOF (propagate into the AOF file if is enabled)
 *   傳播到 AOF
 *
 * + REDIS_PROPAGATE_REPL (propagate into the replication link)
 *   傳播到 slave
 */
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
               int flags)
{
    // 傳播到 AOF
    if (server.aof_state != REDIS_AOF_OFF && flags & REDIS_PROPAGATE_AOF)
        feedAppendOnlyFile(cmd,dbid,argv,argc);

    // 傳播到 slave
    if (flags & REDIS_PROPAGATE_REPL)
        replicationFeedSlaves(server.slaves,dbid,argv,argc);
}


同步AOF

?AOF涉及的緩存有多份,包括

  • 首先先緩存在局部的buf當(dāng)中
  • 然后將局部buf的數(shù)據(jù)拷貝到全局server.aof_buf當(dāng)中
  • 如果現(xiàn)在剛好在重寫(xiě)AOF文件,那么還會(huì)將數(shù)據(jù)拷貝到重寫(xiě)緩存當(dāng)中。
/*
 * 將命令追加到 AOF 文件中,
 * 如果 AOF 重寫(xiě)正在進(jìn)行,那么也將命令追加到 AOF 重寫(xiě)緩存中。
 */
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
    sds buf = sdsempty();
    robj *tmpargv[3];

    /* 
     * 使用 SELECT 命令,顯式設(shè)置數(shù)據(jù)庫(kù),確保之后的命令被設(shè)置到正確的數(shù)據(jù)庫(kù)
     */
    if (dictid != server.aof_selected_db) {
        char seldb[64];

        snprintf(seldb,sizeof(seldb),"%d",dictid);
        buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n",
            (unsigned long)strlen(seldb),seldb);

        server.aof_selected_db = dictid;
    }

    // EXPIRE 、 PEXPIRE 和 EXPIREAT 命令
    if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
        cmd->proc == expireatCommand) {
        /* 
         * 將 EXPIRE 、 PEXPIRE 和 EXPIREAT 都翻譯成 PEXPIREAT
         */
        buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);

    // SETEX 和 PSETEX 命令
    } else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) {
        /* Translate SETEX/PSETEX to SET and PEXPIREAT 
         *
         * 將兩個(gè)命令都翻譯成 SET 和 PEXPIREAT
         */

        // SET
        tmpargv[0] = createStringObject("SET",3);
        tmpargv[1] = argv[1];
        tmpargv[2] = argv[3];
        buf = catAppendOnlyGenericCommand(buf,3,tmpargv);

        // PEXPIREAT
        decrRefCount(tmpargv[0]);
        buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);

    // 其他命令
    } else {
        buf = catAppendOnlyGenericCommand(buf,argc,argv);
    }

    /* 
     * 將命令追加到 AOF 緩存中,
     * 在重新進(jìn)入事件循環(huán)之前,這些命令會(huì)被沖洗到磁盤(pán)上,
     * 并向客戶(hù)端返回一個(gè)回復(fù)。
     */
    if (server.aof_state == REDIS_AOF_ON)
        server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));

    /* 
     * 如果 BGREWRITEAOF 正在進(jìn)行,
     * 那么我們還需要將命令追加到重寫(xiě)緩存中,
     * 從而記錄當(dāng)前正在重寫(xiě)的 AOF 文件和數(shù)據(jù)庫(kù)當(dāng)前狀態(tài)的差異。
     */
    if (server.aof_child_pid != -1)
        aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));

    // 釋放
    sdsfree(buf);
}


命令行映射關(guān)系表

?包含了命令和對(duì)應(yīng)執(zhí)行函數(shù)的映射關(guān)系,應(yīng)該看上去很清晰命令。

struct redisCommand redisCommandTable[] = {
    {"get",getCommand,2,"r",0,NULL,1,1,1,0,0},
    {"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
    {"setnx",setnxCommand,3,"wm",0,NULL,1,1,1,0,0},
    {"setex",setexCommand,4,"wm",0,NULL,1,1,1,0,0},
    {"psetex",psetexCommand,4,"wm",0,NULL,1,1,1,0,0},
    {"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0},
    {"strlen",strlenCommand,2,"r",0,NULL,1,1,1,0,0},
    {"del",delCommand,-2,"w",0,NULL,1,-1,1,0,0},
    {"exists",existsCommand,2,"r",0,NULL,1,1,1,0,0},
    {"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0},
    {"getbit",getbitCommand,3,"r",0,NULL,1,1,1,0,0},
    {"setrange",setrangeCommand,4,"wm",0,NULL,1,1,1,0,0},
    {"getrange",getrangeCommand,4,"r",0,NULL,1,1,1,0,0},
    {"substr",getrangeCommand,4,"r",0,NULL,1,1,1,0,0},
    {"incr",incrCommand,2,"wm",0,NULL,1,1,1,0,0},
    {"decr",decrCommand,2,"wm",0,NULL,1,1,1,0,0},
    {"mget",mgetCommand,-2,"r",0,NULL,1,-1,1,0,0},
    {"rpush",rpushCommand,-3,"wm",0,NULL,1,1,1,0,0},
    {"lpush",lpushCommand,-3,"wm",0,NULL,1,1,1,0,0},
    {"rpushx",rpushxCommand,3,"wm",0,NULL,1,1,1,0,0},
    {"lpushx",lpushxCommand,3,"wm",0,NULL,1,1,1,0,0},
    {"linsert",linsertCommand,5,"wm",0,NULL,1,1,1,0,0},
    {"rpop",rpopCommand,2,"w",0,NULL,1,1,1,0,0},
    {"lpop",lpopCommand,2,"w",0,NULL,1,1,1,0,0},
    {"brpop",brpopCommand,-3,"ws",0,NULL,1,1,1,0,0},
    {"brpoplpush",brpoplpushCommand,4,"wms",0,NULL,1,2,1,0,0},
    {"blpop",blpopCommand,-3,"ws",0,NULL,1,-2,1,0,0},
    {"llen",llenCommand,2,"r",0,NULL,1,1,1,0,0},
    {"lindex",lindexCommand,3,"r",0,NULL,1,1,1,0,0},
    {"lset",lsetCommand,4,"wm",0,NULL,1,1,1,0,0},
    {"lrange",lrangeCommand,4,"r",0,NULL,1,1,1,0,0},
    {"ltrim",ltrimCommand,4,"w",0,NULL,1,1,1,0,0},
    {"lrem",lremCommand,4,"w",0,NULL,1,1,1,0,0},
    {"rpoplpush",rpoplpushCommand,3,"wm",0,NULL,1,2,1,0,0},
    {"sadd",saddCommand,-3,"wm",0,NULL,1,1,1,0,0},
    {"srem",sremCommand,-3,"w",0,NULL,1,1,1,0,0},
    {"smove",smoveCommand,4,"w",0,NULL,1,2,1,0,0},
    {"sismember",sismemberCommand,3,"r",0,NULL,1,1,1,0,0},
    {"scard",scardCommand,2,"r",0,NULL,1,1,1,0,0},
    {"spop",spopCommand,2,"wRs",0,NULL,1,1,1,0,0},
    {"srandmember",srandmemberCommand,-2,"rR",0,NULL,1,1,1,0,0},
    {"sinter",sinterCommand,-2,"rS",0,NULL,1,-1,1,0,0},
    {"sinterstore",sinterstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0},
    {"sunion",sunionCommand,-2,"rS",0,NULL,1,-1,1,0,0},
    {"sunionstore",sunionstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0},
    {"sdiff",sdiffCommand,-2,"rS",0,NULL,1,-1,1,0,0},
    {"sdiffstore",sdiffstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0},
    {"smembers",sinterCommand,2,"rS",0,NULL,1,1,1,0,0},
    {"sscan",sscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
    {"zadd",zaddCommand,-4,"wm",0,NULL,1,1,1,0,0},
    {"zincrby",zincrbyCommand,4,"wm",0,NULL,1,1,1,0,0},
    {"zrem",zremCommand,-3,"w",0,NULL,1,1,1,0,0},
    {"zremrangebyscore",zremrangebyscoreCommand,4,"w",0,NULL,1,1,1,0,0},
    {"zremrangebyrank",zremrangebyrankCommand,4,"w",0,NULL,1,1,1,0,0},
    {"zremrangebylex",zremrangebylexCommand,4,"w",0,NULL,1,1,1,0,0},
    {"zunionstore",zunionstoreCommand,-4,"wm",0,zunionInterGetKeys,0,0,0,0,0},
    {"zinterstore",zinterstoreCommand,-4,"wm",0,zunionInterGetKeys,0,0,0,0,0},
    {"zrange",zrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
    {"zrangebyscore",zrangebyscoreCommand,-4,"r",0,NULL,1,1,1,0,0},
    {"zrevrangebyscore",zrevrangebyscoreCommand,-4,"r",0,NULL,1,1,1,0,0},
    {"zrangebylex",zrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0},
    {"zrevrangebylex",zrevrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0},
    {"zcount",zcountCommand,4,"r",0,NULL,1,1,1,0,0},
    {"zlexcount",zlexcountCommand,4,"r",0,NULL,1,1,1,0,0},
    {"zrevrange",zrevrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
    {"zcard",zcardCommand,2,"r",0,NULL,1,1,1,0,0},
    {"zscore",zscoreCommand,3,"r",0,NULL,1,1,1,0,0},
    {"zrank",zrankCommand,3,"r",0,NULL,1,1,1,0,0},
    {"zrevrank",zrevrankCommand,3,"r",0,NULL,1,1,1,0,0},
    {"zscan",zscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
    {"hset",hsetCommand,4,"wm",0,NULL,1,1,1,0,0},
    {"hsetnx",hsetnxCommand,4,"wm",0,NULL,1,1,1,0,0},
    {"hget",hgetCommand,3,"r",0,NULL,1,1,1,0,0},
    {"hmset",hmsetCommand,-4,"wm",0,NULL,1,1,1,0,0},
    {"hmget",hmgetCommand,-3,"r",0,NULL,1,1,1,0,0},
    {"hincrby",hincrbyCommand,4,"wm",0,NULL,1,1,1,0,0},
    {"hincrbyfloat",hincrbyfloatCommand,4,"wm",0,NULL,1,1,1,0,0},
    {"hdel",hdelCommand,-3,"w",0,NULL,1,1,1,0,0},
    {"hlen",hlenCommand,2,"r",0,NULL,1,1,1,0,0},
    {"hkeys",hkeysCommand,2,"rS",0,NULL,1,1,1,0,0},
    {"hvals",hvalsCommand,2,"rS",0,NULL,1,1,1,0,0},
    {"hgetall",hgetallCommand,2,"r",0,NULL,1,1,1,0,0},
    {"hexists",hexistsCommand,3,"r",0,NULL,1,1,1,0,0},
    {"hscan",hscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
    {"incrby",incrbyCommand,3,"wm",0,NULL,1,1,1,0,0},
    {"decrby",decrbyCommand,3,"wm",0,NULL,1,1,1,0,0},
    {"incrbyfloat",incrbyfloatCommand,3,"wm",0,NULL,1,1,1,0,0},
    {"getset",getsetCommand,3,"wm",0,NULL,1,1,1,0,0},
    {"mset",msetCommand,-3,"wm",0,NULL,1,-1,2,0,0},
    {"msetnx",msetnxCommand,-3,"wm",0,NULL,1,-1,2,0,0},
    {"randomkey",randomkeyCommand,1,"rR",0,NULL,0,0,0,0,0},
    {"select",selectCommand,2,"rl",0,NULL,0,0,0,0,0},
    {"move",moveCommand,3,"w",0,NULL,1,1,1,0,0},
    {"rename",renameCommand,3,"w",0,NULL,1,2,1,0,0},
    {"renamenx",renamenxCommand,3,"w",0,NULL,1,2,1,0,0},
    {"expire",expireCommand,3,"w",0,NULL,1,1,1,0,0},
    {"expireat",expireatCommand,3,"w",0,NULL,1,1,1,0,0},
    {"pexpire",pexpireCommand,3,"w",0,NULL,1,1,1,0,0},
    {"pexpireat",pexpireatCommand,3,"w",0,NULL,1,1,1,0,0},
    {"keys",keysCommand,2,"rS",0,NULL,0,0,0,0,0},
    {"scan",scanCommand,-2,"rR",0,NULL,0,0,0,0,0},
    {"dbsize",dbsizeCommand,1,"r",0,NULL,0,0,0,0,0},
    {"auth",authCommand,2,"rslt",0,NULL,0,0,0,0,0},
    {"ping",pingCommand,1,"rt",0,NULL,0,0,0,0,0},
    {"echo",echoCommand,2,"r",0,NULL,0,0,0,0,0},
    {"save",saveCommand,1,"ars",0,NULL,0,0,0,0,0},
    {"bgsave",bgsaveCommand,1,"ar",0,NULL,0,0,0,0,0},
    {"bgrewriteaof",bgrewriteaofCommand,1,"ar",0,NULL,0,0,0,0,0},
    {"shutdown",shutdownCommand,-1,"arlt",0,NULL,0,0,0,0,0},
    {"lastsave",lastsaveCommand,1,"rR",0,NULL,0,0,0,0,0},
    {"type",typeCommand,2,"r",0,NULL,1,1,1,0,0},
    {"multi",multiCommand,1,"rs",0,NULL,0,0,0,0,0},
    {"exec",execCommand,1,"sM",0,NULL,0,0,0,0,0},
    {"discard",discardCommand,1,"rs",0,NULL,0,0,0,0,0},
    {"sync",syncCommand,1,"ars",0,NULL,0,0,0,0,0},
    {"psync",syncCommand,3,"ars",0,NULL,0,0,0,0,0},
    {"replconf",replconfCommand,-1,"arslt",0,NULL,0,0,0,0,0},
    {"flushdb",flushdbCommand,1,"w",0,NULL,0,0,0,0,0},
    {"flushall",flushallCommand,1,"w",0,NULL,0,0,0,0,0},
    {"sort",sortCommand,-2,"wm",0,sortGetKeys,1,1,1,0,0},
    {"info",infoCommand,-1,"rlt",0,NULL,0,0,0,0,0},
    {"monitor",monitorCommand,1,"ars",0,NULL,0,0,0,0,0},
    {"ttl",ttlCommand,2,"r",0,NULL,1,1,1,0,0},
    {"pttl",pttlCommand,2,"r",0,NULL,1,1,1,0,0},
    {"persist",persistCommand,2,"w",0,NULL,1,1,1,0,0},
    {"slaveof",slaveofCommand,3,"ast",0,NULL,0,0,0,0,0},
    {"debug",debugCommand,-2,"as",0,NULL,0,0,0,0,0},
    {"config",configCommand,-2,"art",0,NULL,0,0,0,0,0},
    {"subscribe",subscribeCommand,-2,"rpslt",0,NULL,0,0,0,0,0},
    {"unsubscribe",unsubscribeCommand,-1,"rpslt",0,NULL,0,0,0,0,0},
    {"psubscribe",psubscribeCommand,-2,"rpslt",0,NULL,0,0,0,0,0},
    {"punsubscribe",punsubscribeCommand,-1,"rpslt",0,NULL,0,0,0,0,0},
    {"publish",publishCommand,3,"pltr",0,NULL,0,0,0,0,0},
    {"pubsub",pubsubCommand,-2,"pltrR",0,NULL,0,0,0,0,0},
    {"watch",watchCommand,-2,"rs",0,NULL,1,-1,1,0,0},
    {"unwatch",unwatchCommand,1,"rs",0,NULL,0,0,0,0,0},
    {"cluster",clusterCommand,-2,"ar",0,NULL,0,0,0,0,0},
    {"restore",restoreCommand,-4,"awm",0,NULL,1,1,1,0,0},
    {"restore-asking",restoreCommand,-4,"awmk",0,NULL,1,1,1,0,0},
    {"migrate",migrateCommand,-6,"aw",0,NULL,0,0,0,0,0},
    {"asking",askingCommand,1,"r",0,NULL,0,0,0,0,0},
    {"readonly",readonlyCommand,1,"r",0,NULL,0,0,0,0,0},
    {"readwrite",readwriteCommand,1,"r",0,NULL,0,0,0,0,0},
    {"dump",dumpCommand,2,"ar",0,NULL,1,1,1,0,0},
    {"object",objectCommand,-2,"r",0,NULL,2,2,2,0,0},
    {"client",clientCommand,-2,"ar",0,NULL,0,0,0,0,0},
    {"eval",evalCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
    {"evalsha",evalShaCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
    {"slowlog",slowlogCommand,-2,"r",0,NULL,0,0,0,0,0},
    {"script",scriptCommand,-2,"ras",0,NULL,0,0,0,0,0},
    {"time",timeCommand,1,"rR",0,NULL,0,0,0,0,0},
    {"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0},
    {"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0},
    {"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0},
    {"wait",waitCommand,3,"rs",0,NULL,0,0,0,0,0},
    {"pfselftest",pfselftestCommand,1,"r",0,NULL,0,0,0,0,0},
    {"pfadd",pfaddCommand,-2,"wm",0,NULL,1,1,1,0,0},
    {"pfcount",pfcountCommand,-2,"w",0,NULL,1,1,1,0,0},
    {"pfmerge",pfmergeCommand,-2,"wm",0,NULL,1,-1,1,0,0},
    {"pfdebug",pfdebugCommand,-3,"w",0,NULL,0,0,0,0,0}
};


redis 通信協(xié)議


新版統(tǒng)一請(qǐng)求協(xié)議

協(xié)議的一般格式如下,注意前面的*或者$等字符,結(jié)尾的\r\n是分隔符。

-----------------------------------------
格式如下:
---------------------------------------------
*<參數(shù)數(shù)量> CR LF
$<參數(shù) 1 的字節(jié)數(shù)量> CR LF
<參數(shù) 1 的數(shù)據(jù)> CR LF
...
$<參數(shù) N 的字節(jié)數(shù)量> CR LF
<參數(shù) N 的數(shù)據(jù)> CR LF


-----------------------------------------
協(xié)議請(qǐng)求例子如下:
-----------------------------------------
*3
$3
SET
$5
mykey
$7
myvalue


-----------------------------------------
實(shí)際傳輸格式如下:
-----------------------------------------
"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"


-----------------------------------------
響應(yīng)報(bào)文格式如下:
-----------------------------------------
狀態(tài)回復(fù)(status reply)的第一個(gè)字節(jié)是 "+"
錯(cuò)誤回復(fù)(error reply)的第一個(gè)字節(jié)是 "-"
整數(shù)回復(fù)(integer reply)的第一個(gè)字節(jié)是 ":"
批量回復(fù)(bulk reply)的第一個(gè)字節(jié)是 "$"
多條批量回復(fù)(multi bulk reply)的第一個(gè)字節(jié)是 "*"


-----------------------------------------
狀態(tài)回復(fù)報(bào)文格式如下:
-----------------------------------------
+OK


-----------------------------------------
錯(cuò)誤回復(fù)報(bào)文格式如下:
-----------------------------------------
-ERR unknown command 'foobar'
-WRONGTYPE Operation against a key holding the wrong kind of value


-----------------------------------------
整數(shù)回復(fù)報(bào)文格式如下:
-----------------------------------------
整數(shù)回復(fù)就是一個(gè)以 ":" 開(kāi)頭, CRLF 結(jié)尾的字符串表示的整數(shù)。
比如說(shuō), ":0\r\n" 和 ":1000\r\n" 都是整數(shù)回復(fù)。


-----------------------------------------
批量回復(fù)報(bào)文格式如下:
-----------------------------------------
服務(wù)器使用批量回復(fù)來(lái)返回二進(jìn)制安全的字符串,字符串的最大長(zhǎng)度為 512 MB 。
服務(wù)器發(fā)送的內(nèi)容中:
*   第一字節(jié)為 `"$"` 符號(hào)
*   接下來(lái)跟著的是表示實(shí)際回復(fù)長(zhǎng)度的數(shù)字值
*   之后跟著一個(gè) CR LF
*   再后面跟著的是實(shí)際回復(fù)數(shù)據(jù)
*   最末尾是另一個(gè) CR LF

對(duì)于前面的命令,服務(wù)器實(shí)際發(fā)送的內(nèi)容為:
"$6\r\nfoobar\r\n"

如果被請(qǐng)求的值不存在, 那么批量回復(fù)會(huì)將特殊值 `-1` 用作回復(fù)的長(zhǎng)度值, 就像這樣:
服務(wù)器:$-1



-----------------------------------------
多條批量回復(fù)報(bào)文格式如下:
-----------------------------------------
像 [LRANGE] 這樣的命令需要返回多個(gè)值, 這一目標(biāo)可以通過(guò)多條批量回復(fù)來(lái)完成。
多條批量回復(fù)是由多個(gè)回復(fù)組成的數(shù)組, 數(shù)組中的每個(gè)元素都可以是任意類(lèi)型的回復(fù), 包括多條批量回復(fù)本身。
多條批量回復(fù)的第一個(gè)字節(jié)為 `"*"` , 后跟一個(gè)字符串表示的整數(shù)值, 這個(gè)值記錄了多條批量回復(fù)所包含的回復(fù)數(shù)量, 再后面是一個(gè) CRLF 。

客戶(hù)端: LRANGE mylist 0 3
服務(wù)器: *4
服務(wù)器: $3
服務(wù)器: foo
服務(wù)器: $3
服務(wù)器: bar
服務(wù)器: $5
服務(wù)器: Hello
服務(wù)器: $5
服務(wù)器: World

在上面的示例中,服務(wù)器發(fā)送的所有字符串都由 CRLF 結(jié)尾。

正如你所見(jiàn)到的那樣, 多條批量回復(fù)所使用的格式, 和客戶(hù)端發(fā)送命令時(shí)使用的統(tǒng)一請(qǐng)求協(xié)議的格式一模一樣。 它們之間的唯一區(qū)別是:

統(tǒng)一請(qǐng)求協(xié)議只發(fā)送批量回復(fù)。
而服務(wù)器應(yīng)答命令時(shí)所發(fā)送的多條批量回復(fù),則可以包含任意類(lèi)型的回復(fù)。
以下例子展示了一個(gè)多條批量回復(fù), 回復(fù)中包含四個(gè)整數(shù)值, 以及一個(gè)二進(jìn)制安全字符串:
*5\r\n
:1\r\n
:2\r\n
:3\r\n
:4\r\n
$6\r\n
foobar\r\n
在回復(fù)的第一行, 服務(wù)器發(fā)送 *5\r\n , 表示這個(gè)多條批量回復(fù)包含 5 條回復(fù), 再后面跟著的則是 5 條回復(fù)的正文。

多條批量回復(fù)也可以是空白的(empty), 就像這樣:
客戶(hù)端: LRANGE nokey 0 1
服務(wù)器: *0\r\n

無(wú)內(nèi)容的多條批量回復(fù)(null multi bulk reply)也是存在的, 
比如當(dāng) [BLPOP]命令的阻塞時(shí)間超過(guò)最大時(shí)限時(shí), 它就返回一個(gè)無(wú)內(nèi)容的多條批量回復(fù), 這個(gè)回復(fù)的計(jì)數(shù)值為 `-1` :
客戶(hù)端: BLPOP key 1
服務(wù)器: *-1\r\n

多條批量回復(fù)中的元素可以將自身的長(zhǎng)度設(shè)置為 `-1` , 從而表示該元素不存在, 并且也不是一個(gè)空白字符串(empty string)。

當(dāng) [SORT] 命令使用 `GET pattern` 選項(xiàng)對(duì)一個(gè)不存在的鍵進(jìn)行操作時(shí), 就會(huì)發(fā)生多條批量回復(fù)中帶有空白元素的情況。

以下例子展示了一個(gè)包含空元素的多重批量回復(fù):
服務(wù)器: *3
服務(wù)器: $3
服務(wù)器: foo
服務(wù)器: $-1
服務(wù)器: $3
服務(wù)器: bar

其中, 回復(fù)中的第二個(gè)元素為空。


內(nèi)聯(lián)命令格式

當(dāng)你需要和 Redis 服務(wù)器進(jìn)行溝通, 但又找不到 redis-cli , 
而手上只有 telnet 的時(shí)候, 你可以通過(guò) Redis 特別為這種情形而設(shè)的內(nèi)聯(lián)命令格式來(lái)發(fā)送命令。
因?yàn)闆](méi)有了統(tǒng)一請(qǐng)求協(xié)議中的 "*" 項(xiàng)來(lái)聲明參數(shù)的數(shù)量, 所以在 telnet 會(huì)話(huà)輸入命令的時(shí)候, 
必須使用空格來(lái)分割各個(gè)參數(shù), 服務(wù)器在接收到數(shù)據(jù)之后, 會(huì)按空格對(duì)用戶(hù)的輸入進(jìn)行分析(parse), 并獲取其中的命令參數(shù)。

例子如下:
-----------------------------------------
客戶(hù)端: PING
服務(wù)器: +PONG
最后編輯于
?著作權(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)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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