前言

在对一个挡板系统进行测试时,遇到一个由于TCP全连接队列被占满而影响系统性能的问题,这里记录下如何进行分析及解决的。

理解下TCP建立连接过程与队列

从图中明显可以看出建立 TCP 连接的时候,有两个队列:syns queue(半连接队列)和accept queue(全连接队列),分别在第一次握手和第三次握手。
半连接队列: 保存 SYN_RECV 状态的连接。
控制参数:

  • 半连接队列的大小:min(backlog, 内核参数 net.core.somaxconn,内核参数tcp_max_syn_backlog).

  • net.ipv4.tcp_max_syn_backlog:能接受 SYN 同步包的最大客户端数量,即半连接上限;

  • tcp_syncookies:当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;

accept队列-全连接队列:保存 ESTABLISHED 状态的连接。
控制参数:

  • 全连接队列的大小: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 给客户端。

系统概况

系统的整体架构比较简单,只有一个挡板服务,业务功能主要是接受业务数据写入日志文件,并加了 35 ms的延时等待,没有复杂的运算等业务逻辑。

开始第一次压测

以 6000 线程并发,加 1 秒的等待,对挡板服务发起压力,压测结果如下:
PS:应客户要求,为了模拟真实业务场景,用较大并发进行测试。

通过上图可以看出,当系统吞吐量也就是 TPS 达到 3800 左右的时候,系统开始出现部分请求失败,继续压一段时间后,报错没有减少,且有增多的趋势。这是什么原因导致的呢,接着我也观察了下,系统的资源使用情况,发现CPU也不是很高,那可以先排除系统CPU资源的问题。

这时候,我们一定要记住,当出现请求事务大量失败的时候,一定要先看以下具体的错误信息,在继续往下面分析,而不是进行盲目的猜测,这里要提一下高楼老师经常强调的证据链,一定要根据详细的错误信息指向进行下一步分析,不能根据猜测进而通过修改一些参数,或者增加系统资源来解决问题。
以下是具体的报错信息:

