【问题标题】:WCF hosted in IIS peformance issue during load test负载测试期间托管在 IIS 性能问题中的 WCF
【发布时间】:2015-01-07 05:43:49
【问题描述】:

总结

托管在 IIS 中的 WCF 服务只有 1 个方法,需要 1 秒才能完成(示例代码中的 Thread.Sleep)。当 5 个“客户端”用请求敲击服务器时,平均响应时间为 1 秒,10 个客户端 - 大约 2 秒,而 20 个客户端的性能下降到最低水平。我尝试了所有类型的设置,但没有任何帮助。

我认为问题出在 WCF 中(MS 试图使其具有故障安全性,并为开发人员提供了很多保护措施):它试图使用尽可能少的线程,因此性能受到影响。

详细说明

代码

我在 IIS 中托管了一个非常简单的 WCF 服务:

using System.IO;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Threading;

namespace WCFPerf
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        [WebInvoke(Method = "POST",
            ResponseFormat = WebMessageFormat.Xml,
            RequestFormat = WebMessageFormat.Xml,
            BodyStyle = WebMessageBodyStyle.Bare,
            UriTemplate = "Test")]
        Stream DoWork(Stream s);
    }

    public class Service1 : IService1
    {

        public Stream DoWork(Stream s)
        {
            Thread.Sleep(1000); // simulate work
            return s;
        }
    }
}

配置文件:

<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
    <httpRuntime />
  </system.web>
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="ServiceBehavior" name="WCFPerf.Service1">
        <endpoint address="" behaviorConfiguration="web" binding="customBinding" bindingConfiguration="" contract="WCFPerf.IService1" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:57676/Service1.svc" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <bindings>
      <customBinding>
        <binding closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00">
          <webMessageEncoding />
          <httpTransport manualAddressing="true" />
        </binding>
      </customBinding>
    </bindings>
    <behaviors>
      <endpointBehaviors>
        <behavior name="web">
          <webHttp />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="ServiceBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceThrottling maxConcurrentCalls="200" maxConcurrentSessions="200" maxConcurrentInstances="200" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
  <system.net>
    <connectionManagement>
      <add address="*" maxconnection="2000" />
    </connectionManagement>
  </system.net>
</configuration>

测试服:

void Main()
{
    var numberOfThreads = new[] {5, 10, 20, 30};
    var table = new List<ResultInfo>();

    foreach (var threadNumber in numberOfThreads)
    {   
        var tasks = new List<Task<List<double>>>();
        for (var i = 0; i < threadNumber; i++)
        {
            tasks.Add(Task<List<double>>.Factory.StartNew(() => 
            {
                var results = new List<double>();
                for (var j = 0; j < 5; j++)
                {
                    results.Add(SendRequest());
                }
                return results;
            }));
        }
        Task.WaitAll(tasks.ToArray());
        var allResults = tasks.SelectMany(t => t.Result);
        table.Add(new ResultInfo{ Threads = threadNumber, Avg = allResults.Average().ToString("F2"), Min = allResults.Min().ToString("F2"), Max = allResults.Max().ToString("F2") });
    }
    table.Dump();
}

public double SendRequest()
{
    var stopwatch = new Stopwatch();
    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri(@"http://localhost:8081/");
        stopwatch.Start();
        var t = client.PostAsync("WCFPerf/Service1.svc/Test", new StringContent("123")).Result;
        stopwatch.Stop();
        return stopwatch.Elapsed.TotalSeconds;
    }
}

public class ResultInfo
{
    public int Threads {get;set;}
    public string Avg {get;set;}
    public string Min {get;set;}
    public string Max {get;set;}
}

测试结果很不可预测,但总有一个不好的数字:

这次运行我将执行时间从 1 秒更改为 5 秒,只是为了证明差异(2 倍、3 倍等)是相对的,而不是绝对的。另请注意,“热身”会有所帮助,但只是一点点。

评论

在我所有的测试期间(我做了大约 100 次不同类型的运行),Windows 任务管理器在 w3wp 进程中报告的最大线程数为 54。我使用的是具有 2 个物理内核和 4 个逻辑内核的 Core i3 CPU,12 Gb RAM,在 Windows 8.1 和 .Net 4.5 下运行。进程的内存打印总是增长,但非常缓慢,最高值在 110 Mb 左右。

dotTrace 报告说,大约 7% 的时间花在我的代码和其他所有内容上 - 在系统代码中。

