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