redis 最基本的一個(gè)內(nèi)部原理和特點(diǎn),就是 redis 實(shí)際上是個(gè)單線程工作模型。
memcached 是早些年各大互聯(lián)網(wǎng)公司常用的緩存方案,但是現(xiàn)在近幾年基本都是 redis,沒什么公司用 memcached 了。
redis 和 memcached 有啥區(qū)別?
redis 支持復(fù)雜的數(shù)據(jù)結(jié)構(gòu)
redis 相比 memcached 來說,擁有更多的數(shù)據(jù)結(jié)構(gòu),能支持更豐富的數(shù)據(jù)操作。如果需要緩存能夠支持更復(fù)雜的結(jié)構(gòu)和操作, redis 會(huì)是不錯(cuò)的選擇。
redis 原生支持集群模式
在 redis3.x 版本中,便能支持 cluster 模式,而 memcached 沒有原生的集群模式,需要依靠客戶端來實(shí)現(xiàn)往集群中分片寫入數(shù)據(jù)。
性能對(duì)比
由于 redis 只使用單核,而 memcached 可以使用多核,所以平均每一個(gè)核上 redis 在存儲(chǔ)小數(shù)據(jù)時(shí)比 memcached 性能更高。而在 100k 以上的數(shù)據(jù)中,memcached 性能要高于 redis,雖然 redis 最近也在存儲(chǔ)大數(shù)據(jù)的性能上進(jìn)行優(yōu)化,但是比起 memcached,還是稍有遜色。
redis 的線程模型
redis 內(nèi)部使用文件事件處理器 file event handler,這個(gè)文件事件處理器是單線程的,所以 redis 才叫做單線程的模型。它采用 IO 多路復(fù)用機(jī)制同時(shí)監(jiān)聽多個(gè) socket,根據(jù) socket 上的事件來選擇對(duì)應(yīng)的事件處理器進(jìn)行處理。
文件事件處理器的結(jié)構(gòu)包含 4 個(gè)部分:
- 多個(gè) socket
- IO 多路復(fù)用程序
- 文件事件分派器
- 事件處理器(連接應(yīng)答處理器、命令請(qǐng)求處理器、命令回復(fù)處理器)
多個(gè) socket 可能會(huì)并發(fā)產(chǎn)生不同的操作,每個(gè)操作對(duì)應(yīng)不同的文件事件,但是 IO 多路復(fù)用程序會(huì)監(jiān)聽多個(gè) socket,會(huì)將 socket 產(chǎn)生的事件放入隊(duì)列中排隊(duì),事件分派器每次從隊(duì)列中取出一個(gè)事件,把該事件交給對(duì)應(yīng)的事件處理器進(jìn)行處理。
來看客戶端與 redis 的一次通信過程(圖片若不清晰,請(qǐng)右擊在新標(biāo)簽中打開圖片):
客戶端 socket01 向 redis 的 server socket 請(qǐng)求建立連接,此時(shí) server socket 會(huì)產(chǎn)生一個(gè) AE_READABLE 事件,IO 多路復(fù)用程序監(jiān)聽到 server socket 產(chǎn)生的事件后,將該事件壓入隊(duì)列中。文件事件分派器從隊(duì)列中獲取該事件,交給連接應(yīng)答處理器。連接應(yīng)答處理器會(huì)創(chuàng)建一個(gè)能與客戶端通信的 socket01,并將該 socket01 的 AE_READABLE 事件與命令請(qǐng)求處理器關(guān)聯(lián)。
假設(shè)此時(shí)客戶端發(fā)送了一個(gè) set key value 請(qǐng)求,此時(shí) redis 中的 socket01 會(huì)產(chǎn)生 AE_READABLE 事件,IO 多路復(fù)用程序?qū)⑹录喝腙?duì)列,此時(shí)事件分派器從隊(duì)列中獲取到該事件,由于前面 socket01 的 AE_READABLE 事件已經(jīng)與命令請(qǐng)求處理器關(guān)聯(lián),因此事件分派器將事件交給命令請(qǐng)求處理器來處理。命令請(qǐng)求處理器讀取 socket01 的 key value 并在自己內(nèi)存中完成 key value 的設(shè)置。操作完成后,它會(huì)將 socket01 的 AE_WRITABLE 事件與命令回復(fù)處理器關(guān)聯(lián)。
如果此時(shí)客戶端準(zhǔn)備好接收返回結(jié)果了,那么 redis 中的 socket01 會(huì)產(chǎn)生一個(gè) AE_WRITABLE 事件,同樣壓入隊(duì)列中,事件分派器找到相關(guān)聯(lián)的命令回復(fù)處理器,由命令回復(fù)處理器對(duì) socket01 輸入本次操作的一個(gè)結(jié)果,比如 ok,之后解除 socket01 的 AE_WRITABLE 事件與命令回復(fù)處理器的關(guān)聯(lián)。
這樣便完成了一次通信。
為啥 redis 單線程模型也能效率這么高?
- 純內(nèi)存操作
- 核心是基于非阻塞的 IO 多路復(fù)用機(jī)制
- 單線程反而避免了多線程的頻繁上下文切換問題
為什么Redis是單線程的
官方答案
因?yàn)镽edis是基于內(nèi)存的操作,CPU不是Redis的瓶頸,Redis的瓶頸最有可能是機(jī)器內(nèi)存的大小或者網(wǎng)絡(luò)帶寬。既然單線程容易實(shí)現(xiàn),而且CPU不會(huì)成為瓶頸,那就順理成章地采用單線程的方案了。
性能指標(biāo)
關(guān)于redis的性能,官方網(wǎng)站也有,普通筆記本輕松處理每秒幾十萬的請(qǐng)求。
詳細(xì)原因
1)不需要各種鎖的性能消耗
Redis的數(shù)據(jù)結(jié)構(gòu)并不全是簡(jiǎn)單的Key-Value,還有l(wèi)ist,hash等復(fù)雜的結(jié)構(gòu),這些結(jié)構(gòu)有可能會(huì)進(jìn)行很細(xì)粒度的操作,比如在很長(zhǎng)的列表后面添加一個(gè)元素,在hash當(dāng)中添加或者刪除一個(gè)對(duì)象。這些操作可能就需要加非常多的鎖,導(dǎo)致的結(jié)果是同步開銷大大增加。
總之,在單線程的情況下,就不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因?yàn)榭赡艹霈F(xiàn)死鎖而導(dǎo)致的性能消耗。
2)單線程多進(jìn)程集群方案
單線程的威力實(shí)際上非常強(qiáng)大,每核心效率也非常高,多線程自然是可以比單線程有更高的性能上限,但是在今天的計(jì)算環(huán)境中,即使是單機(jī)多線程的上限也往往不能滿足需要了,需要進(jìn)一步摸索的是多服務(wù)器集群化的方案,這些方案中多線程的技術(shù)照樣是用不上的。
所以單線程、多進(jìn)程的集群不失為一個(gè)時(shí)髦的解決方案。
3)CPU消耗
采用單線程,避免了不必要的上下文切換和競(jìng)爭(zhēng)條件,也不存在多進(jìn)程或者多線程導(dǎo)致的切換而消耗 CPU。
但是如果CPU成為Redis瓶頸,或者不想讓服務(wù)器其他CPU核閑置,那怎么辦?
可以考慮多起幾個(gè)Redis進(jìn)程,Redis是key-value數(shù)據(jù)庫(kù),不是關(guān)系數(shù)據(jù)庫(kù),數(shù)據(jù)之間沒有約束。只要客戶端分清哪些key放在哪個(gè)Redis進(jìn)程上就可以了。
Redis單線程的優(yōu)劣勢(shì)
單進(jìn)程單線程優(yōu)勢(shì)
代碼更清晰,處理邏輯更簡(jiǎn)單
不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因?yàn)榭赡艹霈F(xiàn)死鎖而導(dǎo)致的性能消耗
不存在多進(jìn)程或者多線程導(dǎo)致的切換而消耗CPU單進(jìn)程單線程弊端
無法發(fā)揮多核CPU性能,不過可以通過在單機(jī)開多個(gè)Redis實(shí)例來完善;
IO多路復(fù)用技術(shù)
redis 采用網(wǎng)絡(luò)IO多路復(fù)用技術(shù)來保證在多連接的時(shí)候, 系統(tǒng)的高吞吐量。
多路-指的是多個(gè)socket連接,復(fù)用-指的是復(fù)用一個(gè)線程。多路復(fù)用主要有三種技術(shù):select,poll,epoll。epoll是最新的也是目前最好的多路復(fù)用技術(shù)。
這里“多路”指的是多個(gè)網(wǎng)絡(luò)連接,“復(fù)用”指的是復(fù)用同一個(gè)線程。采用多路I/O復(fù)用技術(shù)可以讓單個(gè)線程高效的處理多個(gè)連接請(qǐng)求(盡量減少網(wǎng)絡(luò)IO的時(shí)間消耗),且Redis在內(nèi)存中操作數(shù)據(jù)的速度非??欤▋?nèi)存內(nèi)的操作不會(huì)成為這里的性能瓶頸),主要以上兩點(diǎn)造就了Redis具有很高的吞吐量。