自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從青銅到王者:帶你吃透IO模型

開發(fā) 前端
讓我們來看一個(gè)具體的案例,深入了解如何通過優(yōu)化 IO 模型提升系統(tǒng)性能 。某在線教育平臺(tái),其業(yè)務(wù)涵蓋了課程視頻播放、在線直播教學(xué)、用戶資料存儲(chǔ)等多個(gè)方面 。

在計(jì)算機(jī)編程的江湖中,IO 模型堪稱一位深藏不露卻又掌控全局的武林高手。如果把計(jì)算機(jī)程序比作一個(gè)龐大的江湖門派,那么 IO 操作就是門派中最基礎(chǔ)也最關(guān)鍵的 “內(nèi)功心法”,而 IO 模型則是修煉這門 “內(nèi)功” 的不同秘籍 。它看似低調(diào),卻在幕后默默地決定著程序的性能、效率和穩(wěn)定性,深刻影響著程序與外部世界(如文件系統(tǒng)、網(wǎng)絡(luò)等)交互的方式。

對(duì)于程序員來說,理解 IO 模型,就如同武林高手領(lǐng)悟上乘武功心法,是提升編程境界、突破技術(shù)瓶頸的必經(jīng)之路。它不僅能幫助我們優(yōu)化代碼,使其運(yùn)行得更加高效流暢,還能讓我們?cè)诿鎸?duì)復(fù)雜的系統(tǒng)設(shè)計(jì)和性能調(diào)優(yōu)問題時(shí),有更清晰的思路和更強(qiáng)大的解決能力。今天,就讓我們一起深入探索 IO 模型的神秘世界,揭開它那層神秘的面紗。

一、IO模型簡(jiǎn)介

1.1什么是 IO

在計(jì)算機(jī)世界里,IO(Input/Output)即輸入 / 輸出,堪稱計(jì)算機(jī)與外部世界溝通的橋梁,是數(shù)據(jù)在計(jì)算機(jī)內(nèi)部與外部設(shè)備(如磁盤、網(wǎng)絡(luò)、鍵盤、顯示器等)之間的流動(dòng)過程 ,就像人體的血脈,源源不斷地輸送著養(yǎng)分(數(shù)據(jù))。從本質(zhì)上講,IO 實(shí)現(xiàn)了數(shù)據(jù)在不同存儲(chǔ)介質(zhì)或設(shè)備之間的遷移,讓計(jì)算機(jī)能夠獲取外部信息并輸出處理結(jié)果。

以磁盤 IO 為例,當(dāng)我們運(yùn)行一個(gè)程序時(shí),如果程序所需的數(shù)據(jù)不在內(nèi)存中,就會(huì)觸發(fā)磁盤 IO 操作,將數(shù)據(jù)從磁盤讀取到內(nèi)存中。這一過程如同從倉庫(磁盤)中取出貨物(數(shù)據(jù))并搬到工作區(qū)(內(nèi)存)。而網(wǎng)絡(luò) IO 則是在網(wǎng)絡(luò)通信中,數(shù)據(jù)在計(jì)算機(jī)與其他網(wǎng)絡(luò)節(jié)點(diǎn)之間的傳輸,比如我們?yōu)g覽網(wǎng)頁時(shí),計(jì)算機(jī)通過網(wǎng)絡(luò) IO 從服務(wù)器獲取網(wǎng)頁數(shù)據(jù),就像是從遠(yuǎn)方的供應(yīng)商那里接收貨物。

常見的IO模型包括阻塞IO、非阻塞IO、多路復(fù)用IO和異步IO:

  • 阻塞IO(Blocking IO):在執(zhí)行一個(gè)IO操作時(shí),如果數(shù)據(jù)沒有準(zhǔn)備好,程序會(huì)一直等待,直到數(shù)據(jù)準(zhǔn)備就緒才會(huì)返回結(jié)果。這種模型下,應(yīng)用程序需要等待數(shù)據(jù)傳輸完成,期間無法進(jìn)行其他任務(wù)。
  • 非阻塞IO(Non-blocking IO):在執(zhí)行一個(gè)IO操作時(shí),如果數(shù)據(jù)沒有準(zhǔn)備好,程序不會(huì)等待而是立即返回。通過不斷輪詢來檢查是否有數(shù)據(jù)準(zhǔn)備好,然后再進(jìn)行讀寫操作。這樣可以避免長(zhǎng)時(shí)間的阻塞等待,但仍然需要主動(dòng)檢查狀態(tài)。
  • 多路復(fù)用IO(Multiplexing IO):使用select、poll或epoll等系統(tǒng)調(diào)用,在一個(gè)線程中同時(shí)監(jiān)聽多個(gè)文件描述符(sockets),當(dāng)任意一個(gè)文件描述符有就緒事件發(fā)生時(shí),再去進(jìn)行相應(yīng)的讀寫操作。這樣可以同時(shí)處理多個(gè)連接,并且避免了頻繁地輪詢。
  • 異步IO(Asynchronous IO):在執(zhí)行一個(gè)IO操作時(shí),程序發(fā)起請(qǐng)求后就可以繼續(xù)做其他事情,并且在IO操作完成后,系統(tǒng)會(huì)通知應(yīng)用程序進(jìn)行數(shù)據(jù)處理。這種模型下,IO的讀寫操作和結(jié)果處理是分離的,應(yīng)用程序無需等待IO操作完成。

1.2操作系統(tǒng)中的 IO “魔法”

在操作系統(tǒng)層面,IO 操作涉及用戶空間和內(nèi)核空間的交互。用戶空間是應(yīng)用程序運(yùn)行的區(qū)域,而內(nèi)核空間則負(fù)責(zé)管理硬件資源和提供系統(tǒng)服務(wù)。當(dāng)應(yīng)用程序發(fā)起 IO 請(qǐng)求時(shí),比如調(diào)用read或write函數(shù),這一請(qǐng)求會(huì)從用戶空間傳遞到內(nèi)核空間。

具體來說,IO 調(diào)用首先由應(yīng)用程序發(fā)起,發(fā)起后程序會(huì)等待系統(tǒng)內(nèi)核完成實(shí)際的 IO 操作。以讀取網(wǎng)絡(luò)數(shù)據(jù)為例,內(nèi)核先等待數(shù)據(jù)到達(dá)網(wǎng)卡,然后將數(shù)據(jù)拷貝至內(nèi)核緩沖區(qū),這是等待數(shù)據(jù)準(zhǔn)備階段;接著,內(nèi)核將數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到用戶空間,供應(yīng)用程序處理,此為從內(nèi)核向進(jìn)程拷貝數(shù)據(jù)階段。這兩個(gè)階段構(gòu)成了 IO 執(zhí)行的全過程 ,而阻塞、非阻塞、異步、同步等概念,正是基于 IO 執(zhí)行階段的不同狀態(tài)和處理機(jī)制而產(chǎn)生的。

