net/http:请求已取消(等待标头时超出了 Client.Timeout)为什么/该怎么办?

Dom*_*rre 5 http azure go asp.net-web-api azure-web-app-service

TL:DR - 查看编辑 2 中与代码 http 客户端代码等效的 C#,导致〜相同的问题,因此 Go http.Client 不是真正的问题,而是 C# Web API 一旦部署到 Azure...

一旦部署到 Azure Web App [2x 标准 S3],我的 C# Web API 性能就会非常差。起初我问的是:Go 的 http.Client 超时,但用 C# 和 NodeJs 编写类似的客户端给出了相同的结果。

这是我的 http.Client:

func getWebClient() *http.Client {
    var netTransport = &http.Transport{
        Dial: (&net.Dialer{
            Timeout: 5 * time.Second,
        }).Dial,
        TLSHandshakeTimeout:   10 * time.Second,
        MaxIdleConnsPerHost:   2000,
        ResponseHeaderTimeout: 10 * time.Second,
    }

    var netClient = &http.Client{
        Timeout:   time.Second * 10,
        Transport: netTransport,
    }

    return netClient
}
Run Code Online (Sandbox Code Playgroud)

我收到的错误:

net/http: request canceled (Client.Timeout exceeded while awaiting headers)

它可以是 GET、POST、PUT。我收到了这些错误,但使用curl 运行失败的GET 立即得到了答复。

这是我用来调用 API 的示例 Get 函数:

func get(path string, result interface{}) error {
    req, err := http.NewRequest("GET", webDALAPIURL+path, nil)
    if err != nil {
        return err
    }

    req.Header.Set("Content-Type", "application/json")

    wc := getWebClient()
    res, err := wc.Do(req)
    if err != nil {
        return err
    }
    defer res.Body.Close()

    if res.StatusCode >= 400 {
        return fmt.Errorf("[GET] %d - %s", res.StatusCode, webDALAPIURL+path)
    }

    decoder := json.NewDecoder(res.Body)
    return decoder.Decode(result)
}
Run Code Online (Sandbox Code Playgroud)

有趣的是,当 API 在本地运行时从未遇到过这些错误。API 是一个 C# ASP.NET Web API 应用程序。

我开始出现大量 TLS 握手错误,因此我删除了 Azure 应用程序端点的 https。现在我收到这个错误。

我正在跟踪应用程序的日志,但没有发生任何事情 [正在调用的 API]。似乎 Go 无法对同一主机进行多次调用。我不是在一个 cmd 中使用 goroutine 并在另一个 cmd 中使用它们,两者都会导致相同的错误。

当API在同一网络中的Windows计算机上运行时,在开发过程中从未出现过该错误。

编辑1:

请注意,80-85% 的请求运行良好,场景是这样的(伪代码):

for item in items {
  http get /item/{id} => works 90% of the time, 10% timeout

  change item properties

  http PUT /item/{id} => works 80% of the time
}
Run Code Online (Sandbox Code Playgroud)

我在函数中添加了重试get(),因此如果发生超时,它将重试获取,这似乎有效。虽然,我根本不喜欢这种解决方法。

另请注意,我们正在讨论返回超时的快速 GET,当我从curl 运行它们时,它是< 1 秒。对于 PUT 来说也是如此,这些都是高度简单的数据库 SELECT * FROM TABLE WHERE ID = {id} AND UPDATE。

运行 API 的 Azure Web 应用程序是标准 S3 的 2 个实例。

事实上,GET 重试起作用的事实看起来很可能是 API/Azure 应用程序没有承受负载,这对于简单性来说是不可能的,我们正在谈论少于 10 个请求/秒。

另一点不可忽视,在开发服务器上运行时,使用相同的 Azure SQL 数据库,因此 SELECT/UPDATE 性能在开发服务器和 Azure Web App 上应该完全相同。

编辑2:

比较相同的 C# Web API 从本地到 Azure 的速度令人不安。我编写了一个类似的 C# http 客户端来测试 Azure 与本地 Web API。

class Program
{
  static int fails = 0;
  static void Main(string[] args)
  {
    for (int i = 0; i < 2000; i++)
    {
      get(i);
    }
    Console.WriteLine("completed: " + fails.ToString());
    Console.ReadLine();
  }
  static void get(int id)
  {
    id += 22700;
    var c = new HttpClient();

    var resp = c.GetAsync("http://myapphere.azurewebsites.net/api/users/" + id).Result;
    if (!resp.IsSuccessStatusCode)
    {
      Console.WriteLine("");
      fails++;
      Console.WriteLine(string.Format("error getting /users status code {0}", resp.StatusCode));
    }
    else
    {
      Console.Write(".");
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

在 Azure 上运行这个控制台应用程序,我可以清楚地看到 Go 在哪里超时,它非常慢,没有返回错误,但是 Console.Write("."); 打印需要很长时间,是周期性的,打印速度比停止时快 3-4 倍。

再次将其更改为 localhost:1078,使用相同的数据库,没有暂停,并且 Console.Write(".") 的打印速度是 Azure 的 20 倍。

这怎么可能?

编辑3:

刚刚在 Web api 上添加了全局错误处理程序,可能是由于抛出太多异常而导致的不严谨。添加了一个,然后Trace.TraceErrorazure site log tail再次没有显示任何内容。

我什至可以说,本地 Web API 的运行速度比 Azure 标准 S3 实例的运行速度快 25-30 倍。显然这不是真的,但 Web API 非常简单,我不知道如何让 Azure 全速运行它。

Mar*_*rco 0

我认为你可能想使用上下文、go 例程和通道之类的东西来不锁定你的 main. 类似于 c# 中的 Task,它也为您提供更多的吞吐量和性能。

https://marcofranssen.nl/tags/go/

你可以找到我写的一堆博客文章。我也有 C# 背景,这些博客是我学到的一些经验教训。我在博客中确实提到了 C# 与 Go 方法,这样您就可以轻松进行比较。

查看 go 例程和优雅的网络服务器帖子。您可能会在那里找到答案。

此外,您的 C# 实现由于调用 .Result 而被阻塞

您应该使用异步等待,利用任务使代码更加高效。

我预计您的 C# 服务器可能也没有使用 Task 作为返回类型,这意味着它将无法处理大量连接。一旦您在那里使用任务并在新线程中旋转代码,新线程也将能够处理更多并发请求并可能更频繁地成功。因此,基本上在单独的线程/任务中执行数据库操作,并确保处理数据库连接以防止内存泄漏。

这只是一个假设,因为我注意到这篇文章中的客户端代码也没有正确使用它。因此,如果我的假设有误,请原谅我。