日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【面试题】一文讲清,为啥redis单线程还有很高的性能?

發布時間:2025/3/16 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【面试题】一文讲清,为啥redis单线程还有很高的性能? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

面試的時候如果聊到緩存,肯定會聊到redis,因為它現在是緩存事實上的標準。

早些年一些互聯網公司會用到memcached作為緩存,它是多線程的,用c語言開發,不過現在基本很少了,有興趣的同學可以學習下它的源碼。

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

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

學習redis線程模型之前,必須了解socket網絡編程相關知識,如果你不懂socket,肯定搞不懂redis原理。

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

文件事件處理器采用IO多路復用器,同時監聽多個socket,將產生事件的socket放入內存隊列中,事件分發器根據socket上的事件類型,選擇對應的事件處理器進行處理。

圖1? redis 文件事件處理器過程?

文件事件處理器的結構包含 4 個部分:

  • 客戶端socket

  • IO 多路復用器

  • 文件事件分派器

  • 事件處理器(連接應答處理器、命令請求處理器、命令回復處理器)

多個客戶端socket可能會并發產生不同的操作,每個操作對應不同的事件,但是 IO 多路復用器會監聽多個socket,會將產生事件的 socket 放入隊列中排隊,事件分派器每次從隊列中取出一個 socket,根據 socket 的事件類型交給對應的事件處理器進行處理。

連接應答處理器

在redis server啟動的時候,會設置客戶端建立連接時的連接應答處理器acceptTcpHandler,并和服務器監聽套接字的AE_READABLE 事件關聯起來,具體代碼在server.c的initServer方法中:

圖2 redis初始化設置連接應答處理器

在networking.c 文件中acceptTcpHandler方法實現中,redis的連接應答處理器,用于對連接服務器進行監聽,對套接字的客戶端進行應答相應。

#define MAX_ACCEPTS_PER_CALL 1000 void 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);//循環處理連接應答 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);} }

連接應答處理器

在接收到客戶端的連接后,觸發鏈接應答處理器的acceptTcpHandler方法,這個方法里會創建客戶端對于的client對象,它代表著連接到 Redis 客戶端。并且accept客戶端的連接,然后把這個連接與命令處理器readQueryFromClient關聯起來,服務器會將連接成功后的socket的AE_READABLE事件和命令請求處理器關聯起來,命令請求處理器就可以從socket中讀取數據了。

圖3?建立連接后設置命令請求處理器

通過 networking.c中的readQueryFromClient方法,讀取客戶端發送的命令內容。

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主要是將輸入緩沖區中的數據解析成對應的命令,根據命令類型是 PROTO_REQ_MULTIBULK 還是 PROTO_REQ_INLINE,來分別調用 processInlineBuffer 和 processMultibulkBuffer 方法來解析命令。

然后調用 processCommand 方法來執行命令,redis中有一個類似map的東西,會記錄每條命令對應的handler,根據解析出來的命令執行對應的handler即可。

命令回復處理器

命令處理完之后,redis server就要給客戶端返回處理結果。networking.c中的sendReplyToClient方法就是redis 的命令回復處理器,此時會將socket的AE_WRITEABLE事件和命令回復處理器關聯起來。這個處理器負責將服務器執行命令后得到的命令回復先寫到緩存,再通過套接字返回給客戶端。

圖4 設置命令回復處理器

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

圖5?redis線程模型

有道無術,術可成;有術無道,止于術

歡迎大家關注Java之道公眾號

好文章,我在看??

總結

以上是生活随笔為你收集整理的【面试题】一文讲清,为啥redis单线程还有很高的性能?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。