二、五種IO模型詳解

2.1IO模型分析方法

分析IO模型需要了解2個(gè)問題:

問題1:發(fā)送IO請(qǐng)求,IO請(qǐng)求可以理解為用戶空間和內(nèi)核空間數(shù)據(jù)同步,根據(jù)發(fā)起者不同分為以下兩種情況:

  • 由用戶程序發(fā)起(同步IO)。
  • 由內(nèi)核發(fā)起(異步IO)。

問題2:等待數(shù)據(jù)到來,等待數(shù)據(jù)到來的方式有以下幾種:

  • 阻塞(阻塞IO)。
  • 輪詢(非阻塞IO)。
  • 信號(hào)通知(信號(hào)驅(qū)動(dòng)IO)。

圖片圖片

(內(nèi)核空間和用戶空間數(shù)據(jù)同步由誰發(fā)起是分析Linux IO模型最核心問題)

2.2 同步阻塞 IO:最基礎(chǔ)的 “老實(shí)人” 模型

同步阻塞 IO(Blocking IO)是最基礎(chǔ)、最容易理解的 IO 模型,堪稱 IO 世界里的 “老實(shí)人”。在這種模型下,當(dāng)應(yīng)用程序發(fā)起 IO 請(qǐng)求時(shí),比如調(diào)用read函數(shù)讀取數(shù)據(jù),程序會(huì)被阻塞,就像一個(gè)人在等待快遞送達(dá),期間只能干巴巴地等著,什么也做不了 ,直到內(nèi)核將數(shù)據(jù)準(zhǔn)備好并拷貝到用戶空間,IO 操作完成,程序才會(huì)繼續(xù)執(zhí)行后續(xù)代碼。

在 linux中,默認(rèn)情況下所有的 socket都是blocking,一個(gè)典型的讀操作流程大概是這樣:

圖片圖片

當(dāng)用戶進(jìn)程調(diào)用了 recvfrom這個(gè)系統(tǒng)調(diào)用,kernel就開始了IO的第一個(gè)階段:準(zhǔn)備數(shù)據(jù)。

對(duì)于 network io來說,很多時(shí)候數(shù)據(jù)在一開始還沒有到達(dá)(比如,還沒有收到一個(gè)完整的UDP包),這個(gè)時(shí)候 kernel就要等待足夠的數(shù)據(jù)到來。

而在用戶進(jìn)程這邊,整個(gè)進(jìn)程會(huì)被阻塞。當(dāng) kernel一直等到數(shù)據(jù)準(zhǔn)備好了,它就會(huì)將數(shù)據(jù)從 kernel中拷貝到用戶內(nèi)存,然后 kernel返回結(jié)果,用戶進(jìn)程才解除 block的狀態(tài),重新運(yùn)行起來;所以,blocking IO的特點(diǎn)就是在IO執(zhí)行的兩個(gè)階段(等待數(shù)據(jù)和拷貝數(shù)據(jù)兩個(gè)階段)都被 block了。

幾乎所有的程序員第一次接觸到的網(wǎng)絡(luò)編程都是從 listen(),send(),recv(),等接口開始的,使用這些接口可以很方便的構(gòu)建服務(wù)器/客戶機(jī)的模型。然而大部分的 socket接口都是阻塞型的,如下圖:

Tip:所謂阻塞型接口是指系統(tǒng)調(diào)用(一般是IO接口)不返回調(diào)用結(jié)果并讓當(dāng)前線程一直阻塞,只有當(dāng)該系統(tǒng)調(diào)用獲得結(jié)果或者超時(shí)出錯(cuò)時(shí)才返回。

圖片圖片

實(shí)際上,除非特別指定,幾乎所有的 IO接口 ( 包括socket接口 ) 都是阻塞型的。這給網(wǎng)絡(luò)編程帶來了一個(gè)很大的問題,如在調(diào)用 recv(1024)的同時(shí),線程將被阻塞,在此期間,線程將無法執(zhí)行任何運(yùn)算或響應(yīng)任何的網(wǎng)絡(luò)請(qǐng)求。

一個(gè)簡(jiǎn)單地解決方案:

在服務(wù)器端使用多線程(或多進(jìn)程)。
多線程(或多進(jìn)程)的目的是讓每個(gè)連接都擁有獨(dú)立的線程(或進(jìn)程),這樣任何一個(gè)連接的阻塞都不會(huì)影響其他的連接。

該方案的問題是:

開啟多進(jìn)程或都線程的方式,在遇到要同時(shí)響應(yīng)成百上千路的連接請(qǐng)求,則無論多線程還是多進(jìn)程都會(huì)嚴(yán)重占據(jù)系統(tǒng)資源,
降低系統(tǒng)對(duì)外界響應(yīng)效率,而且線程與進(jìn)程本身也更容易進(jìn)入假死狀態(tài)。

改進(jìn)方案:

很多程序員可能會(huì)考慮使用“線程池”或“連接池”。
“線程池”旨在減少創(chuàng)建和銷毀線程的頻率,其維持一定合理數(shù)量的線程,并讓空閑的線程重新承擔(dān)新的執(zhí)行任務(wù)。
“連接池”維持連接的緩存池,盡量重用已有的連接、減少創(chuàng)建和關(guān)閉連接的頻率。
這兩種技術(shù)都可以很好的降低系統(tǒng)開銷,都被廣泛應(yīng)用很多大型系統(tǒng),如websphere、tomcat和各種數(shù)據(jù)庫等。

改進(jìn)后方案其實(shí)也存在著問題:

“線程池”和“連接池”技術(shù)也只是在一定程度上緩解了頻繁調(diào)用IO接口帶來的資源占用。
而且,所謂“池”始終有其上限,當(dāng)請(qǐng)求大大超過上限時(shí),“池”構(gòu)成的系統(tǒng)對(duì)外界的響應(yīng)并不比沒有池的時(shí)候效果好多少。
所以使用“池”必須考慮其面臨的響應(yīng)規(guī)模,并根據(jù)響應(yīng)規(guī)模調(diào)整“池”的大小。

對(duì)應(yīng)上例中的所面臨的可能同時(shí)出現(xiàn)的上千甚至上萬次的客戶端請(qǐng)求,“線程池”或“連接池”或許可以緩解部分壓力,但是不能解決所有問題??傊?,多線程模型可以方便高效的解決小規(guī)模的服務(wù)請(qǐng)求,但面對(duì)大規(guī)模的服務(wù)請(qǐng)求,多線程模型也會(huì)遇到瓶頸,可以用非阻塞接口來嘗試解決這個(gè)問題。

2.3 同步非阻塞 IO:忙碌的 “輪詢者”

