1. 理解單線程模型
- redis 會(huì)將每個(gè)客戶端都關(guān)聯(lián)一個(gè)指令隊(duì)列兑巾∧锼客戶端的指令通過隊(duì)列來按順序處理,先到先服務(wù)赘那。
- 在一個(gè)客戶端的指令隊(duì)列中的指令是順序執(zhí)行的泪喊,但是多個(gè)指令隊(duì)列中的指令是無法保證順序的棚愤,例如執(zhí)行完 client-0 的隊(duì)列中的 command-0 后伪很,接下去是執(zhí)行哪個(gè)隊(duì)列中的第一個(gè)指令是無法確定的戚啥,但是肯定不會(huì)同時(shí)執(zhí)行兩個(gè)指令。
- redis 同樣也會(huì)為每個(gè)客戶端關(guān)聯(lián)一個(gè)響應(yīng)隊(duì)列锉试,通過響應(yīng)隊(duì)列來順序地將指令的返回結(jié)果回復(fù)給客戶端猫十。
- 同樣,一個(gè)響應(yīng)隊(duì)列中的消息可以順序的回復(fù)給客戶端,多個(gè)響應(yīng)隊(duì)列之間是無法保證順序的炫彩。
- 所有的客戶端的隊(duì)列中的指令或者響應(yīng),redis 每次都只能處理一個(gè)絮短,同一時(shí)間絕對(duì)不會(huì)處理超過一個(gè)指令或者響應(yīng)江兢。
2. 為什么redis使用單線程模型還能保證高性能?
(1) 純內(nèi)存訪問
redis 將所有數(shù)據(jù)放在內(nèi)存中丁频,內(nèi)存的響應(yīng)時(shí)長(zhǎng)大約為 100 納秒杉允,這是 redis 的 QPS 過萬的重要基礎(chǔ)。
(2) 非阻塞式IO
- 什么是阻塞式 IO
當(dāng)我們調(diào)用 Scoket 的讀寫方法席里,默認(rèn)它們是阻塞的叔磷。
read() 方法要傳遞進(jìn)去一個(gè)參數(shù) n,表示讀取這么多字節(jié)后再返回奖磁,如果沒有讀夠 n 字節(jié)線程就會(huì)阻塞改基,直到新的數(shù)據(jù)到來或者連接關(guān)閉了, read 方法才可以返回咖为,線程才能繼續(xù)處理秕狰。
write() 方法會(huì)首先把數(shù)據(jù)寫到系統(tǒng)內(nèi)核為 Scoket 分配的寫緩沖區(qū)中,當(dāng)寫緩存區(qū)滿溢躁染,即寫緩存區(qū)中的數(shù)據(jù)還沒有寫入到磁盤鸣哀,就有新的數(shù)據(jù)要寫道寫緩存區(qū)時(shí),write() 方法就會(huì)阻塞吞彤,直到寫緩存區(qū)中有空閑空間我衬。
- 什么是非阻塞式 IO
非阻塞 IO 在 Scoket 對(duì)象上提供了一個(gè)選項(xiàng)Non_Blocking
,當(dāng)這個(gè)選項(xiàng)打開時(shí)饰恕,讀寫方法不會(huì)阻塞挠羔,而是能讀多少讀多少,能寫多少寫多少埋嵌。
能讀多少取決于內(nèi)核為 Scoket 分配的讀緩沖區(qū)的大小褥赊,能寫多少取決于內(nèi)核為 Scoket 分配的寫緩沖區(qū)的剩余空間大小。讀方法和寫方法都會(huì)通過返回值來告知程序?qū)嶋H讀寫了多少字節(jié)數(shù)據(jù)莉恼。
有了非阻塞 IO 意味著線程在讀寫 IO 時(shí)可以不必再阻塞了拌喉,讀寫可以瞬間完成然后線程可以繼續(xù)干別的事了。
(3) IO多路復(fù)用
非阻塞 IO 有個(gè)問題俐银,那就是單個(gè)線程要處理多個(gè)讀寫請(qǐng)求尿背,處理某個(gè)客戶端的的讀數(shù)據(jù)的請(qǐng)求,結(jié)果讀了一部分就返回了捶惜,線程如何知道什么時(shí)候才應(yīng)該繼續(xù)讀數(shù)據(jù)田藐。處理寫請(qǐng)求的時(shí)候,如果緩沖區(qū)滿了,寫不完汽久,剩下的數(shù)據(jù)何時(shí)才應(yīng)該繼續(xù)寫鹤竭?在什么時(shí)候處理什么請(qǐng)求?redis 單線程處理多個(gè)IO請(qǐng)求時(shí)就用到了IO多路復(fù)用技術(shù)景醇。
簡(jiǎn)單的理解下 IO 多路復(fù)用技術(shù)臀稚,假設(shè)每個(gè)客戶端的 IO 請(qǐng)求是一條電路,redis 是一個(gè)開關(guān)三痰,如下圖所示:
在上圖中吧寺,redis 需要處理 3 個(gè) IO 請(qǐng)求,同時(shí)把 3 個(gè)請(qǐng)求的結(jié)果返回給客戶端散劫,所以總共需要處理 6 個(gè) IO 事件稚机,由于 redis 是單線程模型,同一時(shí)間只能處理一個(gè) IO 事件获搏,于是 redis 需要在合適的時(shí)間暫停對(duì)某個(gè) IO 事件的處理赖条,轉(zhuǎn)而去處理另一個(gè) IO 事件,這樣 redis 就好比一個(gè)開關(guān)常熙,當(dāng)開關(guān)撥到哪個(gè) IO 事件這個(gè)電路上谋币,就處理哪個(gè) IO 事件,其他 IO 事件就暫停處理了症概。這就是IO多路復(fù)用技術(shù)蕾额。
以上是大致的理解下 IO 多路復(fù)用技術(shù),在系統(tǒng)底層彼城,IO 多路復(fù)用有 3 種實(shí)現(xiàn)機(jī)制:
- select
- poll
- epoll
這些實(shí)現(xiàn)機(jī)制的底層我不清楚诅蝶,看過一些博客(細(xì)節(jié)也沒看懂),總結(jié)一下就是:
epoll 是目前最新的也是最先進(jìn)的 IO 多路復(fù)用的實(shí)現(xiàn)解決了select 和 poll 的很多問題募壕。而 redis 就是使用的基于 epoll 的 IO 多路復(fù)用技術(shù)调炬。
對(duì)這 3 種實(shí)現(xiàn)機(jī)制感興趣的話,可以去看看大神的博客舱馅,本人在這里就不再胡說了缰泡。
(4) 單線程避免了線程切換和競(jìng)態(tài)產(chǎn)生的消耗。
單線程能帶來幾個(gè)好處:
第一代嗤,單線程可以簡(jiǎn)化數(shù)據(jù)結(jié)構(gòu)和算法的實(shí)現(xiàn)棘钞。并發(fā)數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)不但困難而且開發(fā)測(cè)試比較麻
第二,單線程避免了線程切換和競(jìng)態(tài)產(chǎn)生的消耗干毅,對(duì)于服務(wù)端開發(fā)來說宜猜,鎖和線程切換通常是性能殺手。
單線程的問題:對(duì)于每個(gè)命令的執(zhí)行時(shí)間是有要求的硝逢。如果
某個(gè)命令執(zhí)行過長(zhǎng)姨拥,會(huì)造成其他命令的阻塞绅喉,所以 redis 適用于那些需要快速執(zhí)行的場(chǎng)景。