1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        JAVA 線上故障排查完整套路!牛掰!

        共 10572字,需瀏覽 22分鐘

         ·

        2021-10-01 02:40



        作者 | fredalxin

        來源丨h(huán)ttps://fredal.xin/java-error-check


        線上故障主要會(huì)包括 CPU、磁盤、內(nèi)存以及網(wǎng)絡(luò)問題,而大多數(shù)故障可能會(huì)包含不止一個(gè)層面的問題,所以進(jìn)行排查時(shí)候盡量四個(gè)方面依次排查一遍。同時(shí)例如 jstack、jmap 等工具也是不囿于一個(gè)方面的問題的,基本上出問題就是 df、free、top 三連,然后依次 jstack、jmap 伺候,具體問題具體分析即可。

        CPU

        一般來講我們首先會(huì)排查 CPU 方面的問題。CPU 異常往往還是比較好定位的。原因包括業(yè)務(wù)邏輯問題(死循環(huán))、頻繁 gc 以及上下文切換過多。而最常見的往往是業(yè)務(wù)邏輯(或者框架邏輯)導(dǎo)致的,可以使用 jstack 來分析對(duì)應(yīng)的堆棧情況。
        使用 jstack 分析 CPU 問題
        我們先用 ps 命令找到對(duì)應(yīng)進(jìn)程的 pid(如果你有好幾個(gè)目標(biāo)進(jìn)程,可以先用 top 看一下哪個(gè)占用比較高)。

        接著用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)然更常見的是我們對(duì)整個(gè) jstack 文件進(jìn)行分析,通常我們會(huì)比較關(guān)注 WAITING 和 TIMED_WAITING 的部分,BLOCKED 就不用說了。我們可以使用命令cat jstack.log | grep "java.lang.Thread.State" | sort -nr | uniq -c來對(duì) jstack 的狀態(tài)有一個(gè)整體的把握,如果 WAITING 之類的特別多,那么多半是有問題啦。

        頻繁 gc
        當(dāng)然我們還是會(huì)使用 jstack 來分析問題,但有時(shí)候我們可以先確定下 gc 是不是太頻繁,使用jstat -gc pid 1000命令來對(duì) gc 分代變化情況進(jìn)行觀察,1000 表示采樣間隔(ms),S0C/S1C、S0U/S1U、EC/EU、OC/OU、MC/MU 分別代表兩個(gè) Survivor 區(qū)、Eden 區(qū)、老年代、元數(shù)據(jù)區(qū)的容量和使用量。YGC/YGT、FGC/FGCT、GCT 則代表 YoungGc、FullGc 的耗時(shí)和次數(shù)以及總耗時(shí)。如果看到 gc 比較頻繁,再針對(duì) gc 方面做進(jìn)一步分析,具體可以參考一下 gc 章節(jié)的描述。

        上下文切換
        針對(duì)頻繁上下文問題,我們可以使用vmstat命令來進(jìn)行查看

        cs(context switch)一列則代表了上下文切換的次數(shù)。

        如果我們希望對(duì)特定的 pid 進(jìn)行監(jiān)控那么可以使用 pidstat -w pid命令,cswch 和 nvcswch 表示自愿及非自愿切換。

        磁盤

        磁盤問題和 CPU 一樣是屬于比較基礎(chǔ)的。首先是磁盤空間方面,我們直接使用df -hl來查看文件系統(tǒng)狀態(tài)

        更多時(shí)候,磁盤問題還是性能上的問題。我們可以通過 iostatiostat -d -k -x來進(jìn)行分析

        最后一列%util可以看到每塊磁盤寫入的程度,而rrqpm/s以及wrqm/s分別表示讀寫速度,一般就能幫助定位到具體哪塊磁盤出現(xiàn)問題了。

        另外我們還需要知道是哪個(gè)進(jìn)程在進(jìn)行讀寫,一般來說開發(fā)自己心里有數(shù),或者用 iotop 命令來進(jìn)行定位文件讀寫的來源。

        不過這邊拿到的是 tid,我們要轉(zhuǎn)換成 pid,可以通過 readlink 來找到 pidreadlink -f /proc/*/task/tid/../..。

        找到 pid 之后就可以看這個(gè)進(jìn)程具體的讀寫情況cat /proc/pid/io

        我們還可以通過 lsof 命令來確定具體的文件讀寫情況lsof -p pid

        內(nèi)存

        內(nèi)存問題排查起來相對(duì)比 CPU 麻煩一些,場(chǎng)景也比較多。主要包括 OOM、GC 問題和堆外內(nèi)存。一般來講,我們會(huì)先用free命令先來檢查一發(fā)內(nèi)存的各種情況。

        堆內(nèi)內(nèi)存

        內(nèi)存問題大多還都是堆內(nèi)內(nèi)存問題。表象上主要分為 OOM 和 Stack Overflo。

        OOM

        JMV 中的內(nèi)存不足,OOM 大致可以分為以下幾種:

        Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

        這個(gè)意思是沒有足夠的內(nèi)存空間給線程分配 Java 棧,基本上還是線程池代碼寫的有問題,比如說忘記 shutdown,所以說應(yīng)該首先從代碼層面來尋找問題,使用 jstack 或者 jmap。如果一切都正常,JVM 方面可以通過指定Xss來減少單個(gè) thread stack 的大小。另外也可以在系統(tǒng)層面,可以通過修改/etc/security/limits.confnofile 和 nproc 來增大 os 對(duì)線程的限制

        Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        這個(gè)意思是堆的內(nèi)存占用已經(jīng)達(dá)到-Xmx 設(shè)置的最大值,應(yīng)該是最常見的 OOM 錯(cuò)誤了。解決思路仍然是先應(yīng)該在代碼中找,懷疑存在內(nèi)存泄漏,通過 jstack 和 jmap 去定位問題。如果說一切都正常,才需要通過調(diào)整Xmx的值來擴(kuò)大內(nèi)存。
        Caused by: java.lang.OutOfMemoryError: Meta space
        這個(gè)意思是元數(shù)據(jù)區(qū)的內(nèi)存占用已經(jīng)達(dá)到XX:MaxMetaspaceSize設(shè)置的最大值,排查思路和上面的一致,參數(shù)方面可以通過XX:MaxPermSize來進(jìn)行調(diào)整(這里就不說 1.8 以前的永久代了)。
        Stack Overflow
        棧內(nèi)存溢出,這個(gè)大家見到也比較多。
        Exception in thread "main" java.lang.StackOverflowError
        表示線程棧需要的內(nèi)存大于 Xss 值,同樣也是先進(jìn)行排查,參數(shù)方面通過Xss來調(diào)整,但調(diào)整的太大可能又會(huì)引起 OOM。
        使用 JMAP 定位代碼內(nèi)存泄漏
        上述關(guān)于 OOM 和 Stack Overflo 的代碼排查方面,我們一般使用 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 來查看最大對(duì)象報(bào)告。和線程相關(guān)的問題可以選擇 thread overview 進(jìn)行分析。除此之外就是選擇 Histogram 類概覽來自己慢慢分析,大家可以搜搜 mat 的相關(guān)教程。

        日常開發(fā)中,代碼產(chǎn)生內(nèi)存泄漏是比較常見的事,并且比較隱蔽,需要開發(fā)者更加關(guān)注細(xì)節(jié)。比如說每次請(qǐng)求都 new 對(duì)象,導(dǎo)致大量重復(fù)創(chuàng)建對(duì)象;進(jìn)行文件流操作但未正確關(guān)閉;手動(dòng)不當(dāng)觸發(fā) gc;ByteBuffer 緩存分配不合理等都會(huì)造成代碼 OOM。

        另一方面,我們可以在啟動(dòng)參數(shù)中指定-XX:+HeapDumpOnOutOfMemoryError來保存 OOM 時(shí)的 dump 文件。

        gc 問題和線程

        gc 問題除了影響 CPU 也會(huì)影響內(nèi)存,排查思路也是一致的。一般先使用 jstat 來查看分代變化情況,比如 youngGC 或者 fullGC 次數(shù)是不是太多呀;EU、OU 等指標(biāo)增長(zhǎng)是不是異常呀等。

        線程的話太多而且不被及時(shí) gc 也會(huì)引發(fā) oom,大部分就是之前說的unable to create new native thread。除了 jstack 細(xì)細(xì)分析 dump 文件外,我們一般先會(huì)看下總體線程,通過pstreee -p pid |wc -l。

        或者直接通過查看/proc/pid/task的數(shù)量即為線程數(shù)量。

        堆外內(nèi)存

        如果碰到堆外內(nèi)存溢出,那可真是太不幸了。首先堆外內(nèi)存溢出表現(xiàn)就是物理常駐內(nèi)存增長(zhǎng)快,報(bào)錯(cuò)的話視使用方式都不確定,如果由于使用 Netty 導(dǎo)致的,那錯(cuò)誤日志里可能會(huì)出現(xiàn)OutOfDirectMemoryError錯(cuò)誤,如果直接是 DirectByteBuffer,那會(huì)報(bào)OutOfMemoryError: Direct buffer memory。

        堆外內(nèi)存溢出往往是和 NIO 的使用相關(guān),一般我們先通過 pmap 來查看下進(jìn)程占用的內(nèi)存情況pmap -x pid | sort -rn -k3 | head -30,這段意思是查看對(duì)應(yīng) pid 倒序前 30 大的內(nèi)存段。這邊可以再一段時(shí)間后再跑一次命令看看內(nèi)存增長(zhǎng)情況,或者和正常機(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)存組成了。需要在啟動(dòng)參數(shù)中加入 -XX:NativeMemoryTracking=summary 或者 -XX:NativeMemoryTracking=detail,會(huì)有略微性能損耗。

        一般對(duì)于堆外內(nèi)存緩慢增長(zhǎng)直到爆炸的情況來說,可以先設(shè)一個(gè)基線jcmd pid VM.native_memory baseline。

        然后等放一段時(shí)間后再去看看內(nèi)存增長(zhǎng)的情況,通過jcmd pid VM.native_memory detail.diff(summary.diff)做一下 summary 或者 detail 級(jí)別的 diff。

        可以看到 jcmd 分析出來的內(nèi)存十分詳細(xì),包括堆內(nèi)、線程以及 gc(所以上述其他內(nèi)存異常其實(shí)都可以用 nmt 來分析),這邊堆外內(nèi)存我們重點(diǎn)關(guān)注 Internal 的內(nèi)存增長(zhǎng),如果增長(zhǎng)十分明顯的話那就是有問題了。

        detail 級(jí)別的話還會(huì)有具體內(nèi)存段的增長(zhǎng)情況,如下圖。

        此外在系統(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)鍵還是要看錯(cuò)誤日志棧,找到可疑的對(duì)象,搞清楚它的回收機(jī)制,然后去分析對(duì)應(yīng)的對(duì)象。比如 DirectByteBuffer 分配內(nèi)存的話,是需要 full GC 或者手動(dòng) system.gc 來進(jìn)行回收的(所以最好不要使用-XX:+DisableExplicitGC)。那么其實(shí)我們可以跟蹤一下 DirectByteBuffer 對(duì)象的內(nèi)存情況,通過jmap -histo:live pid手動(dòng)觸發(fā) fullGC 來看看堆外內(nèi)存有沒有被回收。如果被回收了,那么大概率是堆外內(nèi)存本身分配的太小了,通過-XX:MaxDirectMemorySize進(jìn)行調(diào)整。如果沒有什么變化,那就要使用 jmap 去分析那些不能被 gc 的對(duì)象,以及和 DirectByteBuffer 之間的引用關(guān)系了。

        GC 問題

        堆內(nèi)內(nèi)存泄漏總是和 GC 異常相伴。不過 GC 問題不只是和內(nèi)存問題相關(guān),還有可能引起 CPU 負(fù)載、網(wǎng)絡(luò)問題等系列并發(fā)癥,只是相對(duì)來說和內(nèi)存聯(lián)系緊密些,所以我們?cè)诖藛为?dú)總結(jié)一下 GC 相關(guān)問題。

        我們?cè)?CPU 章介紹了使用 jstat 來獲取當(dāng)前 GC 分代變化信息。而更多時(shí)候,我們是通過 GC 日志來排查問題的,在啟動(dòng)參數(shù)中加上-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps來開啟 GC 日志。

        常見的 Young GC、Full GC 日志含義在此就不做贅述了。

        針對(duì) gc 日志,我們就能大致推斷出 youngGC 與 fullGC 是否過于頻繁或者耗時(shí)過長(zhǎng),從而對(duì)癥下藥。我們下面將對(duì) G1 垃圾收集器來做分析,這邊也建議大家使用 G1-XX:+UseG1GC。

        youngGC 過頻繁

        youngGC 頻繁一般是短周期小對(duì)象較多,先考慮是不是 Eden 區(qū)/新生代設(shè)置的太小了,看能否通過調(diào)整-Xmn、-XX:SurvivorRatio 等參數(shù)設(shè)置來解決問題。如果參數(shù)正常,但是 young gc 頻率還是太高,就需要使用 Jmap 和 MAT 對(duì) dump 文件進(jìn)行進(jìn)一步排查了。

        youngGC 耗時(shí)過長(zhǎng)

        耗時(shí)過長(zhǎng)問題就要看 GC 日志里耗時(shí)耗在哪一塊了。以 G1 日志為例,可以關(guān)注 Root Scanning、Object Copy、Ref Proc 等階段。Ref Proc 耗時(shí)長(zhǎng),就要注意引用相關(guān)的對(duì)象。Root Scanning 耗時(shí)長(zhǎng),就要注意線程數(shù)、跨代引用。Object Copy 則需要關(guān)注對(duì)象生存周期。而且耗時(shí)分析它需要橫向比較,就是和其他項(xiàng)目或者正常時(shí)間段的耗時(shí)比較。比如說圖中的 Root Scanning 和正常時(shí)間段比增長(zhǎng)較多,那就是起的線程太多了。

        觸發(fā) fullGC

        G1 中更多的還是 mixedGC,但 mixedGC 可以和 youngGC 思路一樣去排查。觸發(fā) fullGC 了一般都會(huì)有問題,G1 會(huì)退化使用 Serial 收集器來完成垃圾的清理工作,暫停時(shí)長(zhǎng)達(dá)到秒級(jí)別,可以說是半跪了。

        fullGC 的原因可能包括以下這些,以及參數(shù)調(diào)整方面的一些思路:

        • 并發(fā)階段失?。涸诓l(fā)標(biāo)記階段,MixGC 之前老年代就被填滿了,那么這時(shí)候 G1 就會(huì)放棄標(biāo)記周期。這種情況,可能就需要增加堆大小,或者調(diào)整并發(fā)標(biāo)記線程數(shù)-XX:ConcGCThreads。

        • 晉升失敗:在 GC 的時(shí)候沒有足夠的內(nèi)存供存活/晉升對(duì)象使用,所以觸發(fā)了 Full GC。這時(shí)候可以通過-XX:G1ReservePercent來增加預(yù)留內(nèi)存百分比,減少-XX:InitiatingHeapOccupancyPercent來提前啟動(dòng)標(biāo)記,-XX:ConcGCThreads來增加標(biāo)記線程數(shù)也是可以的。

        • 大對(duì)象分配失?。捍髮?duì)象找不到合適的 region 空間進(jìn)行分配,就會(huì)進(jìn)行 fullGC,這種情況下可以增大內(nèi)存或者增大-XX:G1HeapRegionSize。

        • 程序主動(dòng)執(zhí)行 System.gc():不要隨便寫就對(duì)了。

        另外,我們可以在啟動(dòng)參數(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 文件,對(duì)比后主要關(guān)注被 gc 掉的問題對(duì)象來定位問題。

        網(wǎng)絡(luò)

        涉及到網(wǎng)絡(luò)層面的問題一般都比較復(fù)雜,場(chǎng)景多,定位難,成為了大多數(shù)開發(fā)的噩夢(mèng),應(yīng)該是最復(fù)雜的了。這里會(huì)舉一些例子,并從 tcp 層、應(yīng)用層以及工具的使用等方面進(jìn)行闡述。

        超時(shí)

        超時(shí)錯(cuò)誤大部分處在應(yīng)用層面,所以這塊著重理解概念。超時(shí)大體可以分為連接超時(shí)和讀寫超時(shí),某些使用連接池的客戶端框架還會(huì)存在獲取連接超時(shí)和空閑連接清理超時(shí)。

        • 讀寫超時(shí)。readTimeout/writeTimeout,有些框架叫做 so_timeout 或者 socketTimeout,均指的是數(shù)據(jù)讀寫超時(shí)。注意這邊的超時(shí)大部分是指邏輯上的超時(shí)。soa 的超時(shí)指的也是讀超時(shí)。讀寫超時(shí)一般都只針對(duì)客戶端設(shè)置。

        • 連接超時(shí)。connectionTimeout,客戶端通常指與服務(wù)端建立連接的最大時(shí)間。服務(wù)端這邊 connectionTimeout 就有些五花八門了,Jetty 中表示空閑連接清理時(shí)間,Tomcat 則表示連接維持的最大時(shí)間。

        • 其他。包括連接獲取超時(shí) connectionAcquireTimeout 和空閑連接清理超時(shí) idleConnectionTimeout。多用于使用連接池或隊(duì)列的客戶端或服務(wù)端框架。

        我們?cè)谠O(shè)置各種超時(shí)時(shí)間中,需要確認(rèn)的是盡量保持客戶端的超時(shí)小于服務(wù)端的超時(shí),以保證連接正常結(jié)束。

        在實(shí)際開發(fā)中,我們關(guān)心最多的應(yīng)該是接口的讀寫超時(shí)了。

        如何設(shè)置合理的接口超時(shí)是一個(gè)問題。如果接口超時(shí)設(shè)置的過長(zhǎng),那么有可能會(huì)過多地占用服務(wù)端的 tcp 連接。而如果接口設(shè)置的過短,那么接口超時(shí)就會(huì)非常頻繁。

        服務(wù)端接口明明 rt 降低,但客戶端仍然一直超時(shí)又是另一個(gè)問題。這個(gè)問題其實(shí)很簡(jiǎn)單,客戶端到服務(wù)端的鏈路包括網(wǎng)絡(luò)傳輸、排隊(duì)以及服務(wù)處理等,每一個(gè)環(huán)節(jié)都可能是耗時(shí)的原因。

        TCP 隊(duì)列溢出

        tcp 隊(duì)列溢出是個(gè)相對(duì)底層的錯(cuò)誤,它可能會(huì)造成超時(shí)、rst 等更表層的錯(cuò)誤。因此錯(cuò)誤也更隱蔽,所以我們單獨(dú)說一說。

        如上圖所示,這里有兩個(gè)隊(duì)列:syns queue(半連接隊(duì)列)、accept queue(全連接隊(duì)列)。三次握手,在 server 收到 client 的 syn 后,把消息放到 syns queue,回復(fù) syn+ack 給 client,server 收到 client 的 ack,如果這時(shí) accept queue 沒滿,那就從 syns queue 拿出暫存的信息放入 accept queue 中,否則按 tcp_abort_on_overflow 指示的執(zhí)行。

        tcp_abort_on_overflow 0 表示如果三次握手第三步的時(shí)候 accept queue 滿了那么 server 扔掉 client 發(fā)過來的 ack。tcp_abort_on_overflow 1 則表示第三步的時(shí)候如果全連接隊(duì)列滿了,server 發(fā)送一個(gè) rst 包給 client,表示廢掉這個(gè)握手過程和這個(gè)連接,意味著日志里可能會(huì)有很多connection reset / connection reset by peer。

        那么在實(shí)際開發(fā)中,我們?cè)趺茨芸焖俣ㄎ坏?tcp 隊(duì)列溢出呢?

        netstat 命令,執(zhí)行 netstat -s | egrep "listen|LISTEN"

        如上圖所示,overflowed 表示全連接隊(duì)列溢出的次數(shù),sockets dropped 表示半連接隊(duì)列溢出的次數(shù)。

        ss 命令,執(zhí)行 ss -lnt

        上面看到 Send-Q 表示第三列的 listen 端口上的全連接隊(duì)列最大為 5,第一列 Recv-Q 為全連接隊(duì)列當(dāng)前使用了多少。

        接著我們看看怎么設(shè)置全連接、半連接隊(duì)列大小吧:

        全連接隊(duì)列的大小取決于 min(backlog, somaxconn)。backlog 是在 socket 創(chuàng)建的時(shí)候傳入的,somaxconn 是一個(gè) os 級(jí)別的系統(tǒng)參數(shù)。而半連接隊(duì)列的大小取決于 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)。

        在日常開發(fā)中,我們往往使用 servlet 容器作為服務(wù)端,所以我們有時(shí)候也需要關(guān)注容器的連接隊(duì)列大小。在 Tomcat 中 backlog 叫做acceptCount,在 Jetty 里面則是acceptQueueSize。

        RST 異常

        RST 包表示連接重置,用于關(guān)閉一些無(wú)用的連接,通常表示異常關(guān)閉,區(qū)別于四次揮手。

        在實(shí)際開發(fā)中,我們往往會(huì)看到connection reset / connection reset by peer錯(cuò)誤,這種情況就是 RST 包導(dǎo)致的。

        端口不存在

        如果像不存在的端口發(fā)出建立連接 SYN 請(qǐng)求,那么服務(wù)端發(fā)現(xiàn)自己并沒有這個(gè)端口則會(huì)直接返回一個(gè) RST 報(bào)文,用于中斷連接。

        主動(dòng)代替 FIN 終止連接

        一般來說,正常的連接關(guān)閉都是需要通過 FIN 報(bào)文實(shí)現(xiàn),然而我們也可以用 RST 報(bào)文來代替 FIN,表示直接終止連接。實(shí)際開發(fā)中,可設(shè)置 SO_LINGER 數(shù)值來控制,這種往往是故意的,來跳過 TIMED_WAIT,提供交互效率,不閑就慎用。

        客戶端或服務(wù)端有一邊發(fā)生了異常,該方向?qū)Χ税l(fā)送 RST 以告知關(guān)閉連接

        我們上面講的 tcp 隊(duì)列溢出發(fā)送 RST 包其實(shí)也是屬于這一種。這種往往是由于某些原因,一方無(wú)法再能正常處理請(qǐng)求連接了(比如程序崩了,隊(duì)列滿了),從而告知另一方關(guān)閉連接。

        接收到的 TCP 報(bào)文不在已知的 TCP 連接內(nèi)

        比如,一方機(jī)器由于網(wǎng)絡(luò)實(shí)在太差 TCP 報(bào)文失蹤了,另一方關(guān)閉了該連接,然后過了許久收到了之前失蹤的 TCP 報(bào)文,但由于對(duì)應(yīng)的 TCP 連接已不存在,那么會(huì)直接發(fā)一個(gè) RST 包以便開啟新的連接。

        一方長(zhǎng)期未收到另一方的確認(rèn)報(bào)文,在一定時(shí)間或重傳次數(shù)后發(fā)出 RST 報(bào)文

        這種大多也和網(wǎng)絡(luò)環(huán)境相關(guān)了,網(wǎng)絡(luò)環(huán)境差可能會(huì)導(dǎo)致更多的 RST 報(bào)文。

        之前說過 RST 報(bào)文多會(huì)導(dǎo)致程序報(bào)錯(cuò),在一個(gè)已關(guān)閉的連接上讀操作會(huì)報(bào)connection reset,而在一個(gè)已關(guān)閉的連接上寫操作則會(huì)報(bào)connection reset by peer。通常我們可能還會(huì)看到broken pipe錯(cuò)誤,這是管道層面的錯(cuò)誤,表示對(duì)已關(guān)閉的管道進(jìn)行讀寫,往往是在收到 RST,報(bào)出connection reset錯(cuò)后繼續(xù)讀寫數(shù)據(jù)報(bào)的錯(cuò),這個(gè)在 glibc 源碼注釋中也有介紹。

        我們?cè)谂挪楣收蠒r(shí)候怎么確定有 RST 包的存在呢?當(dāng)然是使用 tcpdump 命令進(jìn)行抓包,并使用 wireshark 進(jìn)行簡(jiǎn)單分析了。tcpdump -i en0 tcp -w xxx.cap,en0 表示監(jiān)聽的網(wǎng)卡。

        接下來我們通過 wireshark 打開抓到的包,可能就能看到如下圖所示,紅色的就表示 RST 包了。

        TIME_WAIT 和 CLOSE_WAIT

        TIME_WAIT 和 CLOSE_WAIT 是啥意思相信大家都知道。

        在線上時(shí),我們可以直接用命令netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'來查看 time-wait 和 close_wait 的數(shù)量

        用 ss 命令會(huì)更快ss -ant | awk '{++S[$1]} END {for(a in S) print a, S[a]}'

        TIME_WAIT

        time_wait 的存在一是為了丟失的數(shù)據(jù)包被后面連接復(fù)用,二是為了在 2MSL 的時(shí)間范圍內(nèi)正常關(guān)閉連接。它的存在其實(shí)會(huì)大大減少 RST 包的出現(xiàn)。

        過多的 time_wait 在短連接頻繁的場(chǎng)景比較容易出現(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

        #表示開啟重用。允許將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(shí)間戳錯(cuò)亂導(dǎo)致數(shù)據(jù)包被拒絕的坑了,另外的辦法就是改小tcp_max_tw_buckets,超過這個(gè)數(shù)的 time_wait 都會(huì)被干掉,不過這也會(huì)導(dǎo)致報(bào)time wait bucket table overflow的錯(cuò)。

        CLOSE_WAIT

        close_wait 往往都是因?yàn)閼?yīng)用程序?qū)懙挠袉栴},沒有在 ACK 后再次發(fā)起 FIN 報(bào)文。close_wait 出現(xiàn)的概率甚至比 time_wait 要更高,后果也更嚴(yán)重。往往是由于某個(gè)地方阻塞住了,沒有正常關(guān)閉連接,從而漸漸地消耗完所有的線程。

        想要定位這類問題,最好是通過 jstack 來分析線程堆棧來排查問題,具體可參考上述章節(jié)。這里僅舉一個(gè)例子。

        開發(fā)同學(xué)說應(yīng)用上線后 CLOSE_WAIT 就一直增多,直到掛掉為止,jstack 后找到比較可疑的堆棧是大部分線程都卡在了countdownlatch.await方法,找開發(fā)同學(xué)了解后得知使用了多線程但是確沒有 catch 異常,修改后發(fā)現(xiàn)異常僅僅是最簡(jiǎn)單的升級(jí) sdk 后常出現(xiàn)的class not found。

        程序汪資料鏈接

        程序汪接的7個(gè)私活都在這里,經(jīng)驗(yàn)整理

        Java項(xiàng)目分享  最新整理全集,找項(xiàng)目不累啦 04版

        堪稱神級(jí)的Spring Boot手冊(cè),從基礎(chǔ)入門到實(shí)戰(zhàn)進(jìn)階

        臥槽!字節(jié)跳動(dòng)《算法中文手冊(cè)》火了,完整版 PDF 開放下載!

        臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!

        字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開放下載!

        歡迎添加程序汪個(gè)人微信 itwang008  進(jìn)粉絲群或圍觀朋友圈

        瀏覽 29
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            久久偷看各类女兵18女厕嘘嘘 | 国产精品久久久久永久免费看 | 国内成人视频 | 国产熟女诱惑视频 | 麻豆自拍视频 | 白丝美女喷水 | 欧美日韩一级黄色片 | 大鸡巴操逼视频免费 | 免费成人黄色网址 | 青青草国产精品 |