redis執行流程源碼分析

本文所引用的源碼全部來自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、創建事件循環

server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);

2、創建TCP與UDP Server,啓動服務器,完成bind與listen

/* Open the TCP listening socket for the user commands. */  
    //server.ipfd是個int數組,啓動服務器,完成bind,listen  
    if (listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR)  
        exit(1);  
    /* Open the listening Unix domain socket. */  
    if (server.unixsocket != NULL) {  
        unlink(server.unixsocket); /* don't care if this fails */  
        server.sofd = anetUnixServer(server.neterr,server.unixsocket,server.unixsocketperm);  
        if (server.sofd == ANET_ERR) {  
            redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr);  
            exit(1);  
        }  
    }
Redis2.8.2 TCP同時支持IPv4與IPv6,同時與之前版本的Redis不同,此版本支持多個TCP服務器,listenToPort函數主要還是調用anetTcpServer函數,完成socket()-->bind()-->listen(),下面詳細查看下TCPServer的創建,UDP直接忽略吧,我也不知道UDP具體用在哪。

static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len) {  
    //綁定bind  
    if (bind(s,sa,len) == -1) {  
        anetSetError(err, "bind: %s", strerror(errno));  
        close(s);  
        return ANET_ERR;  
    }  
  
    /* Use a backlog of 512 entries. We pass 511 to the listen() call because 
     * the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1); 
     * which will thus give us a backlog of 512 entries */  
    //監聽  
    if (listen(s, 511) == -1) {  
        anetSetError(err, "listen: %s", strerror(errno));  
        close(s);  
        return ANET_ERR;  
    }  
    return ANET_OK;  
}  
static int _anetTcpServer(char *err, int port, char *bindaddr, int af)  
{  
    int s, rv;  
    char _port[6];  /* strlen("65535") */  
    struct addrinfo hints, *servinfo, *p;  
  
    snprintf(_port,6,"%d",port);  
    memset(&hints,0,sizeof(hints));  
    hints.ai_family = af;  
    hints.ai_socktype = SOCK_STREAM;  
    //套接字地址用於監聽綁定  
    hints.ai_flags = AI_PASSIVE;    /* No effect if bindaddr != NULL */  
    //可以加上hints.ai_protocol = IPPROTO_TCP;  
  
    /**getaddrinfo(const char *hostname, const char *servicename, 
                   const struct addrinfo *hint,struct addrinfo **res); 
       hostname:主機名 
       servicename: 服務名 
       hint: 用於過濾的模板,僅能使用ai_family, ai_flags, ai_protocol, ai_socktype,其餘字段爲0 
       res:得到所有可用的地址 
    */  
    if ((rv = getaddrinfo(bindaddr,_port,&hints,&servinfo)) != 0) {  
        anetSetError(err, "%s", gai_strerror(rv));  
        return ANET_ERR;  
    }  
    //輪流嘗試多個地址,找到一個允許連接到服務器的地址時便停止  
    for (p = servinfo; p != NULL; p = p->ai_next) {  
        if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)  
            continue;  
  
        if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error;  
        //設置套接字選項setsockopt,採用地址複用  
        if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;  
        //bind, listen  
        if (anetListen(err,s,p->ai_addr,p->ai_addrlen) == ANET_ERR) goto error;  
        goto end;  
    }  
    if (p == NULL) {  
        anetSetError(err, "unable to bind socket");  
        goto error;  
    }  
  
error:  
    s = ANET_ERR;  
end:  
    freeaddrinfo(servinfo);  
    return s;  
}  
//if server.ipfd_count = 0, bindaddr = NULL  
int anetTcpServer(char *err, int port, char *bindaddr)  
{  
    return _anetTcpServer(err, port, bindaddr, AF_INET);  
}  
3、將listen的端口加入到事件監聽中,進行監聽,由aeCreateFileEvent函數完成,其註冊的listen端口可讀事件處理函數爲acceptTcpHandler,這樣在listen端口有新連接的時候會調用acceptTcpHandler,後者在accept這個新連接,然後就可以處理後續跟這個客戶端連接相關的事件了。

    /* Create an event handler for accepting new connections in TCP and Unix 
         * domain sockets. */  
         //文件事件,用於處理響應外界的操作請求,事件處理函數爲acceptTcpHandler/acceptUnixHandler  
         //在networking.c  
        for (j = 0; j < server.ipfd_count; j++) {  
            if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,  
                acceptTcpHandler,NULL) == AE_ERR)  
                {  
                    redisPanic(  
                        "Unrecoverable error creating server.ipfd file event.");  
                }  
        }  
        if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,  
            acceptUnixHandler,NULL) == AE_ERR) redisPanic("Unrecoverable error creating server.sofd file event.");  

