从一个线上服务器警告谈谈backlog

时间:2024-01-22 10:59:20

缘起

双十一如期而至,此时的我因为在处理客户的一个问题已经陷入了忙碌。突然,不断接到驻场实施发来的反馈,都是相同的反馈——"客户端操作缓慢"。

我现在负责的服务器是一台接口服务器,所有的卖家都要通过这台服务器连接到自己的数据库上,不得小觑。于是我立马放下手头的话,打开了我事先安装好的服务器监控软件(netdata),便看到了下面的警告:

其实,作为一个服务器端新手,我并不知道什么意思。但是客户反馈慢啊!屁股一想也知道是因为服务器资源不够了嘛!于是,我立马把一小部分客户的配置切换到了另外一台准备好的服务器上,危机化解了,我擦了一把汗。

所以,什么是tcp accept queue?

通过搜索这个警告,找到了一个叫backlog的关键字,网上很多办法是增大这个值,因为当时双十一已经过去了,客户已经没有那么多单子发货了。所以,也没有验证这个方法是否对不对。但backlog的迷还一直留在我的脑海里。

http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html

直到有一天,我遇到了上面的一篇文章,才对backlog有较为具体的认识。我简单的根据文中的意思介绍下backlog参数。

要搞清楚这个问题,我们必须要先了解下tcp建立链接的三次握手,从图中可以看出,第一次握手是客户端给服务器发送syn,此时服务器进入SYN_RECV状态(也叫未完成链接,对应图中的SYN_RCVD),那么既然是一个服务器,所以接受这样的链接就不是一个,是很多,所以会有一个队列去记录这样的未完成链接,暂且称为syn队列。

第一次握手完成后,服务器会给客户端发送 SYN+ACK,客户端收到后,这个链接在客户端方面已经是ESTABLISHED状态了。客户端接着会给服务器发送ACK,当服务器收到这个ACK的时候,服务器上的该链接由SYN_RECV 状态到ESTABLISHED状态,此时它进入了一个新队列,这里叫它accept队列。顾明思议就是供accept来消耗的请求。

两个队列的长度限制由什么参数决定?

经过上面分析,我们知道服务器端处理的请求链接是有两个队列来控制,syn队列(对应图中的syns queue)是等待客户端回复ack的链接,叫做未完成链接或者半连接,而完成三次握手后,该链接进入accept队列,那么这两个队列的长度是多少呢?

由上图可以看出,syn队列就是由 /proc/sys/net/ipv4/tcp_max_syn_backlog参数来控制。处于这个队列上的链接需要等待的全双工的通信时间,所以如果这个参数开的太小,可能对于一些并发性比较高的http服务器来说,容易成为瓶颈。因为这个syns队列满了以后,新的请求就因为队列满的缘故而被忽略。

而accept函数是有两个参数共同决定的,分别是系统函数listen函数的backlog参数以及内核参数somaxconn,这两者会取最小值,如果应用服务器能够很快的从accept队列中取出任务并执行,这个队列并不会成为负担,但如果每个请求都要耗费很长时间处理的话,那么服务器处理线程将会被打满,这个时候accept队列就会被占满。

accept队列被占满的时候,会引入一个新的问题,那就是此时如果未完成链接接收到客户端的ack完成三次握手,需要转入accept队列,此时该怎么办?此时,如果
tcp_abort_on_overflow参数为0的话,此时服务器什么也不做,忽略掉这次申请转到accept队列的请求。那么这个请求就仍然留在了syns队列,服务器隔断时间会再次发送syn+ack给客户端,也就是说会重复走第二部握手。所以,这里accept队列也应该设置大一些,这样可以省去服务器重复发请求的消耗。

关于syn队列长度的限制,大部分文献说的是由 tcp_max_syn_backlog 参数控制,但也有部分博客有更详细的说明,认为syn队列不仅仅受控制与该参数,带着这个疑问,我也看了些源码:

以上linux源码中,max_qlen_log中代表syn队列的最大长度,sysctl_max_syn_backlog 即是tcp_max_syn_backlog参数的值。第一行的nr_table_entries 是计算这个最大值的因子,它是作为一个参数传入进来的,它是传入的backlog参数,上面已经介绍过backlog参数是内核参数somaxconn和listen函数中传入的backlog的最小值,而通过第一行源码我们可以看出来,这里syn队列的最大值是取backlog 和 tcp_max_syn_backlog的最小值,因此得到以下结论。

syn queue length = min(somaxconn,backlog,tcp_max_syn_backlog)

accept queue length = min(somaxconn,backlog)

实验验证

口说无凭,下面进行一个实验,来按照上面的理论分析下。

准备

OS 云机 1核1G的入门机

服务器是nginx + php-fpm

服务器监控软件netdata

ab压测工具

step 1 : 参数设置

经过上面对两个队列的长度限制分析知道somaxconn参数对两个队列有影响,于是设置somaxconn的值为8,其他backlog参数均为1024。

net.ipv4.tcp_max_syn_backlog = 1024

net.core.somaxconn = 8

net.ipv4.tcp_syncookies = 0

为了避免其他因素的影响我们的测试php文件中只有一句

ehco hello world;

step2: 开始压测

ab 压测 工具并发512个请求,请求总数5120个,请求发送完,发现netdata已经收到了报错信息如下:

step3:得到结果

此时,可以看到两个队列都已经开始drop链接了。

step4:将somaxconn参数为1024,再次相同条件压测发现已经没有警告提示了

结论

通过合理的参数设置,是可以提高服务器报警的门槛,将参数调整到服务器能够承受的最大值,确实可以在一定程度上提高服务器的容量。一方面,队列增大后,可以暂时容纳这些队列,供应用程序消耗。如果队列设置过小,那么新来的链接就会被忽略,客户端还会进行重试,另一方面如果accept很快被充满的话,syn队列会被阻塞,而且syn队列上的链接仍要去消耗资源对客户端重新发送syn+ack,导致一个恶性循环。

但这不意味着尽量调整参数就可以万事大吉了,在一些突发访问的网络中,调大参数可以缓解网络情况,但此时客户端访问服务器肯定不会很快,因为服务器已经陷入了繁忙状态。如果服务器一直处于报警状态,即链接不是突发性的,而是长时间处于一个比较高的数量,那么就需要进行优化了。优化分为两方面,第一方面应用程序的优化,即让每个链接的处理速度加快,这样线程可以快速的释放,accept消耗加快,服务器处理速度加快。另外一方面,就是扩容服务器,通过负载均衡水平扩展计算资源,让链接分配到不同机器上快速消耗。