我偶尔收到代理服务器返回的 502 错误。检查数据包流时,我发现 nginx 向源服务器已发送 [FIN,ACK] 的套接字发送 POST 请求。我想了解这是怎么回事以及任何可能的解决方案。这是源服务器的问题(它在发送响应后 5 秒才发送 FIN、ACK)还是代理的问题?
我的理解:
- 来自源的响应是 [PSH,ACK];
- 代理使用该 [P.] 发送一个 [ACK] 来表示接收到的数据(wireshark 确认下一个 [ACK] 是针对之前收到的 [PSH-ACK]);
- 7秒过去了(记下[FIN, ACK]和我们的POST([PSH, ACK])之间的时间戳);
- 源端发送 [FIN, ACK]。当发送第一个 [FIN, ACK] 时,源端 TCP 状态机应处于 FIN_WAIT_1 状态。
- 然后我们发送另一个 POST,导致返回 [RST],因为源并不期待 [PSH, ACK]。
问题:
- 对于这个案例可能的解释是什么?
- 如果代理(nginx)已经收到 FIN 并且实际上正在确认它,为什么它还要发送另一个请求!(POST [PSH,ACK] 数据包中的确认号实际上是 [FIN,ACK] 的 SEQ_NUMBER + 1 - 因此它确认了幻影位 FIN。
- 源端在 5 秒后才返回 [FIN,ACK] 而不是立即返回,可能的原因有哪些?读取超时/空闲超时?
我不拥有起源 —— 因此无法在那里捕捉。
额外细节:
代理上的错误日志(nginx错误日志):
2017/04/17 06:51:07 [error] 123091#0: *225010841 upstream prematurely closed connection while reading response header from upstream, client: X.90.10, server: www.example.com, request: "POST /web/?a=b HTTP/1.1", upstream: "http://X.32.238:80/web/?a=b", host: "www.example.com"
此屏幕截图显示了最后一个请求的 SEQ 和 ACK 编号:
答案1
对于这个案例可能的解释是什么?
源端约 5 秒的空闲计数器与可变的客户端活动之间的竞争条件。第三个涉及的变量当然是网络延迟。
源端似乎有一个约 5 秒的空闲计时器,而您的客户端需要约 5 秒的时间通过 Nginx 代理发出第二个请求 (POST)。如果前者比后者长(包括网络延迟),则没有问题。如果客户端请求的发送时间稍长,则存在问题。
您可以看到 Nginx 的 POST 和 FIN、ACK 几乎同时发送:分别在源的 FIN、ACK 之后 2.4 毫秒和 2.6 毫秒。这可能会让您偏离主题,因为我认为 POST 根本不是对源的 FIN、ACK 的响应。因为它是在源的 FIN、ACK 之后 2.4 毫秒发送的
如果代理(nginx)已经收到 FIN 并且实际上正在确认它,为什么它还要发送另一个请求!(POST [PSH,ACK] 数据包中的确认号实际上是 [FIN,ACK] 的 SEQ_NUMBER + 1 - 因此它确认了幻影位 FIN。
POST 数据包上的 ACK 编号很可能是“200 OK”数据包的编号。HTTP 响应之后,服务器端没有其他数据,因此来自客户端的任何 ACK 都将确认相同的编号。
更新:我们现在知道 POST 数据包的 ACK 编号增加了 1,因此 Nginx 知道 [FIN,ACK]。进一步的调查显示这没问题:如果一台机器在收到远程端的响应后不打算继续连接,那么它可能会发送一个请求并以 [FIN,ACK] 结束,远程端将发送请求的数据并继续 [FIN,ACK] 过程。
这并不能改变存在竞争条件的事实,即源端决定在空闲 5 秒后关闭连接,从而忽略随后不久的 POST 数据包(甚至发回 RST - 尽管不清楚是否会发送此 RST)。
源端在 5 秒后才返回 [FIN,ACK] 而不是立即返回,可能的原因有哪些?读取超时/空闲超时?
您不必立即返回 FIN、ACK,尤其是自 HTTP 1.1 和引入持久连接以来。这 ~5 秒似乎是源上的空闲计时器。
这两件事都在这里得到证实:https://en.wikipedia.org/wiki/HTTP_persistent_connection- 包括 Apache 2.2 或更新版本中的默认 5 秒空闲超时。
建议的解决方案
如果不了解您的基础设施,我无法真正提出解决方案,但粗略地说,您有几种选择:
- 调查客户端为何需要 5 秒钟才能发送第二个请求。缺点:耗时且可能意味着应用程序更改。
- 将源 (Apache?) 的超时时间增加到大约 10 秒。缺点:由于您保持更多资源处于闲置状态,因此扩展存在问题。可能需要更改应用程序以尽快处理连接。
- 不要通过发出“Connection: Close”标头来重用 TCP 连接进行第二次 HTTP 请求。缺点:由于必须建立新的 TCP 会话,因此每个请求的成本更高。可能需要更改应用程序以在所有请求上发出标头或更改 Nginx,从而偏离您的默认配置(增加管理成本)。
- 使用上游配置中 Nginx 上的“keepalive”选项将 keepalive 设置为低于 5 秒。缺点:大量额外流量/噪音。
希望这可以帮助 :)
答案2
我认为这是由于上游服务器套接字保持活动超时引起的,并且套接字将被关闭,默认套接字.setsolinger 将不会打开。
我觉得可以让 nginx 上游服务器 keepalive 超时。这里另一个作者解决了这个问题,请看这