15 | Nginx的499状态码是怎么回事?

你好,我是胜辉。

“实战一:TCP真实案例解密篇”刚刚结束。在过去的十几讲里,我们全面回顾了TCP的各种技术细节,从握手到挥手,从重传等容错机制,到传输速度等效率机制,应该说也是对我们的TCP知识做了一个全面的“体检”。如果你发现自己对TCP的掌握还有不少漏洞,也别着急,可以回头复习一下相应部分的内容,或者在留言区提问,我会给你解答。

从这节课开始,我们要进入网络排查的“实战二:应用层真实案例解密篇”了。今天要给你讲解的是一个关于Nginx的排查案例。

Nginx的499状态码是怎么回事?

你肯定听说过Nginx,或者经常用到它。作为一个高性能的HTTP和反向代理服务器,Nginx不管是用来搭建Web Server,还是用作负载均衡都很合适,并且它可供配置的日志字段也很丰富,从各类HTTP头部到内部的性能数据都有。

不过,你在日常维护Nginx时有没有遇到过这种情况:在Nginx的访问日志中,存在499状态码的日志。但是常见的4xx家族的状态码只有400、401、403、404等,这个499并未在HTTP的RFC文档中定义,是不是很奇怪?

这个499错误日志,在流量较大的场景下,特别是面向Internet的Web站点场景下还是很常见的 。但如果你遇到过,第一感觉可能会是一头雾水,不知道499这个状态码具体是用来干啥的,因为确实跟其他的400系列状态码太不同了。

我在公有云的时候,做过的一个案例正好是关于Nginx的499日志。当时一位客户向我反馈:他们的Nginx服务器会连续几天记录较多的499错误日志,之后几天可能趋零,然后再回升,整体状况起伏不定。

这个客户经营的是To C的电子产品,跟手机端App协同工作。这个App会定时把消息上传到微信消息网关,后者再把这些消息推送到该客户的服务端(在公有云上)做业务处理,整体的消息量约每日三十万条。那么,对消息网关来说,这个服务端就是一个Web回调接口。下面是架构简图:

他们给我提供了499日志趋势图:

图片

由于大量499日志的存在,客户非常担心业务已经受到影响,比如他们的终端消费者是否经常上传数据失败?是否已经严重影响了消费者的体验?所以,我们需要搞清楚499错误日志的含义。

那么,499这个状态码本身能帮到我们什么呢?我们可以查一下它在Nginx里的官方定义

NGX_HTTP_CLIENT_CLOSED_REQUEST | 499

可是,什么叫client closed request(客户端关闭了请求)呢?好像说了跟没说也没太大区别。我们知道499是客户端关闭请求引起的,那又是什么原因,引起了“客户端关闭了请求”呢?关于这个问题,Nginx的文档并没有提及。

有一句话叫做“解决问题的办法,可能不在问题自身所处的这个层面”。应用层日志,其实记录的依然是表象。更深层次的原因,很可能在更底层,比如在传输层或者网络层。

所以,搞清楚499这个状态码是什么意思,对于我们来说,不仅是理解这个499码的底层含义,而且通过这种排查,我们还能掌握一套对HTTP返回码进行网络分析的方法。这种方法,对于维护好Nginx以及其他Web服务,都是很有帮助的。

那么接下来,我们就根据这个案例,一起探讨下如何用抓包分析,来拆解HTTP返回码的真正含义。

锚定到网络层

如前面所说,我是选择用抓包分析这个方法来展开排查的。之所以采用这个方法,是因为我前面也说过,从软件文档已经无法查清楚问题根因了,所以需要下沉到网络层排查。如果你在处理应用层故障,比如HTTP异常返回码(4xx和5xx系列)场景中,也遇到了在应用层找不到答案的情况,你就可以考虑采用抓包分析的方法。

补充:下文中的“客户端”都指微信消息网关,“服务端”指这个客户在公有云的服务器。

这样,我在服务端使用tcpdump工具做了抓包,然后用Wireshark打开抓包文件展开分析。从抓包文件中,我一般会寻找一些比较可疑的报文。正好,这次抓包里有不少RST报文,于是我过滤出了一个典型的带RST报文的TCP流,请看下图:

补充:抓包示例文件已经上传至Gitee,建议用Wireshark打开文件,结合文稿一起学习。

