9官方定义,这种行为被Nginx判定499状态:
- 对内表现为记录499日志
- 对外表现为回复HTTP 400给消息网关
所以,在服务端的Nginx大量499日志条目;在消息网关那头,如果它也做Web日志,就是400报错。
5 从现象到本质
为什么客户端先发送FIN,然后才发送POST body?
回到Wireshark窗口,再关注6号报文:
离上个报文相差了2s。
往前看4号报文:
离3号报文相差3s。加起来,6号报文离TCP握手完成,正好隔 5s整。
一般出现这种整数,就越可疑,因为如果是系统或网络错乱导致,时间分布应 随机,不可能卡在整数时间。经验看, 这和人为设置有关。于是客户仔细查看微信网关使用文档,发现5s超时设置。即若一个HTTP事务无法在5s内完成,就关闭这连接。
啥叫无法完成?
在这抓包里即:HTTP header报文发过去了,但HTTP body报文没一起过去(网络原因导致)。而由于初始阶段报文少, 无法凑齐3个DupAck,所以快速重传没有启动,只好依赖超时重传(12 讲),且这多次超时重传也失败,服务端只好持续等待这丢失的报文。5s后,客户端没收到服务端响应,就主动关闭这次连接(可以下次再试,这次就不继续干等)。
即该场景里Nginx 499错误日志主因:
- 消息网关—>服务器 方向上的一个TCP包丢失(案例里是HTTP POST body报文),引起服务端空闲等待
- 消息网关有5s超时设置,即连接达到5s,消息网关就发FIN关闭连接
逻辑链:
- 要解决499报错的问题,就需要解决5s超时
- 要解决5s超时,就要解决丢包
- 要解决丢包,就要改善网络链路质量
最根本解决方案,如何确保客户端到服务端的 网络连接 可靠稳定,使类似的报文延迟现象降到最低。只要不丢包不延迟,HTTP事务就能在5s内完成,消息网关就不会启动5s超时断开连接机制。
跟客户还有网关工程师配合,确实发现网关到公有云的一条链路有问题。更换为另外一条链路后,丢包率大幅降低,问题极大改善。虽然还是有极小比例的错误日志(约万分之一),但对客户已在可接受范围。
因为丢包,客户端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
6 总结
Nginx 499是Nginx定义状态码,不是RFC中定义HTTP状态码。表示“Nginx收到完整的HTTP request前(或者已经接收到完整的request但还没来得及发送HTTP response前),客户端试图关闭TCP连接”这种反常情况。
超时时间跟499报错数量也有直接关系。如果我们有办法延长消息网关的超时时间,比如从5秒改为50秒,那么客户端就有比较充足的时间去等待丢失的报文被成功重传,从而在50秒内完成HTTP事务,499日志也会少很多。
关注网络延迟对通信的影响。比如客户端发出的两个报文(报文3和报文4)间隔了3秒钟,这在网络通信中是个非常大的延迟。而造成这么大延迟的原因,会有两种可能:
- 消息网关端本身是在握手后隔了3秒才发送了这个报文,属 应用层问题
- 消息网关在握手后立刻发送了这个报文,但在公网上丢失了,微信消息网关就根据“超时重传”的机制重新发了这个报文,并于3秒后到达。属 网络链路问题。
由于上面的抓包是在服务端做的,所以未到达服务器的包自然也不可能抓到,也就是无法确定是具体哪一种原因(客户端应用层问题或网络链路问题)导致,但这并不影响结论。
公网丢包现象不可能完全消失。千分之一左右公网丢包率是正常范围。由于:
- 客户发送量较大(主因)
- 微信消息网关设置5s超时相对较短(次要原因)
问题就会在这个案例中被集中暴露。
设置更长超时阈值(如50s)能解决?出错率会降低不少,但新问题:
- 消息网关会有更多的资源消耗(内存、TCP源端口、计算能力等)
- 消息网关处理事务的平均耗时会增加
所以,5s是权衡后的合适方案。
排查的方法论,对更广泛的应用层报错日志的排查,推荐:
FAQ
- 第7个报文是DupAck,为什么没触发快速重传?
- 消息网关那头的应用日志应该不是499,那会是啥样日志?
本文由博客一文多发平台 OpenWrite 发布!