超时异常 - 请求排队?线程不够?
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(默认为 240)。性能显着提高。我有一个原始负载测试,每秒 40 个请求,没有任何问题。我会得到一个更彻底的测试设置,但我认为问题已经解决了。
我不知道发生了什么,但 HttpClient 似乎没有正确关闭下面的临时端口。我公司的许多开发人员和架构师都看过它,并且看不出代码有什么问题。我尝试在每个请求的 using 语句中使用一个 HttpClient,以及在后端调用的每个 api 使用一个 HttpClient。我尝试过并行和串行使用 HttpClient。我已经尝试过使用 async/await 和没有。无论我尝试什么行为都是一样的。
我希望能够使用 HttpClient,但我不能再花时间在这个问题上,因为我已经使用 HttpWebRequest。我的下一步是让 HttpWebRequests 并行发生。
感谢您的意见。
【问题讨论】:
您可能需要检查您的 WCF 设置以查看您的服务是否设置为允许多个并发请求。例如,如果您打开了节流,然后其中一个请求阻塞了很长时间,那么在它之后进来的所有请求都会等到它完成后才会被处理。 我删除了调用WCF服务的任务,行为是一样的。还有其他想法吗? 不要将await
与ContinueWith
混用。只需使用等待。我会让你的代码更容易理解。
您的应用程序是什么类型的?客户端(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
【讨论】:
以上是关于超时异常 - 请求排队?线程不够?的主要内容,如果未能解决你的问题,请参考以下文章