前言
記錄線上一次故障黎棠,狀態(tài)延遲,狀態(tài)使用短連接镰绎,長(zhǎng)輪訓(xùn)的方式獲取脓斩,在每天的固定時(shí)間點(diǎn),出現(xiàn)狀態(tài)延遲畴栖,持續(xù)幾分鐘随静,然后又莫名其妙的恢復(fù)了,很是怪異吗讶,下面就來(lái)復(fù)盤(pán)下燎猛,這次問(wèn)題的定位和思考。
冰山一角
我們可以掌握的線索有 1.固定的時(shí)間點(diǎn)照皆,發(fā)生扛门。2.通過(guò)監(jiān)控可以看到,流量并不高纵寝,但是TCP TIMEWAIT一瞬間瘋漲 3.出問(wèn)題的時(shí)間點(diǎn)论寨,遠(yuǎn)程客戶的電腦(全內(nèi)網(wǎng)),ping網(wǎng)關(guān)和服務(wù)器爽茴,發(fā)現(xiàn)有大量延時(shí) 4.有同事通過(guò)jstat看葬凳,發(fā)現(xiàn)gc的次數(shù)很多,認(rèn)為gc導(dǎo)致了接口延時(shí)室奏。5.接口超時(shí)的時(shí)間點(diǎn)火焰,CPU不高,內(nèi)存不高胧沫,I/O不高昌简,系統(tǒng)負(fù)載不高,也就是未達(dá)到機(jī)器的性能瓶頸绒怨。機(jī)器配置16核64g
軟件版本:
?操作系統(tǒng) centos7.9?JDK 1.6?nginx7.7
由于纯赎,這個(gè)問(wèn)題牽涉的大客戶,很多技術(shù)人員南蹂,投入進(jìn)來(lái)一起攻破犬金,每個(gè)人的想法不一致,導(dǎo)致問(wèn)題更難以統(tǒng)一突破。
一部分人晚顷,認(rèn)為是網(wǎng)絡(luò)問(wèn)題峰伙,和某為扯了半天,沒(méi)有個(gè)所以然 一部分人该默,認(rèn)為gc太頻繁瞳氓,full gc次數(shù)太多了,所以需要gc調(diào)優(yōu) 一部分人栓袖,認(rèn)為需要在測(cè)試環(huán)境顿膨,壓測(cè),測(cè)試出系統(tǒng)瓶頸
實(shí)施的方案
拋開(kāi)以上的言論叽赊,我們回到線索上?
1恋沃、每天固定時(shí)間出現(xiàn),詢問(wèn)客戶必指,這個(gè)時(shí)間段是否是業(yè)務(wù)高峰期囊咏。回答:是?
2塔橡、從監(jiān)控來(lái)看梅割,TCP-TIMEWAIT高,那我們就要先定位到系統(tǒng)部署方案葛家。
系統(tǒng)部署方案如下 1.nginx反向代理java服務(wù)户辞。nginx監(jiān)聽(tīng)443端口 java服務(wù)監(jiān)聽(tīng)8080端口
每一次輪訓(xùn),發(fā)起http請(qǐng)求癞谒,通過(guò)nginx底燎,nginx請(qǐng)求java服務(wù)
查看8080端口的tcp端口timewait個(gè)數(shù),發(fā)現(xiàn)全是timewait弹砚,現(xiàn)在可以確定的是nginx和tcp連接出現(xiàn)了大量的timewait双仍,導(dǎo)致接口延時(shí)。
翻了運(yùn)維的調(diào)優(yōu)記錄如下:
?根據(jù)監(jiān)控看桌吃,丟棄的連接數(shù)比較多
修改后| 當(dāng)前情況| 說(shuō)明| |--|--|--| | net.core.netdev_max_backlog = 16384 |net.core.netdev_max_backlog = 1000 |系統(tǒng)建立全鏈接隊(duì)列長(zhǎng)度(TCP三次握手成功后的連接放入隊(duì)列) | | net.ipv4.tcp_max_syn_backlog = 8192|net.ipv4.tcp_max_syn_backlog = 512 |系統(tǒng)建立半鏈接隊(duì)列長(zhǎng)度(TCP三次握手過(guò)程中的連接放入隊(duì)列) | | net.core.somaxconn = 4096 |net.core.somaxconn = 128 |系統(tǒng)建立全鏈接隊(duì)列(TCP三次握手成功后的連接放入隊(duì)列 系統(tǒng)級(jí)別的 |
TCP 建立連接時(shí)要經(jīng)過(guò) 3 次握手朱沃,在客戶端向服務(wù)器發(fā)起連接時(shí),對(duì)于服務(wù)器而言茅诱,一個(gè)完整的連接建立過(guò)程逗物,服務(wù)器會(huì)經(jīng)歷 2 種 TCP 狀態(tài):SYN_REVD, ESTABELLISHED
對(duì)應(yīng)也會(huì)維護(hù)兩個(gè)隊(duì)列:
一個(gè)存放 SYN 的隊(duì)列(半連接隊(duì)列) 一個(gè)存放已經(jīng)完成連接的隊(duì)列(全連接隊(duì)列)
?ESTABLISHED列長(zhǎng)度如何計(jì)算?
如果 backlog 大于內(nèi)核參數(shù) net.core.somaxconn瑟俭,則以 net.core.somaxconn 為準(zhǔn)翎卓,
即全連接隊(duì)列長(zhǎng)度 = min(backlog, 內(nèi)核參數(shù) net.core.somaxconn),net.core.somaxconn 默認(rèn)為 128尔当。
這個(gè)很好理解莲祸,net.core.somaxconn 定義了系統(tǒng)級(jí)別的全連接隊(duì)列最大長(zhǎng)度蹂安,
backlog 只是應(yīng)用層傳入的參數(shù)椭迎,不可能超過(guò)內(nèi)核參數(shù)锐帜,所以 backlog 必須小于等于 net.core.somaxconn。
?SYN_RECV隊(duì)列長(zhǎng)度如何計(jì)算畜号?
半連接隊(duì)列長(zhǎng)度由內(nèi)核參數(shù) tcp_max_syn_backlog 決定缴阎,
當(dāng)使用 SYN Cookie 時(shí)(就是內(nèi)核參數(shù) net.ipv4.tcp_syncookies = 1),這個(gè)參數(shù)無(wú)效简软,
半連接隊(duì)列的最大長(zhǎng)度為 backlog蛮拔、內(nèi)核參數(shù) net.core.somaxconn、內(nèi)核參數(shù) tcp_max_syn_backlog 的最小值痹升。
即半連接隊(duì)列長(zhǎng)度 = min(backlog, 內(nèi)核參數(shù) net.core.somaxconn建炫,內(nèi)核參數(shù) tcp_max_syn_backlog)。
這個(gè)公式實(shí)際上規(guī)定半連接隊(duì)列長(zhǎng)度不能超過(guò)全連接隊(duì)列長(zhǎng)度疼蛾,但是tcp_syncooking默認(rèn)是啟用的肛跌,如果按上文的理解,那這個(gè)參數(shù)設(shè)置沒(méi)有多大意義
其實(shí)察郁,對(duì)于 Nginx/Tomcat 等這種 Web 服務(wù)器衍慎,都提供了 backlog 參數(shù)設(shè)置入口,當(dāng)然它們都會(huì)有默認(rèn)值皮钠,通常這個(gè)默認(rèn)值都不會(huì)太大(包括內(nèi)核默認(rèn)的半連接隊(duì)列和全連接隊(duì)列長(zhǎng)度)稳捆。如果應(yīng)用并發(fā)訪問(wèn)非常高,只增大應(yīng)用層 backlog 是沒(méi)有意義的麦轰,因?yàn)榭赡軆?nèi)核參數(shù)關(guān)于連接隊(duì)列設(shè)置的都很小乔夯,一定要綜合應(yīng)用層 backlog 和內(nèi)核參數(shù)一起看,通過(guò)公式很容易調(diào)整出正確的設(shè)置
?nginx調(diào)優(yōu) nginx到j(luò)ava建立的連接Timewait比較大款侵,優(yōu)化nginx配置驯嘱,降低握手次數(shù)。
upstream node {
server 127.0.0.1:8080;
keepalive 10000; //新增
keepalive_timeout 65s; //新增
keepalive_requests 20000; //新增
}
?從以上記錄看喳坠,似乎都和TCP連接數(shù)有關(guān)鞠评。解決思路如下:
1.長(zhǎng)輪訓(xùn) 短連接改為websocket方案 評(píng)估:不確定因素太多,客戶要限時(shí)解決問(wèn)題壕鹉,不敢上線 2.降低tcp 連接數(shù)剃幌,根據(jù)業(yè)務(wù)拆分,降低請(qǐng)求量晾浴。3.控制timewait數(shù)量负乡,保證業(yè)務(wù)高峰期不會(huì)發(fā)生太大的抖動(dòng) 4.詢問(wèn)業(yè)務(wù)人員,請(qǐng)求會(huì)分配到一個(gè)線程的處理脊凰,并且沒(méi)有限制線程數(shù)量抖棘,所以如果請(qǐng)求量很大茂腥,應(yīng)該會(huì)有很多線程,并且cpu應(yīng)該有很高的使用量切省,而現(xiàn)在cpu的使用量很低最岗,不太正常。使用jstack打印線程情況
實(shí)施
?
找到運(yùn)維溝通朝捆,tcp的連接數(shù)設(shè)置高點(diǎn)般渡。然而被告知最多只能創(chuàng)建65535個(gè)。這個(gè)問(wèn)題我們稍后在細(xì)聊下芙盘。
?
控制TCP TIMEWAIT的數(shù)量驯用,設(shè)置參數(shù)
net.ipv4.tcp_max_tw_buckets=1024
?
jstack打印線程池,發(fā)現(xiàn)有很多線程在等待同一個(gè)鎖儒老。鎖是使用synchronized關(guān)鍵字蝴乔,鎖的是HashTable
一個(gè)數(shù)組,鎖的代碼邏輯驮樊,處理比較長(zhǎng)薇正。優(yōu)化:拆分鎖的邏輯,不需要一致性的數(shù)據(jù)踢出去巩剖。
后記
一臺(tái)機(jī)器最多能創(chuàng)建多少個(gè)TCP連接铝穷?
上面的截圖中,tcp timewait的個(gè)數(shù)太多佳魔,到了65535限制曙聂,導(dǎo)致連接被重置,那為什么有這個(gè)結(jié)論呢 注意看上面的nginx配置
upstream node {
server 127.0.0.1:8080;
keepalive 10000; //新增
keepalive_timeout 65s; //新增
keepalive_requests 20000; //新增
}
這里指定了去連接127.0.0.1的8080端口鞠鲜,那么nginx去連接java服務(wù)的時(shí)候如下:| 源ip| 源端口 |目標(biāo)ip |目標(biāo)端口 | |--|--|--|--| | 127.0.0.1|20000 |127.0.0.1 |8080 | | 127.0.0.1|20001 |127.0.0.1 |8080 | | 127.0.0.1|20002 |127.0.0.1 |8080 |
所以為什么會(huì)說(shuō)一個(gè)機(jī)器上最多65535個(gè)端口數(shù)宁脊。這個(gè)端口號(hào)16位的,可以有0~65535端口數(shù)是針對(duì)單個(gè)ip來(lái)描述的贤姆,那我們?cè)跈C(jī)器上可不是只有一個(gè)ip榆苞,所以理論上端口數(shù)是無(wú)上限的,配置nginx的時(shí)候這里需要注意上霞捡。
?端口號(hào)的上限不一定是65535 如果你有幸見(jiàn)過(guò)cannot assign requested address?那么你一定知道 Linux對(duì)可使用的范圍端口有具體限制坐漏,使用以下命令查看
cat /proc/sys/net/ipv4/ip_local_port_range
1024 65000
?文件描述符 Linux 下一切皆文件 我們突破tcp端口限制的時(shí)候,很可能會(huì)遇到以下錯(cuò)誤too many open files
每建立一個(gè)TCP連接碧信,會(huì)分配一個(gè)文件描述符赊琳,linux 對(duì)可打開(kāi)的文件描述符的數(shù)量分別作了三個(gè)方面的限制。
系統(tǒng)級(jí):當(dāng)前系統(tǒng)可打開(kāi)的最大數(shù)量砰碴,通過(guò) cat /proc/sys/fs/file-max 查看
用戶級(jí):指定用戶可打開(kāi)的最大數(shù)量躏筏,通過(guò) cat /etc/security/limits.conf 查看
進(jìn)程級(jí):?jiǎn)蝹€(gè)進(jìn)程可打開(kāi)的最大數(shù)量,通過(guò) cat /proc/sys/fs/nr_open 查看
?C10K 每創(chuàng)建一個(gè)TCP連接呈枉,操作系統(tǒng)都需要消耗一個(gè)線程趁尼,當(dāng)我們TCP連接數(shù)過(guò)多埃碱,會(huì)導(dǎo)致線程不停的上下文切換,導(dǎo)致cpu處理時(shí)間越來(lái)越長(zhǎng)酥泞。C10K就是早期單機(jī)性能的瓶頸代名詞砚殿。所以后續(xù)有了 I/O多路復(fù)用模型。
何時(shí)進(jìn)行JVM調(diào)優(yōu)婶博?
還記得上面說(shuō)過(guò)瓮具,通過(guò)jstat看到gc很頻繁荧飞,full gc次數(shù)很多凡人,所以建議GC調(diào)優(yōu)嗎?
YGC 59085 Young GC次數(shù)
YGCT 318.142 Young 總時(shí)間 單位秒
FGC 407 Full GC 次數(shù)
FGCT 75.310 Full GC 總時(shí)間叹阔,單位為妙
我們看平均時(shí)間
年輕代平均gc時(shí)間
318.142/59085=0.0005
- 老年代平均gc時(shí)間
75.310/407=0.185
我們可以看到GC的次數(shù)很多挠轴,但是GC的平均時(shí)間并不高。
但是這并不是萬(wàn)能的
有一種情況比如Full GC發(fā)生了10次耳幢,平均值不高岸晦,但是某幾次的Full GC時(shí)間為5~6秒,所以為了更確定這個(gè)問(wèn)題睛藻,我們可以使用啟動(dòng)參數(shù)启上,查看每次GC的時(shí)間。
-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:./gc.log
所有的優(yōu)化包券,GC調(diào)優(yōu)應(yīng)該是最后的手段,更多的是優(yōu)化我們的代碼炫贤。
本文使用 文章同步助手 同步