Redis 事件驅動框架(ae.c/ae.h)深度解析
之前咱們用 “超市收銀員” 的例子,簡單看懂了 ae 模塊是 Redis 的 “多任務神器”。現在咱們再往深走一層,不用復雜代碼,只拆它的 “核心運作邏輯”—— 搞懂它怎么做到 “一個收銀員(單線程)高效處理一堆顧客(多請求)”。
一、先明確:ae 模塊到底要解決什么問題?
Redis 剛誕生時,有個關鍵需求:用單線程處理成千上萬的客戶端連接。如果用 “一個連接派一個線程” 的方式,線程多了會互相搶資源(比如內存、CPU),反而變慢。
ae 模塊的作用,就是給單線程裝上 “三頭六臂”—— 讓它能同時 “監聽” 很多連接,哪個連接有請求就處理哪個,還能按時做定時任務(比如清理過期數據)。
二、ae 模塊的 “三大核心零件”
就像一臺機器有幾個關鍵零件,ae 模塊也有三個 “缺一不可” 的核心組件,咱們一個個看:
1. 零件 1:事件循環(aeEventLoop)——“工作總調度臺”
你可以把它想象成收銀員的 “工作臺”,上面放著所有要處理的任務清單。源碼里它是個結構體(簡化后):
struct aeEventLoop {int maxfd; // 目前最多有多少個“顧客呼叫鈴”(最大文件描述符)aeFileEvent \*events; // 所有“呼叫鈴”的清單(文件事件列表)aeTimeEvent \*timeEventHead; // 所有“定時提醒”的清單(時間事件鏈表)int stop; // 下班開關(1=停止,0=工作)};
關鍵作用:
-
管理所有 “待處理任務”(不管是客戶端請求,還是定時任務);
-
控制 “工作流程”:先看有沒有呼叫鈴響,再看有沒有定時提醒到點,循環往復。
2. 零件 2:文件事件(aeFileEvent)——“顧客呼叫鈴”
每個客戶端連接(比如你用命令行連 Redis),在 ae 模塊里都對應一個 “呼叫鈴”—— 這就是文件事件。
簡化結構體:
struct aeFileEvent {int mask; // 鈴的類型(讀鈴/寫鈴,比如客戶端發請求是“讀鈴”)aeFileProc \*rfileProc; // 讀鈴響了要做的事(比如接收客戶端發的命令)aeFileProc \*wfileProc; // 寫鈴響了要做的事(比如給客戶端返回結果)};
舉個例子:
當你在命令行輸入 set name zhangsan
時:
-
你的客戶端連接會觸發 “讀鈴”(mask = 讀事件);
-
ae 模塊會調用
rfileProc
函數,把你輸入的命令接收到 Redis 里。
3. 零件 3:時間事件(aeTimeEvent)——“超市定時提醒”
Redis 里需要 “到點自動做” 的事,比如 “每 10 秒清理一次過期數據”“每天凌晨 3 點做 RDB 備份”,都靠時間事件實現。
簡化結構體:
struct aeTimeEvent {long long id; // 提醒的唯一編號(避免混亂)long long when\_sec; // 到點時間(秒)long long when\_ms; // 到點時間(毫秒,更精確)aeTimeProc \*timeProc; // 到點要做的事(比如清理過期數據的函數)  struct aeTimeEvent \*next; // 下一個提醒(鏈表結構,按時間排序)};
關鍵特點:
-
所有時間事件按 “到點時間” 排序,就像你的鬧鐘按時間先后排好;
-
每次事件循環,只需要檢查 “最早到點” 的提醒(鏈表頭),不用遍歷所有,效率高。
三、ae 模塊的 “工作流程”:單線程怎么高效干活?
咱們用 “超市收銀員” 的場景,再細化一遍 ae 模塊的 “一天工作”—— 對應源碼里的 aeMain
函數(事件循環主函數):
步驟 1:開店前準備(初始化事件循環)
-
收銀員先擺好 “工作臺”(創建 aeEventLoop 實例);
-
把超市大門的 “呼叫鈴” 裝上(注冊監聽端口的文件事件,比如 Redis 默認的 6379 端口);
-
設好 “定時提醒”(比如 “每 10 秒查一次過期商品”)。
步驟 2:開始營業(進入事件循環)
收銀員坐在工作臺前,循環做以下 3 件事:
① 看 “最早的定時提醒還有多久到點”
比如現在有兩個提醒:“10 秒后清理過期商品”“1 小時后備份”,那最早的是 10 秒后。
- 這個時間決定了 “收銀員最多等多久”—— 如果 10 秒內沒人按呼叫鈴,就等 10 秒后處理定時任務;如果期間有人按鈴,就立刻處理。
② 等 “呼叫鈴響”(監聽文件事件)
收銀員調用 aeApiPoll
函數(底層是 epoll/kqueue 等系統工具),“監聽” 所有呼叫鈴:
-
如果沒人按鈴,就等第一步算好的時間(比如 10 秒);
-
如果有人按鈴(比如客戶端發請求),就立刻知道是哪個鈴響了(哪個客戶端)、是讀鈴還是寫鈴。
③ 處理 “響了的鈴” 和 “到點的提醒”
-
先處理所有 “響了的呼叫鈴”(文件事件):比如客戶 A 要結賬(讀事件),就收他的錢;客戶 B 要拿商品(寫事件),就給他拿。
-
再處理 “到點的定時提醒”(時間事件):比如 10 秒到了,就去清理過期商品。
步驟 3:關店(停止事件循環)
當 Redis 收到 “shutdown” 命令時,把 aeEventLoop
的 stop
設為 1,事件循環結束,收銀員下班。
四、ae 模塊的 “性能秘密”:為什么單線程能處理萬級連接?
關鍵靠底層的 “IO 多路復用” 技術 —— 對應源碼里的 aeApi*
系列函數(比如 Linux 下的 ae_epoll.c
)。
咱們用 “超市” 類比,解釋這個技術:
-
普通方式(沒有 IO 多路復用):一個顧客一個收銀員,1000 個顧客要 1000 個收銀員,成本高還亂;
-
IO 多路復用方式:一個收銀員守著所有顧客的 “呼叫鈴”,哪個響了處理哪個 —— 相當于 “一個人同時監聽很多連接”,不用開多線程。
Redis 會根據操作系統自動選最優的 “監聽工具”:
-
Linux 用 epoll(最常用,效率最高,支持萬級連接);
-
BSD 用 kqueue(和 epoll 類似,高效);
-
Windows 用 select(效率低,支持連接少,所以 Redis 在 Windows 上很少用)。
五、動手看源碼:怎么快速找到 ae 模塊的核心?
如果你想自己翻源碼,不用全看,找這幾個關鍵地方就行:
-
ae.c 的 aeCreateEventLoop 函數:看事件循環怎么初始化;
-
ae.c 的 aeProcessEvents 函數:看一次事件循環的完整流程(對應咱們說的步驟 2);
-
ae.c 的 aeCreateTimeEvent 函數:看定時任務怎么創建(比如 server.c 里會調用它注冊清理過期數據的任務);
-
ae_epoll.c(Linux 下):看 epoll 怎么實現 “監聽多個連接”(不用看懂所有代碼,看 aeApiAddEvent 怎么加事件、aeApiPoll 怎么等事件)。
總結
ae 模塊其實就是 Redis 的 “多任務調度中心”:
-
靠 “事件循環” 統管所有任務;
-
靠 “文件事件” 處理客戶端連接;
-
靠 “時間事件” 處理定時任務;
-
靠 “IO 多路復用” 讓單線程能高效處理萬級連接。
理解了 ae 模塊,你就懂了 Redis 單線程高性能的核心 —— 不是靠 “多線程堆資源”,而是靠 “聰明的任務調度”。
如果還想再深入,比如看 “epoll 具體怎么監聽連接”,或者 “時間事件怎么精確到毫秒”,咱們可以繼續拆!