epoll 與 Redis 單線程架構
這篇筆記記錄了 epoll 的基本概念,以及它如何幫助單線程的 Redis 達到極高的效能。
什麼是 epoll?
epoll 是 Linux 核心提供的一種 I/O 多工(I/O Multiplexing)機制。它主要用來解決傳統 select 和 poll 效能瓶頸的問題。
在傳統的 select / poll 中,每當檢查是否有事件發生時,必須將所有的檔案描述符(File Descriptor, FD)全部輪詢(O(N) 複雜度)一遍。當連線數非常巨大時,這個輪詢的開銷會變得非常可觀。
補充:什麼是檔案描述符(File Descriptor, FD)?
在 Linux 作業系統中,「一切皆檔案(Everything is a file)」。不管是普通的文字檔、目錄,還是網路連線(Socket)、硬體設備,作業系統都把它們當作「檔案」來處理。
當一個程式開啟一個檔案或建立一個網路連線時,作業系統核心會回傳一個非負整數(例如:
3,4,5…)給這個程式。這個數字就是「檔案描述符(FD)」。它就像是這個檔案或連線的「身分證字號」或是「號碼牌」。生活例子:
想像你是一間餐廳的服務生,作業系統是帶位櫃檯。 當有新客人(一個網路連線請求)進來時,櫃檯會給這桌客人分配一個「桌號」(例如:
桌號 3)。 這個「桌號 3」就是 FD。之後你(應用程式)要幫這桌客人點餐或上菜(讀寫資料),都是透過告訴櫃檯:「我要處理『桌號 3』的餐點」,而不是告訴櫃檯客人的名字。因此,在網路通訊的語境中,所謂的「輪詢所有的檔案描述符」,就是在輪詢「所有正在連線中的 Socket」,看看哪一條連線有新的資料傳進來。
而 epoll 改變了這個作法:
- 它基於事件驅動:只有當某些 FD 狀態改變(例如有資料可讀或可寫)時,核心才會主動將這些就緒的 FD 加入到一個就緒清單中。
- 當應用程式呼叫
epoll_wait時,只需要獲取這些就緒的 FD,而不需要遍歷所有的 FD(O(1)複雜度)。這讓它在面對大量連線(C10K 問題)時依舊能保持優異的效能。
補充:什麼是 C10K 問題?
C10K 代表 “Client 10,000”(一萬個客戶端並發連線)。這是在 1999 年由 Dan Kegel 提出的一個知名網路伺服器效能瓶頸問題。 在早期的網路架構與作業系統(使用
select或poll)中,當伺服器同時處理的網路連線數達到 10,000 個時,因為O(N)的輪詢開銷與大量的執行緒/行程建立,伺服器的 CPU 資源會被耗費在無意義的等待與切換上,導致系統效能急遽下降,無法回應請求。
epoll的出現,將獲取活躍連線的複雜度降到O(1),正是 Linux 能夠跨越 C10K 門檻(甚至邁向 C10M)的關鍵技術。
簡單來說,select 像是老師每次點名都要把全班名單從頭到尾念一遍問「誰有問題?」;而 epoll 像是老師只看舉手的學生,直接處理他們的問題。
為什麼 Redis 單線程可以如此快?
Redis 主要依賴「單線程(Single-threaded)」來處理命令,卻能達到每秒數萬甚至十萬級別的 QPS。這歸功於以下幾個核心設計:
- 完全基於記憶體操作:絕大部分的資料讀寫都是在記憶體中進行,這是 Redis 效能如此高的根本原因。記憶體的操作速度遠快於磁碟 I/O。
- 優秀的資料結構:Redis 底層設計了許多高效的資料結構(如 Hash, SkipList 等),讓各種操作的時間複雜度降到最低。
- 避免了執行緒切換開銷與競爭:單線程架構避免了多執行緒間頻繁的 Context Switch 開銷,同時也不需要處理複雜的鎖(Lock)問題,排除了死結或競態條件造成的效能損耗。
- 非阻塞 I/O 多工模型:也就是 Redis 廣泛使用的
epoll(在 Linux 環境下),讓單線程能同時處理數以萬計的並發連線。
[!IMPORTANT] Redis 的「單線程」指的是網路請求解析與資料讀寫的執行由單一主執行緒負責。在較新的版本(如 Redis 6.0 之後),已經引入了多執行緒來處理網路 I/O 的讀寫,但核心的指令執行依然是單線程的。
Redis 使用 epoll 解決了什麼問題?
如果沒有 epoll 等 I/O 多工機制,Redis 的單線程如果去讀取一個還沒有資料傳來的網路 Socket,就會被阻塞(Blocked)。一旦主執行緒被阻塞,其他所有客戶端的請求也就無法處理了。
Redis 使用 epoll 解決了以下問題:
- 解決阻塞問題:透過
epoll,Redis 可以將所有的 Socket 交給作業系統監控。主執行緒可以專心執行指令,當有 Socket 準備好可讀或可寫時,epoll會通知 Redis 進行後續處理。這樣一來,單線程就不會因為等待某個連線的資料而被卡死。 - 高效處理海量連線:面對成千上萬的連線,
epoll能夠極為快速地篩選出活躍(有資料進出)的連線,讓 Redis 可以集中 CPU 資源去處理這些真實的請求,而不是浪費時間在無效的連線輪詢上。
總結來說,epoll 作為底層的網路事件多路復用引擎,是 Redis 得以在單線程架構下依然能支撐極高併發的核心關鍵。