1. 程式人生 > 實用技巧 >Redis 檔案事件

Redis 檔案事件

Redis 基於Reactor 模式開發了自己的網路事件處理器: 這個處理器被稱為檔案事件處理器(file event handler):

  • 檔案事件處理器使用I/O 多路複用(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 多路複用程式的所有功能都是通過包裝常見的selectepollevportkqueue這些 I/O 多路複用函式庫來實現的, 每個 I/O 多路複用函式庫在 Redis 原始碼中都對應一個單獨的檔案, 比如ae_select.cae_epoll.cae_kqueue.c, 諸如此類。

因為 Redis 為每個 I/O 多路複用函式庫都實現了相同的 API , 所以 I/O 多路複用程式的底層實現是可以互換的, 如圖 IMAGE_MULTI_LIB 所示。

事件的型別

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

檔案事件的處理器

Redis 為檔案事件編寫了多個處理器, 這些事件處理器分別用於實現不同的網路通訊需求, 比如說:

  • 為了對連線伺服器的各個客戶端進行應答, 伺服器要為監聽套接字關聯連線應答處理器。
  • 為了接收客戶端傳來的命令請求, 伺服器要為客戶端套接字關聯命令請求處理器。
  • 為了向客戶端返回命令的執行結果, 伺服器要為客戶端套接字關聯命令回覆處理器。
  • 當主伺服器和從伺服器進行復制操作時, 主從伺服器都需要關聯特別為複製功能編寫的複製處理器
  • 等等。

在這些事件處理器裡面, 伺服器最常用的要數與客戶端進行通訊的連線應答處理器、 命令請求處理器和命令回覆處理器。

一次完整的客戶端與伺服器連線事件示例

讓我們來追蹤一次 Redis 客戶端與伺服器進行連線併發送命令的整個過程, 看看在過程中會產生什麼事件, 而這些事件又是如何被處理的。

假設一個 Redis 伺服器正在運作, 那麼這個伺服器的監聽套接字的AE_READABLE事件應該正處於監聽狀態之下, 而該事件所對應的處理器為連線應答處理器。

如果這時有一個 Redis 客戶端向伺服器發起連線, 那麼監聽套接字將產生AE_READABLE事件, 觸發連線應答處理器執行: 處理器會對客戶端的連線請求進行應答, 然後建立客戶端套接字, 以及客戶端狀態, 並將客戶端套接字的AE_READABLE事件與命令請求處理器進行關聯, 使得客戶端可以向主伺服器傳送命令請求。

之後, 假設客戶端向主伺服器傳送一個命令請求, 那麼客戶端套接字將產生AE_READABLE事件, 引發命令請求處理器執行, 處理器讀取客戶端的命令內容, 然後傳給相關程式去執行。

執行命令將產生相應的命令回覆, 為了將這些命令回覆傳送回客戶端, 伺服器會將客戶端套接字的AE_WRITABLE事件與命令回覆處理器進行關聯: 當客戶端嘗試讀取命令回覆的時候, 客戶端套接字將產生AE_WRITABLE事件, 觸發命令回覆處理器執行, 當命令回覆處理器將命令回覆全部寫入到套接字之後, 伺服器就會解除客戶端套接字的AE_WRITABLE事件與命令回覆處理器之間的關聯。

圖 IMAGE_COMMAND_PROGRESS 總結了上面描述的整個通訊過程, 以及通訊時用到的事件處理器。