問題1
多人共享開發服務器(windows系統),我們小組有個程序,定時檢測mongodb,redis,mysql連接是否正常,程序啟動一段時間后,服務器管理人員找到我們說,我們的某個pid的程序把TCP連接占滿了,很多功能都不可使用,第一次調查發現未關閉連接,然后修改了,修改之后還是會出現TCP連接被全部耗盡的情況。
調查
復現問題
啟動上述問題程序,找到其對應的java的pid,查看其建立的線程數
netstat -ano | findstr "28720" | find /v /c ""
發現TCP連接在很短的時間內,增長非常快,程序的確有問題
由於我們程序中比較占用TCP的就只有在獲取上述三個服務連接的時候,同事講關閉連接沒問題,所以也就沒有怎么注意那塊,有同事反饋mongodb,mysql,redis都正常連接的時候沒有出現問題,故分別停掉以上三個數據庫,查看該程序占用的TCP連接數
最后發現,停掉redis的時候,TCP連接數增長非常快,所以懷疑是redis連接問題,最后還是要去看下代碼,經過查看代碼,找到了一個懷疑的點,代碼中是這么寫的
private RedisClient client; private StatefulRedisConnection<String,String> conn; public void redisTest(){ try{ client=... conn=... .... }catch(Exception e){ logger.error("",e) }finally{ try{ conn.close(); client.close(); }catch(Exception ex){ logger.error("",ex) } } }
乍一看,似乎沒什么大問題,但這里隱藏了一個不是必現的BUG,由於我們一般啟動程序時,都會配置正確連接,但如果上面的conn為空怎么辦嘞,很明顯會發生空指針,后面的client連接自然就不會釋放,但奇怪的是,在日志文件中也並沒有發現空指針異常日志輸出。
為了驗證猜想,在finally中分別打印出上述conn和client的值
驗證了猜想,conn為空,造成后面的client未被釋放。找到了問題,代碼修改比較簡單,只需要在finally塊中對conn和client做非空判斷即可
}finally{ try{ if (conn != null){ conn.close(); } if(client != null){ client.close(); } }catch(Exception ex){ logger.error("",ex) } }
連接不釋放,TCP連接一直快速增長,造成的危害很大,在代碼中,關閉多個連接時,一定要注意非空判斷。
問題2
程序跑一段時間,內存占用超過了平時的4,5倍
調查
網上有講在使用free -m查看內存時,不能只看used,因為在linux的內存分配機制中,優先使用物理內存,當物理內存還有空閑時,不會釋放其占用內存,就算占用內存的程序已經被關閉了,該程序所占用的內存用來做緩存使用,對於開啟過的程序、或是讀取剛存取過得數據會比較快,應該查看buffers/cached+free,才是可用內存,但通過free -m查看本機的內存占用時,發現buffers/cached+free占用的內存不多,實際被使用到的內存達到了30個G,通過top命令查看每個程序占用到的內存也並不多,每個程序占用內存的百分比都很少,VIRT占用比較大,但它並不是程序占用的內存。
我們知道每個TCP連接也是耗內存的,那會不會是連接數過多造成的內存劇增,查看連接數
netstat -na | grep ESTABLISHED
發現有某個地址的連接非常多
查看當前機器總共建立連接
[root@localhost data]# netstat -na|grep ESTABLISHED|wc -l 21127
該鏈接數還在增長,查看上述出現次數比較多的tcp連接數量(肉眼查看到的,比較low的方法,其實可以用腳本統計處每個外部地址占用的連接數)
[root@localhost data]# netstat -na|grep ESTABLISHED|grep ip_addr |wc -l 21121
發現幾乎所有建立的連接都來自這台外部機器,這台機器部署的了一個模擬程序,停止模擬程序,內存恢復到正常狀態。
腳本統計每個連接到本機的ip的TCP連接數
netstat -na | grep ESTABLISHED | awk '{print $5}'| awk -F ":" '{print $1}'| sort | uniq -c
第一行為連接總數,第二行為連接當前服務器ip地址