代理尝试将数据上游发送到部分关闭的连接后生成的 HTTP 502 响应(重置数据包)

Min*_*ius 4 networking http nginx reverse-proxy

我收到代理服务器返回的零星 502 错误。在检查数据包流时,我看到 nginx 向源服务器已发送 [FIN,ACK] 的套接字发送 POST 请求。我想了解这是如何可能的以及任何潜在的解决方案。是源端的问题(发送响应后 5 秒后才发送 FIN、ACK)还是代理的问题?

以下是说明问题的 PCAP 屏幕截图: 在此输入图像描述

我的理解:

  • 来自源端的响应是 [PSH, ACK];
  • proxy 为通过该 [P.] 接收到的数据发送一个 [ACK](wireshark 确认下一个 [ACK] 是针对之前接收到的 [PSH-ACK]);
  • 7 秒过去了(注意 [FIN, ACK] 和我们的 POST ([PSH, ACK]) 之间的时间戳);
  • origin 发送 [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"
Run Code Online (Sandbox Code Playgroud)

此屏幕截图中显示了最后一个请求的 SEQ 和 ACK 编号:

在此输入图像描述

Ped*_*rez 5

此案可能的解释是什么?

源上大约 5 秒的空闲计数器与可变的客户端活动之间的竞争条件。第三个涉及的变量当然是网络延迟。

源上似乎有大约 5 秒的空闲计时器,而您的客户端需要大约 5 秒的时间通过 Nginx 代理发出第二个请求 (POST)。如果前者比后者长(包括网络延迟),则没有问题。如果发送客户端请求只需要多一点时间,那么您就会遇到问题。

您可以看到来自 Nginx 的 POST 和 FIN、ACK 几乎是一起发送的:分别在源端的 FIN、ACK 之后 2.4 毫秒和 2.6 毫秒。这可能会让您偏离正轨,因为我认为 POST 根本不是对源端 FIN、ACK 的响应。由于是在源端FIN、ACK后2.4ms发送的

如果代理(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 秒。缺点:大量额外的交通/噪音。

希望这可以帮助 :)