同步非阻塞 IO(Non-blocking IO)在同步阻塞 IO 的基礎(chǔ)上,將 socket 設(shè)置為非阻塞模式 ,就像一個(gè)忙碌的 “輪詢者”,不再傻傻地等待,而是不斷地詢問事情是否完成。當(dāng)應(yīng)用程序發(fā)起 IO 請(qǐng)求時(shí),如果數(shù)據(jù)尚未準(zhǔn)備好,函數(shù)會(huì)立即返回,而不會(huì)阻塞線程。但這并不意味著數(shù)據(jù)已經(jīng)讀取成功,應(yīng)用程序需要不斷地輪詢,再次發(fā)起 IO 請(qǐng)求,直到數(shù)據(jù)準(zhǔn)備好并成功讀取 。

Linux下,可以通過設(shè)置socket使其變?yōu)閚on-blocking。當(dāng)對(duì)一個(gè)non-blocking socket執(zhí)行讀操作時(shí),流程是這個(gè)樣子:

圖片圖片

從圖中可以看出,當(dāng)用戶進(jìn)程發(fā)出 read操作時(shí),如果 kernel中的數(shù)據(jù)還沒有準(zhǔn)備好,那么它并不會(huì) block用戶進(jìn)程,而是立刻返回一個(gè) error。

從用戶進(jìn)程角度講 ,它發(fā)起一個(gè) read操作后,并不需要等待,而是馬上就得到了一個(gè)結(jié)果。

用戶進(jìn)程判斷結(jié)果是一個(gè) error時(shí),它就知道數(shù)據(jù)還沒有準(zhǔn)備好,于是用戶就可以在本次到下次再發(fā)起 read詢問的時(shí)間間隔內(nèi)做其他事情,或者直接再次發(fā)送 read操作。

一旦 kernel中的數(shù)據(jù)準(zhǔn)備好了,并且又再次收到了用戶進(jìn)程的 system call,那么它馬上就將數(shù)據(jù)拷貝到了用戶內(nèi)存(這一階段仍然是阻塞的),然后返回。

也就是說非阻塞的 recvform系統(tǒng)調(diào)用之后,進(jìn)程并沒有被阻塞,內(nèi)核馬上返回給進(jìn)程,如果數(shù)據(jù)還沒準(zhǔn)備好,此時(shí)會(huì)返回一個(gè) error。

進(jìn)程在返回之后,可以干點(diǎn)別的事情,然后再發(fā)起 recvform系統(tǒng)調(diào)用。

重復(fù)上面的過程,循環(huán)往復(fù)的進(jìn)行 recvform系統(tǒng)調(diào)用。這個(gè)過程通常被稱之為輪詢。

輪詢檢查內(nèi)核數(shù)據(jù),指導(dǎo)數(shù)據(jù)準(zhǔn)備好,再拷貝數(shù)據(jù)到進(jìn)程,進(jìn)行數(shù)據(jù)處理。

需要注意,拷貝數(shù)據(jù)的整個(gè)過程,進(jìn)程仍然是屬于阻塞的狀態(tài)。

所以,在非阻塞式 IO中,用戶進(jìn)程其實(shí)是需要不斷的主動(dòng)詢問 kernel數(shù)據(jù)準(zhǔn)備好了沒有。

非阻塞 IO示例:

(1)服務(wù)端

# 實(shí)現(xiàn)自己監(jiān)測(cè)IO,遇到IO,就切到我單個(gè)線程的其他用戶去運(yùn)行了,實(shí)現(xiàn)單線程下的并發(fā),并把單線程的效率提到了最高。
from socket import *

server = socket(AF_INET, SOCK_STREAM)
server.bind(("192.168.2.209",8800))
server.listen(5)
server.setblocking(False)      # 默認(rèn)是 True(阻塞),改成 False(非阻塞)
print("starting...")

rlist = []
wlist = []
while True:
    try:
        conn, addr = server.accept()
        rlist.append(conn)
        print(rlist)
    except BlockingIOError:
        # 收消息
        del_rlist = []  # 要?jiǎng)h除的鏈接
        for conn in rlist:
            try:
                data = conn.recv(1024)
                if not data:
                    del_rlist.append(conn)
                    continue
                # conn.send(data.upper())
                wlist.append((conn,data.upper()))
            except BlockingIOError:
                continue

            except Exception:
                conn.close()    # 服務(wù)端單方面斷開,這個(gè)鏈接就可以回收掉了
                del_rlist.append(conn)

        # 發(fā)消息
        del_wlist = []
        for item in wlist:
            try:
                conn = item[0]
                data = item[1]
                # 如果這里拋異常,那么下行代碼運(yùn)行不了,如果沒拋異常,發(fā)成功了,就把鏈接加到刪除的列表
                conn.send(data)
                del_wlist.append(item)
            except BlockingIOError:
                pass

        for item in del_wlist:
            wlist.remove(item)

        for conn in del_rlist:
            rlist.remove(conn)

server.close()

(2)客戶端

from socket import *

client = socket(AF_INET,SOCK_STREAM)
client.connect(("192.168.2.209",8800))

while True:
    msg = input(">>:").strip()
    if not msg:continue
    client.send(msg.encode("utf-8"))
    data = client.recv(1024)
    print("收到的數(shù)據(jù):%s" % data.decode("utf-8"))

client.close()

但是非阻塞 IO模型絕不被推薦。

我們不能否定其優(yōu)點(diǎn):能夠在等待任務(wù)完成的時(shí)間里干其他活了(包括提交其他任務(wù),也就是 “后臺(tái)” 可以有多個(gè)任務(wù)在“”同時(shí)“”執(zhí)行)。

但是也難掩其缺點(diǎn):

循環(huán)調(diào)用 recv()將大幅度推高 CPU占用率;這也是我們?cè)诖a中留一句time.sleep(2)的原因,
否則在低配主機(jī)下極容易出現(xiàn)卡機(jī)情況。

任務(wù)完成的響應(yīng)延遲增大了,因?yàn)槊窟^一段時(shí)間才去輪詢一次 read操作,
而任務(wù)可能在兩次輪詢之間的任意時(shí)間完成。這會(huì)導(dǎo)致整體數(shù)據(jù)吞吐量的降低。

此外,在這個(gè)方案中 recv()更多的是起到檢測(cè)“操作是否完成”的作用,實(shí)際操作系統(tǒng)提供了更為高效的檢測(cè)“操作是否完成“作用的接口,例如 select()多路復(fù)用模式,可以一次檢測(cè)多個(gè)連接是否活躍。

2.4IO 多路復(fù)用:高效的 “事件監(jiān)聽者”

IO 多路復(fù)用(IO Multiplexing)模型是一種高效的 IO 模型,它就像是一個(gè)聰明的 “事件監(jiān)聽者”,能夠同時(shí)監(jiān)聽多個(gè)文件描述符(如 socket)的事件,當(dāng)其中任何一個(gè)文件描述符就緒(有數(shù)據(jù)可讀、可寫或有異常)時(shí),就會(huì)通知應(yīng)用程序進(jìn)行相應(yīng)的處理 。這使得一個(gè)線程可以處理多個(gè) IO 請(qǐng)求,大大提高了程序的并發(fā)處理能力。

