從阻塞到Select/EPoll/IOCP, 高并發(fā)一路高歌...

C10K的問題不知道現(xiàn)在還有多少人還記得?Dan Kegel在01年左右在個(gè)人博客上面拿來探討的話題,指的是在當(dāng)前的機(jī)器設(shè)備情況下能不能單機(jī)扛得住10K的用戶同時(shí)訪問(現(xiàn)在單機(jī)300W并發(fā)都沒得問題了,科技發(fā)展的快速吧!)。所以這里聊聊網(wǎng)絡(luò)從阻塞一路走來...

1. 同步IO走過蠻荒時(shí)期

阻塞模式可能是學(xué)習(xí)網(wǎng)絡(luò)的絕佳方案。早些年異步IO的基礎(chǔ)技術(shù)還不完善的時(shí)候阻塞IO在那個(gè)年代是主要的網(wǎng)絡(luò)通信方式。

一個(gè)線程一個(gè)Connection的模式成為了程序員主要使用方式:每次Accept到一個(gè)新的Connection的時(shí)候就啟動一個(gè)線程來用專門對這個(gè)鏈接做讀取寫入操作。這種方式極大的制約了系統(tǒng)能夠處理鏈接的數(shù)量。(試想下如果有10w個(gè)終端同時(shí)向一臺服務(wù)器發(fā)起請求,那么服務(wù)器一次性創(chuàng)建10w個(gè)線程,系統(tǒng)必然扛不?。?/p>

騰訊的解決方案:在這個(gè)時(shí)期使用TCP的阻塞多線程模型想要構(gòu)建超大規(guī)避的高并發(fā)系統(tǒng)幾乎是不可能。同時(shí)期暴發(fā)的騰訊科技反其道行之,利用UDP構(gòu)建了類似TCP的可靠傳輸(用戶態(tài)模擬了TCP協(xié)議)。這樣可以通過UDP模擬的并發(fā)系統(tǒng)并不需要起對應(yīng)的線程,從而扛起了騰訊當(dāng)時(shí)的流量(實(shí)現(xiàn)UDP的可靠傳輸并不簡單,需要丟包重傳流量擁塞控制等等)。

由于騰訊使用了UDP作為了QQ的通訊手段,導(dǎo)致很多教科書不明緣由。大肆忽悠鼓吹UDP適合IM通訊的場景,實(shí)際上當(dāng)時(shí)是有歷史原因的。而且騰訊現(xiàn)在已經(jīng)換TCP/HTTP作為主要通訊方式了

2. 異步或者半異步帶來真正的高并發(fā)

經(jīng)歷了陣痛的蠻荒期,終于需要支持異步IO系統(tǒng)的接口被提上日程。以Posix-Select Windows IOCP Linux EPoll Mac KQueue 為代表的異步IO接口終于穩(wěn)定的面向了大眾開發(fā)者,也標(biāo)志著C10K甚至C100K的問題(實(shí)際上現(xiàn)在單機(jī)C300k都OK了)終于真正解決了。nginx就是基于異步IO的方法,所以在性能上面完爆Tomcat。

那么這些異步IO接口解決了哪些問題? 主要解決了通訊狀態(tài)的的管理。舉個(gè)例子:在同步IO里面之所以需要一個(gè)連接一個(gè)線程,是因?yàn)槊看螌懭胱x取都是阻塞的。比如當(dāng)前發(fā)起了對連接的讀操作,如果沒有數(shù)據(jù)達(dá)到,操作系統(tǒng)會阻塞到直到有可讀數(shù)據(jù)或者對端主動斷開為止。如果一個(gè)線程同時(shí)管理2個(gè)或者2個(gè)以上的連接,對其中一個(gè)讀寫阻塞會導(dǎo)致影響其他鏈接的讀寫操作。那么異步IO在的讀寫過程中并不是阻塞的(比如當(dāng)前數(shù)據(jù)讀不到的情況下不會卡主線程,而是返回錯(cuò)誤碼),同樣系統(tǒng)提供一組接口可以幫助程序管理大量(百萬千萬上億級別)的鏈接(當(dāng)這些鏈接中出現(xiàn)了一個(gè)或者多個(gè)鏈接可讀可寫主動通知程序去處理)。

以Linux的EPoll為例:首先明白2個(gè)關(guān)鍵知識點(diǎn),系統(tǒng)的網(wǎng)絡(luò)接口發(fā)送Send讀取Write都不是表示把數(shù)據(jù)發(fā)送出去了、或者接受到剛剛對方發(fā)來的數(shù)據(jù)。Send函數(shù)成功只是表明用戶態(tài)需要發(fā)送的數(shù)據(jù)成功拷貝到了內(nèi)核的發(fā)送緩沖中,Write指的是從接受緩沖中讀取到了數(shù)據(jù)。Linux在構(gòu)建鏈接的時(shí)候會順帶構(gòu)建2個(gè)緩沖區(qū),一個(gè)是發(fā)送緩沖一個(gè)是接受緩沖。當(dāng)發(fā)送緩沖用空余的空間出來就表明了當(dāng)前可以'發(fā)送'了,當(dāng)接受緩沖有數(shù)據(jù)的時(shí)候就表明當(dāng)前鏈接的狀態(tài)是可讀的。EPoll底層使用紅黑樹配合雙向列表來維護(hù)Socket和數(shù)據(jù)狀態(tài),所以性能非常高。比如網(wǎng)卡收到對端發(fā)送來的數(shù)據(jù)后,系統(tǒng)內(nèi)核就把數(shù)據(jù)從網(wǎng)卡中讀到內(nèi)核內(nèi)存空間,并把當(dāng)前的套接字句柄添加到讀就緒列表中,整系統(tǒng)通過類似的方式高效的運(yùn)行著。

