線程模型
大家都知道,Redis是單線程的鹏漆,為什么采用單線程的Redis也會(huì)如此之快呢巩梢?接下來(lái)我們分析其中緣由。
嚴(yán)格來(lái)說(shuō)艺玲,<font style="color: rgb(255, 76, 0);"> Redis Server是多線程的括蝠, </font>只是它的請(qǐng)求處理整個(gè)流程是單線程處理的。 這一點(diǎn)我們一定要清楚了解到饭聚,不要單純地認(rèn)為Redis Server是單線程的忌警。
Redis的性能非常之高,每秒可以承受10W+的QPS秒梳,它如此優(yōu)秀的性能主要取決于以下幾個(gè)方面:
- Redis大部分操作在內(nèi)存完成
- 采用IO多路復(fù)用機(jī)制
- 非CPU密集型任務(wù)
- 單線程的優(yōu)勢(shì)
1.純內(nèi)存操作
Redis是一個(gè)內(nèi)存數(shù)據(jù)庫(kù)法绵,它的數(shù)據(jù)都存儲(chǔ)在內(nèi)存中,這意味著我們讀寫(xiě)數(shù)據(jù)都是在內(nèi)存中完成酪碘,這個(gè)速度是非撑笃快的。
Redis底層采用了高效的數(shù)據(jù)結(jié)構(gòu)兴垦,例如哈希表和跳表徙赢,這是它實(shí)現(xiàn)高性能的一個(gè)重要原因。
2.采用IO多路復(fù)用機(jī)制
Redis 基于 Reactor 模式開(kāi)發(fā)了自己的網(wǎng)絡(luò)事件處理器:這個(gè)處理器被稱(chēng)為文件事件處理器(file event handler)探越。文件事件處理器使用 I/O 多路復(fù)用(multiplexing)程序來(lái)同時(shí)監(jiān)聽(tīng)多個(gè)套接字犀忱,并根據(jù)套接字目前執(zhí)行的任務(wù)來(lái)為套接字關(guān)聯(lián)不同的事件處理器。
當(dāng)被監(jiān)聽(tīng)的套接字準(zhǔn)備好執(zhí)行連接應(yīng)答(accept)扶关、讀取(read)数冬、寫(xiě)入(write)节槐、關(guān)閉(close)等操作時(shí),與操作相對(duì)應(yīng)的文件事件就會(huì)產(chǎn)生拐纱,這時(shí)文件事件處理器就會(huì)調(diào)用套接字之前關(guān)聯(lián)好的事件處理器來(lái)處理這些事件铜异。
雖然文件事件處理器以單線程方式運(yùn)行,但通過(guò)使用 I/O 多路復(fù)用程序來(lái)監(jiān)聽(tīng)多個(gè)套接字秸架,文件事件處理器既實(shí)現(xiàn)了高性能的網(wǎng)絡(luò)通信模型揍庄,又可以很好地與 Redis 服務(wù)器中其他同樣以單線程方式運(yùn)行的模塊進(jìn)行對(duì)接,這保持了 Redis 內(nèi)部單線程設(shè)計(jì)的簡(jiǎn)單性东抹。
3.非CPU密集型任務(wù)
采用單線程的缺點(diǎn)很明顯蚂子,無(wú)法使用多核CPU沃测。Redis作者提到,由于Redis的大部分操作并不是CPU密集型任務(wù)食茎,而Redis的瓶頸在于內(nèi)存和網(wǎng)絡(luò)帶寬蒂破。
在高并發(fā)請(qǐng)求下,Redis需要更多的內(nèi)存和更高的網(wǎng)絡(luò)帶寬别渔,否則瓶頸很容易出現(xiàn)在內(nèi)存不夠用和網(wǎng)絡(luò)延遲等待的情況附迷。
當(dāng)然,如果你覺(jué)得單個(gè)Redis實(shí)例的性能不足以支撐業(yè)務(wù)哎媚,Redis作者推薦部署多個(gè)Redis節(jié)點(diǎn)喇伯,組成集群的方式來(lái)利用多核CPU的能力,而不是在單個(gè)實(shí)例上使用多線程來(lái)處理拨与。
4.單線程的優(yōu)點(diǎn)
基于以上特性稻据,Redis采用單線程已足夠達(dá)到非常高的性能,所以Redis沒(méi)有采用多線程模型截珍。
另外攀甚,單線程模型還帶了以下好處:
- 避免多線程上下文切換導(dǎo)致的性能損耗
- 避免多線程訪問(wèn)共享資源加鎖導(dǎo)致的性能損耗
所以Redis正是基于有以上這些優(yōu)點(diǎn),所以采用了單線程模型來(lái)完成請(qǐng)求處理的工作岗喉。
5.單線程的缺點(diǎn)
單線程處理最大的缺點(diǎn)就是秋度,如果前一個(gè)請(qǐng)求發(fā)生耗時(shí)比較久的操作,那么整個(gè)Redis都會(huì)被阻塞钱床,其他請(qǐng)求也無(wú)法進(jìn)來(lái)荚斯,直到這個(gè)耗時(shí)久的操作處理完成并返回,其他請(qǐng)求才能被處理到查牌。
我們平時(shí)遇到Redis響應(yīng)變慢或長(zhǎng)時(shí)間阻塞的問(wèn)題事期,大部分都是因?yàn)镽edis處理請(qǐng)求是單線程這個(gè)原因?qū)е碌摹?/p>
所以,我們?cè)谑褂肦edis時(shí)纸颜,一定要避免非常耗時(shí)的操作兽泣,例如使用時(shí)間復(fù)雜度過(guò)高的方式獲取數(shù)據(jù)、一次性獲取過(guò)多的數(shù)據(jù)胁孙、大量key集中過(guò)期導(dǎo)致Redis淘汰key壓力變大等等唠倦,這些場(chǎng)景都會(huì)阻塞住整個(gè)處理線程,直到它們處理完成涮较,勢(shì)必會(huì)影響業(yè)務(wù)的訪問(wèn)稠鼻。
6.多線程優(yōu)化
Redis Server是多線程的,除了請(qǐng)求處理流程是單線程處理之外狂票,Redis內(nèi)部還有其他工作線程在后臺(tái)執(zhí)行候齿,它負(fù)責(zé)異步執(zhí)行某些比較耗時(shí)的任務(wù),例如AOF每秒刷盤(pán)、AOF文件重寫(xiě)都是在另一個(gè)線程中完成的慌盯。
而在Redis 4.0之后周霉,Redis引入了lazyfree的機(jī)制,提供了unlink润匙、flushall aysc诗眨、flushdb async等命令和lazyfree-lazy-eviction、lazyfree-lazy-expire等機(jī)制來(lái)異步釋放內(nèi)存孕讳,它主要是為了解決在釋放大內(nèi)存數(shù)據(jù)導(dǎo)致整個(gè)redis阻塞的性能問(wèn)題匠楚。
在刪除大key時(shí),釋放內(nèi)存往往都比較耗時(shí)厂财,所以Redis提供異步釋放內(nèi)存的方式芋簿,讓這些耗時(shí)的操作放到另一個(gè)線程中異步去處理,從而不影響主線程的執(zhí)行璃饱,提高性能与斤。
到了Redis 6.0,Redis又引入了多線程來(lái)完成請(qǐng)求數(shù)據(jù)的協(xié)議解析荚恶,進(jìn)一步提升性能撩穿。它主要是解決高并發(fā)場(chǎng)景下,單線程解析請(qǐng)求數(shù)據(jù)協(xié)議帶來(lái)的壓力谒撼。請(qǐng)求數(shù)據(jù)的協(xié)議解析由多線程完成之后食寡,后面的請(qǐng)求處理階段依舊還是單線程排隊(duì)處理。
可見(jiàn)廓潜,Redis并不是保守地認(rèn)為單線程有多好抵皱,也不是為了使用多線程而引入多線程。Redis作者很清楚單線程和多線程的使用場(chǎng)景辩蛋,針對(duì)性地優(yōu)化呻畸。