大量假established

问题

上次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]}'  

netstat_look

服务器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连接情况
test_tcpcount.png

抓包情况
test_packetcapture.png

日志
test_log.png

服务器B
tcp连接情况
jtest_tcpcount.png

抓包情况
jtest_packetcapture.png

分析

先在wireshark中写入下面的过滤条件,看比较正常的http请求流程

tcp.stream eq 9  

tcp<em>stream</em>eq.png

tcp<em>stream</em>tu1.png

tcp<em>stream</em>eq_tu2.png

如上图
首先,26,30,31三个流水号,是典型的三次握手
接下来每一次发送报文,对面都会回复ack
ack的值是怎么确认的呢?通常来说

  1. 如果A给B单纯的只是发送ACK确认报文(就是报文中tcp flag只有ACK的标志位是1),并且假设ack=n,seq=m,那么B给A下次发送的报文中ack=m,seq=n
  2. 如果A给B发送的报文中,tcp flag中SYN或者FIN标志位为1,那么B给A下次发送的报文中ack=m+1, seq=n
  3. 如果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的包 盗图如下:
CapGtMss.png
CapLeMss.png

结论

结果很明显是因为下图中报文5238的ack丢失,导致client重新发送三次握手中的最后一次ack,后面server就不停地发送http回复报文的第一部分,客服端这时候已经傻眼了
result.PNG

上述的抓包下载

参考

  1. 理解TCP序列号(Sequence Number)和确认号(Acknowledgment Number)
  2. 简析TCP的三次握手与四次分手
  3. TCP之深入浅出send和recv
  4. Wireshark抓包TCP报长度比MSS大 && MSS 与MTU关系
  5. wireshark抓到tcp包大于mss的包
  6. 一站式学习Wireshark(四):网络性能排查之TCP重传与重复ACK
  7. 关于wireshark抓包的那点事儿