一次Java程序運(yùn)行緩慢故障排查過程

環(huán)境:一臺(tái)Dell R720服務(wù)器,8核/32G內(nèi)存/1T硬盤。使用ESXi 6.0做虛擬化。然后創(chuàng)建一臺(tái)Centos7虛擬機(jī),劃4核/16G內(nèi)存
軟件安裝:大屏服務(wù)器DataEase2.10、java開發(fā)的MIS系統(tǒng)
故障現(xiàn)象:DataEase的web頁訪問正常,但自己開發(fā)的java程序web端訪問時(shí)頁面很“卡”,跟蹤后臺(tái)日志輸出也很“卡”

排障過程:
1、使用top、free等命令查看系統(tǒng)負(fù)載很低,內(nèi)存使用不到3G,CPU空閑時(shí)間99%,磁盤I/O也不高
2、進(jìn)入ESXi 后臺(tái)查看硬件監(jiān)控,設(shè)備負(fù)載都很低
3、后臺(tái)日志中也沒有明顯錯(cuò)誤輸出
4、檢查JAVA虛擬機(jī)快照

$ ps -ef | grep java      # 找到j(luò)ava進(jìn)程id
$ jstack  java-id> /tmp/dump.log    # 轉(zhuǎn)儲(chǔ)java vm
$ grep http-nio  /tmp/dump.log   # 得到如下輸出:

"http-nio-8063-exec-5" #32 daemon prio=5 os_prio=0 tid=0x00007f0db46e3000 nid=0x20f3 runnable [0x00007f0d4d6ae000]
java.lang.Thread.State: RUNNABLE
at java.net.Inet6AddressImpl.lookupAllHostAddr(Native Method)
at java.net.InetAddress$2.lookupAllHostAddr(InetAddress.java:929)
at java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1324)
at java.net.InetAddress.getLocalHost(InetAddress.java:1501)
- locked <0x00000005c6276720> (a java.lang.Object)
at com.cjsa.utils.IPUtils.getIpAddr(IPUtils.java:24)
at com.cjsa.filter.UserVisitFilter.postHandle(UserVisitFilter.java:30)

分析:程序中自定義的攔截器/過濾器 UserVisitFilter.postHandle會(huì)攔截每一個(gè)web請(qǐng)求,然后調(diào)用工具IPUtils.getIpAddr去獲取訪問者的ip地址

public static InetAddress getLocalHost() throws UnknownHostException {
        SecurityManager var0 = System.getSecurityManager();

        try {
            String var1 = impl.getLocalHostName();
            if (var0 != null) {
                var0.checkConnect(var1, -1);
            }

            if (var1.equals("localhost")) {
                return impl.loopbackAddress();
            } else {
                InetAddress var2 = null;
                synchronized(cacheLock) {
                    long var4 = System.currentTimeMillis();
                    if (cachedLocalHost != null) {
                        if (var4 - cacheTime < 5000L) {
                            var2 = cachedLocalHost;
                        } else {
                            cachedLocalHost = null;
                        }
                    }

                    if (var2 == null) {
                        InetAddress[] var6;
                        try {
                            var6 = getAddressesFromNameService(var1, (InetAddress)null);
                        } catch (UnknownHostException var10) {
                            UnknownHostException var8 = new UnknownHostException(var1 + ": " + var10.getMessage());
                            var8.initCause(var10);
                            throw var8;
                        }

                        cachedLocalHost = var6[0];
                        cacheTime = var4;
                        var2 = var6[0];
                    }
                }

                return var2;
            }
        } catch (SecurityException var12) {
            return impl.loopbackAddress();
        }
    }

以上代碼中,當(dāng)訪問者并非服務(wù)器自身(localhost)時(shí),getLocalHost() 先獲取當(dāng)前機(jī)器的主機(jī)名,然后去向系統(tǒng)的 DNS 服務(wù)器發(fā)起反向解析查詢,試圖把主機(jī)名解析成 IP。注意這句: if (var4 - cacheTime < 5000L) ,這里會(huì)造成緩存 5 秒!
因?yàn)槲业沫h(huán)境并沒有DNS服務(wù)器(雖然在我的網(wǎng)卡中隨便了一個(gè)),另外在/etc/hosts 文件中也沒有對(duì)這臺(tái)服務(wù)器的名稱(xxx-server)作解析,這樣,當(dāng)執(zhí)行到這個(gè)函數(shù)時(shí),每一個(gè) Web 請(qǐng)求都在這里被強(qiáng)行阻塞了 5 秒。
然后,按道理來說,通過其它遠(yuǎn)程終端訪問時(shí),不應(yīng)該頻繁觸發(fā)getLocalHost()函數(shù)。所以,接下來檢查一下IPUtils.getIpAddr()函數(shù)。

public static String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        ...
        if (StringUtils.isNotBlank(ip)
                || ip.equals("127.0.0.1")
                || ip.equals("0:0:0:0:0:0:0:1")) {
            try {
                ip = InetAddress.getLocalHost().getHostAddress();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ip;
    }   

觀察這一行: if (StringUtils.isNotBlank(ip) || ip.equals("127.0.0.1") 時(shí)會(huì)發(fā)現(xiàn)一個(gè)bug:無論只要ip取到值,它就會(huì)觸發(fā)getLocalHost()函數(shù)!也就是說,實(shí)際上客戶的真實(shí)ip是被丟棄了。
因此,這里的 StringUtils.isNotBlank(ip) 改為 StringUtils.isBlank(ip) 就可以了。

總結(jié):引起java程序卡頓的原因,一是DNS配置錯(cuò)誤,二是取ip代碼bug

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容