線程模型
大家都知道,Redis是單線程的,為什么采用單線程的Redis也會(huì)如此之快呢?接下來我們分析其中緣由。
嚴(yán)格來說,<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)存中,這意味著我們讀寫數(shù)據(jù)都是在內(nèi)存中完成,這個(gè)速度是非??斓?。
Redis底層采用了高效的數(shù)據(jù)結(jié)構(gòu),例如哈希表和跳表,這是它實(shí)現(xiàn)高性能的一個(gè)重要原因。
2.采用IO多路復(fù)用機(jī)制
Redis 基于 Reactor 模式開發(fā)了自己的網(wǎng)絡(luò)事件處理器:這個(gè)處理器被稱為文件事件處理器(file event handler)。文件事件處理器使用 I/O 多路復(fù)用(multiplexing)程序來同時(shí)監(jiān)聽多個(gè)套接字,并根據(jù)套接字目前執(zhí)行的任務(wù)來為套接字關(guān)聯(lián)不同的事件處理器。
當(dāng)被監(jiān)聽的套接字準(zhǔn)備好執(zhí)行連接應(yīng)答(accept)、讀取(read)、寫入(write)、關(guān)閉(close)等操作時(shí),與操作相對(duì)應(yīng)的文件事件就會(huì)產(chǎn)生,這時(shí)文件事件處理器就會(huì)調(diào)用套接字之前關(guān)聯(lián)好的事件處理器來處理這些事件。
雖然文件事件處理器以單線程方式運(yùn)行,但通過使用 I/O 多路復(fù)用程序來監(jiān)聽多個(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)然,如果你覺得單個(gè)Redis實(shí)例的性能不足以支撐業(yè)務(wù),Redis作者推薦部署多個(gè)Redis節(jié)點(diǎn),組成集群的方式來利用多核CPU的能力,而不是在單個(gè)實(shí)例上使用多線程來處理。
4.單線程的優(yōu)點(diǎn)
基于以上特性,Redis采用單線程已足夠達(dá)到非常高的性能,所以Redis沒有采用多線程模型。
另外,單線程模型還帶了以下好處:
- 避免多線程上下文切換導(dǎo)致的性能損耗
- 避免多線程訪問共享資源加鎖導(dǎo)致的性能損耗
所以Redis正是基于有以上這些優(yōu)點(diǎn),所以采用了單線程模型來完成請(qǐng)求處理的工作。
5.單線程的缺點(diǎn)
單線程處理最大的缺點(diǎn)就是,如果前一個(gè)請(qǐng)求發(fā)生耗時(shí)比較久的操作,那么整個(gè)Redis都會(huì)被阻塞,其他請(qǐng)求也無(wú)法進(jìn)來,直到這個(gè)耗時(shí)久的操作處理完成并返回,其他請(qǐng)求才能被處理到。
我們平時(shí)遇到Redis響應(yīng)變慢或長(zhǎng)時(shí)間阻塞的問題,大部分都是因?yàn)镽edis處理請(qǐng)求是單線程這個(gè)原因?qū)е碌摹?/p>
所以,我們?cè)谑褂肦edis時(shí),一定要避免非常耗時(shí)的操作,例如使用時(shí)間復(fù)雜度過高的方式獲取數(shù)據(jù)、一次性獲取過多的數(shù)據(jù)、大量key集中過期導(dǎo)致Redis淘汰key壓力變大等等,這些場(chǎng)景都會(huì)阻塞住整個(gè)處理線程,直到它們處理完成,勢(shì)必會(huì)影響業(yè)務(wù)的訪問。
6.多線程優(yōu)化
Redis Server是多線程的,除了請(qǐng)求處理流程是單線程處理之外,Redis內(nèi)部還有其他工作線程在后臺(tái)執(zhí)行,它負(fù)責(zé)異步執(zhí)行某些比較耗時(shí)的任務(wù),例如AOF每秒刷盤、AOF文件重寫都是在另一個(gè)線程中完成的。
而在Redis 4.0之后,Redis引入了lazyfree的機(jī)制,提供了unlink、flushall aysc、flushdb async等命令和lazyfree-lazy-eviction、lazyfree-lazy-expire等機(jī)制來異步釋放內(nèi)存,它主要是為了解決在釋放大內(nèi)存數(shù)據(jù)導(dǎo)致整個(gè)redis阻塞的性能問題。
在刪除大key時(shí),釋放內(nèi)存往往都比較耗時(shí),所以Redis提供異步釋放內(nèi)存的方式,讓這些耗時(shí)的操作放到另一個(gè)線程中異步去處理,從而不影響主線程的執(zhí)行,提高性能。
到了Redis 6.0,Redis又引入了多線程來完成請(qǐng)求數(shù)據(jù)的協(xié)議解析,進(jìn)一步提升性能。它主要是解決高并發(fā)場(chǎng)景下,單線程解析請(qǐng)求數(shù)據(jù)協(xié)議帶來的壓力。請(qǐng)求數(shù)據(jù)的協(xié)議解析由多線程完成之后,后面的請(qǐng)求處理階段依舊還是單線程排隊(duì)處理。
可見,Redis并不是保守地認(rèn)為單線程有多好,也不是為了使用多線程而引入多線程。Redis作者很清楚單線程和多線程的使用場(chǎng)景,針對(duì)性地優(yōu)化。