性能常识 数据为什么会走丢了呢?

CKL的思考 · 2022年03月14日 · 最后由 huangshengnan 回复于 2022年03月16日 · 3349 次阅读

这篇文章的内容写于 2016 年左右,最近在整理材料时翻出来,还是能感觉到当时对于性能测试的热爱,现在都好久没做性能测试了。来看看当年的个人是如何定位问题的吧,也许对于现在做性能测试的同学能带来一些启示。

01 背景介绍

当时接手了一个内部 IM 系统的性能测试,以 Netty 为底层框架,做二次开发,研发属于自己的私有协议,用于内部通信系统的使用。当时处于系统架构验证阶段,还没有业务实现(谁说性能一定要在业务稳定后再测试的?)。在测试一个推送协议时,发现压力机上传输出的数据假如有 10W 条,那么最终入库的时候只有 6W 条左右的数据。中间过程没有发生任何错误。由于这是一个不需要返回的过程(只负责推送,不确认服务器是否收到,不要问为什么,问就是开发这么设计的),所以无法做检查点。那么这些丢失的数据去哪了?

02 排察过程

既然是服务端的数据少了。那么看下服务端收到了多少条数据吧,通过统计服务器日志中的信息,确认服务端确实只接收到了 6W 多的数据:

这里有 12W,是因为这条信息会打印两次,所以实现收的只有 6W 多。既然系统收到的是这么多,是不是系统程序弄丢的?但是不能直接和开发说你们的程序有问题吧,Netty 的框架还是比较成熟的,不太可能会有这样的问题。首先先确认下压力机是否真实发送了这么多的数据,对压力机的网卡做了如下监控:

可以看出,通过压力机网卡的包数量与 LR(当年 Jmeter 还不流行)上显示的数据基本一致(有误差是因为两边启动不同步,且还有其它因素的干扰,但可以忽略)。也就证明了发送端是没有问题的。

和开发沟通,他们还是感觉不是程序的问题,还是那句话,Netty 的框架还是比较成熟的,百度了一下,网上也没有相关的 BUG 说明。那么就继续找问题吧。
数据会不会在网络传输上丢失了?因为是在局域网内,好像也不会存在这么严重的丢包率。为了验证问题,我们监控服务器的网卡,看看是否达到服务器的数据是否准确:

可以看出,到达服务器网卡的数量是对的。数据仿佛真的是应用程序弄丢的。经过反复的排察,发现数据总在接近 7W 的时候开始出现这种情况,之前都是正常的。是哪里有这个限制呢?

03 深入思考

从最开始的脚本开发入手。一个一个环节的分析。首先是性能测试脚本的排查。使用的是 LR 的 Socket 协议,使用长连接,来发送报文,完成后关闭连接。这个看起来也是没错的。在测试其它协议的时候也是正常的(比用户认证,点对点消息推送等,没出现过类似的问题。虽然由于性能问题,TPS 不是很乐观,但是数据是不会丢的)。是 Netty 框架的问题么?好像也不是,否则网上应该能找到相关的资料,总不可能我们是第一次遇到的吧?

于是把这个推送的功能单独拿出来分析。经过一翻讨论和对比,发现推送这个功能最特殊的地方就是它不需要返回报文来确认结果,其它功能都需要返回一个 ACK 包来确认其结果,会不会是这个地方有我们没注意的东西呢?

04 寻找资料

于是我们开始各种找资料,学习 TCP/IP 协议以及与之相关的网卡的工作原理。因为都是较为底层的东西 。最终确认了问题的所在。TCP 对于流量的控制机制导致的问题。相关资料如下:

TCP 中采用滑动窗口来进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。

TCP 连接阶段,双方协商窗口尺寸,同时接收方预留数据缓存区;发送方根据协商的结果,发送符合窗口尺寸的数据字节流,并等待对方的确认;发送方根据确认信息,改变窗口的尺寸,增加或者减少发送未得到确认的字节流中的字节数。调整过程包括:如果出现发送拥塞,发送窗口缩小为原来的一半,同时将超时重传的时间间隔扩大一倍。

为什么要有滑动窗口?在英特网中,可能同时存在着数百万条 TCP 连接。如果这些连接同时无节制的发送数据包,那么整个网络都会被堵死,没有数据包能到达目的地。

因此 TCP 需要根据网络状况,每次发送若干数据包。

简单来说,网卡在控制流量的时候,会有一个缓存机制,当接收的量大于传送给应用程的量时,网卡会把数据缓冲在一个类似于缓冲区的地方,利用滑动窗口机制来控制传输。当连接断开后,由于物理链路的丢失,这部分 “缓冲” 数据也会跟着消失。在上层应用其实这种方法很常见,比如各类中间件的队列,本质上是一样的。

05 验证并解决

