REST Api 服务器因大量请求而挂起(使用 c# HttpClient)

REST Api Server Hangs On Large Volume of Requests (using c# HttpClient)

我们最近升级到了 EMC AppXtender REST Services 8.1。当我们在服务器上安装它时,它创建了一个虚拟目录 (AppXtenderRest)。

我们通过调用此服务器上可用的 REST 服务开发了我们的 Web 应用程序。

当我们在开发 REST 服务器时,从来没有挂过。 但是有一次当我们转向生产时它开始挂起。我们现在每 2-3 小时在此服务器上重新设置 IIS。

经过一些研究,我们在代码中采取了以下步骤。

  1. 将我们所有的代码转换为 async / await
  2. 将 HttpClient 超时设置为 30 分钟
  3. 在 REST 服务器上 no.of 工作进程从 1 增加到 4

没有任何效果。

试图检查是否有任何特定请求导致服务器挂起,但看起来不像那样。所有请求 return JSON 除了 returns Stream (Tiff/Pdf).

这是我们的 REST 服务调用的示例:

using (var client = CreateHttpClient())
{
    using (var response = await client.DeleteAsync(string.Format(RestUrls.deletedoc, DataSource, AppId, docId), GetCancelToken()))
    {
        if (response.IsSuccessStatusCode)
        {
            result = await response.Content.ReadAsStringAsync();
        }
        else
        {
            result = await response.Content.ReadAsStringAsync();
            throw new Exception(result);
        }
    }
}

同时附上服务器上的工作进程请求队列屏幕截图,显示请求在特定时间段后(2-3 小时后)挂起

同时附上挂起后从服务器获取的调试分析报告。

https://drive.google.com/open?id=0Bx6jnZk4gj2Ycmw2M1RKM3RiTzg

由于我们现在正在生产,无法承受频繁的 IIS 重置。

看看这篇关于端口耗尽的文章。它可能会解释你所看到的。我已经看到类似的情况,其中流量不是太大但延迟在增加 - 我相信是由于等待端口(未确认)

You're using HttpClient wrong - Simon Timms

即使在 HttpClient 完成并被释放后,连接仍会在 TIME_WAIT 'waiting to see if any additional packets come in on it because they might have been delayed on the network somewhere' 中保持打开状态。 Windows,默认情况下,将在此状态下保持连接 240 秒。 对此的答案是为您感兴趣的每个域设置一个 HttpClient,并且它们应该与您的应用程序一样长(具有适当的错误处理等)。

最近发现的一个问题是,在重新连接 HttpClient 之前,DNS 更改不会生效 (see here [byterot - aliostad])。 (我知道您已经看过这个,因为那篇文章就是我发现这个问题的地方!)对此有多种解决方案。

var client = new HttpClient(); client.DefaultRequestHeaders.ConnectionClose = true;

var sp = ServicePointManager.FindServicePoint(new Uri("http://foo.bar/baz/123?a=ab")); sp.ConnectionLeaseTimeout = 60*1000;

TLDR - http 客户端连接泄漏修复很好,但您的第一个问题是阻塞线程。另外,您刚刚暴露了敏感数据。也总是先从应用程序池回收而不是 iisreset 开始,以避免关闭整个服务器。

如上所述,您通过使用 using 包装 HTTPClient 泄漏了 TCP 连接,但您已修复该问题,因此这不是主要问题,尽管它仍然是一个等待下一次命中的缩放限制项。

此外,如果您要耗尽所有 TCP 端口,那会更明显地出现异常,而不是挂起。

查看 debugdiag analysis,您的问题似乎是同步 SQL 调用阻塞了 40% 的其他线程。 如果您最终让所有工作线程忙于等待其他阻塞线程,那么请求将排队产生挂起,直到请求队列已满并导致 503 服务不可用。

The following threads in w3wp.exe__AppXtender Rest Services__PID__12056__Date__03_28_2017__Time_09_58_36AM__83__Manual Dump.dmp are waiting to enter a .NET Lock


( 33 34 35 50 52 53 54 56 57 58 59 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 )

50.91% of threads blocked (56 threads)

据报道他们正在等待的线程是 55,它运行 SqlCommand.ExecuteReader

有一个 async version - ExecuteReaderAsync 您应该更改为(或让该组件的所有者更改)

Thread 55 - System ID 17820



Entry point   clr!Thread::intermediateThreadProc 
Create time   3/28/2017 9:51:46 AM 
Time spent in user mode   0 Days 00:00:00.421 
Time spent in kernel mode   0 Days 00:00:00.187 


This thread is waiting on data to be returned from the database server

The current executing command is : SELECT cfgid, cfgvalue FROM ae_cfg WHERE cfgid = 34 and the command timeout is set to 0 seconds. 

 The connection string for this connection : *** and the connection timeout : 15 seconds. 





.NET Call Stack




System_Data_ni!DomainNeutralILStubClass.IL_STUB_PInvoke(SNI_ConnWrapper*, SNI_Packet**, Int32)+84 
[[InlinedCallFrame] (.SNIReadSyncOverAsync)] .SNIReadSyncOverAsync(SNI_ConnWrapper*, SNI_Packet**, Int32) 
System_Data_ni!SNINativeMethodWrapper.SNIReadSyncOverAsync(System.Runtime.InteropServices.SafeHandle, IntPtr ByRef, Int32)+6a 
System_Data_ni!System.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync()+83 
System_Data_ni!System.Data.SqlClient.TdsParserStateObject.TryReadNetworkPacket()+7e 
System_Data_ni!System.Data.SqlClient.TdsParserStateObject.TryPrepareBuffer()+65 
System_Data_ni!System.Data.SqlClient.TdsParserStateObject.TryReadByte(Byte ByRef)+2e 
System_Data_ni!System.Data.SqlClient.TdsParser.TryRun(System.Data.SqlClient.RunBehavior, System.Data.SqlClient.SqlCommand, System.Data.SqlClient.SqlDataReader, System.Data.SqlClient.BulkCopySimpleResultSet, System.Data.SqlClient.TdsParserStateObject, Boolean ByRef)+292 
System_Data_ni!System.Data.SqlClient.SqlDataReader.TryConsumeMetaData()+5c 
System_Data_ni!System.Data.SqlClient.SqlDataReader.get_MetaData()+66 
System_Data_ni!System.Data.SqlClient.SqlCommand.FinishExecuteReader(System.Data.SqlClient.SqlDataReader, System.Data.SqlClient.RunBehavior, System.String)+11d 
System_Data_ni!System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(System.Data.CommandBehavior, System.Data.SqlClient.RunBehavior, Boolean, Boolean, Int32, System.Threading.Tasks.Task ByRef, Boolean, System.Data.SqlClient.SqlDataReader, Boolean)+ba0 
System_Data_ni!System.Data.SqlClient.SqlCommand.RunExecuteReader(System.Data.CommandBehavior, System.Data.SqlClient.RunBehavior, Boolean, System.String, System.Threading.Tasks.TaskCompletionSource`1, Int32, System.Threading.Tasks.Task ByRef, Boolean)+22a 
System_Data_ni!System.Data.SqlClient.SqlCommand.RunExecuteReader(System.Data.CommandBehavior, System.Data.SqlClient.RunBehavior, Boolean, System.String)+62 
System_Data_ni!System.Data.SqlClient.SqlCommand.ExecuteReader(System.Data.CommandBehavior, System.String)+ca 
XtenderSolutions.UtilityLibrary.General.DbCommon.GetStringTypeFromDB(XtenderSolutions.Administration.Database.DbCommonEx)+1aa 
XtenderSolutions.UtilityLibrary.General.DbCommon.Open()+11c 
XtenderSolutions.CMData.CMConnection.Open()+a7 
XtenderSolutions.CMData.CMCfgMgr.Load(XtenderSolutions.CMData.CMConnection, Int16)+55 
XtenderSolutions.CMData.CMConnection.InitEAIHooks()+4f 
XtenderSolutions.CMData.CMConnection.Init(System.String)+595 
XtenderSolutions.CMData.CMConnection..ctor(XtenderSolutions.CMData.CMSession, System.String)+17b 
XtenderSolutions.CMData.CMSession.get_Connection()+7e 
XtenderSolutions.CMData.CMSession.Login(XtenderSolutions.Configuration.DataSourceConfig, System.String, System.String, System.Security.Principal.WindowsIdentity, System.String, Boolean)+46e 

此外,我会强烈建议删除您的 debugdiag 共享或至少在共享之前从中删除敏感数据并更改帐户密码。

提示:Basic Auth headers -> base64 -> 明文 user:pwd

最后 IIS 重置:

如果您还没有处于 http.sys 请求队列填满的阶段,您还可以尝试应用程序池回收,这会为您提供一个新的 w3wp.exe 工作进程甚至池 stop/start 因为你真的不想等待当前请求继续挂起。池回收比关闭整个 IIS 服务器的侵入性更小。 但是一旦 http.sys 队列中有很多请求,您可能最终需要 iisreset。我总是尽量避免 iisreset 特别是如果该主机上还有其他站点/虚拟目录... 您可以监控 IIS perf counters 并据此做出决定