程序員過關(guān)斬將--請(qǐng)不要誤會(huì)Redis 6.0 的多線程
你對(duì)redis的單線程是不是有點(diǎn)誤會(huì)?
你對(duì)redis 6.0的多線程是不是也有點(diǎn)誤會(huì)?
redis多線程一定可以提高性能嗎?
redis官方剛剛發(fā)布的6.0版本已經(jīng)掀起了業(yè)界一陣熱波,在這個(gè)版本中新加了很多新特性,如果你打開redis的官網(wǎng),可以看到6.0現(xiàn)在已經(jīng)是穩(wěn)定版本了。
image
redis現(xiàn)在已經(jīng)成為了面試官必問的知識(shí)點(diǎn)之一,尤其是當(dāng)新版本加入了“多線程”概念之后,面試題又是增加了一道難題。
redis單線程
redis在6.0之前的版本,很多同學(xué)認(rèn)為是單線程,其實(shí)這個(gè)說法嚴(yán)格意義上來講不太準(zhǔn)確。“單線程”是指客戶端發(fā)送的命令的接收,解析,執(zhí)行,結(jié)果返回這個(gè)過程是由一個(gè)線程處理,這個(gè)線程就是主線程,這也是redis素有“單線程”定義的來源。
但是,redis也有其他后臺(tái)線程在處理其他操作,比如那些比較慢,不太適合放主線程執(zhí)行的操作,例如大key的刪除,AOF的重寫,快照的生成,無用連接的釋放等。
單線程機(jī)制使得redis內(nèi)部代碼實(shí)現(xiàn)的復(fù)雜度和難度大大降低,請(qǐng)求都是按照串行化的順序來依次執(zhí)行,這大大降低了由于線程切換帶來的資源消耗,而且又可以避免鎖帶來的一系列問題。所以平時(shí)開發(fā)使用redis的時(shí)候,我們可以實(shí)現(xiàn)分布式鎖等一系列騷操作,這和Actor模型中,單個(gè)Actor的行為十分類似,串行的操作使我們可以擺脫多線程帶來的一系列執(zhí)行順序的痛苦。
至于redis為什么不使用多線程呢?官方曾經(jīng)做過類似問題的回復(fù),大體意思是,redis由于cpu成為瓶頸的幾率幾乎不存在,redis的性能主要受限于內(nèi)存和網(wǎng)絡(luò),當(dāng)然,我認(rèn)為這個(gè)解釋并非是絕對(duì)的。當(dāng)redis開啟持久化之后,吞吐量會(huì)大幅度下降,除非非常必要,不然在很多業(yè)務(wù)場(chǎng)景下盡量不要開啟redis的持久化。
redis單線程瓶頸
redis將所有數(shù)據(jù)放在內(nèi)存中,在充分利用pipelining技術(shù)的情況下,QPS可達(dá)百萬,這個(gè)量級(jí)對(duì)于普通的中小公司已經(jīng)足以,就算是再大一點(diǎn)的請(qǐng)求量,利用redis集群方式也能抗住。但是redis集群也是有缺陷的:
- redis集群需要更多的服務(wù)器資源來支撐,這無疑加大了公司的支出費(fèi)用和資源成本。
- redis集群雖然可以通過增加副本的方式來解決熱點(diǎn)key的讀問題,但是熱點(diǎn)key的寫依然比較棘手
從redis自身的處理請(qǐng)求過程來看,對(duì)網(wǎng)絡(luò)數(shù)據(jù)的讀寫以及對(duì)命令的解析占用了大部分cpu時(shí)間,瓶頸在于網(wǎng)絡(luò)IO的消耗以及對(duì)CPU不能充分的利用。而要突破這個(gè)瓶頸呢,一般有兩種解決方案:
網(wǎng)絡(luò)請(qǐng)求的處理不再依靠?jī)?nèi)核,而在用戶態(tài)處理。但是這種方式需要修改內(nèi)核網(wǎng)絡(luò)棧的實(shí)現(xiàn)方式,這會(huì)帶來很多開發(fā)工作量,而且設(shè)計(jì)到核心代碼修改,可能會(huì)引入新的bug,導(dǎo)致系統(tǒng)不穩(wěn)定
利用多線程優(yōu)勢(shì),充分利用服務(wù)器多核的特性,采用多個(gè)IO線程來并行處理網(wǎng)絡(luò)請(qǐng)求。
很顯然,redis6.0采用的是多線程的方式。
無論是針對(duì)redis集群,還是針對(duì)單體架構(gòu),提高單機(jī)redis的處理速度和吞吐量目前看百利而無一害。
redis多線程
無論redis采用單線程還是多線程,其實(shí)每個(gè)請(qǐng)求的整體處理流程是一致的。
image
在整個(gè)流程中,讀取解析redis客戶端命令和返回客戶端結(jié)果兩個(gè)步驟分別對(duì)應(yīng)網(wǎng)絡(luò)數(shù)據(jù)的讀取和寫入,這兩個(gè)步驟對(duì)于redis來說,占用了大部分cpu時(shí)間,所以redis6.0多線程機(jī)制是針對(duì)這兩個(gè)步驟的。
為了直觀的更好了解整個(gè)流程,一般分為以下幾個(gè)步驟:
- 當(dāng)客戶端有新的socket連接時(shí),主線程會(huì)負(fù)責(zé)接收連接,并把socket放入全局的等待隊(duì)列中,當(dāng)主線程處理完讀事件之后,通過輪訓(xùn)的方式將這些連接分配給IO線程。
- 主線程會(huì)一直阻塞到IO線程讀取Socket并解析完畢,這個(gè)解析過程是多個(gè)IO線程并行處理的,所以會(huì)很快。
- 等到IO線程解析命令完成,主線程以單線程的方式來執(zhí)行這些命令,并把執(zhí)行結(jié)果寫入緩沖區(qū),然后阻塞的等待IO線程回寫socket數(shù)據(jù)
- IO線程讀取緩沖區(qū)結(jié)果數(shù)據(jù),把這些數(shù)據(jù)回寫socket,返回給客戶端。這個(gè)過程也是多個(gè)IO線程并行處理的,所以也會(huì)很快。
- 當(dāng)所有的IO線程回寫socket完畢,主線程回清空全局隊(duì)列,等待下次新的請(qǐng)求到來。
image
從以上步驟可以看出,IO線程只涉及到socket的讀和寫,而實(shí)際命令的執(zhí)行還是主線程以順序化的方式來執(zhí)行,這不僅僅是利用多線程的優(yōu)勢(shì),同時(shí)又保留了單線程的優(yōu)勢(shì),在命令執(zhí)行上不會(huì)產(chǎn)生多線程的一系列問題,比如加鎖帶來的耗時(shí),控制 key、lua、事務(wù),LPUSH/LPOP 等等的并發(fā)及線程安全問題。
“其實(shí)關(guān)于上面所說,我有一點(diǎn)沒想明白:假如有兩個(gè)socket,在單線程的時(shí)候,主線程可以保證優(yōu)先到來的socket命令數(shù)據(jù)優(yōu)先被執(zhí)行,但是在加入了多個(gè)IO線程并行解析過程之后,本來先接收的命令是否可以保證優(yōu)先執(zhí)行呢?希望大佬在評(píng)論區(qū)給予指點(diǎn)
redis 6.0默認(rèn)是禁用多線程機(jī)制的,如果需要開啟,請(qǐng)修改redis.conf:
- io-threads-do-reads yes
開啟時(shí)候還要設(shè)置線程數(shù),否則多線程機(jī)制是不生效的。至于設(shè)置多少個(gè)線程,官方有一個(gè)建議:4核的機(jī)器建議設(shè)置為2或3個(gè)線程,8核的建議設(shè)置為6個(gè)線程,線程數(shù)一定要小于機(jī)器核數(shù)。還需要注意的是,線程數(shù)并不是越大越好,官方認(rèn)為超過了8個(gè)基本就沒什么意義了。
redis6.0多線程測(cè)試
Redis 作者 antirez 在 RedisConf 2019 分享時(shí)曾提到:Redis 6 引入的多線程 IO 特性對(duì)性能提升至少是一倍以上。國(guó)內(nèi)也有大牛曾使用 unstable 版本在阿里云 esc 進(jìn)行過測(cè)試,GET/SET 命令在 4 線程 IO 時(shí)性能相比單線程是幾乎是翻倍了。
“Redis Server:阿里云 Ubuntu 18.04,8 CPU 2.5 GHZ, 8G 內(nèi)存,主機(jī)型號(hào) ecs.ic5.2xlarge Redis Benchmark Client:阿里云 Ubuntu 18.04,8 2.5 GHZ CPU, 8G 內(nèi)存,主機(jī)型號(hào) ecs.ic5.2xlarge
image
image
“這些性能驗(yàn)證的測(cè)試并沒有針對(duì)嚴(yán)謹(jǐn)?shù)难訒r(shí)控制和不同并發(fā)的場(chǎng)景進(jìn)行壓測(cè)。數(shù)據(jù)僅供驗(yàn)證參考而不能作為線上指標(biāo)。
“如果開啟多線程,至少要4核的機(jī)器,且Redis實(shí)例已經(jīng)占用相當(dāng)大的CPU耗時(shí)的時(shí)候才建議采用,否則使用多線程沒有意義。所以估計(jì)80%的公司開發(fā)人員看看就好。
寫在最后
redis6.0利用多線程的優(yōu)勢(shì)很好的解決了當(dāng)前redis的瓶頸問題,同時(shí)又保留了核心命令執(zhí)行過程單線程機(jī)制。不過將來單線程的命令執(zhí)行機(jī)制會(huì)不會(huì)是redis的瓶頸呢?這個(gè)留給大佬們?cè)谠u(píng)論區(qū)!!
最后提出一個(gè)我的疑問:redis6.0在啟用了多線程機(jī)制之后,那先后到達(dá)的socket數(shù)據(jù),在命令執(zhí)行的時(shí)候是否有可能不是按照數(shù)據(jù)到達(dá)的順序呢?redis6.0 是否有機(jī)制來保證這個(gè)順序呢?請(qǐng)大佬在留言區(qū)賜教!!
本文轉(zhuǎn)載自微信公眾號(hào)「架構(gòu)師修行之路」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系架構(gòu)師修行之路公眾號(hào)。


























