问题
上次tomcat并发一直有个问题没有解决,就是直播中途有一段时间,广东这边部分地区不能访问. 而从日志上来看,那个时间段是有用户授权访问的. 而且负载均衡各项硬件资源都是正常的(CPU、内存、网络连接均占用不到20%,未达到瓶颈). 虽然有怀疑是dns解析问题,但是复现不出来了.
活动过后测试
测试复现中出现大量错误 READ ECONNRESET、CONNECT ETIMEOUT
下载了原生的tomcat,只访问index.html,也出现同样的问题
准备过程
服务器A 运行脚本,发送http请求到服务器B上的8081端口 ip地址为112.74.128.91
服务器B 上跑着tomcat,监听8081端口 ip地址为120.24.180.89
两个ip地址都是公网地址
服务器A
第一
为了只对出问题的报文进行研究,我在发送http报文的url上添加一些参数.
//main.js
"use strict"
let http = require('http')
let m = 0;
let n = 0;
for(let i = 0;i < 4000;i++) {
http.get('http://112.74.128.91:8081/?id='+ i, (data) => {
m++;
console.log(data.statusCode + "\t" + i + "\t" + m);
}).on('error', (e) => {
n++;
console.log("error " + e.message + "\t" + i + "\t" + n);
});
}
node ./main.js > 1.log &
tail -f 1.log
第二
同时使用tcpdump监听报文
tcpdump -i eth1 host 112.74.128.91 -w 01.12.15.11.client.cap
第三
在发包过程中,使用如下命令可以随时查看tcp连接情况
netstat -an | grep "112.74.128.91:8081" | awk '/^tcp/ {++y[$NF]} END {for(w in y) print w, y[w]}'
服务器B
第一
启动服务器
export JRE_HOME=/root/jdk1.7.0/jre
export JAVA_HOME=/root/jdk1.7.0/
./startup.sh
第二
抓包
tcpdump -i eth1 port 8081 -w 01.12.15.11.server.cap
第三
查看连接数
netstat -an | grep "112.74.128.91:8081" | awk '/^tcp/ {++y[$NF]} END {for(w in y) print w, y[w]}'
tcp连接状态趋于稳定
服务器A
tcp连接情况
抓包情况
日志
服务器B
tcp连接情况
抓包情况
分析
先在wireshark中写入下面的过滤条件,看比较正常的http请求流程
tcp.stream eq 9
如上图
首先,26,30,31三个流水号,是典型的三次握手
接下来每一次发送报文,对面都会回复ack
ack的值是怎么确认的呢?通常来说
- 如果A给B单纯的只是发送ACK确认报文(就是报文中tcp flag只有ACK的标志位是1),并且假设ack=n,seq=m,那么B给A下次发送的报文中ack=m,seq=n
- 如果A给B发送的报文中,tcp flag中SYN或者FIN标志位为1,那么B给A下次发送的报文中ack=m+1, seq=n
- 如果tcp flag中PSH标志位为1,那么B给A下次的报文中ack=m+TCP_LEN,seq=n
注意4910是4708的延续,tcp报文分片,http报文太大了,被分成两个部分
那什么时候tcp的数据会被分片呢?
MSS是最大报文段长度的缩写,是TCP协议里面的一个概念。
MTU是网络传输最大报文包。
我们看到,如果不考虑尽量避免IP分片所带来的不好的话,MSS跟MTU是没有什么关系的。MTU是传输能力,就像是“这条马路最大能够曾受5t的压力”。MSS是通信终端的应用数据处理能力,就像是“我这个工作间最大能够加工100t的雕像”。
MSS为1460是由1500-20(IP头)-20(TCP头)计算出的。
实际场景下,TCP包头中会带有12字节的选项----时间戳。 这样,单个TCP包实际传输的最大量就缩减为1448字节。1448=1500-20(IP头)-32(20字节TCP头和12字节TCP选项时间戳)
紧接着,新的问题又出现了,可以看到4908序号的报文大小是4410字节,但是两端tcp协商的MSS是1460字节,怎么会超过1460字节大小
得看这篇文章wireshark抓到tcp包大于mss的包
盗图如下:
结论
结果很明显是因为下图中报文5238的ack丢失,导致client重新发送三次握手中的最后一次ack,后面server就不停地发送http回复报文的第一部分,客服端这时候已经傻眼了