Redis源码整体运行流程详解

51 篇文章 0 订阅
27 篇文章 0 订阅

本文所引用的源码全部来自Redis2.8.2版本。

Redis源码整体运行流程的相关文件是:redis.h, redis.c, networking.c, ae.h, ae.c。

转载请注明,本文出自:http://blog.csdn.net/acceptedxukai/article/details/17842119


Redis Server端处理Client请求的流程图




main函数


main函数主要的功能为:调用initServerConfig函数,进行默认的redisServer数据结构的参数初始化;调用daemonize函数,为服务器开始守护进程,对于守护进行相关详细信息见http://blog.csdn.net/acceptedxukai/article/details/8743189;调用initServer函数,初始化服务器;调用loadServerConfig函数,读取Redis的配置文件,使用配置文件中的参数替换默认的参数值;调用aeMain函数,开启事件循环,整个服务器开始工作。


initServer函数


该函数主要为初始化服务器,需要初始化的内容比较多,主要有:

1、创建事件循环

  1. <span style="font-family:Courier New;font-size:14px;">server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);</span>  
2、创建TCP与UDP Server,启动服务器,完成bind与listen

  1. <span style="font-family:Courier New;font-size:14px;">/* Open the TCP listening socket for the user commands. */  
  2.     //server.ipfd是个int数组,启动服务器,完成bind,listen  
  3.     if (listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR)  
  4.         exit(1);  
  5.     /* Open the listening Unix domain socket. */  
  6.     if (server.unixsocket != NULL) {  
  7.         unlink(server.unixsocket); /* don't care if this fails */  
  8.         server.sofd = anetUnixServer(server.neterr,server.unixsocket,server.unixsocketperm);  
  9.         if (server.sofd == ANET_ERR) {  
  10.             redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr);  
  11.             exit(1);  
  12.         }  
  13.     }</span>  
Redis2.8.2 TCP同时支持IPv4与IPv6,同时与之前版本的Redis不同,此版本支持多个TCP服务器,listenToPort函数主要还是调用anetTcpServer函数,完成socket()-->bind()-->listen(),下面详细查看下TCPServer的创建,UDP直接忽略吧,我也不知道UDP具体用在哪。

  1. <span style="font-family:Courier New;">static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len) {  
  2.     //绑定bind  
  3.     if (bind(s,sa,len) == -1) {  
  4.         anetSetError(err, "bind: %s", strerror(errno));  
  5.         close(s);  
  6.         return ANET_ERR;  
  7.     }  
  8.   
  9.     /* Use a backlog of 512 entries. We pass 511 to the listen() call because 
  10.      * the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1); 
  11.      * which will thus give us a backlog of 512 entries */  
  12.     //监听  
  13.     if (listen(s, 511) == -1) {  
  14.         anetSetError(err, "listen: %s", strerror(errno));  
  15.         close(s);  
  16.         return ANET_ERR;  
  17.     }  
  18.     return ANET_OK;  
  19. }  
  20. static int _anetTcpServer(char *err, int port, char *bindaddr, int af)  
  21. {  
  22.     int s, rv;  
  23.     char _port[6];  /* strlen("65535") */  
  24.     struct addrinfo hints, *servinfo, *p;  
  25.   
  26.     snprintf(_port,6,"%d",port);  
  27.     memset(&hints,0,sizeof(hints));  
  28.     hints.ai_family = af;  
  29.     hints.ai_socktype = SOCK_STREAM;  
  30.     //套接字地址用于监听绑定  
  31.     hints.ai_flags = AI_PASSIVE;    /* No effect if bindaddr != NULL */  
  32.     //可以加上hints.ai_protocol = IPPROTO_TCP;  
  33.   
  34.     /**getaddrinfo(const char *hostname, const char *servicename, 
  35.                    const struct addrinfo *hint,struct addrinfo **res); 
  36.        hostname:主机名 
  37.        servicename: 服务名 
  38.        hint: 用于过滤的模板,仅能使用ai_family, ai_flags, ai_protocol, ai_socktype,其余字段为0 
  39.        res:得到所有可用的地址 
  40.     */  
  41.     if ((rv = getaddrinfo(bindaddr,_port,&hints,&servinfo)) != 0) {  
  42.         anetSetError(err, "%s", gai_strerror(rv));  
  43.         return ANET_ERR;  
  44.     }  
  45.     //轮流尝试多个地址,找到一个允许连接到服务器的地址时便停止  
  46.     for (p = servinfo; p != NULL; p = p->ai_next) {  
  47.         if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)  
  48.             continue;  
  49.   
  50.         if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error;  
  51.         //设置套接字选项setsockopt,采用地址复用  
  52.         if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;  
  53.         //bind, listen  
  54.         if (anetListen(err,s,p->ai_addr,p->ai_addrlen) == ANET_ERR) goto error;  
  55.         goto end;  
  56.     }  
  57.     if (p == NULL) {  
  58.         anetSetError(err, "unable to bind socket");  
  59.         goto error;  
  60.     }  
  61.   
  62. error:  
  63.     s = ANET_ERR;  
  64. end:  
  65.     freeaddrinfo(servinfo);  
  66.     return s;  
  67. }  
  68. //if server.ipfd_count = 0, bindaddr = NULL  
  69. int anetTcpServer(char *err, int port, char *bindaddr)  
  70. {  
  71.     return _anetTcpServer(err, port, bindaddr, AF_INET);  
  72. }  
  73. </span>  
