net/http:请求已取消(等待标头时超出 Client.Timeout)为啥/如何处理?

Posted

技术标签:

【中文标题】net/http:请求已取消(等待标头时超出 Client.Timeout)为啥/如何处理?【英文标题】:net/http: request canceled (Client.Timeout exceeded while awaiting headers) why/what to do with this?net/http:请求已取消(等待标头时超出 Client.Timeout)为什么/如何处理? 【发布时间】:2016-11-28 05:14:05 【问题描述】:

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

一旦将 C# Web API 部署到 Azure Web App [2x Standard S3],我的性能就很差。起初我问的是: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

我得到的错误:

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)

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

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

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

当 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

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

还请注意,我们正在谈论返回超时的快速 GET,当我从 curl 运行它们时,它是

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

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

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

编辑 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(".");
    
  

运行此控制台应用程序针对 Azure 我可以清楚地看到 Go 超时的地方,它非常缓慢,没有返回错误,但是 Console.Write(".");需要永远打印,是周期性的,可以打印~比停止打印快 3-4。

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

这怎么可能?

编辑 3:

刚刚在 web api 上添加了全局错误处理程序,可能是由于抛出了过多的 Exception 而造成的。添加了一个Trace.TraceError 和我azure site log tail 它,仍然没有显示任何内容。

我想说的是,本地 Web api 的运行速度比 Azure 标准 S3 的 2 倍实例快 25-30 倍。显然这不可能是真的,但是 Web api 是如此简单,以至于我看不出我可以做些什么来让 Azure 全速运行它。

【问题讨论】:

一个需要确认的问题:Fiddler,您能否使用 IE 或其他浏览器访问托管在 Azure 上的 ASP.NET API?我认为第一件事是我们应该确保您的 API 在 Azure Web App 上运行良好。 API 工作正常,80% 的请求有效,它是周期性的,在收到错误后立即通过 curl 运行失败的请求。 Will Shao 编写了一个 C# 等效应用程序,当使用 Azure 端点运行时速度非常慢,问题根本不在于 Go,但事实上,具有相同数据库的相同应用程序在部署到 Azure 后运行不佳。 您可以尝试将 Application Insights 添加到 API 并使用它来尝试找出正在发生的事情。 【参考方案1】:

我认为您可能希望使用上下文、执行例程和频道等内容来不锁定您的 main。与 c# 中的 Task 类似,它也为您提供更高的吞吐量和性能。

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

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

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

由于调用 .Result,您的 c# 实现也会阻塞

您应该使用 async await,这使得代码更加高效,利用 Task。

我希望您的 c# 服务器也可能没有使用 Task 作为返回类型,这意味着它将无法处理大量连接。只要您在那里使用任务并在新线程中旋转代码,这些代码也将能够处理更多并发请求并可能更频繁地成功。所以基本上在单独的线程/任务中做 db 的东西,并确保处理数据库连接以防止内存泄漏。

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

【讨论】:

以上是关于net/http:请求已取消(等待标头时超出 Client.Timeout)为啥/如何处理?的主要内容,如果未能解决你的问题,请参考以下文章

Docker Toolbox Tutorial Client.Timeout 在等待标头时超出

ECS 任务未启动 - 已停止(CannotPullContainerError:“来自守护程序请求的错误响应在等待连接时取消”

Chrome 在重定向时取消 CORS XHR

java.io.IOException:写入“”字节的请求超出了“条目”的“字节”标头中的大小

使用改造向请求添加标头时如何避免在主线程上等待?

已超出传入邮件的最大邮件大小配额 (65536)