IO 多路復(fù)用的實(shí)現(xiàn)方式主要有select、poll和epoll。select函數(shù)通過監(jiān)聽文件描述符集合,當(dāng)有描述符就緒時(shí)返回,通知應(yīng)用程序進(jìn)行處理 。但它有一些局限性,比如能監(jiān)聽的文件描述符數(shù)量有限(通常為 1024 個(gè)),每次調(diào)用select都需要將文件描述符集合從用戶空間拷貝到內(nèi)核空間,并且返回時(shí)需要遍歷整個(gè)集合來判斷哪些描述符就緒,效率較低 。poll函數(shù)與select類似,但它使用鏈表來存儲(chǔ)文件描述符,突破了文件描述符數(shù)量的限制,但在處理大量文件描述符時(shí),性能仍然會(huì)隨著描述符數(shù)量的增加而下降 。

epoll是select和poll的增強(qiáng)版,它采用事件驅(qū)動(dòng)的方式,在內(nèi)核中維護(hù)一個(gè)事件表,當(dāng)有事件發(fā)生時(shí),直接將事件通知給應(yīng)用程序,而不需要遍歷整個(gè)文件描述符集合 。epoll還支持水平觸發(fā)(Level Triggered,LT)和邊緣觸發(fā)(Edge Triggered,ET)兩種模式,其中邊緣觸發(fā)模式的效率更高,適用于高并發(fā)場(chǎng)景 。

它的基本原理就是 select/epoll這個(gè) function會(huì)不斷的輪詢所負(fù)責(zé)的所有 socket,當(dāng)某個(gè)socket有數(shù)據(jù)到達(dá)了,就通知用戶進(jìn)程。它的流程如圖:

圖片圖片

當(dāng)用戶進(jìn)程調(diào)用了 select,那么整個(gè)進(jìn)程會(huì)被 block,而同時(shí),kernel會(huì)“監(jiān)視”所有 select負(fù)責(zé)的 socket,當(dāng)任何一個(gè) socket中的數(shù)據(jù)準(zhǔn)備好了,select就會(huì)返回。這個(gè)時(shí)候用戶進(jìn)程再調(diào)用 read操作,將數(shù)據(jù)從 kernel拷貝到用戶進(jìn)程。這個(gè)圖和 blocking IO的圖其實(shí)并沒有太大的不同,事實(shí)上還更差一些。因?yàn)檫@里需要使用兩個(gè)系統(tǒng)調(diào)用(select和recvfrom),而 blocking IO只調(diào)用了一個(gè)系統(tǒng)調(diào)用(recvfrom)。但是,用 select的優(yōu)勢(shì)在于它可以同時(shí)處理多個(gè) connection。

強(qiáng)調(diào):

  • 如果處理的連接數(shù)不是很高的話,使用 select/epoll的 web server不一定比使用 multi-threading + blocking IO的 web server性能更好,可能延遲還更大。select/epoll的優(yōu)勢(shì)并不是對(duì)于單個(gè)連接能處理得更快,而是在于能處理更多的連接。
  • 在多路復(fù)用模型中,對(duì)于每一個(gè) socket,一般都設(shè)置成為 non-blocking,但是,如上圖所示,整個(gè)用戶的 process其實(shí)是一直被 block的。只不過 process是被 select這個(gè)函數(shù) block,而不是被 socket IO給 block。

結(jié)論: select的優(yōu)勢(shì)在于可以處理多個(gè)連接,不適用于單個(gè)連接。

select網(wǎng)絡(luò)IO模型示例:

from socket import *
import select

server = socket(AF_INET, SOCK_STREAM)
server.bind(("192.168.2.209",9900))
server.listen(5)
server.setblocking(False)

rlist = [server,]   # 收,存一堆conn,和server
wlist = []          # 發(fā),存一堆conn,一旦緩存區(qū)沒滿,證明可以發(fā)了
wdata = {}
while True:
    rl,wl,xl = select.select(rlist,wlist,[],1)   # 后面的列表是出異常的列表,1是超時(shí)時(shí)間,每隔1s問一遍操作系統(tǒng)
    print("rl",rl)      # 收的套接字
    print("wl",wl)      # 發(fā)的套接字

    for sock in rl:
        if sock == server:
            conn,addr = sock.accept()
            rlist.append(conn)
        else:
            try:    # recv的時(shí)候,客戶端單方面的把鏈接斷開,會(huì)拋異常,在這里捕獲下
                data = sock.recv(1024)
                if not data:    # 客戶端沒發(fā)數(shù)據(jù),跳過
                    sock.close()
                    rlist.remove(sock)
                    continue
                wlist.append(sock)
                wdata[sock] = data.upper()
            except Exception:
                sock.close()
                rlist.remove(sock)

    for sock in wl:
        data = wdata[sock]
        sock.send(data)
        wlist.remove(sock)
        wdata.pop(sock)

server.close()

select監(jiān)聽fd變化的過程分析:

用戶進(jìn)程創(chuàng)建socket對(duì)象,拷貝監(jiān)聽的fd到內(nèi)核空間,每一個(gè)fd會(huì)對(duì)應(yīng)一張系統(tǒng)文件表,內(nèi)核空間的fd響應(yīng)到數(shù)據(jù)后,
就會(huì)發(fā)送信號(hào)給用戶進(jìn)程數(shù)據(jù)已到;
用戶進(jìn)程再發(fā)送系統(tǒng)調(diào)用,比如(accept)將內(nèi)核空間的數(shù)據(jù)copy到用戶空間,同時(shí)作為接受數(shù)據(jù)端內(nèi)核空間的數(shù)據(jù)清除, 
這樣重新監(jiān)聽時(shí)fd再有新的數(shù)據(jù)又可以響應(yīng)到了(發(fā)送端因?yàn)榛赥CP協(xié)議所以需要收到應(yīng)答后才會(huì)清除)。

該模型的優(yōu)點(diǎn):

相比其他模型,使用select() 的事件驅(qū)動(dòng)模型只用單線程(進(jìn)程)執(zhí)行,占用資源少,不消耗太多 CPU,
同時(shí)能夠?yàn)槎嗫蛻舳颂峁┓?wù)。如果試圖建立一個(gè)簡(jiǎn)單的事件驅(qū)動(dòng)的服務(wù)器程序,這個(gè)模型有一定的參考價(jià)值。

該模型的缺點(diǎn):

首先select()接口并不是實(shí)現(xiàn)“事件驅(qū)動(dòng)”的最好選擇。因?yàn)楫?dāng)需要探測(cè)的句柄值較大時(shí),select()接口本身需要消耗大量時(shí)間去輪詢各個(gè)句柄。
很多操作系統(tǒng)提供了更為高效的接口,如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。如果需要實(shí)現(xiàn)更高效的服務(wù)器程序,
類似epoll這樣的接口更被推薦。# 遺憾的是不同的操作系統(tǒng)特供的epoll接口有很大差異,所以使用類似于epoll的接口實(shí)現(xiàn)具有較好跨平臺(tái)能力的服務(wù)器會(huì)比較困難。
其次,該模型將事件探測(cè)和事件響應(yīng)夾雜在一起,一旦事件響應(yīng)的執(zhí)行體龐大,則對(duì)整個(gè)模型是災(zāi)難性的。

