純干貨|深度解析Redis線程模型設(shè)計(jì)原理

單線程模型設(shè)計(jì)
單線程模型為何效率高
文件事件處理器
Redis 基于 Reactor 模式開發(fā)了自己的網(wǎng)絡(luò)事件處理器 - 文件事件處理器(file event handler,后文簡稱為 FEH),而該處理器又是單線程的,所以redis設(shè)計(jì)為單線程模型。
采用I/O多路復(fù)用同時(shí)監(jiān)聽多個(gè)socket,根據(jù)socket當(dāng)前執(zhí)行的事件來為 socket 選擇對(duì)應(yīng)的事件處理器。
當(dāng)被監(jiān)聽的socket準(zhǔn)備好執(zhí)行accept、read、write、close等操作時(shí),和操作對(duì)應(yīng)的文件事件就會(huì)產(chǎn)生,這時(shí)FEH就會(huì)調(diào)用socket之前關(guān)聯(lián)好的事件處理器來處理對(duì)應(yīng)事件。
所以雖然FEH是單線程運(yùn)行,但通過I/O多路復(fù)用監(jiān)聽多個(gè)socket,不僅實(shí)現(xiàn)高性能的網(wǎng)絡(luò)通信模型,又能和 Redis 服務(wù)器中其它同樣單線程運(yùn)行的模塊交互,保證了Redis內(nèi)部單線程模型的簡潔設(shè)計(jì)。
下面來看文件事件處理器的幾個(gè)組成部分。

socket
文件事件就是對(duì)socket操作的抽象, 每當(dāng)一個(gè) socket 準(zhǔn)備好執(zhí)行連接accept、read、write、close等操作時(shí), 就會(huì)產(chǎn)生一個(gè)文件事件。一個(gè)服務(wù)器通常會(huì)連接多個(gè)socket, 多個(gè)socket可能并發(fā)產(chǎn)生不同操作,每個(gè)操作對(duì)應(yīng)不同文件事件。


?I/O多路復(fù)用程序
I/O 多路復(fù)用程序會(huì)負(fù)責(zé)監(jiān)聽多個(gè)socket。
盡管文件事件可能并發(fā)出現(xiàn), 但 I/O 多路復(fù)用程序會(huì)將所有產(chǎn)生事件的socket放入隊(duì)列, 通過該隊(duì)列以有序、同步且每次一個(gè)socket的方式向文件事件分派器傳送socket。


????? 當(dāng)上一個(gè)socket產(chǎn)生的事件被對(duì)應(yīng)事件處理器執(zhí)行完后, I/O 多路復(fù)用程序才會(huì)向文件事件分派器傳送下個(gè)socket, 如下:
?I/O多路復(fù)用程序的實(shí)現(xiàn)
???? Redis 的 I/O 多路復(fù)用程序的所有功能都是通過包裝常見的 select 、 epoll 、 evport 和 kqueue 這些 I/O 多路復(fù)用函數(shù)庫實(shí)現(xiàn)的。
???? 每個(gè) I/O 多路復(fù)用函數(shù)庫在 Redis 源碼中都對(duì)應(yīng)一個(gè)單獨(dú)的文件:

???? 因?yàn)?Redis 為每個(gè) I/O 多路復(fù)用函數(shù)庫都實(shí)現(xiàn)了相同的 API , 所以 I/O 多路復(fù)用程序的底層實(shí)現(xiàn)是可以互換的。Redis 在 I/O 多路復(fù)用程序的實(shí)現(xiàn)源碼`ae.c`文件中宏定義了相應(yīng)規(guī)則,使得程序在編譯時(shí)自動(dòng)選擇系統(tǒng)中性能最高的 I/O 多路復(fù)用函數(shù)庫作為 Redis 的 I/O 多路復(fù)用程序的底層實(shí)現(xiàn):性能降序排列。

?文件事件分派器
文件事件分派器接收 I/O 多路復(fù)用程序傳來的socket, 并根據(jù)socket產(chǎn)生的事件類型, 調(diào)用相應(yīng)的事件處理器。


