redis 命令執(zhí)行過程

系列

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

概述

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


命令執(zhí)行過程

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

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


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


讀取命令

?nread = read(fd, c->querybuf+qblen, readlen);負責讀取命令數(shù)蓝翰,通過processInputBuffer進行下一步處理睦擂。

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);

    // 設置服務器的當前客戶端
    server.current_client = c;
    
    // 讀入長度(默認為 16 MB)
    readlen = REDIS_IOBUF_LEN;

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

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

    // 查詢緩沖區(qū)長度超出服務器最大緩沖區(qū)長度
    // 清空緩沖區(qū)并釋放客戶端
    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;
    }

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

    server.current_client = NULL;
}


解析命令

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

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

    while(sdslen(c->querybuf)) {
        // 判斷請求的類型
        // 兩種類型的區(qū)別可以在 Redis 的通訊協(xié)議上查到:
        // http://redis.readthedocs.org/en/latest/topic/protocol.html
        // 簡單來說疹吃,多條查詢是一般客戶端發(fā)送來的蹦疑,
        // 而內(nèi)聯(lián)查詢則是 TELNET 發(fā)送來的
        if (!c->reqtype) {
            if (c->querybuf[0] == '*') {
                // 多條查詢
                c->reqtype = REDIS_REQ_MULTIBULK;
            } else {
                // 內(nèi)聯(liá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í)行命令萨驶,并重置客戶端
            if (processCommand(c) == REDIS_OK)
                resetClient(c);
        }
    }
}


執(zhí)行命令前準備

?執(zhí)行命令的過程其實主要是尋找命令對應的執(zhí)行函數(shù)歉摧,通過lookupCommand查找對應的執(zhí)行命令,通過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;
    }


    // 查找命令叁温,并進行命令合法性檢查,以及命令參數(shù)個數(shù)檢查
    c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
  
    // 中間省略了很多異常情況的檢測核畴,包括是否超過內(nèi)存限制膝但、是否合法命令等
    
    if (c->flags & REDIS_MULTI &&
        c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
        c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
    {
        // 在事務上下文中
        // 除 EXEC 、 DISCARD 谤草、 MULTI 和 WATCH 命令之外
        // 其他所有命令都會被入隊到事務隊列中
        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í)行命令

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

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

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

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

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

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

        // 如果數(shù)據(jù)庫有被修改泳炉,那么啟用 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í)行命令

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

/* Propagate the specified command (in the context of the specified database id)
 * to AOF and Slaves.
 *
 * 將指定命令(以及執(zhí)行該命令的上下文花鹅,比如數(shù)據(jù)庫 id 等信息)傳播到 AOF 和 slave 。
 *
 * flags are an xor between:
 * FLAG 可以是以下標識的 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當中
  • 然后將局部buf的數(shù)據(jù)拷貝到全局server.aof_buf當中
  • 如果現(xiàn)在剛好在重寫AOF文件刨肃,那么還會將數(shù)據(jù)拷貝到重寫緩存當中。
/*
 * 將命令追加到 AOF 文件中箩帚,
 * 如果 AOF 重寫正在進行真友,那么也將命令追加到 AOF 重寫緩存中。
 */
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
    sds buf = sdsempty();
    robj *tmpargv[3];

    /* 
     * 使用 SELECT 命令紧帕,顯式設置數(shù)據(jù)庫盔然,確保之后的命令被設置到正確的數(shù)據(jù)庫
     */
    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 
         *
         * 將兩個命令都翻譯成 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 緩存中愈案,
     * 在重新進入事件循環(huán)之前,這些命令會被沖洗到磁盤上鹅搪,
     * 并向客戶端返回一個回復站绪。
     */
    if (server.aof_state == REDIS_AOF_ON)
        server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));

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

    // 釋放
    sdsfree(buf);
}


命令行映射關系表

?包含了命令和對應執(zhí)行函數(shù)的映射關系魂挂,應該看上去很清晰命令。

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)一請求協(xié)議

協(xié)議的一般格式如下馁筐,注意前面的*或者$等字符涂召,結尾的\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é)議請求例子如下:
-----------------------------------------
*3
$3
SET
$5
mykey
$7
myvalue


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


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


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


-----------------------------------------
錯誤回復報文格式如下:
-----------------------------------------
-ERR unknown command 'foobar'
-WRONGTYPE Operation against a key holding the wrong kind of value


