日韩精品一区二区三区在线视频放-无码中文字幕V?一区二区-成年片免费观看视频-国内少妇人妻丰满av-国产精品中文字幕免费观看-亚洲成人久久一区二区三区-国内少妇偷人精品视频无缓冲-一区二区国产精品日本一区二区三区在线网

ARTICLE DETAIL

資訊詳情

深耕商務(wù)建站與企業(yè)官網(wǎng)運(yùn)營(yíng)的一線實(shí)戰(zhàn)洞察。

網(wǎng)絡(luò)IO模型詳解:阻塞IO、IO多路復(fù)用、epoll與異步IO選型

網(wǎng)絡(luò)IO模型詳解:阻塞IO、IO多路復(fù)用、epoll與異步IO選型 1. 把一次網(wǎng)絡(luò)讀寫拆到內(nèi)核里看線上有個(gè)服務(wù)QPS 壓到三千左右就上不去了機(jī)器 CPU 看著不高但top里sy系統(tǒng)態(tài)占比一直壓在 40% 以上load也不高連接數(shù)上不去也不掉。一開始懷疑是數(shù)據(jù)庫換了幾輪慢查詢優(yōu)化沒效果后來又懷疑帶寬不對(duì)抓包一看每個(gè)請(qǐng)求的往返都很正常。最后把線程棧 dump 下來發(fā)現(xiàn)幾百個(gè)工作線程全卡在同一個(gè)recv調(diào)用上一半在等數(shù)據(jù)、一半在等鎖。問題不在業(yè)務(wù)代碼在于整個(gè)服務(wù)的網(wǎng)絡(luò) IO 模型選錯(cuò)了——線程池 阻塞 IO 硬扛長(zhǎng)連接連接一多線程全變擺設(shè)。這就是我想聊的話題。網(wǎng)絡(luò) IO 模型說白了就一件事當(dāng)程序要讀網(wǎng)絡(luò)上的數(shù)據(jù)而數(shù)據(jù)還沒到的時(shí)候你的程序該干什么。是原地卡住等還是先回去干別的、過會(huì)兒再來問一遍還是干脆交給一個(gè)“哨兵”統(tǒng)一盯著誰的數(shù)據(jù)到了再通知誰。這五種做法就是常說的阻塞 IO、非阻塞 IO、IO 多路復(fù)用、信號(hào)驅(qū)動(dòng) IO、異步 IO。它們不是教科書里的名詞游戲每一種背后都對(duì)應(yīng)著一筆真實(shí)的成本賬線程數(shù)、內(nèi)存占用、上下文切換次數(shù)、以及你愿不愿意接受一定的編程復(fù)雜度。這篇文章寫給三類人。第一類是寫業(yè)務(wù)代碼的平時(shí)用框架用得很順但遇到連接數(shù)暴漲、超時(shí)莫名其妙、CPU 空轉(zhuǎn)這些問題時(shí)不知道該往哪個(gè)方向查第二類是準(zhǔn)備自己寫網(wǎng)絡(luò)服務(wù)的選型的時(shí)候需要知道每種模型的天花板在哪第三類是要應(yīng)對(duì)面試和架構(gòu)評(píng)審的光背“五種模型”的定義沒用得能說清楚為什么主流選擇是 epoll 那一套、為什么異步 IO 喊了這么多年落地卻不算多。下面的內(nèi)容我會(huì)從內(nèi)核里的一次讀操作開始拆一路講到代碼怎么寫、線上怎么排查。所有參數(shù)和步驟我都會(huì)給出為什么這么定而不是直接甩結(jié)論。1.1 從網(wǎng)卡到用戶緩沖區(qū)一次讀事件的完整路徑很多人對(duì)網(wǎng)絡(luò) IO 的理解停在“read 一個(gè) socket 拿到數(shù)據(jù)”中間發(fā)生了什么是一片模糊。這片模糊恰恰是后面所有模型差異的來源所以必須先講清楚。假設(shè)客戶端發(fā)來一個(gè)數(shù)據(jù)包。網(wǎng)卡收到電信號(hào)通過 DMA 把數(shù)據(jù)寫進(jìn)內(nèi)核里預(yù)先分配好的環(huán)形緩沖區(qū)ring buffer硬件隨即發(fā)一個(gè)中斷。內(nèi)核的中斷處理程序不會(huì)當(dāng)場(chǎng)把數(shù)據(jù)一路解析上去那樣太慢它只做最輕量的事把數(shù)據(jù)包掛到軟中斷待處理隊(duì)列然后盡快返回。真正的協(xié)議棧處理在軟中斷里完成逐層剝掉以太網(wǎng)頭、IP 頭、TCP 頭根據(jù)四元組找到對(duì)應(yīng)的 socket把載荷數(shù)據(jù)追加到該 socket 的接收緩沖區(qū)里。如果這個(gè) socket 上掛著一個(gè)正在睡眠等待的進(jìn)程內(nèi)核喚醒它把它從等待隊(duì)列里挪回可運(yùn)行隊(duì)列。到這一步為止數(shù)據(jù)已經(jīng)在內(nèi)核緩沖區(qū)里躺著了但你的程序還沒看見它。程序要真正拿到這段字節(jié)還需要一次數(shù)據(jù)拷貝從內(nèi)核空間的 socket 接收緩沖區(qū)復(fù)制到用戶空間你聲明的那塊內(nèi)存里。這次拷貝是 CPU 參與的繞不開除非用零拷貝那一套但那是另一個(gè)話題普通 socket 讀寫還是要拷。所以一次完整的網(wǎng)絡(luò)讀可以切成兩個(gè)階段階段一等待數(shù)據(jù)就緒數(shù)據(jù)從網(wǎng)卡到內(nèi)核緩沖區(qū)期間你的程序可能什么都做不了。階段二數(shù)據(jù)拷貝從內(nèi)核緩沖區(qū)到用戶緩沖區(qū)期間 CPU 在干活但你的程序依然處于系統(tǒng)調(diào)用之中。記住這兩個(gè)階段后面五種模型的區(qū)別全都在于“這兩個(gè)階段里進(jìn)程到底在不在等、等在哪”。我在實(shí)際調(diào)試中經(jīng)常用一個(gè)很土的辦法驗(yàn)證這個(gè)路徑在服務(wù)端recv之前加日志然后用ss -m看對(duì)應(yīng)連接的接收隊(duì)列。如果 Recv-Q 有值而你的程序還沒讀到說明數(shù)據(jù)已經(jīng)在內(nèi)核里了程序卡在階段二的入口如果 Recv-Q 是 0 而程序還在阻塞那就是階段一沒等到東西。這個(gè)判斷方法很粗糙但定位方向極其有效比盲目加日志快得多。順帶說一個(gè)容易忽略的點(diǎn)TCP 的接收窗口和內(nèi)核接收緩沖區(qū)是綁定的。如果你的程序讀得慢內(nèi)核緩沖區(qū)堆滿接收窗口就會(huì)縮小到零發(fā)送方被流控卡住。這時(shí)候表現(xiàn)出的“網(wǎng)絡(luò)慢”其實(shí)是應(yīng)用層讀得太慢不是帶寬問題。我在一個(gè)數(shù)據(jù)同步的場(chǎng)景里就遇到過對(duì)方一直抱怨傳輸慢最后發(fā)現(xiàn)是消費(fèi)端每次只讀 1KB內(nèi)核緩沖區(qū)堆了 200KB 讀不出去。1.2 兩個(gè)階段的劃分決定了所有模型的差異把兩個(gè)階段攤開五種模型的差別就一目了然了我先用一段話把它們串起來后面再逐個(gè)拆。阻塞 IO兩個(gè)階段都在等。發(fā)起調(diào)用后進(jìn)程被掛起數(shù)據(jù)到了、拷貝完了才返回。等待期間這個(gè)線程什么都干不了。非阻塞 IO第一個(gè)階段不等。調(diào)用立刻返回沒數(shù)據(jù)就告訴你沒數(shù)據(jù)errno 為 EAGAIN程序自己決定過多久再來問一次。第二個(gè)階段還是同步的數(shù)據(jù)拷貝時(shí)進(jìn)程仍然在系統(tǒng)調(diào)用里。IO 多路復(fù)用第一個(gè)階段交給一個(gè)專門的系統(tǒng)調(diào)用去等。你一次性把一批 fd 注冊(cè)進(jìn)去這個(gè)調(diào)用會(huì)阻塞在那里直到其中任意一個(gè)可讀可寫。也就是說一個(gè)線程能盯住成千上萬個(gè)連接。但注意它只是幫你“等到”拷貝還是你自己調(diào)recv來做所以本質(zhì)上仍然是同步 IO。信號(hào)驅(qū)動(dòng) IO第一個(gè)階段連等都不用等。注冊(cè)一個(gè)信號(hào)處理函數(shù)數(shù)據(jù)就緒時(shí)內(nèi)核發(fā) SIGIO 給你你在信號(hào)處理里再調(diào)recv。理論上很優(yōu)雅實(shí)際用得極少后面會(huì)說原因。異步 IO兩個(gè)階段都不需要你管。你發(fā)起一個(gè)讀請(qǐng)求附帶一塊緩沖區(qū)和回調(diào)或者一個(gè)完成事件然后立刻返回。內(nèi)核把數(shù)據(jù)等到、并且拷貝進(jìn)你給的緩沖區(qū)之后才通知你“完事了”。這是真正的異步和前面四種有本質(zhì)區(qū)別。這里有個(gè)特別常見的誤解就是把 IO 多路復(fù)用當(dāng)成異步 IO。很多文章寫著“NIO 就是非阻塞異步”這個(gè)說法很不嚴(yán)謹(jǐn)。Java 的 NIO 名字叫 Non-blocking I/O但它用的是多路復(fù)用底層是 epollSelector.select()返回可讀之后你還是得自己調(diào)channel.read()把數(shù)據(jù)從內(nèi)核拷到 ByteBuffer 里這一步是同步的。真正對(duì)標(biāo)異步 IO 的是 JDK 7 引入的 NIO.2AIO或者 Linux 上的 io_uring。判斷一個(gè) IO 是同步還是異步標(biāo)準(zhǔn)只有一條數(shù)據(jù)從內(nèi)核緩沖區(qū)到用戶緩沖區(qū)這一步是誰在做進(jìn)程有沒有被阻塞。只要這一步是進(jìn)程自己調(diào)用的那就是同步不管前面等得多花哨。這條標(biāo)準(zhǔn)我在評(píng)審方案時(shí)反復(fù)用能擋掉一大堆名詞包裝。理解了這層再看一個(gè)具體的現(xiàn)象為什么有些服務(wù)線程數(shù)不多但能扛上萬連接因?yàn)樗央A段一交給了 epoll一個(gè)線程盯幾千個(gè) fd只有真正有數(shù)據(jù)的連接才會(huì)被分配線程去處理階段二。這就是事件驅(qū)動(dòng)的基本盤。反過來如果服務(wù)是一連接一線程的阻塞模型一萬連接就是一萬個(gè)線程光線程棧默認(rèn) 8MB實(shí)際虛擬內(nèi)存物理頁按需分配和內(nèi)核調(diào)度開銷就能把機(jī)器拖垮。2. 五種網(wǎng)絡(luò) IO 模型的逐個(gè)拆解與適用邊界這一段是全文的主體我盡量把每種模型“為什么是這樣設(shè)計(jì)的”“在什么情況下會(huì)翻車”講透。不講空話每個(gè)結(jié)論都配一個(gè)能驗(yàn)證它的現(xiàn)象。2.1 阻塞 IO最簡(jiǎn)單也最容易被誤用的模型阻塞 IO 的調(diào)用鏈非常直白accept沒有新連接就卡住recv沒有數(shù)據(jù)就卡住send發(fā)送緩沖區(qū)滿了也卡住。進(jìn)程被放進(jìn) socket 的等待隊(duì)列狀態(tài)變成可中斷睡眠CPU 完全不占用。數(shù)據(jù)到了內(nèi)核喚醒它它繼續(xù)往下走。它的優(yōu)點(diǎn)就一個(gè)字簡(jiǎn)單。代碼是線性的讀完了就處理處理完了就寫回去心智負(fù)擔(dān)為零。所以它在兩類場(chǎng)景里活得很好。一類是連接數(shù)很少的內(nèi)部服務(wù)比如一個(gè)管理后臺(tái)幾十個(gè)并發(fā)用阻塞 IO 加線程池完全夠用沒必要上事件驅(qū)動(dòng)。另一類是配合“一連接一線程”的模型做短連接服務(wù)請(qǐng)求處理很快連接生命周期很短線程復(fù)用池子能兜住。但它有兩個(gè)硬傷第一個(gè)是線程和連接綁定。假設(shè)每個(gè)連接平均每秒只有 10 個(gè)請(qǐng)求單次處理耗時(shí) 5ms那么這個(gè)連接 99.5% 的時(shí)間是空閑的可它依然占著一個(gè)線程。線程本身的開銷分幾塊內(nèi)核里的 task_struct、用戶態(tài)的棧、調(diào)度器維護(hù)的運(yùn)行隊(duì)列節(jié)點(diǎn)還有上下文切換的成本。線程數(shù)上千之后調(diào)度器的紅黑樹操作和緩存失效會(huì)明顯拖慢整體sy占比會(huì)明顯上升。第二個(gè)硬傷是阻塞調(diào)用會(huì)傳染。你在一個(gè)請(qǐng)求里串行調(diào)了三個(gè)下游服務(wù)每個(gè)都可能卡住最壞情況下這個(gè)線程被占用的時(shí)間等于三個(gè)超時(shí)之和。如果下游抖動(dòng)線程池很快被耗干新請(qǐng)求進(jìn)來排隊(duì)超時(shí)擴(kuò)散最終整個(gè)服務(wù)雪崩。我做壓測(cè)時(shí)見過一個(gè)典型現(xiàn)象并發(fā)從 500 加到 800TP99 從 80ms 跳到 3 秒但 CPU 使用率反而下降——這就是線程池打滿、大量線程在等下游的典型特征。要緩解就得給每個(gè)阻塞調(diào)用設(shè)超時(shí)并且用SO_RCVTIMEO或應(yīng)用層超時(shí)把等待時(shí)間卡住。但超時(shí)只是止血不解決根本問題你依然為每個(gè)連接付出了線程成本。一個(gè)經(jīng)驗(yàn)判斷如果你的服務(wù)連接數(shù)長(zhǎng)期在幾百以內(nèi)、單次處理耗時(shí)可控、沒有大量長(zhǎng)連接比如推送、IM、訂閱阻塞 IO 加線程池是最省心的選擇。別為了“先進(jìn)”去上事件驅(qū)動(dòng)事件驅(qū)動(dòng)的代碼復(fù)雜度和調(diào)試難度是另一個(gè)量級(jí)。還有一個(gè)坑要說清楚阻塞 IO 在accept上也有講究。監(jiān)聽 socket 默認(rèn)是阻塞的如果accept返回后你處理連接花了很久期間新連接會(huì)堆在 backlog 隊(duì)列里。backlog 大小由listen(fd, backlog)指定實(shí)際生效值還受net.core.somaxconn限制。這兩個(gè)值不一致的時(shí)候以小的為準(zhǔn)。很多服務(wù)連接建立慢、握手超時(shí)就是 backlog 給太小了。生產(chǎn)環(huán)境我會(huì)把somaxconn調(diào)到 4096 以上listen傳 1024 起步具體看連接建立速率。2.2 非阻塞 IO輪詢的代價(jià)與適用場(chǎng)景非阻塞 IO 就是把 fd 設(shè)成O_NONBLOCK然后調(diào)用立刻返回。沒數(shù)據(jù)時(shí)返回 -1 并置 errno 為 EAGAIN 或 EWOULDBLOCK在 Linux 上這兩個(gè)值相同。程序可以立刻去干別的事過會(huì)兒再來問。聽起來很美好但如果你真的寫一個(gè)while(true) { n recv(fd, buf, len, 0); if (n 0) break; }的循環(huán)你會(huì)發(fā)現(xiàn) CPU 直接 100%而且大部分時(shí)間在做無意義的系統(tǒng)調(diào)用。這就是忙輪詢是最糟糕的用法。系統(tǒng)調(diào)用本身有成本要從用戶態(tài)切到內(nèi)核態(tài)保存寄存器檢查權(quán)限處理完再切回來。一次可能幾百納秒但一秒做幾十萬次就是實(shí)打?qū)嵉拈_銷且這些開銷全部記賬在sy上。所以非阻塞 IO 單獨(dú)用幾乎沒有意義它必須和別的東西配合。最常見的配合方式有三種第一種是和多路復(fù)用配合這也是絕大多數(shù)場(chǎng)景的做法。先用 epoll 等到某個(gè) fd 就緒再對(duì)這個(gè) fd 用非阻塞方式讀寫。為什么就緒了還要非阻塞因?yàn)?epoll 的通知是“曾經(jīng)可讀”等你真正去讀的時(shí)候可能已經(jīng)被別的線程讀走了或者數(shù)據(jù)只到了一部分。非阻塞在這里是兜底保證你永遠(yuǎn)不會(huì)因?yàn)橐淮巫x寫把線程卡死。第二種是帶退避的輪詢。每次 EAGAIN 之后 sleep 一小段時(shí)間再來。這種方式在極低延遲要求的場(chǎng)景下偶爾用比如某些交易系統(tǒng)的行情拉取但絕大多數(shù)業(yè)務(wù)沒必要。退避時(shí)間的選擇很講究太短沒意義太長(zhǎng)丟延遲。第三種是非阻塞加自旋加協(xié)程調(diào)度。協(xié)程庫在遇到 EAGAIN 時(shí)會(huì)把當(dāng)前協(xié)程掛起把 CPU 讓給別的協(xié)程等 fd 就緒了再喚醒。對(duì)上層來說代碼是同步寫法實(shí)際執(zhí)行是事件驅(qū)動(dòng)的。這也是現(xiàn)在很多語言 runtime 的做法。這里必須點(diǎn)出一個(gè)常見誤區(qū)把 socket 設(shè)成非阻塞并不等于性能變好。非阻塞只是改變了“等待”的方式并沒有減少數(shù)據(jù)拷貝也沒有減少系統(tǒng)調(diào)用次數(shù)。如果你的程序邏輯還是“讀一次處理一次”非阻塞帶來的只是更復(fù)雜的錯(cuò)誤處理代碼。真正帶來吞吐提升的是多路復(fù)用帶來的“一個(gè)線程管多連接”。我在實(shí)際項(xiàng)目里給 fd 設(shè)非阻塞的時(shí)機(jī)很固定accept返回的新連接立刻設(shè)connect發(fā)起前設(shè)非阻塞 connect 可以避免三次握手期間卡住然后用 epoll 監(jiān)聽可寫事件判斷連接是否建立成功監(jiān)聽 fd 本身視模型而定。這個(gè)順序不能亂尤其是connect如果先連再設(shè)非阻塞那在連接超時(shí)的情況下你還是會(huì)被卡住。2.3 IO 多路復(fù)用select、poll、epoll 的三代演進(jìn)這一節(jié)是重點(diǎn)因?yàn)樗悄壳皫缀跛懈咝阅芫W(wǎng)絡(luò)服務(wù)的底座。select是最早的方案。它用一個(gè)位圖fd_set表示要監(jiān)聽哪些 fd調(diào)用時(shí)把這個(gè)位圖從用戶態(tài)拷到內(nèi)核態(tài)內(nèi)核遍歷一遍看哪些 fd 有事件把有事件的 fd 在位圖里標(biāo)記出來再拷回用戶態(tài)。程序拿到返回值后還得自己再遍歷一遍位圖找出哪些位被置位了。它有三個(gè)致命問題。第一fd 數(shù)量上限是FD_SETSIZE通常是 1024改這個(gè)值要重新編譯內(nèi)核不現(xiàn)實(shí)。第二每次調(diào)用都要把整個(gè)位圖拷進(jìn)拷出fd 越多拷貝越大這是 O(n) 的內(nèi)存拷貝。第三內(nèi)核要線性掃描所有 fd用戶態(tài)也要線性掃描兩次 O(n)。所以 select 在 fd 上千之后性能急劇下滑而且每次調(diào)用完都得重置 fd_set很容易寫錯(cuò)。poll換了個(gè)數(shù)據(jù)結(jié)構(gòu)用pollfd數(shù)組每個(gè)元素包含 fd、關(guān)心的事件、返回的事件。它解決了 1024 上限的問題上限變成內(nèi)存和ulimit -n決定的也解決了每次要重置的問題events 和 revents 分離。但本質(zhì)沒變還是每次全量拷貝還是內(nèi)核線性掃描還是用戶態(tài)線性掃描。fd 到一萬這個(gè)量級(jí)poll 的拷貝和掃描開銷就很可觀了。epoll是 Linux 2.6 引入的思路完全不一樣把“注冊(cè)”和“等待”拆成兩個(gè)操作。epoll_create在內(nèi)核里創(chuàng)建一個(gè) eventpoll 對(duì)象里面維護(hù)兩塊核心結(jié)構(gòu)一棵紅黑樹用來存所有注冊(cè)進(jìn)來的 fd 及其關(guān)注的事件一個(gè)就緒鏈表用來存已經(jīng)有事件發(fā)生的 fd。紅黑樹保證增刪改是 O(log n)就緒鏈表保證取事件是 O(1)。epoll_ctl負(fù)責(zé)往紅黑樹里加、刪、改。關(guān)鍵是注冊(cè)的時(shí)候內(nèi)核會(huì)給這個(gè) fd 掛一個(gè)回調(diào)當(dāng)該 fd 對(duì)應(yīng)的數(shù)據(jù)到達(dá)、觸發(fā)軟中斷處理時(shí)內(nèi)核直接通過這個(gè)回調(diào)把 fd 塞進(jìn)就緒鏈表。也就是說事件是被主動(dòng)推送過來的不需要掃描。epoll_wait只做一件事看就緒鏈表空不空??站退强站桶焰湵砝锏墓?jié)點(diǎn)拷到一個(gè)用戶傳入的數(shù)組里返回同時(shí)把這個(gè)節(jié)點(diǎn)從鏈表摘掉水平觸發(fā)模式下會(huì)重新掛回去后面細(xì)說。返回值是就緒的 fd 個(gè)數(shù)用戶只需要處理這 n 個(gè)不需要遍歷全部。這三個(gè)改進(jìn)疊加起來效果是注冊(cè)一次長(zhǎng)期有效事件驅(qū)動(dòng)無需掃描只返回就緒的處理量與總連接數(shù)無關(guān)。這就是所謂的 O(1) 事件通知嚴(yán)格說是 O(就緒數(shù))不是 O(總連接數(shù))。但 epoll 不是沒有成本有幾個(gè)點(diǎn)必須知道第一epoll_ctl的調(diào)用是有開銷的比 select 的一次性注冊(cè)攤薄下來要貴。所以如果你的場(chǎng)景是“一批 fd 用一次就扔”比如處理一批短連接select 或 poll 反而可能更劃算。epoll 的優(yōu)勢(shì)在長(zhǎng)連接、高頻復(fù)用。第二epoll 的紅黑樹節(jié)點(diǎn)和就緒鏈表節(jié)點(diǎn)都是內(nèi)核內(nèi)存連接數(shù)巨大時(shí)這部分內(nèi)存不可忽視。粗略估算每個(gè)被監(jiān)聽的 fd 在內(nèi)核里額外占幾百字節(jié)不同內(nèi)核版本有差異量級(jí)在數(shù)百字節(jié)十萬連接就是幾十 MB 的額外開銷。做容量規(guī)劃時(shí)要把這塊算進(jìn)去。第三epoll_wait返回的數(shù)組大小要合理。傳 1 意味著每次只能取一個(gè)就緒事件雖然不會(huì)丟但會(huì)多調(diào)用很多次傳太大又浪費(fèi)?;蚨芽臻g。我一般按min(預(yù)期并發(fā)就緒數(shù), 1024)來定實(shí)測(cè)下來 512 到 1024 是個(gè)舒適區(qū)。第四跨進(jìn)程或者多線程同時(shí)epoll_wait同一個(gè) epoll 實(shí)例會(huì)涉及喚醒競(jìng)爭(zhēng)也就是常說的驚群。Linux 4.5 引入的EPOLLEXCLUSIVE標(biāo)志可以緩解同一時(shí)刻只喚醒一個(gè)等待者另一個(gè)思路是用SO_REUSEPORT讓每個(gè)線程各自持有獨(dú)立的監(jiān)聽 fd 和獨(dú)立的 epoll 實(shí)例。這兩套方案我在不同項(xiàng)目里都用過后者在多核下擴(kuò)展性更好但連接分配的均衡性依賴內(nèi)核哈希不能保證完全均勻。2.4 信號(hào)驅(qū)動(dòng)與異步IO一條容易被混淆的邊界信號(hào)驅(qū)動(dòng) IO的流程是給 socket 設(shè)置屬主并注冊(cè) SIGIO 處理函數(shù)開啟信號(hào)驅(qū)動(dòng)然后進(jìn)程就可以去干別的。數(shù)據(jù)到達(dá)時(shí)內(nèi)核發(fā) SIGIO進(jìn)程在信號(hào)處理函數(shù)里調(diào)recv讀數(shù)據(jù)。它的問題很實(shí)際。首先信號(hào)處理函數(shù)里能做的事情非常受限很多庫函數(shù)不是異步信號(hào)安全的一不小心就死鎖。其次信號(hào)是進(jìn)程級(jí)的多個(gè) fd 共用一個(gè)信號(hào)處理函數(shù)里很難判斷到底是哪個(gè) fd 就緒還得額外維護(hù)映射。第三信號(hào)可能丟失或合并高頻事件下不可靠。所以這套東西除了少數(shù)嵌入式場(chǎng)景和歷史遺留代碼基本沒人用了。你在網(wǎng)上看到它的介紹知道原理就夠了不用花時(shí)間實(shí)踐。異步 IO才是真正意義上的“不等”。你提交一個(gè)讀請(qǐng)求附帶緩沖區(qū)和完成通知方式立刻返回。內(nèi)核負(fù)責(zé)等數(shù)據(jù)、負(fù)責(zé)拷貝全干完了再告訴你。整個(gè)過程中你的進(jìn)程一次都沒有被阻塞在 IO 上。Linux 上的實(shí)現(xiàn)經(jīng)歷了兩個(gè)階段。早期是 POSIX AIOglibc 的用戶態(tài)模擬和內(nèi)核 AIOio_submit/io_getevents前者本質(zhì)是用線程池模擬后者限制很多只支持 O_DIRECT、對(duì)普通文件的支持不好、網(wǎng)絡(luò) IO 支持有限一直沒能普及。近些年 io_uring 出來了用一對(duì)共享內(nèi)存的環(huán)形隊(duì)列提交隊(duì)列和完成隊(duì)列來做用戶態(tài)和內(nèi)核態(tài)的通信既支持文件 IO 也支持網(wǎng)絡(luò) IO還能批量提交通過IORING_SETUP_SQPOLL讓內(nèi)核線程輪詢提交隊(duì)列進(jìn)一步減少系統(tǒng)調(diào)用。它的性能數(shù)據(jù)很漂亮但生態(tài)還在成熟過程中各種語言的綁定質(zhì)量參差不齊線上大規(guī)模用的案例還不算多。至于 Java 的 AIONIO.2在 Linux 上底層用的是 epoll 模擬JDK 內(nèi)部有專門的實(shí)現(xiàn)并不是真正的內(nèi)核異步 IO所以它的“異步”更多是 API 層面的封裝。這也是為什么很多 Java 服務(wù)依然選 Netty 而不是 AIO 的原因之一。選型上的建議除非你有非常明確的理由比如已經(jīng)在用 io_uring 做存儲(chǔ)棧、或者對(duì)系統(tǒng)調(diào)用次數(shù)極度敏感網(wǎng)絡(luò)服務(wù)的默認(rèn)答案就是 epoll 這一套多路復(fù)用加事件循環(huán)。異步 IO 的收益在當(dāng)前階段還不足以抵消它的復(fù)雜度和生態(tài)風(fēng)險(xiǎn)。下面這張表是我自己在做方案對(duì)比時(shí)用的把關(guān)鍵維度擺在一起看比讀十篇介紹文章都直觀。模型階段一等數(shù)據(jù)階段二拷貝單線程可管理連接數(shù)編程復(fù)雜度典型適用場(chǎng)景阻塞 IO進(jìn)程阻塞進(jìn)程阻塞1與線程綁定低連接少、內(nèi)部服務(wù)、短連接非阻塞 IO輪詢不阻塞進(jìn)程阻塞取決于輪詢策略中一般需配合其他模型使用IO 多路復(fù)用由內(nèi)核代為等待進(jìn)程阻塞數(shù)千至數(shù)十萬中高長(zhǎng)連接網(wǎng)關(guān)、推送、代理信號(hào)驅(qū)動(dòng) IO信號(hào)通知不阻塞進(jìn)程阻塞理論上多實(shí)際受限高極少使用異步 IO內(nèi)核完成內(nèi)核完成高高高吞吐存儲(chǔ)、特定場(chǎng)景3. 把 epoll 真正跑起來從調(diào)用到事件循環(huán)理論講完落到代碼。這一段我會(huì)把 epoll 的關(guān)鍵調(diào)用拆開說明每個(gè)參數(shù)為什么這么填然后給出一個(gè)能直接編譯運(yùn)行的最小事件循環(huán)再對(duì)照 Java NIO 的寫法方便不同技術(shù)棧的讀者對(duì)照理解。3.1 epoll 三個(gè)調(diào)用背后的數(shù)據(jù)結(jié)構(gòu)與參數(shù)含義epoll_create1(EPOLL_CLOEXEC)創(chuàng)建一個(gè) epoll 實(shí)例返回一個(gè) fd。參數(shù)用EPOLL_CLOEXEC而不是老的epoll_create(size)原因是前者會(huì)在exec新程序時(shí)自動(dòng)關(guān)閉這個(gè) fd避免子進(jìn)程繼承一堆用不上的句柄。老接口的size參數(shù)在內(nèi)核 2.6.8 之后已經(jīng)沒意義了只是兼容保留不要以為它真的能限制什么。epoll_ctl(epfd, op, fd, event)是增刪改。op有 ADD、DEL、MOD 三種。event里兩個(gè)字段最要緊events是關(guān)注的事件掩碼data是一個(gè)聯(lián)合體可以存 fd 也可以存指針。我強(qiáng)烈建議存指針而不是 fd因?yàn)樵诟卟l(fā)場(chǎng)景下你需要通過這個(gè)字段快速定位到連接對(duì)象包含緩沖區(qū)、狀態(tài)機(jī)、超時(shí)時(shí)間等存 fd 的話還要再查一次哈希表白白多一次間接尋址。存指針的做法是把連接對(duì)象的地址塞進(jìn)data.ptr事件回來時(shí)直接強(qiáng)轉(zhuǎn)。events里常填的EPOLLIN可讀、EPOLLOUT可寫、EPOLLERR錯(cuò)誤、EPOLLHUP掛斷、EPOLLRDHUP對(duì)端關(guān)閉寫端這個(gè)很有用能提前感知半關(guān)閉。注意EPOLLERR和EPOLLHUP是永遠(yuǎn)會(huì)被上報(bào)的不需要你顯式注冊(cè)但你應(yīng)該在事件處理里優(yōu)先檢查它們。epoll_wait(epfd, events, maxevents, timeout)是等待。timeout單位毫秒-1 表示永久阻塞0 表示立即返回純輪詢正數(shù)表示最多等這么久。這里有個(gè)實(shí)操細(xì)節(jié)很多事件循環(huán)會(huì)把 timeout 設(shè)成一個(gè)較小的值比如 100ms 或者按最近的定時(shí)器到期時(shí)間算目的是定期醒來處理定時(shí)任務(wù)連接超時(shí)、心跳檢測(cè)。如果設(shè) -1那定時(shí)任務(wù)就永遠(yuǎn)沒機(jī)會(huì)執(zhí)行。我一般的做法是算出“距離下一個(gè)定時(shí)器還有多少毫秒”作為 timeout 傳進(jìn)去這樣既不空轉(zhuǎn)也不誤事。調(diào)用返回值 n 表示就緒的事件數(shù)接下來遍歷events[0..n-1]逐個(gè)處理。處理時(shí)有個(gè)原則單個(gè)事件的處理時(shí)間要盡可能短。如果某個(gè)連接的業(yè)務(wù)處理很重不要在這個(gè)循環(huán)里做完應(yīng)該丟給業(yè)務(wù)線程池。否則一個(gè)慢請(qǐng)求會(huì)拖住整個(gè)事件循環(huán)所有連接都受影響。這是事件驅(qū)動(dòng)模型最容易被違反的原則也是很多自研框架的性能瓶頸所在。3.2 水平觸發(fā)與邊緣觸發(fā)丟數(shù)據(jù)的坑都在這兒這是 epoll 里最容易出問題的地方我必須單獨(dú)講。水平觸發(fā)LT默認(rèn)的語義是只要 fd 的接收緩沖區(qū)里還有數(shù)據(jù)沒讀完每次epoll_wait都會(huì)告訴你這個(gè) fd 可讀。也就是說你讀了一半就走了下次還會(huì)被通知不會(huì)丟。這個(gè)模式寫起來安全因?yàn)槟隳呐轮蛔x了一個(gè)字節(jié)剩下的下次還能拿到。代價(jià)是如果一直不讀完就會(huì)一直被通知形成忙循環(huán)所以還是要讀完。邊緣觸發(fā)ET的語義是只在狀態(tài)發(fā)生變化時(shí)通知一次。從“無數(shù)據(jù)”變成“有數(shù)據(jù)”時(shí)通知之后無論緩沖區(qū)里還剩多少都不再通知直到下一次新的數(shù)據(jù)到達(dá)。這就要求你收到通知后必須循環(huán)讀取直到返回 EAGAIN把緩沖區(qū)徹底讀空。同時(shí) fd 必須是非阻塞的否則最后一次讀會(huì)因?yàn)闆]數(shù)據(jù)而阻塞住把整個(gè)事件循環(huán)卡死。我在一個(gè)項(xiàng)目里就踩過這個(gè)坑代碼改成了 ET 模式但讀取邏輯還是只讀一次。壓測(cè)時(shí)表現(xiàn)為極低概率的數(shù)據(jù)丟失大概萬分之一的請(qǐng)求少字段。排查了兩天才定位到原因是某個(gè)請(qǐng)求的數(shù)據(jù)包被拆成了兩段第一段到達(dá)觸發(fā)了一次通知程序讀完第一段就去處理了第二段到達(dá)時(shí)因?yàn)榫彌_區(qū)非空狀態(tài)沒變化不再觸發(fā)通知于是第二段永遠(yuǎn)留在內(nèi)核緩沖區(qū)里直到下一個(gè)包到達(dá)才被順帶讀出來——所以表現(xiàn)為“偶發(fā)丟數(shù)據(jù)”而不是“必丟”。這種 bug 極難復(fù)現(xiàn)因?yàn)橐蕾嚲W(wǎng)絡(luò)分包。所以 ET 模式的正確寫法固定是三段式for (;;) { n read(fd, buf, len); if (n 0) { 處理 } else if (n 0) { 對(duì)端關(guān)閉清理 } else { if (errno EAGAIN || errno EWOULDBLOCK) break; else 出錯(cuò)處理 } }。這個(gè)循環(huán)不能省。選用建議沒有明確性能訴求就用 LT。ET 的優(yōu)勢(shì)在于減少epoll_wait的返回次數(shù)在高并發(fā)下能省一些系統(tǒng)調(diào)用但差距沒有想象中大通常 10% 以內(nèi)而它帶來的正確性風(fēng)險(xiǎn)是實(shí)打?qū)嵉摹V挥性谶B接數(shù)極高、且你對(duì)自己的讀取邏輯非常有信心時(shí)才上 ET。另外要注意監(jiān)聽 fdlisten返回的那個(gè)在 ET 模式下也要循環(huán)accept直到 EAGAIN否則并發(fā)連接建立時(shí)會(huì)漏掉一部分連接。3.3 一份可復(fù)用的最小事件循環(huán)先看 C 版本這段代碼我用了很多次去掉業(yè)務(wù)邏輯就是一個(gè)干凈的骨架。#include sys/epoll.h #include sys/socket.h #include fcntl.h #include unistd.h #include errno.h #include stdio.h #define MAX_EVENTS 1024 static int set_nonblocking(int fd) { int flags fcntl(fd, F_GETFL, 0); if (flags -1) return -1; return fcntl(fd, F_SETFL, flags | O_NONBLOCK); } int main(void) { int listenfd socket(AF_INET, SOCK_STREAM, 0); int opt 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); /* bind listen 略backlog 建議 1024 起 */ set_nonblocking(listenfd); int epfd epoll_create1(EPOLL_CLOEXEC); struct epoll_event ev, events[MAX_EVENTS]; ev.events EPOLLIN; /* 監(jiān)聽 fd 用水平觸發(fā)穩(wěn) */ ev.data.fd listenfd; epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, ev); for (;;) { /* timeout 按最近定時(shí)器到期時(shí)間算這里固定 100ms 演示 */ int n epoll_wait(epfd, events, MAX_EVENTS, 100); if (n 0) { if (errno EINTR) continue; /* 被信號(hào)打斷重來 */ perror(epoll_wait); break; } for (int i 0; i n; i) { int fd events[i].data.fd; if (events[i].events (EPOLLERR | EPOLLHUP)) { close(fd); epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); continue; } if (fd listenfd) { for (;;) { /* 循環(huán) accept 直到 EAGAIN */ int conn accept(listenfd, NULL, NULL); if (conn 0) { if (errno EAGAIN || errno EWOULDBLOCK) break; if (errno EINTR) continue; break; } set_nonblocking(conn); ev.events EPOLLIN | EPOLLRDHUP; ev.data.fd conn; epoll_ctl(epfd, EPOLL_CTL_ADD, conn, ev); } } else { /* 業(yè)務(wù)讀取這里若用 ET 必須循環(huán)讀到 EAGAIN */ char buf[4096]; ssize_t r read(fd, buf, sizeof(buf)); if (r 0) { /* 處理數(shù)據(jù) */ } else if (r 0) { close(fd); epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); } else { if (errno ! EAGAIN errno ! EWOULDBLOCK) { close(fd); epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); } } } } } return 0; }這段代碼里有幾個(gè)地方值得單獨(dú)說。EINTR的處理不能省因?yàn)檫M(jìn)程可能被信號(hào)打斷epoll_wait會(huì)返回 -1如果不判斷就直接退出服務(wù)會(huì)因?yàn)橐粋€(gè)無關(guān)信號(hào)掛掉。EPOLLRDHUP加進(jìn)去是為了盡早感知對(duì)端關(guān)閉否則你可能要等一次寫失敗才知道對(duì)面走了。epoll_ctl DEL在 close 之前還是之后其實(shí)都可以因?yàn)?close 會(huì)自動(dòng)把 fd 從 epoll 里移除但顯式 DEL 更清晰也避免 fd 被復(fù)用后出現(xiàn)意外。再看 Java NIO 的等價(jià)寫法思路完全一樣只是把系統(tǒng)調(diào)用包裝成了對(duì)象。Selector selector Selector.open(); ServerSocketChannel server ServerSocketChannel.open(); server.bind(new InetSocketAddress(8080), 1024); server.configureBlocking(false); server.register(selector, SelectionKey.OP_ACCEPT); while (true) { // 1000ms 是為了兜底定時(shí)任務(wù) int n selector.select(1000); if (n 0) continue; IteratorSelectionKey it selector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey key it.next(); it.remove(); // 必須移除否則下次重復(fù)處理 if (!key.isValid()) continue; if (key.isAcceptable()) { SocketChannel ch server.accept(); ch.configureBlocking(false); ch.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel ch (SocketChannel) key.channel(); ByteBuffer buf ByteBuffer.allocate(4096); int r ch.read(buf); if (r -1) { key.cancel(); ch.close(); } else if (r 0) { buf.flip(); /* 處理 */ } } } }it.remove()這一行是新手最容易漏的。selectedKeys()返回的集合是復(fù)用的你不移除下一輪還會(huì)處理同一個(gè) key業(yè)務(wù)邏輯會(huì)重復(fù)執(zhí)行。這個(gè) bug 的表現(xiàn)是“請(qǐng)求被處理了兩次”在冪等接口上不明顯在扣款類操作上就是事故。Java 側(cè)還有兩個(gè)細(xì)節(jié)configureBlocking(false)必須在register之前調(diào)用否則會(huì)拋異常allocate每個(gè)連接都新建一個(gè) ByteBuffer 會(huì)造成大量小對(duì)象生產(chǎn)環(huán)境要用池化或者直接緩沖區(qū)。這些在 Netty 里都被封裝好了但知道底層在干什么出問題時(shí)才能定位。4. 從模型到架構(gòu)Reactor 與線程模型的取舍搞清楚了單個(gè)連接怎么處理下一個(gè)問題是怎么把成千上萬個(gè)連接和有限的 CPU 核數(shù)組織起來。這就是 Reactor 模式要回答的事。4.1 三種 Reactor 結(jié)構(gòu)與它們的成本賬單 Reactor 單線程是最樸素的形態(tài)一個(gè)線程跑事件循環(huán)accept、讀、業(yè)務(wù)處理、寫全在里面。它的好處是無鎖、無競(jìng)爭(zhēng)、邏輯簡(jiǎn)單壞處是任何一步慢都會(huì)拖住全部。適合業(yè)務(wù)極輕的場(chǎng)景比如某些協(xié)議轉(zhuǎn)換、簡(jiǎn)單的回顯服務(wù)。用它跑有數(shù)據(jù)庫訪問的業(yè)務(wù)基本上是自找麻煩。單 Reactor 多線程把業(yè)務(wù)處理挪出去事件循環(huán)只負(fù)責(zé) IO 讀寫和編解碼讀完的完整報(bào)文丟給業(yè)務(wù)線程池。這樣 IO 和計(jì)算解耦一個(gè)慢查詢不會(huì)阻塞其他連接的數(shù)據(jù)接收。代價(jià)是線程間傳遞數(shù)據(jù)要做隊(duì)列隊(duì)列滿了要有背壓策略而且寫操作要從業(yè)務(wù)線程回到 IO 線程涉及跨線程提交任務(wù)。這里的坑是寫并發(fā)多個(gè)業(yè)務(wù)線程同時(shí)想往同一個(gè)連接寫數(shù)據(jù)直接寫會(huì)導(dǎo)致字節(jié)流交錯(cuò)必須加鎖或者串行化到 IO 線程。主從 Reactor更進(jìn)一步把 accept 也分離出來主 Reactor 只負(fù)責(zé)監(jiān)聽和建立連接建立后把新連接分給某個(gè)從 Reactor從 Reactor 各自跑自己的事件循環(huán)處理讀寫。這樣 accept 的壓力不會(huì)影響已有連接而且從 Reactor 的數(shù)量可以按 CPU 核數(shù)擴(kuò)展通常設(shè)成核數(shù)或核數(shù)的 1 到 2 倍。這是目前主流框架的默認(rèn)模型。分連接的方式通常有兩種輪詢和按連接 id 取模。輪詢實(shí)現(xiàn)簡(jiǎn)單但需要原子計(jì)數(shù)高并發(fā)下有競(jìng)爭(zhēng)取模無競(jìng)爭(zhēng)但依賴連接 id 分布均勻。我在實(shí)際項(xiàng)目里更傾向輪詢因?yàn)檫B接 id 不一定連續(xù)取模可能傾斜。這里有個(gè)成本賬要算清楚從 Reactor 線程數(shù)不是越多越好。每個(gè)線程有自己的 epoll 實(shí)例、自己的事件循環(huán)、自己的隊(duì)列。線程多了鎖競(jìng)爭(zhēng)雖然少了但上下文切換和緩存局部性會(huì)變差。我實(shí)測(cè)下來從 Reactor 線程數(shù)設(shè)成 CPU 核數(shù)時(shí)吞吐最好超過 1.5 倍核數(shù)基本沒有收益反而 P99 會(huì)抖動(dòng)。這個(gè)結(jié)論在不同業(yè)務(wù)下會(huì)有差異但量級(jí)上差不多。4.2 連接數(shù)、線程數(shù)與內(nèi)存容量估算的實(shí)際算法做架構(gòu)評(píng)審時(shí)經(jīng)常被問“這套模型能扛多少連接”很多人答不上來。其實(shí)可以算我心里有個(gè)固定的算法。先算文件描述符。每個(gè)連接占一個(gè) fd服務(wù)端自己還要占一些監(jiān)聽、日志、管道、epoll 實(shí)例、定時(shí)器留 200 個(gè)余量比較穩(wěn)。所以u(píng)limit -n至少要等于目標(biāo)連接數(shù)加上 200。這里要注意ulimit -n是每進(jìn)程的限制還有fs.file-max是系統(tǒng)級(jí)限制兩個(gè)都要調(diào)。改ulimit可以在啟動(dòng)腳本里ulimit -n 1048576也可以通過 systemd 的LimitNOFILE配。改完記得驗(yàn)證用cat /proc/pid/limits看實(shí)際生效值。再算內(nèi)核內(nèi)存。每個(gè) TCP 連接在內(nèi)核里有 socket 結(jié)構(gòu)、發(fā)送緩沖區(qū)、接收緩沖區(qū)。緩沖區(qū)大小受net.ipv4.tcp_rmem和tcp_wmem控制是三元組最小、默認(rèn)、最大。默認(rèn)值通常接收 128KB 左右、發(fā)送 16KB 左右但這是上限實(shí)際按需增長(zhǎng)。保守估算每連接 200KB 內(nèi)核內(nèi)存十萬連接就是 20GB。這個(gè)數(shù)字經(jīng)常被忽略結(jié)果連接一多就 OOM。生產(chǎn)上我會(huì)把默認(rèn)值調(diào)小一點(diǎn)比如接收 64KB讓緩沖區(qū)按需增長(zhǎng)避免空閑連接占著大塊內(nèi)存。再算應(yīng)用層內(nèi)存。每個(gè)連接對(duì)象、讀緩沖區(qū)、寫緩沖區(qū)、狀態(tài)機(jī)、定時(shí)器節(jié)點(diǎn)加起來通常幾 KB 到幾十 KB。這里的關(guān)鍵是讀寫緩沖區(qū)別開太大。有的框架默認(rèn)給每個(gè)連接分配 64KB 讀緩沖十萬連接就是 6.4GB。實(shí)際做法是用可變緩沖區(qū)初始 1KB 到 4KB不夠再擴(kuò)并且設(shè)上限。最后算線程棧。Java 默認(rèn)每線程 1MB可用-Xss調(diào)小到 256KBC 的 pthread 默認(rèn) 8MB虛擬內(nèi)存實(shí)際按需分配。線程數(shù)按 Reactor 核數(shù)加業(yè)務(wù)線程池來估通常幾十到幾百這塊開銷不大。真正占內(nèi)存的是連接對(duì)象不是線程。把這些加起來就是一臺(tái)機(jī)器的內(nèi)存需求。我給個(gè)參考十萬長(zhǎng)連接4KB 應(yīng)用層緩沖加 64KB 內(nèi)核緩沖加對(duì)象開銷大約 8 到 12GB需要配 16GB 內(nèi)存的機(jī)器并且把 fd 限制調(diào)到 12 萬以上。這個(gè)估算在幾個(gè)項(xiàng)目里驗(yàn)證過誤差在 20% 以內(nèi)。4.3 背壓與應(yīng)用層協(xié)議的配合模型選對(duì)了還有一個(gè)隱形的坑上游讀得飛快下游寫得慢。事件循環(huán)不停地把數(shù)據(jù)讀進(jìn)來放進(jìn)隊(duì)列業(yè)務(wù)線程處理不過來隊(duì)列無限增長(zhǎng)最后內(nèi)存爆掉。這就是沒有背壓。背壓的手段分幾層。最直接的是停止讀當(dāng)某個(gè)連接的待處理隊(duì)列超過閾值就從 epoll 里把這個(gè) fd 的 EPOLLIN 事件摘掉用epoll_ctl MOD改成只監(jiān)聽 EPOLLOUT或者干脆 DEL 掉這樣內(nèi)核緩沖區(qū)堆滿后TCP 接收窗口縮小發(fā)送方自然減速。這一招最干凈因?yàn)樗褖毫鲗?dǎo)回了源頭符合 TCP 的設(shè)計(jì)初衷。第二層是限制并發(fā)處理數(shù)。業(yè)務(wù)線程池用有界隊(duì)列隊(duì)列滿了就拒絕或者阻塞提交。這里的取舍是阻塞提交會(huì)讓 IO 線程被卡住不可取拒絕則要有一套降級(jí)邏輯比如返回特定錯(cuò)誤碼讓客戶端重試。我一般用有界隊(duì)列加拒絕隊(duì)列容量按“能承受的最大積壓時(shí)間”來定比如目標(biāo) P99 是 100ms處理速度是每秒 1 萬那隊(duì)列 1000 左右比較合適。第三層是協(xié)議層限流。比如在報(bào)文頭里帶一個(gè)序號(hào)或者窗口大小讓客戶端自己控制發(fā)送速率。這在自研協(xié)議里很常見用標(biāo)準(zhǔn)協(xié)議的話就得靠 TCP 自身的流控。我見過最典型的背壓缺失案例一個(gè)推送服務(wù)客戶端網(wǎng)絡(luò)慢服務(wù)端不停往里寫寫緩沖區(qū)從幾 KB 漲到幾 MB幾千個(gè)慢客戶端直接把內(nèi)存吃光。修復(fù)方案是給每個(gè)連接設(shè)寫緩沖上限超過就斷開或者丟棄舊消息。斷開這個(gè)動(dòng)作看著粗暴但對(duì)推送場(chǎng)景是合理的因?yàn)榉e壓的消息本來就已經(jīng)過期了。另外應(yīng)用層協(xié)議的選擇也會(huì)反向影響模型。比如 HTTP/1.1 的 keep-alive 讓連接變長(zhǎng)就必須用多路復(fù)用模型HTTP/2 的多路復(fù)用是在一條連接上跑多個(gè)流需要應(yīng)用層自己做流控WebSocket 是長(zhǎng)連接雙向通信對(duì)事件循環(huán)的寫并發(fā)要求更高。這些在選擇協(xié)議的時(shí)候就得想清楚別等上線了才發(fā)現(xiàn)模型不匹配。5. 線上排查實(shí)錄那些模型理論解釋不了的現(xiàn)象理論和代碼講完最后落到排查。這一節(jié)全是我在實(shí)際環(huán)境里遇到過的現(xiàn)象和對(duì)應(yīng)的定位路徑很多問題光看模型定義是看不出來的。5.1 高頻問題速查表先給一張表按現(xiàn)象查起?,F(xiàn)象優(yōu)先懷疑快速驗(yàn)證方法常見處理CPU 的 sy 占比高但吞吐上不去事件循環(huán)空轉(zhuǎn)、系統(tǒng)調(diào)用過多strace -c -p pid看調(diào)用次數(shù)檢查 timeout 是否設(shè)成 0 或過小檢查是否有忙輪詢連接數(shù)上不去accept報(bào)錯(cuò)fd 限制、backlog 太小cat /proc/pid/limits、ss -lnt看 Send-Q調(diào)ulimit -n、調(diào)大somaxconn和 listen backlog大量 CLOSE_WAIT 堆積應(yīng)用層沒正確關(guān)閉連接ss -tan state close-wait | wc -l檢查讀到 EOF 后是否 close檢查異常路徑是否漏關(guān)偶發(fā)丟數(shù)據(jù)低頻難復(fù)現(xiàn)ET 模式?jīng)]循環(huán)讀完抓包對(duì)比應(yīng)用層收到的字節(jié)數(shù)改為循環(huán)讀到 EAGAIN或退回 LT某幾個(gè)連接延遲極高單個(gè)事件處理太慢拖住循環(huán)在事件循環(huán)里埋點(diǎn)統(tǒng)計(jì)單次處理耗時(shí)把重業(yè)務(wù)移出 IO 線程內(nèi)存持續(xù)增長(zhǎng)不釋放寫緩沖無上限、連接對(duì)象泄漏看堆內(nèi)連接對(duì)象數(shù)量和緩沖大小分布設(shè)緩沖上限加空閑連接回收多線程 accept 時(shí)部分連接不處理epoll 驚群觀察是否所有線程都被喚醒用 EPOLLEXCLUSIVE 或 SO_REUSEPORT這張表里的每一條我都在真實(shí)環(huán)境驗(yàn)證過。尤其是 ET 那條抓包對(duì)比是最直接的在服務(wù)端用 tcpdump 抓一段數(shù)一下某個(gè)請(qǐng)求的 payload 總字節(jié)數(shù)再和應(yīng)用層日志里收到的字節(jié)數(shù)對(duì)比。如果抓包比日志多基本可以確定是讀循環(huán)沒讀干凈。5.2 幾個(gè)必須提前規(guī)避的細(xì)節(jié)第一個(gè)epoll_wait返回后別在遍歷中直接 DEL 當(dāng)前 fd 之外的 fd。這個(gè)坑很隱蔽。你手里拿著一個(gè) events 數(shù)組遍歷到第 3 個(gè)元素時(shí)業(yè)務(wù)邏輯順手關(guān)掉了第 5 個(gè)元素對(duì)應(yīng)的連接并 DEL 了。等遍歷到第 5 個(gè)時(shí)那個(gè) fd 可能已經(jīng)被新連接復(fù)用了你去處理它操作的是完全不相干的連接。正確做法是先把所有事件收集起來處理完再統(tǒng)一做增刪或者用引用計(jì)數(shù)延遲釋放。我在一個(gè)網(wǎng)關(guān)項(xiàng)目里因?yàn)檫@個(gè) bug 排查了整整一周現(xiàn)象是“極低概率的串包”A 用戶收到了 B 用戶的響應(yīng)。第二個(gè)信號(hào)要處理好。事件循環(huán)里被 SIGPIPE 打斷會(huì)導(dǎo)致進(jìn)程直接退出默認(rèn)行為。如果客戶端在你寫數(shù)據(jù)時(shí)突然斷開你調(diào)用 write 就會(huì)收到 SIGPIPE。要么全局忽略它signal(SIGPIPE, SIG_IGN)要么用send的MSG_NOSIGNAL標(biāo)志。這個(gè)必須加不加就是一顆定時(shí)炸彈平時(shí)不出問題用戶批量斷線重啟時(shí)直接崩。第三個(gè)定時(shí)器精度別依賴epoll_wait的 timeout。那個(gè) timeout 只是“最多等這么久”實(shí)際可能提前返回有事件也可能稍微延后調(diào)度延遲。如果你靠它來做精確的超時(shí)控制誤差會(huì)累積。正確做法是自己維護(hù)一個(gè)時(shí)間輪或者最小堆每次算好最近到期時(shí)間作為 timeout同時(shí)每次醒來都檢查一遍當(dāng)前時(shí)間把到期的連接處理掉。第四個(gè)TIME_WAIT 是正常的別急著調(diào)參數(shù)。服務(wù)端主動(dòng)關(guān)閉連接會(huì)進(jìn)入 TIME_WAIT占著四元組 60 秒2 倍 MSL。高并發(fā)短連接場(chǎng)景下 TIME_WAIT 數(shù)量會(huì)很高很多人第一反應(yīng)是開tcp_tw_reuse或者調(diào)小fin_timeout。但在 NAT 環(huán)境下亂開 reuse 有風(fēng)險(xiǎn)而且 TIME_WAIT 的存在是為了保證最后一個(gè) ACK 能重傳強(qiáng)行消滅會(huì)引入數(shù)據(jù)錯(cuò)亂。我的做法是優(yōu)先用長(zhǎng)連接和連接池把短連接降下來實(shí)在降不下來再看有沒有足夠的端口范圍最后才考慮內(nèi)核參數(shù)。第五個(gè)不要在事件循環(huán)里做 DNS 解析、文件讀寫、日志刷盤這些阻塞操作。任何一個(gè)都可能耗時(shí)幾十毫秒而這幾十毫秒里所有連接都停擺。DNS 要單獨(dú)起解析線程或用異步解析庫日志用異步 appender文件操作丟線程池。這些看起來是常識(shí)但我在不止一個(gè)服務(wù)里見過事件循環(huán)里直接logger.info同步刷盤的。第六個(gè)連接關(guān)閉的順序。正確的關(guān)閉是先shutdown(SHUT_WR)發(fā) FIN 告訴對(duì)端我不發(fā)了然后繼續(xù)讀直到讀到 EOF對(duì)端也關(guān)了再 close。直接 close 可能會(huì)導(dǎo)致發(fā)送緩沖區(qū)里沒發(fā)完的數(shù)據(jù)被丟棄除非設(shè)了 SO_LINGER。在推送類服務(wù)里這個(gè)細(xì)節(jié)很關(guān)鍵直接 close 會(huì)導(dǎo)致最后一條消息丟失。寫到這里我個(gè)人的體會(huì)其實(shí)很簡(jiǎn)單網(wǎng)絡(luò) IO 模型的選擇本質(zhì)上是在“線程成本”和“編程復(fù)雜度”之間做交換。阻塞 IO 把復(fù)雜度留給了運(yùn)行時(shí)線程多、切換多事件驅(qū)動(dòng)把復(fù)雜度留給了開發(fā)者狀態(tài)機(jī)、回調(diào)、緩沖管理。沒有哪一種絕對(duì)更好只有適不適合你當(dāng)下的連接規(guī)模、團(tuán)隊(duì)能力和業(yè)務(wù)特征。很多團(tuán)隊(duì)上事件驅(qū)動(dòng)上早了代碼寫成一團(tuán)亂麻問題排查成本遠(yuǎn)超省下來的那點(diǎn)資源。反過來連接數(shù)已經(jīng)過萬了還在用一連接一線程那就是在跟物理規(guī)律較勁。真要給一個(gè)判斷標(biāo)準(zhǔn)我會(huì)看兩條連接數(shù)長(zhǎng)期穩(wěn)定在 500 以下用阻塞 IO 加線程池超過這個(gè)量級(jí)或者有大量空閑長(zhǎng)連接就老老實(shí)實(shí)上 epoll 那一套。中間地帶可以先用“阻塞 IO 合理超時(shí) 連接池”過渡等壓測(cè)數(shù)據(jù)真的支撐不住了再重構(gòu)別一上來就追求終極方案。
返回列表
PREV
查看更多資訊
NEXT
返回資訊列表
亚洲综合一| 青青草视频爽一爽| 欧洲综合无码| 欧美亚洲首页| 国产丰满少妇久久久精品影院| 日韩精品永久在线观看| 明星性猛交ⅹxxx乱大交| 久热99999| 亚洲熟妇丝袜在线观看| 日日夜夜骚| 丁香六月天| 国内三级自拍小视频在线观看| AA级电影三区| 屁股久久久久久| 色拍偷亚洲| www.狠狠干.coom | 欧美色图亚洲色,麻豆| 国产九九久久久精品| 欧美一区二区亚洲天堂| 99日韩| 欧美性爱无码一区二区三区| av中亚| 熟女人妻精品一区二区视频 | 亚洲丝袜诱惑| 久草加勒比一区在线| 99视频自拍区| 精品999一区二区| 天天操福利视频综合网站| 熟女网站最新| 9久精品视频在线观看| 人妻久热在线| 色鬼在线综合| 天天激色| AV色五月天| AV色图| 日韩人妻精品中文字幕| 欧美激情色婷婷花野真衣一区二区| 大香蕉十区| 97欧美精品综合| 9超碰免费| 亚洲熟妇图片| 97在线无精品| 公司1区2区3区精产精| 人妻素股| 国产精品一区二区三区四区五区| 国产Av超碰| 欧美一级久久久久久久大片动画| 凹凸久久人人| 九九黄色视频在线观看| 熟女乱伦二区| 日日骚 av| 欧美亚洲一区二区久久久婷精品大包诱| 日韩丝袜人妻AV| 97超碰jingpin| 色妺妺在线视频| 国产欧美美女免费观看视频| 亚州中文字幕超碰97| 欧美亚洲日韩16色| 亚洲欧美清纯| 国产丝袜欧美在线视频| 97人人操人人摸人人爱| 五月婷婷基地| 欧美成人色| 色香网| 啊啊啊啊啊好多水| 欧美一级特黄淫片在线观看| 日韩AV一起草| 99操| 日本韩国五十路六十路七十路老熟女作爱视频网站| 9Ⅰ超碰| 中日亚韩免费视频| 内射中出日韩在线观看视频| 国产 大胆 对白| 天天日天天干天天摸天天操| 一级特黄aaa大片在线观看成人一级片在线观看 | 久久丁香久草综合网| 亚洲一卡2卡3卡4卡乱码网站| 女人高潮大叫一级毛片| 综合激情97 | 亚洲资源站| 国产一级特黄大片处女| 超碰日本97美女人妻人人玩人人爱 | 久久香蕉网| 91亚洲不卡一区| 天天日天天舔东京热| 91精品国产乱码| 俺去啦自拍| 超碰色图| 色性荡荡荡荡视频| 99热只有这里有精品| 99久热| 亚洲天堂自拍| 色欲久久99国产精品久久久久久| 秋霞免费AV| 水野优香在线观看| 日本亚欧爱爱| 亚洲精品xxx| 久久极品伊人| 久久久无码精品人妻二区| 日韩免费高清大片在线| 九九热免费国产视频婷婷伊人| 97精品久久久久中文字幕| 成人久久久| 日韩成人精品视频自拍| 亚洲欧美91√| 青青草天天亲夜夜操网| 国产精品另类一区大香蕉| 飘花国产午夜精品不卡| 啊啊啊啊啊操我视频| 怡红院久久老司机| 人人操人人摸人 | 久久有码视频| 六月婷婷综合| 中文字幕av片| 韩国一级做A片免费的| 久久精品老司| 97人人夜夜精品视频| 啊好大好舒服| 国内毛片国产欧美拍| 成人欧美一区二区三区黑人一| 国产精品一级二级在线| 久久精品色欧美aⅴ一区二区| 青娱乐福利99| 超碰综合色| 呦呦影院| 中文字幕天天操| 国内偷拍精品一区二区| 欧美在线伊人色| 人妻干天天| 美女的肌被草喷水视频| 51久久夜色精品国产麻豆| 午夜欧美神马久久久久| 看一级特黄a大一片| 99精品久久久久久| 日韩av乱伦| 91白嫩| 人妻天天夜夜爽一区二区| 91一区二匹| 艾草av| 成人无码在线超碰网| 久久精品一区| 97人人操人人摸人人爱| 99热欧美| 亚洲成人色情五月天丁香花| 欧美黄片视频在线观看免费 | 久久久久国产一区二| 日本不卡免费二区| 天天综合网91入口| 成人免费福利网站国产| 亚洲欧洲日韩中文字幕一区| 观看视频图片一区二区三区| 成年人一级黄色毛片大全在线观看| 五月天亚洲网| 日夜干射色啊| 精品久热| 国产三级在线现体验区| 欧美丝袜美女电影一二三四区| 精品婷婷| 999精品国产高清一区二区| 日本不卡一区二区三区| 日本天天人人狠狠在线日美女 | 午夜无遮挡男女啪啪视频| 久久久久久久久久久久欧美日| a网站免费观看| 囯产精品久久久久久久久久梁医生| 中 文字幕一区二区三四 五 区日 日 骚| 欧美黑人168页欧美黑人167| 亚洲精品天天影视综合网 | 天天亚洲综合| 一块操欧美性爱| 级做a爱无码性色永久免费| 97在线欧| 老司机午夜福利视频一区二区| 日欧操屄视频| 人人 操人人 操人人| 天美传媒精品一区二区三区| 五月婷婷hd| 中文字幕在线观看视频www| 国产亚洲精品久久久久小| 精品国产乱码久久久| 国产成人主播| 天天操女人| 成人AV在线电影| 亚洲无无码αⅴ每日更新| 有码专区最新中文字幕有码| 中文字幕、久久精品国产2020、久久综合久久自在自线精品自、亚洲 | 日韩人妻播放| 色婷网| 国产综合永久精品日韩鬼片| 美国一区二区三区视频| 日本免费中文字幕在线| 欧美激情另类一区二区| 国产精品久久aV| 国产精品乱码久久久久久久久| 男人天堂 天天射| 久久婷婷在线观看视频| 有码免费观看| 殴美日韩m| 98一区二区精品| 国产女生在线| 国产精品视频精品一二| 抽插无码高清一区| 成人免费福利网站国产| 最新中文字幕在线亚洲| 精品成人av一区二区三区在线| 亚洲国产欧美一区二区潘金莲| 国产二区三区免费视频| 中文日本免费高清| 超碰在线观看av不卡| 先锋激情∨在线视频播放| 丰满人妻一区二区三区四区| 成人a大片在线观看| 少妇啪啪自拍| 操逼视频亚洲| 欧美综合区| www.久久久久| 欧美熟女激情| 疯操AV| 超碰97国产欧美| 久久久精精精| 精品免费视频国产一区| 18禁免费视频| 90后性网国产欧美| 96AV精品| 尤物黄色在线观看网站| 亚洲日韩美国人妻| 日韩不卡a级视频专区| 国产树林里野战在线看| 一二三啪啪专区| 人妻密肉在线观看| 亚洲色图 图片| 污电影在线观看| 亚洲狠狠入| 2017天天插| 91看黄片| 亚卅熟女乱色| 午夜精品99久久久久传媒| 欧洲中文字幕| 国产97/欧美| 亚洲国产综合图区中文字幕| 日韩精品一区二区高清| 大奶啊啊好爽 | 亚洲图片欧美制度| 91性情| 国产黄色影片在线观看| 丁香六月婷婷综合| 91激情网| 亚洲欧洲激情卡通另类文学四射小说网站 | 亚洲色电影在线| 玖玖爱在线视频免费观看| 黑人狂躁日本妞一区二区三区| 国产一区二区欧美日本| 欧美 亚洲 大香| 亚洲日韩久久精品一区| 永久电影三级在线观看| 91精品无码人妻系列| 蜜桃色色网站视频三区| 黄色十八禁| 97干在线| 综合激情五月丁香| 婷婷香网站| 嗯嗯啊啊操我| 色色五月天激情| 欧美 亚洲 另类 综合| 97免费在线观看视频| 国产成人无码啪| 99爱视频| 日韩高清一二三| 男女啪啪网站免费视频| 99操碰| 国产精品3| 国产91影院| 玖玖在线视频| 欧美激情视频一区二区| 一级久久性爱视频| 骚日日av| 日本 欧美 国产一区| 91熟女综合| 91AV入口| 日韩激情视频| 免费啪啪av| 一区 欧美 日韩 麻豆| 日韩熟女精一区二区三区不卡| 一区二区三区四区免费视频| 欧美91网站| 日本一区二区亚洲综合| 麻豆久久久久久久久丝袜 | 日韩成人精品| 欧洲乱码视频| 久操网视频| 密臀AV在线| 翔田千里无码一区| 男人的天堂色偷偷青青草视频婷婷网| jizz啪啪| 午夜精品久久久久| 日本狂喷奶水在线播放212| 欧美少妇第一页| 亚洲无 码A片在线观看麻豆| 另类天堂| 日韩一999精品| 不卡中文字幕aⅴ在线| 国产日本熟女顶级一区二区三区视频| 九九综合九九综合| 性性久久| 熟女突然公开看18禁影片 | 偷拍精品一区二区三区| 亚州色站 日韩电影| 99无码狠狠久久| 日产国产精品中文久久婷婷| 人妻啪| 91男同| 国产做?爰片久久毛片?片美国| 韩日自拍| 99久久婷婷国产综合精品草原| 九九九九九九精品| 国产欧美日韩在线观看麻豆传媒公司| 九九精品99| 日韩视频小说在线观看 | 国产精品69久久久久孕妇欧美| 人人操人人uiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii | 色欧美色交综合| 毛片电影一区二区三区| 超碰95| 一及黄久一点| 熟女人妇一区二区三区| 夜夜欧美| A片A5445444| 神马久久网| 久久无码电影| 日韩成人在线性爱视频| 夜夜春夜夜操| 欧美爆操91| 熟女精品va中文字幕| 欧美激情区| 欧美熟女妇同| 一区二区三区精品黑丝白丝酒店对鸡 | 日韩有码一区三区| 人人妻人射| 亚洲综合嫩| 中文字幕丝袜美腿| AV中文在线| 久久透逼视频| 2017天天插| 香蕉免费一区二区三区不读| 欧美超碰9798| 色播丁香| 国产又爽又黄| 丝袜熟女一区二区三区| 色色毛片| 精品视频在线观看| 午夜视频久久久| 校园春色欧美色图| 99热| 国产99 中文字幕日韩小视频| 久久精品国产亚洲AV清纯| 在线电影亚洲色图| 韩国女主播青草在线| 91美女丝袜诱惑视频| 欧美日韩激情无码专区| 高清无码久操视频| 国产三级片在线观看| 一区二区亚州激情久婷婷欧美| 一区二区首页| 亚洲色图久久成人| 欧美在线视频99| 亚洲av影院在线观看| 色屁屁影院www国产| 亚洲天堂精品日韩电影| 后入式免费视频| 亚州,欧美在线| 欧美日韩亚洲电影| 使劲用力艹少妇视频一区二区| 色一射色一射| 日本熟人妻中文字幕在线|...久久国产精品-国产精品_日本一区二区三区中文字幕 | 亚洲 欧美 另类 日韩 人妻一区| 亚洲中文字幕乱码无码一区二区| 国产自产91区13区| 超碰公开久久网| 天天日少妇逼AV| 99久在线精品99re8热| 岛园激情| 欧美人人AAA| 色爱三区| 综合欧美日本三级| 妺妺跟我一起洗澡没忍住| 干干干天天| av在线观看不卡网站| 国产精品久久久久久亚洲色欲| 精品无码久久久久久久久果冻糖心| 花花AV导航| 熟妇视频一区二区三区在线| 色欧洲| 欧美高清色| 中文操逼字幕| 激情五月天插| 免费簧片在线观看| 婷婷伊人綜合中文字幕| 国产按摩一区二区三区| 久久久久久中文字幕中文字幕最新| 亚洲另类综合欧美| 欧美色图中文字幕| 欧美超碰人妻97| 男人成人黄色视频在线观看免费下载| 超碰精品97| 国产97色在线| 精品一区二区在线针对华人免费观看这里只有精品免费观看 | 宗合情欲网| 欧美激情久久久久| 亚洲在线A| 一二三区精品视频| 日韩精品一区二区高清| 国产女人91精品嗷嗷嗷嗷| 综合色色婷婷| 国产v片在线免费观看| 一区二区三区亚洲| 在线播放成人网站| 男人天堂站| 91AV天美在线视频| 精品熟女呻吟久久91| 无码精品久久| 亚洲成人碰碰| 国产十八禁视频| 成人草草视频| 操逼无码操逼| 狠狠操狠狠爱| 蘋果手機免費看成人Av| 欧美天堂在线| 亚洲二区精品在线观看 | 大香蕉中文201| 久久超碰国产一区二区三区| 日本精品不卡一二三区| 日欧美色| 久久久91| 91精品无码人妻系列| 久久天天性久久伊人| 老女人老91妇女老热女| 91色狼| AV无码久久久精品| 亚洲AV无码久久精品蜜桃小说| 久久精品电影在线| 亚洲欧美在线观看免费| 伊人97色天使| 欧美高清第一页| 91操人| 欧美婷婷久久| 亚洲天堂欧美| 天天干天天做| 在线 欧美 亚洲| 五月天久久综合网| 久久久久久久久久久人妻| 欧美一区二区三熟女剧情| 白丝被操91| 亚洲欧美激情另类色图| 深喉吞精| 人人操人人操人妻人| 国产亚洲精品无码三区| 天天享受天天看| 97在线观看| 综合久久99| 亚洲精品九九九九九九| 综合色色网| 成人小电影网站tex| 操b网站亚洲无码| av天堂5| 少妇一区二区三区在线观看| 精品久久久av无码免费| 人妻少妇色综合| 91AV国产精品| 日本九九九九| 欧美激情精品| 91 在线亚洲| 91丝袜视频在线观看| 色情五月丁香| 欧美双插| 丝袜制服字幕在线| AV麻豆免费一区| AV天堂电影网| 亚洲欧洲日韩天堂av| 丝袜视频一区二区在线播放国产中文| 色哟哟综合| 人人超碰在线观看黄| 欧美成人A√在线一区二区| 国产SV一线| 日本免费一级AAA大片器| 啊操爽品善一区二区三区| 国产JDAV无码视频在线观看| 中文字幕在线观看网页| 国内亚洲精彩视频在线| 久久久久久九九九九九九| av天堂5| 极品肉射| 一区=区三区视频| 国产亚洲性生活视频播放| 男人天堂综合| 综合一区中亚洲国产成人综合精品| 色噜噜人妻av中文字幕| 丰满人妻一区二区三区免费,| 九七超碰| 日日摸日日碰| 亚洲 综合 欧美| 欧美性天天影视| 香蕉国产精品麻豆亚洲欧美日韩| 97精品国产手机| 国产成人精品日本亚洲语言| 亚洲成a人v欧美综合天堂下载| 午夜精品久久久99| 中文字幕av乱伦| 99国产精品| 岛国不卡超碰护士AV在线播放| 日本99视频| 国产黄色小视频网站| 亚洲熟妇AV日韩熟妇在线| 亚洲欧洲偷拍一区| 伊人久久综合精品欧美| 亚洲综合九九| 国产吹潮女在线观看| 久久香蕉国产传媒一区剧情天美| 伊人久久大香线蕉无码| 在线视频97| 亚洲色棕合| 99re69综合| a网站免费观看| 日韩操逼HD| 久久久久亚洲| 亚洲天堂电影网99999| 蜜臀AV成人精品蜜臀AV久久| 婷婷综合五月| 韩日性爱av| 中文字幕国产在线天堂| 亚洲第一页色网| 日韩精品人妻中文字有码在线| 91美女中出| 亚洲欧美日韩精品久| 十八禁视频一区二区| 日韩精品-原创伙伴| 日本孕妇孕交| 极品久久久久久久久久久久久久| 久操B网| 97精品综合久久| 日韩欧美一级特黄大片| 欧洲成人性爱视频| 尤物网址| 精品视频免费在线一区| 国产农村妇女精品一| 色九区| 手机看片1025| 日韩激情中文字幕有码| 色狠狠 - 百度| 欧美性爱免费短视频| 男女啪啪啪18禁网站| 激情一区二区| 青青草福利视频| 久久久久白虎| 91欧美经典| 丰满人妻-区二区三区免费看| 91精品操美女| 伊人网在线点播| 啊啊啊啊,啊啊好多水| 偷拍片久久| 国产自产22区| 大白逼三四级| 91欧美情色| 加勒比aⅴ| 另类欧美色| 欧美亚洲综合高清在线| 欧美裸体美女日麻屄| 97在线免费| 操b在线观看| 亚洲一卡二卡在线免费| 看大黄色大片原件| 91爆操视频| 影音先锋国产精品| 伊人久久88国产女| 黄色二级片网站| 久久综合女优| 2019亚洲男人天堂| 青娱乐黄色录像| 亚州性色| 国产精品久久aV| 久久人人爽爽人人爽人人片αV| 亚洲色图日韩精品| 噜噜在线| 69精品人人人人| 综合av影片| 欧日韩一二三f区| 夜夜躁狠狠躁日日躁av| a片自拍直播视频| 欧美天天干| 日本九九九九| 天天爽夜夜欢视| 啊啊啊啊好大好硬啊啊啊啊啊 | 国内精品不卡无毒99999| 午夜欧美J进J出白浆流出久久久| 久久宗合亚洲| 美女黄网| 日韩丰满熟妇| 蜜色网色哟哟| 亚洲 无码 有码 中文字幕| 欧成人精品一区二区三区| 久久国产AⅤ| 日韩免费看黄片| 99中出在线| 国产乱子伦一区二区三区免看| 亚洲男人天堂2017| 久久人人爽爽人人爽人人片αV| 五月大香蕉| 青青草国产盗摄一二三区| 老司机香蕉| 国产av强奸美女| 五月开心网| 久久精品99久久久久久| 操国产逼| 99免费在线视频| av东京热男人的天堂| 97干97色| 美日韩成人| 99久在线精品99re8热视频在线| 97超碰日韩| 懂色AV蜜臀无码精品APP | 日韩有码 一区二区三区| 99爱爱| 欧美日韩另类字幕中文| 26uuu久久| 亚洲天堂电影精品一区| 欧美综合色综合| 人妻啪| 亭亭丁香激情| 欧美97| 这里只有精品视频| 大香蕉草草| 在线强奷到舒服的无码视频| 欧美大片天天看| 日本羞羞的视频在线播放| 91色情黑丝搞鸡在线观看一区二区三区三州| 九九国产| 成人av影院在线观看| 粉嫩国产精品久久久| 亚洲无992tv| 亚洲有码 欧美精品| 亚洲交性| 五月天婷婷成人网| 777超碰| 99久久婷婷国产综合精品草原| 久久精品人妻一区二区三区| 91小视频| 成人资源中文字幕在线观看| 人人看人人摸人人色| 久久人妻无码毛片A片麻豆| 成人小说视频在线精品欧美| 五月婷婷hd| 久久久久久久久久久免费精品| 亚洲综合码| 成人自拍三级在线观看| 欧洲一区二区三区免费| 琪琪精品免费一区二区三区| 午夜精品久久久久久久99热影院| 男女激烈网站最新| 小骚逼被操的爽不爽| 亚洲一级特黄大片在线播放91| 久久久男人的天堂| 亚洲最大无码中文字幕网站| 玖玖爱视频网站| 欧美一区91大爱| 青春草莓视频在线观看网址| 天天摸夜夜摸| 久久久亚洲高清不打码| 日本精品一区二区三| 六月婷婷色综合| 久久久蜜桃臀无码视频| 97中文字幕色| 91人妻尻屄视频| 亚洲一区二区av| 婷婷成人久久久精品| 精品久久久久久无码| 91精品久久久久久77777| 欧美黄片视频在线观看免费| 亚洲在线91| 午夜精品久久久久久久99热影院| 色yeye成人免费视频| 亚洲情色 自拍| 青青欧美在线| 亚洲国产麻豆一区二区三区| 欧美日本不卡在线| 自拍欧美| 色五月激情综合网| 国产辣妈在线视频福利| 国产97/欧美| 人人操天天爽| 97任你吞精| 天天看天天日天天操| 国产1727欧美| 嗯嗯嗯啊啊啊在线免费观看| 亚洲无码免费看| 超碰精品在线| 乱性AV| 狠狠色五月亚洲91| 精品九九九九九九九| 欧美顶级黄片AAAAA在线免费看| 啊啊啊好想要| 99日视频在线免费| 久久久18禁| 情色AV电影| 亚洲AV操| 三男一女不戴套的A片| 欧洲亚洲人妻无码高清久久三区四区| 六月丁操逼| 五月丁香| 囯产乱伦一区二区三女 | 中国AV美女| 97超碰超| 人妻少妇无码| 老司机午夜福利视频一区二区| 99re这里只有精品中心播放| 色综合尤物| 亚洲欧美自拍偷拍| 一级性爱视频免费观看| www.91视频网| 一卡二卡三卡| 久久高清无码夜夜操| 亚洲国成人情色好看电影| 日韩另类色图| 五月丁香久久| 成人AV在线电影| 日韩激情中文字幕有码| 欧美 日韩 亚洲 春色| 成 人 A V免费视频在线观看| 91久| 色色九区| 97综合激情| 中文字幕人妻丝袜乱一区三区| 无码人妻系列少妇| 夜夜骑操视频| 好舒服视频| 精品小视频在线| 色情亚洲日本成人| 日韩影片中文字幕一区二区三区| 亚洲熟妇自偷自拍另欧美| 日本韩欧美在线播放a| 亚洲av影院在线观看| 久久亚洲AV无码专区国产精品| 先锋色眉乱伦资源| 人干人人人操人人摸| 精品一区二区成人| 国产 日韩 欧美 中文 另类,国产 欧美 另类 制服 变态,高清 日韩 欧美 中文,高 | 色综合久久久久| 色五月激情综合网| 丁香五月天堂网| www.色五月| 精品久久一区二区三区四区五区| 久热9| 午夜天堂精品久久久久91| 99热在线观看| 亚洲熟妇丝袜在线观看| 久久成年精品| 97超碰超欧美。| 99热线麻豆 | 色狠狠一区二区三区香蕉| 啊啊啊骚| 97香蕉网| 日韩综合无码色欲vv| 日韩乱伦影音先锋| 五月丁香啪啪网| 野狼激情网| 9 7超碰在线免费观看| 欧美大干日韩| 91综合无码| 东京热一区二区中文字幕| 老熟女搡BBBB搡BBBB视频| 日韩精品黄片免费观看| 99婷婷| 99操| 亚州黄站| 98人妻精品一区二区色欲| 国产亚洲中文不卡二区| 亚洲综合九九| 91jk色拍| 欧美韩日精品99综合| 国产精品美女久久久久久网站| 激情抓乳插进去啪啪啪日韩| 91精品大奶人妻| 亚洲不卡不卡中文字幕不卡 | 国产真实子伦对白| 国产福利在线视频网站| 99热这里只有精品9| 四虎视频在线观看| 亚洲中文sv| 亚洲国产精品9999在线观看| 五月天久久久| 极品久久久久久久久久久久久久| 国产精品丝袜在线| 亚洲图片激情综合另类| 欲香欲色综合天天伊人| 国产精品成久久久久午夜午夜| 久久精品店| GVH-003 母子姦 青木玲-麻豆视频,麻豆视传媒短视频网站入口,麻豆视传媒官网直 | 免费观看的av| 大香蕉综合在线| 97综合网| 97香蕉碰碰人妻国产欧美| 国产污视频麻豆传媒一区二区 | 激情深爱五月天| 精品成人女人久久| 亚洲无码视频免费在线观看网址!| 日韩人妻 中文字幕| 97玖玖人妻| 97K超碰在线| 熟女色综合久久| 色五月第四色| 亚洲九九九| 色九九综合| 亚洲精品黑丝| 2017人人操,人人摸| 麻豆天美国美国产| 熟女少妇视频| 精品人人插人人操| 大香蕉之青青草原| 一区操逼| 操香逼| 97爱爱爱| 色五月婷婷网| 欧美黑人与女人91| 全球成人中文在线| 欧美最大综合网| 乱伦a片视频| 亚洲av热热色| 亚洲欧洲av影音| 超碰久久性爱| 欧美爱爱97| 亚洲春色激情小说| 亚洲图片欧美| 精品四五区| 2019天天干| 亚洲麻豆精品二区三区| 欧美亚洲手机在线| 国产精品一二三免费网站| 水滴偷拍| 五月天激情网站| 激情一区二区| 欧美激情总合网| 九九九九精品九九九九| www.狠狠干.coom | 欧美 日韩 国产传媒| 免费看黄片现成| 五十路熟女工口 | 亚洲九九视频| 无码自拍SM| 日韩综合97p| 9久综合网| 曰韩中文人妻视频| 欧美 综合| 欧美日韩国产另类综合| 91女优在线观看| 无遮挡h肉动漫在线观看| 91综合天天看| 日韩乱码av| 日韩在线欧美精品一区二区| 大香蕉啪啪啪啪在线| 午夜寂寞欧美| 中文字幕乱妇免费视频| 好淫网一二三视区| 中文字幕人成乱码熟女香港| 91丨精品丨国产丨丝袜| 欧美做爰无码A片视频| 欧美日韩色| 97综合国产精品高潮久久| 精品一区二区成人| 一本色道人妻久久| 蜜桃久久久久久久| 日韩乱伦影音先锋| 美欧色综合| 91撸色网 玖玖网 欧美| 九月丁香综合网| 青青草久草| 美女毛片999| 91free福利| 99婷婷一区二区| 91色s| 色香阁在线| 大香网站| 久久人妻| 嫩草黄页| 日韩熟女精品无码专区一区二区| 国产精品极品美女视频| 久久久久久999| 日韩少妇无吗| 久久久久13| 亚洲AV操| 男啪女色黄无遮挡免费观看| 男人的天堂日韩| 欧美熟爽综合| 国产伦乱91| 国产成人 综合亚洲 天堂| 亚洲欧美校园| 91中文精品日韩欧美在线| 丁香五月天堂网| 国产又黄又粗的视频| 阿姨一区二区免费视频-高清正片西瓜视频下载app-T450AV | 美女黄色91| 级品肉射| 欧美中文字幕男人天堂久久精品| 久久一区二区三区入口| 91精品微拍福利| 色婷婷一区二区三区久久| 亚洲欧洲激情| 久久一二三四五六七八九区区| 色色色热| 人看人人摸人人操| 日小BB小视频| 亚洲伊人久久综合97| 国内毛片四区| 中文熟女五十乱码在线| 97免费免费视频网| 东京热一区二区中文字幕| 东京热大香焦| 蜜臀久久久国产| 色999偷自拍拍| 亚洲欧洲小说图片视频 | 麻豆这里只有精品| 亚洲1区2区三区高清中文字幕| 秋霞色色影院| 中欧人妻丝袜中文字幕| AV女资源| 欧美色亚洲色| 不卡六六在线91| 中字一区| 久久宗合亚洲| 久久综合久色欧美综合狠狠 | 日本福利二区视频| 无码乱人伦中文视频| 无码WWW免费视频网站| 偷拍亚洲熟女视频播放| 国产精品盗摄 偷窥盗摄| 操一操摸一摸| 国产福利一区二| 国产偷人伦激情在线观看| 青青草毛片| 黄骗免费网站| TS人妖另类精品视频系列| 欧美色图片| 亚洲av噜噜噜噜噜噜| 热思思免费视频| 亚洲性爱成人| 亚洲成人在线高清| 伊人精品久久网站| 人干人人人操人人摸| 少妇干B| 日本三级R| 另类小说五月天| 志村玲子视频一区二区| 亚洲图片 91| 亚洲一区二区三区麻豆传媒| 欧美日韩国第一区| 亚州色站 日韩电影| 妇女视频网站| 色悠久久久av| 日韩内| 国产精品国产精品国产| 亚洲āv网址在线观看| 性爱Av免费| 色欲日韩欧美在线一区| 97人人操人人干| 亚洲s在线观看| 欧美日韩一干二干| 欧美91色| 女人综合网| 亚洲无码成人精品| 国产情侣自拍在线播放| 伊人AAA| 射欧美综合| 少妇精品久久久八区九区| 精品久久97观看在线视频| 最新国产精品| 国产剧情AV不卡在线观看| 色九月| 九九热五区| 日本高清一本二本免费不卡| 久久国产乱子伦精品免费女,网站| 内射夫妻三片| 成人日本视频人妻在线| 丝袜熟女2P| 亚洲黄a三级三级三级看三级| 清纯唯美亚洲另类| 婷婷干黄色| 日本国产成人亚洲精品无码| 九月丁香婷婷| 久久久熟女一区| 亚洲人妻在线精品| 久久久精品视频免费观看| 超碰亚洲97| 免费看国产大AB| 午夜精品久久一区二区| 玖玖玖玖精品国产剧情| 精品网站9999| 9久9久9久9久视频网站| 欧美激情视频在线一区| www久久久| jiujiujiujingpin| 一起草高清无码| 免费综合亚洲中文| 国产天天看| 风月影院十八禁| 唐山老熟妇露脸啪啪叫| 欧美永久激情一区二区| 美女性91| 人妻日日干| 中文字幕-区二区三区四区视频中国| 操一对老熟妇爽上天视频| 97精品熟女少妇一区| 伊人影院在线理论播放| 国内毛片四区| 美国aaaaa一级黄片| 蜜臀AV成人精品蜜臀AV久久| 色香天天| 亚洲福利中文字幕在线| 超碰免费欧美7| 无遮挡一级毛片视频免费的| 1024久久高清视频| 久久国产精品m码| 国产福利影视| 色欧洲97| 2020中文在线一区二区三区| 亚洲丝袜色图| 亚洲色婷婷久久91| 精品丰满人妻一区二区三区免费观| 蜜臀一二三区| 久久99午夜精品一区人妻| 久久伦理视频久久大香蕉视频| 亚欧无码线免费观看视频| 亚洲日本成人动漫| 精精品人妻一区二区三区| 色色色999| 色九九久九九| 亚洲av无码成人精品国产| 精品国产综合久久福利,热99这里有精品综合久久,99热这里只有免费国产精品,精 | 99精品在线观看| a啊啊啊啊啊啊啊啊一区二区| 亚洲欧洲日韩国产自在线| 秋霞鲁丝午夜无码一区二区三| 少妇一区二区三区高速| 成人热久久精品| 日韩操p| 超碰人妻久久人妻中文97| 伊人五月天婷婷| 美女91在线观看| 国产一级内射无挡观看| 人人搡人人肉久久精品| 国产 日韩 欧美高清| 久久久久9999精品九九九| 国产精品干干干| 蜜桃视频啊啊啊啊| 亚洲福利中文字幕在线| 97在线公开视频| 丁香五月激情网| 天天躁日日躁狠狠躁| 色鬼在线综合| 日韩综合成人免费视频| 日韩精品电影| 草莓精品视频| 欧美日韩狠狠爱| 啊好大好舒服| 操B视频日韩无码| 人妻熟女一区二区| 97干在线| 秋霞怕怕片| 亚洲色图国产另类| 黄网色一区二区三区四区精品| 欧色网址| 亚洲中文字幕乱码无码一区二区| 国产对白刺激视频| 中文字幕免费在线观看| 欧亚综合一卡二卡中文字幕| 综合亚洲欧美精品日韩?v| 丝袜亚洲91| 色情综合| 日韩三级伦理中文字幕| 大香蕉欧美国产日韩高潮| 日韩av影片在线观看| 亚洲精品久久久久久久蜜桃臀| 综合网亚| 曰韩无码777| 国产一级舔足在线观看| 涩涩久久精品| 人人人人人人少妇| 成全在线观看免费观看| 久操频道免费在线呗看| 久久超碰免费的| 九九aV| gogogo免费高清看中国国语| 欧美另类天堂| 久久蜜桃综合网| 十八禁啪啦拍视频无遮挡| 国产一区二区三区高清视频| 好吊爽好吊爽在线视频,中文字幕精品一区二区日本,国产良妇出轨视频在线观看, | 一区黄二区黄| 一区二区三区免费岛国片| 成人久久久| 欧美洲精品一级| 久久不卡一区二区| 日本护士高潮| AV在线性爱| 国产路线专区| 极品色电影院| 亚州色站 日韩电影| 婷婷91| 精品亚洲国产成人精品| 日韩精品在线视频,日韩精品……| 999熟女精品| 97国产亚洲中文在线| 色爱综合网| 久久久久久精品免费看A级| 一区二区三区美女超清| 男人的天堂VA| 日产成人久久| 久草资源欧美在线视频| 好爽免费视频,| 嗯嗯嗯啊啊啊在线免费观看| 日韩欧美tv一区二区在线观看| 99久久精品无码一区二区| 男人天堂综合| 91超碰人人操| 男人天堂黄片| 中文字幕熟女人妻丝袜| 久久九九热| 亚洲污一污二| 日韩伦理视频| 玖玖视频在线资源一区二区三区| 亚洲欧美天堂在线| 国产又黄又粗又猛大片| 嗯嗯嗯啊啊在线观看| 青青草啪啪网| 青青操97| 色爱综合网| 欧美综合区| 亚洲αv一区二区三区| 无码精品久久| 国产99999| 久久嫩草| 天天夜夜rb| 欧美最婬乱婬爆婬牲视频| 九九九九一级| 九九九九九用不成了| 99综合自拍| 白丝AV网站| 欧美亚综合色图| 久久天天摸| 爱射综合| 日本大片日本一区二区免费高清| 欧美v亚洲v日韩v最新在线二区| 亚洲成人日韩小说| 免费的很黄很污的全部视频| 久久这里是精品| 九九热久久99精品re| 亚洲性爱电影| 天天色,天天干,天天干| 日韩国产欧美伦理在线| 国产特级毛片AAAAAA高潮流水 | 久久欧美性爱视频| 免费啪啪一级视频| 激情视屏国产乱伦强奸| 中文字幕av乱伦|