3、将listen的端口加入到事件监听中,进行监听,由aeCreateFileEvent函数完成,其注册的listen端口可读事件处理函数为acceptTcpHandler,这样在listen端口有新连接的时候会调用acceptTcpHandler,后者在accept这个新连接,然后就可以处理后续跟这个客户端连接相关的事件了。

  1. /* Create an event handler for accepting new connections in TCP and Unix 
  2.      * domain sockets. */  
  3.      //文件事件,用于处理响应外界的操作请求,事件处理函数为acceptTcpHandler/acceptUnixHandler  
  4.      //在networking.c  
  5.     for (j = 0; j < server.ipfd_count; j++) {  
  6.         if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,  
  7.             acceptTcpHandler,NULL) == AE_ERR)  
  8.             {  
  9.                 redisPanic(  
  10.                     "Unrecoverable error creating server.ipfd file event.");  
  11.             }  
  12.     }  
  13.     if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,  
  14.         acceptUnixHandler,NULL) == AE_ERR) redisPanic("Unrecoverable error creating server.sofd file event.");  


acceptTcpHandler函数


上面介绍了,initServer完成listen端口后,会加入到事件循环中,该事件为可读事件,并记录处理函数为fe->rfileProc = acceptTcpHandler;该函数分两步操作:用acceptTcpHandler接受这个客户端连接;然第二部初始化这个客户端连接的相关数据,将clientfd加入事件里面,设置的可读事件处理函数为readQueryFromClient,也就是读取客户端请求的函数。

  1. <span style="font-family:Courier New;">void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {  
  2.     int cport, cfd;  
  3.     char cip[REDIS_IP_STR_LEN];  
  4.     REDIS_NOTUSED(el);//无意义  
  5.     REDIS_NOTUSED(mask);  
  6.     REDIS_NOTUSED(privdata);  
  7.   
  8.     //cfd为accept函数返回的客户端文件描述符,accept使服务器完成一个客户端的链接  
  9.     cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);  
  10.     if (cfd == AE_ERR) {  
  11.         redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);  
  12.         return;  
  13.     }  
  14.     redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);  
  15.     //将cfd加入事件循环并设置回调函数为readQueryFromClient,并初始化redisClient  
  16.     acceptCommonHandler(cfd,0);  
  17. }</span>  

第一步很简单即完成accept,主要关注第二步acceptCommonHandler函数

  1. static void acceptCommonHandler(int fd, int flags) {  
  2.     redisClient *c;  
  3.     if ((c = createClient(fd)) == NULL) {//创建新的客户端  
  4.         redisLog(REDIS_WARNING,  
  5.             "Error registering fd event for the new client: %s (fd=%d)",  
  6.             strerror(errno),fd);  
  7.         close(fd); /* May be already closed, just ignore errors */  
  8.         return;  
  9.     }  
  10.     /* If maxclient directive is set and this is one client more... close the 
  11.      * connection. Note that we create the client instead to check before 
  12.      * for this condition, since now the socket is already set in non-blocking 
  13.      * mode and we can send an error for free using the Kernel I/O */  
  14.     //当前连接的客户端数目大于服务器最大运行的连接数,则拒绝连接  
  15.     if (listLength(server.clients) > server.maxclients) {  
  16.         char *err = "-ERR max number of clients reached\r\n";  
  17.   
  18.         /* That's a best effort error message, don't check write errors */  
  19.         if (write(c->fd,err,strlen(err)) == -1) {  
  20.             /* Nothing to do, Just to avoid the warning... */  
  21.         }  
  22.         server.stat_rejected_conn++;  
  23.         freeClient(c);  
  24.         return;  
  25.     }  
  26.     server.stat_numconnections++;  
  27.     c->flags |= flags;  
  28. }  