相信你也一眼就看到了那个结尾处的RST。但问题是,这个TCP流一定跟499日志有关系吗?

得益于TCP/IP的精妙的分层设计,应用层只需要通过系统调用,就可以像使用文件IO那样使用网络IO,具体的网络细节都由内核处理了。可是由此也带来了一个问题:以应用层的视角,是无法“看到”具体的网络报文的

我们需要根据一些关键信息来确定应用层日志跟网络报文的对应关系。比如在这里,我可以确认上面这个带有RST的TCP流,就是日志中记录的一条499日志记录。这是如何做到的呢?

就是因为以下三点。

  • 客户端IP:日志中的remote IP跟抓包文件里面的IP符合。
  • 时间戳:日志的时间戳也跟这个TCP流的时间吻合。
  • 应用层请求:日志里的HTTP URL路径和这个TCP流里的URL相同。

补充:如果你对第4讲有印象,应该记得当时也是用类似的方式找到了应用日志跟报文的对应关系。

实际上,在真实的抓包分析场景中,“如何把应用层问题跟网络层抓包关联起来”,始终是一个关键环节。同时,这也是比较令人困扰的关键技术障碍,很多人就是在这一关前败下阵来,导致没有办法真正彻底地查到根因。所以这里的方法可以作为给你的参考,当你以后再处理这种关键环节的时候,也可以根据上面提到的三个维度的信息,即IP、时间戳、应用层请求(包括URL和header),来达到“把应用层问题锚定到网络层数据包”的目的。

好,既然确定这个流就是代表了一次499事件,那么我们就需要好好分析一下这些报文里面的文章了。

TCP流的解读

这里,你可以先注意一下我在下图的这个TCP流示意图中,标记出的红框部分,在后续的分析过程中,我会重点分析这几个部分。

首先是报文1~3,表示TCP握手成功。

然后是报文4(客户端发出),表示客户端(消息网关)向服务器发送报文,这个报文里只包含HTTP header,其声明该请求为POST方法,但不含POST body。这其实是正常的,因为HTTP协议就是这样规定的,数据的先后顺序是:先header(包含method、URL、headers),后body。所以,既然方法(method)和URL单独位于一个报文里面了,那么按顺序来说,body就是在后续的报文里面。

接下来是报文5(服务端发出),它是一个确认报文。它的意思是:我(服务端)确认收到了你(客户端)发过来的报文4。

紧接着是报文6(客户端发出),此时距离上一个报文的时间是2秒。这个报文被Wireshark标记为了红色,注释为TCP Previous segment not captured,意思是它之前的TCP报文段没有被抓到。

什么叫做“之前的TCP报文”呢?其实就是按TCP序列号顺序,排在当前报文之前的报文。我对这个6号报文标注了3处红框,它们都有很重要的含义。这里我们先关注下右边一个红框圈出来的FIN标志位,这说明,这是一个客户端主动关闭连接的报文。

我们可以把到目前为止的报文情况,用下面这个示意图来表示:

你看这里是不是很奇怪?明明HTTP POST请求的body(也称为HTTP载荷)部分还没发过来,这个客户端就嚷嚷着要关闭连接了?这就好比有个朋友跟你说:“我有个事情要你帮忙,嗯,拜拜~”,你刚听到上半句他的求助意向,还没听到这个忙具体是什么,他就跟你说再见了。惊不惊喜,意不意外?你可能暂时看不出这里究竟出了什么问题,不过没关系,先放一放。

我们继续看报文7(服务端发出)。服务器收到了FIN+ACK报文(6号报文),但发现序列号并不是它期待的309,而是777,于是服务器TCP协议栈判断:有一个长度为777-309=468的TCP段(TCP segment)丢失了。

按TCP的约定,这时候服务端只可以确认其收到的字节的最后位置,在这里,就是上一次(报文5)的ACK位置。形式上,报文7就成了一个DupAck(重复确认)。

当客户端收到DupAck的时候,它就需要长一个心眼了:“情况有点微妙,如果凑满3个DupAck可能有丢包啊”。

补充:如果凑满3个DupAck就重传的机制,被称为快速重传机制,我们在第12讲我们有深入学习过。

为了帮助理解,这里我再展示下报文4的TCP信息:

