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

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

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

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

我的理解:

  • 来自源的响应是 [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 超时。这里另一个作者解决了这个问题,请看

相关内容