Redis 基於 Reactor 模式開發了本身的網絡事件處理器: 這個處理器被稱爲文件事件處理器(file event handler):node
雖然文件事件處理器以單線程方式運行, 但經過使用 I/O 多路複用程序來監聽多個套接字, 文件事件處理器既實現了高性能的網絡通訊模型, 又能夠很好地與 redis 服務器中其餘一樣以單線程方式運行的模塊進行對接, 這保持了 Redis 內部單線程設計的簡單性。redis
圖 IMAGE_CONSTRUCT_OF_FILE_EVENT_HANDLER 展現了文件事件處理器的四個組成部分, 它們分別是套接字、 I/O 多路複用程序、 文件事件分派器(dispatcher)、 以及事件處理器。api
文件事件是對套接字操做的抽象, 每當一個套接字準備好執行鏈接應答(accept)、寫入、讀取、關閉等操做時, 就會產生一個文件事件。 由於一個服務器一般會鏈接多個套接字, 因此多個文件事件有可能會併發地出現。服務器
I/O 多路複用程序負責監聽多個套接字, 並向文件事件分派器傳送那些產生了事件的套接字。網絡
儘管多個文件事件可能會併發地出現, 但 I/O 多路複用程序老是會將全部產生事件的套接字都入隊到一個隊列裏面, 而後經過這個隊列, 以有序(sequentially)、同步(synchronously)、每次一個套接字的方式向文件事件分派器傳送套接字: 當上一個套接字產生的事件被處理完畢以後(該套接字爲事件所關聯的事件處理器執行完畢), I/O 多路複用程序纔會繼續向文件事件分派器傳送下一個套接字, 如圖 IMAGE_DISPATCH_EVENT_VIA_QUEUE 。併發
文件事件分派器接收 I/O 多路複用程序傳來的套接字, 並根據套接字產生的事件的類型, 調用相應的事件處理器。socket
服務器會爲執行不一樣任務的套接字關聯不一樣的事件處理器, 這些處理器是一個個函數, 它們定義了某個事件發生時, 服務器應該執行的動做。函數
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 所示。this
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
事件。
這也就是說, 若是一個套接字又可讀又可寫的話, 那麼服務器將先讀套接字, 後寫套接字。
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 總結了上面描述的整個通信過程, 以及通信時用到的事件處理器。