和杠精聊Redis多線程
周末被一位小同學(xué)憋的很窩火。他要和我探討一下,redis到底是多線程的還是單線程的。這個(gè)問(wèn)題本來(lái)比較好解釋,但我遇到的卻是一個(gè)杠精。
答案是顯而易見(jiàn)的:redis6,逃不過(guò)真香定理,引入了多線程;而在redis6之前,卻是單線程的。
也就是說(shuō),這不是一個(gè)是和否的問(wèn)題,還涉及到第二維度的版本參與。
可是,這位同學(xué)要打我的臉。不知道小姐姐的臉皮很嫩么?摸不得。
“照你的邏輯,redis5是單線程的了?”
“是的。”
“那下面這張截圖是怎么回事?”
同學(xué)甩給我一張圖,并送來(lái)一個(gè)鄙視的眼神。
“使用top -Hp 查看。redis5有4個(gè)線程。該怎么解釋?”
這個(gè)問(wèn)題,我也不知道怎么跟他解釋。使用top命令去觀測(cè),redis5肯定是多線程的,比如bgsave,aof等,肯定要開(kāi)啟一個(gè)線程去操作,否則早就炸了。
按照這個(gè)邏輯去說(shuō),redis就從來(lái)沒(méi)有單進(jìn)程過(guò)。看著這張圖,我陷入了無(wú)盡的憂愁。
“Redis是否是單進(jìn)程,主要是針對(duì)Redis的讀寫(xiě)操作來(lái)說(shuō)的”。但這句話對(duì)于杠精并沒(méi)有什么信服力。
“寫(xiě)程序要嚴(yán)謹(jǐn),你們這些人都太不嚴(yán)謹(jǐn)了。多線程就是多線程,你應(yīng)該問(wèn)'redis的讀寫(xiě)操作到底是不是多線程的'”。
我問(wèn)你個(gè)大頭鬼。我并不想再和他交流,因?yàn)槲覟樽约旱牟W(xué)感到無(wú)地自容。
但他接下來(lái)的一個(gè)問(wèn)題,卻讓我陷入了真正的沉思。
1. redis的多線程有多快?
redis的多線程到底有什么性能提升呢?
官方的說(shuō)法是:possible to easily speedup two times。可能會(huì)比較容易的提升到兩倍速度。
我英文不太好,對(duì)這種英文的修飾感到很迷惑。既然easily了,為什么還有possible。two times,到底是提升了2倍,還是提升到2倍。
官方說(shuō),到底能夠提升多少,還要看硬件的能力。
官方推薦,只有你的CPU核數(shù),達(dá)到4個(gè)的時(shí)候,才有必要試一試這個(gè)多線程的Feature。
不要用土豪的眼睛盯著我,這種4core的配置,已經(jīng)打死了大多數(shù)公司了。所以Redis貼心的把多線程功能是關(guān)閉的。(好像有點(diǎn)語(yǔ)病)
我只能求助那些在一線的前同事們。他們有沒(méi)有在生產(chǎn)環(huán)境,用上這劃時(shí)代的多線程Redis6x呢?
結(jié)果很令我滿意,沒(méi)有!
其中有一個(gè)回復(fù)我特別滿意。他說(shuō):“你竟然在問(wèn)一個(gè)停留在JDK1.6的我,跑著Windows版本Redis的我,是否用到了Redis6。我還在用著Redis3呢。”
另外一個(gè)回復(fù)我感到更滿意,他說(shuō):“滾!”
2. 怎么用?
新技術(shù)肯定是要吹捧一下的,否則沒(méi)人實(shí)踐踩坑,作為追隨者就只能吃翔。
多線程在理論上,肯定是會(huì)有性能提升的。一個(gè)爸爸賺錢(qián)和2個(gè)爸爸賺錢(qián),效果自然不一樣,只是苦了媽媽了。
Redis6的多線程開(kāi)啟,需要配置一個(gè)參數(shù)。
- io-threads 4
當(dāng)開(kāi)啟之后,只有出流量使用多線程,如果你想要入流量也走多線程,那也可以配置以下參數(shù)。
- io-threads-do-reads yes
就這么兩個(gè)參數(shù),可以看到現(xiàn)在的redis多線程,還是稍顯寒磣了一些。
我們把它開(kāi)啟之后,仍然使用top -Hp 查看相關(guān)進(jìn)程,可以看到多了3個(gè)io_thd進(jìn)程。
這部分邏輯,是在networking.c種實(shí)現(xiàn)的。這個(gè)文件已經(jīng)達(dá)到了3k多行,也是夠龐大的了。
3. Redis為什么又搞多線程了
使用redis-benchmark測(cè)試,單機(jī)單核的吞吐量,能夠達(dá)到10w+。
1秒是1000000000納秒,單次內(nèi)存操作大約是100納秒左右,那內(nèi)存操作可以達(dá)到1000w/s的速度。那Redis的瓶頸在哪里呢?
使用perf進(jìn)行追蹤,可以發(fā)現(xiàn)它的耗時(shí),主要是體現(xiàn)在sys_write系統(tǒng)調(diào)用上,也就是向socket寫(xiě)數(shù)據(jù)。
既然瓶頸找到了,那就把它優(yōu)化掉。redis選擇的方式是使用多線程。
我使用benchmark測(cè)試了一下,4core的機(jī)器,CPU跑滿的時(shí)候,QPS達(dá)到了16w,并沒(méi)有翻倍(相對(duì)于單核的9w/s)。
- benchmark 6379 clients 32
- 164519.20 requests per second
- 165411.09 requests per second
用這么強(qiáng)的硬件,獲得這樣有限的性能提升,差強(qiáng)人意。
這就不難解釋為什么現(xiàn)在實(shí)踐的人那么少。出了因?yàn)樾?,還是不夠吸引人。
畢竟,4core的機(jī)器,我部署上3臺(tái)redis cluster的實(shí)例,理論上會(huì)提升三倍呢。
redis配置文件里,有不少內(nèi)容在注釋這個(gè)新特性。
4. 怎么實(shí)現(xiàn)?
如圖,一次redis請(qǐng)求,要建立連接,然后獲取操作的命令,然后執(zhí)行命令,最后將響應(yīng)的結(jié)果寫(xiě)到socket上。
在redis的多線程模式下,獲取、解析命令,以及輸出結(jié)果著兩個(gè)過(guò)程,可以配置成多線程執(zhí)行的,因?yàn)樗吘故俏覀兌ㄎ坏降闹饕臅r(shí)點(diǎn)。
但命令的執(zhí)行,也就是內(nèi)存操作,依然是單線程運(yùn)行的。
這種設(shè)計(jì)造成了一個(gè)特性。
redis現(xiàn)在依然沒(méi)有多線程的鎖競(jìng)爭(zhēng)和線程安全問(wèn)題,因?yàn)樗臄?shù)據(jù)讀取這一步驟,仍然是單線程的,要排隊(duì)運(yùn)行。一些耗時(shí)的操作,比如keys *,hgetall等,仍然要注意。
redis并不是傳統(tǒng)的reactor模型,說(shuō)實(shí)話很多東西硬套概念的話肯定只能鉆進(jìn)個(gè)頭去漏出個(gè)尾巴。它也并不是master,worker這種干干凈凈的類似于memcached的模型,因?yàn)樗衙顖?zhí)行操作給抽取出來(lái)了。其中緣由,看上面這張圖就夠了。
End
那么,下一個(gè)吸引杠精的問(wèn)題難題來(lái)了:在這種多線程應(yīng)用場(chǎng)景下,redis算是I/O密集型,還是計(jì)算密集型呢?
或許,如果redis多線程中,無(wú)處不在的輪詢,屬于“計(jì)算”的話,它算是一個(gè)計(jì)算密集型應(yīng)用吧。