acceptTcpHandler函數


上面介紹了,initServer完成listen端口後,會加入到事件循環中,該事件爲可讀事件,並記錄處理函數爲fe->rfileProc = acceptTcpHandler;該函數分兩步操作:用acceptTcpHandler接受這個客戶端連接;然第二部初始化這個客戶端連接的相關數據,將clientfd加入事件裏面,設置的可讀事件處理函數爲readQueryFromClient,也就是讀取客戶端請求的函數。

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {  
    int cport, cfd;  
    char cip[REDIS_IP_STR_LEN];  
    REDIS_NOTUSED(el);//無意義  
    REDIS_NOTUSED(mask);  
    REDIS_NOTUSED(privdata);  
  
    //cfd爲accept函數返回的客戶端文件描述符,accept使服務器完成一個客戶端的鏈接  
    cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);  
    if (cfd == AE_ERR) {  
        redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);  
        return;  
    }  
    redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);  
    //將cfd加入事件循環並設置回調函數爲readQueryFromClient,並初始化redisClient  
    acceptCommonHandler(cfd,0);  
}

第一步很簡單即完成accept,主要關注第二步acceptCommonHandler函數

    static void acceptCommonHandler(int fd, int flags) {  
        redisClient *c;  
        if ((c = createClient(fd)) == NULL) {//創建新的客戶端  
            redisLog(REDIS_WARNING,  
                "Error registering fd event for the new client: %s (fd=%d)",  
                strerror(errno),fd);  
            close(fd); /* May be already closed, just ignore errors */  
            return;  
        }  
        /* If maxclient directive is set and this is one client more... close the 
         * connection. Note that we create the client instead to check before 
         * for this condition, since now the socket is already set in non-blocking 
         * mode and we can send an error for free using the Kernel I/O */  
        //當前連接的客戶端數目大於服務器最大運行的連接數,則拒絕連接  
        if (listLength(server.clients) > server.maxclients) {  
            char *err = "-ERR max number of clients reached\r\n";  
      
            /* That's a best effort error message, don't check write errors */  
            if (write(c->fd,err,strlen(err)) == -1) {  
                /* Nothing to do, Just to avoid the warning... */  
            }  
            server.stat_rejected_conn++;  
            freeClient(c);  
            return;  
        }  
        server.stat_numconnections++;  
        c->flags |= flags;  
    }  

createClient函數


