超时异常 - 请求排队?线程不够?

Posted

技术标签:

【中文标题】超时异常 - 请求排队?线程不够?【英文标题】:Timeout Exception - Queuing of Requests? Not enough threads? 【发布时间】:2014-06-04 23:08:18 【问题描述】:

背景:

我有一项服务可以汇总来自多个其他服务的数据。为了让事情及时发生,我在整个代码中使用异步,然后将各种请求收集到一个任务列表中。

以下是部分代码摘录:

private async Task<List<Foo>> Baz(..., int timeout)

    var tasks = new List<Task<IEnumerable<Foo>>>();
    Tasks.Add(GetFoo1(..., timeout));
    Tasks.Add(GetFoo2(..., timeout));
    // Up to 6, depending on other parameters.  Some tasks return multiple objects.

    return await Task.WhenAll(tasks).ContinueWith((antecedent) =>  return antecedent.Result.AsEnumerable().SelectMany(f => f).ToList(); ).ConfigureAwait(false);
    
private async Task<IEnumerable<Foo>> GetFoo1(..., int timeout)

Stopwatch sw = new Stopwatch();
sw.Start();

    var value = await SomeAsyncronousService.GetAsync(..., timeout).ConfigureAwait(false);

sw.Stop();
// Record timing...
    return new[]  new Foo(..., value) ;

private async Task<IEnumerable<Foo>> GetFoo2(..., int timeout)

return await Task.Run(() => 
    Stopwatch sw = new Stopwatch();
    sw.Start();
    var r = new[]  new Foo(..., SomeSyncronousService.Get(..., timeout)) ;
    sw.Start();
    sw.Stop();
    // Record timing...
    return r;
).ConfigureAwait(false);
  

// In class SomeAsyncronousService
public async Task<string> GetAsync(..., int timeout)

...
    try
    
        using (var httpClient = HttpClientFactory.Create())
        
            // I have tried it with both timeout and CTS.  The behavior is the same.
            //httpClient.Timeout = TimeSpan.FromMilliseconds(timeout);
            var cts = new CancellationTokenSource();
            cts.CancelAfter(timeout);

            var content = ...;
            var responseMessage = await httpClient.PostAsync(Endpoint, content, cts.Token).ConfigureAwait(false);
            if (responseMessage.IsSuccessStatusCode)
            
                var contentData = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
                ...
                return ...
            
            ...             
        
    
    catch (OperationCanceledException ex)
    
        // Log statement ...
    
    catch (Exception ex)
    
        // Log statement ...
    
    return ...;

症状:

这段代码在我的本地机器上运行良好,并且大部分时间在我们的测试服务器上运行良好。但是,偶尔我们会收到大量记录的超时 - 由上面的“记录时间”cmets 和 OperationCanceledExceptions 上的 Log 语句记录。我无法判断我调用的服务是否真的超时了。

现在,当我说一系列超时时,我的意思是大多数或所有任务(以及除一个之外都使用的 HttpClients,另一个使用 WCF 服务)几乎同时超时。

现在,我知道你在想什么,我正在通过相同的超时。没错,但我在 250 毫秒内通过,各种秒表报告的运行时间约为 800 毫秒或更高。

现在,我确实在日志中看到了 OperationCanceledExceptions,但异常的时间戳与秒表结束时(或在 2-3 毫秒内)的时间戳相同,并且我的服务失败了,因为客户期待它会在 500 毫秒或更短的时间内做出响应,而不是 800 毫秒。

现在,各种服务通常会在 100 毫秒内做出响应,结果之间存在很大差异。当我们出现问题时,大多数/全部在 800 毫秒或更长时间内返回,它们仅相差约 10 毫秒。我调用的依赖项都在不同的域上。似乎不太可能所有这些人都真的需要这么长时间才能同时做出回应。

我想可能存在网络问题,同时影响所有请求,但我们网络中的其他服务不会遇到相同的行为 - 它仅限于我正在编写的新服务。

即使是这种情况,我也希望取消异常会在 250 毫秒后发生,然后任务结束并且秒表记录 250(加上 5-20 毫秒左右的异常处理时间)。

所以我认为这不是网络问题。现在我确信至少部分问题与我没有正确取消/超时有关,但在我看来,来自服务的所有发出的请求都同时受到影响,与 HttpClient 无关。

我之所以这么说是因为当其余请求超时时,WCF 服务也会显示 800+ 毫秒(根据秒表)。 WCF 服务不是异步的。超时设置如下:

var binding = new BasicHttpBinding()

    Security = new BasicHttpSecurity()
    
        Mode = BasicHttpSecurityMode.TransportCredentialOnly,
        Transport = new HttpTransportSecurity()
        
            ClientCredentialType = HttpClientCredentialType.Ntlm
        

    ,
    ReceiveTimeout = TimeSpan.FromMilliseconds(timeout)
