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倍等)是相对的,而不是绝对的。还要注意“热身”#39;帮助,但只是一点点。
在我的所有测试期间(我做了大约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,{{3 }}
我的理由(它应该如何工作):虽然我们有足够的内存用于每个IIS / WCF为每个请求创建一个新线程。例如,当我们有40个客户&#39;连接到服务器 - 在w3wp进程中将有大约45个线程(40个工作线程和一些其他用于良好测量)。因为我的所有客户都是&#39;在发送下一个请求之前等待响应,响应时间可能会略有不同(例如20-30%,不是2x甚至是我现在看到的10x)。
答案 0 :(得分:1)
我在kb aritcle number 2538826找到了答案,示例代码也可以在this blog post中找到(有一些其他信息,比如如何添加perf计数器和写入负载测试)。
如果您有所描述的行为,此解决方案将仅帮助您 (查看提供的链接以获取详细信息):您使用的线程数(线程数性能计数器)不扩展速度与您的要求一样快。知识库文章的图片:
所以修复是使用其他方式来创建线程......什么是比使用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;
}
}