此函數用來爲新連接的客戶端初始化一個redisClient數據結構,該數據結構有比較多的參數,詳見redis.h。該函數完成兩個操作,第一、爲客戶端創建事件處理函數readQueryFromClient專門接收客戶端發來的指令,第二、初始化redisClient數據結構相關參數。 

    redisClient *createClient(int fd) {  
        redisClient *c = zmalloc(sizeof(redisClient));  
      
        /* passing -1 as fd it is possible to create a non connected client. 
         * This is useful since all the Redis commands needs to be executed 
         * in the context of a client. When commands are executed in other 
         * contexts (for instance a Lua script) we need a non connected client. */  
         /** 
            因爲 Redis 命令總在客戶端的上下文中執行, 
            有時候爲了在服務器內部執行命令,需要使用僞客戶端來執行命令 
            在 fd == -1 時,創建的客戶端爲僞終端 
         */  
        if (fd != -1) {  
            //下面三個都是設置socket屬性  
            anetNonBlock(NULL,fd);//非阻塞  
            anetEnableTcpNoDelay(NULL,fd);//no delay  
            if (server.tcpkeepalive)  
                anetKeepAlive(NULL,fd,server.tcpkeepalive);//keep alive  
      
            //創建一個accept fd的FileEvent事件,事件的處理函數是readQueryFromClient  
            if (aeCreateFileEvent(server.el,fd,AE_READABLE,  
                readQueryFromClient, c) == AE_ERR)  
            {  
                close(fd);  
                zfree(c);  
                return NULL;  
            }  
        }  
      
        selectDb(c,0);//默認選擇第0個db, db.c  
        c->fd = fd;//文件描述符  
        c->name = NULL;  
        c->bufpos = 0;//將指令結果發送給客戶端的字符串長度  
        c->querybuf = sdsempty();//請求字符串初始化  
        c->querybuf_peak = 0;//請求字符串頂峯時的長度值  
        c->reqtype = 0;//請求類型  
        c->argc = 0;//參數個數  
        c->argv = NULL;//參數內容  
        c->cmd = c->lastcmd = NULL;//操作指令  
        c->multibulklen = 0;//塊個數  
        c->bulklen = -1;//每個塊的長度  
        c->sentlen = 0;  
        c->flags = 0;//客戶類型的標記,比較重要  
        c->ctime = c->lastinteraction = server.unixtime;  
        c->authenticated = 0;  
        c->replstate = REDIS_REPL_NONE;  
        c->reploff = 0;  
        c->repl_ack_off = 0;  
        c->repl_ack_time = 0;  
        c->slave_listening_port = 0;  
        c->reply = listCreate();//存放服務器應答的數據  
        c->reply_bytes = 0;  
        c->obuf_soft_limit_reached_time = 0;  
        listSetFreeMethod(c->reply,decrRefCountVoid);  
        listSetDupMethod(c->reply,dupClientReplyValue);  
        c->bpop.keys = dictCreate(&setDictType,NULL);//下面三個參數在list數據阻塞操作時使用  
        c->bpop.timeout = 0;  
        c->bpop.target = NULL;  
        c->io_keys = listCreate();  
        c->watched_keys = listCreate();//事務命令CAS中使用  
        listSetFreeMethod(c->io_keys,decrRefCountVoid);  
        c->pubsub_channels = dictCreate(&setDictType,NULL);  
        c->pubsub_patterns = listCreate();  
        listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);  
        listSetMatchMethod(c->pubsub_patterns,listMatchObjects);  
        // 如果不是僞客戶端,那麼將客戶端加入到服務器客戶端列表中  
        if (fd != -1) listAddNodeTail(server.clients,c);//添加到server的clients鏈表  
        initClientMultiState(c);//初始化事務指令狀態  
        return c;  
    }  
客戶端的請求指令字符串始終存放在querybuf中,在對querybuf解析後,將指令參數的個數存放在argc中,具體的指令參數存放在argv中;但是服務器應答的結果有兩種存儲方式:buf字符串、reply列表。

readQueryFromClient函數


