十年網(wǎng)站開發(fā)經(jīng)驗 + 多家企業(yè)客戶 + 靠譜的建站團隊
量身定制 + 運營維護+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
服務(wù)端的多路復用如何解決連接高并發(fā)問題,針對這個問題,這篇文章詳細介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供天津網(wǎng)站建設(shè)、天津做網(wǎng)站、天津網(wǎng)站設(shè)計、天津網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計與制作、天津企業(yè)網(wǎng)站模板建站服務(wù),10多年天津做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡(luò)服務(wù)。
在淘寶上購物,比如雙11秒殺某個商品,對后臺都是一次考驗。一個網(wǎng)站系統(tǒng),在很短時間內(nèi)收到巨量請求時,穩(wěn)定性至關(guān)重要,用戶都不喜歡看自己的app老是轉(zhuǎn)菊花。具體的后臺并發(fā)詳見前文:大白話描述淘寶服務(wù)端分布式架構(gòu)演進之路----系列一大白話分布式系統(tǒng)演進之路----系列二
這里先不講策略性的東西,講講socket層面多連接下的并發(fā)問題。先來看I/O操作,調(diào)用read可以從流中讀入數(shù)據(jù),調(diào)用write可以往流寫入數(shù)據(jù)。那么如果調(diào)用read,但流中沒有數(shù)據(jù)怎么辦呢?只能使用阻塞I/O。對于服務(wù)端,有多個連接的情況,如果一個描述符fd阻塞了,導致另一個描述符fd即使收到數(shù)據(jù)也無法處理。對于這種情況,就得采用特殊方法處理。
一種方法是使用多進程,可使每個進程都執(zhí)行阻塞read,但是進程間的信號同步就比較麻煩。
另一種辦法就是使用一個進程執(zhí)行程序,使用非阻塞I/O讀取數(shù)據(jù)。采用輪詢方式讀取所有套接字是否能收到數(shù)據(jù)。這種方法不足之處就是浪費CPU的計算資源。
這里要說的就是I/O多路復用,也就是poll、epoll、pselect、select這幾個調(diào)用??梢酝ㄟ^以太驅(qū)動程序來大概看一下網(wǎng)卡接收數(shù)據(jù)的過程:
MAC網(wǎng)卡接收到數(shù)據(jù)包后通過DMA寫入內(nèi)存約定地址(這里如果剛好內(nèi)存的數(shù)據(jù)緩存在cache里,就有DMA和Cache數(shù)據(jù)不一致的問題,不過這里不是本文重點),然后觸發(fā)硬件中斷通知CPU,CPU調(diào)用中斷程序讀取內(nèi)存中的數(shù)據(jù)。
接下來可以看看操作系統(tǒng)如何通過select來實現(xiàn)多個socket的讀寫操作。前文講過進程調(diào)度的幾種方式:進程調(diào)度與fork,大致就是操作系統(tǒng)會維護一個隊列(假定任務(wù)優(yōu)先級相同),任務(wù)調(diào)度器按順序撈取隊列上的任務(wù)執(zhí)行:
當任務(wù)A創(chuàng)建socket時,操作系統(tǒng)會創(chuàng)建一個socket對象(一切描述符都是文件)。這個socket對象里面包含了發(fā)送接收緩沖區(qū)、任務(wù)等待隊列等成員。參考lwip的實現(xiàn):
創(chuàng)建socket:
進程A當執(zhí)行read時,由于無數(shù)據(jù)可讀,進程A由運行態(tài)變?yōu)樽枞麘B(tài),操作系統(tǒng)會把進程A掛在socket的等待隊列下,并從工作隊列移除,這個時候只有進程B和C占用CPU資源。
這里要注意,并非直接把進程A掛接在等待隊列下,而是把進程A的郵箱recvbox掛上去了:
當接收到數(shù)據(jù)時,任務(wù)調(diào)度器會將進程A設(shè)置為就緒態(tài)并掛到工作隊列中,重新接受任務(wù)調(diào)度。
那么,服務(wù)器需要管理多條連接,而一個read/recv只能阻塞一個socket,這里就用到了select。這里不放代碼,而是直接用圖說明:
進程A會監(jiān)視所有的socket,任何一個連接收到數(shù)據(jù)后,中斷程序都會喚醒等待隊列上的進程,然后只有真正收到數(shù)據(jù)的那個進程運行,其余進程繼續(xù)阻塞。這個就是服務(wù)器的”驚群”現(xiàn)象。
有沒有更合適的方法監(jiān)聽事件呢?答案是epoll。
epoll的接口如下:
int epoll_create(int size);//創(chuàng)建一個epoll的句柄,size為監(jiān)聽的數(shù)目int epoll_ctl(int epfd, int op, int fd,struct epoll_event *event);int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
select的缺點就是進程不知道哪些sock收到了數(shù)據(jù),因此只能遍歷所有sock。而epoll維護了一個就緒列表,引用收到數(shù)據(jù)的socket,可以避免遍歷:
當進程調(diào)用epoll_create時,會創(chuàng)建一個 eventpoll 對象。eventpoll對象與socket對象類似,也有等待數(shù)組。然后進程可以使用epoll_ctl添加所要監(jiān)聽的socket,內(nèi)核會將eventpoll添加到socket的等待隊列中。
當socket收到數(shù)據(jù)后,中斷程序會給eventpoll的“就緒列表”添加發(fā)生事件的socket序號,如sock2上面收到了數(shù)據(jù),就把2寫入eventpoll的rdlist數(shù)組里面:
當進程執(zhí)行epoll_wait時,如果rdlist里面有socket,那么epoll_wait直接返回,否則阻塞進程,阻塞的進程掛在eventpoll的等待隊列下面,如下面的圖表示進程A和進程F被阻塞:
收到數(shù)據(jù)時,中斷程序?qū)?yīng)的socket序號寫入rdlist,同時喚醒eventpoll 等待隊列中的進程,進程A和F進入運行狀態(tài)。
關(guān)于服務(wù)端的多路復用如何解決連接高并發(fā)問題問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識。