createClient函数


此函数用来为新连接的客户端初始化一个redisClient数据结构,该数据结构有比较多的参数,详见redis.h。该函数完成两个操作,第一、为客户端创建事件处理函数readQueryFromClient专门接收客户端发来的指令,第二、初始化redisClient数据结构相关参数。

  1. redisClient *createClient(int fd) {  
  2.     redisClient *c = zmalloc(sizeof(redisClient));  
  3.   
  4.     /* passing -1 as fd it is possible to create a non connected client. 
  5.      * This is useful since all the Redis commands needs to be executed 
  6.      * in the context of a client. When commands are executed in other 
  7.      * contexts (for instance a Lua script) we need a non connected client. */  
  8.      /** 
  9.         因为 Redis 命令总在客户端的上下文中执行, 
  10.         有时候为了在服务器内部执行命令,需要使用伪客户端来执行命令 
  11.         在 fd == -1 时,创建的客户端为伪终端 
  12.      */  
  13.     if (fd != -1) {  
  14.         //下面三个都是设置socket属性  
  15.         anetNonBlock(NULL,fd);//非阻塞  
  16.         anetEnableTcpNoDelay(NULL,fd);//no delay  
  17.         if (server.tcpkeepalive)  
  18.             anetKeepAlive(NULL,fd,server.tcpkeepalive);//keep alive  
  19.   
  20.         //创建一个accept fd的FileEvent事件,事件的处理函数是readQueryFromClient  
  21.         if (aeCreateFileEvent(server.el,fd,AE_READABLE,  
  22.             readQueryFromClient, c) == AE_ERR)  
  23.         {  
  24.             close(fd);  
  25.             zfree(c);  
  26.             return NULL;  
  27.         }  
  28.     }  
  29.   
  30.     selectDb(c,0);//默认选择第0个db, db.c  
  31.     c->fd = fd;//文件描述符  
  32.     c->name = NULL;  
  33.     c->bufpos = 0;//将指令结果发送给客户端的字符串长度  
  34.     c->querybuf = sdsempty();//请求字符串初始化  
  35.     c->querybuf_peak = 0;//请求字符串顶峰时的长度值  
  36.     c->reqtype = 0;//请求类型  
  37.     c->argc = 0;//参数个数  
  38.     c->argv = NULL;//参数内容  
  39.     c->cmd = c->lastcmd = NULL;//操作指令  
  40.     c->multibulklen = 0;//块个数  
  41.     c->bulklen = -1;//每个块的长度  
  42.     c->sentlen = 0;  
  43.     c->flags = 0;//客户类型的标记,比较重要  
  44.     c->ctime = c->lastinteraction = server.unixtime;  
  45.     c->authenticated = 0;  
  46.     c->replstate = REDIS_REPL_NONE;  
  47.     c->reploff = 0;  
  48.     c->repl_ack_off = 0;  
  49.     c->repl_ack_time = 0;  
  50.     c->slave_listening_port = 0;  
  51.     c->reply = listCreate();//存放服务器应答的数据  
  52.     c->reply_bytes = 0;  
  53.     c->obuf_soft_limit_reached_time = 0;  
  54.     listSetFreeMethod(c->reply,decrRefCountVoid);  
  55.     listSetDupMethod(c->reply,dupClientReplyValue);  
  56.     c->bpop.keys = dictCreate(&setDictType,NULL);//下面三个参数在list数据阻塞操作时使用  
  57.     c->bpop.timeout = 0;  
  58.     c->bpop.target = NULL;  
  59.     c->io_keys = listCreate();  
  60.     c->watched_keys = listCreate();//事务命令CAS中使用  
  61.     listSetFreeMethod(c->io_keys,decrRefCountVoid);  
  62.     c->pubsub_channels = dictCreate(&setDictType,NULL);  
  63.     c->pubsub_patterns = listCreate();  
  64.     listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);  
  65.     listSetMatchMethod(c->pubsub_patterns,listMatchObjects);  
  66.     // 如果不是伪客户端,那么将客户端加入到服务器客户端列表中  
  67.     if (fd != -1) listAddNodeTail(server.clients,c);//添加到server的clients链表  
  68.     initClientMultiState(c);//初始化事务指令状态  
  69.     return c;  
  70. }  
客户端的请求指令字符串始终存放在querybuf中,在对querybuf解析后,将指令参数的个数存放在argc中,具体的指令参数存放在argv中;但是服务器应答的结果有两种存储方式:buf字符串、reply列表。

readQueryFromClient函数


