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

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

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


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


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

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

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


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


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


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

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

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

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



