IO多路復(fù)用
假設(shè)你是一個(gè)機(jī)場(chǎng)的空管宛渐, 你需要管理到你機(jī)場(chǎng)的所有的航線方面, 包括進(jìn)港屁奏,出港岩榆, 有些航班需要放到停機(jī)坪等待,有些航班需要去登機(jī)口接乘客。
你會(huì)怎么做?
最簡(jiǎn)單的做法勇边,就是你去招一大批空管員犹撒,然后每人盯一架飛機(jī), 從進(jìn)港粒褒,接客识颊,排位,出港奕坟,航線監(jiān)控祥款,直至交接給下一個(gè)空港,全程監(jiān)控月杉。
那么問題就來了:
- 很快你就發(fā)現(xiàn)空管塔里面聚集起來一大票的空管員镰踏,交通稍微繁忙一點(diǎn),新的空管員就已經(jīng)擠不進(jìn)來了沙合。
- 空管員之間需要協(xié)調(diào)奠伪,屋子里面就1, 2個(gè)人的時(shí)候還好,幾十號(hào)人以后 首懈,基本上就成菜市場(chǎng)了绊率。
- 空管員經(jīng)常需要更新一些公用的東西,比如起飛顯示屏究履,比如下一個(gè)小時(shí)后的出港排期滤否,最后你會(huì)很驚奇的發(fā)現(xiàn),每個(gè)人的時(shí)間最后都花在了搶這些資源上最仑。
現(xiàn)實(shí)上我們的空管同時(shí)管幾十架飛機(jī)稀松平常的事情藐俺, 他們?cè)趺醋龅哪兀克麄冇眠@個(gè)東西
這個(gè)東西叫 flight progress strip. 每一個(gè)塊代表一個(gè)航班泥彤,不同的槽代表不同的狀態(tài)欲芹,然后一個(gè)空管員可以管理一組這樣的塊(一組航班),而他的工作吟吝,就是在航班信息有新的更新的時(shí)候菱父,把對(duì)應(yīng)的塊放到不同的槽子里面。
這個(gè)東西現(xiàn)在還沒有淘汰哦剑逃,只是變成電子的了而已浙宜。
是不是覺得一下子效率高了很多,一個(gè)空管塔里可以調(diào)度的航線可以是前一種方法的幾倍到幾十倍蛹磺。
如果你把每一個(gè)航線當(dāng)成一個(gè)Sock(I/O 流), 空管當(dāng)成你的服務(wù)端Sock管理代碼的話.
第一種方法就是最傳統(tǒng)的多進(jìn)程并發(fā)模型 (每進(jìn)來一個(gè)新的I/O流會(huì)分配一個(gè)新的進(jìn)程管理粟瞬。)
第二種方法就是I/O多路復(fù)用 (單個(gè)線程,通過記錄跟蹤每個(gè)I/O流(sock)的狀態(tài)萤捆,來同時(shí)管理多個(gè)I/O流 裙品。)
其實(shí)“I/O 多路復(fù)用”這個(gè)坑爹翻譯可能是這個(gè)概念在中文里面如此難理解的原因俗批。所謂的I/O多路復(fù)用在英文中其實(shí)叫 I/O multiplexing. 如果你搜索multiplexing啥意思,基本上都會(huì)出這個(gè)圖:
[圖片上傳失敗...(image-40ee69-1621308159741)]
于是大部分人都直接聯(lián)想到"一根網(wǎng)線清酥,多個(gè)sock復(fù)用" 這個(gè)概念扶镀,包括上面的幾個(gè)回答, 其實(shí)不管你用多進(jìn)程還是I/O多路復(fù)用焰轻, 網(wǎng)線都只有一根好伐臭觉。多個(gè)Sock復(fù)用一根網(wǎng)線這個(gè)功能是在內(nèi)核+驅(qū)動(dòng)層實(shí)現(xiàn)的**。
重要的事情再說一遍辱志。這里面的 multiplexing 指的其實(shí)是在單個(gè)線程通過記錄跟蹤每一個(gè)Sock(I/O流)的狀態(tài)(對(duì)應(yīng)空管塔里面的Fight progress strip槽)來同時(shí)管理多個(gè)I/O流.
發(fā)明它的原因蝠筑,是盡量多的提高服務(wù)器的吞吐能力。
是不是聽起來好拗口揩懒,看個(gè)圖就懂了.
在同一個(gè)線程里面什乙, 通過撥開關(guān)的方式,來同時(shí)傳輸多個(gè)I/O流已球, (學(xué)過EE的人現(xiàn)在可以站出來義正嚴(yán)辭說這個(gè)叫“時(shí)分復(fù)用”了)臣镣。
什么,你還沒有搞懂“一個(gè)請(qǐng)求到來了智亮,nginx使用epoll接收請(qǐng)求的過程是怎樣的”忆某, 多看看這個(gè)圖就了解了。提醒下阔蛉,ngnix會(huì)有很多鏈接進(jìn)來弃舒, epoll會(huì)把他們都監(jiān)視起來,然后像撥開關(guān)一樣状原,誰有數(shù)據(jù)就撥向誰聋呢,然后調(diào)用相應(yīng)的代碼處理。
了解這個(gè)基本的概念以后颠区,其他的就很好解釋了削锰。
select, poll, epoll 都是I/O多路復(fù)用的具體的實(shí)現(xiàn),之所以有這三個(gè)鬼存在瓦呼,其實(shí)是他們出現(xiàn)是有先后順序的喂窟。
I/O多路復(fù)用這個(gè)概念被提出來以后, select是第一個(gè)實(shí)現(xiàn) (1983 左右在BSD里面實(shí)現(xiàn)的)央串。
select 被實(shí)現(xiàn)以后,很快就暴露出了很多問題碗啄。
select 會(huì)修改傳入的參數(shù)數(shù)組质和,這個(gè)對(duì)于一個(gè)需要調(diào)用很多次的函數(shù),是非常不友好的稚字。
select 如果任何一個(gè)sock(I/O stream)出現(xiàn)了數(shù)據(jù)饲宿,select 僅僅會(huì)返回厦酬,但是并不會(huì)告訴你是那個(gè)sock上有數(shù)據(jù),于是你只能自己一個(gè)一個(gè)的找瘫想,10幾個(gè)sock可能還好仗阅,要是幾萬的sock每次都找一遍,這個(gè)無謂的開銷就頗有海天盛筵的豪氣了国夜。
select 只能監(jiān)視1024個(gè)鏈接减噪, 這個(gè)跟草榴沒啥關(guān)系哦,linux 定義在頭文件中的车吹,參見FD_SETSIZE筹裕。
select 不是線程安全的,如果你把一個(gè)sock加入到select, 然后突然另外一個(gè)線程發(fā)現(xiàn)窄驹,尼瑪朝卒,這個(gè)sock不用,要收回乐埠。對(duì)不起抗斤,這個(gè)select 不支持的,如果你喪心病狂的竟然關(guān)掉這個(gè)sock, select的標(biāo)準(zhǔn)行為是丈咐。瑞眼。呃。扯罐。不可預(yù)測(cè)的负拟, 這個(gè)可是寫在文檔中的哦.
“If a file descriptor being monitored by select() is closed in another thread, the result is unspecified”
于是 14 年以后(1997 年)一幫人又實(shí)現(xiàn)了 poll, poll 修復(fù)了 select 的很多問題,比如
- poll 去掉了1024個(gè)鏈接的限制歹河,于是要多少鏈接呢掩浙, 主人你開心就好。
- poll 從設(shè)計(jì)上來說秸歧,不再修改傳入數(shù)組厨姚,不過這個(gè)要看你的平臺(tái)了,所以行走江湖键菱,還是小心為妙谬墙。
其實(shí)拖 14 年那么久也不是效率問題, 而是那個(gè)時(shí)代的硬件實(shí)在太弱经备,一臺(tái)服務(wù)器處理1千多個(gè)鏈接簡(jiǎn)直就是神一樣的存在了拭抬,select很長(zhǎng)段時(shí)間已經(jīng)滿足需求。
但是poll仍然不是線程安全的侵蒙, 這就意味著造虎,不管服務(wù)器有多強(qiáng)悍,你也只能在一個(gè)線程里面處理一組 I/O 流纷闺。你當(dāng)然可以拿多進(jìn)程來配合了算凿,不過然后你就有了多進(jìn)程的各種問題份蝴。
于是5年以后, 在2002, 大神 Davide Libenzi 實(shí)現(xiàn)了epoll,epoll 可以說是 I/O 多路復(fù)用最新的一個(gè)實(shí)現(xiàn)氓轰,epoll 修復(fù)了poll 和select絕大部分問題, 比如:
- epoll 現(xiàn)在是線程安全的婚夫。
- epoll 現(xiàn)在不僅告訴你sock組里面數(shù)據(jù),還會(huì)告訴你具體哪個(gè)sock有數(shù)據(jù)署鸡,你不用自己去找了案糙。
貼一張霸氣的圖,看看當(dāng)年神一樣的性能(測(cè)試代碼都是死鏈了储玫, 如果有人可以刨墳找出來侍筛,可以研究下細(xì)節(jié)怎么測(cè)的).
[圖片上傳失敗...(image-42ab25-1621308159741)]
橫軸 Dead connections 就是鏈接數(shù)的意思,叫這個(gè)名字只是它的測(cè)試工具叫deadcon. 縱軸是每秒處理請(qǐng)求的數(shù)量撒穷,你可以看到匣椰,epoll每秒處理請(qǐng)求的數(shù)量基本不會(huì)隨著鏈接變多而下降的。poll 和/dev/poll 就很慘了端礼。
可是epoll 有個(gè)致命的缺點(diǎn)禽笑。。只有l(wèi)inux支持蛤奥。比如BSD上面對(duì)應(yīng)的實(shí)現(xiàn)是kqueue佳镜。
而ngnix 的設(shè)計(jì)原則里面, 它會(huì)使用目標(biāo)平臺(tái)上面最高效的I/O多路復(fù)用模型咯凡桥,所以才會(huì)有這個(gè)設(shè)置蟀伸。一般情況下,如果可能的話缅刽,盡量都用epoll/kqueue吧