readQueryFromClient函数用来读取客户端的请求命令行数据,并调用processInputBuffer函数依照redis通讯协议对数据进行解析。服务器使用最原始的read函数来读取客户端发送来的请求命令,并将字符串存储在querybuf中,根据需要对querybuf进行扩展。

  1. void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {  
  2.     redisClient *c = (redisClient*) privdata;  
  3.     int nread, readlen;  
  4.     size_t qblen;  
  5.     REDIS_NOTUSED(el);  
  6.     REDIS_NOTUSED(mask);  
  7.   
  8.     server.current_client = c;  
  9.     readlen = REDIS_IOBUF_LEN; //1024 * 16  
  10.     /* If this is a multi bulk request, and we are processing a bulk reply 
  11.      * that is large enough, try to maximize the probability that the query 
  12.      * buffer contains exactly the SDS string representing the object, even 
  13.      * at the risk of requiring more read(2) calls. This way the function 
  14.      * processMultiBulkBuffer() can avoid copying buffers to create the 
  15.      * Redis Object representing the argument. */  
  16.     if (c->reqtype == REDIS_REQ_MULTIBULK && c->multibulklen && c->bulklen != -1  
  17.         && c->bulklen >= REDIS_MBULK_BIG_ARG)  
  18.     {  
  19.         int remaining = (unsigned)(c->bulklen+2)-sdslen(c->querybuf);  
  20.   
  21.         if (remaining < readlen) readlen = remaining;  
  22.     }  
  23.   
  24.     qblen = sdslen(c->querybuf);  
  25.     if (c->querybuf_peak < qblen) c->querybuf_peak = qblen;  
  26.     //对querybuf的空间进行扩展  
  27.     c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);  
  28.     //读取客户端发来的操作指令  
  29.     nread = read(fd, c->querybuf+qblen, readlen);  
  30.     if (nread == -1) {  
  31.         if (errno == EAGAIN) {  
  32.             nread = 0;  
  33.         } else {  
  34.             redisLog(REDIS_VERBOSE, "Reading from client: %s",strerror(errno));  
  35.             freeClient(c);  
  36.             return;  
  37.         }  
  38.     } else if (nread == 0) {  
  39.         redisLog(REDIS_VERBOSE, "Client closed connection");  
  40.         freeClient(c);  
  41.         return;  
  42.     }  
  43.     if (nread) {  
  44.         //改变querybuf的实际长度和空闲长度,len += nread, free -= nread;  
  45.         sdsIncrLen(c->querybuf,nread);  
  46.         c->lastinteraction = server.unixtime;  
  47.         if (c->flags & REDIS_MASTER) c->reploff += nread;  
  48.     } else {  
  49.         server.current_client = NULL;  
  50.         return;  
  51.     }  
  52.     //客户端请求的字符串长度大于服务器最大的请求长度值  
  53.     if (sdslen(c->querybuf) > server.client_max_querybuf_len) {  
  54.         sds ci = getClientInfoString(c), bytes = sdsempty();  
  55.   
  56.         bytes = sdscatrepr(bytes,c->querybuf,64);  
  57.         redisLog(REDIS_WARNING,"Closing client that reached max query buffer length: %s (qbuf initial bytes: %s)", ci, bytes);  
  58.         sdsfree(ci);  
  59.         sdsfree(bytes);  
  60.         freeClient(c);  
  61.         return;  
  62.     }  
  63.     //解析请求  
  64.     processInputBuffer(c);  
  65.     server.current_client = NULL;  
  66. }  
processInputBuffer函数主要用来处理请求的解析工作,redis有两种解析方式;行指令解析与多重指令解析,行指令解析直接忽略,下面详解多重指令解析。

  1. void processInputBuffer(redisClient *c) {  
  2.     /* Keep processing while there is something in the input buffer */  
  3.     while(sdslen(c->querybuf)) {  
  4.         /* Immediately abort if the client is in the middle of something. */  
  5.         if (c->flags & REDIS_BLOCKED) return;  
  6.   
  7.         /* REDIS_CLOSE_AFTER_REPLY closes the connection once the reply is 
  8.          * written to the client. Make sure to not let the reply grow after 
  9.          * this flag has been set (i.e. don't process more commands). */  
  10.         if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;  
  11.   
  12.         /* Determine request type when unknown. */  
  13.         //当请求类型未知时,先确定属于哪种请求  
  14.         if (!c->reqtype) {  
  15.             if (c->querybuf[0] == '*') {  
  16.                 c->reqtype = REDIS_REQ_MULTIBULK;//多重指令解析  
  17.             } else {  
  18.                 c->reqtype = REDIS_REQ_INLINE;//按行解析  
  19.             }  
  20.         }  
  21.   
  22.         if (c->reqtype == REDIS_REQ_INLINE) {  
  23.             if (processInlineBuffer(c) != REDIS_OK) break;  
  24.         } else if (c->reqtype == REDIS_REQ_MULTIBULK) {  
  25.             if (processMultibulkBuffer(c) != REDIS_OK) break;  
  26.         } else {  
  27.             redisPanic("Unknown request type");  
  28.         }  
  29.   
  30.         /* Multibulk processing could see a <= 0 length. */  
  31.         if (c->argc == 0) {  
  32.             resetClient(c);  
  33.         } else {  
  34.             /* Only reset the client when the command was executed. */  
  35.             //执行相应指令  
  36.             if (processCommand(c) == REDIS_OK)  
  37.                 resetClient(c);  
  38.         }  
  39.     }  
  40. }  
