1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        【面試題】一文講清,為啥redis單線程還有很高的性能?

        共 5800字,需瀏覽 12分鐘

         ·

        2021-09-27 08:01

        面試的時(shí)候如果聊到緩存,肯定會(huì)聊到redis,因?yàn)樗F(xiàn)在是緩存事實(shí)上的標(biāo)準(zhǔn)。


        早些年一些互聯(lián)網(wǎng)公司會(huì)用到memcached作為緩存,它是多線程的,用c語(yǔ)言開(kāi)發(fā),不過(guò)現(xiàn)在基本很少了,有興趣的同學(xué)可以學(xué)習(xí)下它的源碼。


        那么聊redis,第一個(gè)問(wèn)題就是它的工作原理,redis最最重要的工作原理,就是它的線程模型。


        redis是單線程、nio、異步的線程模型,你要是這個(gè)都不知道,工作中用到redis,出了問(wèn)題都不知道如何入手排查。


        學(xué)習(xí)redis線程模型之前,必須了解socket網(wǎng)絡(luò)編程相關(guān)知識(shí),如果你不懂socket,肯定搞不懂redis原理。


        redis使用文件事件處理器(file event handler)處理所有事件,這個(gè)文件事件處理器是單線程的,所以redis才叫單線程的。


        文件事件處理器采用IO多路復(fù)用器,同時(shí)監(jiān)聽(tīng)多個(gè)socket,將產(chǎn)生事件的socket放入內(nèi)存隊(duì)列中,事件分發(fā)器根據(jù)socket上的事件類(lèi)型,選擇對(duì)應(yīng)的事件處理器進(jìn)行處理。


        圖1  redis 文件事件處理器過(guò)程 


        文件事件處理器的結(jié)構(gòu)包含 4 個(gè)部分:

        • 客戶(hù)端socket
        • IO 多路復(fù)用器
        • 文件事件分派器
        • 事件處理器(連接應(yīng)答處理器、命令請(qǐng)求處理器、命令回復(fù)處理器)

        多個(gè)客戶(hù)端socket可能會(huì)并發(fā)產(chǎn)生不同的操作,每個(gè)操作對(duì)應(yīng)不同的事件,但是 IO 多路復(fù)用器會(huì)監(jiān)聽(tīng)多個(gè)socket,會(huì)將產(chǎn)生事件的 socket 放入隊(duì)列中排隊(duì),事件分派器每次從隊(duì)列中取出一個(gè) socket,根據(jù) socket 的事件類(lèi)型交給對(duì)應(yīng)的事件處理器進(jìn)行處理。

        連接應(yīng)答處理器

        在redis server啟動(dòng)的時(shí)候,會(huì)設(shè)置客戶(hù)端建立連接時(shí)的連接應(yīng)答處理器acceptTcpHandler,并和服務(wù)器監(jiān)聽(tīng)套接字的AE_READABLE 事件關(guān)聯(lián)起來(lái),具體代碼在server.c的initServer方法中:


        圖2 redis初始化設(shè)置連接應(yīng)答處理器

        在networking.c 文件中acceptTcpHandler方法實(shí)現(xiàn)中,redis的連接應(yīng)答處理器,用于對(duì)連接服務(wù)器進(jìn)行監(jiān)聽(tīng),對(duì)套接字的客戶(hù)端進(jìn)行應(yīng)答相應(yīng)。

        #define MAX_ACCEPTS_PER_CALL 1000void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {int cport, cfd, max = MAX_ACCEPTS_PER_CALL;char cip[NET_IP_STR_LEN];    UNUSED(el);    UNUSED(mask);    UNUSED(privdata);  //循環(huán)處理連接應(yīng)答while(max--) {        cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);if (cfd == ANET_ERR) {if (errno != EWOULDBLOCK)                serverLog(LL_WARNING,"Accepting client connection: %s", server.neterr);return;        }        serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);        acceptCommonHandler(connCreateAcceptedSocket(cfd),0,cip);    }}

        連接應(yīng)答處理器

        在接收到客戶(hù)端的連接后,觸發(fā)鏈接應(yīng)答處理器的acceptTcpHandler方法,這個(gè)方法里會(huì)創(chuàng)建客戶(hù)端對(duì)于的client對(duì)象,它代表著連接到 Redis 客戶(hù)端。并且accept客戶(hù)端的連接,然后把這個(gè)連接與命令處理器readQueryFromClient關(guān)聯(lián)起來(lái),服務(wù)器會(huì)將連接成功后的socket的AE_READABLE事件和命令請(qǐng)求處理器關(guān)聯(lián)起來(lái),命令請(qǐng)求處理器就可以從socket中讀取數(shù)據(jù)了。

        圖3 建立連接后設(shè)置命令請(qǐng)求處理器

        通過(guò) networking.c中的readQueryFromClient方法,讀取客戶(hù)端發(fā)送的命令內(nèi)容。
        void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {    client *c = (client*) privdata;    int nread, readlen;    size_t qblen;UNUSED(el);UNUSED(mask);
        readlen = PROTO_IOBUF_LEN;/* 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 == PROTO_REQ_MULTIBULK && c->multibulklen && c->bulklen != -1 && c->bulklen >= PROTO_MBULK_BIG_ARG) { ssize_t remaining = (size_t)(c->bulklen+2)-sdslen(c->querybuf);
        /* Note that the 'remaining' variable may be zero in some edge case, * for example once we resume a blocked client after CLIENT PAUSE. */if (remaining > 0 && 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) {return; } else { serverLog(LL_VERBOSE, "Reading from client: %s",strerror(errno)); freeClient(c);return; } } else if (nread == 0) { serverLog(LL_VERBOSE, "Client closed connection"); freeClient(c);return; } else if (c->flags & CLIENT_MASTER) {/* Append the query buffer to the pending (not applied) buffer * of the master. We'll use this buffer later in order to have a * copy of the string applied by the last command executed. */c->pending_querybuf = sdscatlen(c->pending_querybuf,c->querybuf+qblen,nread); }
        sdsIncrLen(c->querybuf,nread);c->lastinteraction = server.unixtime;if (c->flags & CLIENT_MASTER) c->read_reploff += nread; server.stat_net_input_bytes += nread;if (sdslen(c->querybuf) > server.client_max_querybuf_len) { sds ci = catClientInfoString(sdsempty(),c), bytes = sdsempty();
        bytes = sdscatrepr(bytes,c->querybuf,64); serverLog(LL_WARNING,"Closing client that reached max query buffer length: %s (qbuf initial bytes: %s)", ci, bytes); sdsfree(ci); sdsfree(bytes); freeClient(c);return; }
        /* Time to process the buffer. If the client is a master we need to * compute the difference between the applied offset before and after * processing the buffer, to understand how much of the replication stream * was actually applied to the master state: this quantity, and its * corresponding part of the replication stream, will be propagated to * the sub-slaves and to the replication backlog. */ processInputBufferAndReplicate(c);}
        void processInputBufferAndReplicate(client *c) {if (!(c->flags & CLIENT_MASTER)) { processInputBuffer(c); } else { size_t prev_offset = c->reploff; processInputBuffer(c); size_t applied = c->reploff - prev_offset;if (applied) { replicationFeedSlavesFromMasterStream(server.slaves,c->pending_querybuf, applied); sdsrange(c->pending_querybuf,applied,-1); } }}
        processInputBuffer主要是將輸入緩沖區(qū)中的數(shù)據(jù)解析成對(duì)應(yīng)的命令,根據(jù)命令類(lèi)型是 PROTO_REQ_MULTIBULK 還是 PROTO_REQ_INLINE,來(lái)分別調(diào)用 processInlineBuffer 和 processMultibulkBuffer 方法來(lái)解析命令。

        然后調(diào)用 processCommand 方法來(lái)執(zhí)行命令,redis中有一個(gè)類(lèi)似map的東西,會(huì)記錄每條命令對(duì)應(yīng)的handler,根據(jù)解析出來(lái)的命令執(zhí)行對(duì)應(yīng)的handler即可。

        命令回復(fù)處理器

        命令處理完之后,redis server就要給客戶(hù)端返回處理結(jié)果。networking.c中的sendReplyToClient方法就是redis 的命令回復(fù)處理器,此時(shí)會(huì)將socket的AE_WRITEABLE事件和命令回復(fù)處理器關(guān)聯(lián)起來(lái)。這個(gè)處理器負(fù)責(zé)將服務(wù)器執(zhí)行命令后得到的命令回復(fù)先寫(xiě)到緩存,再通過(guò)套接字返回給客戶(hù)端。

        圖4 設(shè)置命令回復(fù)處理器

        最后畫(huà)一幅完整的流程圖,方便大家理解記憶。

        圖5 redis線程模型

        有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)

        歡迎大家關(guān)注Java之道公眾號(hào)


        好文章,我在看??

        瀏覽 30
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            日韩成人精品一区二区 | 亚洲成人自拍 | 嫩BX区二区三区的区别 | 成人毛片在线免费观看 | 欧美视频手机在线 | 久久久久久夜精品精品免费 | 一级A片60分钟免费看 | 成人网站在线 | 成人做爱全过程免费的视频 | 三级在线观看视频 |