redis客户端向服务器发送命令时, redis服务器都需要对命令进行解析,然后调用对应的命令处理函数进行处理.需要说明的是redis的任何协议命令均以\r\n
结束.
在之前客户端的连接流程中介绍中, redis服务器会为新连接的客户端创建一个文件事件对象,并监听其可读状态,该文件事件对象的触发回调函数为readQueryFromClient
,即假设客户端已经通过如下telnet命令连接到了redis服务器:
telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
接着在控制终端继续输入redis set命令(这里以set命令为例来触发客户端的连接):
set key 3
redis服务器的select系统调用将返回,并导致函数readQueryFromClient
被调用,该函数是读取客户端命令输入的通用函数,其实现为(redis.c):
832 static void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
833 redisClient *c = (redisClient*) privdata;
834 char buf[REDIS_QUERYBUF_LEN];
835 int nread;
836 REDIS_NOTUSED(el);
837 REDIS_NOTUSED(mask);
838
839 nread = read(fd, buf, REDIS_QUERYBUF_LEN);
840 if (nread == -1) {
841 if (errno == EAGAIN) {
842 nread = 0;
843 } else {
844 redisLog(REDIS_DEBUG, "Reading from client: %s",strerror(errno));
845 freeClient(c);
846 return;
847 }
848 } else if (nread == 0) {
849 redisLog(REDIS_DEBUG, "Client closed connection");
850 freeClient(c);
851 return;
852 }
853 if (nread) {
854 c->querybuf = sdscatlen(c->querybuf, buf, nread);
855 c->lastinteraction = time(NULL);
856 } else {
857 return;
858 }
859
860 again:
861 if (c->bulklen == -1) {
862 /* Read the first line of the query */
863 char *p = strchr(c->querybuf,'\n');
864 size_t querylen;
865 if (p) {
866 sds query, *argv;
867 int argc, j;
868
869 query = c->querybuf;
870 c->querybuf = sdsempty();
871 querylen = 1+(p-(query));
872 if (sdslen(query) > querylen) {
873 /* leave data after the first line of the query in the buffer */
874 c->querybuf = sdscatlen(c->querybuf,query+querylen,sdslen(query)-querylen);
875 }
876 *p = '\0'; /* remove "\n" */
877 if (*(p-1) == '\r') *(p-1) = '\0'; /* and "\r" if any */
878 sdsupdatelen(query);
879
880 /* Now we can split the query in arguments */
881 if (sdslen(query) == 0) {
882 /* Ignore empty query */
883 sdsfree(query);
884 return;
885 }
886 argv = sdssplitlen(query,sdslen(query)," ",1,&argc);
887 sdsfree(query);
888 if (argv == NULL) oom("Splitting query in token");
889 for (j = 0; j < argc && j < REDIS_MAX_ARGS; j++) {
890 if (sdslen(argv[j])) {
891 c->argv[c->argc] = argv[j];
892 c->argc++;
893 } else {
894 sdsfree(argv[j]);
895 }
896 }
897 free(argv);
898 /* Execute the command. If the client is still valid
899 * after processCommand() return and there is something
900 * on the query buffer try to process the next command. */
901 if (processCommand(c) && sdslen(c->querybuf)) goto again;
902 return;
903 } else if (sdslen(c->querybuf) >= 1024) {
904 redisLog(REDIS_DEBUG, "Client protocol error");
905 freeClient(c);
906 return;
907 }
908 } else {
909 /* Bulk read handling. Note that if we are at this point
910 the client already sent a command terminated with a newline,
911 we are reading the bulk data that is actually the last
912 argument of the command. */
913 int qbl = sdslen(c->querybuf);
914
915 if (c->bulklen <= qbl) {
916 /* Copy everything but the final CRLF as final argument */
917 c->argv[c->argc] = sdsnewlen(c->querybuf,c->bulklen-2);
918 c->argc++;
919 c->querybuf = sdsrange(c->querybuf,c->bulklen,-1);
920 processCommand(c);
921 return;
922 }
923 }
924 }
Line839通过read系统调用读取客户端输入,读取操作将是非阻塞的,如果没有错误发生,Line854将读取到的数据保存在客户端对象的字段querybuf
中, 共后续分析命令及其参数使用.函数sdscatlen
是操作sds
系列函数簇之一,其逻辑是把字节追加到指定的sds
之中,如果指定的sds
空间不够,则自动调整其容量,最后该函数返回存储了追加数据的sds
对象.函数sdscatlen
的实现为(sds.c):
114 sds sdscatlen(sds s, void *t, size_t len) {
115 struct sdshdr *sh;
116 size_t curlen = sdslen(s);
117
118 s = sdsMakeRoomFor(s,len);
119 if (s == NULL) return NULL;
120 sh = (void*) (s-(sizeof(struct sdshdr)));
121 memcpy(s+curlen, t, len);
122 sh->len = curlen+len;
123 sh->free = sh->free-len;
124 s[curlen+len] = '\0';
125 return s;
126 }
Line118为sds
对象分配新的空间来存储追加的数据, 如果sds
对象内的空闲空间不能存储要追加的数据,该函数将分配新空间,如果sds
对象内的空闲空间可以存储要追加的数据,则不需要分配新空间.函数sdsMakeRoomFor
的实现为(sds.c):
94 static sds sdsMakeRoomFor(sds s, size_t addlen) {
95 struct sdshdr *sh, *newsh;
96 size_t free = sdsavail(s);
97 size_t len, newlen;
98
99 if (free >= addlen) return s;
100 len = sdslen(s);
101 sh = (void*) (s-(sizeof(struct sdshdr)));
102 newlen = (len+addlen)*2;
103 newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1);
104 #ifdef SDS_ABORT_ON_OOM
105 if (newsh == NULL) sdsOomAbort();
106 #else
107 if (newsh == NULL) return NULL;
108 #endif
109
110 newsh->free = newlen - len;
111 return newsh->buf;
112 }
Line96获得sds
对象的空闲空间大小,Line99比较其空闲空间和要追加的数据量的大小,如果空闲空间能存储要追加的数据,则直接返回.Line100:103动态分配新空间,使新空间将存储之前sds
对象已存储的数据,又存储要追加的数据,除此之前新空间还预留了空间.Line103库函数realloc
将把sds
对象已有数据拷贝到新分配空间,并释放之前的sds
对象,注意这里的拷贝将包括sds
的元数据,即包括sdshdr
对象信息.Line110调整空闲空间的大小,即新分配的大小减去之前sds
对象中已有数据的长度.Line111返回sds
对象,sds
对象其实就是指向类型sdshdr
对象的buf
字段.
回到函数sdscatlen
,Line121:123将要追加的数据存储在新的sds对象的空闲空间,并调整sds对象的存储的数据长度和空闲空间的大小.回到函数readQueryFromClient
,Line854字段querybuf
将指向存储了客户端命令及其参数数据的sds
对象.Line861判断,对于非bulk类型的redis命令或者bulk型的命令的首次命令读取, 客户端对象的bulklen
字段的值为-1.Line862:907解析读取到字段querybuf
中的redis命令及其参数.Line863定位到新行字节’\n’,如果找到了新行字节说明收入数据中肯定包含了redis命令字符串,该命令的参数可能输入完整,也可能没有输入完整.具体而言,对于bulk类型的redis命令,存在参数没有输入完整的可能性,对于非bulk类型的redis命令,通常已经是完整的输入.如果输入中没有定位到新行字节,说明redis命令字符窜都没有输入完整,需要返回主事件循环, 继续调用select监听. Line869:870类型sds
的对象query指向客户端对象的字段querybuf
,同时将该字段指向空的sds
对象.Line871计算sds
对象保存的数据的起始位置到新行字节的数据长度. Line872:875的条件分支得到满足,则说明新行字节后面还有数据,Line874的含义是将客户端对象的字段querybuf
指向新行字节后面的数据.Line876:877类型为char *的局部变量p指向新行字节开始的数据, 将新行字节替换为0, 如果新行字节前面是回车字节,也替换为0.因为局部sds
对象query指向的数据的’\r’和’\n’被替换为0, Line878调用函数sdsupdatelen
调整其长度,其实现为(sds.c):
87 void sdsupdatelen(sds s) {
88 struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
89 int reallen = strlen(s);
90 sh->free += (sh->len-reallen);
91 sh->len = reallen;
92 }
从实现可以看出,该函数只能正确处理以’\0’结束的C语言的字符窜.
回到函数readQueryFromClient
, Line880:885如果经过长度调整以后,类型sds
的局部对象query数据内容为空,返回主事件循环,继续调用select监听.Line886调用函数sdssplitlen
使用空格分割字符分割输入中的redis命令及其参数,该函数的实现为(sds.c):
266 sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
267 int elements = 0, slots = 5, start = 0, j;
268
269 sds *tokens = malloc(sizeof(sds)*slots);
270 #ifdef SDS_ABORT_ON_OOM
271 if (tokens == NULL) sdsOomAbort();
272 #endif
273 if (seplen < 1 || len < 0 || tokens == NULL) return NULL;
274 for (j = 0; j < (len-(seplen-1)); j++) {
275 /* make sure there is room for the next element and the final one */
276 if (slots < elements+2) {
277 slots *= 2;
278 sds *newtokens = realloc(tokens,sizeof(sds)*slots);
279 if (newtokens == NULL) {
280 #ifdef SDS_ABORT_ON_OOM
281 sdsOomAbort();
282 #else
283 goto cleanup;
284 #endif
285 }
286 tokens = newtokens;
287 }
288 /* search the separator */
289 if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {
290 tokens[elements] = sdsnewlen(s+start,j-start);
291 if (tokens[elements] == NULL) {
292 #ifdef SDS_ABORT_ON_OOM
293 sdsOomAbort();
294 #else
295 goto cleanup;
296 #endif
297 }
298 elements++;
299 start = j+seplen;
300 j = j+seplen-1; /* skip the separator */
301 }
302 }
303 /* Add the final element. We are sure there is room in the tokens array. */
304 tokens[elements] = sdsnewlen(s+start,len-start);
305 if (tokens[elements] == NULL) {
306 #ifdef SDS_ABORT_ON_OOM
307 sdsOomAbort();
308 #else
309 goto cleanup;
310 #endif
311 }
312 elements++;
313 *count = elements;
314 return tokens;
315
316 #ifndef SDS_ABORT_ON_OOM
317 cleanup:
318 {
319 int i;
320 for (i = 0; i < elements; i++) sdsfree(tokens[i]);
321 free(tokens);
322 return NULL;
323 }
324 #endif
325 }
该函数的基本思想是,Line269先预先分配一个类型sds
的数组,用来指向redis命令及其参数,预分配的数组大小为5,因为大部分redis命令及其参数都小于这个数目.如果个别redis命令的参数比较多,Line278动态的调整这个数组大小.Line304数组中的sds
对象指向调用函数sdsnewlen
新创建的sds
对象,这些新创建的sds
对象存储redis命令及其参数.
回到函数readQueryFromClient
, 执行完Lline886以后, 局部变量argc保存分割的参数的数量(包括redis命令),局部对象argv指向分割的sds
对象,这些对象是动态分配的,因此,Line887调用函数sdsfree
可以释放原始的sds
对象了.Line889:897将局部对象argv中指向的sds
对象记录在客户端对象的字段argv中,参数个数记录到argc字段中,完成之后释放局部管理数组argv.至此,本次调用read系统调用读取的数据中,新行字节以前的数据解析已经结束,其中肯定是包含了redis命令, 其参数可能是完整的,也可能不是完整的.Line901调用函数processCommand
准备处理具体的redis命令,如果该函数返回的时候,客户端对象的字段querybuf
里面仍然有数据,则跳转到Line860处继续刚才的解析处理.Lline909:922客户端对象的bulklen
的值不是-1, 只可能是bulk类型的redis命令, 并且之前的读取到了redis命令及其部分参数值,Line913判断客户端对象接收缓存中的数据长度,(可能是本次读取的数据长度,也可能是之前读取的数据和本次读取的数据),Lline915:922如果客户端缓存对象中累计读取的长度满足bulk类型字节数据要求,则继续更新客户端对象的argv和argc,并调用函数processCommand
处理,此时传给该函数的客户端对象已经准备完毕redis命令及其参数.
函数processCommand
的实现为(redis.c):
774 /* If this function gets called we already read a whole
775 * command, argments are in the client argv/argc fields.
776 * processCommand() execute the command or prepare the
777 * server for a bulk read from the client.
778 *
779 * If 1 is returned the client is still alive and valid and
780 * and other operations can be performed by the caller. Otherwise
781 * if 0 is returned the client was destroied (i.e. after QUIT). */
782 static int processCommand(redisClient *c) {
783 struct redisCommand *cmd;
784
785 sdstolower(c->argv[0]);
786 /* The QUIT command is handled as a special case. Normal command
787 * procs are unable to close the client connection safely */
788 if (!strcmp(c->argv[0],"quit")) {
789 freeClient(c);
790 return 0;
791 }
792 cmd = lookupCommand(c->argv[0]);
793 if (!cmd) {
794 addReplySds(c,sdsnew("-ERR unknown command\r\n"));
795 resetClient(c);
796 return 1;
797 } else if ((cmd->arity > 0 && cmd->arity != c->argc) ||
798 (c->argc < -cmd->arity)) {
799 addReplySds(c,sdsnew("-ERR wrong number of arguments\r\n"));
800 resetClient(c);
801 return 1;
802 } else if (cmd->type == REDIS_CMD_BULK && c->bulklen == -1) {
803 int bulklen = atoi(c->argv[c->argc-1]);
804
805 sdsfree(c->argv[c->argc-1]);
806 if (bulklen < 0 || bulklen > 1024*1024*1024) {
807 c->argc--;
808 c->argv[c->argc] = NULL;
809 addReplySds(c,sdsnew("-ERR invalid bulk write count\r\n"));
810 resetClient(c);
811 return 1;
812 }
813 c->argv[c->argc-1] = NULL;
814 c->argc--;
815 c->bulklen = bulklen+2; /* add two bytes for CR+LF */
816 /* It is possible that the bulk read is already in the
817 * buffer. Check this condition and handle it accordingly */
818 if ((signed)sdslen(c->querybuf) >= c->bulklen) {
819 c->argv[c->argc] = sdsnewlen(c->querybuf,c->bulklen-2);
820 c->argc++;
821 c->querybuf = sdsrange(c->querybuf,c->bulklen,-1);
822 } else {
823 return 1;
824 }
825 }
826 /* Exec the command */
827 cmd->proc(c);
828 resetClient(c);
829 return 1;
830 }
Line785将redis命令转换为小写格式, 所以客户端输入的redis命令字符窜可以是大写格式也可以是小写格式, 客户端对象的字段argv[0]指向的sds
保存redis命令字符串,argv[1],argv[2]等依次是其命令参数.Line788:791如果是客户端发送了quit
命令主动结束连接,则调用函数freeClient
释放客户端相关的所有资源,并关闭TCP连接,其实现为(redis.c):
701 static void freeClient(redisClient *c) {
702 listNode *ln;
703
704 aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
705 aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
706 sdsfree(c->querybuf);
707 listRelease(c->reply);
708 freeClientArgv(c);
709 close(c->fd);
710 ln = listSearchKey(server.clients,c);
711 assert(ln != NULL);
712 listDelNode(server.clients,ln);
713 free(c);
714 }
调用函数aeDeleteFileEvent
释放该客户端对象相关的文件事件对象.释放字段querybuf
指向的输入接收缓存区.调用函数listRelease
释放客户端对象的响应列表.调用函数freeClientArgv
释放客户端对象的redis命令及其参数.系统调用close
关闭TCP连接.Line710:712将该客户端对象从全局链表中删除.因为客户端对象都是在堆上动态分配的,Line713释放其内存.
回到函数processCommand
, Line792查找命令字符串对应的处理函数,函数lookupCommand
的实现为(redis.c):
759 static struct redisCommand *lookupCommand(char *name) {
760 int j = 0;
761 while(cmdTable[j].name != NULL) {
762 if (!strcmp(name,cmdTable[j].name)) return &cmdTable[j];
763 j++;
764 }
765 return NULL;
766 }
通过比较命令字符串,如果找到匹配的,则返回该命令处理函数.全局对象cmdTable
定义为(redis.c):
180 static struct redisCommand cmdTable[] = {
181 {"get",getCommand,2,REDIS_CMD_INLINE},
182 {"set",setCommand,3,REDIS_CMD_BULK},
183 {"setnx",setnxCommand,3,REDIS_CMD_BULK},
184 {"del",delCommand,2,REDIS_CMD_INLINE},
185 {"exists",existsCommand,2,REDIS_CMD_INLINE},
186 {"incr",incrCommand,2,REDIS_CMD_INLINE},
187 {"decr",decrCommand,2,REDIS_CMD_INLINE},
188 {"rpush",rpushCommand,3,REDIS_CMD_BULK},
189 {"lpush",lpushCommand,3,REDIS_CMD_BULK},
190 {"rpop",rpopCommand,2,REDIS_CMD_INLINE},
191 {"lpop",lpopCommand,2,REDIS_CMD_INLINE},
192 {"llen",llenCommand,2,REDIS_CMD_INLINE},
193 {"lindex",lindexCommand,3,REDIS_CMD_INLINE},
194 {"lset",lsetCommand,4,REDIS_CMD_BULK},
195 {"lrange",lrangeCommand,4,REDIS_CMD_INLINE},
196 {"ltrim",ltrimCommand,4,REDIS_CMD_INLINE},
197 {"sadd",saddCommand,3,REDIS_CMD_BULK},
198 {"srem",sremCommand,3,REDIS_CMD_BULK},
199 {"sismember",sismemberCommand,3,REDIS_CMD_BULK},
200 {"scard",scardCommand,2,REDIS_CMD_INLINE},
201 {"sinter",sinterCommand,-2,REDIS_CMD_INLINE},
202 {"smembers",sinterCommand,2,REDIS_CMD_INLINE},
203 {"randomkey",randomkeyCommand,1,REDIS_CMD_INLINE},
204 {"select",selectCommand,2,REDIS_CMD_INLINE},
205 {"move",moveCommand,3,REDIS_CMD_INLINE},
206 {"rename",renameCommand,3,REDIS_CMD_INLINE},
207 {"renamenx",renamenxCommand,3,REDIS_CMD_INLINE},
208 {"keys",keysCommand,2,REDIS_CMD_INLINE},
209 {"dbsize",dbsizeCommand,1,REDIS_CMD_INLINE},
210 {"ping",pingCommand,1,REDIS_CMD_INLINE},
211 {"echo",echoCommand,2,REDIS_CMD_BULK},
212 {"save",saveCommand,1,REDIS_CMD_INLINE},
213 {"bgsave",bgsaveCommand,1,REDIS_CMD_INLINE},
214 {"shutdown",shutdownCommand,1,REDIS_CMD_INLINE},
215 {"lastsave",lastsaveCommand,1,REDIS_CMD_INLINE},
216 {"type",typeCommand,2,REDIS_CMD_INLINE},
217 {"",NULL,0,0}
218 };
类型redisCommand
的定义为(redis.c):
114 typedef void redisCommandProc(redisClient *c);
115 struct redisCommand {
116 char *name;
117 redisCommandProc *proc;
118 int arity;
119 int type;
120 };
字段name
是redis命令字符串,字段proc
是该命令的处理函数,字段arity
是redis命令参数个数,包含命令本身,字段type
说明该命令是否是bulk命令.
回到函数processCommand
, Line793:802如果redis服务端不支持客户端的命令或者命令参数个数不正确,则调用函数resetClient
复位该客户端对象,该函数的实现为(redis.c):
768 /* resetClient prepare the client to process the next command */
769 static void resetClient(redisClient *c) {
770 freeClientArgv(c);
771 c->bulklen = -1;
772 }
复位客户端对象会把解析的redis命令及其参数释放,并赋值字段bulklen
为初始值,该函数不会断开与客户端的连接.
回到函数processCommand
, Line802如果该命令是bulk类型,并且是首次解析其参数,即字段bulklen为-1,那么解析的最后一个参数必定为value字节数据的长度,Line803将该字符串参数转换为整数,转换完成后,Line805就可以释放掉该sds
对象.Line806:812可以看出,value字节数最大支持1GB.Line815设置接下来需要继续读取的value的字节数,包括以\r\n结束的2个字节.Line816:822如果客户端对象接收缓存中已经存在要读取的字节,则创建新的sds
对象存储value字节,注意value字节是不包含\r\n结束字节的,同时客户端对象记录该sds
对象,至此该redis命令的所有参数都已经准备完毕,Line827调用该命令的处理函数,处理完成后,调用resetClient
复位客户端对象,释放动态sds
对象.如果Line818的条件判断不满足,说明参数输入不完整,需要读取参数,则返回到主事件循环,继续监听文件事件对象,等待客户端输入.如果Line802的条件判断不满足,说明该命令或者是非bulk类型命令,或者是bulk命令,且其所有参数都已经记录在客户端对象中,这时候直接调用Line827命令处理函数.