您好,登錄后才能下訂單哦!
這篇文章的內(nèi)容主要圍繞怎么解析Redis6中的單線程和多線程模型進(jìn)行講述,文章內(nèi)容清晰易懂,條理清晰,非常適合新手學(xué)習(xí),值得大家去閱讀。感興趣的朋友可以跟隨小編一起閱讀吧。希望大家通過(guò)這篇文章有所收獲!
如果單純的說(shuō)redis是單線程或多線程,這個(gè)回答肯定不嚴(yán)謹(jǐn),不同版本使用的線程模型是不一樣的?!鞠嚓P(guān)推薦:Redis視頻教程】
版本3.x ,最早版本,也就是大家口口相傳的redis是單線程。
版本4.x,嚴(yán)格意義來(lái)說(shuō)也不是單線程,而是負(fù)責(zé)處理客戶端請(qǐng)求的線程是單線程,但是 開始加了點(diǎn)多線程的東西(異步刪除)
。
最新版本的6.0.x后, 告別了大家印象中的單線程,用一種全新的多線程
來(lái)解決問(wèn)題。
2.1 單線程真實(shí)含義
主要是指Redis的網(wǎng)絡(luò)IO和鍵值對(duì)讀寫是由一個(gè)線程來(lái)完成的,Redis在處理客戶端的請(qǐng)求時(shí)包括獲取 (socket 讀)、解析、執(zhí)行、內(nèi)容返回 (socket 寫) 等都由一個(gè)順序串行的主線程處理,這就是所謂的“單線程”。這也是Redis對(duì)外提供鍵值存儲(chǔ)服務(wù)的主要流程。
但Redis的其他功能, 比如持久化、異步刪除、集群數(shù)據(jù)同步等等,其實(shí)是由額外的線程
執(zhí)行的。
可以這么說(shuō),Redis工作線程是單線程的。但是,整個(gè)Redis來(lái)說(shuō),是多線程的
;
2.2 單線程性能快原因
Redis 3.x 單線程時(shí)代但是性能很快的主要原因
:
基于內(nèi)存操作:所有數(shù)據(jù)都存在內(nèi)存中,因此所有的運(yùn)算都是內(nèi)存級(jí)別的
數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單:Redis的數(shù)據(jù)結(jié)構(gòu)是專門設(shè)計(jì)的,而這些簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu)的查找和操作的時(shí)間大部分復(fù)雜度都是o(1)
多路復(fù)用和費(fèi)阻塞IO:使用IO多路復(fù)用功能監(jiān)聽多個(gè)socket連接客戶端,這樣就可以使用一個(gè)線程連接處理多個(gè)請(qǐng)求,減少線程切換帶來(lái)的開銷,同時(shí)避免IO阻塞操作
避免上下文切換:因?yàn)槭菃尉€程模型,就可以避免不必要的上先文切換和多線程競(jìng)爭(zhēng),這樣可以省去多線程切換帶來(lái)的時(shí)間和性能上的消耗,而且單線程不會(huì)導(dǎo)致死鎖問(wèn)題的發(fā)生
2.3 采用單線程原因
Redis 是基于內(nèi)存操作的, 因此他的瓶頸可能是機(jī)器的內(nèi)存或者網(wǎng)絡(luò)帶寬而并非 CPU
,既然 CPU 不是瓶頸,那么自然就采用單線程的解決方案了,況且使用多線程比較麻煩。 但是在 Redis 4.0 中開始支持多線程了,例如后臺(tái)刪除等功能
。
簡(jiǎn)單來(lái)說(shuō),Redis 4.0 之前一直采用單線程的主要原因有以下三個(gè):
使用單線程模型是 Redis 的開發(fā)和維護(hù)更簡(jiǎn)單,因?yàn)閱尉€程模型方便開發(fā)和調(diào)試;多線程模型雖然在某些方面表現(xiàn)優(yōu)異,但是它卻引入了程序執(zhí)行順序的不確定性,帶來(lái)了并發(fā)讀寫的一系列問(wèn)題,增加了系統(tǒng)復(fù)雜度、同時(shí)可能存在線程切換、甚至加鎖解鎖、死鎖造成的性能損耗。Redis通過(guò)AE事件模型以及IO多路復(fù)用等技術(shù),處理性能非常高,因此沒(méi)有必要使用多線程。單線程機(jī)制使得 Redis 內(nèi)部實(shí)現(xiàn)的復(fù)雜度大大降低,Hash 的惰性 Rehash、Lpush 等等 “線程不安全” 的命令都可以無(wú)鎖進(jìn)行。
即使使用單線程模型也并發(fā)的處理多客戶端的請(qǐng)求,主要使用的是多路復(fù)用和非阻塞 IO;
對(duì)于 Redis 系統(tǒng)來(lái)說(shuō), 主要的性能瓶頸是內(nèi)存或者網(wǎng)絡(luò)帶寬而并非 CPU
。
3.1 引入多線程原因
既然單線程那么好,為啥又要引入多線程?
單線程也有自己的煩惱,比如大key刪除問(wèn)題:
正常情況下使用 del 指令可以很快的刪除數(shù)據(jù),而當(dāng)被刪除的 key 是一個(gè)非常大的對(duì)象時(shí),例如時(shí)包含了成千上萬(wàn)個(gè)元素的 hash 集合時(shí),那么 del 指令就會(huì)造成 Redis 主線程卡頓。
因此,在 Redis 4.0 中就新增了多線程的模塊,當(dāng)然此版本中的多線程主要是為了解決刪除數(shù)據(jù)效率比較低的問(wèn)題??梢酝ㄟ^(guò)惰性刪除有效避免Redis卡頓問(wèn)題(大key刪除等問(wèn)題),步驟如下:
unlink key
: 與DEL一樣刪除key功能的lazy free實(shí)現(xiàn),唯一不同是,UNLINK在刪除集合類型鍵時(shí),如果集合鍵的元素個(gè)數(shù)大于64個(gè),主線程中只是把待刪除鍵從數(shù)據(jù)庫(kù)字典中摘除,會(huì)把真正的內(nèi)存釋放操作,給單獨(dú)的bio來(lái)操作。如果元素個(gè)數(shù)較少(少于64個(gè))或者是String類型,也會(huì)在主線程中直接刪除。
flushall/flushdb async
: 對(duì)于清空數(shù)據(jù)庫(kù)命令flushall/flushdb,添加了async異步清理選項(xiàng),使得redis在清空數(shù)據(jù)庫(kù)時(shí)都是異步操作。實(shí)現(xiàn)邏輯是為數(shù)據(jù)庫(kù)新建一個(gè)新的空的字典,把原有舊的數(shù)據(jù)庫(kù)字典給后臺(tái)線程來(lái)逐一刪除其中的數(shù)據(jù),釋放內(nèi)存。
把刪除工作交給了后臺(tái)子進(jìn)程異步刪除數(shù)據(jù)
因?yàn)镽edis是單個(gè)主線程處理,redis之父antirez一直強(qiáng)調(diào)"Lazy Redis is better Redis". 而lazy free
的本質(zhì)就是把某些cost(主要時(shí)間復(fù)制度,占用主線程cpu時(shí)間片)較高刪除操作
, 從redis主線程剝離讓bio子線程來(lái)處理,極大地減少主線阻塞時(shí)間。從而減少刪除導(dǎo)致性能和穩(wěn)定性問(wèn)題。
Redis 4.0 就引入了多個(gè)線程來(lái)實(shí)現(xiàn)數(shù)據(jù)的異步惰性刪除等功能,但是其處理讀寫請(qǐng)求的仍然只有一個(gè)線程,所以仍然算是狹義上的單線程。
從上一小結(jié)分析:Redis的主要性能瓶頸是內(nèi)存或網(wǎng)絡(luò)帶寬而并非CPU。內(nèi)存問(wèn)題比較好解決,因此Redis的瓶頸原因?yàn)榫W(wǎng)絡(luò)IO。接下來(lái),引入多線程模型。
3.2 多線程工作原理
I/O 的讀和寫本身是堵塞的,比如當(dāng) socket 中有數(shù)據(jù)時(shí),Redis 會(huì)通過(guò)調(diào)用先將數(shù)據(jù)從內(nèi)核態(tài)空間拷貝到用戶態(tài)空間,再交給 Redis 調(diào)用,而這個(gè)拷貝的過(guò)程就是阻塞的,當(dāng)數(shù)據(jù)量越大時(shí)拷貝所需要的時(shí)間就越多,而這些操作都是基于單線程完成的。
在 Redis 6.0 中新增了多線程的功能來(lái)提高 I/O 的讀寫性能,他的主要實(shí)現(xiàn)思路是將主線程的 IO 讀寫任務(wù)拆分給一組獨(dú)立的線程去執(zhí)行,這樣就可以使多個(gè) socket 的讀寫可以并行化了,采用多路 I/O 復(fù)用技術(shù)可以讓單個(gè)線程高效的處理多個(gè)連接請(qǐng)求(盡量減少網(wǎng)絡(luò)IO的時(shí)間消耗),將最耗時(shí)的Socket的讀取、請(qǐng)求解析、寫入單獨(dú)外包出去,剩下的命令執(zhí)行仍然由主線程串行執(zhí)行并和內(nèi)存的數(shù)據(jù)交互。
結(jié)合上圖可知,將網(wǎng)絡(luò)數(shù)據(jù)讀寫、請(qǐng)求協(xié)議解析通過(guò)多個(gè)IO線程的來(lái)處理,對(duì)于真正的命令執(zhí)行來(lái)說(shuō),仍然使用主線程操作(線程安全),是個(gè)不錯(cuò)的折中辦法。因此,對(duì)于整個(gè)Redis來(lái)說(shuō)是多線程的,但是對(duì)于工作線程(命令執(zhí)行)仍舊是單線程
。
核心流程大概如下:
流程簡(jiǎn)述如下:
主線程獲取 socket 放入等待列表
將 socket 分配給各個(gè) IO 線程(并不會(huì)等列表滿)
主線程阻塞等待 IO 線程(多線程)
讀取 socket 完畢
主線程執(zhí)行命令 - 單線程
(如果命令沒(méi)有接收完畢,會(huì)等 IO 下次繼續(xù))
主線程阻塞等待 IO 線程(多線程)
將數(shù)據(jù)回寫 socket 完畢(一次沒(méi)寫完,會(huì)等下次再寫)
解除綁定,清空等待隊(duì)列
特點(diǎn)如下:
IO 線程要么同時(shí)在讀 socket,要么同時(shí)在寫,不會(huì)同時(shí)讀或?qū)?/p>
IO 線程只負(fù)責(zé)讀寫 socket 解析命令,不負(fù)責(zé)命令處理(主線程串行執(zhí)行命令)
IO 線程數(shù)可自行配置(目前代碼限制上限為 512,默認(rèn)為 1(關(guān)閉此功能))
經(jīng)過(guò)有心人士的壓測(cè),目前性能能提高 1 倍以上。
疑問(wèn)1:等待列表不滿 一直阻塞不處理嗎?
回復(fù):阻塞時(shí)檢測(cè)的是,IO 線程是否還有任務(wù)。等處理完了才繼續(xù)往下。這些任務(wù)是在執(zhí)行時(shí)添加的,如果 任務(wù)數(shù)< 線程數(shù),那有些線程就拿不到任務(wù),它的待處理任務(wù)就是 0 。分配了任務(wù)的線程,在處理好 IO 事件后,pending 就會(huì)清零,沒(méi)拿到任務(wù)的線程 pending 本來(lái)就是 0,所以不會(huì)阻塞。 這塊還是有點(diǎn)疑問(wèn),哪位大佬可以解釋下(評(píng)論哈)?
3.4 默認(rèn)開啟多線程嗎?
在Redis6.0中, 多線程機(jī)制默認(rèn)是關(guān)閉的
,如果需要使用多線程功能,需要在redis.conf中完成兩個(gè)設(shè)置。
設(shè)置io-thread-do-reads配置項(xiàng)為yes,表示啟動(dòng)多線程。
設(shè)置線程個(gè)數(shù)。關(guān)于線程數(shù)的設(shè)置,官方的建議是如果為 4 核的 CPU,建議線程數(shù)設(shè)置為 2 或 3, 如果為 8 核 CPU 建議線程數(shù)設(shè)置為 6
,線程數(shù)一定要小于機(jī)器核數(shù),線程數(shù)并不是越大越好。
Redis自身出道就是優(yōu)秀,基于內(nèi)存操作、數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單、多路復(fù)用和非阻塞 I/O、避免了不必要的線程上下文切換等特性,在單線程的環(huán)境下依然很快;
但對(duì)于大數(shù)據(jù)的 key 刪除還是卡頓厲害,因此在 Redis 4.0 引入了多線程unlink key/flushall async 等命令,主要用于 Redis 數(shù)據(jù)的異步刪除;
而在 Redis 6.0 中引入了 I/O 多線程的讀寫,這樣就可以更加高效的處理更多的任務(wù)了, Redis 只是將 I/O 讀寫變成了多線程
,而 命令的執(zhí)行依舊是由主線程串行執(zhí)行的
,因此在多線程下操作 Redis 不會(huì)出現(xiàn)線程安全的問(wèn)題
。
Redis 無(wú)論是當(dāng)初的單線程設(shè)計(jì),還是如今與當(dāng)初設(shè)計(jì)相背的多線程,目的只有一個(gè):讓 Redis 變得越來(lái)越快。
感謝你的閱讀,相信你對(duì)“怎么解析Redis6中的單線程和多線程模型”這一問(wèn)題有一定的了解,快去動(dòng)手實(shí)踐吧,如果想了解更多相關(guān)知識(shí)點(diǎn),可以關(guān)注億速云網(wǎng)站!小編會(huì)繼續(xù)為大家?guī)?lái)更好的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。