漫畫(huà) | 看進(jìn)程小 P 講述它的網(wǎng)絡(luò)性能故事!
大家好,我是飛哥!
今天給大家?guī)?lái)的是一個(gè)漫畫(huà)故事!
01
大家好,我是一個(gè)進(jìn)程,我的名字的小 P。
我和很多其它小伙伴一樣,都由老大操作系統(tǒng)創(chuàng)建和管理。

要問(wèn)我是怎么來(lái)的,噓小點(diǎn)聲,不能讓那幫應(yīng)用開(kāi)發(fā)們聽(tīng)見(jiàn)。
其實(shí)就是內(nèi)核的開(kāi)發(fā)都認(rèn)為應(yīng)用開(kāi)發(fā)是傻逼,怕應(yīng)用開(kāi)發(fā)的代碼把服務(wù)器給搞壞。就設(shè)計(jì)了我們進(jìn)程出來(lái),專(zhuān)門(mén)運(yùn)行各種用戶(hù)態(tài)的代碼。

我們天然和內(nèi)核里的小伙伴們被隔離開(kāi)來(lái)。我們大部分時(shí)間都運(yùn)行在用戶(hù)態(tài),其它的兄弟們都一直運(yùn)行在內(nèi)核態(tài)。

我們沒(méi)有權(quán)限訪(fǎng)問(wèn)硬盤(pán)、網(wǎng)卡等設(shè)備。
如果我們需要這些功能的時(shí)候,需要通過(guò)系統(tǒng)調(diào)用先陷入到內(nèi)核態(tài)中。不過(guò)在陷入之前,系統(tǒng)調(diào)用入口要對(duì)我們執(zhí)行嚴(yán)格的安檢。

好了背景就是這樣,今天我給大家講講我和我的好朋友們之間是怎么配合處理網(wǎng)絡(luò)IO的。
02
我們進(jìn)程通過(guò)一個(gè)叫 socket 的哥們來(lái)和我們的用戶(hù)通信。但是實(shí)際上所有的 socket 以及整臺(tái)機(jī)器上的網(wǎng)絡(luò)包都是在內(nèi)核態(tài)來(lái)把控著的,我們只能拿到 socket 的編號(hào)。

在很久很久以前,我們一般只處理一條 TCP 連接。
我們通過(guò)一個(gè)叫 recvfrom 的系統(tǒng)調(diào)用來(lái)讀取我們的用戶(hù)發(fā)送過(guò)來(lái)的數(shù)據(jù)。假如運(yùn)氣好的話(huà),我們 recvfrom 的時(shí)候就可以把數(shù)據(jù)取走!

但是其實(shí)根本我們不知道用戶(hù)那邊啥時(shí)候給我們打數(shù)據(jù)包過(guò)來(lái),所以大部分情況下都不會(huì)運(yùn)氣那么好。
如果 read 的時(shí)候數(shù)據(jù)包沒(méi)有就緒,我們就得按照規(guī)矩主動(dòng)把 CPU 讓出來(lái)。

不過(guò)那時(shí)我們也確實(shí)只處理一條連接,連接上沒(méi)請(qǐng)求被阻塞掉也正常。
后來(lái)老板不斷的壓榨我們,讓我們一個(gè)進(jìn)程處理成百上千條連接。這時(shí)候 read 某條連接的時(shí)候,沒(méi)有數(shù)據(jù)就把我們掛起來(lái),我們哪兒受得了哇, 我們還有其它好多連接要處理呢。

而且頻繁的阻塞導(dǎo)致我的工作效率特別低下。 第一我們阻塞要花不少的時(shí)間保存我們當(dāng)前的工作狀態(tài),第二我們?cè)?L1/L2/L3 等 cache 里準(zhǔn)備了好多工作時(shí)要用的緩存這下全沒(méi)用了。
后來(lái)我們就給操作系統(tǒng)老大求了個(gè)情,要求把連接設(shè)置成非阻塞。
我:“哥,我只是來(lái)看看這條連接上有沒(méi)有數(shù)據(jù)哈,有就給我,沒(méi)有也別阻塞我可以不?”
操作系統(tǒng):“準(zhǔn)!”
這下就好了,我就可以用循環(huán)遍歷的方式把我所有的 socket 挨個(gè)到內(nèi)核中去看一遍。