?文件事件處理器
服務(wù)器會(huì)為執(zhí)行不同任務(wù)的套接字關(guān)聯(lián)不同的事件處理器, 這些處理器是一個(gè)個(gè)函數(shù), 它們定義了某個(gè)事件發(fā)生時(shí), 服務(wù)器應(yīng)該執(zhí)行的動(dòng)作。
Redis 為各種文件事件需求編寫了多個(gè)處理器,若客戶端:
連接Redis,對(duì)連接服務(wù)器的各個(gè)客戶端進(jìn)行應(yīng)答,就需要將socket映射到連接應(yīng)答處理器
寫數(shù)據(jù)到Redis,接收客戶端傳來的命令請(qǐng)求,就需要映射到命令請(qǐng)求處理器
從Redis讀數(shù)據(jù),向客戶端返回命令的執(zhí)行結(jié)果,就需要映射到命令回復(fù)處理器
當(dāng)主服務(wù)器和從服務(wù)器進(jìn)行復(fù)制操作時(shí), 主從服務(wù)器都需要映射到特別為復(fù)制功能編寫的復(fù)制處理器。


?文件事件的類型
I/O 多路復(fù)用程序可以監(jiān)聽多個(gè)socket的 ae.h/AE_READABLE 事件和 ae.h/AE_WRITABLE 事件, 這兩類事件和套接字操作之間的對(duì)應(yīng)關(guān)系如下:


當(dāng)socket可讀(比如客戶端對(duì)Redis執(zhí)行write/close操作),或有新的可應(yīng)答的socket出現(xiàn)時(shí)(即客戶端對(duì)Redis執(zhí)行connect操作),socket就會(huì)產(chǎn)生一個(gè)AE_READABLE事件

當(dāng)socket可寫時(shí)(比如客戶端對(duì)Redis執(zhí)行read操作),socket會(huì)產(chǎn)生一個(gè)AE_WRITABLE事件。

I/O多路復(fù)用程序可以同時(shí)監(jiān)聽AE_REABLE和AE_WRITABLE兩種事件,要是一個(gè)socket同時(shí)產(chǎn)生這兩種事件,那么文件事件分派器優(yōu)先處理AE_REABLE事件。即一個(gè)socket又可讀又可寫時(shí), Redis服務(wù)器先讀后寫socket。
總結(jié)
????最后,讓我們梳理一下客戶端和Redis服務(wù)器通信的整個(gè)過程:

Redis啟動(dòng)初始化時(shí),將連接應(yīng)答處理器跟AE_READABLE事件關(guān)聯(lián)。
若一個(gè)客戶端發(fā)起連接,會(huì)產(chǎn)生一個(gè)AE_READABLE事件,然后由連接應(yīng)答處理器負(fù)責(zé)和客戶端建立連接,創(chuàng)建客戶端對(duì)應(yīng)的socket,同時(shí)將這個(gè)socket的AE_READABLE事件和命令請(qǐng)求處理器關(guān)聯(lián),使得客戶端可以向主服務(wù)器發(fā)送命令請(qǐng)求。
當(dāng)客戶端向Redis發(fā)請(qǐng)求時(shí)(不管讀還是寫請(qǐng)求),客戶端socket都會(huì)產(chǎn)生一個(gè)AE_READABLE事件,觸發(fā)命令請(qǐng)求處理器。處理器讀取客戶端的命令內(nèi)容, 然后傳給相關(guān)程序執(zhí)行。
當(dāng)Redis服務(wù)器準(zhǔn)備好給客戶端的響應(yīng)數(shù)據(jù)后,會(huì)將socket的AE_WRITABLE事件和命令回復(fù)處理器關(guān)聯(lián),當(dāng)客戶端準(zhǔn)備好讀取響應(yīng)數(shù)據(jù)時(shí),會(huì)在socket產(chǎn)生一個(gè)AE_WRITABLE事件,由對(duì)應(yīng)命令回復(fù)處理器處理,即將準(zhǔn)備好的響應(yīng)數(shù)據(jù)寫入socket,供客戶端讀取。
命令回復(fù)處理器全部寫完到 socket 后,就會(huì)刪除該socket的AE_WRITABLE事件和命令回復(fù)處理器的映射。
參考
《Redis 設(shè)計(jì)與實(shí)現(xiàn)》