多重指令解析的处理函数为processMultibulkBuffer,下面先简单介绍下Redis的通讯协议:

[plain]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. 以下是这个协议的一般形式:  
  2. *< 参数数量 > CR LF  
  3. $< 参数 1 的字节数量 > CR LF  
  4. < 参数 1 的数据 > CR LF  
  5. ...  
  6. $< 参数 N 的字节数量 > CR LF  
  7. < 参数 N 的数据 > CR LF  
  8. 举个例子,以下是一个命令协议的打印版本:  
  9. *3  
  10. $3  
  11. SET  
  12. $3  
  13. foo  
  14. $3  
  15. bar  
  16. 这个命令的实际协议值如下:  
  17. "*3\r\n$3\r\nSET\r\n$3\r\foo\r\n$3\r\bar\r\n"  
  1. /** 
  2.     例:querybuf = "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n" 
  3. */  
  4. int processMultibulkBuffer(redisClient *c) {  
  5.     char *newline = NULL;  
  6.     int pos = 0, ok;  
  7.     long long ll;  
  8.   
  9.     if (c->multibulklen == 0) {//参数数目为0,表示这是新的请求指令  
  10.         /* The client should have been reset */  
  11.         redisAssertWithInfo(c,NULL,c->argc == 0);  
  12.   
  13.         /* Multi bulk length cannot be read without a \r\n */  
  14.         newline = strchr(c->querybuf,'\r');  
  15.         if (newline == NULL) {  
  16.             if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) {  
  17.                 addReplyError(c,"Protocol error: too big mbulk count string");  
  18.                 setProtocolError(c,0);  
  19.             }  
  20.             return REDIS_ERR;  
  21.         }  
  22.   
  23.         /* Buffer should also contain \n */  
  24.         if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2))  
  25.             return REDIS_ERR;  
  26.   
  27.         /* We know for sure there is a whole line since newline != NULL, 
  28.          * so go ahead and find out the multi bulk length. */  
  29.         redisAssertWithInfo(c,NULL,c->querybuf[0] == '*');  
  30.         //将字符串转为long long整数,转换得到的结果存到ll中,ll就是后面参数的个数  
  31.         ok = string2ll(c->querybuf+1,newline-(c->querybuf+1),&ll);  
  32.         if (!ok || ll > 1024*1024) {  
  33.             addReplyError(c,"Protocol error: invalid multibulk length");  
  34.             setProtocolError(c,pos);  
  35.             return REDIS_ERR;  
  36.         }  
  37.   
  38.         pos = (newline-c->querybuf)+2;//跳过\r\n  
  39.         if (ll <= 0) {//参数个数小于0,表示后面的参数数目大于等于绝对值ll  
  40.              /** s = sdsnew("Hello World"); 
  41.              * sdsrange(s,1,-1); => "ello World" 
  42.              */  
  43.             sdsrange(c->querybuf,pos,-1);//querybuf="$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"  
  44.             return REDIS_OK;  
  45.         }  
  46.   
  47.         c->multibulklen = ll;//得到指令参数个数  
  48.   
  49.         /* Setup argv array on client structure */  
  50.         if (c->argv) zfree(c->argv);  
  51.         c->argv = zmalloc(sizeof(robj*) * c->multibulklen);//申请参数内存空间  
  52.     }  
  53.   
  54.     redisAssertWithInfo(c,NULL,c->multibulklen > 0);  
  55.     /** 
  56.         开始抽取字符串 
  57.         querybuf = "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n" 
  58.         pos = 4 
  59.     */  
  60.     while(c->multibulklen) {  
  61.         /* Read bulk length if unknown */  
  62.         if (c->bulklen == -1) {//参数的长度为-1,这里用来处理每个参数的字符串长度值  
  63.             /**newline = "\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"*/  
  64.             newline = strchr(c->querybuf+pos,'\r');  
  65.             if (newline == NULL) {  
  66.                 if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) {  
  67.                     addReplyError(c,"Protocol error: too big bulk count string");  
  68.                     setProtocolError(c,0);  
  69.                 }  
  70.                 break;  
  71.             }  
  72.   
  73.             /* Buffer should also contain \n */  
  74.             if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2))  
  75.                 break;  
  76.   
  77.             //每个字符串以$开头,后面的数字表示其长度  
  78.             if (c->querybuf[pos] != '$') {  
  79.                 addReplyErrorFormat(c,  
  80.                     "Protocol error: expected '$', got '%c'",  
  81.                     c->querybuf[pos]);  
  82.                 setProtocolError(c,pos);  
  83.                 return REDIS_ERR;  
  84.             }  
  85.   
  86.             //得到字符串的长度值,ll  
  87.             ok = string2ll(c->querybuf+pos+1,newline-(c->querybuf+pos+1),&ll);  
  88.             if (!ok || ll < 0 || ll > 512*1024*1024) {  
  89.                 addReplyError(c,"Protocol error: invalid bulk length");  
  90.                 setProtocolError(c,pos);  
  91.                 return REDIS_ERR;  
  92.             }  
  93.   
  94.             //pos = 8  
  95.             pos += newline-(c->querybuf+pos)+2;//跳过\r\n "SET\r\n$3\r\nfoo\r\n$3\r\nbar\r"  
  96.             if (ll >= REDIS_MBULK_BIG_ARG) {//字符串长度超过1024*32,需要扩展  
  97.                 size_t qblen;  
  98.   
  99.                 /* If we are going to read a large object from network 
  100.                  * try to make it likely that it will start at c->querybuf 
  101.                  * boundary so that we can optimize object creation 
  102.                  * avoiding a large copy of data. */  
  103.                  /** 
  104.                     sdsrange(querybuf,pos,-1)是将[pos,len-1]之间的字符串使用memmove前移, 
  105.                     然后后面的直接截断 
  106.                  */  
  107.                 sdsrange(c->querybuf,pos,-1);//"SET\r\n$3\r\nfoo\r\n$3\r\nbar\r"  
  108.                 pos = 0;  
  109.                 qblen = sdslen(c->querybuf);  
  110.                 /* Hint the sds library about the amount of bytes this string is 
  111.                  * going to contain. */  
  112.                 if (qblen < ll+2)//这里只会到最后一个字符串才可能为True,并且数据不完整,数据不完整是由于redis使用非阻塞的原因  
  113.                     c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2-qblen);  
  114.             }  
  115.             c->bulklen = ll;  
  116.         }  
  117.   
  118.         /* Read bulk argument */  
  119.         //读取参数,没有\r\n表示数据不全,也就是说服务器接收到的数据不完整  
  120.         if (sdslen(c->querybuf)-pos < (unsigned)(c->bulklen+2)) {  
  121.             /* Not enough data (+2 == trailing \r\n) */  
  122.             break;  
  123.         } else {//数据完整  
  124.             /* Optimization: if the buffer contains JUST our bulk element 
  125.              * instead of creating a new object by *copying* the sds we 
  126.              * just use the current sds string. */  
  127.             if (pos == 0 &&  
  128.                 c->bulklen >= REDIS_MBULK_BIG_ARG &&  
  129.                 (signed) sdslen(c->querybuf) == c->bulklen+2)  
  130.             {//数据刚好完整,那么就直接使用c->querybuf,然后清空querybuf,注意这里只可能在最后一个字符串才可能出现  
  131.                 c->argv[c->argc++] = createObject(REDIS_STRING,c->querybuf);  
  132.                 sdsIncrLen(c->querybuf,-2); /* remove CRLF */  
  133.                 c->querybuf = sdsempty();  
  134.                 /* Assume that if we saw a fat argument we'll see another one 
  135.                  * likely... */  
  136.                 c->querybuf = sdsMakeRoomFor(c->querybuf,c->bulklen+2);  
  137.                 pos = 0;  
  138.             } else {  
  139.                 //抽取出具体的字符串,比如SET,建立一个stringObject  
  140.                 c->argv[c->argc++] =  
  141.                     createStringObject(c->querybuf+pos,c->bulklen);  
  142.                 pos += c->bulklen+2;//跳过\r\n  
  143.             }  
  144.             c->bulklen = -1;  
  145.             c->multibulklen--;  
  146.         }  
  147.     }  
  148.   
  149.     /** 
  150.         由于采用的是非阻塞读取客户端数据的方式,那么如果c->multibulklen != 0,那么就表示 
  151.         数据没有接收完全,首先需要将当前的querybuf数据截断 
  152.     */  
  153.     /* Trim to pos */  
  154.     if (pos) sdsrange(c->querybuf,pos,-1);  
  155.   
  156.     /* We're done when c->multibulk == 0 */  
  157.     if (c->multibulklen == 0) return REDIS_OK;  
  158.   
  159.     /* Still not read to process the command */  
  160.   
  161.     return REDIS_ERR;  
  162. }  