;

问题:

因此,简而言之,我认为某些原因导致对任何域的所有传出请求都暂停或排队,从而导致观察到的行为。

我花了几天时间试图弄清楚发生了什么,但没有运气。有什么想法吗?

编辑

我认为正在发生的事情是请求被搁置,因为没有可用的线程,然后几百毫秒后线程可用并且任务开始。计时方法调用显示它需要 800 毫秒,但 HttpClient 上的超时不会开始,直到有线程可用于运行异步调用。

这也可以解释为什么我看到该方法需要 800+ 毫秒,但有时它仍然完成而没有显示超时异常。其他时候它会抛出一个超时异常并且没有完成。

我尝试在 Application_Start 中将 ServicePointManager.DefaultConnectionLimit 设置为 200,但这并没有解决问题。

与我们的其他服务相比,该服务没有占用那么多流量,而且其他服务似乎都没有同样的问题。

有什么想法吗?

编辑 2

我在进行(次要)负载测试时登录了该框并监控了 netstat。

使用 HttpClient,每秒有 1-2 个请求,端口将显示 ESTABLISHED,然后移动到 TIME_WAIT 大约 4 分钟。每秒有 3 个以上的请求,我最终会得到大约每秒 100 个恒定请求的 ESTABLISHED 端口(因此每秒 3 个负载测试为 300 个),然后我会开始看到它们转到 CLOSE_WAIT 而不是 TIME_WAIT - 表示错误关闭条件。同时,我会看到异常数量和执行请求的时间激增。 (TcpTimedWaitDelay 不适用于 CLOSE_WAIT)。

所以我重写了整个事情以串行使用 HttpWebRequests,而不是并行使用 HttpClient。然后我进行了同样的测试。

现在 ESTABLISHED 端口等于每秒 0-2 个请求,然后端口按预期移动到 TIME_CLOSE。性能和吞吐量有所提高,但并未完全清除。

然后我将 TcpTimedWaitDelay 设置为 30(默认为 24​​0)。性能显着提高。我有一个原始负载测试,每秒 40 个请求,没有任何问题。我会得到一个更彻底的测试设置,但我认为问题已经解决了。

我不知道发生了什么,但 HttpClient 似乎没有正确关闭下面的临时端口。我公司的许多开发人员和架构师都看过它,并且看不出代码有什么问题。我尝试在每个请求的 using 语句中使用一个 HttpClient,以及在后端调用的每个 api 使用一个 HttpClient。我尝试过并行和串行使用 HttpClient。我已经尝试过使用 async/await 和没有。无论我尝试什么行为都是一样的。

我希望能够使用 HttpClient,但我不能再花时间在这个问题上,因为我已经使用 HttpWebRequest。我的下一步是让 HttpWebRequests 并行发生。

感谢您的意见。

【问题讨论】:

您可能需要检查您的 WCF 设置以查看您的服务是否设置为允许多个并发请求。例如,如果您打开了节流,然后其中一个请求阻塞了很长时间,那么在它之后进来的所有请求都会等到它完成后才会被处理。 我删除了调用WCF服务的任务,行为是一样的。还有其他想法吗? 不要将awaitContinueWith 混用。只需使用等待。我会让你的代码更容易理解。 您的应用程序是什么类型的?客户端(WCF、WinForms、SL 等)还是服务器(ASP.NET、Windows 服务等)? 这是一个 WebApi 服务应用程序。 【参考方案1】:

我在使用 HttpClient 时也遇到过类似的挫败感。在我的场景中,我发现在 ServicePointManager 上将 MaxServicePointIdleTime 设置为低得多的值并将 DefaultConnectionLimit 设置为高值解决了我的问题。我相信在我的情况下,当连接保持打开状态时,我正在经历池饥饿。

您可能还想在没有附加调试器的情况下在发布时进行测试,如果您还没有这样做的话,因为 TaskScheduler 在调试时的行为会有所不同。

下面的 MSDN 文章很有帮助:http://blogs.msdn.com/b/jpsanders/archive/2009/05/20/understanding-maxservicepointidletime-and-defaultconnectionlimit.aspx

【讨论】:

以上是关于超时异常 - 请求排队?线程不够?的主要内容,如果未能解决你的问题,请参考以下文章

如何解决高并发,连接等待超时的异常

ExecutorService 使用 invokeAll 和超时异常后可调用线程上的超时未终止

调用第三方超时处理

java后台调用外部接口超时了怎么办

nginx中的超时设置,请求超时响应等待超时等

gRPC请求超时和异常处理