來自:fredal的博客
線上故障主要會包括cpu淆珊、磁盤耸别、內(nèi)存以及網(wǎng)絡(luò)問題健芭,而大多數(shù)故障可能會包含不止一個層面的問題,所以進(jìn)行排查時候盡量四個方面依次排查一遍秀姐。同時例如 jstack慈迈、jmap 等工具也是不囿于一個方面的問題的,基本上出問題就是df省有、free痒留、top 三連,然后依次jstack蠢沿、jmap伺候伸头,具體問題具體分析即可。
CPU
一般來講我們首先會排查 CPU 方面的問題舷蟀。CPU 異常往往還是比較好定位的熊锭。原因包括業(yè)務(wù)邏輯問題(死循環(huán))、頻繁gc以及上下文切換過多雪侥。而最常見的往往是業(yè)務(wù)邏輯(或者框架邏輯)導(dǎo)致的碗殷,可以使用jstack來分析對應(yīng)的堆棧情況。
使用 jstack 分析 CPU 問題
我們先用 ps 命令找到對應(yīng)進(jìn)程的pid(如果你有好幾個目標(biāo)進(jìn)程速缨,可以先用top看一下哪個占用比較高)锌妻。
接著用top -H -p pid
來找到 CPU 使用率比較高的一些線程
然后將占用最高的 pid 轉(zhuǎn)換為 16 進(jìn)制printf '%x\n' pid
得到 nid
接著直接在 jstack 中找到相應(yīng)的堆棧信息jstack pid |grep 'nid' -C5 –color
可以看到我們已經(jīng)找到了 nid 為 0x42 的堆棧信息,接著只要仔細(xì)分析一番即可旬牲。
當(dāng)然更常見的是我們對整個 jstack 文件進(jìn)行分析仿粹,通常我們會比較關(guān)注 WAITING 和 TIMED_WAITING 的部分搁吓,BLOCKED 就不用說了。我們可以使用命令
cat jstack.log | grep "java.lang.Thread.State" | sort -nr | uniq -c
來對 jstack 的狀態(tài)有一個整體的把握吭历,如果 WAITING 之類的特別多堕仔,那么多半是有問題啦。
頻繁 gc
當(dāng)然我們還是會使用 jstack 來分析問題晌区,但有時候我們可以先確定下 gc 是不是太頻繁摩骨,使用jstat -gc pid 1000
命令來對 gc 分代變化情況進(jìn)行觀察,1000 表示采樣間隔(ms)朗若,S0C/S1C恼五、S0U/S1U、EC/EU哭懈、OC/OU灾馒、MC/MU 分別代表兩個 Survivor 區(qū)、Eden 區(qū)遣总、老年代睬罗、元數(shù)據(jù)區(qū)的容量和使用量。
YGC/YGT旭斥、FGC/FGCT傅物、GCT 則代表 YoungGc、FullGc 的耗時和次數(shù)以及總耗時琉预。如果看到 gc 比較頻繁董饰,再針對 gc 方面做進(jìn)一步分析,具體可以參考一下 gc 章節(jié)的描述圆米。
上下文切換
針對頻繁上下文問題卒暂,我們可以使用vmstat
命令來進(jìn)行查看
cs(context switch)一列則代表了上下文切換的次數(shù)。
如果我們希望對特定的 pid 進(jìn)行監(jiān)控那么可以使用 pidstat -w pid
命令娄帖,cswch 和 nvcswch 表示自愿及非自愿切換也祠。
磁盤
磁盤問題和 CPU 一樣是屬于比較基礎(chǔ)的。首先是磁盤空間方面近速,我們直接使用df -hl
來查看文件系統(tǒng)狀態(tài)
更多時候诈嘿,磁盤問題還是性能上的問題。我們可以通過 iostat -d -k -x
來進(jìn)行分析
最后一列%util可以看到每塊磁盤寫入的程度削葱,而rrqpm/s以及wrqm/s分別表示讀寫速度奖亚,一般就能幫助定位到具體哪塊磁盤出現(xiàn)問題了。另外我們還需要知道是哪個進(jìn)程在進(jìn)行讀寫析砸,一般來說開發(fā)自己心里有數(shù)昔字,或者用 iotop 命令來進(jìn)行定位文件讀寫的來源。
不過這邊拿到的是 tid首繁,我們要轉(zhuǎn)換成 pid作郭,可以通過 readlink 來找到 pidreadlink -f /proc/*/task/tid/../..陨囊。
找到 pid 之后就可以看這個進(jìn)程具體的讀寫情況cat /proc/pid/io
我們還可以通過 lsof 命令來確定具體的文件讀寫情況 lsof -p pid
內(nèi)存
內(nèi)存問題排查起來相對比 CPU 麻煩一些,場景也比較多夹攒。主要包括 OOM蜘醋、GC 問題和堆外內(nèi)存。一般來講咏尝,我們會先用 free 命令先來檢查一發(fā)內(nèi)存的各種情況压语。
堆內(nèi)內(nèi)存
內(nèi)存問題大多還都是堆內(nèi)內(nèi)存問題。表象上主要分為 OOM 和 Stack Overflow状土。
OOM
JMV 中的內(nèi)存不足,OOM 大致可以分為以下幾種:
Exception in thread “main” java.lang.OutOfMemoryError: unable to create new native thread
這個意思是沒有足夠的內(nèi)存空間給線程分配 Java 棧伺糠,基本上還是線程池代碼寫的有問題蒙谓,比如說忘記 shutdown,所以說應(yīng)該首先從代碼層面來尋找問題训桶,使用 jstack 或者 jmap累驮。如果一切都正常,JVM 方面可以通過指定Xss來減少單個 thread stack 的大小舵揭。另外也可以在系統(tǒng)層面谤专,可以通過修改/etc/security/limits.confnofile 和 nproc 來增大 os 對線程的限制。
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
這個意思是堆的內(nèi)存占用已經(jīng)達(dá)到-Xmx 設(shè)置的最大值午绳,應(yīng)該是最常見的 OOM 錯誤了置侍。解決思路仍然是先應(yīng)該在代碼中找,懷疑存在內(nèi)存泄漏拦焚,通過 jstack 和 jmap 去定位問題蜡坊。如果說一切都正常,才需要通過調(diào)整Xmx的值來擴(kuò)大內(nèi)存赎败。
Caused by: java.lang.OutOfMemoryError: Meta space
這個意思是元數(shù)據(jù)區(qū)的內(nèi)存占用已經(jīng)達(dá)到XX:MaxMetaspaceSize設(shè)置的最大值秕衙,排查思路和上面的一致,參數(shù)方面可以通過XX:MaxPermSize來進(jìn)行調(diào)整(這里就不說 1.8 以前的永久代了)僵刮。
Stack Overflow
棧內(nèi)存溢出据忘,這個大家見到也比較多。
Exception in thread “main” java.lang.StackOverflowError
表示線程棧需要的內(nèi)存大于 Xss 值搞糕,同樣也是先進(jìn)行排查勇吊,參數(shù)方面通過Xss來調(diào)整,但調(diào)整的太大可能又會引起 OOM窍仰。
使用 JMAP 定位代碼內(nèi)存泄漏
上述關(guān)于 OOM 和 Stack Overflow 的代碼排查方面萧福,我們一般使用 JMAPjmap -dump:format=b,file=filename pid來導(dǎo)出 dump 文件
通過 mat(Eclipse Memory Analysis Tools)導(dǎo)入 dump 文件進(jìn)行分析,內(nèi)存泄漏問題一般我們直接選 Leak Suspects 即可辈赋,mat 給出了內(nèi)存泄漏的建議鲫忍。另外也可以選擇 Top Consumers 來查看最大對象報告膏燕。
和線程相關(guān)的問題可以選擇 thread overview 進(jìn)行分析。除此之外就是選擇 Histogram 類概覽來自己慢慢分析悟民,大家可以搜搜 mat 的相關(guān)教程坝辫。
日常開發(fā)中,代碼產(chǎn)生內(nèi)存泄漏是比較常見的事射亏,并且比較隱蔽近忙,需要開發(fā)者更加關(guān)注細(xì)節(jié)。比如說每次請求都 new 對象智润,導(dǎo)致大量重復(fù)創(chuàng)建對象及舍;進(jìn)行文件流操作但未正確關(guān)閉;手動不當(dāng)觸發(fā) gc窟绷;ByteBuffer 緩存分配不合理等都會造成代碼 OOM锯玛。
另一方面,我們可以在啟動參數(shù)中指定-XX:+HeapDumpOnOutOfMemoryError來保存 OOM 時的 dump 文件兼蜈。
gc 問題和線程
gc 問題除了影響 CPU 也會影響內(nèi)存攘残,排查思路也是一致的。一般先使用 jstat 來查看分代變化情況为狸,比如 youngGC 或者 fullGC 次數(shù)是不是太多呀歼郭;EU、OU 等指標(biāo)增長是不是異常呀等辐棒。
線程的話太多而且不被及時 gc 也會引發(fā) oom病曾,大部分就是之前說的unable to create new native thread。除了 jstack 細(xì)細(xì)分析 dump 文件外漾根,我們一般先會看下總體線程知态,通過 pstreee -p pid |wc -l。
或者直接通過查看/proc/pid/task的數(shù)量即為線程數(shù)量立叛。
堆外內(nèi)存
如果碰到堆外內(nèi)存溢出负敏,那可真是太不幸了。首先堆外內(nèi)存溢出表現(xiàn)就是物理常駐內(nèi)存增長快秘蛇,報錯的話視使用方式都不確定其做,如果由于使用 Netty 導(dǎo)致的,那錯誤日志里可能會出現(xiàn)OutOfDirectMemoryError錯誤赁还,如果直接是 DirectByteBuffer妖泄,那會報OutOfMemoryError: Direct buffer memory
。
堆外內(nèi)存溢出往往是和 NIO 的使用相關(guān)艘策,一般我們先通過 pmap 來查看下進(jìn)程占用的內(nèi)存情況
pmap -x pid | sort -rn -k3 | head -30
這段意思是查看對應(yīng) pid 倒序前 30 大的內(nèi)存段蹈胡。這邊可以再一段時間后再跑一次命令看看內(nèi)存增長情況,或者和正常機(jī)器比較可疑的內(nèi)存段在哪里。
我們?nèi)绻_定有可疑的內(nèi)存端罚渐,需要通過 gdb 來分析
gdb --batch --pid {pid} -ex "dump memory filename.dump {內(nèi)存起始地址} {內(nèi)存起始地址+內(nèi)存塊大小}"
獲取 dump 文件后可用 heaxdump 進(jìn)行查看hexdump -C filename | less却汉,不過大多數(shù)看到的都是二進(jìn)制亂碼。
NMT 是 Java7U40 引入的 HotSpot 新特性荷并,配合 jcmd 命令我們就可以看到具體內(nèi)存組成了合砂。需要在啟動參數(shù)中加入 -XX:NativeMemoryTracking=summary 或者 -XX:NativeMemoryTracking=detail,會有略微性能損耗源织。
一般對于堆外內(nèi)存緩慢增長直到爆炸的情況來說翩伪,可以先設(shè)一個基線
jcmd pid VM.native_memory baseline
然后等放一段時間后再去看看內(nèi)存增長的情況,通過jcmd pid VM.native_memory detail.diff(summary.diff)做一下 summary 或者 detail 級別的 diff谈息。
可以看到 jcmd 分析出來的內(nèi)存十分詳細(xì)缘屹,包括堆內(nèi)、線程以及 gc(所以上述其他內(nèi)存異常其實(shí)都可以用 nmt 來分析)侠仇,這邊堆外內(nèi)存我們重點(diǎn)關(guān)注 Internal 的內(nèi)存增長轻姿,如果增長十分明顯的話那就是有問題了。
detail 級別的話還會有具體內(nèi)存段的增長情況傅瞻,如下圖踢代。
此外在系統(tǒng)層面盲憎,我們還可以使用 strace 命令來監(jiān)控內(nèi)存分配 strace -f -e “brk,mmap,munmap” -p pid
這邊內(nèi)存分配信息主要包括了 pid 和內(nèi)存地址嗅骄。
不過其實(shí)上面那些操作也很難定位到具體的問題點(diǎn),關(guān)鍵還是要看錯誤日志棧饼疙,找到可疑的對象溺森,搞清楚它的回收機(jī)制,然后去分析對應(yīng)的對象窑眯。比如 DirectByteBuffer 分配內(nèi)存的話屏积,是需要 full GC 或者手動 system.gc 來進(jìn)行回收的(所以最好不要使用-XX:+DisableExplicitGC)。
那么其實(shí)我們可以跟蹤一下 DirectByteBuffer 對象的內(nèi)存情況磅甩,通過jmap -histo:live pid手動觸發(fā) fullGC 來看看堆外內(nèi)存有沒有被回收炊林。如果被回收了,那么大概率是堆外內(nèi)存本身分配的太小了卷要,通過-XX:MaxDirectMemorySize進(jìn)行調(diào)整渣聚。如果沒有什么變化,那就要使用 jmap 去分析那些不能被 gc 的對象僧叉,以及和 DirectByteBuffer 之間的引用關(guān)系了奕枝。
GC 問題
堆內(nèi)內(nèi)存泄漏總是和 GC 異常相伴。不過 GC 問題不只是和內(nèi)存問題相關(guān)瓶堕,還有可能引起 CPU 負(fù)載隘道、網(wǎng)絡(luò)問題等系列并發(fā)癥,只是相對來說和內(nèi)存聯(lián)系緊密些,所以我們在此單獨(dú)總結(jié)一下 GC 相關(guān)問題谭梗。
我們在 CPU 章介紹了使用 jstat 來獲取當(dāng)前 GC 分代變化信息忘晤。而更多時候,我們是通過 GC 日志來排查問題的默辨,在啟動參數(shù)中加上-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps來開啟 GC 日志德频。
常見的 Young GC、Full GC 日志含義在此就不做贅述了缩幸。
針對 gc 日志壹置,我們就能大致推斷出 youngGC 與 fullGC 是否過于頻繁或者耗時過長,從而對癥下藥表谊。我們下面將對 G1 垃圾收集器來做分析钞护,這邊也建議大家使用 G1-XX:+UseG1GC。
youngGC 過頻繁
youngGC 頻繁一般是短周期小對象較多爆办,先考慮是不是 Eden 區(qū)/新生代設(shè)置的太小了难咕,看能否通過調(diào)整-Xmn、-XX:SurvivorRatio 等參數(shù)設(shè)置來解決問題距辆。如果參數(shù)正常余佃,但是 young gc 頻率還是太高,就需要使用 Jmap 和 MAT 對 dump 文件進(jìn)行進(jìn)一步排查了跨算。
youngGC 耗時過長
耗時過長問題就要看 GC 日志里耗時耗在哪一塊了遥椿。以 G1 日志為例沾鳄,可以關(guān)注 Root Scanning垦缅、Object Copy自赔、Ref Proc 等階段。Ref Proc 耗時長背犯,就要注意引用相關(guān)的對象坏瘩。Root Scanning 耗時長,就要注意線程數(shù)漠魏、跨代引用倔矾。Object Copy 則需要關(guān)注對象生存周期。
而且耗時分析它需要橫向比較柱锹,就是和其他項目或者正常時間段的耗時比較哪自。比如說圖中的 Root Scanning 和正常時間段比增長較多,那就是起的線程太多了奕纫。
觸發(fā) fullGC
G1 中更多的還是 mixedGC提陶,但 mixedGC 可以和 youngGC 思路一樣去排查。觸發(fā) fullGC 了一般都會有問題匹层,G1 會退化使用 Serial 收集器來完成垃圾的清理工作隙笆,暫停時長達(dá)到秒級別锌蓄,可以說是半跪了。
fullGC 的原因可能包括以下這些撑柔,以及參數(shù)調(diào)整方面的一些思路:
并發(fā)階段失斎乘:在并發(fā)標(biāo)記階段,MixGC 之前老年代就被填滿了铅忿,那么這時候 G1 就會放棄標(biāo)記周期剪决。這種情況,可能就需要增加堆大小檀训,或者調(diào)整并發(fā)標(biāo)記線程數(shù)-XX:ConcGCThreads柑潦。
晉升失敗:在 GC 的時候沒有足夠的內(nèi)存供存活/晉升對象使用峻凫,所以觸發(fā)了 Full GC渗鬼。這時候可以通過-XX:G1ReservePercent來增加預(yù)留內(nèi)存百分比,減少-XX:InitiatingHeapOccupancyPercent來提前啟動標(biāo)記荧琼,-XX:ConcGCThreads來增加標(biāo)記線程數(shù)也是可以的譬胎。
大對象分配失敗:大對象找不到合適的 region 空間進(jìn)行分配命锄,就會進(jìn)行 fullGC堰乔,這種情況下可以增大內(nèi)存或者增大-XX:G1HeapRegionSize。
程序主動執(zhí)行 System.gc():不要隨便寫就對了脐恩。
另外镐侯,我們可以在啟動參數(shù)中配置-XX:HeapDumpPath=/xxx/dump.hprof來 dump fullGC 相關(guān)的文件,并通過 jinfo 來進(jìn)行 gc 前后的 dump
jinfo -flag +HeapDumpBeforeFullGC pid
jinfo -flag +HeapDumpAfterFullGC pid
jinfo -flag +HeapDumpBeforeFullGC pid
jinfo -flag +HeapDumpAfterFullGC pid
這樣得到 2 份 dump 文件被盈,對比后主要關(guān)注被 gc 掉的問題對象來定位問題析孽。
網(wǎng)絡(luò)
涉及到網(wǎng)絡(luò)層面的問題一般都比較復(fù)雜搭伤,場景多只怎,定位難,成為了大多數(shù)開發(fā)的噩夢怜俐,應(yīng)該是最復(fù)雜的了身堡。這里會舉一些例子,并從 tcp 層拍鲤、應(yīng)用層以及工具的使用等方面進(jìn)行闡述贴谎。
超時
超時錯誤大部分處在應(yīng)用層面,所以這塊著重理解概念季稳。超時大體可以分為連接超時和讀寫超時擅这,某些使用連接池的客戶端框架還會存在獲取連接超時和空閑連接清理超時。
讀寫超時景鼠。readTimeout/writeTimeout仲翎,有些框架叫做 so_timeout 或者 socketTimeout,均指的是數(shù)據(jù)讀寫超時。注意這邊的超時大部分是指邏輯上的超時溯香。soa 的超時指的也是讀超時鲫构。讀寫超時一般都只針對客戶端設(shè)置。
連接超時玫坛。connectionTimeout结笨,客戶端通常指與服務(wù)端建立連接的最大時間。服務(wù)端這邊 connectionTimeout 就有些五花八門了湿镀,Jetty 中表示空閑連接清理時間炕吸,Tomcat 則表示連接維持的最大時間。
其他勉痴。包括連接獲取超時 connectionAcquireTimeout 和空閑連接清理超時 idleConnectionTimeout算途。多用于使用連接池或隊列的客戶端或服務(wù)端框架。
我們在設(shè)置各種超時時間中蚀腿,需要確認(rèn)的是盡量保持客戶端的超時小于服務(wù)端的超時嘴瓤,以保證連接正常結(jié)束。
在實(shí)際開發(fā)中莉钙,我們關(guān)心最多的應(yīng)該是接口的讀寫超時了廓脆。
如何設(shè)置合理的接口超時是一個問題。如果接口超時設(shè)置的過長磁玉,那么有可能會過多地占用服務(wù)端的 tcp 連接停忿。而如果接口設(shè)置的過短,那么接口超時就會非常頻繁蚊伞。
服務(wù)端接口明明 rt 降低席赂,但客戶端仍然一直超時又是另一個問題。這個問題其實(shí)很簡單时迫,客戶端到服務(wù)端的鏈路包括網(wǎng)絡(luò)傳輸颅停、排隊以及服務(wù)處理等,每一個環(huán)節(jié)都可能是耗時的原因掠拳。
TCP 隊列溢出
tcp 隊列溢出是個相對底層的錯誤癞揉,它可能會造成超時、rst 等更表層的錯誤溺欧。因此錯誤也更隱蔽喊熟,所以我們單獨(dú)說一說。
如上圖所示姐刁,這里有兩個隊列:syns queue(半連接隊列)芥牌、accept queue(全連接隊列)。三次握手聂使,在 server 收到 client 的 syn 后壁拉,把消息放到 syns queue拐叉,回復(fù) syn+ack 給 client,server 收到 client 的 ack扇商,如果這時 accept queue 沒滿凤瘦,那就從 syns queue 拿出暫存的信息放入 accept queue 中,否則按 tcp_abort_on_overflow 指示的執(zhí)行案铺。
tcp_abort_on_overflow 0 表示如果三次握手第三步的時候 accept queue 滿了那么 server 扔掉 client 發(fā)過來的 ack蔬芥。tcp_abort_on_overflow 1 則表示第三步的時候如果全連接隊列滿了,server 發(fā)送一個 rst 包給 client控汉,表示廢掉這個握手過程和這個連接笔诵,意味著日志里可能會有很多connection reset / connection reset by peer。
那么在實(shí)際開發(fā)中姑子,我們怎么能快速定位到 tcp 隊列溢出呢乎婿?
netstat 命令,執(zhí)行 netstat -s | egrep “l(fā)isten|LISTEN”
如上圖所示街佑,overflowed 表示全連接隊列溢出的次數(shù)谢翎,sockets dropped 表示半連接隊列溢出的次數(shù)。
ss 命令沐旨,執(zhí)行 ss -lnt
上面看到 Send-Q 表示第三列的 listen 端口上的全連接隊列最大為 5森逮,第一列 Recv-Q 為全連接隊列當(dāng)前使用了多少。
接著我們看看怎么設(shè)置全連接磁携、半連接隊列大小吧:
全連接隊列的大小取決于 min(backlog, somaxconn)褒侧。backlog 是在 socket 創(chuàng)建的時候傳入的,somaxconn 是一個 os 級別的系統(tǒng)參數(shù)谊迄。而半連接隊列的大小取決于 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)闷供。
在日常開發(fā)中,我們往往使用 servlet 容器作為服務(wù)端统诺,所以我們有時候也需要關(guān)注容器的連接隊列大小歪脏。在 Tomcat 中 backlog 叫做acceptCount,在 Jetty 里面則是acceptQueueSize篙议。
RST 異常
RST 包表示連接重置唾糯,用于關(guān)閉一些無用的連接怠硼,通常表示異常關(guān)閉鬼贱,區(qū)別于四次揮手。
在實(shí)際開發(fā)中香璃,我們往往會看到connection reset / connection reset by peer錯誤这难,這種情況就是 RST 包導(dǎo)致的。
端口不存在
如果像不存在的端口發(fā)出建立連接 SYN 請求葡秒,那么服務(wù)端發(fā)現(xiàn)自己并沒有這個端口則會直接返回一個 RST 報文姻乓,用于中斷連接嵌溢。
主動代替 FIN 終止連接
一般來說,正常的連接關(guān)閉都是需要通過 FIN 報文實(shí)現(xiàn)蹋岩,然而我們也可以用 RST 報文來代替 FIN赖草,表示直接終止連接。實(shí)際開發(fā)中剪个,可設(shè)置 SO_LINGER 數(shù)值來控制秧骑,這種往往是故意的,來跳過 TIMED_WAIT扣囊,提供交互效率乎折,不閑就慎用。
客戶端或服務(wù)端有一邊發(fā)生了異常侵歇,該方向?qū)Χ税l(fā)送 RST 以告知關(guān)閉連接
我們上面講的 tcp 隊列溢出發(fā)送 RST 包其實(shí)也是屬于這一種骂澄。這種往往是由于某些原因,一方無法再能正常處理請求連接了(比如程序崩了惕虑,隊列滿了)坟冲,從而告知另一方關(guān)閉連接。
接收到的 TCP 報文不在已知的 TCP 連接內(nèi)
比如溃蔫,一方機(jī)器由于網(wǎng)絡(luò)實(shí)在太差 TCP 報文失蹤了樱衷,另一方關(guān)閉了該連接,然后過了許久收到了之前失蹤的 TCP 報文酒唉,但由于對應(yīng)的 TCP 連接已不存在矩桂,那么會直接發(fā)一個 RST 包以便開啟新的連接。
接下來我們通過 wireshark 打開抓到的包痪伦,可能就能看到如下圖所示侄榴,紅色的就表示 RST 包了。
TIME_WAIT 和 CLOSE_WAIT
TIME_WAIT 和 CLOSE_WAIT 是啥意思相信大家都知道网沾。
在線上時癞蚕,我們可以直接用命令netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’來查看 time-wait 和 close_wait 的數(shù)量
用 ss 命令會更快ss -ant | awk ‘{++S[$1]} END {for(a in S) print a, S[a]}’
TIME_WAIT
time_wait 的存在一是為了丟失的數(shù)據(jù)包被后面連接復(fù)用,二是為了在 2MSL 的時間范圍內(nèi)正常關(guān)閉連接辉哥。它的存在其實(shí)會大大減少 RST 包的出現(xiàn)桦山。
過多的 time_wait 在短連接頻繁的場景比較容易出現(xiàn)。這種情況可以在服務(wù)端做一些內(nèi)核參數(shù)調(diào)優(yōu):
#表示開啟重用醋旦。允許將TIME-WAIT sockets重新用于新的TCP連接恒水,默認(rèn)為0,表示關(guān)閉net.ipv4.tcp_tw_reuse = 1#表示開啟TCP連接中TIME-WAIT sockets的快速回收饲齐,默認(rèn)為0钉凌,表示關(guān)閉net.ipv4.tcp_tw_recycle = 1
當(dāng)然我們不要忘記在 NAT 環(huán)境下因?yàn)闀r間戳錯亂導(dǎo)致數(shù)據(jù)包被拒絕的坑了,另外的辦法就是改小tcp_max_tw_buckets
捂人,超過這個數(shù)的 time_wait 都會被干掉御雕,不過這也會導(dǎo)致報time wait bucket table overflow
的錯矢沿。
CLOSE_WAIT
close_wait 往往都是因?yàn)閼?yīng)用程序?qū)懙挠袉栴},沒有在 ACK 后再次發(fā)起 FIN 報文酸纲。close_wait 出現(xiàn)的概率甚至比 time_wait 要更高捣鲸,后果也更嚴(yán)重。往往是由于某個地方阻塞住了闽坡,沒有正常關(guān)閉連接摄狱,從而漸漸地消耗完所有的線程。
想要定位這類問題无午,最好是通過 jstack 來分析線程堆棧來排查問題媒役,具體可參考上述章節(jié)。這里僅舉一個例子宪迟。
開發(fā)同學(xué)說應(yīng)用上線后 CLOSE_WAIT 就一直增多酣衷,直到掛掉為止,jstack 后找到比較可疑的堆棧是大部分線程都卡在了countdownlatch.await方法次泽,找開發(fā)同學(xué)了解后得知使用了多線程但是確沒有 catch 異常穿仪,修改后發(fā)現(xiàn)異常僅僅是最簡單的升級 sdk 后常出現(xiàn)的class not found。
一方長期未收到另一方的確認(rèn)報文意荤,在一定時間或重傳次數(shù)后發(fā)出 RST 報文
這種大多也和網(wǎng)絡(luò)環(huán)境相關(guān)了啊片,網(wǎng)絡(luò)環(huán)境差可能會導(dǎo)致更多的 RST 報文。
之前說過 RST 報文多會導(dǎo)致程序報錯玖像,在一個已關(guān)閉的連接上讀操作會報connection reset紫谷,而在一個已關(guān)閉的連接上寫操作則會報connection reset by peer。通常我們可能還會看到broken pipe錯誤捐寥,這是管道層面的錯誤笤昨,表示對已關(guān)閉的管道進(jìn)行讀寫,往往是在收到 RST握恳,報出connection reset錯后繼續(xù)讀寫數(shù)據(jù)報的錯瞒窒,這個在 glibc 源碼注釋中也有介紹。
我們在排查故障時候怎么確定有 RST 包的存在呢乡洼?當(dāng)然是使用 tcpdump 命令進(jìn)行抓包崇裁,并使用 wireshark 進(jìn)行簡單分析了。tcpdump -i en0 tcp -w xxx.cap束昵,en0 表示監(jiān)聽的網(wǎng)卡拔稳。