https://draveness.me/redis-io-multiplexing
redis是一個單線程的程序,在IO方面由于是單線程亮隙,所以為了提高IO的效率垢夹,使用了多路復(fù)用的IO模型。
Blocking I/O
最原始的Blocking IO為阻塞的IO煤裙。讀寫操作等待用戶輸入或輸出都是阻塞的硼砰,每次的IO都是要等待的
當(dāng)使用 read 或者 write 對某一個文件描述符(File Descriptor 以下簡稱 FD)题翰,如果當(dāng)前 FD 不可讀或不可寫,整個 Redis 服務(wù)就不會對其它的操作作出響應(yīng)豹障,導(dǎo)致整個服務(wù)不可用血公。
模型圖如下(這種圖的風(fēng)格看著好舒服袄勰А):
這種模型在每次有一個FD來的時候會阻塞住,這時候FD的數(shù)據(jù)在系統(tǒng)中還沒有準(zhǔn)備好吕世,當(dāng)準(zhǔn)備好了的時候在從內(nèi)核準(zhǔn)備好的FD中把數(shù)據(jù)復(fù)制到進程的內(nèi)存里梯投,這時候阻塞才會釋放。 每次凡是有一個FD那么就會阻塞住其他的分蓖。其實一次IO操作可以分成兩個操作部分,一個是等待數(shù)據(jù)準(zhǔn)備好漓帚,然后在從內(nèi)核復(fù)制尝抖,這第二步其實是真正的IO復(fù)制操作。有時可能由于一次網(wǎng)絡(luò)連接昧辽,連接準(zhǔn)備好了搅荞,但是由于網(wǎng)絡(luò)延遲沒有數(shù)據(jù)傳輸過來咕痛,導(dǎo)致這個整個過程特別的耗時茉贡。那么我們怎么改進呢?
其實操作系統(tǒng)已經(jīng)考慮了這種情況腔丧,每個操作系統(tǒng)都為我們提供了可以檢測有哪些FD已經(jīng)準(zhǔn)備好了愉粤,所以我們不用在阻塞整個IO操作了衣厘,我們可以一次連接多個FD头滔,然后遍歷這些FD,有哪些已經(jīng)準(zhǔn)備好了,我們只需要使用這些準(zhǔn)備好的進行IO操作即可期吓。
圖片如下:
具體有操作系統(tǒng)有哪些函數(shù)可以供我們使用呢箭跳?
Redis 會優(yōu)先選擇時間復(fù)雜度為 $O(1)$ 的 I/O 多路復(fù)用函數(shù)作為底層實現(xiàn),包括 Solaries 10 中的
evport
刨晴、Linux 中的
epoll
和 macOS/FreeBSD 中的
kqueue
狈癞,上述的這些函數(shù)都使用了內(nèi)核內(nèi)部的結(jié)構(gòu)蝶桶,并且能夠服務(wù)幾十萬的文件描述符。
但是如果當(dāng)前編譯環(huán)境沒有上述函數(shù)脐雪,就會選擇
select
作為備選方案战秋,由于其在使用時會掃描全部監(jiān)聽的描述符获询,所以其時間復(fù)雜度較差 $O(n)$,并且只能同時服務(wù) 1024 個文件描述符吉嚣,所以一般并不會以
select
作為第一方案使用尝哆。
總結(jié)
Redis 對于 I/O 多路復(fù)用模塊的設(shè)計非常簡潔秋泄,通過宏保證了 I/O 多路復(fù)用模塊在不同平臺上都有著優(yōu)異的性能琐馆,將不同的 I/O 多路復(fù)用函數(shù)封裝成相同的 API 提供給上層使用。
整個模塊使 Redis 能以單進程運行的同時服務(wù)成千上萬個文件描述符恒序,避免了由于多進程應(yīng)用的引入導(dǎo)致代碼實現(xiàn)復(fù)雜度的提升瘦麸,減少了出錯的可能性。