一文帶你快速了解Redis中的線程IO模型

redis是單線程的,但為什么那么快尼?原因之一就是redis使用非阻塞io與多路復(fù)用處理大量的客戶端連接。下面本篇文章就來(lái)帶大家了解一下redis中的線程io模型,希望對(duì)大家有所幫助!

一文帶你快速了解Redis中的線程IO模型

Redis是一個(gè)單線程的應(yīng)用程序,NodeJs、Nginx都是單線程,它們都屬于服務(wù)器高性能的典范。【相關(guān)推薦:Redis視頻教程

Redis之所以是單線程還能這么快的原因:

其一是因?yàn)樗械臄?shù)據(jù)都在內(nèi)存當(dāng)中,所有的運(yùn)算都是內(nèi)存級(jí)別的運(yùn)算,所以使用redis時(shí),要注意時(shí)間復(fù)雜度為O(n)的指令,因?yàn)槭菃尉€程的,如果數(shù)據(jù)量太大,會(huì)讓其他指令被阻塞等待;

其二是因?yàn)閞edis使用非阻塞IO與多路復(fù)用處理大量的客戶端連接。

非阻塞IO

當(dāng)我們使用套接字的讀寫方法時(shí),默認(rèn)是阻塞的,

即調(diào)用read方法傳遞一個(gè)參數(shù)n,表示最多讀取n個(gè)字節(jié)后返回,如果一個(gè)字節(jié)都沒有,線程就會(huì)在read方法這里持續(xù)等待,直到有數(shù)據(jù)過來(lái)或者連接被關(guān)閉,read方法此時(shí)返回,線程才能執(zhí)行下面的邏輯,

write方法一般不會(huì)阻塞,除非內(nèi)核為套接字分配的寫緩沖區(qū)滿了,write方法才會(huì)阻塞,一直到緩存區(qū)中有空間閑出來(lái)。

下圖是套接字讀寫的細(xì)節(jié)流程。

一文帶你快速了解Redis中的線程IO模型

非阻塞IO在使用套接字時(shí)提供了一個(gè)選項(xiàng)Non_Blocking,當(dāng)這個(gè)選項(xiàng)打開時(shí),讀寫方法不會(huì)阻塞,而是能讀多少讀多少,能寫多少寫多少,

能讀多少取決與內(nèi)核為套接字分配的讀緩沖區(qū)的數(shù)據(jù)字節(jié)數(shù),能寫多少取決于內(nèi)核為套接字寫緩沖區(qū)分配的數(shù)據(jù)字節(jié)數(shù),

讀寫方法都會(huì)通過返回值告訴程序讀寫了多少字節(jié)數(shù)。

非阻塞IO意味著讀寫時(shí),線程不必再被阻塞著,讀寫可以瞬間完成,線程可以繼續(xù)往下做別的事情。

多路復(fù)用(事件輪詢)

非阻塞IO雖然很快,但是也帶來(lái)一個(gè)問題,線程讀數(shù)據(jù),讀了一部分就返回了,沒有讀完,剩下的數(shù)據(jù)何時(shí)繼續(xù)讀?,寫數(shù)據(jù),緩沖區(qū)滿了,沒有寫完,剩下的數(shù)據(jù)何時(shí)繼續(xù)寫?

當(dāng)可以繼續(xù)讀或者可以繼續(xù)寫時(shí),應(yīng)該給應(yīng)用程序一個(gè)通知,告訴應(yīng)用程序可以繼續(xù)讀或者繼續(xù)寫,事件輪詢API就是用來(lái)處理這個(gè)問題的。

select

操作系統(tǒng)提供了一個(gè)select函數(shù)給用戶程序,輸入是讀寫描述符列表 read_fds & write_fds,輸出是與之對(duì)應(yīng)的可讀可寫事件,

同時(shí)還提供了timeout參數(shù),線程最多等待timeout的時(shí)間,在這期間有事件過來(lái),方法立刻返回,線程往下處理,如果超過timeout時(shí)間,方法也會(huì)返回,