processCommand与call函数


客户端指令解析完之后,需要执行该指令,执行指令的两个函数为processCommand与call函数,这两个函数除了单纯的执行指令外,还做了许多其他的工作,这里不详解,看代码仅仅找到指令如何执行还是很简单的。


指令执行完之后,需要将得到的结果集返回给客户端,这部分是如何工作的,下面开始分析。

在networking.c中可以发现许多以addRelpy为前缀的函数名,这些函数都是用来处理各种不同类型的结果的,我们以典型的addReply函数为例,进行分析。


addReply函数

该函数第一步工作就是调用prepareClientToWrite函数为客户端创建一个写文件事件,事件的处理函数即将结果集发送给客户端的函数为sendReplyToClient.


  1. int prepareClientToWrite(redisClient *c) {  
  2.     if (c->flags & REDIS_LUA_CLIENT) return REDIS_OK;  
  3.     if ((c->flags & REDIS_MASTER) &&  
  4.         !(c->flags & REDIS_MASTER_FORCE_REPLY)) return REDIS_ERR;  
  5.     if (c->fd <= 0) return REDIS_ERR; /* Fake client */  
  6.     if (c->bufpos == 0 && listLength(c->reply) == 0 &&  
  7.         (c->replstate == REDIS_REPL_NONE ||  
  8.          c->replstate == REDIS_REPL_ONLINE) &&  
  9.         aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,  
  10.         sendReplyToClient, c) == AE_ERR) return REDIS_ERR;  
  11.     return REDIS_OK;  
  12. }  
