網(wǎng)絡(luò)優(yōu)化并發(fā)模型
從IO的角度
第一種是最常用的 I/O 多路復(fù)用技術(shù) epoll顶岸,主要用來取代 select 和 poll。這其實是解決 C10K 問題的關(guān)鍵辖佣,也是目前很多網(wǎng)絡(luò)應(yīng)用默認(rèn)使用的機(jī)制卷谈。
reactor
第二種是使用異步 I/O(Asynchronous I/O,AIO)世蔗。AIO 允許應(yīng)用程序同時發(fā)起很多 I/O 操作,而不用等待這些操作完成顶滩。等到 I/O 完成后寸爆,系統(tǒng)會用事件通知的方式,告訴應(yīng)用程序結(jié)果救氯。不過歌憨,AIO 的使用比較復(fù)雜,你需要小心處理很多邊緣情況务嫡。
CGI應(yīng)該是這種模式
proactor
從進(jìn)程并發(fā)模型
第一種,主進(jìn)程 + 多個 worker 子進(jìn)程准谚。其中,主進(jìn)程負(fù)責(zé)管理網(wǎng)絡(luò)連接樊破,而子進(jìn)程負(fù)責(zé)實際的業(yè)務(wù)處理唆铐。這也是最常用的一種模型。
第二種顺少,監(jiān)聽到相同端口的多進(jìn)程模型王浴。在這種模型下,所有進(jìn)程都會監(jiān)聽相同接口氓辣,并且開啟 SO_REUSEPORT 選項,由內(nèi)核負(fù)責(zé)筛婉,把請求負(fù)載均衡到這些監(jiān)聽進(jìn)程中去。
CGI用的是這種方式
nginx呢入蛆?
應(yīng)用層的網(wǎng)絡(luò)協(xié)議優(yōu)
- 使用長連接取代短連接硕勿,可以顯著降低 TCP 建立連接的成本。在每秒請求次數(shù)較多時扼褪,這樣做的效果非常明顯粱栖。
http1.1默認(rèn)支持,1.0以下需要手動設(shè)置keepalive - 使用內(nèi)存等方式闹究,來緩存不常變化的數(shù)據(jù),可以降低網(wǎng)絡(luò) I/O 次數(shù),同時加快應(yīng)用程序的響應(yīng)速度吉嫩。
client cache - 使用 DNS 緩存嗅定、預(yù)取、HTTPDNS 等方式忙迁,減少 DNS 解析的延遲智什,也可以提升網(wǎng)絡(luò) I/O 的整體速度。
套接字
套接字可以屏蔽掉 Linux 內(nèi)核中不同協(xié)議的差異荠锭,為應(yīng)用程序提供統(tǒng)一的訪問接口晨川。每個套接字共虑,都有一個讀寫緩沖區(qū)。
- 讀緩沖區(qū)妈拌,緩存了遠(yuǎn)端發(fā)過來的數(shù)據(jù)尘分。如果讀緩沖區(qū)已滿,就不能再接收新的數(shù)據(jù)培愁。
- 寫緩沖區(qū),緩存了要發(fā)出去的數(shù)據(jù)谍咆。如果寫緩沖區(qū)已滿私股,應(yīng)用程序的寫操作就會被阻塞」┖浚【臟頁大量出現(xiàn)?】
調(diào)整這些緩沖區(qū)的大胁槠骸:
- 增大每個套接字的緩沖區(qū)大小 net.core.optmem_max偿曙;
- 增大套接字接收緩沖區(qū)大小 net.core.rmem_max 和發(fā)送緩沖區(qū)大小 net.core.wmem_max;
-
增大 TCP 接收緩沖區(qū)大小 net.ipv4.tcp_rmem 和發(fā)送緩沖區(qū)大小 net.ipv4.tcp_wmem望忆。
image.png
tcp_rmem 和 tcp_wmem 的三個數(shù)值分別是 min启摄,default,max傅是,系統(tǒng)會根據(jù)這些設(shè)置蕾羊,自動調(diào)整 TCP 接收 / 發(fā)送緩沖區(qū)的大小。udp_mem 的三個數(shù)值分別是 min书闸,pressure利凑,max,系統(tǒng)會根據(jù)這些設(shè)置哀澈,自動調(diào)整 UDP 發(fā)送緩沖區(qū)的大小日丹。
套接字接口還提供了一些配置選項,用來修改網(wǎng)絡(luò)連接的行為:
- 為 TCP 連接設(shè)置 TCP_NODELAY 后哲虾,就可以禁用 Nagle 算法束凑;
- 為 TCP 連接開啟 TCP_CORK 后,可以讓小包聚合成大包后再發(fā)送(注意會阻塞小包的發(fā)送)汪诉;kafka
- 使用 SO_SNDBUF 和 SO_RCVBUF 谈秫,可以分別調(diào)整套接字發(fā)送緩沖區(qū)和接收緩沖區(qū)的大小鱼鼓。
傳輸層網(wǎng)絡(luò)性能優(yōu)化
1.高qps場景
在請求數(shù)比較大的場景下,你可能會看到大量處于 TIME_WAIT 狀態(tài)的連接迄本,它們會占用大量內(nèi)存和端口資源嘉赎。這時,我們可以優(yōu)化與 TIME_WAIT 狀態(tài)相關(guān)的內(nèi)核選項公条,比如采取下面幾種措施。
- 增大處于 TIME_WAIT 狀態(tài)的連接數(shù)量 net.ipv4.tcp_max_tw_buckets 寥袭,并增大連接跟蹤表的大小 net.netfilter.nf_conntrack_max抓韩。
- 減小 net.ipv4.tcp_fin_timeout 和 net.netfilter.nf_conntrack_tcp_timeout_time_wait 谒拴,讓系統(tǒng)盡快釋放它們所占用的資源涉波。
- 開啟端口復(fù)用 net.ipv4.tcp_tw_reuse。這樣啤覆,被 TIME_WAIT 狀態(tài)占用的端口窗声,還能用到新建的連接中。
- 增大本地端口的范圍 net.ipv4.ip_local_port_range 笨觅。這樣就可以支持更多連接,提高整體的并發(fā)能力见剩。
- 增加最大文件描述符的數(shù)量。你可以使用 fs.nr_open 和 fs.file-max 固翰,分別增大進(jìn)程和系統(tǒng)的最大文件描述符數(shù);或在應(yīng)用程序的 systemd 配置文件中疗琉,配置 LimitNOFILE 歉铝,設(shè)置應(yīng)用程序的最大文件描述符數(shù)。
2.SYN 狀態(tài)相關(guān)的內(nèi)核選項
為了緩解 SYN FLOOD 等送火,利用 TCP 協(xié)議特點進(jìn)行攻擊而引發(fā)的性能問題先匪,你可以考慮優(yōu)化與 SYN 狀態(tài)相關(guān)的內(nèi)核選項,比如采取下面幾種措施坚俗。
- 增大 TCP 半連接的最大數(shù)量 net.ipv4.tcp_max_syn_backlog 岸裙,或者開啟 TCP SYN Cookies net.ipv4.tcp_syncookies ,來繞開半連接數(shù)量限制的問題(注意恩闻,這兩個選項不可同時使用)剧董。
- 減少 SYN_RECV 狀態(tài)的連接重傳 SYN+ACK 包的次數(shù) net.ipv4.tcp_synack_retries。
3.長連接
在長連接的場景中尉剩,通常使用 Keepalive 來檢測 TCP 連接的狀態(tài)毅臊,以便對端連接斷開后,可以自動回收皂林。但是宠蚂,系統(tǒng)默認(rèn)的 Keepalive 探測間隔和重試次數(shù),一般都無法滿足應(yīng)用程序的性能要求著隆。所以,這時候你需要優(yōu)化與 Keepalive 相關(guān)的內(nèi)核選項美浦,比如:
- 縮短最后一次數(shù)據(jù)包到 Keepalive 探測包的間隔時間 net.ipv4.tcp_keepalive_time浦辨;
- 縮短發(fā)送 Keepalive 探測包的間隔時間 net.ipv4.tcp_keepalive_intvl;
-
減少 Keepalive 探測失敗后币厕,一直到通知應(yīng)用程序前的重試次數(shù) net.ipv4.tcp_keepalive_probes芽腾。
image.png
內(nèi)核線程
Linux 在啟動過程中,有三個特殊的進(jìn)程阴绢,也就是 PID 號最小的三個進(jìn)程艰躺。
- 0 號進(jìn)程為 idle 進(jìn)程,這也是系統(tǒng)創(chuàng)建的第一個進(jìn)程左电,它在初始化 1 號和 2 號進(jìn)程后页响,演變?yōu)榭臻e任務(wù)。當(dāng) CPU 上沒有其他任務(wù)執(zhí)行時,就會運(yùn)行它陪腌。
- 1 號進(jìn)程為 init 進(jìn)程烟瞧,通常是 systemd 進(jìn)程,在用戶態(tài)運(yùn)行强岸,用來管理其他用戶態(tài)進(jìn)程砾赔。
- 2 號進(jìn)程為 kthreadd 進(jìn)程青灼,在內(nèi)核態(tài)運(yùn)行妓盲,用來管理內(nèi)核線程悯衬。
要查找內(nèi)核線程,我們只需要從 2 號進(jìn)程開始筋粗,查找它的子孫進(jìn)程即可。比如丽已,你可以使用 ps 命令暇唾,來查找 kthreadd 的子進(jìn)程:
$ ps -f --ppid 2 -p 2
UID PID PPID C STIME TTY TIME CMD
root 2 0 0 12:02 ? 00:00:01 [kthreadd]
root 9 2 0 12:02 ? 00:00:21 [ksoftirqd/0]
root 10 2 0 12:02 ? 00:11:47 [rcu_sched]
root 11 2 0 12:02 ? 00:00:18 [migration/0]
...
root 11094 2 0 14:20 ? 00:00:00 [kworker/1:0-eve]
root 11647 2 0 14:27 ? 00:00:00 [kworker/0:2-cgr]
更簡單的:
$ ps -ef | grep "\[.*\]"
root 2 0 0 08:14 ? 00:00:00 [kthreadd]
root 3 2 0 08:14 ? 00:00:00 [rcu_gp]
root 4 2 0 08:14 ? 00:00:00 [rcu_par_gp]
...
常見內(nèi)核線程
- kswapd0:用于內(nèi)存回收策州。在 Swap 變高 案例中,我曾介紹過它的工作原理旁仿。
- kworker:用于執(zhí)行內(nèi)核工作隊列孽糖,分為綁定 CPU (名稱格式為 kworker/CPU86330)和未綁定 CPU(名稱格式為 kworker/uPOOL86330)兩類。
- migration:在負(fù)載均衡過程中尘奏,把進(jìn)程遷移到 CPU 上病蛉。每個 CPU 都有一個 migration 內(nèi)核線程。
- jbd2/sda1-8:jbd 是 Journaling Block Device 的縮寫俗孝,用來為文件系統(tǒng)提供日志功能魄健,以保證數(shù)據(jù)的完整性;名稱中的 sda1-8革骨,表示磁盤分區(qū)名稱和設(shè)備號。每個使用了 ext4 文件系統(tǒng)的磁盤分區(qū)卤橄,都會有一個 jbd2 內(nèi)核線程臂外。
- pdflush:用于將內(nèi)存中的臟頁(被修改過,但還未寫入磁盤的文件頁)寫入磁盤(已經(jīng)在 3.10 中合并入了 kworker 中)嚎货。
pstack無法觀察內(nèi)核線程蔫浆。
perf可以
# 采樣30s后退出
$ perf record -a -g -p 9 -- sleep 30
內(nèi)核的調(diào)用非常復(fù)雜瓦盛,上圖來看大概是這么幾個過程。
- net_rx_action 和 netif_receive_skb挠唆,表明這是接收網(wǎng)絡(luò)包(rx 表示 receive)嘱吗。
- br_handle_frame ,表明網(wǎng)絡(luò)包經(jīng)過了網(wǎng)橋(br 表示 bridge)俄讹。
- br_nf_pre_routing 绕德,表明在網(wǎng)橋上執(zhí)行了 netfilter 的 PREROUTING(nf 表示 netfilter)耻蛇。而我們已經(jīng)知道 PREROUTING 主要用來執(zhí)行 DNAT,所以可以猜測這里有 DNAT 發(fā)生城丧。
- br_pass_frame_up亡哄,表明網(wǎng)橋處理后布疙,再交給橋接的其他橋接網(wǎng)卡進(jìn)一步處理愿卸。比如截型,在新的網(wǎng)卡上接收網(wǎng)絡(luò)包宦焦、執(zhí)行 netfilter 過濾規(guī)則等等。