tcp协议在定位中的应用(2)

  上一篇文章说到还是产生 accpet open too many files的错误。

  这个一般是通过修改ulimit就可以了,但是修改这个参数有一个误区,就是生效的时机。虽然你登录终端看ulimit -a看到open files连接数是够大了,但是对于app运行的环境并不一定是这个数目。具体可以通过cat /proc/{app自己的pid}/limits来确认真实的limits。

  对于当时情况是:ulimit 修改后没有对用户进程产生作用。经过不停排除发现进程是由supervisor来启动,先找到supervisor,修改superv里面的fd值,发现还是不能生效。发现supervisord又是由 systemctrl启动,在systemtrl的配置文件里面修改了最大文件数,最后才生效。

  通过修改好limit open fils这一项,后续再也没有出现open too many files的错误了。

       当时在看出现open too many files的时候,发现netstat -apnt 查看,发现有很多close_wait。这个是如何产生的?

       首先我们理解accept: open too many files中的open too many files是句柄数目的限制。那么accept是什么?

       tcp连接中,分为两种连接,半连接和全连接。全连接的也就是 ESTABLISHED 建立的连接,也就是accept操作建立的连接。accpet队列就是全连接队列。这个队列的大小由两个参数的最小值决定,一个是系统级别,另一个是独立应用级别的。

  • 全连接队列的大小:min(backlog, /proc/sys/net/core/somaxconn),意思是取backlog 与 somaxconn 两值的最小值,net.core.somaxconn 定义了系统级别的全连接队列最大长度,而 backlog 只是应用层传入的参数,所以 backlog 值尽量小于net.core.somaxconn;

  • net.core.somaxconn(内核态参数,系统中每一个端口最大的监听队列的长度);

  • net.core.netdev_max_backlog(每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目);

  • ServerSocket(int port, int backlog) 代码中的backlog参数;

  • 文件句柄;

  • net.ipv4.tcp_abort_on_overflow = 0,此值为 0 表示握手到第三步时全连接队列满时则扔掉 client 发过来的 ACK,此值为 1 则说明握手到第三步时全连接队列满时则返回 reset 给客户端。

  连接的数量有一个地方需要澄清,客户端进入了连接状态,但是服务端不一定进入了连接状态,根据状态转移图:有一个步骤的时间差,就是收到客户端的ack,后服务端才认为自己正式建立了连接。所以连接数,在客户端与服务端之间有一个时间差。

 

  实际中,发现服务端建立连接,但是由于服务端各种原因,自己可以让服务睡眠时间久点。accept客户端的连接请求慢点,导致最终accept队列满了,一般阈值是(128+1)个连接。然后客户端超时,请求关闭还没有进入到全连接队列的连接,SYN_RECV 状态持续一段时间后会消失。服务端对于客户端的关闭请求回复了ack,进入了close_wait, 但是服务端的连接还在被accept阻塞,所以会一直处于close_wait的状态中不释放。
  最终要把close_wait释放的手段,一种程序内部调用close关闭连接(需要改代码),一种是被kill掉。通过tcpdump抓包,发现这种是服务端主动发起RST报文,然后关闭了连接。
       所以线上出现了close_wait问题,那么很有可能就是程序被阻塞,或者是某种情况没有close掉连接
 
注意,该代码没有给连接主动close掉。
package main

import (
        "log"
        "net"
        "time"
)

func main() {
        l, err := net.Listen("tcp", ":8889")
        if err != nil {
                log.Println("error listen:", err)
                return
        }
        defer l.Close()
        log.Println("listen ok")

        var i int
        for {
                time.Sleep(time.Second * 100000)
                log.Printf("%d: accept a new connection
", i)
                if _, err := l.Accept(); err != nil {
                        log.Println("accept error:", err)
                        break
                }
                i++
                log.Printf("%d: accept a new connection
", i)
        }
}

netstat -antp 查看

cp        0      0 192.168.0.105:8889      192.168.0.100:40088     SYN_RECV    -                   
tcp        0      0 192.168.0.105:8889      192.168.0.100:40100     SYN_RECV    -                   
tcp        0      0 192.168.0.105:8889      192.168.0.100:40102     SYN_RECV    -                   
tcp        0      0 192.168.0.105:8889      192.168.0.100:40090     SYN_RECV    -                   
tcp        0      0 192.168.0.105:8889      192.168.0.100:40098     SYN_RECV    -                   
tcp        0      0 192.168.0.105:8889      192.168.0.100:40092     SYN_RECV    -                   
tcp        0      0 192.168.0.105:8889      192.168.0.100:40096     SYN_RECV    -                   
tcp        0      0 192.168.0.105:8889      192.168.0.100:40094     SYN_RECV    -                   
tcp        0      0 192.168.0.105:8889      192.168.0.100:40086     SYN_RECV    -                   
tcp6     129      0 :::8889                 :::*                    LISTEN      37482/server        
tcp6       0      0 192.168.0.105:8889      192.168.0.100:40034     ESTABLISHED -                   
tcp6       0      0 192.168.0.105:8889      192.168.0.100:40014     ESTABLISHED -                   
tcp6       0      0 192.168.0.105:8889      192.168.0.100:39844     ESTABLISHED -                   
tcp6       0      0 192.168.0.105:8889      192.168.0.100:40002     ESTABLISHED -                   
tcp6       0      0 192.168.0.105:8889      192.168.0.100:40040     ESTABLISHED -                   
tcp6       0      0 192.168.0.105:8889      192.168.0.100:39942     ESTABLISHED -                   
tcp6       0      0 192.168.0.105:8889      192.168.0.100:40022     ESTABLISHED -                   
tcp6       0      0 192.168.0.105:8889      192.168.0.100:39992     ESTABLISHED -         
......
 
[jet@192 ~]$ sudo netstat -lpnt  | grep 8889
tcp6     129      0 :::8889                 :::*                    LISTEN      37482/server    
 
客户端:
[jet@192 ~]$ sudo netstat -antp | grep client | wc -l
129
 
服务端:
jet@192 ~]$ sudo sysctl -a | grep conn
net.core.somaxconn = 128
 
 
kill 掉客户端请求后,就发现服务端的状态进入了close_wait,因为kill掉客户端后,客户端会发出关闭连接请求, SYN_RECV    状态请求在半连接队列里面,后面会消失:
[jet@192 ~]$ sudo netstat -antp | grep client 
[jet@192 ~]$ 
服务端
[jet@192 ~]$ sudo netstat -anpt  | grep 8889 | grep CLOSE_WAIT | wc -l
129
原文地址:https://www.cnblogs.com/studyNT/p/13975600.html