2.5異步IO

Linux下的 asynchronous IO其實(shí)用得不多,從內(nèi)核2.6版本才開始引入。先看一下它的流程:

圖片圖片

用戶進(jìn)程發(fā)起 read操作之后,立刻就可以開始去做其它的事,而另一方面,從 kernel的角度,當(dāng)它受到一個(gè) asynchronous read之后,首先它會(huì)立刻返回,所以不會(huì)對(duì)用戶進(jìn)程產(chǎn)生任何 block。

然后,kernel會(huì)等待數(shù)據(jù)準(zhǔn)備完成,然后將數(shù)據(jù)拷貝到用戶內(nèi)存,當(dāng)這一切都完成之后,kernel會(huì)給用戶進(jìn)程發(fā)送一個(gè)signal,告訴它read操作完成了。

2.6IO模型比較分析

到目前為止,已經(jīng)將四個(gè) IO Model都介紹完了?,F(xiàn)在回過頭來回答最初的那幾個(gè)問題:blocking和non-blocking的區(qū)別在哪,synchronous IO和asynchronous IO的區(qū)別在哪。

先回答最簡(jiǎn)單的這個(gè):blocking vs non-blocking。前面的介紹中其實(shí)已經(jīng)很明確的說明了這兩者的區(qū)別。

調(diào)用 blocking IO會(huì)一直block住對(duì)應(yīng)的進(jìn)程直到操作完成,而non-blocking IO在kernel還準(zhǔn)備數(shù)據(jù)的情況下會(huì)立刻返回。

再說明 synchronous IO和asynchronous IO的區(qū)別之前,需要先給出兩者的定義。Stevens給出的定義(其實(shí)是POSIX的定義)是這樣子的:A synchronous I/O operation causes the requesting process to be blocked until that I/O operationcompletes;An asynchronous I/O operation does not cause the requesting process to be blocked;兩者的區(qū)別就在于 synchronous IO做”IO operation”的時(shí)候會(huì)將 process阻塞。

按照這個(gè)定義,四個(gè)IO模型可以分為兩大類,之前所述的 blocking IO,non-blocking IO,IO multiplexing都屬于synchronous IO這一類,而 asynchronous I/O后一類 。

有人可能會(huì)說,non-blocking IO并沒有被 block啊。這里有個(gè)非?!敖苹钡牡胤剑x中所指的”IO operation”是指真實(shí)的IO操作,就是例子中的 recvfrom這個(gè) system call。

non-blocking IO在執(zhí)行recvfrom這個(gè)system call的時(shí)候,如果kernel的數(shù)據(jù)沒有準(zhǔn)備好,這時(shí)候不會(huì)block進(jìn)程。

但是,當(dāng) kernel中數(shù)據(jù)準(zhǔn)備好的時(shí)候,recvfrom會(huì)將數(shù)據(jù)從 kernel拷貝到用戶內(nèi)存中,這個(gè)時(shí)候進(jìn)程是被 block了,在這段時(shí)間內(nèi),進(jìn)程是被 block的。

而 asynchronous IO則不一樣,當(dāng)進(jìn)程發(fā)起 IO操作之后,就直接返回再也不理睬了,直到 kernel發(fā)送一個(gè)信號(hào),告訴進(jìn)程說 IO完成。在這整個(gè)過程中,進(jìn)程完全沒有被 block。

各個(gè) IO Model的比較如圖所示:

圖片圖片

經(jīng)過上面的介紹,會(huì)發(fā)現(xiàn) non-blocking IO和 asynchronous IO的區(qū)別還是很明顯的。

在 non-blocking IO中,雖然進(jìn)程大部分時(shí)間都不會(huì)被 block,但是它仍然要求進(jìn)程去主動(dòng)的 check,并且當(dāng)數(shù)據(jù)準(zhǔn)備完成以后,也需要進(jìn)程主動(dòng)的再次調(diào)用 recvfrom來將數(shù)據(jù)拷貝到用戶內(nèi)存。

而 asynchronous IO則完全不同。它就像是用戶進(jìn)程將整個(gè) IO操作交給了他人(kernel)完成,然后他人做完后發(fā)信號(hào)通知。在此期間,用戶進(jìn)程不需要去檢查 IO操作的狀態(tài),也不需要主動(dòng)的去拷貝數(shù)據(jù)。

三、走進(jìn)IO模型的世界

3.1同步與異步:任務(wù)協(xié)作的不同策略

同步和異步,是 IO 模型中的兩種基本協(xié)作模式,它們決定了程序在處理任務(wù)時(shí)的執(zhí)行順序和響應(yīng)方式。簡(jiǎn)單來說,同步就像是一場(chǎng)按部就班的接力賽,每個(gè)選手必須等前一個(gè)選手完成交接后才能出發(fā);而異步則更像一場(chǎng)并行的馬拉松,選手們可以各自按照自己的節(jié)奏奔跑,不需要相互等待。

在日常生活中,同步和異步的例子隨處可見。比如你去餐廳點(diǎn)餐,如果你選擇坐在餐桌前等待服務(wù)員做好餐食并端上桌,這就是同步的方式 —— 你必須等待當(dāng)前的點(diǎn)餐任務(wù)完成(餐食做好并送達(dá)),才能進(jìn)行下一步行動(dòng),比如開始用餐。而如果你選擇點(diǎn)外賣,下單后你可以繼續(xù)做其他事情,等外賣送到時(shí)再去取餐,這便是異步的過程 —— 你不需要一直等待外賣送達(dá),在等待過程中可以并行處理其他事務(wù) 。

在代碼世界里,同步和異步的區(qū)別也十分明顯。以 Python 代碼為例:

import time


# 同步函數(shù)
def synchronous_task():
    print("同步任務(wù)開始")
    time.sleep(2)  # 模擬耗時(shí)操作
    print("同步任務(wù)結(jié)束")


# 異步函數(shù)
async def asynchronous_task():
    print("異步任務(wù)開始")
    await asyncio.sleep(2)  # 模擬耗時(shí)操作
    print("異步任務(wù)結(jié)束")


# 同步調(diào)用
print("開始同步調(diào)用")
synchronous_task()
print("同步調(diào)用結(jié)束")

# 異步調(diào)用(使用asyncio庫)
import asyncio

print("開始異步調(diào)用")
asyncio.run(asynchronous_task())
print("異步調(diào)用結(jié)束")

