【问题标题】:Corrupted and split TCP frames [closed]损坏和拆分的 TCP 帧 [关闭]
【发布时间】:2020-05-12 03:13:20
【问题描述】:

我们在负载均衡器后面运行一个用 Go 编写的 HTTP 服务器。在随机数量的请求(每几亿次)之后,一个请求以某种方式分成两个数据包,第二个数据包缺少第一个字节。

我们分析了我们的服务器库 fasthttp 和 Go 的代码,但没有发现任何可以解释这种行为的东西。

接下来我们用 tcpdump 捕获了很多这样的请求,它们没有被拆分 - 整个请求都在一个 TCP 数据包中,它们看起来是正确的。

Go 服务器仍然认为有两个数据包,并且 strace 确认套接字正在被读取两次:

2289 23:09:37.239558 read(212, "GET /json HTTP/1.1\r\nContent-Type: application/x-stream\r\nAccept: application/x-stream\r\nAccept-Encoding: gzip\r\nUser-Agent: Java/Android\r\nConnection: Keep-Alive\r\nKeep-Alive: 5000\r\nHttp-version: HTTP/1.1\r\nHost: <snipsnip>\r\nX-TCPI: 91", 4096) = 229
32289 23:09:37.239620 read(212, "217.26.11   \r\n\r\n", 3867) = 16 

所以我们认为它要么是负载均衡器,在这种情况下,tcpdump 会以某种方式修改数据包以使其看起来正确,要么是内核。

接下来我们应该看哪里?

【问题讨论】:

    标签: linux networking tcp kernel


    【解决方案1】:

    不过,Go 服务器认为有两个数据包......

    这是错误的解释。 Go 服务器从 TCP 套接字读取。 TCP 套接字没有数据包的概念,它只看到一个字节流。在传输过程中如何打包字节流根本不重要,即单个读取可能是单个数据包或多个数据包甚至半个数据包等的结果。类似的 TCP 堆栈可能会将多个写入放入单个数据包或也可能将单个写入分散到多个数据包中。

    假设将完成特定打包、单个读取将匹配单个数据包或对发送方的写入将导致在接收方完全读取的应用程序是错误的。它可能大部分时间都可以工作,但如果在不同的上下文中使用,它可能会突然中断,机器上的负载更大,套接字缓冲区的大小不同等。这种错误的假设通常会导致难以触发和难以调试的问题。

    ...一个请求以某种方式分成两个数据包第二个数据包缺少第一个字节。 ...接下来我们应该看哪里?

    请检查数据包捕获是否该字节在数据到达服务器之前丢失或是否在您的代码中。根据数据损坏的位置,您要么需要修复代码,要么需要修复服务器前面的任何内容并损坏数据。它甚至可能是一个坏掉的客户。您可以通过策略性地在网络中的各个位置进行数据包捕获来缩小问题的原因。

    【讨论】:

    • 如前所述,使用 tcpdump 捕获的数据包(与 Go 服务器在同一台机器上)表明到达服务器的数据是正确的,包括丢失的字节。
    • @Vlad:虽然您可能已经在物理服务器上捕获了数据,但您应该直接在 HTTP 服务器之前(即负载均衡器之后)捕获数据,以缩小问题的原因。虽然并非完全不可能,但我发现这不太可能是内核问题。考虑到有多少人使用内核没有问题,而有多少人使用您自制的 HTTP 服务器,那么问题很可能出在您自己的代码(或负载平衡器)中。
    • 对不起,我解释的不够清楚。负载均衡器不在同一台机器上,它通过网络将这些数据包发送到 Go 服务器。
    • @Vlad:我明白了。因为tcpdump 很确定不是“...不知何故正在修改数据包以使其看起来正确,” 而且内核不太可能比您的应用程序更容易出错,我怀疑您的自我制作HTTP服务器是实际问题。同样,阅读两次应该不是问题。
    • 我假设 strace 捕获的 read() 调用证明“自制 HTTP 服务器”(fasthttp)不是问题。
    猜你喜欢
    • 1970-01-01
    • 2013-09-16
    • 2011-11-07
    • 1970-01-01
    • 2013-02-25
    • 1970-01-01
    • 2012-01-15
    • 1970-01-01
    • 2023-03-11
    相关资源
    最近更新 更多