我已尝试执行以下操作:

在C:\Windows\Microsoft.NET\Framework\v4.0.30319\Aspnet.config:

  <system.web>
    <applicationPool maxConcurrentRequestsPerCPU="5000" maxConcurrentThreadsPerCPU="0" requestQueueLimit="5000"/>
  </system.web>

在C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\machine.config

  <system.net>
    <connectionManagement>
        <add address = "*" maxconnection = "400" />
    </connectionManagement>
  </system.net>
  <system.web>
    <processModel autoConfig="false" maxWorkerThreads="80" maxIoThreads="80" />
    <httpRuntime minFreeThreads="10" minLocalRequestFreeThreads="10" />

链接:Web Settings Schema、 Element、applicationPool maxConcurrentRequestsPerCPU、 Element、Thread Throttling in IIS-hosted WCF、processModel Element、httpRuntime Element、ASP Registry Entries (IIS 6.0)

想法

我的理由(它应该如何工作):虽然我们有足够的内存供每个 IIS/WCF 使用,但会为每个请求创建一个新线程。例如,当我们有 40 个“客户端”连接到服务器时 - w3wp 进程中将有大约 45 个线程(40 个工作线程和一些其他线程用于很好的衡量)。因为我所有的“客户”在发送下一个请求之前都在等待响应,所以响应时间可能会有一点差异(比如 20-30%,而不是我现在看到的 2 倍甚至 10 倍)。

问题(如我所见):

  1. 线程不足(70 个客户端只有 54 个线程)
  2. 存在某种队列,因为已经有 20 个“客户”,平均响应时间至少比实际逻辑长两倍
  3. 存在不确定的行为,因为每次运行的结果差异很大

【问题讨论】:

  • 请务必注意,客户端 Windows 版本(如 Windows 7 或 Windows 8.1)存在限制。这样做是为了防止将客户端系统用于服务器的东西。因此,要获得任何合理的负载测试性能,您需要添加服务器(如 Windows Server 2008 R2 或 Windows Server 2012)

标签: performance wcf iis threadpool scalability


【解决方案1】:

我在kb aritcle number 2538826 中找到了答案,示例代码也可以在this blog post 中找到(还有一些附加信息,例如如何添加性能计数器和编写负载测试)。

此解决方案仅在您具有所描述的行为(查看提供的链接以了解详细信息)时为您提供帮助:您使用的线程数(线程计数性能计数器)不根据您的要求快速扩展。图片来自知识库文章:

所以修复是使用其他方式来创建线程...还有什么比使用 ThreadPool 更好的方式?

你需要这两个类:

public class WorkerThreadPoolSynchronizer : SynchronizationContext
{
    public override void Post(SendOrPostCallback d, object state)
    {
        // WCF almost always uses Post
        ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
    }

    public override void Send(SendOrPostCallback d, object state)
    {
        // Only the peer channel in WCF uses Send
        d(state);
    }
}

和

[AttributeUsage(AttributeTargets.Class)]
public class WorkerThreadPoolBehaviorAttribute : Attribute, IContractBehavior
{
    private static WorkerThreadPoolSynchronizer synchronizer = new WorkerThreadPoolSynchronizer();

    void IContractBehavior.AddBindingParameters(
        ContractDescription contractDescription, 
        ServiceEndpoint endpoint, 
        BindingParameterCollection bindingParameters)
    {
    }

    void IContractBehavior.ApplyClientBehavior(
        ContractDescription contractDescription, 
        ServiceEndpoint endpoint, 
        ClientRuntime clientRuntime)
    {
    }

    void IContractBehavior.ApplyDispatchBehavior(
        ContractDescription contractDescription, 
        ServiceEndpoint endpoint, 
        DispatchRuntime dispatchRuntime)
    {
        dispatchRuntime.SynchronizationContext = synchronizer;
    }

    void IContractBehavior.Validate(
        ContractDescription contractDescription, 
        ServiceEndpoint endpoint)
    {
    }

}

然后将属性应用到您的服务:

[WorkerThreadPoolBehavior] // this is what changed
public class Service1 : IService1
{

    public Stream DoWork(Stream s)
    {
        Thread.Sleep(1000); // simulate work
        return s;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-14
    • 1970-01-01
    • 1970-01-01
    • 2019-12-30
    • 1970-01-01
    • 2012-03-05
    • 1970-01-01
    • 2011-07-26
    相关资源
    最近更新 更多