Redis 基于?[Reactor 模式](http://en.wikipedia.org/wiki/Reactor_pattern)開發了自己的網絡事件處理器: 這個處理器被稱為文件事件處理器(file event handler):
* 文件事件處理器使用?[I/O 多路復用(multiplexing)](http://en.wikipedia.org/wiki/Multiplexing)程序來同時監聽多個套接字, 并根據套接字目前執行的任務來為套接字關聯不同的事件處理器。
* 當被監聽的套接字準備好執行連接應答(accept)、讀取(read)、寫入(write)、關閉(close)等操作時, 與操作相對應的文件事件就會產生, 這時文件事件處理器就會調用套接字之前關聯好的事件處理器來處理這些事件。
雖然文件事件處理器以單線程方式運行, 但通過使用 I/O 多路復用程序來監聽多個套接字, 文件事件處理器既實現了高性能的網絡通信模型, 又可以很好地與 Redis 服務器中其他同樣以單線程方式運行的模塊進行對接, 這保持了 Redis 內部單線程設計的簡單性。
## 文件事件處理器的構成
圖 IMAGE_CONSTRUCT_OF_FILE_EVENT_HANDLER 展示了文件事件處理器的四個組成部分, 它們分別是套接字、 I/O 多路復用程序、 文件事件分派器(dispatcher)、 以及事件處理器。

文件事件是對套接字操作的抽象, 每當一個套接字準備好執行連接應答(accept)、寫入、讀取、關閉等操作時, 就會產生一個文件事件。 因為一個服務器通常會連接多個套接字, 所以多個文件事件有可能會并發地出現。
I/O 多路復用程序負責監聽多個套接字, 并向文件事件分派器傳送那些產生了事件的套接字。
盡管多個文件事件可能會并發地出現, 但 I/O 多路復用程序總是會將所有產生事件的套接字都入隊到一個隊列里面, 然后通過這個隊列, 以有序(sequentially)、同步(synchronously)、每次一個套接字的方式向文件事件分派器傳送套接字: 當上一個套接字產生的事件被處理完畢之后(該套接字為事件所關聯的事件處理器執行完畢), I/O 多路復用程序才會繼續向文件事件分派器傳送下一個套接字, 如圖 IMAGE_DISPATCH_EVENT_VIA_QUEUE 。

文件事件分派器接收 I/O 多路復用程序傳來的套接字, 并根據套接字產生的事件的類型, 調用相應的事件處理器。
服務器會為執行不同任務的套接字關聯不同的事件處理器, 這些處理器是一個個函數, 它們定義了某個事件發生時, 服務器應該執行的動作。
## I/O 多路復用程序的實現
Redis 的 I/O 多路復用程序的所有功能都是通過包裝常見的?`select`?、?`epoll`?、?`evport`?和?`kqueue`?這些 I/O 多路復用函數庫來實現的, 每個 I/O 多路復用函數庫在 Redis 源碼中都對應一個單獨的文件, 比如?`ae_select.c`?、?`ae_epoll.c`?、?`ae_kqueue.c`?, 諸如此類。
因為 Redis 為每個 I/O 多路復用函數庫都實現了相同的 API , 所以 I/O 多路復用程序的底層實現是可以互換的, 如圖 IMAGE_MULTI_LIB 所示。

Redis 在 I/O 多路復用程序的實現源碼中用?`#include`?宏定義了相應的規則, 程序會在編譯時自動選擇系統中性能最高的 I/O 多路復用函數庫來作為 Redis 的 I/O 多路復用程序的底層實現:
~~~
/* Include the best multiplexing layer supported by this system.
* The following should be ordered by performances, descending. */
#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
~~~
## 事件的類型
I/O 多路復用程序可以監聽多個套接字的?`ae.h/AE_READABLE`?事件和?`ae.h/AE_WRITABLE`?事件, 這兩類事件和套接字操作之間的對應關系如下:
* 當套接字變得可讀時(客戶端對套接字執行?`write`?操作,或者執行?`close`?操作), 或者有新的可應答(acceptable)套接字出現時(客戶端對服務器的監聽套接字執行?`connect`?操作), 套接字產生?`AE_READABLE`?事件。
* 當套接字變得可寫時(客戶端對套接字執行?`read`?操作), 套接字產生?`AE_WRITABLE`?事件。
I/O 多路復用程序允許服務器同時監聽套接字的?`AE_READABLE`?事件和?`AE_WRITABLE`?事件, 如果一個套接字同時產生了這兩種事件, 那么文件事件分派器會優先處理?`AE_READABLE`?事件, 等到?`AE_READABLE`?事件處理完之后, 才處理?`AE_WRITABLE`?事件。
這也就是說, 如果一個套接字又可讀又可寫的話, 那么服務器將先讀套接字, 后寫套接字。
## API
`ae.c/aeCreateFileEvent`?函數接受一個套接字描述符、 一個事件類型、 以及一個事件處理器作為參數, 將給定套接字的給定事件加入到 I/O 多路復用程序的監聽范圍之內, 并對事件和事件處理器進行關聯。
`ae.c/aeDeleteFileEvent`?函數接受一個套接字描述符和一個監聽事件類型作為參數, 讓 I/O 多路復用程序取消對給定套接字的給定事件的監聽, 并取消事件和事件處理器之間的關聯。
`ae.c/aeGetFileEvents`?函數接受一個套接字描述符, 返回該套接字正在被監聽的事件類型:
* 如果套接字沒有任何事件被監聽, 那么函數返回?`AE_NONE`?。
* 如果套接字的讀事件正在被監聽, 那么函數返回?`AE_READABLE`?。
* 如果套接字的寫事件正在被監聽, 那么函數返回?`AE_WRITABLE`?。
* 如果套接字的讀事件和寫事件正在被監聽, 那么函數返回?`AE_READABLE?|?AE_WRITABLE`?。
`ae.c/aeWait`?函數接受一個套接字描述符、一個事件類型和一個毫秒數為參數, 在給定的時間內阻塞并等待套接字的給定類型事件產生, 當事件成功產生, 或者等待超時之后, 函數返回。
`ae.c/aeApiPoll`?函數接受一個?`sys/time.h/struct?timeval`?結構為參數, 并在指定的時間內, 阻塞并等待所有被?`aeCreateFileEvent`?函數設置為監聽狀態的套接字產生文件事件, 當有至少一個事件產生, 或者等待超時后, 函數返回。
`ae.c/aeProcessEvents`?函數是文件事件分派器, 它先調用?`aeApiPoll`?函數來等待事件產生, 然后遍歷所有已產生的事件, 并調用相應的事件處理器來處理這些事件。
`ae.c/aeGetApiName`?函數返回 I/O 多路復用程序底層所使用的 I/O 多路復用函數庫的名稱: 返回?`"epoll"`?表示底層為?`epoll`?函數庫, 返回`"select"`?表示底層為?`select`?函數庫, 諸如此類。
## 文件事件的處理器
Redis 為文件事件編寫了多個處理器, 這些事件處理器分別用于實現不同的網絡通訊需求, 比如說:
* 為了對連接服務器的各個客戶端進行應答, 服務器要為監聽套接字關聯連接應答處理器。
* 為了接收客戶端傳來的命令請求, 服務器要為客戶端套接字關聯命令請求處理器。
* 為了向客戶端返回命令的執行結果, 服務器要為客戶端套接字關聯命令回復處理器。
* 當主服務器和從服務器進行復制操作時, 主從服務器都需要關聯特別為復制功能編寫的復制處理器。
* 等等。
在這些事件處理器里面, 服務器最常用的要數與客戶端進行通信的連接應答處理器、 命令請求處理器和命令回復處理器。
### 連接應答處理器
`networking.c/acceptTcpHandler`?函數是 Redis 的連接應答處理器, 這個處理器用于對連接服務器監聽套接字的客戶端進行應答, 具體實現為`sys/socket.h/accept`?函數的包裝。
當 Redis 服務器進行初始化的時候, 程序會將這個連接應答處理器和服務器監聽套接字的?`AE_READABLE`?事件關聯起來, 當有客戶端用`sys/socket.h/connect`?函數連接服務器監聽套接字的時候, 套接字就會產生?`AE_READABLE`?事件, 引發連接應答處理器執行, 并執行相應的套接字應答操作, 如圖 IMAGE_SERVER_ACCEPT_CONNECT 所示。

### 命令請求處理器
`networking.c/readQueryFromClient`?函數是 Redis 的命令請求處理器, 這個處理器負責從套接字中讀入客戶端發送的命令請求內容, 具體實現為?`unistd.h/read`?函數的包裝。
當一個客戶端通過連接應答處理器成功連接到服務器之后, 服務器會將客戶端套接字的?`AE_READABLE`?事件和命令請求處理器關聯起來, 當客戶端向服務器發送命令請求的時候, 套接字就會產生?`AE_READABLE`?事件, 引發命令請求處理器執行, 并執行相應的套接字讀入操作, 如圖 IMAGE_SERVER_RECIVE_COMMAND_REQUEST 所示。

在客戶端連接服務器的整個過程中, 服務器都會一直為客戶端套接字的?`AE_READABLE`?事件關聯命令請求處理器。
### 命令回復處理器
`networking.c/sendReplyToClient`?函數是 Redis 的命令回復處理器, 這個處理器負責將服務器執行命令后得到的命令回復通過套接字返回給客戶端, 具體實現為?`unistd.h/write`?函數的包裝。
當服務器有命令回復需要傳送給客戶端的時候, 服務器會將客戶端套接字的?`AE_WRITABLE`?事件和命令回復處理器關聯起來, 當客戶端準備好接收服務器傳回的命令回復時, 就會產生?`AE_WRITABLE`?事件, 引發命令回復處理器執行, 并執行相應的套接字寫入操作, 如圖 IMAGE_SERVER_SEND_REPLY 所示。

當命令回復發送完畢之后, 服務器就會解除命令回復處理器與客戶端套接字的?`AE_WRITABLE`?事件之間的關聯。
### 一次完整的客戶端與服務器連接事件示例
讓我們來追蹤一次 Redis 客戶端與服務器進行連接并發送命令的整個過程, 看看在過程中會產生什么事件, 而這些事件又是如何被處理的。
假設一個 Redis 服務器正在運作, 那么這個服務器的監聽套接字的?`AE_READABLE`?事件應該正處于監聽狀態之下, 而該事件所對應的處理器為連接應答處理器。
如果這時有一個 Redis 客戶端向服務器發起連接, 那么監聽套接字將產生?`AE_READABLE`?事件, 觸發連接應答處理器執行: 處理器會對客戶端的連接請求進行應答, 然后創建客戶端套接字, 以及客戶端狀態, 并將客戶端套接字的?`AE_READABLE`?事件與命令請求處理器進行關聯, 使得客戶端可以向主服務器發送命令請求。
之后, 假設客戶端向主服務器發送一個命令請求, 那么客戶端套接字將產生?`AE_READABLE`?事件, 引發命令請求處理器執行, 處理器讀取客戶端的命令內容, 然后傳給相關程序去執行。
執行命令將產生相應的命令回復, 為了將這些命令回復傳送回客戶端, 服務器會將客戶端套接字的?`AE_WRITABLE`?事件與命令回復處理器進行關聯: 當客戶端嘗試讀取命令回復的時候, 客戶端套接字將產生?`AE_WRITABLE`?事件, 觸發命令回復處理器執行, 當命令回復處理器將命令回復全部寫入到套接字之后, 服務器就會解除客戶端套接字的?`AE_WRITABLE`?事件與命令回復處理器之間的關聯。
圖 IMAGE_COMMAND_PROGRESS 總結了上面描述的整個通訊過程, 以及通訊時用到的事件處理器。

- 介紹
- 前言
- 致謝
- 簡介
- 第一部分:數據結構與對象
- 簡單動態字符串
- SDS 的定義
- SDS 與 C 字符串的區別
- SDS API
- 重點回顧
- 參考資料
- 鏈表
- 鏈表和鏈表節點的實現
- 鏈表和鏈表節點的 API
- 重點回顧
- 字典
- 字典的實現
- 哈希算法
- 解決鍵沖突
- rehash
- 漸進式 rehash
- 字典 API
- 重點回顧
- 跳躍表
- 跳躍表的實現
- 跳躍表 API
- 重點回顧
- 整數集合
- 整數集合的實現
- 升級
- 升級的好處
- 降級
- 整數集合 API
- 重點回顧
- 壓縮列表
- 壓縮列表的構成
- 壓縮列表節點的構成
- 連鎖更新
- 壓縮列表 API
- 重點回顧
- 對象
- 對象的類型與編碼
- 字符串對象
- 列表對象
- 哈希對象
- 集合對象
- 有序集合對象
- 類型檢查與命令多態
- 內存回收
- 對象共享
- 對象的空轉時長
- 重點回顧
- 第二部分:單機數據庫的實現
- 數據庫
- 數據庫鍵空間
- 重點回顧
- RDB 持久化
- RDB 文件結構
- 重點回顧
- AOF 持久化
- AOF 持久化的實現
- 重點回顧
- 事件
- 文件事件
- 重點回顧
- 參考資料
- 客戶端
- 客戶端屬性
- 重點回顧
- 服務器
- 命令請求的執行過程
- 重點回顧
- 第三部分:多機數據庫的實現
- 復制
- 舊版復制功能的實現
- 重點回顧
- Sentinel
- 啟動并初始化 Sentinel
- 重點回顧
- 參考資料
- 集群
- 節點
- 重點回顧
- 第四部分:獨立功能的實現
- 發布與訂閱
- 頻道的訂閱與退訂
- 重點回顧
- 參考資料
- 事務
- 事務的實現
- 重點回顧
- Lua 腳本
- 創建并修改 Lua 環境
- 重點回顧
- 排序
- SORT <key> 命令的實現
- 重點回顧
- 二進制位數組
- GETBIT 命令的實現
- 重點回顧
- 慢查詢日志
- 慢查詢記錄的保存
- 慢查詢日志的閱覽和刪除
- 添加新日志
- 重點回顧
- 監視器
- 成為監視器
- 向監視器發送命令信息
- 重點回顧
- 源碼、相關資源和勘誤