2 時間事件
時間事件記錄着那些要在指定時間點運作的事件,多個時間事件以無序連結清單結構儲存在伺服器狀态中。
無序連結清單并不影響時間事件處理器的性能。
在Redis3.0版本,正常模式下的 Redis 隻帶有 serverCron 一個時間事件, 而在 benchmark 模式下, Redis 也隻使用兩個時間事件。
在這種情況下, 程式幾乎是将無序連結清單退化成一個指針來使用, 是以使用無序連結清單來儲存時間事件, 并不影響事件處理器性能。
時間事件的資料結構

根據
timeProc
函數傳回值,将時間事件分類如下:
- 傳回 AE_NOMORE
-
Redis事件處理機制詳解(下)2 時間事件3 兩種事件的排程 - 那麼這個事件為單次執行事件。該事件會在指定時間被處理一次,之後該事件就會被删除
傳回一個非 AE_NOMORE 的整數值,則為循環執行事件。該事件會在指定時間被處理,之後它會按照timeProc的傳回值,更新事件的 when 屬性,讓這個事件在之後某時間點再運作,以這種方式一直更新運作。
僞代碼表示的兩種事件處理:
def handle_time_event(server, time_event):
# 執行事件處理器,并擷取傳回值
retval = time_event.timeProc()
if retval == AE_NOMORE:
# 如果傳回 AE_NOMORE ,那麼将事件從連結清單中删除,不再執行
server.time_event_linked_list.delete(time_event)
else:
# 否則,更新事件的 when 屬性
# 讓它在目前時間之後的 retval 毫秒之後再次運作
time_event.when = unix_ts_in_ms() + retval
當時間事件處理器被執行時, 它周遊連結清單中所有的時間事件, 檢查它們的
when
屬性,并執行已到達事件:
def process_time_event(server):
# 周遊時間事件連結清單
for time_event in server.time_event_linked_list:
# 檢查事件是否已經到達
if time_event.when <= unix_ts_in_ms():
# 處理已到達事件
handle_time_event(server, time_event)
時間事件執行個體
伺服器需要定期對自身的資源和狀态進行檢查、整理, 保證伺服器維持在一個健康穩定狀态, 這類操作被統稱為正常操作(cron job)。
在 Redis 中, 正常操作由
redis.c/serverCron
實作, 包括如下操作:
更新伺服器的各類統計資訊,比如時間、記憶體占用、資料庫占用情況等
清理資料庫中的過期鍵值對
對不合理的資料庫進行大小調整
關閉和清理連接配接失效的用戶端
嘗試進行 AOF 或 RDB 持久化操作
如果伺服器是主節點的話,對附屬節點進行定期同步
如果處于叢集模式的話,對叢集進行定期同步和連接配接測試
Redis 将 serverCron(後文簡稱為sC) 作為時間事件運作, 確定它能夠定期自動運作一次,又因 sC 需要在 Redis 伺服器運作期一直定期運作, 是以它是一個循環時間事件:sC 會一直定期執行,直至伺服器關閉。
Redis 2.6 的 sC 每秒運作 10 次,即平均每 100 ms運作一次。
Redis 2.8 使用者可以通過修改 hz 選項設定 sC 的每秒執行次數。
3 兩種事件的排程
簡單地說, Redis 裡面的兩種事件呈協作關系, 它們之間包含如下屬性:
- 一種事件會等待另一種事件執行完後,才開始執行,事件之間不會出現搶占
- 事件處理器先處理檔案事件(即處理指令請求),再執行時間事件(調用 sC)
檔案事件的等待時間(類 poll 函數的最大阻塞時間),由距離到達時間最短的時間事件決定
這表明, 實際處理時間事件的時間, 通常會比事件所預定的時間要晚, 延遲時間取決于時間事件執行前, 執行完成檔案事件所耗時間。
示例
正常案例
雖然時間事件 Time Event Y 可設定其
when
屬性計劃在
t1
時間執行, 但因為檔案事件 File Event X 正在運作, 是以 Time Event Y 的執行被延遲。
sC 案例
而且對于 sC 這類循環執行的時間事件來說,如果事件處理器的傳回值是 t ,那麼 Redis 隻保證:
如果兩次執行時間事件處理器之間的時間間隔≥t ,則該時間事件至少會被處理一次
而非,每隔 t 時間,就一定要執行一次事件
這對于不使用搶占排程的 Redis 事件處理器而言,也不可能做到
比如,雖然 sC 設定的間隔為 10 ms,但它并非是如下那樣每隔 10 ms就運作一次:
實際的 sC 運作方式更可能如下:
根據情況,如果處理檔案事件耗費了非常多的時間,sC 被推遲到一兩秒之後才能執行,也有可能。
整個事件處理器程式可以用以下僞代碼描述:
def process_event():
# 擷取執行時間最接近現在的一個時間事件
te = get_nearest_time_event(server.time_event_linked_list)
# 檢查該事件的執行時間和現在時間之差
# 如果值 <= 0 ,說明至少有一個時間事件已到達
# 如果值 > 0 ,說明目前沒有任何時間事件到達
nearest_te_remaind_ms = te.when - now_in_ms()
if nearest_te_remaind_ms <= 0:
# 若有時間事件已達,則調用不阻塞的檔案事件等待函數
poll(timeout=None)
else:
# 若時間事件還沒到達,則阻塞的最大時間不超過 te 的到達時間
poll(timeout=nearest_te_remaind_ms)
# 優先處理已就緒的檔案事件
process_file_events()
# 再處理已到達的時間事件
process_time_event()
可以看出:
- 到達時間最近的時間事件,決定了 poll 的最大阻塞時長
- 檔案事件優先于時間事件處理
将這個事件處理函數置于一個循環中,加上初始化和清理函數,這就構成了 Redis 伺服器的主
函數調用:
def redis_main():
# 初始化伺服器
init_server()
# 一直處理事件,直到伺服器關閉為止
while server_is_not_shutdown():
process_event()
# 清理伺服器
clean_server()