那么,按TCP的设计,客户端将要发送的下一个报文的序列号(309)= 本次序列号(1) + 本次数据长度(308),也就是图中的Next sequence number。

我们再来看报文8(客户端发出),过了16秒之久,客户端重传了这个报文,包含POST body的数据,长度为 468 字节。你看,这是不是就跟前面说的777-309=468对应起来了。

可能你在这里又有点困惑了,明明这个468字节的报文是第一次出现,怎么就算重传了呢?

其实是因为,这个抓包文件是在服务端生成的,所以它的视角,是无法看到多次传送同样这个报文的现象的。但我判断,在客户端抓包的话,一定可以看到这个468字节的报文被试图传送了多次。

我们就以服务端视角来判断,一开始这个报文应该是走丢了,没有达到服务端,所以没有在这个服务端抓包文件里现身。又因为过了16秒之久才到达,很可能不是单纯一次重传,而是多次重传后才最终到达的。因此从这一点上讲,确实属于重传。

我们继续分析。接下来就是报文9,服务端对这个POST body的数据包回复了确认报文。

最后是报文10,服务端发送了HTTP 400的响应报文给消息网关。这个信息并没有被Wireshark直接按HTTP格式进行展示,但是因为HTTP是文本编码的,所以我们可以鼠标选中Transmission Control Protcol部分,在底下的文本栏直接看到HTTP 400这段文本:

有趣的是,这个 HTTP 400报文也是带FIN标志位的,也就是服务端操作系统“图省事”了,把应用层的应答数据(HTTP 400),跟操作系统对TCP连接关闭的控制报文(这个FIN),合并在同一个报文里面了。也就是我们在第3讲提到的搭顺风车(Piggybacking),提升了网络利用效率。

这个阶段的报文图示如下:

那么,从这些报文的顺序来看,我们会发现它确实是有问题的。特别是有以下几个疑点:

  • 服务端先收到了HTTP header报文,随后并没有收到期望的HTTP body报文,而是收到了FIN报文,即客户端试图关闭连接。这个行为十分古怪,要知道HTTP请求还没发送到服务端,服务端回复HTTP响应更是无从谈起,这个时候客户端发送FIN就不符合常理了(即前面说的朋友求帮忙的类比)。
  • 服务端回复了HTTP 400,并且也发送FIN关闭了这个连接。
  • 客户端回复RST彻底关闭这个连接。

而把上面这几条信息综合起来看,你有没有发现一个重要的线索?客户端先发送了FIN,之后才发送POST body。现在让我们把全部过程拼接起来,看一下全景图:

这么古怪的行为,可以描述为“服务端还没回复数据而客户端已经要关闭连接”。按照499的官方定义,这种行为就被Nginx判定为了499状态。对内表现为记录499日志,对外表现为回复HTTP 400给消息网关。

所以,在服务端的Nginx日志中,就留下了大量的499日志条目;而在消息网关那头,如果它也做Web日志的话,相信就不是499日志,而是400的报错了。

那么到这里,问题是水落石出了吗?其实不是。

从现象到本质

我们还需要搞清楚最底层的疑问:为什么客户端先发送FIN,然后才发送POST body?

我们回到Wireshark窗口,再次关注下6号报文:

它离上一个报文相差了2秒,而我们知道这个信息,是因为Wireshark很友好地显示了报文之间的间隔时长。

我们再往前看4号报文:

离3号报文相差了2.997秒,几乎就是3秒整了。那么加起来,6号报文离TCP握手完成,正好隔了 5秒整

一般出现这种整数,就越发可疑了,因为如果是系统或者网络的错乱导致的行为,其时间分布上应该是随机的,不可能卡在整数时间上。就我的经验来看,这往往跟某种人为的设置有关系

所以,经过我的提醒,客户自己仔细查看了微信网关的使用文档,果然发现了它确实有5秒超时的设置。也就是说,如果一个HTTP事务(在这个例子里是HTTP POST事务)无法在5秒内完成,就关闭这个连接。