在上述代碼中,synchronous_task是一個(gè)同步函數(shù),當(dāng)調(diào)用它時(shí),程序會(huì)阻塞在time.sleep(2)這一行,直到 2 秒后函數(shù)執(zhí)行完畢,才會(huì)繼續(xù)執(zhí)行后續(xù)代碼。而asynchronous_task是一個(gè)異步函數(shù),await asyncio.sleep(2)雖然也模擬了 2 秒的耗時(shí)操作,但在這 2 秒內(nèi),程序并不會(huì)阻塞,而是可以去執(zhí)行其他異步任務(wù),當(dāng)asyncio.sleep(2)完成后,才會(huì)繼續(xù)執(zhí)行asynchronous_task函數(shù)后續(xù)的代碼。

在 IO 操作中,同步和異步的表現(xiàn)也截然不同。同步 IO 操作會(huì)導(dǎo)致程序阻塞,直到 IO 操作完成。例如,當(dāng)我們使用requests庫發(fā)送 HTTP 請(qǐng)求獲取網(wǎng)頁內(nèi)容時(shí):

import requests

response = requests.get('https://www.example.com')  # 同步請(qǐng)求,程序會(huì)阻塞在這里
print(response.text)

在這段代碼中,程序會(huì)在requests.get這一行阻塞,直到服務(wù)器響應(yīng)并返回網(wǎng)頁內(nèi)容,期間程序無法執(zhí)行其他任務(wù)。

而異步 IO 操作則不會(huì)阻塞程序。在 Python 的aiohttp庫中,我們可以實(shí)現(xiàn)異步的 HTTP 請(qǐng)求:

import aiohttp
import asyncio


async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()


async def main():
    async with aiohttp.ClientSession() as session:
        html = await fetch(session, 'https://www.example.com')
        print(html)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

在這個(gè)例子中,fetch函數(shù)使用aiohttp庫進(jìn)行異步的 HTTP 請(qǐng)求。在請(qǐng)求過程中,程序不會(huì)阻塞,可以繼續(xù)執(zhí)行其他異步任務(wù),當(dāng)請(qǐng)求完成后,才會(huì)處理返回的結(jié)果。這種方式大大提高了程序的執(zhí)行效率,尤其是在處理大量 IO 操作時(shí),能夠充分利用系統(tǒng)資源,減少等待時(shí)間。

3.2阻塞與非阻塞:等待數(shù)據(jù)的不同姿態(tài)

阻塞與非阻塞,描述的是程序在等待 IO 操作完成時(shí)的狀態(tài)。阻塞就像是在交通擁堵的道路上,車輛必須停下來等待前方道路暢通;非阻塞則如同在暢通無阻的高速公路上,車輛可以自由行駛,無需等待。

在 IO 操作中,阻塞 IO 是最常見的方式。當(dāng)一個(gè)進(jìn)程發(fā)起阻塞 IO 操作時(shí),如果數(shù)據(jù)尚未準(zhǔn)備好,該進(jìn)程會(huì)被掛起,進(jìn)入等待狀態(tài),直到數(shù)據(jù)準(zhǔn)備就緒并完成 IO 操作,進(jìn)程才會(huì)被喚醒繼續(xù)執(zhí)行 。例如,使用read函數(shù)從文件中讀取數(shù)據(jù):

try:
    with open('example.txt', 'r') as file:
        data = file.read()  # 阻塞IO操作,如果文件讀取緩慢,程序會(huì)在此阻塞
        print(data)
except FileNotFoundError:
    print("文件未找到")

在這段代碼中,如果example.txt文件較大或者磁盤讀取速度較慢,file.read()操作會(huì)阻塞程序,直到數(shù)據(jù)讀取完成。在阻塞期間,程序無法執(zhí)行其他任務(wù),CPU 資源被浪費(fèi)。

非阻塞 IO 則不同,當(dāng)一個(gè)進(jìn)程發(fā)起非阻塞 IO 操作時(shí),如果數(shù)據(jù)尚未準(zhǔn)備好,函數(shù)會(huì)立即返回一個(gè)錯(cuò)誤碼或狀態(tài)標(biāo)識(shí),而不會(huì)阻塞進(jìn)程。進(jìn)程可以繼續(xù)執(zhí)行其他任務(wù),然后通過輪詢等方式再次檢查數(shù)據(jù)是否準(zhǔn)備好 。在 Python 中,可以使用socket模塊實(shí)現(xiàn)非阻塞 IO:

import socket


# 創(chuàng)建一個(gè)socket對(duì)象
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False)  # 設(shè)置為非阻塞模式

try:
    # 嘗試連接服務(wù)器
    sock.connect(('www.example.com', 80))
except BlockingIOError:
    pass  # 連接未完成,繼續(xù)執(zhí)行其他任務(wù)

# 可以在此處執(zhí)行其他任務(wù)

# 輪詢檢查連接是否完成
while True:
    try:
        sock.send(b'GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n')
        break
    except BlockingIOError:
        continue

# 接收數(shù)據(jù)
data = sock.recv(1024)
print(data.decode('utf-8'))
sock.close()

在上述代碼中,sock.setblocking(False)將 socket 設(shè)置為非阻塞模式。在調(diào)用connect方法時(shí),如果連接不能立即建立,程序不會(huì)阻塞,而是繼續(xù)執(zhí)行后續(xù)代碼。然后通過輪詢的方式,不斷嘗試發(fā)送數(shù)據(jù),直到連接成功。這種方式提高了程序的并發(fā)性,使得程序在等待 IO 操作的過程中可以執(zhí)行其他任務(wù),充分利用了 CPU 資源 。

實(shí)際應(yīng)用中,阻塞 IO 適用于對(duì)響應(yīng)時(shí)間要求不高、IO 操作較少且數(shù)據(jù)量小的場(chǎng)景,例如簡(jiǎn)單的文件讀取、本地?cái)?shù)據(jù)庫查詢等。而非阻塞 IO 則更適合高并發(fā)、對(duì)響應(yīng)時(shí)間要求較高的場(chǎng)景,如網(wǎng)絡(luò)服務(wù)器的并發(fā)處理、實(shí)時(shí)數(shù)據(jù)采集等。在這些場(chǎng)景中,非阻塞 IO 能夠避免線程被大量阻塞,提高系統(tǒng)的吞吐量和響應(yīng)速度。

四、IO模型常見問題解析

4.1阻塞IO和非阻塞IO區(qū)別?

a.阻塞IO:在阻塞IO模型中,當(dāng)應(yīng)用程序發(fā)起一個(gè)IO操作(例如讀取文件或網(wǎng)絡(luò)數(shù)據(jù)),如果數(shù)據(jù)沒有準(zhǔn)備好,該操作會(huì)一直阻塞線程的執(zhí)行。線程會(huì)等待,直到數(shù)據(jù)準(zhǔn)備好后才能繼續(xù)執(zhí)行后續(xù)的代碼。這意味著在進(jìn)行阻塞IO操作期間,線程無法執(zhí)行其他任務(wù)。