-----------------------------------------
整數(shù)回復報文格式如下:
-----------------------------------------
整數(shù)回復就是一個以 ":" 開頭眯漩, CRLF 結尾的字符串表示的整數(shù)芹扭。
比如說, ":0\r\n" 和 ":1000\r\n" 都是整數(shù)回復赦抖。


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

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

如果被請求的值不存在轮锥, 那么批量回復會將特殊值 `-1` 用作回復的長度值, 就像這樣:
服務器:$-1



-----------------------------------------
多條批量回復報文格式如下:
-----------------------------------------
像 [LRANGE] 這樣的命令需要返回多個值要尔, 這一目標可以通過多條批量回復來完成舍杜。
多條批量回復是由多個回復組成的數(shù)組, 數(shù)組中的每個元素都可以是任意類型的回復赵辕, 包括多條批量回復本身既绩。
多條批量回復的第一個字節(jié)為 `"*"` , 后跟一個字符串表示的整數(shù)值还惠, 這個值記錄了多條批量回復所包含的回復數(shù)量饲握, 再后面是一個 CRLF 。

客戶端: LRANGE mylist 0 3
服務器: *4
服務器: $3
服務器: foo
服務器: $3
服務器: bar
服務器: $5
服務器: Hello
服務器: $5
服務器: World

在上面的示例中蚕键,服務器發(fā)送的所有字符串都由 CRLF 結尾救欧。

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

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

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

無內(nèi)容的多條批量回復(null multi bulk reply)也是存在的状答, 
比如當 [BLPOP]命令的阻塞時間超過最大時限時冷守, 它就返回一個無內(nèi)容的多條批量回復, 這個回復的計數(shù)值為 `-1` :
客戶端: BLPOP key 1
服務器: *-1\r\n

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

當 [SORT] 命令使用 `GET pattern` 選項對一個不存在的鍵進行操作時馆截, 就會發(fā)生多條批量回復中帶有空白元素的情況充活。

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

其中, 回復中的第二個元素為空蜡娶。


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

當你需要和 Redis 服務器進行溝通混卵, 但又找不到 redis-cli , 
而手上只有 telnet 的時候窖张, 你可以通過 Redis 特別為這種情形而設的內(nèi)聯(lián)命令格式來發(fā)送命令幕随。
因為沒有了統(tǒng)一請求協(xié)議中的 "*" 項來聲明參數(shù)的數(shù)量, 所以在 telnet 會話輸入命令的時候宿接, 
必須使用空格來分割各個參數(shù)赘淮, 服務器在接收到數(shù)據(jù)之后, 會按空格對用戶的輸入進行分析(parse)睦霎, 并獲取其中的命令參數(shù)梢卸。

例子如下:
-----------------------------------------
客戶端: PING
服務器: +PONG
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末巫延,一起剝皮案震驚了整個濱河市爵卒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌籍茧,老刑警劉巖肮塞,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件襟齿,死亡現(xiàn)場離奇詭異,居然都是意外死亡枕赵,警方通過查閱死者的電腦和手機猜欺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拷窜,“玉大人开皿,你說我怎么就攤上這事±好粒” “怎么了赋荆?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長懊昨。 經(jīng)常有香客問我窄潭,道長,這世上最難降的妖魔是什么酵颁? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任嫉你,我火速辦了婚禮月帝,結果婚禮上,老公的妹妹穿的比我還像新娘幽污。我一直安慰自己嚷辅,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布距误。 她就那樣靜靜地躺著簸搞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪准潭。 梳的紋絲不亂的頭發(fā)上趁俊,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音惋鹅,去河邊找鬼则酝。 笑死,一個胖子當著我的面吹牛闰集,可吹牛的內(nèi)容都是我干的沽讹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼武鲁,長吁一口氣:“原來是場噩夢啊……” “哼爽雄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沐鼠,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤挚瘟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后饲梭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乘盖,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年憔涉,在試婚紗的時候發(fā)現(xiàn)自己被綠了订框。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡兜叨,死狀恐怖穿扳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情国旷,我是刑警寧澤矛物,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站跪但,受9級特大地震影響履羞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一吧雹、第九天 我趴在偏房一處隱蔽的房頂上張望骨杂。 院中可真熱鬧涂身,春花似錦雄卷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悴能,卻和暖如春揣钦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背漠酿。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工冯凹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人炒嘲。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓宇姚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親夫凸。 傳聞我的和親對象是個殘疾皇子浑劳,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容