这个“无法完成”,在这个抓包里面体现为:HTTP header报文发过去了,但HTTP body报文没有一起过去(网络原因导致)。而由于初始阶段报文少,无法凑齐3个DupAck,所以快速重传没有被启动,只好依赖超时重传(关于超时重传的知识在第12讲也有详细的介绍),而且这多次超时重传也失败了,服务端只好持续等待这个丢失的报文。5秒钟过后,客户端(微信消息网关)没有收到服务端的响应,就主动关闭了这次连接(可以下次再试,这次就不继续干等了)。

也就是说,这个场景里的Nginx 499错误日志的产生,主要是由于两个因素造成的:

  • “消息网关—>服务器”方向上的一个TCP包丢失(案例里是HTTP POST body报文),引起服务端空闲等待;
  • 消息网关有一个5秒超时的设置,即连接达到5秒时,消息网关就发送FIN关闭连接。

所以到这里,想必你也明白了这里的逻辑链条,也就是:

  • 要解决499报错的问题,就需要解决5秒超时的问题;
  • 要解决5秒超时的问题,就需要解决丢包问题;
  • 要解决丢包的问题,就需要改善网络链路质量。

最根本的解决方案,就是如何确保客户端到服务端的网络连接可靠稳定,使得类似的报文延迟的现象降到最低。只要不丢包不延迟,HTTP事务就能在5秒内完成,消息网关就不会启动5秒超时断开连接的机制。

这样,我们跟客户还有网关的工程师一起配合,确实发现网关到我们公有云的一条链路有问题。更换为另外一条链路后,丢包率大幅降低,问题得到了极大改善。虽然还是有极小比例的错误日志(大约万分之一),但是这对于客户来说,完全在可接受范围之内了。

另外,因为丢包的存在,客户端的FIN报文跟HTTP POST body报文一样,也可能会丢失。不过,无论这个FIN是否被服务端及时收到,这次HTTP事务本身也已经在客户端被记为失败了,也就是不改变这件事的结果。

你可能会问了:链路丢包这种问题应该挺明显的,为什么没有在第一时间发现呢?

这其实是多种因素导致的:

  • 我们虽然对主要链路的整体状况有细致的监控,但这里的网关到客户的公有云服务属于“点到点”的链接,本身也属于客户自身的业务,公有云难以对这种情况做监控,理想情况是客户自己来实现监控。
  • 客户的消息量很大,哪怕整体失败比例不高,但乘以绝对的消息量,产生的错误的绝对数也就比较可观了。

至于Nginx为什么要“创造”499这个独有的状态码的原因,其实在 Nginx源码的注释部分里,已经写得非常清楚了。它并非标新立异,而确实是为了弥补标准HTTP协议的不足。相关代码如下:

/*
 * HTTP does not define the code for the case when a client closed
 * the connection while we are processing its request so we introduce
 * own code to log such situation when a client has closed the connection
 * before we even try to send the HTTP header to it
 */
#define NGX_HTTP_CLIENT_CLOSED_REQUEST     499

翻译过来就是:HTTP并没有对服务端还在处理请求的时候客户端就关闭连接的情况,做一个状态码的定义。所以我们定义了自己的状态码(499),以记录这种“还没来得及发送返回,客户端就关闭了连接”的情形。

小结

现在,我们就清楚在这个例子里,造成499状态码的根因了。不过基于普适性的应用需求,我想把这个案例再延伸拓展一下,希望可以帮助你了解到更多的知识,并且在理解了这些知识点之后,你能够有效应用在类似的HTTP异常码的故障排查里。

首先,我们要知道,Nginx 499是Nginx自身定义的状态码,并非任何RFC中定义的HTTP状态码。它表示的是“Nginx收到完整的HTTP request前(或者已经接收到完整的request但还没来得及发送HTTP response前),客户端试图关闭TCP连接”这种反常情况。

第二,超时时间跟499报错数量也有直接关系。如果我们有办法延长消息网关的超时时间,比如从5秒改为50秒,那么客户端就有比较充足的时间去等待丢失的报文被成功重传,从而在50秒内完成HTTP事务,499日志也会少很多。

第三,我们要关注网络延迟对通信的影响。比如客户端发出的两个报文(报文3和报文4)间隔了3秒钟,这在网络通信中是个非常大的延迟。而造成这么大延迟的原因,会有两种可能:一是消息网关端本身是在握手后隔了3秒才发送了这个报文,属于应用层问题;二是消息网关在握手后立刻发送了这个报文,但在公网上丢失了,微信消息网关就根据“超时重传”的机制重新发了这个报文,并于3秒后到达。这属于网络链路问题