b.非阻塞IO:相比之下,非阻塞IO模型允許應(yīng)用程序在發(fā)起一個(gè)IO操作后立即返回,并且無論數(shù)據(jù)是否準(zhǔn)備好都可以繼續(xù)執(zhí)行后續(xù)的代碼。如果數(shù)據(jù)還沒有準(zhǔn)備好,非阻塞IO模型會(huì)立即返回一個(gè)錯(cuò)誤碼或者空數(shù)據(jù)。應(yīng)用程序需要通過輪詢或者重復(fù)嘗試來檢查數(shù)據(jù)是否已經(jīng)準(zhǔn)備好。

非阻塞IO與阻塞IO相比具有以下特點(diǎn):

  1. 異步通知:在非阻塞IO模型中,應(yīng)用程序可以使用回調(diào)函數(shù)或者事件驅(qū)動(dòng)機(jī)制來處理IO操作完成的通知。當(dāng)數(shù)據(jù)準(zhǔn)備就緒時(shí),系統(tǒng)會(huì)通知應(yīng)用程序進(jìn)行讀取或?qū)懭氩僮鳌?/span>
  2. 非阻塞輪詢:為了檢查是否有數(shù)據(jù)可用,應(yīng)用程序需要通過輪詢的方式重復(fù)檢查IO狀態(tài)。這意味著在沒有數(shù)據(jù)可讀取時(shí),應(yīng)用程序需要不斷地輪詢以避免線程被阻塞。
  3. 處理更多任務(wù):由于非阻塞IO模型允許應(yīng)用程序同時(shí)處理多個(gè)任務(wù),在等待一個(gè)任務(wù)的結(jié)果時(shí)可以繼續(xù)執(zhí)行其他任務(wù)。這可以提高并發(fā)性和吞吐量。

4.2同步IO和異步IO區(qū)別?

同步IO(Synchronous IO):在同步IO模型中,應(yīng)用程序發(fā)起一個(gè)IO操作后會(huì)被阻塞等待操作完成。這意味著應(yīng)用程序必須等待IO操作完成后才能繼續(xù)執(zhí)行后續(xù)代碼。當(dāng)數(shù)據(jù)就緒時(shí),系統(tǒng)將數(shù)據(jù)傳輸給應(yīng)用程序并解除阻塞狀態(tài),應(yīng)用程序可以讀取或?qū)懭霐?shù)據(jù)。同步IO通常以函數(shù)調(diào)用的形式進(jìn)行,例如讀取文件、網(wǎng)絡(luò)請(qǐng)求等。

異步IO(Asynchronous IO):在異步IO模型中,應(yīng)用程序發(fā)起一個(gè)IO操作后立即返回,并不會(huì)阻塞等待結(jié)果返回。應(yīng)用程序可以繼續(xù)執(zhí)行后續(xù)代碼而無需等待。當(dāng)數(shù)據(jù)就緒時(shí),系統(tǒng)會(huì)通知應(yīng)用程序進(jìn)行讀取或?qū)懭氩僮?,并提供相?yīng)的回調(diào)函數(shù)或事件處理機(jī)制來處理完成通知。異步IO常見的實(shí)現(xiàn)方式包括回調(diào)函數(shù)、Promise/Future對(duì)象、協(xié)程/生成器等。

關(guān)鍵區(qū)別:

  • 同步IO需要阻塞等待結(jié)果返回,而異步IO則不需要。
  • 同步IO只能進(jìn)行一次性的單個(gè)操作,而異步IO可以同時(shí)處理多個(gè)任務(wù)。
  • 同步IO適合于簡(jiǎn)單和可預(yù)測(cè)的IO操作,而異步IO適合于需要同時(shí)處理大量任務(wù)和高并發(fā)的場(chǎng)景。

4.3信號(hào)驅(qū)動(dòng)IO和異步IO區(qū)別?

信號(hào)驅(qū)動(dòng)IO:信號(hào)驅(qū)動(dòng)IO使用操作系統(tǒng)提供的信號(hào)機(jī)制來通知應(yīng)用程序IO事件的發(fā)生。應(yīng)用程序首先通過調(diào)用sigaction()函數(shù)注冊(cè)一個(gè)信號(hào)處理函數(shù),并指定需要監(jiān)聽的IO事件,如可讀、可寫等。當(dāng)指定的事件發(fā)生時(shí),操作系統(tǒng)會(huì)發(fā)送相應(yīng)的信號(hào)給應(yīng)用程序,并執(zhí)行注冊(cè)的信號(hào)處理函數(shù)進(jìn)行后續(xù)操作。這種模型下,應(yīng)用程序可以繼續(xù)執(zhí)行其他任務(wù)而無需等待結(jié)果返回。

異步IO:異步IO則是通過操作系統(tǒng)提供的異步IO接口進(jìn)行實(shí)現(xiàn)。應(yīng)用程序通過向內(nèi)核發(fā)起異步IO請(qǐng)求,并傳入回調(diào)函數(shù)或其他形式的完成通知方式。當(dāng)數(shù)據(jù)就緒時(shí),內(nèi)核會(huì)主動(dòng)通知應(yīng)用程序并觸發(fā)回調(diào)函數(shù)或完成事件來進(jìn)行后續(xù)操作。在異步IO模型中,應(yīng)用程序可以并行地執(zhí)行其他任務(wù),而不需要一直等待結(jié)果返回。

關(guān)鍵區(qū)別:

  • 信號(hào)驅(qū)動(dòng)IO使用了操作系統(tǒng)提供的信號(hào)機(jī)制,而異步IO則使用了專門設(shè)計(jì)的異步接口。
  • 信號(hào)驅(qū)動(dòng)IO以信號(hào)為觸發(fā)點(diǎn)進(jìn)行通知,而異步IO則以回調(diào)函數(shù)或事件為觸發(fā)點(diǎn)進(jìn)行通知。
  • 信號(hào)驅(qū)動(dòng)IO的實(shí)現(xiàn)相對(duì)較為底層,需要應(yīng)用程序自行處理信號(hào)和回調(diào)函數(shù),而異步IO則提供了更高級(jí)的接口封裝。

選擇使用信號(hào)驅(qū)動(dòng)IO還是異步IO取決于具體應(yīng)用的需求和平臺(tái)支持情況。在某些平臺(tái)上,可能更適合使用信號(hào)驅(qū)動(dòng)IO來處理特定類型的事件。而在其他情況下,使用操作系統(tǒng)提供的異步IO接口能夠更方便地實(shí)現(xiàn)非阻塞IO操作。

4.4非阻塞IO是不是異步IO?

非阻塞IO(Non-blocking IO)和異步IO(Asynchronous IO)是兩種不同的IO操作模型,盡管它們都用于實(shí)現(xiàn)非阻塞式的IO處理,但在實(shí)現(xiàn)機(jī)制上有所不同。

