環(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