在加载测试期间,WCF在IIS性能问题中托管

时间:2014-11-10 19:58:07

标签: performance wcf iis threadpool scalability

摘要

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;}
}

测试结果非常难以预料,但总是有一个不好的数字:

first run

second run

这次运行我将执行时间从1秒改为5秒,只是为了证明差异(2倍,3倍等)是相对的,而不是绝对的。还要注意“热身”#39;帮助,但只是一点点。

3d run

评论

在我的所有测试期间(我做了大约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 ElementapplicationPool maxConcurrentRequestsPerCPU ElementThread Throttling in IIS-hosted WCFprocessModel ElementhttpRuntime Element,{{3 }}

我的理由(它应该如何工作):虽然我们有足够的内存用于每个IIS / WCF为每个请求创建一个新线程。例如,当我们有40个客户&#39;连接到服务器 - 在w3wp进程中将有大约45个线程(40个工作线程和一些其他用于良好测量)。因为我的所有客户都是&#39;在发送下一个请求之前等待响应,响应时间可能会略有不同(例如20-30%,不是2x甚至是我现在看到的10x)。

问题(正如我所见):

  1. 没有足够的线程(70个客户端只有54个线程)
  2. 有一些排队,因为已经有20个客户&#39;平均响应时间至少是实际逻辑的两倍
  3. 存在非确定性行为,因为结果与运行之间存在很大差异

1 个答案:

答案 0 :(得分:1)

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

如果您有所描述的行为,此解决方案将仅帮助您 (查看提供的链接以获取详细信息):您使用的线程数(线程数性能计数器)不扩展速度与您的要求一样快。知识库文章的图片: enter image description here

所以修复是使用其他方式来创建线程......什么是比使用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;
    }
}