readQueryFromClient函數用來讀取客戶端的請求命令行數據,並調用processInputBuffer函數依照redis通訊協議對數據進行解析。服務器使用最原始的read函數來讀取客戶端發送來的請求命令,並將字符串存儲在querybuf中,根據需要對querybuf進行擴展。

    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; //1024 * 16  
        /* 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;  
        //對querybuf的空間進行擴展  
        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) {  
            //改變querybuf的實際長度和空閒長度,len += nread, free -= nread;  
            sdsIncrLen(c->querybuf,nread);  
            c->lastinteraction = server.unixtime;  
            if (c->flags & REDIS_MASTER) c->reploff += nread;  
        } else {  
            server.current_client = NULL;  
            return;  
        }  
        //客戶端請求的字符串長度大於服務器最大的請求長度值  
        if (sdslen(c->querybuf) > server.client_max_querybuf_len) {  
            sds ci = getClientInfoString(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;  
        }  
        //解析請求  
        processInputBuffer(c);  
        server.current_client = NULL;  
    }  

processInputBuffer函數主要用來處理請求的解析工作,redis有兩種解析方式;行指令解析與多重指令解析,行指令解析直接忽略,下面詳解多重指令解析。

    void processInputBuffer(redisClient *c) {  
        /* Keep processing while there is something in the input buffer */  
        while(sdslen(c->querybuf)) {  
            /* 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;//按行解析  
                }  
            }  
      
            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);  
            }  
        }  
    }  

多重指令解析的處理函數爲processMultibulkBuffer,下面先簡單介紹下Redis的通訊協議

    以下是這個協議的一般形式:  
    *< 參數數量 > CR LF  
    $< 參數 1 的字節數量 > CR LF  
    < 參數 1 的數據 > CR LF  
    ...  
    $< 參數 N 的字節數量 > CR LF  
    < 參數 N 的數據 > CR LF  
    舉個例子,以下是一個命令協議的打印版本:  
    *3  
    $3  
    SET  
    $3  
    foo  
    $3  
    bar  
    這個命令的實際協議值如下:  
    "*3\r\n$3\r\nSET\r\n$3\r\foo\r\n$3\r\bar\r\n"  

    /** 
        例:querybuf = "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n" 
    */  
    int processMultibulkBuffer(redisClient *c) {  
        char *newline = NULL;  
        int pos = 0, ok;  
        long long ll;  
      
        if (c->multibulklen == 0) {//參數數目爲0,表示這是新的請求指令  
            /* The client should have been reset */  
            redisAssertWithInfo(c,NULL,c->argc == 0);  
      
            /* Multi bulk length cannot be read without a \r\n */  
            newline = strchr(c->querybuf,'\r');  
            if (newline == NULL) {  
                if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) {  
                    addReplyError(c,"Protocol error: too big mbulk count string");  
                    setProtocolError(c,0);  
                }  
                return REDIS_ERR;  
            }  
      
            /* 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] == '*');  
            //將字符串轉爲long long整數,轉換得到的結果存到ll中,ll就是後面參數的個數  
            ok = string2ll(c->querybuf+1,newline-(c->querybuf+1),&ll);  
            if (!ok || ll > 1024*1024) {  
                addReplyError(c,"Protocol error: invalid multibulk length");  
                setProtocolError(c,pos);  
                return REDIS_ERR;  
            }  
      
            pos = (newline-c->querybuf)+2;//跳過\r\n  
            if (ll <= 0) {//參數個數小於0,表示後面的參數數目大於等於絕對值ll  
                 /** s = sdsnew("Hello World"); 
                 * sdsrange(s,1,-1); => "ello World" 
                 */  
                sdsrange(c->querybuf,pos,-1);//querybuf="$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"  
                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);  
        /** 
            開始抽取字符串 
            querybuf = "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n" 
            pos = 4 
        */  
        while(c->multibulklen) {  
            /* Read bulk length if unknown */  
            if (c->bulklen == -1) {//參數的長度爲-1,這裏用來處理每個參數的字符串長度值  
                /**newline = "\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"*/  
                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);  
                    }  
                    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;  
                }  
      
                //得到字符串的長度值,ll  
                ok = string2ll(c->querybuf+pos+1,newline-(c->querybuf+pos+1),&ll);  
                if (!ok || ll < 0 || ll > 512*1024*1024) {  
                    addReplyError(c,"Protocol error: invalid bulk length");  
                    setProtocolError(c,pos);  
                    return REDIS_ERR;  
                }  
      
                //pos = 8  
                pos += newline-(c->querybuf+pos)+2;//跳過\r\n "SET\r\n$3\r\nfoo\r\n$3\r\nbar\r"  
                if (ll >= REDIS_MBULK_BIG_ARG) {//字符串長度超過1024*32,需要擴展  
                    size_t qblen;  
      
                    /* If we are going to read a large object from network 
                     * try to make it likely that it will start at c->querybuf 
                     * boundary so that we can optimize object creation 
                     * avoiding a large copy of data. */  
                     /** 
                        sdsrange(querybuf,pos,-1)是將[pos,len-1]之間的字符串使用memmove前移, 
                        然後後面的直接截斷 
                     */  
                    sdsrange(c->querybuf,pos,-1);//"SET\r\n$3\r\nfoo\r\n$3\r\nbar\r"  
                    pos = 0;  
                    qblen = sdslen(c->querybuf);  
                    /* Hint the sds library about the amount of bytes this string is 
                     * going to contain. */  
                    if (qblen < ll+2)//這裏只會到最後一個字符串纔可能爲True,並且數據不完整,數據不完整是由於redis使用非阻塞的原因  
                        c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2-qblen);  
                }  
                c->bulklen = ll;  
            }  
      
            /* Read bulk argument */  
            //讀取參數,沒有\r\n表示數據不全,也就是說服務器接收到的數據不完整  
            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->querybuf,然後清空querybuf,注意這裏只可能在最後一個字符串纔可能出現  
                    c->argv[c->argc++] = createObject(REDIS_STRING,c->querybuf);  
                    sdsIncrLen(c->querybuf,-2); /* remove CRLF */  
                    c->querybuf = sdsempty();  
                    /* Assume that if we saw a fat argument we'll see another one 
                     * likely... */  
                    c->querybuf = sdsMakeRoomFor(c->querybuf,c->bulklen+2);  
                    pos = 0;  
                } else {  
                    //抽取出具體的字符串,比如SET,建立一個stringObject  
                    c->argv[c->argc++] =  
                        createStringObject(c->querybuf+pos,c->bulklen);  
                    pos += c->bulklen+2;//跳過\r\n  
                }  
                c->bulklen = -1;  
                c->multibulklen--;  
            }  
        }  
      
        /** 
            由於採用的是非阻塞讀取客戶端數據的方式,那麼如果c->multibulklen != 0,那麼就表示 
            數據沒有接收完全,首先需要將當前的querybuf數據截斷 
        */  
        /* 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;  
    }  


processCommand與call函數


客戶端指令解析完之後,需要執行該指令,執行指令的兩個函數爲processCommand與call函數,這兩個函數除了單純的執行指令外,還做了許多其他的工作,這裏不詳解,看代碼僅僅找到指令如何執行還是很簡單的。


指令執行完之後,需要將得到的結果集返回給客戶端,這部分是如何工作的,下面開始分析。

在networking.c中可以發現許多以addRelpy爲前綴的函數名,這些函數都是用來處理各種不同類型的結果的,我們以典型的addReply函數爲例,進行分析。


addReply函數

該函數第一步工作就是調用prepareClientToWrite函數爲客戶端創建一個寫文件事件,事件的處理函數即將結果集發送給客戶端的函數爲sendReplyToClient.


int prepareClientToWrite(redisClient *c) {  
    if (c->flags & REDIS_LUA_CLIENT) return REDIS_OK;  
    if ((c->flags & REDIS_MASTER) &&  
        !(c->flags & REDIS_MASTER_FORCE_REPLY)) return REDIS_ERR;  
    if (c->fd <= 0) return REDIS_ERR; /* Fake client */  
    if (c->bufpos == 0 && listLength(c->reply) == 0 &&  
        (c->replstate == REDIS_REPL_NONE ||  
         c->replstate == REDIS_REPL_ONLINE) &&  
        aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,  
        sendReplyToClient, c) == AE_ERR) return REDIS_ERR;  
    return REDIS_OK;  
} 

