当前位置: 首页 > 科技观察

Linux上调优TCP的几个内核参数

时间:2023-03-13 11:43:14 科技观察

Linux作为一个强大的操作系统,提供了一系列的内核参数供我们调优。仅TCP就有50多个调整参数。笔者在对抗线上问题的过程中,积累了一些在内网环境下应该调优的参数。分享到这里,希望对大家有所帮助。调优清单好吧,让我们先列出调优清单。请记住,这只是笔者在内网调优TCP内核参数的经验,仅供参考。同时,作者会在剩下的博客中详细说明为什么需要这些调优!序号内核参数值备注1.1/proc/sys/inet/ipv4/tcp_max_syn_backlog20481.2/proc/sys/net/core/somaxconn20481.3/proc/sys/net/ipv4/tcp_abort_on_overflow12.1/proc/sys/net/ipv4/tcp_tw_recycle0NAT环境必须是02.2/proc/sys/net/ipv4/tcp_tw_reuse13.1/proc/sys/net/ipv4/tcp_syn_retries33.2/proc/sys/net/ipv4/tcp_retries253.3/proc/sys/net/ipv4/tcp_slow_start_after_idle0当然这里只列出了一些TCP的内核参数。更多关于web性能的建议请看tcp_max_syn_backlog,somaxconn,tcp_abort_on_overflowtcp_max_syn_backlog,somaxconn,tcp_abort_on_overflow这三个参数是关于内核TCP连接缓冲队列的设置。如果应用层来不及从队列中取出已经成功建立三次握手的TCP连接,就会在溢出缓冲队列(fullconnectionqueue)后丢弃该连接。如下图所示:结果,产生了一些奇怪的现象。这种现象比较奇怪的是,在第三次TCP握手的时候连接被丢弃了,如图,第二次握手的SYNACK发给了客户端。所以会出现client端认为连接成功,但是server端确实已经丢弃连接的现象!因为它无法感知到服务器已经丢弃了连接。所以如果没有心跳,只有发送完第一个请求后,服务器才会发送一个reset,通知连接已经被丢弃。如果第二天建立连接使用,会报错!所以我们需要增加Backlog队列!echo2048>/proc/sys/inet/ipv4/tcp_max_syn_backlogecho2048>/proc/sys/net/core/somaxconn当然为了避免第一次调用失败,我们也需要设置echo1>/proc/sys/net/ipv4/tcp_abort_on_overflow设置该值后,服务器端内核会在连接溢出后向客户端发送复位包。如果我们的客户端是NIO,我们可以收到一个socketclose事件来感知连接关闭!注意Java默认的Backlog是50,TCPBacklog的queuesize是min(tcp_max_syn_backlog,somaxconn,由应用层backlog设置),如果Java没有额外设置,Backlog的默认值只有50。C语言在使用listen调用时需要传入Backlog参数。参数tcp_tw_recycleetcp_tw_recycle一般用来抑制TIME_WAIT的次数,但是有一个副作用。即当tcp_timestamps开启时(Linux默认开启),tcp_tw_recycle往往会造成如下现象。也就是说,如果你的Server开启了tcp_tw_recycle,那么如果有人通过NAT之类的方式调用你的Server,只有NAT后面的一台机器可以正常工作,其他情况大概率会失败。具体原因如下图所示:当tcp_tw_recycle=1和tcp_timestamps(默认开启时),相同IP的连接会受到这样的限制,即前后建立的连接的时间戳必须大于那些之前建立的连接的最后一个时间戳,但是一个通过NAT的IP后面是不同的机器,时间戳相差很大,会导致内核直接丢弃时间戳较低的连接。由于该参数带来的问题,高版本内核已经去掉了该参数。如果考虑TIME_WAIT问题,可以考虑设置echo1>/proc/sys/net/ipv4/tcp_tw_reuseetcp_syn_retries这个参数值是客户端发送SYN的次数,如果服务器没有回复,重传SYN。对我们的直接影响就是connet建立连接的超时时间。当然,Java允许我们通过一些C原生系统调用的组合来设置超时时间。Linux中默认设置为5,下面给出了建议值3和默认值5之间的超时时间。tcp_syn_retriestimeout1min(so_sndtimeo,3s)2min(so_sndtimeo,7s)3min(so_sndtimeo,15s)4min(so_sndtimeo,31s)5min(so_sndtimeo,63s)下图是重传和超时的对应图:当然不同内核版本超时可能不同,因为初始RTO在内核次要版本之间略有不同。因此,有时在抓包时可能会出现(3,6,12...)这样的序列。当然,JavaAPI是有超时的:java://函数调用带有超时publicvoidconnect(SocketAddressendpoint,inttimeout);因此,对于Java来说,这个内核参数的设置并不是那么重要。但是有些代码可能会忘记设置超时时间,比如某个版本的Kafka,所以在我们的一些混沌测试中,容灾时间会达到一分钟以上,主要时间卡在了上面的Connect中-_-!,而此时我们的tcp_syn_retries设置为5,即超时时间为63s。减少这个恢复时间的手段是:echo3>/proc/sys/net/ipv4/tcp_syn_retriestcp_retries2这个参数表面上就是传输过程中tcp重传的次数。但是到了某个版本之后,linux内核就只用这个tcp_retries2来计算超时时间了。这段时间内的重传次数纯粹由RTO等环境因素决定。重传超时时间在5/15下的表现是:Response525.6s-51.2s根据动态rto15924.6s-1044.6s根据动态rto如果我们在应用层设置的Socket的ReadTimeouts都很小(比如3s),这个内核参数调整是没有必要的。但是笔者经常发现,在一些系统中,因为一两个慢接口或者SQL,导致ReadTimeout设置的很大。通常,这种情况不是问题,因为慢请求的频率很低,不会对系统造成任何风险。但是,当物理机突然宕机时,情况就不一样了。因为ReadTimeOut设置太大,所有陷入这个宕机的机器都会在min(ReadTimeOut,(924.6s-1044.6s)(Linux默认tcp_retries2为15))从read系统调用返回。假设ReadTimeout设置为5分钟,系统总线程数为200,那么只要5分钟内有200个请求落到宕机服务器,系统A就会失去响应!但是如果tcp_retries2设置为5,那么超时返回时间为min(ReadTimeOut5min,25.6-51.2s),也就是30s左右,大大缓解了这种情况。echo5>/proc/sys/net/ipv4/tcp_retries2但是对于这种现象,最好做资源隔离,比如线程隔离或者机器级别的隔离。golang的goroutine调度模型可以很好的解决线程资源不足的问题,但是缺点是goroutine中不能有阻塞的系统调用,否则和上面一样,只是针对系统间的相互调用,它们是都是非阻塞IO,所以golang对于微服务来说还是很不错的。当然我这个大Java写纯IO事件触发的代码不会有问题,但是精神负担太高了-_-!物理机的突然宕机不同于进程的宕机。值得注意的是,物理机的宕机和进程的宕机但内核仍然存在,并且表现完全不同。只有进程宕机而内核还活着,那么内核会立即向对端发送一个reset,这样系统A的线程资源就不会被卡住。tcp_slow_start_after_idle另一个可能需要调整的参数是tcp_slow_start_after_idle。Linux默认为1,即打开状态。启用该参数后,我们的TCP拥塞窗口会在一个RTO时间空闲后重新设置为初始拥塞窗口(CWND)大小,这无疑大大降低了长连接的优势。对应的linux源码为:staticvoidtcp_event_data_sent(structtcp_sock*tp,structsk_buff*skb,structsock*sk){//如果开启start_after_idle,本次发送时间-上次发送时间>anrto,restartSettcpcongestionwindowif(sysctl_tcp_slow_start_after_idle&&(!tp->packets_out&&(s32)(now-tp->lsndtime)>icsk->icsk_rto))tcp_cwnd_restart(sk,__sk_dst_get(sk));}关闭此参数后,无疑会提高一些请求的传输速度(在带宽足够的情况下)。echo0>/proc/sys/net/ipv4/tcp_slow_start_after_idlelinux开启这个参数当然是有原因的。如果我们的网络情况时刻都在变化,比如拿着手机四处走动,那么重置拥塞窗口确实是一个不错的选择。但是就我们内网系统之间的调用来说,就没有必要了。InitialCWNDsize毫无疑问,新连接后的初始TCP拥塞窗口大小也直接影响我们的请求率。在Linux2.6.32源码中,它的初始拥塞窗口是(2-4)mss大小,对应内网估计是(2.8-5.6K)(MTU1500),这个大小对于一些大的请求可能有点拉长。在Linux2.6.39以上或者RedHat维护的一些小版本中,CWND增加到RFC6928中规定的10段,估计在内网14K左右(MTU1500)。Linux新版本/*TCP初始拥塞窗口*/#defineTCP_INIT_CWND10总结Linux提供了很多内部参数供我们调优,其默认设置在很多情况下并不是最佳实践,因此需要我们潜心研究,找到最适合当前环境的组合。