如果拿到事件了,線程即可挨個(gè)處理相應(yīng)的事件,處理完了以后繼續(xù)調(diào)用 select api 輪詢,所以該線程其實(shí)是一個(gè)死循環(huán),不停的 select,不停的處理,來(lái)回這樣,這個(gè)死循環(huán)被稱之為事件循環(huán),一個(gè)循環(huán)即一個(gè)周期。

一文帶你快速了解Redis中的線程IO模型

事件循環(huán)偽代碼:

while?True ????read_events,?write_events?=?select(read_fds,?write_fds,?timeout) ????for?event?in?read_events: ????????handle_read(event.fd) ????for?event?in?write_events: ????????handle_write(event.fd) ????handle_others()?#?做其他的邏輯處理,處理定時(shí)任務(wù)等等

通過select函數(shù)我們可以處理多個(gè)通道描述符的讀寫事件,所以將select這類的系統(tǒng)函數(shù)調(diào)用稱之為多路復(fù)用API,

現(xiàn)代操作系統(tǒng)的多路復(fù)用API已經(jīng)不使用select系統(tǒng)調(diào)用,改用epoll(linux)和kqueue(FreeBSD、macosx),

select的性能在描述符變多時(shí)會(huì)變得很差,epoll與select使用起來(lái)略有差異,不過都可以用上面的偽代碼理解,都是當(dāng)描述符發(fā)生事件時(shí),循環(huán)對(duì)描述符的事件做出處理,

serversocket對(duì)象的讀操作是指調(diào)用accept接受客戶端新連接,何時(shí)有連接來(lái)臨,也是通過select調(diào)用的讀事件通知的。

Java中的NIO技術(shù)就是事件輪詢,其他語(yǔ)言也有這個(gè)技術(shù)。

指令隊(duì)列

Redis為每一個(gè)客戶端套接字關(guān)聯(lián)一個(gè)指令隊(duì)列,客戶端發(fā)來(lái)的指令通過隊(duì)列進(jìn)行先進(jìn)先出的順序處理。

響應(yīng)隊(duì)列

同樣Redis返回的結(jié)果也通過為每個(gè)客戶端關(guān)聯(lián)的一個(gè)隊(duì)列返回,如果隊(duì)列為空,則暫時(shí)不需要去獲取寫事件,

此時(shí)會(huì)將該客戶端描述符從write_fds里移除,等隊(duì)列有數(shù)據(jù)的時(shí)候,再將描述符放進(jìn)去,這樣可以避免select系統(tǒng)調(diào)用返回寫事件時(shí),發(fā)現(xiàn)沒數(shù)據(jù)可寫,造成空輪詢、無(wú)用輪詢,對(duì)機(jī)器CPU的消耗。

定時(shí)任務(wù)

服務(wù)器不單要響應(yīng)IO事件,有些其他的事情也需要處理,例如應(yīng)用程序自身的定時(shí)任務(wù),如果線程阻塞在select調(diào)用上,等待select的返回,這會(huì)造成有些定時(shí)任務(wù)到期了,卻沒有執(zhí)行,

Redis的定時(shí)任務(wù)記錄在一個(gè)稱為 最小堆 的數(shù)據(jù)結(jié)構(gòu)中,這個(gè)堆中,最快要執(zhí)行的任務(wù)排在最上方,每個(gè)循環(huán)周期里,redis會(huì)對(duì)堆中已經(jīng)到時(shí)間點(diǎn)的任務(wù)進(jìn)行處理,

處理完畢后,將堆中即將要執(zhí)行的任務(wù)還需要的時(shí)間記錄下來(lái),再次調(diào)用select時(shí),這個(gè)時(shí)間就是timeout的值,在這期間內(nèi)不會(huì)有其他任務(wù)需要執(zhí)行了,redis可以放心的最多阻塞這么久,然后到時(shí)間后進(jìn)行相應(yīng)的處理。

NodeJs和Nginx的事件處理原理和Redis也是類似的形式。

更多編程相關(guān)知識(shí),請(qǐng)?jiān)L問:Redis視頻教程!!

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊15 分享