系列
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

命令執(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