但是我的問(wèn)題是我還是不知道用戶(hù)啥時(shí)候把數(shù)據(jù)發(fā)過(guò)來(lái)。如果沒(méi)有就緒的,那我只能就頻繁循環(huán)地不斷地來(lái)內(nèi)核詢(xún)問(wèn)。
“去看看 1 號(hào) socket 上有數(shù)據(jù)了沒(méi)?” “沒(méi)有”
“去看看 2 號(hào) socket 上有數(shù)據(jù)了沒(méi)?” “沒(méi)有”
“去看看 3 號(hào) socket 上有數(shù)據(jù)了沒(méi)?” “沒(méi)有”
...
“去看看 1 號(hào) socket 上有數(shù)據(jù)了沒(méi)?” “沒(méi)有”
“去看看 2 號(hào) socket 上有數(shù)據(jù)了沒(méi)?” “沒(méi)有”
“去看看 3 號(hào) socket 上有數(shù)據(jù)了沒(méi)?” “終于有啦”
干這事可特么把我累壞個(gè)屁的了,運(yùn)氣不好的時(shí)候我得訪(fǎng)問(wèn)成千上萬(wàn)次才能等到數(shù)據(jù)真正到來(lái)!
03
終于?。?!
后來(lái)操作系統(tǒng)老大在內(nèi)核態(tài)搞出了各種支持多路復(fù)用的新系統(tǒng)調(diào)用,它們是 select、poll、和 epoll。

不過(guò)嘿嘿,我最喜歡 epoll 這個(gè)新家伙。
我把需要觀察的 socket 都交給他,他替我都維護(hù)了起來(lái)了,據(jù)說(shuō)是內(nèi)部用了一個(gè)叫啥紅黑樹(shù)的高深技術(shù)。

不過(guò)其實(shí)愛(ài)用啥用啥,我只關(guān)注能解放我的體力就行。
我是終于不用再不斷的輪詢(xún)了,每次我想要知道哪個(gè) socket 上有請(qǐng)求的時(shí)候,直接進(jìn)入內(nèi)核態(tài)查看一下就緒隊(duì)列就行了。
這種爽歪歪的感覺(jué),你們真的無(wú)法體會(huì)。這就是我喜歡用這個(gè)家伙的原因。

如果請(qǐng)求很多,那我就可以一直 epoll_wait 獲取請(qǐng)求,一直處理,而不用阻塞。
直到時(shí)間片耗盡被再次丟到就緒隊(duì)列等待調(diào)度。
我的工作效率發(fā)揮到了極致,能處理的并發(fā)也越來(lái)越多。
在 redis 上,我最高能達(dá)到每秒 10W 的qps,怎么樣厲害吧!
不過(guò)在所有的連接上都沒(méi)有數(shù)據(jù)的時(shí)候,我也需要阻塞起來(lái)。
這個(gè)我是接受的,畢竟沒(méi)活兒干的時(shí)候還占著 CPU 資源,我也會(huì)覺(jué)得怪不好意思。

等網(wǎng)卡收到我的數(shù)據(jù)請(qǐng)求包的時(shí)候,我的另外一個(gè)兄弟軟中斷會(huì)從 epoll 的 wq 上找到我,把我叫醒。

不過(guò)我所謂的叫醒,其實(shí)只是推入到就緒隊(duì)列而已。真正的調(diào)度還得等進(jìn)程調(diào)度器老哥把我拉起來(lái)。
看,我和 epoll、軟中斷、進(jìn)程調(diào)度器等幾兄弟配合的是不是天衣無(wú)縫!
結(jié)語(yǔ)
理解 epoll 這種內(nèi)核級(jí)的技術(shù)會(huì)極大地提升你的內(nèi)功能力。之前飛哥從源碼級(jí)別講了一遍,反響非常不錯(cuò),在一萬(wàn)粉的情況下竟然達(dá)到了 5000 的閱讀數(shù)。
飛哥今天又改進(jìn)了一遍,把它抽象成了一個(gè)故事。以這樣一種全新的方式表達(dá),大家看起來(lái)應(yīng)該會(huì)更輕松。不知道大伙兒是否喜歡這篇新文,如果喜歡的話(huà)就幫飛哥三連走起,贊,再看,轉(zhuǎn)發(fā)!
另:飛哥建了技術(shù)群,另外我的電子書(shū)《理解了實(shí)現(xiàn)再談網(wǎng)絡(luò)性能》也分享到群里了。想要進(jìn)技術(shù)群或者領(lǐng)取這本電子書(shū)的同學(xué)加我微信: zhangyanfei748527
Github: https://github.com/yanfeizhang/coder-kung-fu
