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