1、redis為什么選擇單線程
1.1、redis7單線程
Redis是單線程
主要是指Redis的網(wǎng)絡(luò)IO和鍵值對讀寫是由一個線程來完成的
Redis在處理客戶端的請求時包括獲取 (socket 讀)、解析、執(zhí)行、內(nèi)容返回 (socket 寫) 等都由一個順序串行的主線程處理,這就是所謂的“單線程”。
這也是Redis對外提供鍵值存儲服務(wù)的主要流程。
但Redis的其他功能,比如持久化RDB、AOF、異步刪除、集群數(shù)據(jù)同步等等,其實是由額外的線程執(zhí)行的。
Redis命令工作線程是單線程的,但是,整個Redis來說,是多線程的;
1.2、redis7單線程快
基于內(nèi)存操作:redis的所有數(shù)據(jù)都存在內(nèi)存中,因此所有的運算都是內(nèi)存級別的,性能比較高
數(shù)據(jù)結(jié)構(gòu)簡單:redis的數(shù)據(jù)結(jié)構(gòu)是專門設(shè)計的,而這些簡單的數(shù)據(jù)結(jié)構(gòu)的查找和操作的時間大部分復(fù)雜度都是O(1)
多路復(fù)用和非阻塞I/O:redis使用I/O多路復(fù)用功能來監(jiān)聽多個socket連接客戶端,這樣就可以使用一個線程來處理多個請求,減少線程切換帶來的開銷。同時也避免了I/O阻塞操作
避免上下文切換:因為是單線程模型,因此就避免了不必要的上下文切換和多線程競爭,省去了多線程切換帶來的時間和性能上的消耗,而且單線程不會導(dǎo)致死鎖問題的發(fā)生
簡單來說,Redis4.0之前一直采用單線程的主要原因有以下三個:
1 使用單線程模型是 Redis 的開發(fā)和維護更簡單,因為單線程模型方便開發(fā)和調(diào)試;
2 即使使用單線程模型也并發(fā)的處理多客戶端的請求,主要使用的是IO多路復(fù)用和非阻塞IO;
3 對于Redis系統(tǒng)來說,主要的性能瓶頸是內(nèi)存或者網(wǎng)絡(luò)帶寬而并非 CPU。
2、為啥加入多線程
1、單線程的問題
正常情況下使用 del 指令可以很快的刪除數(shù)據(jù)
而當(dāng)被刪除的 key 是一個非常大的對象時,例如時包含了成千上萬個元素的 hash 集合時,那么 del 指令就會造成 Redis 主線程卡頓。
這就是redis3.x單線程時代最經(jīng)典的故障,大key刪除的頭疼問題,
由于redis是單線程的,del bigKey .....
等待很久這個線程才會釋放,類似加了一個synchronized鎖,你可以想象高并發(fā)下,程序堵成什么樣子?
2、解決辦法
使用惰性刪除可以有效的避免redis卡頓的問題
比如當(dāng)我(Redis)需要刪除一個很大的數(shù)據(jù)時,因為是單線程原子命令操作,這就會導(dǎo)致 Redis 服務(wù)卡頓,
于是在 Redis 4.0 中就新增了多線程的模塊,當(dāng)然此版本中的多線程主要是為了解決刪除數(shù)據(jù)效率比較低的問題的。
unlink key
flushdb async
flushall async
把刪除工作交給了后臺的小弟(子線程)異步來刪除數(shù)據(jù)了。
因為Redis是單個主線程處理,redis之父antirez一直強調(diào)"Lazy Redis is better Redis".
而lazy free的本質(zhì)就是把某些cost(主要時間復(fù)制度,占用主線程cpu時間片)較高刪除操作,
從redis主線程剝離讓bio子線程來處理,極大地減少主線阻塞時間。從而減少刪除導(dǎo)致性能和穩(wěn)定性問題。
在redis4.0就引入了多個線程來實現(xiàn)數(shù)據(jù)的異步惰性刪除等功能
但是其處理讀寫請求的仍然只有一個線程,所以仍然算是狹義上的單線程
3、redis6/7多線程特性和IO多路復(fù)用
3.1、redis主要的性能瓶頸是內(nèi)存或者網(wǎng)絡(luò)帶寬而非CPU
3.2、redis6/7真正多線程登場
在Redis6/7中,非常受關(guān)注的第一個新特性就是多線程。
這是因為,Redis一直被大家熟知的就是它的單線程架構(gòu),雖然有些命令操作可以用后臺線程或子進(jìn)程執(zhí)行(比如數(shù)據(jù)刪除、快照生成、AOF重寫)。
但是,從網(wǎng)絡(luò)IO處理到實際的讀寫命令處理,都是由單個線程完成的。
隨著網(wǎng)絡(luò)硬件的性能提升,Redis的性能瓶頸有時會出現(xiàn)在網(wǎng)絡(luò)IO的處理上,也就是說,單個主線程處理網(wǎng)絡(luò)請求的速度跟不上底層網(wǎng)絡(luò)硬件的速度,
為了應(yīng)對這個問題:
采用多個IO線程來處理網(wǎng)絡(luò)請求,提高網(wǎng)絡(luò)請求處理的并行度,Redis6/7就是采用的這種方法。
但是,Redis的多IO線程只是用來處理網(wǎng)絡(luò)請求的,對于讀寫操作命令Redis仍然使用單線程來處理。
這是因為,Redis處理請求時,網(wǎng)絡(luò)處理經(jīng)常是瓶頸,通過多個IO線程并行處理網(wǎng)絡(luò)操作,可以提升實例的整體處理性能。
而繼續(xù)使用單線程執(zhí)行命令操作,就不用為了保證Lua腳本、事務(wù)的原子性,額外開發(fā)多線程互斥加鎖機制了(不管加鎖操作處理),這樣一來,Redis線程模型實現(xiàn)就簡單了
3.3、主線程和IO線程是協(xié)作完成請求處理
3.4、Unix網(wǎng)絡(luò)編程中的五種IO模型
Blocking IO 阻塞IO
NoneBlocking IO 非阻塞IO
IO Multiplexing IO多路復(fù)用
signal driven IO 信號驅(qū)動IO
asynchronous IO 異步IO
IO多路復(fù)用
一個服務(wù)端進(jìn)程可以同時處理多個套接字描述符
實現(xiàn)IO多路復(fù)用的模型有3種:select-->poll-->epoll三個階段來描述
模擬一個tcp服務(wù)器處理30個客戶socket。
假設(shè)你是一個監(jiān)考老師,讓30個學(xué)生解答一道競賽考題,然后負(fù)責(zé)驗收學(xué)生答卷,你有下面幾個選擇:
第一種選擇(輪詢):按順序逐個驗收,先驗收A,然后是B,之后是C、D。。。這中間如果有一個學(xué)生卡住,全班都會被耽誤,你用循環(huán)挨個處理socket,根本不具有并發(fā)能力。
第二種選擇(來一個new一個,1對1服務(wù)):你創(chuàng)建30個分身線程,每個分身線程檢查一個學(xué)生的答案是否正確。這種類似于為每一個用戶創(chuàng)建一個進(jìn)程或者線程處理連接。
第三種選擇(響應(yīng)式處理,1對多服務(wù)),你站在講臺上等,誰解答完誰舉手。這時C、D舉手,表示他們解答問題完畢,你下去依次檢查C、D的答案,然后繼續(xù)回到講臺上等。
此時E、A又舉手,然后去處理E和A。。。
這種就是IO復(fù)用模型。Linux下的select、poll和epoll就是干這個的。
將用戶socket對應(yīng)的文件描述符(FileDescriptor)注冊進(jìn)epoll,然后epoll幫你監(jiān)聽哪些socket上有消息到達(dá),這樣就避免了大量的無用操作。
此時的socket應(yīng)該采用非阻塞模式。這樣,整個過程只在調(diào)用select、poll、epoll這些調(diào)用的時候才會阻塞,
收發(fā)客戶消息是不會阻塞的,整個進(jìn)程或者線程就被充分利用起來,這就是事件驅(qū)動,所謂的reactor反應(yīng)模式。
在單個線程通過記錄跟蹤每一個Sockek(I/O流)的狀態(tài)來同時管理多個I/O流. 一個服務(wù)端進(jìn)程可以同時處理多個套接字描述符。
目的是盡量多的提高服務(wù)器的吞吐能力。
大家都用過nginx,nginx使用epoll接收請求,ngnix會有很多鏈接進(jìn)來, epoll會把他們都監(jiān)視起來,然后像撥開關(guān)一樣,誰有數(shù)據(jù)就撥向誰,然后調(diào)用相應(yīng)的代碼處理。
redis類似同理,這就是IO多路復(fù)用原理,有請求就響應(yīng),沒請求不打擾。
3.5、簡單說明
redis工作線程是單線程的
但是整個redis來說,是多線程的
4、redis7如何開啟多線程
1.設(shè)置io-thread-do-reads配置項為yes,表示啟動多線程。
2。設(shè)置線程個數(shù)。
關(guān)于線程數(shù)的設(shè)置,官方的建議是如果為 4 核的 CPU,建議線程數(shù)設(shè)置為 2 或 3,如果為 8 核 CPU 建議線程數(shù)設(shè)置為 6,線程數(shù)一定要小于機器核數(shù),線程數(shù)并不是越大越好。
5、總結(jié)
Redis自身出道就是優(yōu)秀,基于內(nèi)存操作、數(shù)據(jù)結(jié)構(gòu)簡單、多路復(fù)用和非阻塞 I/O、避免了不必要的線程上下文切換等特性,在單線程的環(huán)境下依然很快;
但對于大數(shù)據(jù)的 key 刪除還是卡頓厲害,因此在 Redis 4.0 引入了多線程unlink key/flushall async 等命令,主要用于 Redis 數(shù)據(jù)的異步刪除;
而在 Redis6/7中引入了 I/O 多線程的讀寫,這樣就可以更加高效的處理更多的任務(wù)了,Redis 只是將 I/O 讀寫變成了多線程
而命令的執(zhí)行依舊是由主線程串行執(zhí)行的,因此在多線程下操作 Redis 不會出現(xiàn)線程安全的問題。
Redis 無論是當(dāng)初的單線程設(shè)計,還是如今與當(dāng)初設(shè)計相背的多線程,目的只有一個:讓 Redis 變得越來越快。
審核編輯:湯梓紅
-
網(wǎng)絡(luò)
+關(guān)注
關(guān)注
14文章
7514瀏覽量
88627 -
多線程
+關(guān)注
關(guān)注
0文章
277瀏覽量
19921 -
Redis
+關(guān)注
關(guān)注
0文章
371瀏覽量
10846 -
單線程
+關(guān)注
關(guān)注
0文章
17瀏覽量
1769
發(fā)布評論請先 登錄
相關(guān)推薦
評論