前言:Redis服務(wù)器是一個(gè)事件驅(qū)動(dòng)程序跛十,服務(wù)器需要處理兩種事件:
1卑硫、文件事件:Redis服務(wù)器通過套接字和客戶端進(jìn)行鏈接觉增,而文件事件就是服務(wù)器對套接字操作的抽象奠货。服務(wù)端和客戶端之間的通信會(huì)產(chǎn)生相應(yīng)的文件事件堕担,而服務(wù)器則通過監(jiān)聽并處理這些事件來完成一系列網(wǎng)絡(luò)通信操作已慢。
2、時(shí)間事件:Redis服務(wù)器中的一些操作霹购,比如serverCron函數(shù)需要在給定的時(shí)間點(diǎn)執(zhí)行佑惠,而時(shí)間事件就是服務(wù)器對這類定時(shí)操作的抽象。
1齐疙、文件事件
? ? redis基于Reactor模式開發(fā)了自己的網(wǎng)絡(luò)事件處理器:這個(gè)處理器被稱為文件事件處理器膜楷。
? ? 1、文件事件處理器使用I/O多路復(fù)用程序來同時(shí)監(jiān)聽多個(gè)套接字贞奋,并根據(jù)套接字目前執(zhí)行的任務(wù)來為套接字關(guān)聯(lián)不同的事件處理器赌厅。
? ? 2、當(dāng)被監(jiān)聽的套接字準(zhǔn)備好執(zhí)行連接應(yīng)答(accept)轿塔、讀忍卦浮(read)仲墨、寫入(write)、關(guān)閉(close)等操作時(shí)揍障,與操作相對應(yīng)的文件事件就會(huì)產(chǎn)生目养,這時(shí)文件事件處理器就會(huì)調(diào)用套接字之前關(guān)聯(lián)好的事件處理器來處理這些事件。
? ? 雖然文件事件處理器以單線程方式進(jìn)行毒嫡,但通過使用I/O多路復(fù)用程序來監(jiān)聽多個(gè)套接字癌蚁,文件事件處理器既實(shí)現(xiàn)了高性能的網(wǎng)絡(luò)通信模型,又可以很好地與redis服務(wù)器中其他同樣以單線程模式方式運(yùn)行的模塊進(jìn)行對接兜畸,這保持了redis內(nèi)部單線程設(shè)計(jì)的簡單性努释。
1.1、文件事件處理器的構(gòu)成
? ? 文件事件處理器包括四個(gè)部分:套接字咬摇、I/O多路復(fù)用程序洽洁,文件事件分派器,事件處理器菲嘴。
? ? 文件事件是對套接字的抽象,每當(dāng)一個(gè)套接字準(zhǔn)備好執(zhí)行連接應(yīng)答(accept)汰翠、寫入龄坪、讀取、關(guān)閉等操作時(shí)复唤,就會(huì)產(chǎn)生一個(gè)文件事件健田。因?yàn)橐粋€(gè)服務(wù)器通常會(huì)連接多個(gè)套接字,所以多個(gè)文件事件有可能并發(fā)的出現(xiàn)佛纫。
? ? I/O多路復(fù)用程序負(fù)責(zé)監(jiān)聽多個(gè)套接字妓局,并向文件時(shí)間分派器傳送那些產(chǎn)生了事件的套接字。
? ? 盡管多個(gè)文件事件可能會(huì)并發(fā)的出現(xiàn)呈宇,但I/O多路復(fù)用程序總是會(huì)將所有產(chǎn)生事件的套接字都放到一個(gè)隊(duì)列里面好爬,然后通過這個(gè)隊(duì)列,?以有序(sequentially)甥啄、同步(synchronously)存炮、每次一個(gè)套接字的方式向文件事件分派器傳送套接字: 當(dāng)上一個(gè)套接字產(chǎn)生的事件被處理完畢之后(該套接字為事件所關(guān)聯(lián)的事件處理器執(zhí)行完畢), I/O 多路復(fù)用程序才會(huì)繼續(xù)向文件事件分派器傳送下一個(gè)套接字蜈漓。
? ? 文件事件分配器接受I/O多路復(fù)用程序傳來的套接字穆桂,并根據(jù)套接字產(chǎn)生的事件類型,調(diào)用相應(yīng)的事件處理器融虽。
? ? 服務(wù)器會(huì)為執(zhí)行不同任務(wù)的套接字關(guān)聯(lián)不同的事件處理器享完,這些處理器是一個(gè)個(gè)函數(shù),它們定義了某個(gè)事件發(fā)生時(shí)有额,服務(wù)器應(yīng)該執(zhí)行的動(dòng)作般又。
1.2彼绷、I/O多路復(fù)用程序的實(shí)現(xiàn)
? ??Redis 的 I/O 多路復(fù)用程序的所有功能都是通過包裝常見的?select?、?epoll?倒源、?evport?和?kqueue?這些 I/O 多路復(fù)用函數(shù)庫來實(shí)現(xiàn)的苛预, 每個(gè) I/O 多路復(fù)用函數(shù)庫在 Redis 源碼中都對應(yīng)一個(gè)單獨(dú)的文件, 比如?ae_select.c?笋熬、?ae_epoll.c?热某、?ae_kqueue.c?, 諸如此類胳螟。
? ??Redis 在 I/O 多路復(fù)用程序的實(shí)現(xiàn)源碼中用?#include?宏定義了相應(yīng)的規(guī)則昔馋, 程序會(huì)在編譯時(shí)自動(dòng)選擇系統(tǒng)中性能最高的 I/O 多路復(fù)用函數(shù)庫來作為 Redis 的 I/O 多路復(fù)用程序的底層實(shí)現(xiàn)。
1.3糖耸、事件的類型
????I/O 多路復(fù)用程序可以監(jiān)聽多個(gè)套接字的?ae.h/AE_READABLE?事件和?ae.h/AE_WRITABLE?事件秘遏, 這兩類事件和套接字操作之間的對應(yīng)關(guān)系如下:
? ? 1、當(dāng)套接字變得可讀時(shí)(客戶端對套接字執(zhí)行?write?操作嘉竟,或者執(zhí)行?close?操作)邦危, 或者有新的可應(yīng)答(acceptable)套接字出現(xiàn)時(shí)(客戶端對服務(wù)器的監(jiān)聽套接字執(zhí)行?connect?操作), 套接字產(chǎn)生?AE_READABLE?事件舍扰。
? ? 2倦蚪、當(dāng)套接字變得可寫時(shí)(客戶端對套接字執(zhí)行?read?操作), 套接字產(chǎn)生?AE_WRITABLE?事件边苹。
????I/O 多路復(fù)用程序允許服務(wù)器同時(shí)監(jiān)聽套接字的?AE_READABLE?事件和?AE_WRITABLE?事件陵且, 如果一個(gè)套接字同時(shí)產(chǎn)生了這兩種事件, 那么文件事件分派器會(huì)優(yōu)先處理?AE_READABLE?事件个束, 等到?AE_READABLE?事件處理完之后慕购, 才處理?AE_WRITABLE?事件。這也就是說茬底, 如果一個(gè)套接字又可讀又可寫的話沪悲, 那么服務(wù)器將先讀套接字, 后寫套接字阱表。
1.4可训、文件事件處理器
? ??Redis 為文件事件編寫了多個(gè)處理器, 這些事件處理器分別用于實(shí)現(xiàn)不同的網(wǎng)絡(luò)通訊需求捶枢, 比如說:
? ? 1握截、為了對連接服務(wù)器的各個(gè)客戶端進(jìn)行應(yīng)答, 服務(wù)器要為監(jiān)聽套接字關(guān)聯(lián)連接應(yīng)答處理器烂叔。
? ? 2谨胞、為了接收客戶端傳來的命令請求, 服務(wù)器要為客戶端套接字關(guān)聯(lián)命令請求處理器蒜鸡。
? ? 3胯努、為了向客戶端返回命令的執(zhí)行結(jié)果牢裳, 服務(wù)器要為客戶端套接字關(guān)聯(lián)命令回復(fù)處理器。
????在這些事件處理器里面叶沛, 服務(wù)器最常用的要數(shù)與客戶端進(jìn)行通信的連接應(yīng)答處理器蒲讯、 命令請求處理器和命令回復(fù)處理器
? ? ? ??
2、時(shí)間事件
? ? redis的時(shí)間事件可以分為兩類:
? ? 1灰署、定時(shí)事件:讓一段程序在指定的時(shí)間之后執(zhí)行一次判帮。
? ? 2、周期性事件:讓一段代碼每隔制定事件就執(zhí)行一次溉箕。
? ? 一個(gè)事件事件主要由一下三個(gè)屬性組成:
? ? 1晦墙、id:服務(wù)器為時(shí)間事件創(chuàng)建的全局唯一ID。ID從小到大遞增肴茄,新事件的ID要比舊事件的ID大晌畅。
? ? 2、when:毫秒時(shí)間戳寡痰,記錄時(shí)間事件的到達(dá)時(shí)間抗楔。
? ? 3、timeproc:時(shí)間事件處理器 拦坠,一個(gè)函數(shù)连躏。當(dāng)時(shí)間事件到達(dá)時(shí),服務(wù)器就會(huì)調(diào)用相應(yīng)的處理器來處理事件贪婉。
? ? 一個(gè)時(shí)間事件是定時(shí)事件還是周期性事件取決于時(shí)間事件處理器的返回值。
? ? 1卢肃、如果返回值是ae.h/AE_NOMORE疲迂,那么是定時(shí)事件,表示只執(zhí)行一次莫湘,執(zhí)行完就會(huì)被刪除尤蒿。
? ? 2、如果返回值不是ae.h/AE_NOMORE幅垮,那么是周期性事件:當(dāng)一個(gè)時(shí)間事件到達(dá)之后腰池,服務(wù)器會(huì)根據(jù)時(shí)間事件處理器的返回值,對時(shí)間事件的when屬性進(jìn)行更新忙芒,讓這個(gè)時(shí)間可以在一段事件后再次到達(dá)示弓。比如,返回30呵萨,那么服務(wù)器就會(huì)對這個(gè)時(shí)間時(shí)間的when更新奏属,30ms之后讓這個(gè)事件再次到達(dá)。? ? ?
? ? 目前潮峦,redis只支持了周期性事件囱皿,并為支持定時(shí)事件勇婴。
2.1、實(shí)現(xiàn)? ??
? ? 服務(wù)器將所有的時(shí)間事件都放在一個(gè)無序鏈表中嘱腥,每當(dāng)時(shí)間事件執(zhí)行器運(yùn)行時(shí)耕渴,它就遍歷整個(gè)鏈表,查找所有已到達(dá)的時(shí)間事件齿兔,并調(diào)用相應(yīng)的事件處理器橱脸。
? ? 新生成的事件ID都是較大的,他們總是會(huì)被插入到鏈表的表頭 愧驱,事件一般都是逆序排序的慰技。
? ? 注意:我們說保存時(shí)間事件的鏈表是無序鏈表,指的不是鏈表沒有按ID排序组砚,而是指吻商,該鏈表不按when排序。正因?yàn)闆]有按when排序糟红,所以當(dāng)時(shí)間事件執(zhí)行器運(yùn)行的時(shí)候艾帐,它必須遍歷鏈表中所有的時(shí)間事件,這樣才能確保服務(wù)器中所有已經(jīng)到達(dá)的時(shí)間事件都會(huì)被處理盆偿。
? ? 其實(shí)柒爸,無序鏈表不影響時(shí)間事件處理器的性能,因?yàn)檫@個(gè)鏈表中事扭,目前只有一個(gè)serverCron時(shí)間事件捎稚。
2.2、serverCron函數(shù)? ??
????持續(xù)運(yùn)行的redis服務(wù)器需要定期對自身的資源和狀態(tài)進(jìn)行檢查和調(diào)整求橄,從而確保服務(wù)器可以長期今野、穩(wěn)定的運(yùn)行下去,這些操作都由serverCron函數(shù)來負(fù)責(zé)執(zhí)行罐农。包括:
????1条霜、更新服務(wù)器的各類統(tǒng)計(jì)信息,比如時(shí)間涵亏、內(nèi)存占用宰睡、數(shù)據(jù)庫占用情況。
? ? 2气筋、清理數(shù)據(jù)庫的過期鍵(定期刪除)
? ? 3拆内、關(guān)閉和清理失敗的客戶端
? ? 4、嘗試進(jìn)行AOF或RDB持久化操作
? ? 5宠默、如果是主服務(wù)器矛纹,那么對從服務(wù)器進(jìn)行定期同步
? ? 6、如果是集群模式光稼,對集群進(jìn)行定期同步和連接測試
? ??
? ? redis2.6以前 或南,服務(wù)器默認(rèn)規(guī)定每秒執(zhí)行10次孩等,100ms一次。
? ? redis2.8以后采够,配置文件hz選項(xiàng)可配置肄方。
3、時(shí)間的調(diào)度與執(zhí)行
? ? 因?yàn)榉?wù)器同時(shí)存在文件事件和時(shí)間事件兩種 事件類型蹬癌,所以服務(wù)器必須對這兩種事件進(jìn)行調(diào)度权她,決定何時(shí)執(zhí)行時(shí)間事件,何時(shí)執(zhí)行文件事件逝薪,以及花多少事件來處理他們隅要。
1.aeApiPoll函數(shù)的最大阻塞時(shí)間是由到達(dá)時(shí)間最接近當(dāng)前時(shí)間的時(shí)間事件決定,這個(gè)方法既可以避免服務(wù)器對時(shí)間事件進(jìn)行頻繁的輪詢(忙等待)董济,也可以確保aeApiPoll函數(shù)不會(huì)阻塞過長時(shí)間步清。? ??
2.因?yàn)槲募录请S機(jī)出現(xiàn)的,如果等待并處理完一次文件事件之后虏肾,仍未有任何時(shí)間事件到達(dá)廓啊,那么服務(wù)器將再次等待并處理文件事件。隨著文件事件的不斷執(zhí)行封豪,時(shí)間會(huì)逐漸向時(shí)間事件所設(shè)置的到達(dá)時(shí)間逼近谴轮,并最終來到到達(dá)事件,這時(shí)服務(wù)器就可以開始處理到達(dá)的時(shí)間事件了吹埠。
3.對文件事件和時(shí)間事件的處理都是同步第步,有序,原子地執(zhí)行的缘琅,服務(wù)器不會(huì)中途中斷事件處理粘都,也不會(huì)對事件進(jìn)行搶占,因此胯杭,不管是文件事件的處理器驯杜,還是時(shí)間事件的處理器受啥,它們都會(huì)盡可能地減少程序的阻塞事件做个,并在有需要時(shí)主動(dòng)讓出執(zhí)行權(quán),從而降低造成事件饑餓的可能性滚局。比如說居暖,再命令回復(fù)處理器將一個(gè)命令回復(fù)寫入到客戶端套接字時(shí),如果寫入字節(jié)數(shù)超過了一個(gè)預(yù)設(shè)常量的話藤肢,命令回復(fù)處理器就會(huì)主動(dòng)用break跳出寫入循環(huán)太闺,將余下的數(shù)據(jù)留到下次再寫;另外嘁圈,時(shí)間事件也會(huì)將非常耗時(shí)的持久化操作放到子線程或者子進(jìn)程執(zhí)行省骂。
4.因?yàn)闀r(shí)間事件在文件事件之后執(zhí)行蟀淮,并且事件之間不會(huì)出現(xiàn)搶占,所以時(shí)間事件的實(shí)際處理時(shí)間钞澳,通常會(huì)比時(shí)間事件設(shè)定的到達(dá)時(shí)間稍微晚一些怠惶。