天天看點

netstat統計的tcp連接配接數與⁄proc⁄pid⁄fd下socket類型fd數量不一緻的分析

新blog位址: http://hengyunabc.github.io/netstat-difference-proc-fd-socket-stat/

最近,線上一個應用,發現socket數緩慢增長,并且不回收,超過警告線之後,被運維監控自動重新開機了。

首先到zabbix上觀察JVM曆史記錄,發現JVM-Perm space最近兩周沒有資料,猜測是程式從JDK7切換到JDK8了。問過開發人員之後,程式已經很久沒有重新開機了,最近才重新釋出的。而在這期間,線上的Java運作環境已經從JDK7更新到JDK8了。

因為jdk8裡沒有Perm space了,換成了Metaspace。

netstat

到線上伺服器上,用netstat來統計程序的connection數量。

netstat -antp | grep pid | wc -l           

發現比zabbix上的統計socket數量要少100多,netstat統計隻有100多,而zabbix上監控資料有300多。

于是到/proc/$pid/fd下統計socket類型的fd數量:

cd /proc/$pid/fd
ls -al | grep socket | wc -l           

發現資料和zabbix上的資料一緻。

netstat是怎麼統計的

下載下傳netstat的源代碼

http://unix.stackexchange.com/questions/21503/source-code-of-netstat
apt-get source net-tools           

從netstat的代碼裡,大概可以看到是讀取/proc/net/tcp裡面的資料來擷取統計資訊的。

java和c版的簡單netstat的實作

java版的

http://www.cs.earlham.edu/~jeremiah/LinuxSocket.java

C版的:

http://www.netmite.com/android/mydroid/system/core/toolbox/netstat.c

用starce跟蹤netstat

strace netstat -antp            

可以發現netstat把/proc 下的很多資料都讀取出來了。于是大緻可以知道netstat是把/proc/pid/fd 下面的資料和/proc/net/下面的資料彙總,對照得到統計結果的。

哪些socket會沒有被netstat統計到?

又在網上找了下,發現這裡有說到socket如果建立了,沒有bind或者connect,就不會被netstat統計到。

http://serverfault.com/questions/153983/sockets-found-by-lsof-but-not-by-netstat

實際上,也就是如果socket建立了,沒有被使用,那麼就隻會在/proc/pid/fd下面有,而不會在/proc/net/下面有相關資料。

簡單測試了下,的确是這樣:

int socket = socket(PF_INET,SOCK_STREAM,0); //不使用           

另外,即使socket是使用過的,如果執行shutdown後,剛開始裡,用netstat可以統計到socket的狀态是FIN_WAIT1。過一段時間,netstat統計不到socket的資訊的,但是在/proc/pid/fd下,還是可以找到。

中間的時候,自己寫了個程式,把/proc/pid/fd 下的inode和/proc/net/下面的資料比較,發現的确有些socket的inode不會出現在/proc/net/下。

用lsof檢視

用lsof檢視socket inode:

觸發GC,回收socket

于是嘗試觸發GC,看下socket會不會被回收:

jmap -histo:live <pid>           

結果,發現socket都被回收了。

再看下AbstractPlainSocketImpl的finalize方法:

/**
     * Cleans up if the user forgets to close it.
     */
    protected void finalize() throws IOException {
        close();
    }           

可以看到socket是會在GC時,被close掉的。

寫個程式來測試下:

public class TestServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        for(int i = 0; i < 10; ++i){
            ServerSocket socket = new ServerSocket(i + 10000);
            System.err.println(socket);
        }
        System.in.read();
    }
}           

先執行,檢視/proc/pid/fd,可以發現有相關的socket fd,再觸發GC,可以發現socket被回收掉了。

其它的東東

anon_inode:[eventpoll]

ls -al /proc/pid/fd           

可以看到有像這樣的輸出:

661 -> anon_inode:[eventpoll]           

這種類型的inode,是epoll建立的。

再扯遠一點,linux下java裡的selector實作是epoll結合一個pipe來實作事件通知功能的。是以在NIO程式裡,會有anon_inode:[eventpoll]和pipe類型的fd。

為什麼tail -f /proc/$pid/fd/1 不能讀取到stdout的資料

http://unix.stackexchange.com/questions/152773/why-cant-i-tail-f-proc-pid-fd-1

總結

原因是jdk更新之後,GC的工作方式有變化,FullGC執行的時間變長了,導緻有些空閑的socket沒有被回收。

本文比較亂,記錄下一些工具和技巧。