redis原理-对象以及命令解析与执行
一、 redis对象
适用场景:
redis是使用对象来存放数据的,基于前面的几种数据结构创建对象。
原理解释:
redis对象类型有下面5种:
/*Object types */
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
对象的编码:
/* Objects encoding. Some kind of objectslike Strings and Hashes can be
*internally represented in multiple ways. The 'encoding' field of the object
* isset to one of this fields for this object. */
#define REDIS_ENCODING_RAW 0 /* Raw representation */ sds字符串
#define REDIS_ENCODING_INT 1 /* Encoded as integer */ 整形
#define REDIS_ENCODING_HT 2 /* Encoded as hash table */ hash表
#define REDIS_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /*Encoded as regular linked list */ 双端列表
#define REDIS_ENCODING_ZIPLIST 5 /* Encodedas ziplist */ 压缩列表
#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */ 整数集合
#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */ 跳跃表
#define REDIS_ENCODING_EMBSTR 8 /* Embedded sds string encoding */ 基于emb格式的sds
对象对应的编码:
REDIS_STRING:REDIS_ENCODING_RAW, REDIS_ENCODING_INT, REDIS_ENCODING_EMBSTR
REDIS_LIST:REDIS_ENCODING_LINKEDLIST,REDIS_ENCODING_ZIPLIST
REDIS_SET:REDIS_ENCODING_INTSET,REDIS_ENCODING_HT
REDIS_ZSET:REDIS_ENCODING_ZIPLIST,REDIS_ENCODING_SKIPLIST
REDIS_HASH:REDIS_ENCODING_ZIPLIST,REDIS_ENCODING_HT
//编码对应的字符串
char *strEncoding(int encoding) {
switch(encoding) {
case REDIS_ENCODING_RAW: return "raw";
case REDIS_ENCODING_INT: return "int";
case REDIS_ENCODING_HT: return "hashtable";
case REDIS_ENCODING_LINKEDLIST: return "linkedlist";
case REDIS_ENCODING_ZIPLIST: return "ziplist";
case REDIS_ENCODING_INTSET: return "intset";
caseREDIS_ENCODING_SKIPLIST: return "skiplist";
case REDIS_ENCODING_EMBSTR: return "embstr";
default: return "unknown";
}
}
//redis对象
typedef struct redisObject {
unsigned type:4; //类型
unsigned encoding:4; //编码
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock)
int refcount;//引用个数
void *ptr; //实际数据
} robj;
创建对象:
robj *createObject(int type, void *ptr) {
robj *o = zmalloc(sizeof(*o));
o->type = type;
o->encoding = REDIS_ENCODING_RAW;
o->ptr = ptr;
o->refcount = 1;
/* Set the LRU to the current lruclock (minutes resolution). */
o->lru = LRU_CLOCK();
return o;
}
//创建一个sds字符串,这里分配了2次空间 sdsnewlen 和 createObject:zmalloc
/* Create a string object with encodingREDIS_ENCODING_RAW, that is a plain
*string object where o->ptr points to a proper sds string. */
robj *createRawStringObject(char *ptr,size_t len) {
returncreateObject(REDIS_STRING,sdsnewlen(ptr,len));
}
//创建一个emb格式的字符串,分配了一次空间
/* Create a string object with encodingREDIS_ENCODING_EMBSTR, that is
* an object where the sds string isactually an unmodifiable string
* allocated in the same chunk as the objectitself. */
robj *createEmbeddedStringObject(char *ptr,size_t len) {
robj *o = zmalloc(sizeof(robj)+sizeof(structsdshdr)+len+1);
struct sdshdr *sh = (void*)(o+1);
o->type = REDIS_STRING;
o->encoding = REDIS_ENCODING_EMBSTR;
o->ptr = sh+1;
o->refcount = 1;
o->lru = LRU_CLOCK();
sh->len = len;
sh->free = 0;
if (ptr) {
memcpy(sh->buf,ptr,len);
sh->buf[len] = '\0';
} else {
memset(sh->buf,0,len+1);
}
return o;
}
//如果长度小于39咱们就创建emb字符串,否则就原始的sds
/* Create a string object with EMBSTRencoding if it is smaller than
*REIDS_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
*used.
*
*The current limit of 39 is chosen so that the biggest string object
* weallocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
#define REDIS_ENCODING_EMBSTR_SIZE_LIMIT 39
robj *createStringObject(char *ptr, size_tlen) {
if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT)
return createEmbeddedStringObject(ptr,len);
else
return createRawStringObject(ptr,len);
}
//longlong类型也可以存储为字符串
robj *createStringObjectFromLongLong(longlong value) {
robj*o;
//首先从引用里面找
if (value >= 0 && value < REDIS_SHARED_INTEGERS) {
incrRefCount(shared.integers[value]);
o = shared.integers[value];
}else {
if (value >= LONG_MIN && value <= LONG_MAX) {
o = createObject(REDIS_STRING, NULL);
o->encoding = REDIS_ENCODING_INT;
o->ptr = (void*)((long)value);//直接转换成指针
} else {
o = createObject(REDIS_STRING,sdsfromlonglong(value));
}
}
return o;
}
//获取longlong对象
int getLongLongFromObject(robj *o, longlong *target) {
long long value;
char *eptr;
if (o == NULL) {
value = 0;
}else {
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
if (sdsEncodedObject(o)) {
errno = 0;
value = strtoll(o->ptr, &eptr, 10);
if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
errno == ERANGE)
return REDIS_ERR;
} else if (o->encoding == REDIS_ENCODING_INT) {
value = (long)o->ptr;//转换成值
} else {
redisPanic("Unknown string encoding");
}
}
if (target) *target = value;
return REDIS_OK;
}
//创建列表对象
robj *createListObject(void) {
list *l = listCreate();
robj *o = createObject(REDIS_LIST,l);
listSetFreeMethod(l,decrRefCountVoid);
o->encoding = REDIS_ENCODING_LINKEDLIST;
return o;
}
//创建压缩列表对象
robj *createZiplistObject(void) {
unsigned char *zl = ziplistNew();
robj *o = createObject(REDIS_LIST,zl);
o->encoding = REDIS_ENCODING_ZIPLIST;
return o;
}
//创建集合对象
robj *createSetObject(void) {
dict *d = dictCreate(&setDictType,NULL);
robj *o = createObject(REDIS_SET,d);
o->encoding = REDIS_ENCODING_HT;
return o;
}
//创建整数集合对象
robj *createIntsetObject(void) {
intset *is = intsetNew();
robj *o = createObject(REDIS_SET,is);
o->encoding = REDIS_ENCODING_INTSET;
return o;
}
//创建hash对象
robj *createHashObject(void) {
unsigned char *zl = ziplistNew();
robj *o = createObject(REDIS_HASH, zl);
o->encoding = REDIS_ENCODING_ZIPLIST;
return o;
}
//创建有序集合对象
robj *createZsetObject(void) {
zset *zs = zmalloc(sizeof(*zs));
robj *o;
zs->dict = dictCreate(&zsetDictType,NULL);
zs->zsl = zslCreate();
o= createObject(REDIS_ZSET,zs);
o->encoding = REDIS_ENCODING_SKIPLIST;
return o;
}
//关于编码转换的一些说明:
列表对象:都满足下面2个条件就会使用ziplist,否则使用双端链表:
1、 所有字符串长度都小于64
2、 保存的元素个数小于512
hash对象:都满足下面2个条件就会使用ziplist,否则使用hash表:
1、 hash对象保存的键值对字符长度都小于64
2、 键值对对数数量小于512
集合对象:都满足下面2个条件使用intset,否则使用hash表:
1、 集合的所有元素都是整数
2、 集合数量不能超过512
有序集合:都满足下面2个条件使用ziplist,否则使用跳跃表:
1、 有序集合元素小于128个
2、 有序集合元素成员长度都小于64字节
二、 命令解析与执行
//针对不同命令,redis使用redisCommand这个结构体统一实现命令的
//命令实现回调函数
typedef void redisCommandProc(redisClient*c);
typedef int *redisGetKeysProc(structredisCommand *cmd, robj **argv, int argc, int *numkeys);
//命令结构体定义
struct redisCommand {
char *name; //命令的名字
redisCommandProc *proc;//实际处理命令的函数
int arity; //参数个数
char *sflags; /* Flags as string representation, one char per flag. */字符串flags(标志用户可以执行什么操作)
int flags; /* The actual flags,obtained from the 'sflags' field. */
/* Use a function to determine keys arguments in a command line.
* Used for Redis Cluster redirect. */
redisGetKeysProc *getkeys_proc;
/* What keys should be loaded in background when calling this command?*/
int firstkey; /* The first argument that's a key (0 = no keys) */ //指示那个参数是key
int lastkey; /* The last argumentthat's a key */
int keystep; /* The step betweenfirst and last key */
long long microseconds, calls;//命令执行的毫秒数,执行的总次数
};
//命令列表部分展示
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},
}
命令说明:
/* Our command table.
*
*Every entry is composed of the following fields:
*
*name: a string representing the command name. //命令的名字
*function: pointer to the C function implementing the command.//命令的实现函数(回调)
*arity: number of arguments, it is possible to use -N to say >= N
*sflags: command flags as string. See below for a table of flags.
*flags: flags as bitmask. Computed by Redis using the 'sflags' field.
*get_keys_proc: an optional function to get key arguments from a command.
* This is only used when thefollowing three fields are not
* enough to specify whatarguments are keys.
*first_key_index: first argument that is a key //第一个参数是key
*last_key_index: last argument that is a key //最后一个参数是key
*key_step: step to get all the keys from first to last argument. For instance//key的步长
* in MSET the step is two since arguments are key,val,key,val,...
*microseconds: microseconds of total execution time for this command.//执行这个命令花费的毫秒数
*calls: total number of calls of this command.
*
*The flags, microseconds and calls fields are computed by Redis and should //由redis通过sflags计算
* alwaysbe set to zero.//默认初始化为0
*
*Command flags are expressed using strings where every character represents
* aflag. Later the populateCommandTable() function will take care of
*populating the real 'flags' field using this characters.
*
*This is the meaning of the flags://标志代表的意思
*
* w:write command (may modify the key space). //写入命令 可能会修改keyspace
* r:read command (will never modify the keyspace). //读命令,永远不会修改keyspace
* m:may increase memory usage once called. Don't allow if out of memory.//执行时可能会占用很大的内存,执行的时候需要检测
* a:admin command, like SAVE or SHUTDOWN.//管理员命令
* p:Pub/Sub related command. //订阅发布命令
* f:force replication of this command, regardless of server.dirty.//无视server.dirty,强制执行
* s:command not allowed in scripts. //该命令不能用到脚本中
* R:random command. Command is not deterministic, that is, the same command //随机命令
* with the same arguments, with the same key space, may have different
* results. For instance SPOP and RANDOMKEY are two random commands.
* S:Sort command output array if called from script, so that the output//如果在脚本中对输出的排序,结果是确定性的
* isdeterministic.
* l:Allow command while loading the database. //允许载入数据库使用的命令
* t:Allow command while a slave has stale data but is not allowed to //允许子节点带有过期数据执行的命令,这种命令很少
* server this data. Normally no command is accepted in this condition
* butjust a few.
* M:Do not automatically propagate the command on MONITOR. //不能monitor模式下自动广播的命令
* k:Perform an implicit ASKING for this command, so the command will be
* accepted in cluster mode if the slot is marked as 'importing'.
* F:Fast command: O(1) or O(log(N)) command that should never delay //时间复杂度很少的命令
* itsexecution as long as the kernel scheduler is giving us time.
* Note that commands that may trigger a DEL as a side effect (like SET)
* arenot fast commands.
在redis.h文件定义了一个全局的 redisServer的服务器类,所有的管理都在这个类里面。
redisServer:
dict *commands; /*Command table */ 命令列表
dict *orig_commands; /*Command table before command renaming. */ 命令重命名前列表(命令备份列表)
//把命令映射到redisServer的commands和orig_commands里面。
/* Populates the Redis Command Tablestarting from the hard coded list
* wehave on top of redis.c file. */
void populateCommandTable(void) {
int j;
int numcommands = sizeof(redisCommandTable)/sizeof(struct redisCommand);//命令个数
for (j = 0; j < numcommands; j++) {
struct redisCommand *c = redisCommandTable+j;//根据索引获取到当前命令
char *f = c->sflags;
int retval1, retval2;
//这里根据设置设置标志
while(*f != '\0') {
switch(*f) {
case 'w': c->flags |= REDIS_CMD_WRITE; break;
case 'r': c->flags |= REDIS_CMD_READONLY; break;
case 'm': c->flags |= REDIS_CMD_DENYOOM; break;
case 'a': c->flags |= REDIS_CMD_ADMIN; break;
case 'p': c->flags |= REDIS_CMD_PUBSUB; break;
case 's': c->flags |= REDIS_CMD_NOSCRIPT; break;
case 'R': c->flags |= REDIS_CMD_RANDOM; break;
case 'S': c->flags |= REDIS_CMD_SORT_FOR_SCRIPT; break;
case 'l': c->flags |= REDIS_CMD_LOADING; break;
case 't': c->flags |= REDIS_CMD_STALE; break;
case 'M': c->flags |= REDIS_CMD_SKIP_MONITOR; break;
case 'k': c->flags |= REDIS_CMD_ASKING; break;
case 'F': c->flags |= REDIS_CMD_FAST; break;
default: redisPanic("Unsupported command flag"); break;
}
f++;
}
//添加到字典中
retval1 = dictAdd(server.commands, sdsnew(c->name), c);
/* Populate an additional dictionary that will be unaffected
* by rename-command statements in redis.conf. */
//在备份字典中也添加上
retval2 = dictAdd(server.orig_commands, sdsnew(c->name), c);
//断言测试
redisAssert(retval1 == DICT_OK && retval2 == DICT_OK);
}
}
//字符所对应的标志
/* Command flags. Please check the commandtable defined in the redis.c file
*for more information about the meaning of every flag. */
#define REDIS_CMD_WRITE 1 /* "w" flag */
#define REDIS_CMD_READONLY 2 /* "r" flag */
#define REDIS_CMD_DENYOOM 4 /* "m" flag */
#define REDIS_CMD_NOT_USED_1 8 /* no longer used flag */
#define REDIS_CMD_ADMIN 16 /* "a" flag */
#define REDIS_CMD_PUBSUB 32 /* "p" flag */
#define REDIS_CMD_NOSCRIPT 64 /* "s" flag */
#define REDIS_CMD_RANDOM 128 /* "R" flag */
#define REDIS_CMD_SORT_FOR_SCRIPT 256 /* "S" flag */
#define REDIS_CMD_LOADING 512 /* "l" flag */
#define REDIS_CMD_STALE 1024 /* "t" flag */
#define REDIS_CMD_SKIP_MONITOR 2048 /* "M" flag */
#define REDIS_CMD_ASKING 4096 /* "k" flag */
在redis.h文件定义了一个全局的 redisClient的客户端类。
redisClient:
robj *name; /* As setby CLIENT SETNAME */
sds querybuf;
int argc; //参数个数
robj **argv; //具体参数
struct redisCommand *cmd, *lastcmd; //关联的命令
命令解析流程:
1、redis.c/initServer->2、 redis.c/aeCreateFileEvent -> 3、networking.c/acceptTcpHandler -> networking.c/ 4、acceptCommonHandler-> 5、 networking.c/ createClient -> 6、ae.c/ aeCreateFileEvent ->7、networking.c/ readQueryFromClient
//接收数据的函数
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; //设置服务器当前的客户端
readlen = REDIS_IOBUF_LEN; //#defineREDIS_IOBUF_LEN (1024*16) /* Generic I/O buffer size */
/* If this is a multi bulk request, and we are processing a bulk reply
* that is large enough, try to maximize the probability that the query
* buffer contains exactly the SDS string representing the object, even
* at the risk of requiring more read(2) calls. This way the function
* processMultiBulkBuffer() can avoid copying buffers to create the
* Redis Object representing the argument. */
if (c->reqtype == REDIS_REQ_MULTIBULK && c->multibulklen&& c->bulklen != -1
&& c->bulklen >= REDIS_MBULK_BIG_ARG)
{
int remaining = (unsigned)(c->bulklen+2)-sdslen(c->querybuf);
if (remaining < readlen) readlen = remaining;
}
qblen = sdslen(c->querybuf);
if (c->querybuf_peak < qblen) c->querybuf_peak = qblen;
c->querybuf= sdsMakeRoomFor(c->querybuf, readlen);
//把数据读入自身的缓冲区
nread = read(fd, c->querybuf+qblen, readlen);
if (nread == -1) {
if (errno == EAGAIN) {
nread = 0;
} else {
redisLog(REDIS_VERBOSE, "Reading from client:%s",strerror(errno));
freeClient(c);
return;
}
}else if (nread == 0) {
redisLog(REDIS_VERBOSE, "Client closed connection");
freeClient(c);
return;
}
if (nread) {
sdsIncrLen(c->querybuf,nread);
c->lastinteraction = server.unixtime;
if (c->flags & REDIS_MASTER) c->reploff += nread;
server.stat_net_input_bytes += nread;
}else {
server.current_client = NULL;
return;
}
//最大的请求数据长度是1g
//server.client_max_querybuf_len = #define REDIS_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max //query buffer. */
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 querybuffer length: %s (qbuf initial bytes: %s)", ci, bytes);
sdsfree(ci);
sdsfree(bytes);
freeClient(c);
return;
}
//处理输入的缓冲区数据
processInputBuffer(c);
server.current_client = NULL;
}
//处理缓冲区数据
void processInputBuffer(redisClient *c) {
/*Keep processing while there is something in the input buffer */
//一直当缓冲区数据为空
while(sdslen(c->querybuf)) {
/* Return if clients are paused. */
if (!(c->flags & REDIS_SLAVE) && clientsArePaused())return;
/* Immediately abort if the client is in the middle of something. */
if (c->flags & REDIS_BLOCKED) return;
/* REDIS_CLOSE_AFTER_REPLY closes the connection once the reply is
* written to the client. Make sure to not let the reply grow after
* this flag has been set (i.e. don't process more commands). */
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;
/* Determine request type when unknown. */
//确定未知的请求类型
if (!c->reqtype) {
//如果请求的数据第一位是*代表是多条
if (c->querybuf[0] == '*') {
c->reqtype =REDIS_REQ_MULTIBULK;
} else {
//内联命令
c->reqtype =REDIS_REQ_INLINE;
}
}
//官方关于内联和多条的解释
//Multiple commands andpipelining
//Aclient can use the same connection in order to issue multiple commands.Pipelining is supported //so multiple commands can be sent with a single writeoperation by the client, without the need //to read the server reply of theprevious command before issuing the next one. All the replies can //be read atthe end.
//Formore information please check our page about Pipelining.
//一个客户端可以用同一个连接发送多个命令顺序执行,顺序执行支持多个命令,客户端不用等到前一个命令//回复,所有的操作可以在最后返回
//Inline Commands
//Sometimesyou have only telnet in your hands and you need to send a command tothe Redis server. //While the Redis protocol is simple to implement it is notideal to use in interactive sessions, //and redis-cli may not alwaysbe available. For this reason Redis also accepts commands in a //special waythat is designed for humans, and is called theinline command format.
//Thefollowing is an example of a server/client chat using an inline command (theserver chat starts //with S:, the client chat with C:)
//C:PING
//S:+PONG
//Thefollowing is another example of an inline command returning an integer:
//C:EXISTS somekey
//S::0
//有时候你可能会用类似telnet客户端给redis服务器发送命令,客户端可能有时候不能用,基于这种原因redis服务器支持这种交互的内联命令
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. */
//处理命令
if (processCommand(c) == REDIS_OK)
resetClient(c);
}
}
}
//处理内联命令
int processInlineBuffer(redisClient *c) {
char *newline;
int argc, j;
sds *argv, aux;
size_t querylen;
/*Search for end of line */
//搜索到第一个\n结束
newline = strchr(c->querybuf,'\n');
/* Nothing to do without a \r\n */
if (newline == NULL) {
if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) {
addReplyError(c,"Protocol error: too big inline request");
setProtocolError(c,0);
}
return REDIS_ERR;
}
/* Handle the \r\n case. */
if (newline && newline != c->querybuf && *(newline-1)== '\r')
newline--;
/*Split the input buffer up to the \r\n */
querylen = newline-(c->querybuf);
aux =sdsnewlen(c->querybuf,querylen);
//分割数据到argv
argv = sdssplitargs(aux,&argc);
sdsfree(aux);
if (argv == NULL) {
addReplyError(c,"Protocol error: unbalanced quotes inrequest");
setProtocolError(c,0);
return REDIS_ERR;
}
/* Newline from slaves can be used to refresh the last ACK time.
* This is useful for a slave to ping back while loading a big
* RDB file. */
if (querylen == 0 && c->flags & REDIS_SLAVE)
c->repl_ack_time = server.unixtime;
/*Leave data after the first line of the query in the buffer */
//删除已经读的
sdsrange(c->querybuf,querylen+2,-1);
//对每一个参数分配一个字符串对象,并且赋值
/*Setup argv array on client structure */
//如果个数大于0,咱就是否以前有的数据,然后重新分配空间
if (argc) {
if (c->argv) zfree(c->argv);
c->argv = zmalloc(sizeof(robj*)*argc);
}
/* Create redis objects for all arguments. */
for (c->argc = 0, j = 0; j < argc; j++) {
if (sdslen(argv[j])) {
//保存数据都argv
c->argv[c->argc] = createObject(REDIS_STRING,argv[j]);
c->argc++;
} else {
sdsfree(argv[j]);
}
}
//把临时的释放了
zfree(argv);
return REDIS_OK;
}
//处理多条的
int processMultibulkBuffer(redisClient *c){
char *newline = NULL;
int pos = 0, ok;
long long ll;
if (c->multibulklen == 0) {
/* The client should have been reset */
redisAssertWithInfo(c,NULL,c->argc == 0);
//*3\r\n
//$3\r\n
//SET\r\n
//$5\r\n
//HENRY\r\n
//$8\r\n
//HENRYFAN\r\n
/* Multi bulk length cannot be read without a \r\n */
//如果找不到\r 说明协议是有问题的
newline = strchr(c->querybuf,'\r');
if (newline == NULL) {
//如果长度大于#define REDIS_INLINE_MAX_SIZE (1024*64) /* Max size of inline reads */ 64k
if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) {
addReplyError(c,"Protocolerror: too big mbulk count string");
setProtocolError(c,0);
}
return REDIS_ERR;
}
//判断buff的长度是否符合规范
/* Buffer should also contain \n */
if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2))
return REDIS_ERR;
/* We know for sure there is a whole line since newline != NULL,
* so go ahead and find out the multi bulk length. */
//断言此处第一个字符是 *
redisAssertWithInfo(c,NULL,c->querybuf[0] == '*');
//获取到 第一个字符串并且转换成一个长整形
ok = string2ll(c->querybuf+1,newline-(c->querybuf+1),&ll);
//如果转换的字符串为空或者大于1m
if (!ok || ll > 1024*1024) {
addReplyError(c,"Protocol error: invalid multibulk length");
setProtocolError(c,pos);
return REDIS_ERR;
}
//定位新的位置
pos = (newline-c->querybuf)+2;
if (ll <= 0) {
sdsrange(c->querybuf,pos,-1);
return REDIS_OK;
}
//这里是数据的个数
c->multibulklen = ll;
//释放以前的
/* Setup argv array on client structure */
if (c->argv) zfree(c->argv);
//按数据个数来分配缓冲区
c->argv= zmalloc(sizeof(robj*)*c->multibulklen);
}
redisAssertWithInfo(c,NULL,c->multibulklen > 0);
while(c->multibulklen) {
/* Read bulk length if unknown */
if (c->bulklen == -1) {
newline = strchr(c->querybuf+pos,'\r');
if (newline == NULL) {
if (sdslen(c->querybuf) >REDIS_INLINE_MAX_SIZE) {
addReplyError(c,
"Protocol error:too big bulk count string");
setProtocolError(c,0);
return REDIS_ERR;
}
break;
}
//检测数据正确性
/* Buffer should also contain \n */
if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2))
break;
//长度必须以 $开头
if (c->querybuf[pos] != '$') {
addReplyErrorFormat(c,
"Protocol error:expected '$', got '%c'",
c->querybuf[pos]);
setProtocolError(c,pos);
return REDIS_ERR;
}
//截取参数长度
ok =string2ll(c->querybuf+pos+1,newline-(c->querybuf+pos+1),&ll);
if (!ok || ll < 0 || ll > 512*1024*1024) {
addReplyError(c,"Protocolerror: invalid bulk length");
setProtocolError(c,pos);
return REDIS_ERR;
}
pos += newline-(c->querybuf+pos)+2;
if (ll >= REDIS_MBULK_BIG_ARG) {
size_t qblen;
/* If we are going to read alarge object from network
* try to make it likely thatit will start at c->querybuf
* boundary so that we canoptimize object creation
* avoiding a large copy ofdata. */
sdsrange(c->querybuf,pos,-1);
pos = 0;
qblen = sdslen(c->querybuf);
/* Hint the sds library aboutthe amount of bytes this string is
* going to contain. */
if (qblen < (size_t)ll+2)
c->querybuf =sdsMakeRoomFor(c->querybuf,ll+2-qblen);
}
//设置参数长度
c->bulklen = ll;
}
//开始截取参数
/* Read bulk argument */
if (sdslen(c->querybuf)-pos < (unsigned)(c->bulklen+2)) {
/* Not enough data (+2 == trailing \r\n) */
break;
} else {
/* Optimization: if the buffer contains JUST our bulk element
* instead of creating a new object by *copying* the sds we
* just use the current sds string. */
if (pos == 0 &&
c->bulklen >=REDIS_MBULK_BIG_ARG &&
(signed) sdslen(c->querybuf)== c->bulklen+2)
{
c->argv[c->argc++] =createObject(REDIS_STRING,c->querybuf);
sdsIncrLen(c->querybuf,-2);/* remove CRLF */
c->querybuf = sdsempty();
/* Assume that if we saw a fatargument we'll see another one
* likely... */
c->querybuf =sdsMakeRoomFor(c->querybuf,c->bulklen+2);
pos = 0;
} else {
//保存数据到argv
c->argv[c->argc++] =
createStringObject(c->querybuf+pos,c->bulklen);
pos += c->bulklen+2;
}
c->bulklen = -1;
c->multibulklen--;
}
}
/* Trim to pos */
if (pos) sdsrange(c->querybuf,pos,-1);
/* We're done when c->multibulk == 0 */
if (c->multibulklen == 0) return REDIS_OK;
/* Still not read to process the command */
return REDIS_ERR;
}
//处理和过滤命令
/* If this function gets called we alreadyread a whole
*command, arguments are in the client argv/argc fields.
*processCommand() execute the command or prepare the
*server for a bulk read from the client.
*
* If1 is returned the client is still alive and valid and
*other operations can be performed by the caller. Otherwise
* if0 is returned the client was destroyed (i.e. after QUIT). */
//在这个函数,我们已经获取到了一个完整的命令,参数等信息保存在argv/argc字段里面,
//返回1标示客户端仍然存活,caller可以继续执行其他命令,返回0标示客户端已经销毁
int processCommand(redisClient *c) {
/* The QUIT command is handled separately. Normal command procs will
* go through checking for replication and QUIT will cause trouble
* when FORCE_REPLICATION is enabled and would be implemented in
* a regular command proc. */
//是退出命令就直接处理了
if (!strcasecmp(c->argv[0]->ptr,"quit")) {
addReply(c,shared.ok);
c->flags |= REDIS_CLOSE_AFTER_REPLY;
return REDIS_ERR;
}
/* Now lookup the command and check ASAP about trivial error conditions
* such as wrong arity, bad command name and so forth. */
//在集合里面查找命令
c->cmd= c->lastcmd = lookupCommand(c->argv[0]->ptr);
//命令没有找到
if(!c->cmd) {
//如果是事务,就设置事务标志
flagTransaction(c);
//回复 未知命令
addReplyErrorFormat(c,"unknown command '%s'",
(char*)c->argv[0]->ptr);
return REDIS_OK;
}else if ((c->cmd->arity > 0 && c->cmd->arity !=c->argc) ||//错误的参数个数
(c->argc <-c->cmd->arity)) {
flagTransaction(c);
addReplyErrorFormat(c,"wrong number of arguments for '%s'command",
c->cmd->name);
return REDIS_OK;
}
//检测是否授权
/* Check if the user is authenticated */
if (server.requirepass && !c->authenticated &&c->cmd->proc != authCommand)
{
flagTransaction(c);
addReply(c,shared.noautherr);
return REDIS_OK;
}
//集群模式处理
/* If cluster is enabled perform the cluster redirection here.
* However we don't perform the redirection if: //我们不进行转向操作
* 1) The sender of this command is our master.//发送命令是主节点
* 2) The command has no key arguments. */ //命令没有参数
if (server.cluster_enabled &&
!(c->flags & REDIS_MASTER) &&
!(c->flags & REDIS_LUA_CLIENT &&
server.lua_caller->flags & REDIS_MASTER) &&
!(c->cmd->getkeys_proc == NULL && c->cmd->firstkey== 0))
{
inthashslot;
if (server.cluster->state != REDIS_CLUSTER_OK) {
flagTransaction(c);
clusterRedirectClient(c,NULL,0,REDIS_CLUSTER_REDIR_DOWN_STATE);
return REDIS_OK;
} else {
int error_code;
clusterNode *n =getNodeByQuery(c,c->cmd,c->argv,c->argc,&hashslot,&error_code);
if (n == NULL || n != server.cluster->myself) {
flagTransaction(c);
clusterRedirectClient(c,n,hashslot,error_code);
return REDIS_OK;
}
}
}
//如果设置了最大内存
/* Handle the maxmemory directive.
*
* First we try to free some memory if possible (if there are volatile
* keys in the dataset). If there are not the only thing we can do
* is returning an error. */
//首先我们尽可能尝试是否一些内存,
if(server.maxmemory) {
int retval = freeMemoryIfNeeded();
/* freeMemoryIfNeeded may flush slave output buffers. This may result
* into a slave, that may be the active client, to be freed. */
if (server.current_client == NULL) return REDIS_ERR;
/* It was impossible to free enough memory, and the command the client
* is trying to execute is denied during OOM conditions? Error. */
//如果命令会占用很大的内存,咱就返回oom了
if ((c->cmd->flags & REDIS_CMD_DENYOOM) && retval ==REDIS_ERR) {
flagTransaction(c);
addReply(c, shared.oomerr);
return REDIS_OK;
}
}
//如果是主服务器,并且服务器之前执行了bgsave发生了错误,咱就返回
/* Don't accept write commands if there are problems persisting on disk
* and if this is a master instance. */
if (((server.stop_writes_on_bgsave_err &&
server.saveparamslen > 0 &&
server.lastbgsave_status == REDIS_ERR) ||
server.aof_last_write_status == REDIS_ERR) &&
server.masterhost == NULL &&
(c->cmd->flags & REDIS_CMD_WRITE ||
c->cmd->proc == pingCommand))
{
flagTransaction(c);
if (server.aof_last_write_status == REDIS_OK)
addReply(c, shared.bgsaveerr);
else
addReplySds(c,
sdscatprintf(sdsempty(),"-MISCONFErrors writing to the AOF file: %s\r\n",
strerror(server.aof_last_write_errno)));
return REDIS_OK;
}
// min-slaves-to-write已经打开,如果没有good 从服务器,拒绝执行写命令
/* Don't accept write commands if there are not enough good slaves and
* user configured the min-slaves-to-write option. */
if (server.masterhost == NULL &&
server.repl_min_slaves_to_write &&
server.repl_min_slaves_max_lag &&
c->cmd->flags & REDIS_CMD_WRITE &&
server.repl_good_slaves_count < server.repl_min_slaves_to_write)
{
flagTransaction(c);
addReply(c, shared.noreplicaserr);
return REDIS_OK;
}
//如果只有一个制度的从服务器,拒绝执行写命令
/* Don't accept write commands if this is a read only slave. But
* accept write commands if this is our master. */
if (server.masterhost && server.repl_slave_ro &&
!(c->flags & REDIS_MASTER) &&
c->cmd->flags & REDIS_CMD_WRITE)
{
addReply(c, shared.roslaveerr);
return REDIS_OK;
}
//对于订阅发布上下文,仅仅只允许执行SUBSCRIBE 和 UNSUBSCRIBE
/* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */
if (c->flags & REDIS_PUBSUB &&
c->cmd->proc != pingCommand &&
c->cmd->proc != subscribeCommand &&
c->cmd->proc != unsubscribeCommand &&
c->cmd->proc != psubscribeCommand &&
c->cmd->proc != punsubscribeCommand) {
addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUITallowed in this context");
return REDIS_OK;
}
//当slave-serve-stale-data未启用并且咱是从服务器和主服务器断开连接,那么只允许执行INFO slaveof
/* Only allow INFO and SLAVEOF when slave-serve-stale-data is no and
* we are a slave with a broken link with master. */
if (server.masterhost && server.repl_state != REDIS_REPL_CONNECTED&&
server.repl_serve_stale_data == 0 &&
!(c->cmd->flags & REDIS_CMD_STALE))
{
flagTransaction(c);
addReply(c, shared.masterdownerr);
return REDIS_OK;
}
//如果是启动导入数据,如果命令没有REDIS_CMD_LOADING 咱们返回
/* Loading DB? Return an error if the command has not the
* REDIS_CMD_LOADING flag. */
if (server.loading && !(c->cmd->flags &REDIS_CMD_LOADING)) {
addReply(c, shared.loadingerr);
return REDIS_OK;
}
//lua脚本超时,只允许下面执行:authCommand replconfCommand shutdownCommand
/* Lua script too slow? Only allow a limited number of commands. */
if (server.lua_timedout &&
c->cmd->proc != authCommand &&
c->cmd->proc != replconfCommand &&
!(c->cmd->proc == shutdownCommand &&
c->argc == 2 &&
tolower(((char*)c->argv[1]->ptr)[0]) == 'n') &&
!(c->cmd->proc == scriptCommand &&
c->argc == 2 &&
tolower(((char*)c->argv[1]->ptr)[0]) == 'k'))
{
flagTransaction(c);
addReply(c, shared.slowscripterr);
return REDIS_OK;
}
/* Exec the command */
if (c->flags & REDIS_MULTI &&
c->cmd->proc != execCommand && c->cmd->proc !=discardCommand &&
c->cmd->proc != multiCommand && c->cmd->proc !=watchCommand)
{
//如果是事务命令,除了上面4个,都要入事务队列里面
queueMultiCommand(c);
addReply(c,shared.queued);
} else{
//执行常规命令
call(c,REDIS_CALL_FULL);
c->woff = server.master_repl_offset;
if (listLength(server.ready_keys))
handleClientsBlockedOnLists();
}
return REDIS_OK;
}
//查找命令
struct redisCommand *lookupCommand(sdsname) {
return dictFetchValue(server.commands, name);
}
//其实就是在字典中根据名字查找
void *dictFetchValue(dict *d, const void*key) {
dictEntry *he;
// T = O(1)
he = dictFind(d,key);
return he ? dictGetVal(he) : NULL;
}
//执行命令
/* Call() is the core of Redis execution ofa command */
void call(redisClient *c, int flags) {
long long dirty, start, duration;
int client_old_flags = c->flags;
//在monitor模式下,当不是从aof读取的命令,发送给客户端
/* Sent the command to clients in MONITOR mode, only if the commands are
* not generated from reading an AOF. */
if (listLength(server.monitors) &&
!server.loading &&
!(c->cmd->flags & (REDIS_CMD_SKIP_MONITOR|REDIS_CMD_ADMIN)))
{
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
}
/* Call the command. */
c->flags &= ~(REDIS_FORCE_AOF|REDIS_FORCE_REPL);
redisOpArrayInit(&server.also_propagate);
//备份旧的计时器值
dirty= server.dirty;
//执行前的时间
start= ustime();
//执行命令
c->cmd->proc(c);
//执行所耗费的时间
duration= ustime()-start;
//执行后的计时器
dirty = server.dirty-dirty;
if (dirty < 0) dirty = 0;
//不统计lua的命令
/* When EVAL is called loading the AOF we don't want commands called
* from Lua to go into the slowlog or to populate statistics. */
if (server.loading && c->flags & REDIS_LUA_CLIENT)
flags &= ~(REDIS_CALL_SLOWLOG | REDIS_CALL_STATS);
//如果客户端是lua,根据命令标志和客户端标志打开传播标志
/* If the caller is Lua, we want to force the EVAL caller to propagate
* the script if the command flag or client flag are forcing the
* propagation. */
if (c->flags & REDIS_LUA_CLIENT && server.lua_caller) {
if (c->flags & REDIS_FORCE_REPL)
server.lua_caller->flags |= REDIS_FORCE_REPL;
if (c->flags & REDIS_FORCE_AOF)
server.lua_caller->flags |= REDIS_FORCE_AOF;
}
//需要的时候记录命令日志
/* Log the command into the Slow log if needed, and populate the
* per-command statistics that we show in INFO commandstats. */
if (flags & REDIS_CALL_SLOWLOG && c->cmd->proc !=execCommand) {
char *latency_event = (c->cmd->flags & REDIS_CMD_FAST) ?
"fast-command" : "command";
latencyAddSampleIfNeeded(latency_event,duration/1000);
slowlogPushEntryIfNeeded(c->argv,c->argc,duration);
}
//命令执行时间统计调用次数加1
if (flags & REDIS_CALL_STATS) {
c->cmd->microseconds += duration;
c->cmd->calls++;
}
//传播和aof处理
/* Propagate the command into the AOF and replication link */
if (flags & REDIS_CALL_PROPAGATE) {
int flags = REDIS_PROPAGATE_NONE;
if (c->flags & REDIS_FORCE_REPL) flags |= REDIS_PROPAGATE_REPL;
if (c->flags & REDIS_FORCE_AOF) flags |= REDIS_PROPAGATE_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);
}
/* Restore the old FORCE_AOF/REPL flags, since call can be executed
* recursively. */
c->flags &= ~(REDIS_FORCE_AOF|REDIS_FORCE_REPL);
c->flags |= client_old_flags &(REDIS_FORCE_AOF|REDIS_FORCE_REPL);
/* Handle the alsoPropagate() API to handle commands that want topropagate
* multiple separated commands. */
if (server.also_propagate.numops) {
int j;
redisOp *rop;
for (j = 0; j < server.also_propagate.numops; j++) {
rop = &server.also_propagate.ops[j];
propagate(rop->cmd, rop->dbid, rop->argv, rop->argc,rop->target);
}
redisOpArrayFree(&server.also_propagate);
}
server.stat_numcommands++;
}