Redis 6.0多線程介紹

0. 背景

Redis作為一個(gè)基于內(nèi)存的緩存系統(tǒng),一直以高性能著稱,
在單線程處理情況下,讀速度可達(dá)到11萬次/s,寫速度達(dá)到8.1萬次/s。

Redis6.0之前為什么一直不使用多線程?

官方曾做過類似問題的回復(fù):使用Redis時(shí),幾乎不存在CPU成為瓶頸的情況, Redis主要受限于內(nèi)存和網(wǎng)絡(luò)。

但是,單線程的設(shè)計(jì)也給Redis帶來一些問題:

  • 只能使用CPU一個(gè)核
  • 如果刪除的鍵過大(eg: Set類型中有上百萬個(gè)對象),會導(dǎo)致服務(wù)端阻塞好幾秒
  • QPS難再提高

針對上面問題,Redis在4.0版本以及6.0版本分別引入了Lazy Free以及多線程IO,逐步向多線程過渡。

?

1. Redis單線程架構(gòu)原理

Redis單線程是如何支持客戶端并發(fā)請求的呢?

Redis服務(wù)器是一個(gè)事件驅(qū)動程序,服務(wù)器需要處理以下兩類事件:

  • 文件事件

Redis服務(wù)器通過套接字與客戶端(或者其他Redis服務(wù)器)進(jìn)行連接。
文件事件就是服務(wù)器對套接字操作的抽象。

服務(wù)器與客戶端的通信會產(chǎn)生相應(yīng)的文件事件,而服務(wù)器則通過監(jiān)聽并處理這些事件來完成一系列網(wǎng)絡(luò)通信操作。
(eg: 連接accept,read,write,close等)


  • 時(shí)間事件

Redis服務(wù)器中的一些操作(eg: serverCron函數(shù))需要在給定的時(shí)間點(diǎn)執(zhí)行。
時(shí)間事件就是服務(wù)器對這類定時(shí)操作的抽象
(eg: 過期鍵清理,服務(wù)狀態(tài)統(tǒng)計(jì)等)


Redis將文件事件和時(shí)間事件進(jìn)行抽象,時(shí)間輪詢器會監(jiān)聽I/O事件表:
一旦有文件事件就緒,Redis就會優(yōu)先處理文件事件,
接著處理時(shí)間事件。
在上述所有事件處理上,Redis都是以單線程形式處理,所以說Redis是單線程的。
處理過程見下圖


單線程處理事件

Redis基于Reactor模式開發(fā)了自己的I/O事件處理器,也就是文件事件處理器。
Redis在I/O事件處理上,采用了I/O多路復(fù)用技術(shù),同時(shí)監(jiān)聽多個(gè)套接字,
并為套接字關(guān)聯(lián)不同的事件處理函數(shù),通過一個(gè)線程實(shí)現(xiàn)了多客戶端并發(fā)處理。
處理過程見下圖


基于I/O多路復(fù)用技術(shù)的文件事件處理

上述的設(shè)計(jì),在數(shù)據(jù)處理上避免了加鎖操作,既使得實(shí)現(xiàn)上足夠簡潔,也保證了其高性能。
當(dāng)然,Redis單線程只是指其在事件處理上,實(shí)際上,Redis也并不是單線程的,比如生成RDB文件,就會fork一個(gè)子進(jìn)程來實(shí)現(xiàn)。

?

2. Redis 4.0的Lazy Free機(jī)制

背景:
客戶端向Redis發(fā)送一條耗時(shí)較長的命令,比如刪除一個(gè)含有上百萬對象的Set鍵,或者執(zhí)行flushdb,flushall操作,
Redis服務(wù)器需要回收大量的內(nèi)存空間,導(dǎo)致服務(wù)器卡住好幾秒,對負(fù)載較高的緩存系統(tǒng)而言將會是個(gè)災(zāi)難。

為了解決這個(gè)問題,在Redis 4.0版本引入了Lazy Free,將慢操作異步化,這也是在事件處理上向多線程邁進(jìn)了一步。

將大鍵的刪除操作異步化,采用非阻塞刪除(對應(yīng)命令UNLINK)。
大鍵的空間回收交由單獨(dú)線程實(shí)現(xiàn),主線程只做關(guān)系解除,可以快速返回,繼續(xù)處理其他事件,避免服務(wù)器長時(shí)間阻塞。

意義:
Redis在4.0版本引入了Lazy Free,自此Redis有了一個(gè)Lazy Free線程專門用于大鍵的回收。
同時(shí),也去掉了聚合類型的共享對象,這為多線程帶來可能。
這為Redis在6.0版本實(shí)現(xiàn)了多線程I/O打下了基礎(chǔ)。

?

3. Redis 6.0多線程實(shí)現(xiàn)機(jī)制

Redis 6.0的多線程并未將事件處理改成多線程,而是在I/O上。
因?yàn)?,如果把事件處理改成多線程,不但會導(dǎo)致鎖競爭,而且會有頻繁的上下文切換,
即使用分段鎖來減少競爭,對Redis內(nèi)核也會有較大改動,性能也不一定有明顯提升。

流程簡述如下:

1、主線程負(fù)責(zé)接收建立連接請求,獲取 socket 放入全局等待讀處理隊(duì)列
2、主線程處理完讀事件之后,通過 RR(Round Robin) 將這些連接分配給這些 IO 線程
3、主線程阻塞等待 IO 線程讀取 socket 完畢
4、主線程通過單線程的方式執(zhí)行請求命令,請求數(shù)據(jù)讀取并解析完成,但并不執(zhí)行
5、主線程阻塞等待 IO 線程將數(shù)據(jù)回寫 socket 完畢
6、解除綁定,清空等待隊(duì)列

見下圖


Redis I/O多線程流程

?

4. Redis6.0多線程的設(shè)置

Redis6.0的多線程默認(rèn)是禁用的,只使用主線程。
如需開啟需要修改redis.conf配置文件:

io-threads-do-reads yes

開啟多線程后,還需要設(shè)置線程數(shù),否則是不生效的。
同樣修改redis.conf配置文件:

io-threads  4
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容