由于上面的抓包是在服务端做的,所以未到达服务器的包自然也不可能抓到,也就是无法确定是具体哪一种原因(客户端应用层问题或网络链路问题)导致,但这并不影响结论。

最后一点,就是我们要清楚,公网上丢包现象不可能完全消失。千分之一左右的公网丢包率属于正常范围。由于客户发送量比较大(这是主要原因),加上微信消息网关设置的5秒超时相对比较短(这是次要原因),这两个因素一结合,问题就会在这个案例中被集中暴露出来。

那么,像上面第二点说的那样,设置更长的超时阈值(比如50秒)能解决问题吗?相信出错率会降低不少,但是这样新的问题也来了:

  • 消息网关会有更多的资源消耗(内存、TCP源端口、计算能力等);
  • 消息网关处理事务的平均耗时会增加。

所以,选择5秒应该是一个做过权衡后的适中的方案。

而从排查的方法论上来说,对于更广泛的应用层报错日志的排查,我的推荐是这样的:

  • 首先查看应用文档,初步确定问题性质,大体确定排查方向。
  • 通过对比应用日志和抓取的报文,在传输层和网络层寻找可疑报文。在这一步,可以采用以下的比对策略来找到可疑报文:
    • 日志中的IP跟报文中的IP对应;
    • 日志和报文的时间戳对应;
    • 应用层请求信息和报文信息对应。
  • 结合协议规范和报文现象,推导出根因。

思考题

给你留两个思考题,欢迎在留言区分享你的答案和思考,我们一起交流讨论。

  • 第7个报文是DupAck,为什么没有触发快速重传呢?
  • 消息网关那头的应用日志应该不是499,那会是什么样的日志呢?

欢迎你把今天的内容分享给更多的朋友,我们一起成长。

