大家好编曼,我是小富~
前言
之前有個(gè)小伙伴在技術(shù)交流群里咨詢過(guò)一個(gè)問(wèn)題,我當(dāng)時(shí)還給提供了點(diǎn)排查思路剩辟,是個(gè)典型的八股文轉(zhuǎn)實(shí)戰(zhàn)分析的案例掐场,我覺得挺有意思往扔,趁著中午休息簡(jiǎn)單整理出來(lái)和大家分享下,有不嚴(yán)謹(jǐn)?shù)牡胤綒g迎大家指出熊户。
[圖片上傳失敗...(image-f057f3-1678959123451)]
問(wèn)題分析
我們先來(lái)看看他的問(wèn)題萍膛,下邊是他在群里對(duì)這個(gè)問(wèn)題的描述,我大致的總結(jié)了一下嚷堡。
他們有很多的 IOT 設(shè)備與服務(wù)端建立連接卦羡,當(dāng)增加設(shè)備并發(fā)請(qǐng)求變多,TCP
連接數(shù)在接近1024個(gè)時(shí)麦到,可用TCP
連接數(shù)會(huì)降到200左右并且無(wú)法建立新連接,而且分析應(yīng)用服務(wù)的GC和內(nèi)存情況均未發(fā)現(xiàn)異常欠肾。
[圖片上傳失敗...(image-a003b0-1678959123451)]
從他的描述中我提取了幾個(gè)關(guān)鍵值瓶颠,1024
、200
刺桃、無(wú)法建立新連接
粹淋。
看到這幾個(gè)數(shù)值,直覺告訴我大概率是TCP請(qǐng)求溢出了瑟慈,我給的建議是先直接調(diào)大全連接隊(duì)列
和半連接隊(duì)列
的閥值試一下效果桃移。
[圖片上傳失敗...(image-5a7a2-1678959123451)]
那為什么我會(huì)給出這個(gè)建議?
半連接隊(duì)列和全連接隊(duì)列又是個(gè)啥玩意葛碧?
弄明白這些回顧下TCP的三次握手流程借杰,一切就迎刃而解了~
回顧TCP
TCP三次握手,熟悉吧进泼,面試八股里經(jīng)常全文背誦的題目蔗衡。
話不多說(shuō)先上一張圖,看明白TCP連接的整個(gè)過(guò)程乳绕。
[圖片上傳失敗...(image-641ed-1678959123451)]
第一步:客戶端發(fā)起SYN_SEND
連接請(qǐng)求绞惦,服務(wù)端收到客戶端發(fā)起的SYN
請(qǐng)求后,會(huì)先將連接請(qǐng)求放入半連接隊(duì)列洋措;
第二步:服務(wù)端向客戶端響應(yīng)SYN+ACK
济蝉;
第三步:客戶端會(huì)返回ACK
確認(rèn),服務(wù)端收到第三次握手的 ACK
后標(biāo)識(shí)連接成功菠发。如果這時(shí)全連接隊(duì)列沒滿王滤,內(nèi)核會(huì)把連接從半連接隊(duì)列移除,創(chuàng)建新的連接并將其添加到全連接隊(duì)列雷酪,等待客戶端調(diào)用accept()
方法將連接取出來(lái)使用淑仆;
TCP協(xié)議三次握手的過(guò)程,Linux
內(nèi)核維護(hù)了兩個(gè)隊(duì)列哥力,SYN
半連接隊(duì)列和accepet
全連接隊(duì)列蔗怠。即然叫隊(duì)列墩弯,那就存在隊(duì)列被壓滿的時(shí)候,這種情況我們稱之為隊(duì)列溢出
寞射。
當(dāng)半連接隊(duì)列或全連接隊(duì)列滿了時(shí)渔工,服務(wù)器都無(wú)法接收新的連接請(qǐng)求,從而導(dǎo)致客戶端無(wú)法建立連接桥温。
全連接隊(duì)列
隊(duì)列信息
全連接隊(duì)列溢出時(shí)引矩,首先要查看全連接隊(duì)列的狀態(tài),服務(wù)端通常使用 ss
命令即可查看侵浸,ss
命令獲取的數(shù)據(jù)又分為 LISTEN
狀態(tài) 和 非LISTEN
兩種狀態(tài)下旺韭,通常只看LISTEN
狀態(tài)數(shù)據(jù)就可以。
LISTEN
狀態(tài)
Recv-Q:當(dāng)前全連接隊(duì)列的大小掏觉,表示上圖中已完成三次握手等待可用的 TCP 連接個(gè)數(shù)区端;
Send-Q:全連接最大隊(duì)列長(zhǎng)度,如上監(jiān)聽8888端口的TCP連接最大全連接長(zhǎng)度為128澳腹;
# -l 顯示正在Listener 的socket
# -n 不解析服務(wù)名稱
# -t 只顯示tcp
[root@VM-4-14-centos ~]# ss -lnt | grep 8888
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 100 :::8888 :::*
非LISTEN
狀態(tài)下Recv-Q织盼、Send-Q字段含義有所不同
Recv-Q:已收到但未被應(yīng)用進(jìn)程讀取的字節(jié)數(shù);
Send-Q:已發(fā)送但未收到確認(rèn)的字節(jié)數(shù)酱塔;
# -n 不解析服務(wù)名稱
# -t 只顯示tcp
[root@VM-4-14-centos ~]# ss -nt | grep 8888
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 100 :::8888 :::*
隊(duì)列溢出
一般在請(qǐng)求量過(guò)大沥邻,全連接隊(duì)列設(shè)置過(guò)小會(huì)發(fā)生全連接隊(duì)列溢出,也就是LISTEN
狀態(tài)下 Send-Q < Recv-Q 的情況羊娃。接收到的請(qǐng)求數(shù)大于TCP全連接隊(duì)列的最大長(zhǎng)度唐全,后續(xù)的請(qǐng)求將被服務(wù)端丟棄,客戶端無(wú)法創(chuàng)建新連接迁沫。
# -l 顯示正在Listener 的socket
# -n 不解析服務(wù)名稱
# -t 只顯示tcp
[root@VM-4-14-centos ~]# ss -lnt | grep 8888
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 200 100 :::8888 :::*
如果發(fā)生了全連接隊(duì)列溢出芦瘾,我們可以通過(guò)netstat -s
命令查詢溢出的累計(jì)次數(shù),若這個(gè)times
持續(xù)的增長(zhǎng)集畅,那就說(shuō)明正在發(fā)生溢出近弟。
[root@VM-4-14-centos ~]# netstat -s | grep overflowed
7102 times the listen queue of a socket overflowed #全連接隊(duì)列溢出的次數(shù)
拒絕策略
在全連接隊(duì)列已滿的情況,Linux提供了不同的策略去處理后續(xù)的請(qǐng)求挺智,默認(rèn)是直接丟棄祷愉,也可以通過(guò)tcp_abort_on_overflow
配置來(lái)更改策略,其值 0 和 1 表示不同的策略赦颇,默認(rèn)配置 0二鳄。
# 查看策略
[root@VM-4-14-centos ~]# cat /proc/sys/net/ipv4/tcp_abort_on_overflow
0
tcp_abort_on_overflow = 0:全連接隊(duì)列已滿時(shí),服務(wù)端直接丟棄客戶端發(fā)送的 ACK
媒怯,此時(shí)服務(wù)端仍然是 SYN_RCVD
狀態(tài)订讼,在該狀態(tài)下服務(wù)端會(huì)重試幾次向客戶端推送 SYN + ACK
。
[圖片上傳失敗...(image-682f50-1678959123451)]
重試次數(shù)取決于tcp_synack_retries
配置扇苞,重試次數(shù)超過(guò)此配置后后欺殿,服務(wù)端不在重傳寄纵,此時(shí)客戶端發(fā)送數(shù)據(jù),服務(wù)端直接向客戶端回復(fù)RST
復(fù)位報(bào)文脖苏,告知客戶端本次建立連接已失敗程拭。
RST
: 連接 reset 重置消息,用于連接的異常關(guān)閉棍潘。常用場(chǎng)景例如:服務(wù)端接收不存在端口的連接請(qǐng)求恃鞋;客戶端或者服務(wù)端異常,無(wú)法繼續(xù)正常的連接處理亦歉,發(fā)送 RST 終止連接操作恤浪;長(zhǎng)期未收到對(duì)方確認(rèn)報(bào)文,經(jīng)過(guò)一定時(shí)間或者重傳嘗試后肴楷,發(fā)送 RST 終止連接资锰。
[root@VM-4-14-centos ~]# cat /proc/sys/net/ipv4/tcp_synack_retries
0
tcp_abort_on_overflow = 1:全連接隊(duì)列已滿時(shí),服務(wù)端直接丟棄客戶端發(fā)送的 ACK
阶祭,直接向客戶端回復(fù)RST
復(fù)位報(bào)文,告知客戶端本次連接終止直秆,客戶端會(huì)報(bào)錯(cuò)提示connection reset by peer
濒募。
隊(duì)列調(diào)整
解決全連接隊(duì)列溢出我們可以通過(guò)調(diào)整TCP參數(shù)來(lái)控制全連接隊(duì)列的大小,全連接隊(duì)列的大小取決于 backlog 和 somaxconn 兩個(gè)參數(shù)圾结。
這里需要注意一下瑰剃,兩個(gè)參數(shù)要同時(shí)調(diào)整,因?yàn)槿〉膬烧咧凶钚≈?code>min(backlog,somaxconn)筝野,經(jīng)常發(fā)生只挑調(diào)大其中一個(gè)另一個(gè)值很小導(dǎo)致不生效的情況晌姚。
backlog
是在socket 創(chuàng)建的時(shí)候 Listen() 函數(shù)傳入的參數(shù),例如我們也可以在 Nginx 配置中指定 backlog 的大小歇竟。
server {
listen 8888 default backlog = 200
server_name fire100.top
.....
}
somaxconn
是個(gè) OS 級(jí)別的參數(shù)挥唠,默認(rèn)值是 128,可以通過(guò)修改 net.core.somaxconn
配置焕议。
[root@localhost core]# sysctl -a | grep net.core.somaxconn
net.core.somaxconn = 128
[root@localhost core]# sysctl -w net.core.somaxconn=1024
net.core.somaxconn = 1024
[root@localhost core]# sysctl -a | grep net.core.somaxconn
net.core.somaxconn = 1024
如果服務(wù)端處理請(qǐng)求的速度跟不上連接請(qǐng)求的到達(dá)速度宝磨,隊(duì)列可能會(huì)被快速填滿,導(dǎo)致連接超時(shí)或丟失盅安。應(yīng)該及時(shí)增加隊(duì)列大小唤锉,以避免連接請(qǐng)求被拒絕或超時(shí)。
增大該參數(shù)的值雖然可以增加隊(duì)列的容量别瞭,但是也會(huì)占用更多的內(nèi)存資源窿祥。一般來(lái)說(shuō),建議將全連接隊(duì)列的大小設(shè)置為服務(wù)器處理能力的兩倍左右蝙寨。
半連接隊(duì)列
隊(duì)列信息
上邊TCP三次握手過(guò)程中晒衩,我們知道服務(wù)端SYN_RECV
狀態(tài)的TCP連接存放在半連接隊(duì)列嗤瞎,所以直接執(zhí)行如下命令查看半連接隊(duì)列長(zhǎng)度。
[root@VM-4-14-centos ~] netstat -natp | grep SYN_RECV | wc -l
1111
隊(duì)列溢出
半連接隊(duì)列溢出最常見的場(chǎng)景就是浸遗,客戶端沒有及時(shí)向服務(wù)端回ACK
猫胁,使得服務(wù)端有大量處于SYN_RECV
狀態(tài)的連接,導(dǎo)致半連接隊(duì)列被占滿跛锌,得不到ACK
響應(yīng)半連接隊(duì)列中的 TCP 連接無(wú)法移動(dòng)全連接隊(duì)列弃秆,以至于后續(xù)的SYN
請(qǐng)求無(wú)法創(chuàng)建。這也是一種常見的DDos攻擊方式髓帽。
[圖片上傳失敗...(image-12d9fc-1678959123451)]
查看TCP半連接隊(duì)列溢出情況菠赚,可以執(zhí)行netstat -s
命令,SYNs to LISTEN
前的數(shù)值表示溢出的次數(shù)郑藏,如果反復(fù)查詢幾次數(shù)值持續(xù)增加衡查,那就說(shuō)明半連接隊(duì)列正在溢出。
[root@VM-4-14-centos ~]# netstat -s | egrep “l(fā)isten|LISTEN”
1606 times the listen queue of a socket overflowed
1606 SYNs to LISTEN sockets ignored
隊(duì)列調(diào)整
可以修改 Linux 內(nèi)核配置 /proc/sys/net/ipv4/tcp_max_syn_backlog
來(lái)調(diào)大半連接隊(duì)列長(zhǎng)度必盖。
[root@VM-4-14-centos ~]# echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog
為什么建議
看完上邊對(duì)兩個(gè)隊(duì)列的粗略介紹拌牲,相信大家也能大致明白,為啥我會(huì)直接建議他去調(diào)大隊(duì)列了歌粥。
因?yàn)閺乃拿枋鲋刑岬搅藘蓚€(gè)關(guān)鍵值塌忽,TCP連接數(shù)增加至1024個(gè)時(shí),可用連接數(shù)會(huì)降至200以內(nèi)失驶,一般centos
系統(tǒng)全連接隊(duì)列長(zhǎng)度一般默認(rèn) 128土居,半連接隊(duì)列默認(rèn)長(zhǎng)度 1024。所以隊(duì)列溢出可以作為第一嫌疑對(duì)象嬉探。
全連接隊(duì)列默認(rèn)大小 128
[root@localhost core]# sysctl -a | grep net.core.somaxconn
net.core.somaxconn = 128
半連接隊(duì)列默認(rèn)大小 1024
[root@iZ2ze3ifc44ezdiif8jhf7Z ~]# cat /proc/sys/net/ipv4/tcp_max_syn_backlog
1024
總結(jié)
簡(jiǎn)單分享了一點(diǎn)TCP全連接隊(duì)列擦耀、半連接隊(duì)列的相關(guān)內(nèi)容,講的比較淺顯涩堤,如果有不嚴(yán)謹(jǐn)?shù)牡胤綒g迎留言指正眷蜓,畢竟還是個(gè)老菜鳥。
全連接隊(duì)列胎围、半連接隊(duì)列溢出是比較常見账磺,但又容易被忽視的問(wèn)題,往往上線會(huì)遺忘這兩個(gè)配置痊远,一旦發(fā)生溢出垮抗,從CPU
、線程狀態(tài)
碧聪、內(nèi)存
看起來(lái)都比較正常冒版,偏偏連接數(shù)上不去。
[圖片上傳失敗...(image-7b19d2-1678959123451)]
定期對(duì)系統(tǒng)壓測(cè)是可以暴露出更多問(wèn)題的逞姿,不過(guò)話又說(shuō)回來(lái)辞嗡,就像我和小伙伴聊的一樣捆等,即便測(cè)試環(huán)境程序跑的在穩(wěn)定,到了線上環(huán)境也總會(huì)出現(xiàn)各種奇奇怪怪的問(wèn)題续室。
我是小富栋烤,下期見~
技術(shù)交流,歡迎關(guān)注公眾號(hào):程序員小富