一文弄懂Redis 線程模型
一、概述
【1】Redis
是基于 Reactor 模式開發(fā)的網(wǎng)絡(luò)事件處理器:這個處理器被稱為文件事件處理器(file event handler
),這個文件事件處理器是單線程的,所以 Redis
才叫做單線程的模型:
- 文件事件處理器使用
I/O
多路復(fù)用(multiplexing)機(jī)制監(jiān)聽多個套接字Socket
,根據(jù)Socket
上的事件來選擇對應(yīng)的事件處理器進(jìn)行處理。 - 當(dāng)被監(jiān)聽的套接字準(zhǔn)備好執(zhí)行連接應(yīng)答(
accept
)、讀取(read
)、寫入(write
)、關(guān)閉(close
)等操作時。與操作相對應(yīng)的文件事件就會產(chǎn)生,這時文件事件處理器就會調(diào)用套接字之前關(guān)聯(lián)好的事件處理器來處理這些事件。
【2】雖然文件事件處理器以單線程的方式運行,但其使用 I/O
多路復(fù)用程序來監(jiān)聽多個套接字,文件事件處理器既實現(xiàn)了高性能的網(wǎng)絡(luò)通信模型,又可以很好地與 Redis
服務(wù)器中其他同樣以單線程方式運行的模塊進(jìn)行對接,這保持了 Redis
內(nèi)部單線程設(shè)計的簡單性。
二、文件事件處理器的結(jié)構(gòu)
【1】文件事件處理器的結(jié)構(gòu)包含 4 個部分:
- 多個
socket
-
IO
多路復(fù)用程序 - 文件事件分派器
- 事件處理器(連接應(yīng)答處理器、命令請求處理器、命令回復(fù)處理器)
【2】多個 socket
可能會并發(fā)產(chǎn)生不同的操作,每個操作對應(yīng)不同的文件事件,但是 IO
多路復(fù)用程序會監(jiān)聽多個 socket
,會將 socket
產(chǎn)生的事件放入隊列中排隊,以有序(sequentially
)、同步(synchronously
)、每次一個套接字的方式向文件事件分派器傳送套接字。當(dāng)上一個套接字產(chǎn)生的事件被處理完畢之后(該套接字為事件所關(guān)聯(lián)的事件處理器執(zhí)行完畢), I/O
多路復(fù)用程序才會繼續(xù)向文件事件分派器傳送下一個套接字, 如圖:
文件事件分派器接收 I/O
多路復(fù)用程序傳來的套接字, 并根據(jù)套接字產(chǎn)生的事件的類型, 調(diào)用相應(yīng)的事件處理器。服務(wù)器會為執(zhí)行不同任務(wù)的套接字關(guān)聯(lián)不同的事件處理器, 這些處理器是一個個函數(shù), 它們定義了某個事件發(fā)生時, 服務(wù)器應(yīng)該執(zhí)行的動作。
【3】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)一個單獨的文件, 比如 ae_select.c、ae_epoll.c、ae_kqueue.c
, 諸如此類。因為 Redis
為每個 I/O
多路復(fù)用函數(shù)庫都實現(xiàn)了相同的 API
, 所以 I/O
多路復(fù)用程序的底層實現(xiàn)是可以互換的, 如下圖所示:
Redis
在 I/O
多路復(fù)用程序的實現(xiàn)源碼中用 #include
宏定義了相應(yīng)的規(guī)則, 程序會在編譯時自動選擇系統(tǒng)中性能最高的 I/O
多路復(fù)用函數(shù)庫來作為 Redis
的 I/O
多路復(fù)用程序的底層實現(xiàn):
/* 包括此系統(tǒng)支持的最佳復(fù)用層。 * 以下應(yīng)按性能降序排列。 */ #ifdef HAVE_EVPORT #include "ae_evport.c" #else #ifdef HAVE_EPOLL #include "ae_epoll.c" #else #ifdef HAVE_KQUEUE #include "ae_kqueue.c" #else #include "ae_select.c" #endif #endif #endif
【4】事件的類型:I/O
多路復(fù)用程序可以監(jiān)聽多個套接字的 ae.h/AE_READABLE
事件和 ae.h/AE_WRITABLE
事件, 這兩類事件和套接字操作之間的對應(yīng)關(guān)系如下:
- 當(dāng)套接字變得可讀時(客戶端對套接字執(zhí)行
write
操作,或者執(zhí)行close
操作), 或者有新的可應(yīng)答(acceptable
)套接字出現(xiàn)時(客戶端對服務(wù)器的監(jiān)聽套接字執(zhí)行connect
操作), 套接字產(chǎn)生AE_READABLE
事件。 - 當(dāng)套接字變得可寫時(客戶端對套接字執(zhí)行
read
操作), 套接字產(chǎn)生AE_WRITABLE
事件。
I/O
多路復(fù)用程序允許服務(wù)器同時監(jiān)聽套接字的AE_READABLE
事件和AE_WRITABLE
事件, 如果一個套接字同時產(chǎn)生了這兩種事件, 那么文件事件分派器會優(yōu)先處理AE_READABLE
事件, 等到AE_READABLE
事件處理完之后, 才處理AE_WRITABLE
事件。這也就是說, 如果一個套接字又可讀又可寫的話, 那么服務(wù)器將先讀套接字, 后寫套接字。
【5】API
:ae.c/aeCreateFileEvent
函數(shù)接收一個套接字描述符、 一個事件類型、 以及一個事件處理器作為參數(shù), 將給定套接字的給定事件加入到 I/O
多路復(fù)用程序的監(jiān)聽范圍之內(nèi), 并對事件和事件處理器進(jìn)行關(guān)聯(lián)。ae.c/aeDeleteFileEvent
函數(shù)接收一個套接字描述符和一個監(jiān)聽事件類型作為參數(shù), 讓 I/O
多路復(fù)用程序取消對給定套接字的給定事件的監(jiān)聽, 并取消事件和事件處理器之間的關(guān)聯(lián)。ae.c/aeGetFileEvents
函數(shù)接收一個套接字描述符, 返回該套接字正在被監(jiān)聽的事件類型:
- 如果套接字沒有任何事件被監(jiān)聽, 那么函數(shù)返回
AE_NONE
; - 如果套接字的讀事件正在被監(jiān)聽, 那么函數(shù)返回
AE_READABLE
; - 如果套接字的寫事件正在被監(jiān)聽, 那么函數(shù)返回
AE_WRITABLE
; - 如果套接字的讀事件和寫事件正在被監(jiān)聽, 那么函數(shù)返回
AE_READABLE | AE_WRITABLE
;
ae.c/aeWait
函數(shù)接受一個套接字描述符、一個事件類型和一個毫秒數(shù)為參數(shù), 在給定的時間內(nèi)阻塞并等待套接字的給定類型事件產(chǎn)生, 當(dāng)事件成功產(chǎn)生, 或者等待超時之后, 函數(shù)返回。
ae.c/aeApiPoll
函數(shù)接受一個 sys/time.h/struct timeval
結(jié)構(gòu)為參數(shù), 并在指定的時間內(nèi), 阻塞并等待所有被 aeCreateFileEvent
函數(shù)設(shè)置為監(jiān)聽狀態(tài)的套接字產(chǎn)生文件事件, 當(dāng)有至少一個事件產(chǎn)生, 或者等待超時后, 函數(shù)返回。ae.c/aeProcessEvents
函數(shù)是文件事件分派器, 它先調(diào)用 aeApiPoll
函數(shù)來等待事件產(chǎn)生, 然后遍歷所有已產(chǎn)生的事件, 并調(diào)用相應(yīng)的事件處理器來處理這些事件。
ae.c/aeGetApiName
函數(shù)返回 I/O
多路復(fù)用程序底層所使用的 I/O
多路復(fù)用函數(shù)庫的名稱: 返回 "epoll"
表示底層為 epoll
函數(shù)庫, 返回"select"
表示底層為 select
函數(shù)庫, 諸如此類。
【6】文件事件的處理器:Redis
為文件事件編寫了多個處理器, 這些事件處理器分別用于實現(xiàn)不同的網(wǎng)絡(luò)通訊需求, 比如:
- 為了對連接服務(wù)器的各個客戶端進(jìn)行應(yīng)答, 服務(wù)器要為監(jiān)聽套接字關(guān)聯(lián)連接應(yīng)答處理器;
- 為了接收客戶端傳來的命令請求, 服務(wù)器要為客戶端套接字關(guān)聯(lián)命令請求處理器;
- 為了向客戶端返回命令的執(zhí)行結(jié)果, 服務(wù)器要為客戶端套接字關(guān)聯(lián)命令回復(fù)處理器;
- 當(dāng)主服務(wù)器和從服務(wù)器進(jìn)行復(fù)制操作時, 主從服務(wù)器都需要關(guān)聯(lián)特別為復(fù)制功能編寫的復(fù)制處理器;
在這些事件處理器里面, 服務(wù)器最常用的要數(shù)與客戶端進(jìn)行通信的連接應(yīng)答處理器、 命令請求處理器和命令回復(fù)處理器。
【7】連接應(yīng)答處理器:networking.c/acceptTcpHandler
函數(shù)是 Redis
的連接應(yīng)答處理器, 這個處理器用于對連接服務(wù)器監(jiān)聽套接字的客戶端進(jìn)行應(yīng)答, 具體實現(xiàn)為sys/socket.h/accept
函數(shù)的包裝。當(dāng) Redis
服務(wù)器進(jìn)行初始化的時候, 程序會將這個連接應(yīng)答處理器和服務(wù)器監(jiān)聽套接字的 AE_READABLE
事件關(guān)聯(lián)起來, 當(dāng)有客戶端用sys/socket.h/connect
函數(shù)連接服務(wù)器監(jiān)聽套接字的時候, 套接字就會產(chǎn)生 AE_READABLE
事件, 引發(fā)連接應(yīng)答處理器執(zhí)行, 并執(zhí)行相應(yīng)的套接字應(yīng)答操作, 如圖 IMAGE_SERVER_ACCEPT_CONNECT
所示。
【8】命令請求處理器:networking.c/readQueryFromClient
函數(shù)是 Redis 的命令請求處理器, 這個處理器負(fù)責(zé)從套接字中讀入客戶端發(fā)送的命令請求內(nèi)容, 具體實現(xiàn)為 unistd.h/read
函數(shù)的包裝。當(dāng)一個客戶端通過連接應(yīng)答處理器成功連接到服務(wù)器之后, 服務(wù)器會將客戶端套接字的 AE_READABLE
事件和命令請求處理器關(guān)聯(lián)起來, 當(dāng)客戶端向服務(wù)器發(fā)送命令請求的時候, 套接字就會產(chǎn)生 AE_READABLE
事件, 引發(fā)命令請求處理器執(zhí)行, 并執(zhí)行相應(yīng)的套接字讀入操作, 如圖 IMAGE_SERVER_RECIVE_COMMAND_REQUEST
所示。
當(dāng)命令回復(fù)發(fā)送完畢之后, 服務(wù)器就會解除命令回復(fù)處理器與客戶端套接字的 AE_WRITABLE
事件之間的關(guān)聯(lián)。
三、客戶端與 redis 的一次通信過程
【1】客戶端 socket01
向 redis
的 server
socket
請求建立連接,此時 server
socket
會產(chǎn)生一個 AE_READABLE
事件,IO
多路復(fù)用程序監(jiān)聽到 server
socket
產(chǎn)生的事件后,將該事件壓入隊列中。文件事件分派器從隊列中獲取該事件,交給連接應(yīng)答處理器。連接應(yīng)答處理器會創(chuàng)建一個能與客戶端通信的 socket01
,并將該 socket01
的 AE_READABLE
事件與命令請求處理器關(guān)聯(lián)。
【2】假設(shè)此時客戶端發(fā)送了一個 set key value
請求,此時 redis
中的 socket01
會產(chǎn)生 AE_READABLE
事件,IO 多路復(fù)用程序?qū)⑹录喝腙犃?,此時事件分派器從隊列中獲取到該事件,由于前面 socket01
的 AE_READABLE
事件已經(jīng)與命令請求處理器關(guān)聯(lián),因此事件分派器將事件交給命令請求處理器來處理。命令請求處理器讀取 socket01
的 key value
并在自己內(nèi)存中完成 key value
的設(shè)置。操作完成后,它會將 socket01
的 AE_WRITABLE
事件與命令回復(fù)處理器關(guān)聯(lián)。
【3】如果此時客戶端準(zhǔn)備好接收返回結(jié)果了,那么 redis
中的 socket01
會產(chǎn)生一個 AE_WRITABLE
事件,同樣壓入隊列中,事件分派器找到相關(guān)聯(lián)的命令回復(fù)處理器,由命令回復(fù)處理器對 socket01
輸入本次操作的一個結(jié)果,比如 ok
,之后解除 socket01
的 AE_WRITABLE
事件與命令回復(fù)處理器的關(guān)聯(lián)。這樣便完成了一次通信。
四、為啥 redis 單線程模型也能效率這么高
- 純內(nèi)存操作
- 核心是基于非阻塞的
IO
多路復(fù)用機(jī)制 - 單線程反而避免了多線程的頻繁上下文切換問題
到此這篇關(guān)于一文弄懂Redis 線程模型的文章就介紹到這了,更多相關(guān)Redis 線程模型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis和Nginx實現(xiàn)限制接口請求頻率的示例
限流就是限制API訪問頻率,當(dāng)訪問頻率超過某個閾值時進(jìn)行拒絕訪問等操作,本文主要介紹了Redis和Nginx實現(xiàn)限制接口請求頻率的示例,具有一定的參考價值,感興趣的可以了解一下2024-02-02詳解Redis數(shù)據(jù)結(jié)構(gòu)之跳躍表
這篇文章主要介紹了Redis數(shù)據(jù)結(jié)構(gòu)中的跳躍表的相關(guān)知識,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11在Centos?8.0中安裝Redis服務(wù)器的教程詳解
由于考慮到linux服務(wù)器的性能,所以經(jīng)常需要把一些中間件安裝在linux服務(wù)上,今天通過本文給大家介紹下在Centos?8.0中安裝Redis服務(wù)器的詳細(xì)過程,感興趣的朋友一起看看吧2022-03-03如何提高Redis服務(wù)器的最大打開文件數(shù)限制
文章討論了如何提高Redis服務(wù)器的最大打開文件數(shù)限制,以支持高并發(fā)服務(wù),本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2025-01-01