第二步,就是根據相應的條件,將得到的結果rboj數據存儲到buf中或者reply鏈表中。對於存儲的策略:redis優先將數據存儲在固定大小的buf中,也就是redisClient結構體buf[REDIS_REPLY_CHUNK_BYTES]裏,默認大小爲16K。如果有數據沒有發送完或c->buf空間不足,就會放到c->reply鏈表裏面,鏈表每個節點都是內存buf,後來的數據放入最後面。具體的處理函數爲_addReplyToBuffer和_addReplyStringToList兩個函數。

    void addReply(redisClient *c, robj *obj) {  
        if (prepareClientToWrite(c) != REDIS_OK) return;  
      
        /* This is an important place where we can avoid copy-on-write 
         * when there is a saving child running, avoiding touching the 
         * refcount field of the object if it's not needed. 
         * 
         * If the encoding is RAW and there is room in the static buffer 
         * we'll be able to send the object to the client without 
         * messing with its page. */  
        if (obj->encoding == REDIS_ENCODING_RAW) {//字符串類型  
            //是否能將數據追加到c->buf中  
            if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)  
                _addReplyObjectToList(c,obj);//添加到c->reply鏈表中  
        } else if (obj->encoding == REDIS_ENCODING_INT) {//整數類型  
            /* Optimization: if there is room in the static buffer for 32 bytes 
             * (more than the max chars a 64 bit integer can take as string) we 
             * avoid decoding the object and go for the lower level approach. */  
             //追加到c->buf中  
            if (listLength(c->reply) == 0 && (sizeof(c->buf) - c->bufpos) >= 32) {  
                char buf[32];  
                int len;  
      
                len = ll2string(buf,sizeof(buf),(long)obj->ptr);//整型轉string  
                if (_addReplyToBuffer(c,buf,len) == REDIS_OK)  
                    return;  
                /* else... continue with the normal code path, but should never 
                 * happen actually since we verified there is room. */  
            }  
            obj = getDecodedObject(obj);//64位整數,先轉換爲字符串  
            if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)  
                _addReplyObjectToList(c,obj);  
            decrRefCount(obj);  
        } else {  
            redisPanic("Wrong obj->encoding in addReply()");  
        }  
    }  

    /** 
        Server將數據發送給Client,有兩種存儲數據的緩衝形式,具體參見redisClient結構體 
        1、Response buffer 
            int bufpos; //回覆 
            char buf[REDIS_REPLY_CHUNK_BYTES]; //長度爲16 * 1024 
        2、list *reply; 
            unsigned long reply_bytes; Tot bytes of objects in reply list 
            int sentlen;            已發送的字節數 
        如果已經使用reply的形式或者buf已經不夠存儲,那麼就將數據添加到list *reply中 
        否則將數據添加到buf中 
    */  
    int _addReplyToBuffer(redisClient *c, char *s, size_t len) {  
        size_t available = sizeof(c->buf)-c->bufpos;//計算出c->buf的剩餘長度  
      
        if (c->flags & REDIS_CLOSE_AFTER_REPLY) return REDIS_OK;  
      
        /* If there already are entries in the reply list, we cannot 
         * add anything more to the static buffer. */  
        if (listLength(c->reply) > 0) return REDIS_ERR;  
      
        /* Check that the buffer has enough space available for this string. */  
        if (len > available) return REDIS_ERR;  
      
        //回覆數據追加到buf中  
        memcpy(c->buf+c->bufpos,s,len);  
        c->bufpos+=len;  
        return REDIS_OK;  
    }  
      
    /** 
        1、如果鏈表長度爲0: 新建一個節點並直接將robj追加到鏈表的尾部 
        2、鏈表長度不爲0: 首先取出鏈表的尾部節點 
            1)、尾部節點的字符串長度 + robj中ptr字符串的長度 <= REDIS_REPLY_CHUNK_BYTES: 
                將robj->ptr追加到尾節點的tail->ptr後面 
            2)、反之: 新建一個節點並直接將robj追加到鏈表的尾部 
    */  
    void _addReplyObjectToList(redisClient *c, robj *o) {  
        robj *tail;  
      
        if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;  
      
        //鏈表長度爲0  
        if (listLength(c->reply) == 0) {  
            incrRefCount(o);//增加引用次數  
            listAddNodeTail(c->reply,o);//添加到鏈表末尾  
            c->reply_bytes += zmalloc_size_sds(o->ptr); //計算o->ptr的佔用內存大小  
        } else {  
            //取出鏈表尾中的數據  
            tail = listNodeValue(listLast(c->reply));  
      
            /* Append to this object when possible. */  
            // 如果最後一個節點所保存的回覆加上新回覆內容總長度小於等於 REDIS_REPLY_CHUNK_BYTES  
            // 那麼將新回覆追加到節點回復當中。  
            if (tail->ptr != NULL &&  
                sdslen(tail->ptr)+sdslen(o->ptr) <= REDIS_REPLY_CHUNK_BYTES)  
            {  
                c->reply_bytes -= zmalloc_size_sds(tail->ptr);  
                tail = dupLastObjectIfNeeded(c->reply);  
                tail->ptr = sdscatlen(tail->ptr,o->ptr,sdslen(o->ptr));  
                c->reply_bytes += zmalloc_size_sds(tail->ptr);  
            } else {//爲新回覆單獨創建一個節點  
                incrRefCount(o);  
                listAddNodeTail(c->reply,o);  
                c->reply_bytes += zmalloc_size_sds(o->ptr);  
            }  
        }  
        // 如果突破了客戶端的最大緩存限制,那麼關閉客戶端  
        asyncCloseClientOnOutputBufferLimitReached(c);  
    }  


sendReplyToClient函數


終於到了最後一步,把c->buf與c->reply中的數據發送給客戶端即可,發送同樣使用的是最原始的write函數。發送完成之後,redis將當前客戶端釋放,並且刪除寫事件,代碼比較簡單,不詳細解釋。



小結

本文粗略的介紹了Redis整體運行的流程,從服務器的角度,介紹Redis是如何初始化,創建socket,接收客戶端請求,解析請求及指令的執行,反饋執行的結果集給客戶端等。如果讀者想更深入的瞭解Redis的運行機制,需要親自閱讀源碼,本文可以用作參考。同時也是學習linux socket編程的好工具,原本簡簡單單的socket->bind->listen->accept->read->write也可以用來做許多高效的業務,是Linux socket學習的不二選擇。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章