看到报错信息后,发现有大量的 “Connection reset” 错误,导致这种错误的原因就是服务端因为某种原因关闭了 Connection,而客户端仍然在读写数据,此时服务器会返回复位标志 “RST”,也就是刚才提到的 `“java.net.SocketException: Connection reset”。参考 Oracle 的相关文档,看到这么一段话,原文如下:

By contrast, an abortive close uses the RST (Reset) message. If either side issues an RST, this means the entire connection is aborted and the TCP stack can throw away any queued data which has not been sent or received by either application.

翻译过来也就是说:

如果任何一方发出RST,这意味着整个连接被中止,TCP栈可以丢弃任何没有被任何应用程序发送或接收的队列数据。

这样的话,问题就很明显了,接下来看下 TCP 连接队列的溢出数据统计情况,命令为:“netstat -s

# 查看TCP半连接队列溢出:
netstat -s | grep LISTEN # 查看TCPaccept队列溢出:
netstat -s | grep overflow

通过反复敲命令,可以看出这个 overflow 的值一直在增加,那么这个现象说明 server 的TCP 全连接队列的确是满了。这时候应该想到的是,全连接队列已经溢出了,下一步就应该看一下,全连接队列的占用情况,命令为:

参数说明:

  • Recv-Q:全连接当前长度

  • Send-Q:如果连接不是在建立状态,则是当前全连接最大队列长度

从上图第三列的 Send-Q 可以看出,5000 端口服务的全连接队列最大为 50,而 Recv-Q 为当前使用了多少。在压测过程中,查看指定端口的 TCP 全连接队列使用情况,如下:

上图可以看出,全连接队列几乎已经被占满,那么最终可以确定问题所在了。找到原因后,现在只要增大全连接队列的长度就可以了。
通过上面介绍的全连接队列中,我们知道全连接队列的大小为 backlog 和 somaxconn 的最小值,那么来看下 somaxconn 的取值。

可以看出 somaxconn 的值是很大的,那就只有通知开发,增加应用代码中的 backlog 的值来加大全连接队列的长度。

调大backlog值为5000后,再次进行压测

调整后的全连接队列如下图所示:

继续以 6000 并发对系统发起压力,测试结果如下:

从上面的测试结果数据看出,已经没有错误请求了,再次查看TCP全连接队列的使用情况,Recv-Q的值也变得很大,但是仍小于 5000,这也说明之前的 50 的确太小,导致全连接队列被占满,最终影响系统性能,出现大量请求失败,到此,由 TCP 连接队列满导致的问题解决。

但是仔细看上面的 JMeter 的测试结果数据,发现当系统并发达到 4600 多后,再继续加大线程,系统的响应时间开始大幅度的增加,TPS增加趋势变缓,可以看出来此时系统仍存在瓶颈。
发现仍问题后,接着往下分析。系统没有报错,响应时间变长,导致系统吞吐量增长速度变慢。这时应该清楚的是,接下来该看什么。首先查看系统CPU使用情况,发现并不是很高,说明不是系统资源不够用而引起的问题。因为挡板服务本身没有什么业务逻辑,只是加了 35 ms的延时,那么如果响应时间变慢了,那么多半是由于网络传输出现阻塞导致。
所以使用命令:

netstat -ano | grep 10.231.44.249:5000 | grep ESTABLISHED | more

看下网络队列情况:

参数说明:

  • Send-Q:发送队列中没有被远程主机确认的 bytes 数;

  • Recv-Q:指收到的数据还在缓存中,还没被进程读取,这个值就是还没被进程读取的

  • bytes;一般是CPU处理不过来导致的。

可以看出图中标红列的数据不为 0,通过上面的解释可以判断出是系统 CPU 处理不过来了,但是CPU也没有被充分使用,那为什么会出现这种情况呢。接下我们就该看一下,CPU 在做什么。
这里使用阿里的开源工具 arthas(arthas的安装及使用这里不过多介绍了),看下挡板服务是否存在线程的资源争用或者阻塞等,发现结果如下,存在大量的线程状态为 BLOCKED
命令:thread | grep BLOCKED

命令:thread -b

看到具体的线程栈信息后,问题就比较明显了,是一个写日志的锁导致出现线程阻塞,严重影响系统的处理能力。
为了快速的验证是写日志导致的,调整日志级别为 ERROR,再次发起压力,看问题是否解决,测试结果如下:

调整日志级别后,系统的响应时间保持在 37 ms左右,吞吐量有了大幅度的提升,问题解决。

小结

通过上面的分析案例,需注意以下几点:

  1. 压测时,如果出现请求大量失败时,记住一定要先解决报错,在进行下一步的分析;

  2. 进行性能分析时,一定要找到相应的证据链一步一步的往下分析,而不是盲目的猜测,通过修改参数及加大资源配置来解决问题;

  3. 响应时间长,TPS上不去这种问题,一定要对时间进行拆分拆解,找到时间具体慢在哪里,再进行进一步的分析优化。

转自:https://mp.weixin.qq.com/s/Bjjrk85MTTSiHoECNIWEaw

多学一点:

一、查看当前系统下所有连接状态的数
    netstat -n|awk '/^tcp/{++S[$NF]}END{for (key in S) print key,S[key]}'
    ESTABLISHED 38
    TIME_WAIT 1000
 
二、看下我系统上默认的SYN队列大小
    [root@log]# cat /proc/sys/net/ipv4/tcp_max_syn_backlog 
    262144
    定义SYN队列大小:
    echo 4096 > /proc/sys/net/ipv4/tcp_max_syn_backlog   --定义是php配置的两倍,大于php的就行
 
三、看下我系统上默认的TIME_WAIT队列大小
    cat /proc/sys/net/ipv4/tcp_max_tw_buckets
    1000
    定义TIME_WAIT的大小:
    echo 4096 > /proc/sys/net/ipv4/tcp_max_tw_buckets
 
四、修改backlog参数
    Kernel会为LISTEN状态的socket维护两个队列,一个是SYN RECEIVED状态,另一个是ESTABLISHED状态,而backlog就是这两个队列的大小之和。
    当前Linux版本使用上面说法,有两个队列:具有由系统范围设置指定的大小的SYN队列 和 应用程序(也就是backlog参数)指定的accept队列。
 
五、查看系统默认数量:
    cat /proc/sys/net/core/netdev_max_backlog
    定义队列的数据包的最大数目
    echo "4096"  > /proc/sys/net/core/netdev_max_backlog  #在每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目。
 
六、nginx 配置参数优化:
    有代理就设置在代理上:没有代理就直接设置在web应用上(或者两个都设置)
    upstream js_sdk {
            #ip_hash;
            server ******* weight=1 max_fails=3 fail_timeout=10s;
            server ******* weight=1 max_fails=3 fail_timeout=10s;
            keepalive 1000;
    }
    
    server {
        listen 80 default backlog=1024;
        listen 443 ssl default backlog=1024;
 
七、php优化:(-1  表示没有使用系统的 backlog )    
    vim /usr/local/php/etc/php-fpm.conf
   
    listen.backlog = 2048   #每一个端口最大的监听队列的长度,需要配置nginx配置文件使用,如下(暂时只更改这里)
 
八、linux内核参进行优化:
    vim /etc/sysctl.conf
    
     net.ipv4.tcp_syncookies = 1
     net.ipv4.tcp_max_syn_backlog = 4096  #对于还未获得对方确认的连接请求,可保存在队列中的最大数目。如果服务器经常出现过载,可以尝试增加这个数字。
     net.core.somaxconn = 4096   #定义了系统中每一个端口最大的监听队列的长度,这是个全局的参数。backlog需要设置这个
 
    使用命令使之生效:sysctl -p

转自:https://blog.csdn.net/u010917150/article/details/95621873

性能分析之TCP全连接队列占满问题分析及优化过程(转载)的更多相关文章

  1. TCP全连接队列和半连接队列已满之后的连接建立过程抓包分析[转]

    最近项目需要做单机100万长连接与高并发的服务器,我们开发完服务器以后,通过自己搭的高速压测框架压测服务端的时候,发生了奇怪的现象,就是服务端莫名其妙的少接收了连接,造成了数据包的丢失,通过网上查资料 ...

  2. 五分钟带你读懂 TCP全连接队列(图文并茂)

    爱生活,爱编码,微信搜一搜[架构技术专栏]关注这个喜欢分享的地方. 本文 架构技术专栏 已收录,有各种视频.资料以及技术文章. 一.问题 今天有个小伙伴跑过来告诉我有个奇怪的问题需要协助下,问题确实也 ...

  3. [TimLinux] TCP全连接队列满

    0. TCP三次握手 该图来自:TCP SOCKET中backlog参数的用途是什么? syns queue: 半连接队列 accept queue: 全连接队列 控制参数存放在文件:/proc/sy ...

  4. TCP 半连接队列和全连接队列满了会发生什么?又该如何应对?

    前言 网上许多博客针对增大 TCP 半连接队列和全连接队列的方式如下: 增大 TCP 半连接队列的方式是增大 /proc/sys/net/ipv4/tcp_max_syn_backlog: 增大 TC ...

  5. TCP实战二(半连接队列、全连接队列)

    TCP实验一我们利用了tcpdump以及Wireshark对TCP三次握手.四次挥手.流量控制做了深入的分析,今天就让我们一同深入理解TCP三次握手中两个重要的结构:半连接队列.全连接队列. 参考文献 ...

  6. 三次握手 四次握手 原因分析 TCP 半连接队列 全连接队列

    小结 1. 三次握手的原因:保证双方收和发消息功能正常: [生活模型] "请问能听见吗""我能听见你的声音,你能听见我的声音吗" [原理]A先对B:你在么?我在 ...

  7. 【转】关于TCP 半连接队列和全连接队列

    摘要: # 关于TCP 半连接队列和全连接队列 > 最近碰到一个client端连接异常问题,然后定位分析并查阅各种资料文章,对TCP连接队列有个深入的理解 > > 查资料过程中发现没 ...

  8. 关于TCP 半连接队列和全连接队列

    关于TCP 半连接队列和全连接队列 http://jm.taobao.org/2017/05/25/525-1/ 发表于 2017-05-25   |   作者   蛰剑     |   分类于 网络 ...

  9. 【转载】socket 的 connect、listen、accept 和全连接队列、半连接队列的原理

    转自:http://blog.csdn.net/tennysonsky/article/details/45621341 写在前面: 1. accept 只是从全连接队列拿出一个已经建立好的socke ...

随机推荐

  1. Odoo13 新变化:存货核算

    Odoo13将于2019年10月发布,本次发布也包含了大量的改进,例如,对存货核算的重构. 去掉了 产品历史价格product.price.history ,增加了 stock valuation l ...

  2. elasticsearch数据库(ES)

    1. http://blog.csdn.net/cnweike/article/details/33736429 http://www.oschina.net/translate/elasticsea ...

  3. lua table 的操作(四)

    table在前面作过介绍,它是一种关联数组,这种关联指的是可以设置各类类型的key来存储值. 1.table 间的数据传递 -- 为 table a 并设置元素,然后将 a 赋值给 b,则 a 与 b ...

  4. linux学习笔记(1) -- 关于命令的一些操作

    Linux 目录 /:根目录,一般根目录下只存放目录,在Linux下有且只有一个根目录.所有的东西都是从这里开始.当你在终端里输入“/home”,你其实是在告诉电脑,先从/(根目录)开始,再进入到ho ...

  5. Linux(CentOS7)系统中部署Django web框架

    1. 概述 部署django和vue架在逻辑上可以分为web层与数据库层:web前端通过实现了WSGI协议的模块对python代码进行解析,而python代码中则通过特定于数据库的操作接口对数据库进行 ...

  6. 【leetcode】287. 寻找重复数

    题目链接:传送门 题目描述: 给定一个数组 nums 包含 n + 1 个整数,每个整数在 1 到 n 之间,包括 1 和 n.现在假设数组中存在一个重复的数字,找到该重复的数字. 注意 不能修改数组 ...

  7. 一、python快速入门(每个知识点后包含练习)

    1. 编程与编程语言 编程的目的是什么? #计算机的发明,是为了用机器取代/解放人力,而编程的目的则是将人类的思想流程按照某种能够被计算机识别的表达方式传递给计算机,从而达到让计算机能够像人脑/电脑一 ...

  8. POJ 1789 Prim

    给定N个字符串,某个字符串转为另一个字符串的花费为他们每一位不相同的字符数. 求最小花费Q. Input 多组输入,以0结束. 保证N不超过2000. Output 每组输出"The hig ...

  9. 安装 node.js npm,cnpm

    参考:https://blog.csdn.net/suiyuehuimou/article/details/74143436 https://www.liaoxuefeng.com/wiki/0014 ...

  10. 5. Java的注释,标识符、标识符的命名规范

      什么是标识符符? 凡是可以由自己命名的地方都称为修饰符. 例: 项目名 ,包名 ,类名 .方法名 2.   命名规范. ①    不可使用java关键字和保留字,但是可以包含关键字和保留字. ②  ...