Redis事件
Redis事件
Redis的ae(Redis用的事件模型庫) ae.c
Redis服務器是一個事件驅動程序,服務器需要處理以下兩類事件:
- 文件事件(file event):Redis服務器通過套接字與客戶端(或者其他Redis服務器)進行連接,而文件事件就是服務器對套接字操作的抽象。
- 時間事件(time event):Redis服務器中的一些操作(比如serverCron函數)需要在給定的時間點執行,而時間事件就是服務器對這類定時操作的抽象。
一、文件事件
Redis基於Reactor模式(將消息放到了一個隊列中,通過異步線程池對其進行消費)開發了自己的網絡事件處理器,被稱為文件事件處理器(file event handler):
文件事件處理器使用I/O多路復用(multiplexing)程序來同時監聽多個套接字,並根據套接字目前執行的任務來為套接字關聯不同的事件處理器。
當被監聽的套接字準備好執行連接應答(accept)、讀取(read)、寫入(write)、關閉(close)等操作時,與操作相對應的文件事件就會產生,這時文件事件處理器就會調用套接字之前關聯好的事件處理器來處理這些事件。
雖然文件事件處理器以單線程方式運行,但通過使用I/O多路復用程序來監聽多個套接字,文件事件處理器既實現了高性能的網絡通信模型,又可以很好地與Redis服務器中其他同樣以單線程方式運行地模塊進行對接,這保持了Redis內部單線程設計地簡單性。
底層實現ae_epoll.c、ae_select.c、ae_kqueue.c、ae_evport.c多路復用程序
文件事件是對套接字操作的抽象,每當一個套接字準備好執行連接應答(accept),寫入,讀取,關閉等操作時,就會產生一個文件事件.因為一個服務器通常會連接多個套接字,所以多個文件事件有可能會並發地出現.
I/O多路復用程序負責監聽多個套接字,並向文件事件分派器傳送產生了事件地套接字。
盡管多個文件事件可能會並發地出現,但I/O多路復用程序總是會將所有產生事件地套接字都放到一個隊列裏面,然後通過這個隊列,以有序(sequentially)同步(synchronously),每次一個套接字的方式向文件事件分派器傳送套接字。當上一個套接字產生的事件被處理完畢之後
Redis的I/O多路復用程序的功能都是通過包裝常見的select,epoll,evport和kqueue這些I/O多路復用函數庫來實現的。
I/O多路復用程序可以監聽多個套接字的ae.h/AE_READABLE事件和ae.h/AE_WRITEABLE事件,這兩類事件和套接字操作之間的對應關系如下:
- 當套接字變得可讀時(客戶端對套接字執行write操作,或者執行close操作),或者有新的可應答(acceptable)套接字出現時(客戶端對服務器的監聽套接字執行connect操作),套接字產生AE_READABLE事件。
- 當套接字變得可寫時(客戶端對套接字執行read操作),套接字產生AE_WRITEABLE事件。
- 如果一個套接字又可讀又可寫,那麽服務器優先讀套接字,後寫套接字。
二、時間事件
serverCron是redis裏主要的定時處理函數,在initServer中通過調用aeCreateTimeEvent,將serverCron做為callback註冊到全局的eventLoop結構當中。
Redis服務器中的serverCron函數默認每個100毫秒執行一次,這個函數負責管理服務器的資源,並保持服務器自身的良好運轉。
主要工作:
- 更新服務器時間緩存
Redis服務器中有不少功能是需要獲取系統的當前時間,而每次獲取系統的當前時間都需要執行一次系統調用,為了減少系統的執行次數,服務器狀態中的unixtime屬性和mstime屬性被用作當前時間的緩存
struct redisServer { // ... // 保存了秒級精度的系統當前UNIX時間戳 time_t unixtime; // 保存了毫秒級精度的系統當前UNIX時間戳 long long mstime; // ... };
服務器只會在打印日誌、更新服務器的LRU時鐘、決定是否執行持久化任務、計算服務器線上時間(uptime)這類對時間精確度要求不高的功能上"使用unixtime屬性和mstime屬性"。
為鍵設置過期時間、添加慢查詢日誌這種需要高精確度時間的功能來說,服務器還是會再次執行系統調用,從而獲得最精確的系統當前時間。
- 更新LRU時鐘
lru記錄的是服務器最後一次被訪問的時間,是用於服務器的計算空轉時長,用屬性lruclock進行存儲。默認情況下,每10秒更新一次。另外,每個redis對象也存了一個lru,保存的是該對象最後一次被訪問的時間。當要計算redis對象的空轉時間,則會用服務器的lru減去redis對象的lru,獲得的結果即對象的空轉時長。
在redis客戶端,用命令OBJECT IDLETIME <key>,可以查看該key的空轉時長,返回結果是以秒為單位。由於redis每10秒更新一次服務器的最後訪問時間,因此不是很精確。lruclock時鐘的當前值可以通過INFO server命令的lur_clock 域查看。
- 更新服務器每秒執行命令次數
這個不是通過掃描全部的鍵,而是采用抽樣的方式確定的結果。每100毫秒1次,隨機抽取一些鍵,查看最近1秒是否有操作,來確定最近1秒的操作次數。接著,會將這個值,與上一次的結果,取平均值,作為本次計算的每秒執行命令數。在存入結構體中,供下次取平均值使用。
- 更新服務器內存峰值記錄
redis服務器中,用stat_peak_memory記錄服務器內存峰值。每次執行serverCron函數,會查看當前內存使用量,並且與stat_peak_memory比較,如果超過這個值,就更新這個屬性。
- 處理SIGTERM信號
redis服務器,用屬性shutdown_asap記錄當前的結果,0是不用進行操作,1的話是要求服務器盡快關閉。因此,服務器關閉命令shutdown執行,並不會立即關閉服務器,而是將服務器的shutdown_asap屬性置成1,當下一次serverCron讀取時,就會拒絕新的請求,完成當前正在執行的命令後,開始持久化相關的操作,結束持久化後才會關閉服務器。
- 管理客戶端資源
主要是會檢查客戶端的兩個內容:
客戶端很長時間沒有和服務器響應,服務器認為該客戶端超時,則會斷開和該客戶端的連接。
當客戶端在上一次執行命令請求後,輸入緩沖區超過一定的長度,程序會釋放輸入緩沖區,並創建一個默認大小的緩沖區,防止緩沖區過分消耗。
- 管理數據庫資源
主要是檢查鍵是否過期,並且按照配置的策略,刪除過期的鍵。如懶惰刪除、定期刪除等。
- 執行被延遲的BGREWRITEAOF
redis用屬性aof_rewrite_scheduled記錄是否有延遲的bgrewriteaof命令。當執行bgsave命令期間,如果接收到bgrewriteaof命令,不會立即執行該命令,而是會將屬性aof_rewrite_scheduled置成1。每次執行serverCron函數執行時,發現屬性aof_rewrite_scheduled是1,會檢查當前是否在執行bgsave命令或bgrewriteaof命令,如果沒有在執行這兩個命令,則會執行bgrewriteaof命令。
- 檢查持久化操作的運行狀態
redis服務器分別用rdb_child_pid和aof_child_pid屬性,記錄rdb和aof的子進程號(即子進程pid),如果沒有在執行相應的持久化,則值是-1。
- 將AOF緩沖區中的內容寫入AOF文件
如果服務器開啟了AOF持久化功能,並且AOF緩沖區裏面還有待寫入的數據,那麽serverCron函數會調用相應的程序,將AOF緩沖區的內容寫入到AOF文件裏面。
- 關閉異步客戶端
服務器會關閉那些輸出緩沖區大小超出限制的客戶端。
- 增加cronloops計數器的值
redis用屬性cronloops保存serverCron函數執行的次數。當執行一次serverCron,則會將屬性值加1。這個值目前的作用,是在主從復制情況下,會有一個條件是,每執行n次serverCron,則執行一次指定代碼。
Redis事件