Redis系列(六):你說(shuō)要看Redis線程模型?安排
?作者:z小趙★?一枚用心堅(jiān)持寫(xiě)原創(chuàng)的“無(wú)趣”程序猿,在自身受益的同時(shí)也讓朋友們?cè)诩夹g(shù)上有所提升。
最近有朋友說(shuō),能不能寫(xiě)一篇關(guān)于 Redis 線程模型的文章,面試被問(wèn)到不會(huì)導(dǎo)致比較尷尬;今天就來(lái)給安排上。
Redis 事件是什么?
不是講線程模型嗎?和事件有什么關(guān)系?實(shí)際上 Redis 是一個(gè)事件驅(qū)動(dòng)程序;大白話理解一下:就是通過(guò)事件的方式來(lái)運(yùn)行 Redis 的。比如客戶端向服務(wù)端發(fā)起一個(gè) get 請(qǐng)求,在做好了建連、發(fā)送請(qǐng)求、響應(yīng)請(qǐng)求、關(guān)閉連接等準(zhǔn)備操作后,就觸發(fā)了一個(gè)事件。所以在了解線程模型之前,先來(lái)看看事件。
Redis 的事件分為兩種,分別是文件事件和時(shí)間事件兩種。
什么是文件事件?
文件事件是 socket 的一個(gè)抽象;客戶端發(fā)送請(qǐng)求到服務(wù)端,會(huì)先建立連接,然后通過(guò)連接發(fā)送命令請(qǐng)求,其中每個(gè)連接就是一個(gè) socket。對(duì)于一個(gè) Redis 服務(wù)端,在同一時(shí)刻會(huì)有很多 socket 連接,每一個(gè) socket 都可以理解成一個(gè)文件事件。
每產(chǎn)生一個(gè)文件事件后,就將其交給文件事件處理器去處理,文件事件處理器是由 I/O 多路復(fù)用處理器、文件事件分發(fā)器、事件處理器幾部分組成。
服務(wù)端在接收到請(qǐng)求后,是怎么工作的呢?

從上圖可以看出,當(dāng)客戶端發(fā)送請(qǐng)求到服務(wù)端后;
首先服務(wù)端通過(guò) I/O 多路復(fù)用處理器接收文件事件(即一個(gè)一個(gè)的 socket),并將接收到的文件事件插入到事件隊(duì)列中。 接收文件事件分發(fā)器將接收到的時(shí)間按照事件類型分發(fā)給不同的事件處理器去執(zhí)行;比如客戶端發(fā)送了一個(gè) get 請(qǐng)求,文件事件處理器就會(huì)將該請(qǐng)求發(fā)送給讀取事件處理器去執(zhí)行。 相應(yīng)的事件處理器接收到請(qǐng)求后,就去執(zhí)行相應(yīng)的操作,然后將相應(yīng)的結(jié)果返回。 當(dāng)一個(gè)事件處理完畢并返回相應(yīng)的結(jié)果后,文件事件分發(fā)器繼續(xù)處理事件隊(duì)列中的下一個(gè)請(qǐng)求,重復(fù)上述的動(dòng)作,直到?jīng)]有文件事件可以被處理。這個(gè)流程其實(shí)有點(diǎn)像生產(chǎn)者消費(fèi)者的樣子。
I/O 對(duì)路復(fù)用處理器在 Redis 中用很多種實(shí)現(xiàn),比如 epool、select、evport、kquene 等等,Redis 服務(wù)端在運(yùn)行的時(shí)候會(huì)根據(jù)預(yù)先設(shè)定的 include 宏定義來(lái)選擇效率最高的模型去處理網(wǎng)絡(luò)事件。
以上基本上就是 Redis 的線程工作模型,不過(guò)還差一點(diǎn)就是文章開(kāi)頭講到的另外一種事件類型,時(shí)間事件。
什么是時(shí)間事件 ?
時(shí)間事件,顧名思義就是和時(shí)間相關(guān)的一些事件操作。舉個(gè)例子,在 Redis 中最不陌生的應(yīng)該就是各種定時(shí)處理器,每隔一段時(shí)間就出發(fā)一個(gè)操作。具體的應(yīng)用如 RDB 和 AOF 文件定時(shí)做持久化操作;如果集群是主從架構(gòu)的,定時(shí)將主庫(kù)上的數(shù)據(jù)同步給從庫(kù),定期發(fā)送心跳信息給集群內(nèi)各個(gè)節(jié)點(diǎn),檢查節(jié)點(diǎn)是否還在正常提供服務(wù);定期檢查庫(kù)里面設(shè)置了過(guò)期時(shí)間的 key 并將已過(guò)期的 key 從內(nèi)存中提出等等一些實(shí)際應(yīng)用。
時(shí)間事件是怎么實(shí)現(xiàn)的?
Redis 將所有時(shí)間事件通過(guò)鏈表串聯(lián)起來(lái),每個(gè)結(jié)點(diǎn)代表一個(gè)時(shí)間事件,每個(gè)結(jié)點(diǎn)上存儲(chǔ)著當(dāng)前時(shí)間下一次要發(fā)生的時(shí)間點(diǎn);每隔一段時(shí)間,該鏈表就會(huì)被遍歷一次,發(fā)現(xiàn)那個(gè)時(shí)間事件該執(zhí)行就去執(zhí)行對(duì)應(yīng)的事件,然后更新其下一次應(yīng)該執(zhí)行的時(shí)間點(diǎn)。
文件事件和時(shí)間事件是怎么配合工作的?

