1. https://www.cnblogs.com/codelogs/p/16060820.html

简介#

这要从一次压测项目说起,那是我们公司的系统与另几家同行公司的系统做性能比拼,性能数据会直接影响项目中标,因此压力非常大。

当时甲方给大家提供了17台服务器供系统部署,并使用LoadRunner对系统进行压测,乙方有完全的服务器使用权,甲方派一个人负责压测并记录性能数据,要求压测QPS不低于4000。

项目开工#

接收到项目后,我们leader作为本项目的技术负责人,很快就为本项目指定了架构,web层使用nginx作为负载均衡,应用层使用weblogic服务器(理解为类似tomcat的东西即可)集群,数据库使用Oracle RAC集群,如下:

看看这系统架构也挺简单的,心里想,高并发系统不过如此嘛?

待系统代码按照上述架构部署完毕后,我们就开始了第一轮压测。

好家伙,我们一开始压测,就发现压测机上有大量的网络报错,类似于Connection timed outRead timed out之类的网络异常。

但奇怪的是,我们登录到nginx或weblogic上观察机器情况,cpu、memory使用都不高,查看应用日志,发现日志中似乎并没有收到多少请求!

整个组内满脸的疑惑,压测请求为什么没到应用系统中来?那请求去哪了?一番讨论过后,组内没人能回答这个问题,然后就都埋头去网上搜索答案去了。

可是,经过半天的研究,大家都没有找出问题所在,包括leader也没有,中途参照网上调整过一些nginx、jvm配置啥的,都没见起啥明显效果!

请求外援#

leader见问题一时半会解决不了,于是就从众包网站上找到了一些高手,虽然需要花点钱,但问题能快速解决还是值得的。

首先第一位高手远程进来了,操作了大概10分钟后就放弃了,看来钱不好挣。

然后第二位高手远程进来了,弄了个把小时,还重新编译了nginx,最终也放弃了,问题没有解决。

然后第三位高手远程进来了,只见他进来之后,就吩咐我们开始压测,然后就是他的show time了。

只见他不断地观察着一个命令的输出结果,命令如下:

  1. $ netstat -nat | awk '/tcp/{print $6}'|sort|uniq -c
  2. 16 CLOSE_WAIT
  3. 80 ESTABLISHED
  4. 6 FIN_WAIT2
  5. 11 LAST_ACK
  6. 8 LISTEN
  7. 22 SYN_RECV
  8. 400 TIME_WAIT

观察了一会后,他就叫我们停下来,他要调整一些内核参数了,调整参数如下:

  1. $ vi /etc/sysctl.conf
  2. net.ipv4.tcp_max_syn_backlog = 8192
  3. net.core.netdev_max_backlog = 8192
  4. net.core.somaxconn = 8192
  5. net.ipv4.ip_local_port_range = 1024 65000
  6. net.ipv4.tcp_syn_retries = 3
  7. net.ipv4.tcp_synack_retries = 3
  8. net.ipv4.tcp_retries1 = 3
  9. net.ipv4.tcp_retries2 = 5
  10. net.ipv4.tcp_fin_timeout = 30
  11. net.ipv4.tcp_tw_reuse = 1

调整完毕后,我们又开始压测,效果十分明显,虽然后端系统此时也有很多报错,但至少请求都进来了!

至此,本次压测的核心技术难题已经解决,虽然后面也调整过一些jvm参数与代码,但都在我们自己的掌控范围内,这些就不再提了。

但我当时根本就看懵了,不知道大佬弄了些啥?

半连接队列与全连接队列#

经过一段时间的网上搜索,我发现了新的知识点,就是TCP建立连接的过程里面,有两个队列,分别是半连接队列与全连接队列,如下:

  1. 服务端要提供服务,会调用bind()listen()函数,创建一个LISTEN状态的Socket。
  2. 当客户端建立连接时,会发SYN包,即三次握手的第一步,服务端收到后,会创建一个SYN_RECV状态的Socket,放入半连接队列,并且回复SYN+ACK(即三次握手第二步)。
  3. 当客户端收到SYN+ACK后,将自己的Socket状态变更为ESTABLISHED,并给服务端回复ACK。
  4. 服务端收到ACK后,将对应半连接队列里的Socket移动到全连接队列,并将其状态变更为ESTABLISHED。
  5. 服务端accept()轮询线程这时会从全连接队列里面取到新的Socket,然后就可以使用此Socket与客户端交换数据了。

可以看到,在服务端建立连接的过程中,会经历半连接队列与全连接队列,如果半连接队列或全连接队列满了,会怎么样呢?

  1. 如果半连接队列满了,会丢弃客户端发来的SYN,而客户端如果发了几次SYN都没有收到SYN+ACK,会报Connection timed out异常,这解释了我们压测机上的大量报错。
  2. 如果全连接队列满了,会丢弃客户端回复的ACK与数据包,因为此时客户端是ESTABLISHED(连接已建立)状态,然后客户端会发具体的请求数据包,然后请求数据包一直被丢弃,会导致客户端报Software caused connection abort :socket write errorRead timed out
  3. 另外,全连接队列满了,也会导致丢弃第一次握手的SYN,这也会导致Connection timed out异常。

