【问题标题】:REST Api Server Hangs On Large Volume of Requests (using c# HttpClient)REST Api 服务器挂起大量请求(使用 c# HttpClient)
【发布时间】:2017-03-29 22:50:31
【问题描述】:

我们最近升级到了 EMC AppXtender REST 服务 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 小时后)挂起

还附上刚刚挂起的服务器上的调试分析报告。

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

由于我们现在处于生产阶段,因此无法承受频繁的 IIS 重置。

【问题讨论】:

    标签: c# asp.net rest dotnet-httpclient


    【解决方案1】:

    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 
    

    另外,我强烈建议删除您的调试诊断共享或至少删除其中的敏感数据,然后再共享并更改帐户密码。

    提示:Basic Auth headers -> base64 -> 明文用户:密码

    最后 IISReset:

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

    【讨论】:

    • 谢谢先生!!修改代码以使用静态 HttpClient 并添加 semaphoreslim 到几个将数据写入 sql server 的方法,以便一个接一个地发送 db 请求。应用程序池挂起在一定程度上有所减少,但仍会发生。你认为simaphoreslim会有什么影响吗?还想提一下,我们有一个 webfarm(3 个服务器),请求被创建并发送到另一个服务器(单个)进行处理。这和挂机有关系吗?
    【解决方案2】:

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

    You're using HttpClient wrong - Simon Timms

    即使在 HttpClient 完成并被处理后,连接仍会在 TIME_WAIT 中保持打开状态,“等待查看是否有其他数据包进入,因为它们可能在网络上的某个地方被延迟了”。默认情况下,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;

    【讨论】:

    • 根据您建议的文章对代码 1 进行了这些更改。使您的 HttpClient 静态。 2. 不要在 using 中处理或包装 HttpClient ,除非您明确地寻找特定行为(例如导致您的服务失败)。仍然请求被挂起。
    • 您是否在所有等待调用中都使用了 ConfigureAwait(false)?请在此处查看 Stephen Cleary 的文章中的“避免上下文”部分blog.stephencleary.com/2012/02/async-and-await.html
    • 这是否意味着我们每次都可以使用新的HttpClient,但是将Connection设置为接近true?对于每个实例?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-31
    • 1970-01-01
    • 1970-01-01
    • 2021-05-23
    • 1970-01-01
    相关资源
    最近更新 更多