精选留言

  • 追风筝的人

    2022-03-01 10:12:51

    看老师的解密就像福尔摩斯在探案 精彩(改善客户端到服务端的网络质量)
    作者回复

    谢谢你的支持,其实排查就是这样,很有趣,满足了像我这样的人的探索的欲望,从中也学习到新的知识,是一种很不错的“学习方法”~

    2022-03-02 21:46:24

  • 那时刻

    2022-03-21 13:35:32

    文中的抓包文件显示 POST请求,Packet size limited during capture: HTTP truncated。这个原因是IP包头里Length是348,而实际的包体小于这个值,所有wireshark才有此显示吧?

    另外,HTTP 协议就是这样规定的,数据的先后顺序是:先 header(包含 method、URL、headers),后 body。而我测试了下抓包POST请求,在body不大的情况下,body和header在一个数据包里;而body过大的话,产生分片,此时第一个数据包包含了header以及部分body。请问老师,在POST请求里,存在第一个数据包只包含header,而第二个数据包含有body的情况么?
    作者回复

    是的,Packet size limited during capture: HTTP truncated的原因就是你说的这个~
    HTTP是先发header后发body(如果有body的话)。header和body是否在不同的TCP分段中(严格来说不是“分片”)取决于应用程序往发送缓冲区里写入的速度。理论上说,TCP不认识HTTP,也不知道哪些数据是header哪些是body,所以header没凑满一个MSS就先发出去也是可能的。

    2022-03-23 21:55:59

  • 2022-03-12 12:09:46

    3、后端服务异常导致

    这个是情况最复杂多样也是最难排查定位的,因为这种情况并单纯是 499 往往还伴有随之而来来的 502/503/504 情况。

    一般来说:前端请求超时设置 > LB请求后端超时设置 > 后端服务超时设置,但是大部分应该都不会令 “前端请求超时设置 > LB请求后端超时设置”,因为前端请求超时设置是根据用户不同业务场景设置不同超时时间的,而 LB请求后端的超时时间 是针对所有转发请求的。

    若后端服务中的某一环出现硬盘/CPU/内存等资源不足,在高流量情况下会产生流量堆积,从而呈现出:后端先出现大量的 504,LB 机器间歇的出现大量504和499,最后出现大量的 502/503 的情况。(这种情况是否应该:先重启一下服务重置一下资源的占用情况,保证后续的服务能正常响应,缩短影响时间??)

    1). 人为因素:查看网络出入口带宽情况,看看在最近的时间内站点是否有大量的流量涌入导致了流量堆积,从而导致机器负载飙升。
    ①. 恶意攻击:有日志平台可以快速看一下是否是同一个IP或UA,针对IP/UA迅速屏蔽掉对应的请求。
    ②. 正常流量:新接入了流量渠道导致流量一下子增加。有没有限流措施可以实施?能否快速横向扩增机器来抗住流量的涌入?如果都不行,先暂时下掉流量渠道

    2). 非人为因素:自底向上排查到底是服务哪一环出现问题?
    数据库-->缓存-->业务机器,因为下层问题都会导致上层受影响,如果一开始就从上层排查,情况会特别棘手。
    ①. 数据库机器负载:查看是否有频繁的IO写入操作?查看是否索引问题导致大量慢日志?是否因为缓存雪崩/缓存穿透/缓存击穿,导致缓存失效从而流量查询直接到了数据库?
    ②. 缓存机器负载:IO/内存情况-看key的使用效率和IO操作?big key问题导致内存不足?往往是缓存的数据结构设计不正确导致
    ③. 业务机器负载:是否有代码逻辑不合理,导致业务机器有大量的IO处理、内存溢出...等情况?

    非人为因素,没有固定的解决办法,往往需要在各个环节都做出调整,譬如:数据库添加合适的索引、针对热点查询增加正确的缓存结构、业务代码进行调整(如:避免在循环中查询数据/缓存)
  • 那时刻

    2022-02-23 09:25:09

    请问老师客户端在报文6发送了fin,而服务器在报文8收到http body,这个报文8是由于客户端内核超时重传报文导致的吧?
    客户端对于服务器报文10的fin,回复rst,是不是因为报文10的seq number不合法导致的呢?
    作者回复

    关于第一个问题:这确实是一个有点迷惑人的地方,因为没有客户端的代码(特别是5秒超时的时候tcp连接是如何关闭的,用了close还是shutdown,是否有Linger设置等等),我们只能间接的推测,可能是下面这样:
    因为http请求数据量不大,可以在初始拥塞窗口里就能一次发送完,所以客户端把HTTP POST的所有报文都一次性发送了出去,但是因为网络问题,没有收到服务端的全部确认,所以有一部分数据滞留在发送缓冲区,并最终由于超时重传和重试机制得以发出(报文8的http body);
    到5秒时,应用程序启动了关闭socket的操作,一个FIN报文也进入了发送缓冲区,不过这个报文“运气好”,立刻被成功收到了。
    结合上面的情况,就出现了http body反而在FIN后到达的情况。

    第二个问题:报文10的seq是正确的,因为服务端还没有http响应,也就是没有真实的数据发送,所以seq还停留在1,这是对的。收到RST的原因,应该还是客户端那头的tcp连接已经被销毁,因为离客户端发起第一个FIN已经过去16秒。虽然16秒还是小于一般TIME_WAIT需要等待的时间(一般是60秒),但是考虑到客户端作为网关,要处理大量的连接,所以可能对TIME_WAIT做了优化(比如控制TW bucket的长度等)。16秒后,客户端认为这个报文已不属于任何连接,所以直接回复RST。而且这个RST没有ack号,这也符合“因为没有连接而回复”的RST的特征。

    2022-02-26 22:26:06

  • 上杉夏香

    2022-08-13 16:09:08

    有两个问题,想要询问老师。
    1、client在包6已经发送FIN,为啥包8还能发送数据呢?FIN不就是意味着发送端不再拥有发送能力了吗?我能想到的答案就是,这个包8是client在发送包6之前就发送的,只不过经过漫长的路由,才到达server端。

    2、关于包8被server端标记为『重传』的标志。因为包是在server端抓取的,就算client确实在不断的重发,对于server来说,它不知道。可是它依然标记为『重传』。那么我觉得它一定有做出如此判断的依据,我能想到的答案是:1、时间;2、包6的seq超过包8.但是最终wireshark为何如此做出判断,那就得看它的源代码了?
    作者回复

    你的思考很深入,给你点赞!
    1. 这个报文8如你所说,应该是公网上漫游延迟后到达的,这也是TCP状态机设计的时候,考虑TIME_WAIT需要等待2MSL时间的原因,因为难保有一些报文会延迟到达,这种延迟在这里已经达到了十多秒。
    2. 如果要确认这个判断逻辑,只能看一下wireshark源码了。我推测是因为FIN都已经到达了,那么序列号在FIN之前的报文已经都到达了(哪怕wiresahrk没有看到这个报文)。在FIN之后到达的报文,就被wireshark认为是重传。

    2022-08-26 14:53:59

  • salt

    2022-04-20 22:14:52

    6号报文带了Fin 。7号报文回的不是Fin +ack 而是dup ack。这是因为dup ack的优先级更高? 这样设计的原因是什么?
    作者回复

    这个dupAck是因为客户端发的5号和6号报文之间,数据有间隔,所以服务端回复的ack只能“停留”在数据中断处。如果客户端收到这样的三个dupAck就会启动快速重传。当然这里并没有条件做快速重传了,因为没有凑满三个dupAck。如果还有疑问,可以继续回复我~

    2022-04-22 00:45:51

  • 2022-03-12 12:09:29

    499的排查思路总结(麻烦老师帮忙看看是否正确):

    1、前/后端设置超时不对导致。

    譬如后端某接口后端需要3秒,而前端设置的请求超时时间小于3秒,那么会导致大量请求被客户端主动关闭,从而引发大量 NGINX 499。一般根据业务重设前端的请求超时时间即可,这种情况下:单独窗口访问接口、LB机器、后端机器、数据库机器等...负载、慢日志可能都是显示都正常的。
    该情况一般多见于新对接功能对整体超时时间不熟悉的情况下,感觉倒是不太常见。
    作者回复

    你的总结是对的:)确实是我们和客户都对这个微信网关的超时逻辑不熟悉导致的,不过这也给我们带去了经验的积累,以后遇到这类问题,会先查一下几个环节的超时设置,因为有可能就是它们没对齐导致了问题~

    2022-03-20 01:27:35

  • Geek_1bca66

    2024-03-03 16:49:15

    Nginx 499是Nginx自身定义的状态码,并非任何RFC中定义的HTTP状态码。它表示的是“Nginx收到完整的HTTP request前(或者已经接收到完整的request但还没来得及发送HTTP response前),客户端试图关闭TCP连接”这种反常情况。
    老师您好,根据499的定义,假如网络链路没有问题,但是服务端的处理时间比较长(比如应用的算法有问题),是不是也会导致499呢?
    作者回复

    其实499只是nginx的“规范”,并不是通用的RFC规范,我们理解nginx 499的含义就行。在其他软件里可能会不同。比如,同样是这种在服务端还没来得及返回response之前就关闭连接的情况,ngnix用499,而envoy用的就不是499,而是0。
    你说的“应用的算法有问题”,如果是指这种算法问题导致服务端不能在一定时间内返回response,那么客户端有可能有超时关闭连接的行为,此时nginx记录499,而envoy则记录为0.

    2024-10-20 14:31:36

  • 原则

    2023-06-09 08:07:19

    1. 第 7 个报文是 DupAck,为什么没有触发快速重传呢?

    因为要三个 DupAck,才会触发快速重传。

    2. 消息网关那头的应用日志应该不是 499,那会是什么样的日志呢?

    消息网关的日志大概率是接收超时。
  • Geek_93970d

    2023-04-03 17:32:14

    6 号报文的 seq num 是 777, tcp len 是 0,但是 next seq 为啥是 778 呢?
  • Geek_93970d

    2023-04-03 17:28:12

    在关闭流程里,客户端发送 fin 并收到服务端回复的 ack 后,客户端进入了 fin-wait2,这时候客户端是不会再向服务端发送数据了,可是在这个案例里,客户在收到 ack 后过了 16s 也就是在第 21s 由8号报文重发了丢失的数据包,请问,这个现象这是因为服务端回复的 ack 不是期望的 ack 导致的吗?(服务端的7号报文回复 Ack=309,而服务端期望回复的 Ack=778)
  • 蝴蝶

    2022-11-21 22:16:16

    如果先去看 nginx 的 499 的介绍,是不是就能更快的定位问题了,哈哈
    作者回复

    哈哈好问题。其实关于499的介绍,一开始看的是官方的简单文档,但不足以说明问题,主要两个原因:
    1. 这里的CLIENT CLOSED说的不清晰,为什么关闭?没有更具体的信息
    2. 我们的客户是需要更加详细的证据的,比如抓包分析。这是更加重要的原因

    源码注释里面的信息更加丰富一些,不过也还是需要结合抓包分析,才足够有说服力:)

    2022-11-27 00:01:24

  • 风铃

    2022-11-10 21:19:24

    老师你好,看了这么多的资料了,有个疑惑的问题,tcpdump 抓包的方式,很多参数,怎么来判断使用哪些参数抓包合理了,希望解答下,感谢
    作者回复

    这个好办,你可以直接看一下第25讲,也就是抓包分析的回顾、拾遗,和提高。那里有我总结的场景和使用细节。不过还是建议把全专栏都看一遍,实操一下~

    2022-11-13 00:28:26

  • 阿强

    2022-11-02 21:50:50

    10号报文21s从服务端发出,也就是在消息网关发出fin后的16s后再发出,此时消息网关的应用层代码来看,tcp连接应该已经断开了,服务端返回的http状态码400等信息,消息网关的应用层应该是收不到吧,所以消息网关那头的应用日志,只能知道是http超时然后自己规定一个状态码,并不会获取到服务端的400状态码吧?
  • Jack

    2022-03-29 11:56:19

    请教一下老师,wireshark抓到报文之后Follow Tcp Stream显示的都是乱码,这个怎么解决呢
    作者回复

    你抓取的是什么应用的报文呢?如果是没有加密的http,应该能看到明文;如果是TLS,那确实是密文,也就是你说的乱码了:)

    2022-03-29 19:25:57

  • Jack

    2022-03-29 10:18:56

    root用户登录机器使用wireshark抓包时报这个错误The temporary file to which the capture would be saved ("/tmpy/
    wireshark_eth0_20220328155843_EYvwXW.pcapng") could not be
    opened: Permission denied. 这个怎么解决呢
    作者回复

    你好,可以参考这里的解答:https://osqa-ask.wireshark.org/questions/39864/the-temporary-file-to-which-the-capture-would-be-saved-could-not-be-opened-invalid-argument/

    简单来说是两步:1. 创建一个目录 2. 把TEMP环境变量的值设置为这个目录的绝对路径
    你可以试试~

    2022-04-01 23:39:10

  • 2022-03-12 12:12:01

    对于上述还有:还有可能是TCP的连接句柄不足等情况导致,这个之前遇到过,时间太久已经忘记当时的关注结果~期待老师能补充一下。(由于评论有字数限制...所以分了几种情况写)
  • 2022-03-12 12:09:37

    2、LB 机器问题导致
    在 LB 机器上手动的请求一下要访问的接口,LB代理异常或网络抖动也会导致LB通过内网请求后端服务时超时,从而引发客户端主动关闭请求,导致 LB Nginx 大量的 499。
    这个场景,多数由于网络抖动或运维人员进行网络调整之后导致。
    作者回复

    是的,你说的场景是这样:
    客户端->LB->nginx
    那么如果客户端跟LB网络稳定,而LB跟nginx网络不稳定的话,很可能客户端发送请求是及时完成的,但是LB转发给nginx的过程会比较长,导致客户端超时后发送FIN,会间接导致nginx 499~

    2022-03-20 01:32:04

  • 小白

    2022-03-04 15:41:45

    如何判断,post请求是否发送了body。我记得之前的列子中MTU那节。我也没看到那个post请求的body已发送呀。
    作者回复

    您好,可以参考抓包示例文件,我在文中有给出链接:https://gitee.com/steelvictor/network-analysis/tree/master/15
    在这个抓包文件第4个报文,就是客户端发送的HTTP POST请求头部,而body是在第8个报文。你下载抓包文件对比看看呢~有问题可以这里继续回复的。

    2022-03-04 22:19:11

  • bingo

    2022-02-23 13:27:25

    报文的时间有点乱,看得有点懵逼
    作者回复

    我看看示意图如何优化一下,把时间线展示的更加清楚些~
    就顺序来说,示意图里对每个报文表上了数字序号,跟抓包文件里的序号是一致的~

    2022-02-24 22:16:42