可见,半连接队列与全连接队列的大小非常重要,而内核默认配置都是128,这太小了!如下:

  1. $ vi /etc/sysctl.conf
  2. # 这是半连接队列的大小
  3. net.ipv4.tcp_max_syn_backlog = 8192
  4. # 这是全连接队列的大小
  5. net.core.somaxconn = 8192
  6. # 使得配置修改生效
  7. $ sysctl -p
  8. # 查看当前配置
  9. $ sysctl -a

另外,Socket网络编程一般也可以指定一个backlog参数,如java中的ServerSocket:

  1. int port=8080;
  2. int backlog=8192;
  3. ServerSocket ss = new ServerSocket(port, backlog);
  4. while(true){
  5. //接收到新连接
  6. Socket s = ss.accept();
  7. new Thread(()->{
  8. //socket读写操作...
  9. }, "socket-thread-" + s).start();
  10. }

这个backlog参数,就是配置全连接队列的大小的,但全连接队列大小实际由内核与应用程序同时决定,取net.core.somaxconn与应用中backlog的最小值。

所以,一般网络服务程序(tomcat、redis、mysql等)都会有一个backlog的配置,在springboot内置的tomcat中,backlog配置方法如下:

  1. server:
  2. port: 8080
  3. tomcat:
  4. accept-count: 8192 # 内置tomcat的全连接队列大小配置

观测TCP连接队列#

如上,大佬当时诊断问题时,主要使用的是netstat命令,用于统计各状态Socket的数量,如下:

  1. $ netstat -nat | awk '/tcp/{print $6}'|sort|uniq -c
  2. 16 CLOSE_WAIT
  3. 80 ESTABLISHED
  4. 6 FIN_WAIT2
  5. 11 LAST_ACK
  6. 8 LISTEN
  7. 22 SYN_RECV
  8. 400 TIME_WAIT

从这里可以看到,ESTABLISHED状态的Socket数量较少,大佬估计是觉得压测时正常连接不应该这么少,然后就认定是TCP连接队列大小配置有问题,导致压测QPS上不去。

当然,这是非常依赖经验的,因为这个数据并不直观,估计大佬有多年的性能优化经验,才能感觉到这个连接数是不正常的。

经过我多方搜索,我发现有一些方法,可以直接观测到连接队列的使用情况。

  1. 观测连接队列长度
  1. # ss命令对于LISTEN状态的Socket
  2. # Recv-Q是全连接队列的当前大小
  3. # Send-Q是全连接队列的最大值
  4. $ ss -nltp
  5. State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
  6. LISTEN 0 10 0.0.0.0:8080 0.0.0.0:* users:(("ncat",pid=25760,fd=3))
  7. # netstat命令对于LISTEN状态的Socket
  8. # Recv-Q是半连接队列的当前大小
  9. # Send-Q一般显示为0
  10. $ netstat -nltp
  11. Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
  12. tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 25760/ncat

可见,通过ss或netstat命令,可以直接观测到TCP连接队列的大小,另外,也有办法观测到由于队列溢出而丢弃包的次数,如下:

  1. 观测连接队列溢出而丢弃包的次数
  1. $ netstat -s|grep -iE LISTEN
  2. 52 times the listen queue of a socket overflowed # backlog满了,溢出的次数
  3. 52 SYNs to LISTEN sockets ignored # SYN包drop的次数

从上可以看到,SYN被drop了52次,可能是连接队列设置过小导致。

ss其它用法#

一般做过网络编程的话,应该了解过,每个socket都可以设置recv buffer(读缓冲)和send buffer(写缓冲),而对于ESTABLISHED状态的socket,ss命令可以观测到这两个buffer的使用情况,如下:

  • Recv-Q:表示的是recv buffer中用户进程未来得及read()走的数据大小。
  • Send-Q:表示的是write()到send buffer中远程主机还未返回ACK确认包的数据大小。

一般来说,这两个buffer内核可自动调整大小,并不需要我们手动配置,另外,使用netstat还可以观测到这两个buffer不足导致的丢包情况,如下:

  1. $ netstat -s|grep -E 'pruned|collapsed'
  2. 19963 packets pruned from receive queue because of socket buffer overrun
  3. 665 packets collapsed in receive queue due to low socket buffer
  • collapsed:由于tcp包的header部分内容有很多是一样的,Linux为了节省Tcp header所占用的内存,使相同header的tcp包复用同一个header以节省内存,当socket的recv buffer不足时,会触发这个行为。
  • pruned:是指内核在collapsed时的尝试后,仍未有足够空间接收包则此时直接丢包。

总结#

这次压测给我的印象很深,发现了在Java的世界外面,还有很多的底层机制运行着,它们一般不出问题,但一旦出现问题,将是非常难以定位的问题,这也促使我后面花了大量时间去补Linux和网络相关的知识点。

另外,其实TCP连接队列相关知识点,在运维圈子里会分享的更多,因为他们经常部署系统、部署软件嘛,时间久了自然会了解到,这也促使我后面越来越多的关注到运维与DBA层面的知识点。

往期内容#

