Linux TCP参数调优

在写这篇文章之前写过一篇文章:tcp too many orphaned sockets 问题引发的思考,里面讲了主要讲了下端口,socket相关也涉及到部分参数调优,但是最近又遇到一些网络方面的问题涉及到一些调参,所以我觉得应该单独整理一片关于TCP调优的博客,也不会显得那么冗余和杂乱了。

(下文中涉及到对/etc/sysctl.conf中参数的修改都是永久修改,后文 不再赘述。方法:将参数添加到/etc/sysctl.conf中,然后执行sysctl -p使参数生效,永久生效)

0x01 端口相关

关于端口耗尽的问题,之前那篇博客也有讲到,这里就不啰嗦了。一般作为服务端,都是监听一个端口,然后for循环去accept连接,然后进行处理就行。因此一般也不存在服务端端口被耗尽。
比如golang中这样的代码:

//以省略不相关代码
func main() {
    l, err := net.Listen("tcp", ":8888")
    if err != nil {
        fmt.Println("listen error:", err)
        return
    }

    for {
        c, err := l.Accept()
        if err != nil {
            fmt.Println("accept error:", err)
            break
        }
        // start a new goroutine to handle
        // the new connection.
        go handleConn(c)
    }
}

当然服务器上一般不会只有一个服务,可能还有其他服务,或者你的服务收到请求后还会作为client去调用其他service,这个时候就需要关注机器的端口范围了。
调优点一:
默认端口范围

# sysctl -a|grep ip_local_port_range
net.ipv4.ip_local_port_range = 32768	60999

可以视情况调整,比如:

net.ipv4.ip_local_port_range = 1024 65535

0x02 资源相关

accpet调用成功之后就成功建立了一个连接了,java中直接返回一个socket,golang中还封装了一下返回一个net.Conn,但是最终结果都是一样的:通过持有这个socket进行读写,socket的本质是一个文件描述符这个在之前的博客中也讲到了。

//golang 的net.Conn的 Read Write 函数:
// Read implements the Conn Read method.
func (c *conn) Read(b []byte) (int, error) {
    if !c.ok() {
        return 0, syscall.EINVAL
    }
    n, err := c.fd.Read(b)
    if err != nil && err != io.EOF {
        err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
    }
    return n, err
}

// Write implements the Conn Write method.
func (c *conn) Write(b []byte) (int, error) {
    if !c.ok() {
        return 0, syscall.EINVAL
    }
    n, err := c.fd.Write(b)
    if err != nil {
        err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
    }
    return n, err
}

上面废话这么多就是想重点说明:socket作为一种文件资源,就会收到Linux系统的限制,你不能随意无限使用。这个限制体现在:Linux对文件描述符资源的限制。
调优点二:
可调优参数有:

  1. 用户级别
    查看Linux系统用户最大打开的文件限制
#ulimit -n 
65535

修改打开文件限制
vim /etc/security/limits.conf

root soft nofile 102400
root hard nofile 102400

其中root指定了要修改哪个用户的打开文件数限制。
可用’*'号表示修改所有用户的限制;soft或hard指定要修改软限制还是硬限制;
102400则指定了想要修改的新的限制值,即最大打开文件数(请注意软限制值要小于或等于硬限制)

  1. 系统级别
    调优点三:
    查看Linux系统对同时打开文件数的硬限制:
# sysctl -a|grep file-max
fs.file-max = 65535

这个限制是指所有用户的总和,即整个系统支持的最大值
修改file-max限制,/etc/sysctl.conf文件中

fs.file-max = 6815744

一般好点的机器上这个值都还是比较大的。

上面说的了Linux对文件资源的限制,此外大家知道TCP在收发信息的时候是有一个缓冲区的,而缓冲区又是一种内存资源,所有Linux系统肯定也要对该资源进行限制。

调优点四:

  1. 缓冲区调优
    设置TCPTCP数据接收、发送窗口大小
net.core.optmem_max = 513920
net.core.rmem_default = 256960
net.core.rmem_max = 513920
net.core.wmem_default = 256960
net.core.wmem_max = 513920

还有这两个参数

net.ipv4.tcp_rmem = 8760  256960  4088000
net.ipv4.tcp_wmem = 8760  256960  4088000

其中第一个值是为socket接收缓冲区分配的最少字节数;第二个值是默认值(该值会被rmem_default覆 盖),缓冲区在系统负载不重的情况下可以增长到这个值;第三个值是接收缓冲区空间的最大字节数(该值会被rmem_max覆盖)。

  1. TCP可使用总内存
    这个参数在之前的博文中也有提到,其意义是限制系统中所有TCP可使用的内存总量