如上圖,文件事件和時(shí)間事件配合工作流程圖。
服務(wù)器啟動(dòng)后,開(kāi)始監(jiān)聽(tīng)文件事件,如果在一段時(shí)間內(nèi)有監(jiān)聽(tīng)到文件事件,則會(huì)執(zhí)行文件事件,執(zhí)行完文件事件后,開(kāi)始遍歷時(shí)間事件,這里需要注意的是,如果文件事件執(zhí)行時(shí)間過(guò)長(zhǎng),不會(huì)讓其一直執(zhí)行,而是暫停,等待下一個(gè)周期在繼續(xù)執(zhí)行。 文件事件執(zhí)行完,開(kāi)始遍歷執(zhí)行時(shí)間事件,遇到需要執(zhí)行的時(shí)間事件,比如定時(shí)持久化 RDB 和 AOF 等比較耗時(shí)的操作時(shí),會(huì) Fock 出一個(gè)子線程去執(zhí)行,而不會(huì)夯住當(dāng)前線程。 依次循環(huán)上述流程,就完成了文件事件和時(shí)間事件的配合工作的流程了。
Redis6.0 為什么要引入多線程?
相信很多朋友已經(jīng)知道了,2020 年 Redis 官方在 5 月份發(fā)布多了多線程版本,不過(guò)默認(rèn)是不開(kāi)啟的,可通過(guò) conf 配置開(kāi)啟。從目前 Redis 在實(shí)際生產(chǎn)環(huán)境中的使用情況看,其每秒鐘支持的吞吐量已經(jīng)非常高了。Redis 發(fā)展到目前,其主要的性能瓶頸主要在網(wǎng)絡(luò) I/O 和內(nèi)存兩個(gè)方面。
內(nèi)存容量問(wèn)題:如果采用集群部署的方式,可以通過(guò)部署多個(gè)端口,使得總?cè)萘康玫教嵘?,在一定程度上可以解決內(nèi)存的問(wèn)題。
網(wǎng)絡(luò) I/O 問(wèn)題:在實(shí)際生產(chǎn)環(huán)境中,Redis 在執(zhí)行命令時(shí)計(jì)算基于內(nèi)存是可以在很快時(shí)間內(nèi)完成的,但是數(shù)據(jù)傳輸過(guò)程中需要經(jīng)過(guò)網(wǎng)絡(luò) I/O 操作,這一步才是真正耗時(shí)所在。所以 Redis 在 6.0 版本引入了所謂的多線程,其主要是在做網(wǎng)絡(luò) I/O 操作的時(shí)候采用了多線程的方式加快 I/O 操作,而命令的執(zhí)行還是采用單線程進(jìn)行工作的。
總結(jié)
本文通過(guò)事件的角度分析了 Redis 線程的工作模型,下篇文章分析一下生產(chǎn)環(huán)境中常用的一些緩存方案。敬請(qǐng)期待,拜拜了您。
