事件
事件是 Redis 服務(wù)器的核心, 它處理兩項(xiàng)重要的任務(wù):
文件事件(file event):在多個(gè)客戶端中實(shí)現(xiàn)多路復(fù)用,接受它們發(fā)來的命令請(qǐng)求,并將命令的執(zhí)行結(jié)果>返回給客戶端。
時(shí)間事件(time event):實(shí)現(xiàn)服務(wù)器常規(guī)操作(server cron job)研铆。
文件事件
Redis 服務(wù)器通過在多個(gè)客戶端之間進(jìn)行多路復(fù)用, 從而實(shí)現(xiàn)高效的命令請(qǐng)求處理: 多個(gè)客戶端通過套接字連接到 Redis 服務(wù)器中州叠, 但只有在套接字可以無阻塞地進(jìn)行讀或者寫時(shí)棵红, 服務(wù)器才會(huì)和這些客戶端進(jìn)行交互。
Redis 將這類因?yàn)閷?duì)套接字進(jìn)行多路復(fù)用而產(chǎn)生的事件稱為文件事件(file event)咧栗, 文件事件可以分為讀事件和寫事件兩類逆甜。
多路復(fù)用產(chǎn)生的套接字都放到一個(gè)隊(duì)列中。以有序致板,同步方式向文件事件分派器發(fā)送套接字
讀事件
讀事件標(biāo)志著客戶端命令請(qǐng)求的發(fā)送狀態(tài)交煞。
當(dāng)一個(gè)新的客戶端連接到服務(wù)器時(shí), 服務(wù)器會(huì)給為該客戶端綁定讀事件斟或, 直到客戶端斷開連接之后素征, 這個(gè)讀事件才會(huì)被移除。
讀事件在整個(gè)網(wǎng)絡(luò)連接的生命期內(nèi)萝挤, 都會(huì)在等待和就緒兩種狀態(tài)之間切換:
當(dāng)客戶端只是連接到服務(wù)器御毅,但并沒有向服務(wù)器發(fā)送命令時(shí),該客戶端的讀事件就處于等待狀態(tài)怜珍。
當(dāng)客戶端給服務(wù)器發(fā)送命令請(qǐng)求端蛆,并且請(qǐng)求已到達(dá)時(shí)(相應(yīng)的套接字可以無阻塞地執(zhí)行讀操作),該客戶端的讀事件處于就緒狀態(tài)绘面。
當(dāng)事件處理器被執(zhí)行時(shí)欺税, 就緒的文件事件會(huì)被識(shí)別到侈沪, 相應(yīng)的命令請(qǐng)求會(huì)被發(fā)送到命令執(zhí)行器揭璃, 并對(duì)命令進(jìn)行求值。
寫事件
寫事件標(biāo)志著客戶端對(duì)命令結(jié)果的接收狀態(tài)亭罪。
和客戶端自始至終都關(guān)聯(lián)著讀事件不同瘦馍, 服務(wù)器只會(huì)在有命令結(jié)果要傳回給客戶端時(shí), 才會(huì)為客戶端關(guān)聯(lián)寫事件应役, 并且在命令結(jié)果傳送完畢之后情组, 客戶端和寫事件的關(guān)聯(lián)就會(huì)被移除。
一個(gè)寫事件會(huì)在兩種狀態(tài)之間切換:
當(dāng)服務(wù)器有命令結(jié)果需要返回給客戶端箩祥,但客戶端還未能執(zhí)行無阻塞寫院崇,那么寫事件處于等待狀態(tài)。
當(dāng)服務(wù)器有命令結(jié)果需要返回給客戶端袍祖,并且客戶端可以進(jìn)行無阻塞寫底瓣,那么寫事件處于就緒狀態(tài)。
當(dāng)客戶端向服務(wù)器發(fā)送命令請(qǐng)求蕉陋, 并且請(qǐng)求被接受并執(zhí)行之后捐凭, 服務(wù)器就需要將保存在緩存內(nèi)的命令執(zhí)行結(jié)果返回給客戶端拨扶, 這時(shí)服務(wù)器就會(huì)為客戶端關(guān)聯(lián)寫事件。
同時(shí)關(guān)聯(lián)寫事件和讀事件
前面提到過茁肠,讀事件只有在客戶端斷開和服務(wù)器的連接時(shí)患民,才會(huì)被移除。
這也就是說垦梆,當(dāng)客戶端關(guān)聯(lián)寫事件的時(shí)候匹颤,實(shí)際上它在同時(shí)關(guān)聯(lián)讀/寫兩種事件。
因?yàn)樵谕淮挝募录幚砥鞯恼{(diào)用中托猩, 單個(gè)客戶端只能執(zhí)行其中一種事件(要么讀惋嚎,要么寫,但不能又讀又寫)站刑, 當(dāng)出現(xiàn)讀事件和寫事件同時(shí)就緒的情況時(shí)另伍, 事件處理器優(yōu)先處理讀事件。這也就是說绞旅, 當(dāng)服務(wù)器有命令結(jié)果要返回客戶端摆尝, 而客戶端又有新命令請(qǐng)求進(jìn)入時(shí), 服務(wù)器先處理新命令請(qǐng)求因悲。
時(shí)間事件
時(shí)間事件記錄著那些要在指定時(shí)間點(diǎn)運(yùn)行的事件堕汞, 多個(gè)時(shí)間事件以無序鏈表的形式保存在服務(wù)器狀態(tài)中。
redis時(shí)間事件分為2類:
定時(shí)事件:在指定時(shí)間之后晃琳,執(zhí)行一次讯检。
周期性事件:每隔一段時(shí)間就執(zhí)行一次。
每個(gè)時(shí)間事件主要由以下幾個(gè)屬性組成:
id:全局唯一ID卫旱,新事件ID號(hào)比舊事件ID號(hào)大人灼。
when :以毫秒格式的 UNIX 時(shí)間戳為單位,記錄了應(yīng)該在什么時(shí)間點(diǎn)執(zhí)行事件處理函數(shù)顾翼。
timeProc :事件處理函數(shù)投放。
next 指向下一個(gè)時(shí)間事件,形成鏈表适贸。
根據(jù) timeProc 函數(shù)的返回值灸芳,可以將時(shí)間事件劃分為兩類:
如果事件處理函數(shù)返回 ae.h/AE_NOMORE ,那么這個(gè)事件為單次執(zhí)行事件:該事件會(huì)在指定的時(shí)間被處理一次拜姿,之后該事件就會(huì)被刪除烙样,不再執(zhí)行。
如果事件處理函數(shù)返回一個(gè)非 AE_NOMORE 的整數(shù)值蕊肥,那么這個(gè)事件為循環(huán)執(zhí)行事件:該事件會(huì)在指定的時(shí)間被處理谒获,之后它會(huì)按照事件處理函數(shù)的返回值,更新事件的 when 屬性,讓這個(gè)事件在之后的某個(gè)時(shí)間點(diǎn)再次運(yùn)行究反,并以這種方式一直更新并運(yùn)行下去寻定。
實(shí)現(xiàn)
服務(wù)器將所有時(shí)間事件都放在一個(gè)無序列表中,每當(dāng)時(shí)間事件執(zhí)行器運(yùn)行時(shí)精耐,就遍歷列表狼速,查找所有已到達(dá)時(shí)間事件,并調(diào)用相應(yīng)事件處理器卦停。無序列表不是指不按ID排序向胡,是指不按when排序。
無序鏈表并不影響時(shí)間事件處理器的性能
在目前的版本中惊完, 正常模式下的 Redis 只帶有 serverCron 一個(gè)時(shí)間事件僵芹, 而在 benchmark 模式下, Redis 也只使用兩個(gè)時(shí)間事件小槐。
在這種情況下拇派, 程序幾乎是將無序鏈表退化成一個(gè)指針來使用, 所以使用無序鏈表來保存時(shí)間事件凿跳, 并不影響事件處理器的性能件豌。
服務(wù)器常規(guī)操作
對(duì)于持續(xù)運(yùn)行的服務(wù)器來說, 服務(wù)器需要定期對(duì)自身的資源和狀態(tài)進(jìn)行必要的檢查和整理控嗜, 從而讓服務(wù)器維持在一個(gè)健康穩(wěn)定的狀態(tài)茧彤, 這類操作被統(tǒng)稱為常規(guī)操作(cron job)。
在 Redis 中疆栏, 常規(guī)操作由 redis.c/serverCron 實(shí)現(xiàn)曾掂, 它主要執(zhí)行以下操作:
更新服務(wù)器的各類統(tǒng)計(jì)信息,比如時(shí)間壁顶、內(nèi)存占用珠洗、數(shù)據(jù)庫占用情況等。
清理數(shù)據(jù)庫中的過期鍵值對(duì)博助。
對(duì)不合理的數(shù)據(jù)庫進(jìn)行大小調(diào)整险污。
關(guān)閉和清理連接失效的客戶端。
嘗試進(jìn)行 AOF 或 RDB 持久化操作富岳。
如果服務(wù)器是主節(jié)點(diǎn)的話,對(duì)附屬節(jié)點(diǎn)進(jìn)行定期同步拯腮。
如果處于集群模式的話窖式,對(duì)集群進(jìn)行定期同步和連接測(cè)試。
Redis 將 serverCron 作為時(shí)間事件來運(yùn)行动壤, 從而確保它每隔一段時(shí)間就會(huì)自動(dòng)運(yùn)行一次萝喘, 又因?yàn)?serverCron 需要在 Redis 服務(wù)器運(yùn)行期間一直定期運(yùn)行, 所以它是一個(gè)循環(huán)時(shí)間事件: serverCron 會(huì)一直定期執(zhí)行,直到服務(wù)器關(guān)閉為止阁簸。
在 Redis 2.6 版本中爬早, 程序規(guī)定 serverCron 每秒運(yùn)行 10 次, 平均每 100 毫秒運(yùn)行一次启妹。 從 Redis 2.8 開始筛严, 用戶可以通過修改 hz 選項(xiàng)來調(diào)整 serverCron 的每秒執(zhí)行次數(shù), 具體信息請(qǐng)參考 redis.conf 文件中關(guān)于 hz 選項(xiàng)的說明饶米。
事件的執(zhí)行與調(diào)度
既然 Redis 里面既有文件事件桨啃, 又有時(shí)間事件, 那么如何調(diào)度這兩種事件就成了一個(gè)關(guān)鍵問題檬输。
簡單地說照瘾, Redis 里面的兩種事件呈合作關(guān)系, 它們之間包含以下三種屬性:
一種事件會(huì)等待另一種事件執(zhí)行完畢之后丧慈,才開始執(zhí)行析命,事件之間不會(huì)出現(xiàn)搶占。
事件處理器先處理文件事件(處理命令請(qǐng)求)逃默,再執(zhí)行時(shí)間事件(調(diào)用 serverCron)
文件事件的等待時(shí)間(類 poll 函數(shù)的最大阻塞時(shí)間)碳却,由距離到達(dá)時(shí)間最短的時(shí)間事件決定。
這些屬性表明笑旺, 實(shí)際處理時(shí)間事件的時(shí)間昼浦, 通常會(huì)比時(shí)間事件所預(yù)定的時(shí)間要晚, 至于延遲的時(shí)間有多長筒主, 取決于時(shí)間事件執(zhí)行之前关噪, 執(zhí)行文件事件所消耗的時(shí)間。