系統(tǒng)異步的接口(由于Epoll的接口只是通知程序可讀/可寫,具體的讀寫操作還是要程序主動完成,所以也有人說EPoll不是完全的異步操作)讓我們可以使用一個(gè)線程(或者少量的線程)就可以維護(hù)數(shù)以萬計(jì)的Socket狀態(tài)管理。TCP終于可以單機(jī)可以和數(shù)百萬的客戶端同時(shí)通訊了。

到這里問個(gè)簡單的問題(敲黑板@@@@!)? 歷史上TCP和UDP哪個(gè)出現(xiàn)的比較早? 可能很多人覺得TCP是穩(wěn)定可靠的一定比UDP這種不可靠通訊方式出現(xiàn)來的晚吧。實(shí)際上UDP是在TCP出現(xiàn)之后才出現(xiàn)的,UDP之初一定程度就是為了彌補(bǔ)TCP的不足而誕生的。

那么使用UDP的場景是不是很少了? 確實(shí)!大部分能用UDP的場景都可以使用TCP代替,除了部分歷史原因。比如DNS就是選的UDP實(shí)現(xiàn)的,現(xiàn)在如果想改TCP基本不可能,而且也沒有必要。但是TCP存在一些性能問題,比如:TCP每次鏈接都需要三次握手,結(jié)束需要4次揮手。TCP在運(yùn)行過程中會產(chǎn)生更多的SysCall(系統(tǒng)調(diào)用,每次調(diào)用都要自陷入內(nèi)核,這個(gè)耗時(shí)就大了)。如果能夠在用戶態(tài)實(shí)現(xiàn)可靠傳輸協(xié)議(包括流量控制 丟包重傳 等等特性),能夠優(yōu)化上面的TCP協(xié)議存在問題,說白了TCP協(xié)議本身設(shè)計(jì)的還是有冗余的。尤其在局域網(wǎng)環(huán)境(服務(wù)器基本都是搭建在局域網(wǎng)里面)這種網(wǎng)絡(luò)環(huán)境可控的情況下可以極大提升網(wǎng)絡(luò)性能,我簡單試過基于UDP的內(nèi)網(wǎng)的傳輸方式比TCP的的通道建立時(shí)間要短很多,數(shù)據(jù)收發(fā)速度也有很大的提升(5倍以上)?;谶@種方式的組件的分布式架構(gòu)網(wǎng)絡(luò)通訊帶來的數(shù)據(jù)延遲可以降到更低,分布式系統(tǒng)的性能會更好。

3. 更加極端的DPDK

傳統(tǒng)的網(wǎng)絡(luò)從網(wǎng)卡走到用戶態(tài)處理數(shù)據(jù)經(jīng)過了太多的數(shù)據(jù)處理過程:


image

DPDK的處理流程:


image

DPDK直接把網(wǎng)卡數(shù)據(jù)傳遞給了用戶態(tài),減少了大量的內(nèi)核的處理邏輯。所以DPDK的處理性能比傳統(tǒng)的網(wǎng)絡(luò)處理性能更高??墒怯脩魬B(tài)拿到數(shù)據(jù)是物理幀(沒有經(jīng)過協(xié)議處理的原始數(shù)據(jù))需要用戶態(tài)模擬出整個(gè)協(xié)議棧。其次由于放棄了系統(tǒng)原生的中斷處理方式,所以需要獨(dú)立一個(gè)線程不停的LOOP去收發(fā)數(shù)據(jù)。

所以DPDK天生就是為高并發(fā)而生的,比如反向代理負(fù)載均衡器就可以采用DPDK處理。

4. 協(xié)程

基于異步IO已經(jīng)可以解決高并發(fā)的IO的問題了,但是異步IO需要配合Callback:也就是每次收到數(shù)據(jù)回調(diào)對應(yīng)處理函數(shù),但是并不是每次回調(diào)都是完整的Package(TCP是流可能在任何位置被分包),所以我們需要在回調(diào)函數(shù)里面判別是不是收到一個(gè)完整的邏輯包。如果收到了就做對應(yīng)的邏輯處理,如果數(shù)據(jù)還不夠就暫時(shí)先不出處理。所以這樣處理程序是碎片化的,碎片化最大的問題程序可讀性被打斷了。并沒有同步IO的那種流暢的可讀性。協(xié)程就在這個(gè)情況下孕育而生了,每個(gè)Connection被放置在一個(gè)協(xié)程中,調(diào)度程序根據(jù)當(dāng)前是否可讀還是可寫來調(diào)度不同的協(xié)程執(zhí)行不同的邏輯。例如:在一個(gè)協(xié)程里面執(zhí)行了讀操作,但是當(dāng)前Connection是不可以讀的。協(xié)程調(diào)度器就會把當(dāng)前執(zhí)行的協(xié)程切換下來,一直到Connection可讀了才繼續(xù)被調(diào)度。整個(gè)過程和多線程調(diào)度類似,只是協(xié)程在用戶態(tài)更加輕量?,F(xiàn)在系統(tǒng)同時(shí)并行運(yùn)行和調(diào)度百萬個(gè)協(xié)程沒有壓力。

所以協(xié)程只是優(yōu)化了異步IO的交互體驗(yàn),有保證了異步IO的高并發(fā)性。

THE - END

版權(quán)所有,如有轉(zhuǎn)載請聯(lián)系我本人http://www.breakerror.com/archives/101-i.html

?著作權(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)容