Linux命令拾遗-入门篇
原来awk真是神器啊
Linux文本命令技巧(上)
Linux文本命令技巧(下)
字符编码解惑

[转帖]神秘的backlog参数与TCP连接队列的更多相关文章

  1. 设置TCP_USER_TIMEOUT参数来判断tcp连接是否断开

    [TOC] 1. bug描述 前段时间遇到这样的一个问题,openstack一个控制节点宕机后,在宕机后一段时间内创建的虚拟机,一直卡在创建中的状态.有的甚至要等到16分钟之后虚拟机才会切换到下一个状 ...

  2. 曹工说Redis源码(4)-- 通过redis server源码来理解 listen 函数中的 backlog 参数

    文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读.由于 ...

  3. TCP/IP协议中backlog参数

    TCP建立连接是要进行三次握手,但是否完成三次握手后,服务器就处理(accept)呢? backlog其实是一个连接队列,在Linux内核2.2之前,backlog大小包括半连接状态和全连接状态两种队 ...

  4. TCP之三:TCP/IP协议中backlog参数(队列参数)

    目录: <TCP洪水攻击(SYN Flood)的诊断和处理> <TCP/IP协议中backlog参数> TCP建立连接是要进行三次握手,但是否完成三次握手后,服务器就处理(ac ...

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

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

  6. 性能分析之TCP全连接队列占满问题分析及优化过程(转载)

    前言 在对一个挡板系统进行测试时,遇到一个由于TCP全连接队列被占满而影响系统性能的问题,这里记录下如何进行分析及解决的. 理解下TCP建立连接过程与队列 从图中明显可以看出建立 TCP 连接的时候, ...

  7. 我为 Netty 贡献源码 | 且看 Netty 如何应对 TCP 连接的正常关闭,异常关闭,半关闭场景

    欢迎关注公众号:bin的技术小屋,本文图片加载不出来的话可查看公众号原文 本系列Netty源码解析文章基于 4.1.56.Final版本 写在前面..... 本文是笔者肉眼盯 Bug 系列的第三弹,前 ...

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

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

  9. Linux网络编程二、tcp连接API

    一.服务端 1.创建套接字: int socket(int domain, int type, int protocol); domain:指定协议族,通常选用AF_INET. type:指定sock ...

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

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

随机推荐

  1. 【DevCloud · 敏捷智库】暴走在发布前夜的开发,你怕不怕?

    摘要:每个月都有2天开发团队要通宵熬夜,大家苦不堪言.有个别的开发同学,骂完公司骂同事,骂完同事骂客户的,甚至连自己都不放过-- 来自一个CEO的叙述 在一次企业交流会上,一个公司的CEO提道,&qu ...

  2. 讲透学烂二叉树(五):分支平衡—AVL树与红黑树伸展树自平衡

    简叙二叉树 二叉树的最大优点的就是查找效率高,在二叉排序树中查找一个结点的平均时间复杂度是O(log₂N): 在<讲透学烂二叉树(二):树与二叉/搜索/平衡等树的概念与特征>提到 二叉排序 ...

  3. 用火山引擎DataTester,这家企业开始了“数据驱动增长”

    年末购物季已至,近些年来,预售抵扣.平台满减.品类专享券.大额补贴--动辄四五种计算方法叠加的大促活动,让不少消费者"懵"感十足.同一样商品,到底谁家卖的最便宜?比价平台应声发展而 ...

  4. Python 将省、市 json 替换 成拼音

    1.将 city_code_cn.json 中的省.市.区,翻译成英文,或直接替换去掉省.市 如:苏州市  -> 苏州 转成拼音后就变成 Suzhou,否则就会转成 Suzhoushi 怪怪的 ...

  5. Codeforces Round 908 (Div. 2)

    总结 T1 题目大意: A,B两人玩游戏,游戏规则如下: 整场游戏有多轮,每轮游戏先胜 \(X\) 局的人获胜,每场游戏先胜 \(Y\) 局的人获胜. 你在场边观看了比赛,但是你忘记了 \(x\) 和 ...

  6. 愉快的了解Charles

    charles是PC端常用的网络封包截取工具,在做移动开发时,我们为了调试与服务器端的网络通讯协议,常常需要截取网络封包来分析.除了在做移动开发中调式端口外,charles也可以用于分析第三方应用的通 ...

  7. Windows 端使用 C++ 服务操作类

    #pragma once #include <windows.h> #include <string> // #include <iostream> class S ...

  8. Socket | 大小端问题和网络字节序转换函数

    不同 CPU 中,4 字节整数 1 在内存空间的存储方式是不同的.4 字节整数 1 可用 2 进制表示如下: 00000000 00000000 00000000 00000001 有些 CPU 以上 ...

  9. signed main 和 int main 的区别

    事实上只是因为有人直接 #define int long long 了...然后int main改成signed main就行了 #define int long long ... signed ma ...

  10. AtCoder Beginner Contest 208 A~E个人题解

    比赛链接:Here A - Rolling Dice 水题 一个六面的骰子,请问摇动 \(A\) 次最后的点数和能否为 \(B\) 如果 \(B \in [a,6a]\) 输出 YES C++ voi ...