(三)Redis 執行緒與IO模型

冬先生發表於2024-06-05

1、Redis 單執行緒

通常說 Redis 是單執行緒,主要是指 Redis 的網路 IO 和鍵值對讀寫是由一個執行緒來完成的,其他功能,比如持久化、非同步刪除、叢集資料同步等,是由額外的執行緒執行的,所以嚴格來說,Redis 並不是單執行緒。

多執行緒開發會不可避免的帶來併發控制和資源開銷的問題,如果沒有良好的系統設計往往會適得其反,為了避免這些問題,Redis 直接採用了單執行緒模式。

Redis 單執行緒模型能達到每秒數十萬級別的處理能力,一方面是大部分操作在記憶體上完成 + 高效的資料結構,例如雜湊表和跳錶。另一方面,就是採用了多路複用機制,使其在網路 IO 操作中能併發處理大量的客戶端請求,實現高吞吐率。

在瞭解多路複用之前,要先明白網路操作的基本 IO 模型和潛在的阻塞點。如果單執行緒被阻塞了,就無法進行多路複用了。以 Get 請求為例如下圖,bind/listen、accept、recv、parse 和 send 屬於網路 IO 處理,get 屬於鍵值資料操作。
這裡的網路 IO 操作中,潛在的阻塞點分別是 accept() 和 recv()。當 Redis 監聽到一個客戶端有連線請求,但一直未能成功建立起連線時,會阻塞在 accept() 函式這裡,導致其他客戶端無法和 Redis 建立連線。類似的,當 Redis 透過 recv() 從一個客戶端讀取資料時,如果資料一直沒有到達,Redis 也會一直阻塞在 recv(),這就導致 Redis 整個執行緒阻塞,無法處理其他客戶端請求,效率很低。不過,Socket 網路模型可以設定非阻塞模式,基於此 Linux 中的 IO 多路複用機制就要登場了。

2、多路複用機制

Linux 中的 IO 多路複用機制是指一個執行緒處理多個 IO 流,就是我們經常聽到的 select/epoll 機制。簡單來說,在 Redis 只執行單執行緒的情況下,該機制允許核心中,同時存在多個監聽套接字和已連線套接字。核心會一直監聽這些套接字上的連線請求或資料請求。一旦有請求到達,就會交給 Redis 執行緒處理,這就實現了一個 Redis 執行緒處理多個 IO 流的效果。
圖中的多個 FD 就是指多個套接字,Redis 網路框架呼叫 epoll 機制,讓核心監聽這些套接字。此時,Redis 執行緒不會阻塞在某一個特定的監聽或已連線套接字上,所以,Redis 可以同時和多個客戶端連線並處理請求,從而提升併發性。

為了在請求到達時能通知到 Redis 執行緒,select/epoll 提供了基於事件的回撥機制,即針對不同事件的發生,呼叫相應的處理函式,select/epoll 一旦監測到 FD 上有請求到達時,就會觸發相應的事件。這些事件會被放進一個事件佇列,Redis 單執行緒對該事件佇列不斷進行處理。這樣一來,Redis 無需一直輪詢是否有請求實際發生,這就可以避免造成 CPU 資源浪費。同時,Redis 在對事件佇列中的事件進行處理時,會呼叫相應的處理函式,這就實現了基於事件的回撥。因為 Redis 一直在對事件佇列進行處理,所以能及時響應客戶端請求,提升 Redis 的響應效能。

3、Redis 6.0 多執行緒特性

Redis 6.0 之前,雖然有些命令操作可以用後臺執行緒或子程序執行(比如資料刪除、快照生成、AOF 重寫),但是,從網路 IO 處理到實際的讀寫命令處理,都是由單個執行緒完成的,有時會成為 Redis 的效能瓶頸。Redis 6.0 之後採用多個 IO 執行緒來處理網路請求,提高網路請求處理的並行度,對於讀寫命令,仍然使用單執行緒來處理。

具體流程:
(1)主執行緒接收到客戶端連線請求後建立連線,將 Socket 放入全域性等待佇列中,透過輪詢分配給 IO 執行緒。
(2)分配後主執行緒就會進入阻塞狀態,等待 IO 執行緒完成客戶端請求讀取和解析,多個 IO 執行緒在並行處理,嗖嗖嗖。
(3)IO 執行緒解析完請求,主執行緒還是會以單執行緒的方式執行這些命令操作。
(4)主執行緒執行完請求操作後,把返回結果寫入緩衝區,主執行緒阻塞等待 IO 執行緒把這些結果回寫到 Socket 中,並返回給客戶端。
和 IO 執行緒讀取和解析請求一樣,IO 執行緒回寫 Socket 時,也是有多個執行緒在併發執行,所以回寫 Socket 的速度也很快。等到 IO 執行緒回寫 Socket 完畢,主執行緒會清空全域性佇列,等待客戶端的後續請求。

4、IO 多執行緒配置

在實際應用中,如果 Redis 例項的 CPU 開銷不大,吞吐量卻沒有提升,可以考慮使用多執行緒機制提升吞吐量,redis.conf 中設定:
1. 設定 io-thread-do-reads 配置項為 yes,表示啟用多執行緒

io-threads-do-reads yes

2. 設定執行緒個數要小於 Redis 例項所在機器的 CPU 核個數,例如,對於一個 8 核的機器來說,Redis 官方建議配置 6 個 IO 執行緒

io-threads  6

相關文章