Redis為什么又引入了多線程?單線程不香了?
相信你一定不止一次見過Redis是單線程模式,不過說實(shí)話那只是個老版本,這個問題是一位老哥的大廠面試題,跟我分享了一下。想著自己就知道redis6.0以前一直都是單線程,到了6的版本才加入了多線程,還不是很清楚,在多方打聽并且搜索之下總結(jié)了這篇文章。
一、問題概述
Redis 6.0 之后的版本拋棄了單線程模型這一設(shè)計,原本使用單線程運(yùn)行的 Redis 也開始選擇性使用多線程模型,乍一看Redis的作者這么牛,也逃不過“真香定律”,
仔細(xì)想想,這個問題其實(shí)可以拆分,拆分為兩個主要的問題:
(1)為什么 Redis 一開始選擇單線程模型(單線程的好處)?
(2)為什么 Redis 在 6.0 之后加入了多線程(在某些情況下,單線程出現(xiàn)了缺點(diǎn),多線程可以解決)?
其實(shí),作者并不是沒有逃脫真香定理,而是隨著時間的推移,出現(xiàn)的問題也越來越多,原來的設(shè)計肯定就有些不合時宜,該做出改變就做出改變。OK,帶著倆問題,我們就來好好地分析一下。
二、為什么Redis一開始使用單線程
不管是單線程或者是多線程都是為了提升Redis的開發(fā)效率,因?yàn)镽edis是一個基于內(nèi)存的數(shù)據(jù)庫,還要處理大量的外部的網(wǎng)絡(luò)請求,這就不可避免的要進(jìn)行多次IO。好在Redis使用了很多優(yōu)秀的機(jī)制來保證了它的高效率。那么為什么Redis要設(shè)計成單線程模式的呢?可以總結(jié)如下:
(1)IO多路復(fù)用
我們來看一下Redis頂層設(shè)計。
FD是一個文件描述符,意思是表示當(dāng)前文件處于可讀、可寫還是異常狀態(tài)。使用 I/O 多路復(fù)用機(jī)制同時監(jiān)聽多個文件描述符的可讀和可寫狀態(tài)。你可以理解為具有了多線程的特點(diǎn)。
一旦受到網(wǎng)絡(luò)請求就會在內(nèi)存中快速處理,由于絕大多數(shù)的操作都是純內(nèi)存的,所以處理的速度會非常地快。也就是說在單線程模式下,即使連接的網(wǎng)絡(luò)處理很多,因?yàn)橛蠭O多路復(fù)用,依然可以在高速的內(nèi)存處理中得到忽略。
(2)可維護(hù)性高
多線程模型雖然在某些方面表現(xiàn)優(yōu)異,但是它卻引入了程序執(zhí)行順序的不確定性,帶來了并發(fā)讀寫的一系列問題。單線程模式下,可以方便地進(jìn)行調(diào)試和測試。
(3)基于內(nèi)存,單線程狀態(tài)下效率依然高
多線程能夠充分利用CPU的資源,但對于Redis來說,由于基于內(nèi)存速度那是相當(dāng)?shù)母撸苓_(dá)到在一秒內(nèi)處理10萬個用戶請求,如果一秒十萬還不能滿足,那我們就可以使用Redis分片的技術(shù)來交給不同的Redis服務(wù)器。這樣的做飯避免了在同一個 Redis 服務(wù)中引入大量的多線程操作。
而且基于內(nèi)存,除非是要進(jìn)行AOF備份,否則基本上不會涉及任何的 I/O 操作。這些數(shù)據(jù)的讀寫由于只發(fā)生在內(nèi)存中,所以處理速度是非常快的;用多線程模型處理全部的外部請求可能不是一個好的方案。
現(xiàn)在我們知道了基本上可以總結(jié)成兩句話,基于內(nèi)存而且使用多路復(fù)用技術(shù),單線程速度很快,又保證了多線程的特點(diǎn)。因?yàn)闆]有必要使用多線程。
三、為什么引入多線程?
剛剛說了一堆使用單線程的好處,現(xiàn)在話鋒一轉(zhuǎn),又要說為什么要引入多線程,別不適應(yīng)。引入多線程說明Redis在有些方面,單線程已經(jīng)不具有優(yōu)勢了。
因?yàn)樽x寫網(wǎng)絡(luò)的read/write系統(tǒng)調(diào)用在Redis執(zhí)行期間占用了大部分CPU時間,如果把網(wǎng)絡(luò)讀寫做成多線程的方式對性能會有很大提升。
Redis 的多線程部分只是用來處理網(wǎng)絡(luò)數(shù)據(jù)的讀寫和協(xié)議解析,執(zhí)行命令仍然是單線程。之所以這么設(shè)計是不想 Redis 因?yàn)槎嗑€程而變得復(fù)雜,需要去控制 key、lua、事務(wù),LPUSH/LPOP 等等的并發(fā)問題。
Redis 在最新的幾個版本中加入了一些可以被其他線程異步處理的刪除操作,也就是我們在上面提到的 UNLINK、FLUSHALL ASYNC 和 FLUSHDB ASYNC,我們?yōu)槭裁磿枰@些刪除操作,而它們?yōu)槭裁葱枰ㄟ^多線程的方式異步處理?
我們知道Redis可以使用del命令刪除一個元素,如果這個元素非常大,可能占據(jù)了幾十兆或者是幾百兆,那么在短時間內(nèi)是不能完成的,這樣一來就需要多線程的異步支持。
現(xiàn)在刪除工作可以在后臺進(jìn)行。
四、總結(jié)
Redis 選擇使用單線程模型處理客戶端的請求主要還是因?yàn)?CPU 不是 Redis 服務(wù)器的瓶頸,所以使用多線程模型帶來的性能提升并不能抵消它帶來的開發(fā)成本和維護(hù)成本,系統(tǒng)的性能瓶頸也主要在網(wǎng)絡(luò) I/O 操作上;而 Redis 引入多線程操作也是出于性能上的考慮,對于一些大鍵值對的刪除操作,通過多線程非阻塞地釋放內(nèi)存空間也能減少對 Redis 主線程阻塞的時間,提高執(zhí)行的效率。
一句話講完:之前用單線程是因?yàn)榛趦?nèi)存速度快,而且多路復(fù)用有多路復(fù)用的作用,也就是足夠了,現(xiàn)在引入是因?yàn)樵谀承┎僮饕獌?yōu)化,比如刪除操作,因此引入了多線程。