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

标签 c# asp.net rest dotnet-httpclient

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

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

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

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

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

没有任何效果。

试图检查是否有任何特定请求导致服务器挂起,但看起来不像那样。所有请求都返回 JSON,但返回 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 小时后)挂起

enter image description here

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

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

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

最佳答案

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 -> 明文用户:pwd

最后是 IISReset:

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

关于c# - REST Api 服务器挂起大量请求(使用 c# HttpClient),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43105569/

相关文章:

c# - 单独调用委托(delegate)?

c# - OData 服务不返回项目在数据库中的默认顺序

javascript - Webservice、JS、CSS 不能在 HTTPS(安全页面)上工作

asp.net - 如何从 HTTP 请求中获取 MAC 地址?

ios - Alamofire - 使用照片时图像上传失败

java - "JsonParseException: Unparseable date:"- 安卓错误

java - Binance Api 签名端点上的 Http Response 400

c# - MSCharts 和 .NET 3.5 SP1

C# MySQL 插入

c# - 如何处理任务并行库中的目录文件?