明白了网卡的工作原理后,就可以解释上面的现象了。由于我们大量发送数据,服务器的网卡在接收这些数据后,往应用层传送数据时,启动了滑动窗口机制,对一部分数据做了 “缓冲”。这些数据是全部达到网卡这一层的,所以我们的监控也没有问题,因为数据确实是到达了网卡。但是由于这个功能不需要确认机制,客户端发送完数据后,就关闭了连接。导致网卡 “缓冲区” 里的数据不知道往哪送了。所以数据就丢了,因为数据也没有到达应用层,所以应用层日志时也没有错误。

在修改过内核参数后(调整滑动窗口的大小),经验证,数据丢失的量会随着参数的变化而变化。证明了我们的猜想是正确的。

解决办法:客户端发送完消息后,脚本不马上结束,保持一段时间的链接,让服务端网卡里缓冲区的数据 “知道” 往哪走,问题就消失了。同时告诉开发这个机制,在后续的业务开发中,需要特别注意这种场景。

06 小结

在这个问题的处理上可以看出,性能测试涉及到方方面面,不但要懂软件,还要懂硬件。在解决了这个问题之后,我们就能更清楚的知道网络是怎么处理等待的,为什么带宽满了后响应时间会变长。对这些问题有了更深的了解。

另:为什么其它功能不会出现此问题。是因为其它的情况下,我们需要一个 ACK 的返回包来确认我们的结果。在没有得到结果之前,连接是会一直保持着的。所以在测试 Socket 之上的协议时,基本不会出现此问题,但是越底层的协议越需要注意这些细节。同时这个问题也可以解释为什么在同样的压力下,网络的好坏也会影响响应时间(网络较差时,数据在 “缓冲区” 时的时间会越长,容易超时,导致数连断开,客户端产生超时的现象)。

PS:性能测试其实是个很锻炼个人思维的测试活动,需要有足够的知识量和分析问题的能力,很庆幸能在工作前期就有机会锻炼自己的逻辑思维,感谢天胜和 Mike。
做性能,如果你只关心如何使用工具,很大概率会跑偏了。

原文链接:https://mp.weixin.qq.com/s/Aosgl0IY79dyMGJboYTA2A

共收到 12 条回复 时间 点赞

找到了两篇相关文章:

没想到协议栈底层原来会这样处理数据,send函数只是将数据写到了内核缓冲区,所以send函数的返回值不代表对方已经收到多少数据,挺有意思。

当时处于系统架构验证阶段,还没有业务实现(谁说性能一定要在业务稳定后再测试的?)

确认这个阶段可以做性能测试吗?

Thirty-Thirty 回复

当然啦,对于一些技术选型,基础底层框架的性能验证,和业务无关。

很不错的文章,先增加一些场景细化复现场景,然后查资料了解黑盒子背后的原理,提出假设 + 验证确认,点赞!

话说,这种为了高性能服务,并且发了后不用管的,用 UDP 应该比 TCP 更合适,当时开发为啥不考虑直接用 UDP ,这个有和开发探讨过么?

CKL的思考 回复

业务实现后,是不是还需要做整体性能测试?两次测试结果会一致吗?应该以哪次结果为准?

学习了!
当发生 TCP 堵塞时,数据会先存在接收方的接收缓存区中,这时如果 TCP 连接断开竟然会导致缓存区数据丢失。
至于数据丢失的原因,应该是 TCP 连接断开时,接收方的 socket 套接字(存放 TCP4 元组、程序信息)也会被注销,导致缓存数据不知道该被发往哪个程序。

1、性能测试中常见的 IO 问题。性能测试过程中,除了常见的 CPU、Memory 资源占用,如果加入 IO、带宽的观察,这类问题的发现就相对容易得多。
2、很多初学者以为性能测试就是拿工具发送压力(跟一部分人理解的 “API 测试就是发送请求” 一样),其实事前有很多知识需要了解和准备, 例如端口范围、文件/TCP 链接最大限制、TCP 超时时间、存活间隔... 当然这也是系统/网络调优的一部分
3、性能测试什么时候开始做,跟测试的对象和目的有关。可以只对单个协议进行测试,可以只对单个中间件进行测试,可以只对某段代码进行测试,当然也可以对业务场景进行测试

陈恒捷 回复

😅 当时没想到过,还是懂的少

Thirty-Thirty 回复

还是需要的啊,随着业务代码的加入,性能肯定会有一些损耗,以最终业务场景下的性能测试为准。框架级的性能测试主要是验证框架本身的性能够用,否则到业务代码引入后,发现是框架的性能问题,改起来就很麻烦了。

CKL的思考 回复

多谢释疑, 😃

厉害了,无论如何我都不会想到问题出在这。😀

测试对应的性能测试感觉和开发对应的后端开发一样, 都是俩四字的名字. 都代表了较低的门槛和无限高的上限. 名字表达的意思太范围笼统了.

CKL的思考 构建性能测试知识体系 中提及了此贴 05月05日 14:49
需要 登录 後方可回應,如果你還沒有帳號按這裡 注册