非阻塞IO指的是應(yīng)用程序在進(jìn)行IO操作時(shí),如果沒有立即得到所需的數(shù)據(jù)或結(jié)果,不會(huì)一直等待而是立即返回一個(gè)錯(cuò)誤碼。應(yīng)用程序可以通過輪詢或選擇性地調(diào)用其他任務(wù)來繼續(xù)執(zhí)行,然后再次檢查IO是否就緒。這種模型下,應(yīng)用程序需要主動(dòng)查詢和處理IO狀態(tài)。

異步IO則是一種更高級(jí)的模型,在發(fā)起一個(gè)IO請(qǐng)求后,應(yīng)用程序無需一直等待結(jié)果返回,而是可以繼續(xù)執(zhí)行其他任務(wù)。當(dāng)數(shù)據(jù)就緒或操作完成時(shí),操作系統(tǒng)會(huì)通知應(yīng)用程序,并觸發(fā)事先注冊(cè)好的回調(diào)函數(shù)或者發(fā)送事件通知。這種模型下,應(yīng)用程序可以并行執(zhí)行多個(gè)任務(wù),并且在適當(dāng)時(shí)候接收通知。

盡管非阻塞IO和異步IO都能夠?qū)崿F(xiàn)非阻塞式的IO處理,但其實(shí)現(xiàn)方式和編程模型上有所差異。非阻塞IO需要應(yīng)用程序自行查詢和處理狀態(tài)變化,而異步IO則由操作系統(tǒng)負(fù)責(zé)監(jiān)測(cè)和通知狀態(tài)變化。因此,雖然非阻塞IO可以視為一種基本形式的異步IO,但兩者在編程模型和使用方式上還是有區(qū)別的。

五、優(yōu)化IO性能的實(shí)戰(zhàn)經(jīng)驗(yàn)

讓我們來看一個(gè)具體的案例,深入了解如何通過優(yōu)化 IO 模型提升系統(tǒng)性能 。某在線教育平臺(tái),其業(yè)務(wù)涵蓋了課程視頻播放、在線直播教學(xué)、用戶資料存儲(chǔ)等多個(gè)方面 。在平臺(tái)發(fā)展初期,用戶量較少,系統(tǒng)采用了同步阻塞 IO 模型來處理網(wǎng)絡(luò)請(qǐng)求和文件讀寫操作 ,這種簡(jiǎn)單直接的方式在當(dāng)時(shí)能夠滿足業(yè)務(wù)需求 。

然而,隨著平臺(tái)知名度的提升,用戶數(shù)量急劇增加,特別是在黃金時(shí)間段,大量用戶同時(shí)在線觀看課程視頻和參與直播,系統(tǒng)開始出現(xiàn)嚴(yán)重的性能問題 。視頻播放卡頓、直播延遲明顯,用戶投訴不斷 。經(jīng)過深入分析,發(fā)現(xiàn)同步阻塞 IO 模型在高并發(fā)情況下,線程頻繁阻塞,導(dǎo)致系統(tǒng)資源利用率低下,無法及時(shí)處理大量的請(qǐng)求 。

為了解決這些問題,開發(fā)團(tuán)隊(duì)決定對(duì)系統(tǒng)進(jìn)行 IO 模型的優(yōu)化 。他們將核心的網(wǎng)絡(luò)請(qǐng)求處理部分從同步阻塞 IO 模型切換為 IO 多路復(fù)用模型(采用 epoll 實(shí)現(xiàn)) ,并結(jié)合異步 IO 來處理文件讀寫操作,尤其是在視頻文件的讀取和緩存方面 。具體來說,在網(wǎng)絡(luò)請(qǐng)求處理上,通過 epoll 監(jiān)聽多個(gè)客戶端連接,當(dāng)有請(qǐng)求到達(dá)時(shí),迅速將其分配到線程池中進(jìn)行處理,大大提高了并發(fā)處理能力 。在視頻文件讀取時(shí),采用異步 IO,提前將熱門視頻文件異步加載到內(nèi)存緩存中,當(dāng)用戶請(qǐng)求播放視頻時(shí),可以直接從緩存中讀取數(shù)據(jù),減少了磁盤 IO 的等待時(shí)間 。

優(yōu)化后,系統(tǒng)性能得到了顯著提升 。視頻播放卡頓現(xiàn)象幾乎消失,直播延遲降低到了可接受的范圍內(nèi),用戶滿意度大幅提高 。同時(shí),服務(wù)器的資源利用率也得到了優(yōu)化,能夠以更少的硬件資源支持更多的用戶并發(fā)訪問 。從這個(gè)案例中,我們可以總結(jié)出以下經(jīng)驗(yàn)教訓(xùn):在系統(tǒng)設(shè)計(jì)初期,就應(yīng)該充分考慮業(yè)務(wù)的發(fā)展趨勢(shì)和可能面臨的并發(fā)場(chǎng)景,選擇合適的 IO 模型,避免后期因性能問題進(jìn)行大規(guī)模的架構(gòu)調(diào)整 。在優(yōu)化 IO 模型時(shí),要綜合考慮系統(tǒng)的各個(gè)環(huán)節(jié),不僅僅是網(wǎng)絡(luò)請(qǐng)求處理,還包括文件讀寫、數(shù)據(jù)緩存等,進(jìn)行全面的優(yōu)化才能取得最佳效果 。

責(zé)任編輯:武曉燕 來源: 深度Linux
相關(guān)推薦

2025-04-27 02:33:00

epoll核心機(jī)制服務(wù)器

2020-05-10 18:02:42

機(jī)器學(xué)習(xí)神經(jīng)網(wǎng)絡(luò)深度學(xué)習(xí)

2018-03-22 04:48:06

2019-12-10 14:50:54

創(chuàng)業(yè)技術(shù)CTO

2022-11-25 10:01:02

團(tuán)隊(duì)敏捷團(tuán)隊(duì)

2022-12-23 14:29:18

團(tuán)隊(duì)Leader

2017-07-27 09:54:06

MySQL數(shù)據(jù)庫

2017-08-31 16:26:06

數(shù)據(jù)庫MySQL命令

2020-02-07 11:07:53

數(shù)組鏈表單鏈表

2022-10-27 12:15:20

DLP技術(shù)數(shù)據(jù)自主保護(hù)

2019-05-07 17:31:57

華為

2020-12-07 14:48:15

Python開發(fā)工具

2022-10-13 17:43:10

MySQL存放數(shù)據(jù)

2021-01-04 05:53:35

MyBatis底層Java

2021-04-29 19:07:33

Redis演進(jìn)微服務(wù)

2020-09-23 12:32:18

網(wǎng)絡(luò)IOMySQL

2023-11-17 09:00:00

Kafka開發(fā)

2021-01-26 05:13:12

錕斤拷String 二進(jìn)制

2024-09-18 13:57:15

2022-03-03 08:01:41

阻塞與非阻塞同步與異步Netty
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)