简介这要从一次压测项目说起,那是我们公司的系统与另几家同行公司的系统做性能比拼,性能数据会直接影响项目中标,因此压力非常大。 当时甲方给大家提供了17台服务器供系统部署,并使用LoadRunner对系统进行压测,乙方有完全的服务器使用权,甲方派一个人负责压测并记录性能数据,要求压测QPS不低于4000。 项目开工接收到项目后,我们leader作为本项目的技术负责人,很快就为本项目指定了架构,web层使用nginx作为负载均衡,应用层使用weblogic服务器(理解为类似tomcat的东西即可)集群,数据库使用Oracle RAC集群,如下: 看看这系统架构也挺简单的,心里想,高并发系统不过如此嘛? 待系统代码按照上述架构部署完毕后,我们就开始了第一轮压测。 好家伙,我们一开始压测,就发现压测机上有大量的网络报错,类似于Connection timed out、Read timed out之类的网络异常。 但奇怪的是,我们登录到nginx或weblogic上观察机器情况,cpu、memory使用都不高,查看应用日志,发现日志中似乎并没有收到多少请求!
可是,经过半天的研究,大家都没有找出问题所在,包括leader也没有,中途参照网上调整过一些nginx、jvm配置啥的,都没见起啥明显效果! 请求外援leader见问题一时半会解决不了,于是就从众包网站上找到了一些高手,虽然需要花点钱,但问题能快速解决还是值得的。 首先第一位高手远程进来了,操作了大概10分钟后就放弃了,看来钱不好挣。
$ netstat -nat | awk '/tcp/{print $6}'|sort|uniq -c 16 CLOSE_WAIT 80 ESTABLISHED 6 FIN_WAIT2 11 LAST_ACK 8 LISTEN 22 SYN_RECV 400 TIME_WAIT复制代码 观察了一会后,他就叫我们停下来,他要调整一些内核参数了,调整参数如下:
调整完毕后,我们又开始压测,效果十分明显,虽然后端系统此时也有很多报错,但至少请求都进来了! 至此,本次压测的核心技术难题已经解决,虽然后面也调整过一些jvm参数与代码,但都在我们自己的掌控范围内,这些就不再提了。 但我当时根本就看懵了,不知道大佬弄了些啥? 半连接队列与全连接队列经过一段时间的网上搜索,我发现了新的知识点,就是TCP建立连接的过程里面,有两个队列,分别是半连接队列与全连接队列,如下:
可以看到,在服务端建立连接的过程中,会经历半连接队列与全连接队列,如果半连接队列或全连接队列满了,会怎么样呢?
可见,半连接队列与全连接队列的大小非常重要,而内核默认配置都是128,这太小了!如下: $ vi /etc/sysctl.conf# 这是半连接队列的大小net.ipv4.tcp_max_syn_backlog = 8192# 这是全连接队列的大小net.core.somaxconn = 8192# 使得配置修改生效$ sysctl -p# 查看当前配置$ sysctl -a复制代码 另外,Socket网络编程一般也可以指定一个backlog参数,如java中的ServerSocket:
这个backlog参数,就是配置全连接队列的大小的,但全连接队列大小实际由内核与应用程序同时决定,取net.core.somaxconn与应用中backlog的最小值。 所以,一般网络服务程序(tomcat、redis、mysql等)都会有一个backlog的配置,在springboot内置的tomcat中,backlog配置方法如下: server: port: 8080 tomcat: accept-count: 8192 # 内置tomcat的全连接队列大小配置复制代码 观测TCP连接队列如上,大佬当时诊断问题时,主要使用的是netstat命令,用于统计各状态Socket的数量,如下:
从这里可以看到,ESTABLISHED状态的Socket数量较少,大佬估计是觉得压测时正常连接不应该这么少,然后就认定是TCP连接队列大小配置有问题,导致压测QPS上不去。 当然,这是非常依赖经验的,因为这个数据并不直观,估计大佬有多年的性能优化经验,才能感觉到这个连接数是不正常的。 经过我多方搜索,我发现有一些方法,可以直接观测到连接队列的使用情况。
# ss命令对于LISTEN状态的Socket# Recv-Q是全连接队列的当前大小# Send-Q是全连接队列的最大值$ ss -nltpState Recv-Q Send-Q Local Address:Port Peer Address:Port ProcessLISTEN 0 10 0.0.0.0:8080 0.0.0.0:* users:(('ncat',pid=25760,fd=3))# netstat命令对于LISTEN状态的Socket# Recv-Q是半连接队列的当前大小# Send-Q一般显示为0$ netstat -nltpProto Recv-Q Send-Q Local Address Foreign Address State PID/Program nametcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 25760/ncat复制代码 可见,通过ss或netstat命令,可以直接观测到TCP连接队列的大小,另外,也有办法观测到由于队列溢出而丢弃包的次数,如下:
从上可以看到,SYN被drop了52次,可能是连接队列设置过小导致。 ss其它用法一般做过网络编程的话,应该了解过,每个socket都可以设置recv buffer(读缓冲)和send buffer(写缓冲),而对于ESTABLISHED状态的socket,ss命令可以观测到这两个buffer的使用情况,如下:
一般来说,这两个buffer内核可自动调整大小,并不需要我们手动配置,另外,使用netstat还可以观测到这两个buffer不足导致的丢包情况,如下: $ netstat -s|grep -E 'pruned|collapsed' 19963 packets pruned from receive queue because of socket buffer overrun 665 packets collapsed in receive queue due to low socket buffer复制代码
总结这次压测给我的印象很深,发现了在Java的世界外面,还有很多的底层机制运行着,它们一般不出问题,但一旦出现问题,将是非常难以定位的问题,这也促使我后面花了大量时间去补Linux和网络相关的知识点。 另外,其实TCP连接队列相关知识点,在运维圈子里会分享的更多,因为他们经常部署系统、部署软件嘛,时间久了自然会了解到,这也促使我后面越来越多的关注到运维与DBA层面的知识点。 |
|