第二步,就是根据相应的条件,将得到的结果rboj数据存储到buf中或者reply链表中。对于存储的策略:redis优先将数据存储在固定大小的buf中,也就是redisClient结构体buf[REDIS_REPLY_CHUNK_BYTES]里,默认大小为16K。如果有数据没有发送完或c->buf空间不足,就会放到c->reply链表里面,链表每个节点都是内存buf,后来的数据放入最后面。具体的处理函数为_addReplyToBuffer和_addReplyStringToList两个函数。


  1. void addReply(redisClient *c, robj *obj) {  
  2.     if (prepareClientToWrite(c) != REDIS_OK) return;  
  3.   
  4.     /* This is an important place where we can avoid copy-on-write 
  5.      * when there is a saving child running, avoiding touching the 
  6.      * refcount field of the object if it's not needed. 
  7.      * 
  8.      * If the encoding is RAW and there is room in the static buffer 
  9.      * we'll be able to send the object to the client without 
  10.      * messing with its page. */  
  11.     if (obj->encoding == REDIS_ENCODING_RAW) {//字符串类型  
  12.         //是否能将数据追加到c->buf中  
  13.         if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)  
  14.             _addReplyObjectToList(c,obj);//添加到c->reply链表中  
  15.     } else if (obj->encoding == REDIS_ENCODING_INT) {//整数类型  
  16.         /* Optimization: if there is room in the static buffer for 32 bytes 
  17.          * (more than the max chars a 64 bit integer can take as string) we 
  18.          * avoid decoding the object and go for the lower level approach. */  
  19.          //追加到c->buf中  
  20.         if (listLength(c->reply) == 0 && (sizeof(c->buf) - c->bufpos) >= 32) {  
  21.             char buf[32];  
  22.             int len;  
  23.   
  24.             len = ll2string(buf,sizeof(buf),(long)obj->ptr);//整型转string  
  25.             if (_addReplyToBuffer(c,buf,len) == REDIS_OK)  
  26.                 return;  
  27.             /* else... continue with the normal code path, but should never 
  28.              * happen actually since we verified there is room. */  
  29.         }  
  30.         obj = getDecodedObject(obj);//64位整数,先转换为字符串  
  31.         if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)  
  32.             _addReplyObjectToList(c,obj);  
  33.         decrRefCount(obj);  
  34.     } else {  
  35.         redisPanic("Wrong obj->encoding in addReply()");  
  36.     }  
  37. }  

  1. /** 
  2.     Server将数据发送给Client,有两种存储数据的缓冲形式,具体参见redisClient结构体 
  3.     1、Response buffer 
  4.         int bufpos; //回复 
  5.         char buf[REDIS_REPLY_CHUNK_BYTES]; //长度为16 * 1024 
  6.     2、list *reply; 
  7.         unsigned long reply_bytes; Tot bytes of objects in reply list 
  8.         int sentlen;            已发送的字节数 
  9.     如果已经使用reply的形式或者buf已经不够存储,那么就将数据添加到list *reply中 
  10.     否则将数据添加到buf中 
  11. */  
  12. int _addReplyToBuffer(redisClient *c, char *s, size_t len) {  
  13.     size_t available = sizeof(c->buf)-c->bufpos;//计算出c->buf的剩余长度  
  14.   
  15.     if (c->flags & REDIS_CLOSE_AFTER_REPLY) return REDIS_OK;  
  16.   
  17.     /* If there already are entries in the reply list, we cannot 
  18.      * add anything more to the static buffer. */  
  19.     if (listLength(c->reply) > 0) return REDIS_ERR;  
  20.   
  21.     /* Check that the buffer has enough space available for this string. */  
  22.     if (len > available) return REDIS_ERR;  
  23.   
  24.     //回复数据追加到buf中  
  25.     memcpy(c->buf+c->bufpos,s,len);  
  26.     c->bufpos+=len;  
  27.     return REDIS_OK;  
  28. }  
  29.   
  30. /** 
  31.     1、如果链表长度为0: 新建一个节点并直接将robj追加到链表的尾部 
  32.     2、链表长度不为0: 首先取出链表的尾部节点 
  33.         1)、尾部节点的字符串长度 + robj中ptr字符串的长度 <= REDIS_REPLY_CHUNK_BYTES: 
  34.             将robj->ptr追加到尾节点的tail->ptr后面 
  35.         2)、反之: 新建一个节点并直接将robj追加到链表的尾部 
  36. */  
  37. void _addReplyObjectToList(redisClient *c, robj *o) {  
  38.     robj *tail;  
  39.   
  40.     if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;  
  41.   
  42.     //链表长度为0  
  43.     if (listLength(c->reply) == 0) {  
  44.         incrRefCount(o);//增加引用次数  
  45.         listAddNodeTail(c->reply,o);//添加到链表末尾  
  46.         c->reply_bytes += zmalloc_size_sds(o->ptr); //计算o->ptr的占用内存大小  
  47.     } else {  
  48.         //取出链表尾中的数据  
  49.         tail = listNodeValue(listLast(c->reply));  
  50.   
  51.         /* Append to this object when possible. */  
  52.         // 如果最后一个节点所保存的回复加上新回复内容总长度小于等于 REDIS_REPLY_CHUNK_BYTES  
  53.         // 那么将新回复追加到节点回复当中。  
  54.         if (tail->ptr != NULL &&  
  55.             sdslen(tail->ptr)+sdslen(o->ptr) <= REDIS_REPLY_CHUNK_BYTES)  
  56.         {  
  57.             c->reply_bytes -= zmalloc_size_sds(tail->ptr);  
  58.             tail = dupLastObjectIfNeeded(c->reply);  
  59.             tail->ptr = sdscatlen(tail->ptr,o->ptr,sdslen(o->ptr));  
  60.             c->reply_bytes += zmalloc_size_sds(tail->ptr);  
  61.         } else {//为新回复单独创建一个节点  
  62.             incrRefCount(o);  
  63.             listAddNodeTail(c->reply,o);  
  64.             c->reply_bytes += zmalloc_size_sds(o->ptr);  
  65.         }  
  66.     }  
  67.     // 如果突破了客户端的最大缓存限制,那么关闭客户端  
  68.     asyncCloseClientOnOutputBufferLimitReached(c);  
  69. }  


sendReplyToClient函数


终于到了最后一步,把c->buf与c->reply中的数据发送给客户端即可,发送同样使用的是最原始的write函数。发送完成之后,redis将当前客户端释放,并且删除写事件,代码比较简单,不详细解释。



小结

本文粗略的介绍了Redis整体运行的流程,从服务器的角度,介绍Redis是如何初始化,创建socket,接收客户端请求,解析请求及指令的执行,反馈执行的结果集给客户端等。如果读者想更深入的了解Redis的运行机制,需要亲自阅读源码,本文可以用作参考。同时也是学习linux socket编程的好工具,原本简简单单的socket->bind->listen->accept->read->write也可以用来做许多高效的业务,是Linux socket学习的不二选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值