net.ipv4.tcp_mem = 131072  262144  524288

第一个值是内存使用的下限;第二个值是内存压力模式开始对缓冲区使用应用压力 的上限;第三个值是内存使用的上限。在这个层次上可以将报文丢弃,从而减少对内存的使用。对于较大的BDP可以增大这些值(注意,其单位是内存页而不是字节)。

上述参数中可以视情况调更大些。

0x03 防火墙相关

线上Hbase集群在访问量突增的时候发现短时间部分请求失败,在一台机器上dmesg发现错误日志如下:

nf_conntrack: table full, dropping packet

这是因为开启了防火墙,防火墙会对每个连接进行追踪,这个数量是有限制的如果超过了就丢包了。因此需要调大该参数,如下:
调优点四:

net.netfilter.nf_conntrack_max=1048576
net.nf_conntrack_max=1048576

0x04 比较隐晦的几个参数

调优点五

net.core.netdev_max_backlog = 400000
net.core.somaxconn = 100000

这两个参数对服务的影响还是挺重要的,如果用nginx做反向代理,如果突然流量暴增,这个时候nginx可能会返回大量502,我觉得其中的一个原因就是这两个参数配置过低导致的。
这两个参数一般在机器上设置的比较小,尤其需要引起注意。比如你可能看到的默认值是这样的:

# sysctl -a|grep netdev_max
net.core.netdev_max_backlog = 1000
# sysctl -a|grep somaxconn
net.core.somaxconn = 128

这两个参数是什么意思呢?
netdev_max_backlog对应的是TCP半连接队列大小,对应图中sync queue
somaxconn对应的是TCP的全连接队列大小,对应图中的accept queue
TCP三次握手中,在第一步server收到client的syn后,就会把相关信息放到半连接队列(sync queue)中,同时回复syn+ack给client(第二步)

(sync flood 攻击也是利用这个弱点,直接打满你的sync queu,这个时候其他请求的连接也进不来了。)

第三步的时候server收到client的ack,如果这时全连接队列没满,那么从半连接队列拿出相关信息放入到全连接队列中,否则按tcp_abort_on_overflow指示的执行。

这里又涉及到两个参数:
调优点六:

net.ipv4.tcp_abort_on_overflow=0
net.ipv4.tcp_synack_retries = 2

tcp_abort_on_overflow 为0表示如果三次握手第三步的时候全连接队列满了那么server扔掉client 发过来的ack(在server端认为连接还没建立起来)
但是server会过一段时间再次发送syn+ack给client(也就是重新走握手的第二步),如果client超时等待比较短,就很容易异常了。如果tcp_abort_on_overflow的值为1的话,那么就会直接rst这个连接。

此外,对于一个TCP连接来说,其半连接队列和全连接队列不是直接使用netdev_max_backlog和somaxconn的值,而是如下方式:
全连接队列的大小取决于:min(backlog, somaxconn),backlog是你写代码的时候创建socket的时候传入的
半连接队列的大小取决于:max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)。 不同版本的os会有些差异

0x05连接回收

如果机器上出现大量FIN_WAIT2, TIME_WAIT等状态那就应该考虑调下面几个参数了
调优点七:

net.ipv4.tcp_syncookies = 1 
net.ipv4.tcp_tw_reuse = 1 
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30

TIME_WAIT状态将持续2个MSL(Max Segment Lifetime),在Windows下默认为4分钟,即240秒。一般情况下TIME_WAIT状态下的socket不能被回收使用,因此需要优化上述参数,避免端口占用。

那如果是CLOSE_WAIT状态呢?
这个时候应该看看自己代码是不是有问题了,这个状态是被动关闭方在收到FIN报文后但没有向主动关闭方发送FIN的状态。

注:上面涉及到的所有调优点都应该根据自己的运行环境来选择适当的值,最好先经过测试环境的验证在放到线上

参考:

https://blog.csdn.net/Just_shunjian/article/details/78288229
https://testerhome.com/topics/7509
关于somaxconn, 阿里中间件的博客:http://jm.taobao.org/2017/05/25/525-1/
https://blog.csdn.net/erlib/article/details/50236919
https://colobu.com/2014/09/18/linux-tcpip-tuning/
golang tcp 编程:https://tonybai.com/2015/11/17/tcp-programming-in-golang/
https://cnblogs.com/pangguoping/p/5830328.html

你可能感兴趣的:(TCP/IP)