推薦學習:
Redis在6.0以前是單執行緒的,在6.0之後可以通過組態檔開啟多執行緒,6.0之後的多執行緒是指在io方面使用多執行緒來執行以加快I/O的速度。
檔案事件處理器是由四部分組成的分別是通訊端、I/O多路複用程式、檔案事件分派器、事件處理器。
I/O多路複用程式負責監聽多個通訊端,並向檔案事件分派器傳送那些產生了事件的通訊端。儘管多個檔案事件可能會並行的出現,單I/O多路複用程式總是會將所有產的事件的通訊端都放到一個佇列裡面,然後通過這個佇列,以有序的、同步的、每次一個通訊端的方式向檔案事件分派傳送通訊端。當上一個通訊端產生的事件被處理完畢之後(該通訊端所關聯的事件處理執行完畢),I/O多路複用程式才會繼續向檔案事件分派器傳送下一個通訊端,檔案事件分派器接收I/O多路複用程式傳來的通訊端,並根據通訊端產生的事件的型別條用相應的事件處理器,伺服器會執行不同人物的通訊端關聯不同的事件處理器,這些處理器定義了某個事件發生時伺服器該執行的動作。
Redis的多路複用程式的所有功能都是通過包裝select、epoll、evport、kqueue這些I/O多路複用函數庫來實現的
AE_READABLE事件
當通訊端變得可讀時(使用者端執行write或者close操作)或者有新的可應答的通訊端出現時通訊端將會產生AE_READABLE事件。
AE_WAITABLE事件
當通訊端變得可寫時(使用者端執行read操作)將產生AE_WAITABLE事件
I/O多路複用程式會同時監聽AE_READABLE事件和AE_WAITABLE事件,如果一個通訊端同時產生了這兩種事件,那麼事件分派器會優先處理AE_READABLE事件,也即是說伺服器將先讀通訊端後寫通訊端。
首先Redis使用者端向伺服器發起連線,那麼監聽通訊端將產生AE_READABEL事件,觸發連線應答處理器執行,處理器會對使用者端的連線請求進行應答,然後建立使用者端通訊端,以及使用者端狀態,並將使用者端通訊端的AE_READABEL事件與命令請求處理器進行關聯,使得使用者端可以向主伺服器傳送命令請求。
之後假設使用者端向主伺服器傳送一個命令請求,那麼使用者端通訊端將產生AE_READABEL事件,引發命令請求處理器執行,處理器讀取使用者端的命令,然後傳給相關聯的程式去執行。
執行命令將產生相應的命令回覆,為了將這行命令回覆傳送回給使用者端,伺服器會將AE_WAITABLE事件和命令回覆處理器進行關聯。當用戶端嘗試讀取命令回覆的時候使用者端會產生AE_WAITABLE事件,觸發命令回覆處理器執行,當命令回覆處理器將命令回覆全服寫入通訊端之後,伺服器就會解除使用者端通訊端的AE_WAITABLE事件與命令回覆處理器執行的關聯。
一個時間事件是定時事件還是週期性事件取決於時間事件處理器的返回值,如果事件處理器返回ae.h/AE_NOMORE,那麼這個事件為定時事件,該事件在到達一次後就會被刪除,之後不再到達。如果事件處理器返回一個非AE_NOMORE的整數值,那麼這個事件為週期性事件,當一個時間事件達到後,伺服器會根據事件處理器的返回值,對事件的when屬性進行更新,讓這個事件在一段時間後再次到達,並以這種方式一直更新並執行下去。
伺服器將所有時間事件都放在一個無序連結串列中(無序的並不是指id欄位,而是when欄位所以每次執行都要遍歷完真個連結串列。),每當時間事件執行器執行時,它就會遍歷整個連結串列,查詢到所有已經到達的事件,並呼叫相應的事件處理器。
這裡需要說明的是雖然是無序連結串列但是由於連結串列的長度不會很長正常模式下Redis伺服器只使用serverCron一個時間事件所以這個地方機會退化成了指標的作用,而benchmark模式下,伺服器也只使用兩個時間事件,所以全遍歷對效能影響可以忽略。
持續執行的Redis伺服器需要定期對自身的資源和狀態進行檢查和調整,從而確保伺服器可以長期、穩定地執行,這些定期操作由redis.c/serverCron函數負責執行,它的主要工作包括:
因為伺服器中同時存在檔案事件和時間事件兩種事件型別,所以伺服器必須對這兩種事件進行排程,決定何時應該處理檔案事件,何時又應該處理時間事件,以及花多少時間來處理它們等等。
處理過程的虛擬碼如下:
def aeProcessEvents(): # 獲取到達時間離當前最近的時間事件 tem_event = aeSearchNearestTimer() # 計算上一步獲得到的事件 距離到達還有多少秒 remaind_ms = time_event.when - unix_ts_now() # 如果事件已經到達, 那麼remaind_ms的值可能為負數,設定為0 remaind_ms = max(remaind_ms, 0) # 阻塞並等待檔案事件產生,最大阻塞時間由timeval結構決定, # 如果remaind_ms的值為0,那麼aeAPiPoll呼叫之後馬上返回,不阻塞 aeApiPoll(timeval) # 處理所有已經產生的檔案事件 processFileEvents() # 處理所有已經到達的時間事件 proccessTimeEvents()
事件的排程和執行規則:
1)aeApiPoll函數的最大阻塞時間由到達時間最接近當前時間的時間事件決定,這個方法既可以避免伺服器對時間事件進行頻繁的輪詢(忙等待),也可以確保aeApiPoll函數不會阻塞過長時間。
2)因為檔案事件是隨機出現的,如果等待並處理完一次檔案事件之後,仍未有任何時間事件到達,那麼伺服器將再次等待並處理檔案事件。隨著檔案事件的不斷執行,時間會逐漸向時間事件所設定的到達時間逼近,並最終來到到達時間,這時伺服器就可以開始處理到達的時間事件了。
3)對檔案事件和時間事件的處理都是同步、有序、原子地執行的,伺服器不會中途中斷事件處理,也不會對事件進行搶佔,因此,不管是檔案事件的處理器,還是時間事件的處理器,它們都會盡可地減少程式的阻塞時間,並在有需要時主動讓出執行權,從而降低造成事件飢餓的可能性。比如說,在命令回覆處理器將一個命令回覆寫人到使用者端通訊端時,如果寫人位元組數超過了一個預設常數的話,命令回覆處理器就會主動用break跳出寫入迴圈,將餘下的資料留到下次再寫;另外,時間事件也會將非常耗時的持久化操作放到子執行緒或者子程序執行。
4)因為時間事件在檔案事件之後執行,並且事件之間不會出現搶佔,所以時間事件的實際處理時間,通常會比時間事件設定的到達時間稍晚一些。
檔案事件和時間事件之間是合作關係,伺服器會輪流處理這兩種事件,並且處理事件的過程中也不會進行搶佔。時間事件的實際處理時間通常會比設定的到達時間晚一些。
推薦學習:
以上就是一起聊聊redis檔案事件和時間事件的詳細內容,更多請關注TW511.COM其它相關文章!