实例上下文模式和并发模式的性能影响
Windows Communication Foundation (WCF) 应用程序的性能取决于许多不同的因素。应用程序设计、实例化模式、并发模式、传输、绑定设置和限制设置都会影响应用程序的性能。InstanceContextMode 和 ConcurrencyMode 设置彼此交互,对 WCF 应用程序性能会有很大影响。本主题讨论这两个设置如何影响 WCF 服务性能。在讨论这两个设置如何影响 WCF 之前,先简单说明一下其中每个设置的作用。
实例上下文模式
InstanceContextMode 控制在响应客户端调用时如何分配服务实例。InstanceContextMode 可以设置为以下值:
Single - 为所有客户端调用分配一个服务实例。
PerCall – 为每个客户端调用分配一个服务实例。
PerSession – 为每个客户端会话分配一个服务实例。
InstanceContextMode 的默认设置为 PerSession。
有关 InstanceContextMode 的更多信息,请参见:
InstanceContextMode 枚举
并发模式
ConcurrencyMode 控制如何一次允许多个线程进入服务。ConcurrencyMode 可以设置为以下值之一:
ConcurrencyMode 的默认设置为 Single。默认选择此值是为了让开发人员无需考虑同步问题。有关 ConcurrencyMode 的更多信息,请参见: ConcurrencyMode
实例上下文模式、并发模式和性能
InstanceContextMode 和 ConcurrencyMode 设置可以相互影响,因此了解它们如何影响性能时,必须同时查看这两个设置。例如,将 InstanceContextMode 设置为 PerCall 时,会忽略 ConcurrencyMode 设置。这是因为,每个客户端调用都将路由到新的服务实例,因此一次只会有一个线程在服务实例中运行。将 InstanceContextMode 设置为 Single 时,仅创建一个服务实例,因此 ConcurrencyMode 会影响应用程序的吞吐量。
将 InstanceContextMode 设置为 Single 的原因很多,其中包括:
创建服务实例需要大量的处理工作。当多个客户端访问服务时,仅允许创建一个服务实例可以降低所需处理量。
一个服务实例可以创建多个对象。将 ConcurrencyMode 设置为 Single 可以降低垃圾回收成本,因为不必为每个调用创建和销毁服务创建的对象。
通过将 ConcurrencyMode 设置为 Single,可以在多个客户端之间共享服务实例。
如果 ConcurrencyMode 设置为 Single 并且 InstanceContextMode 设置为 Single,则一次只允许一个客户端调用通过。如果有大量客户端,这可能会导致较大的瓶颈。
如果由服务对必须在线程之间共享的数据执行主要操作,并因此造成多个线程必须按顺序访问该数据,则将 ConcurrencyMode 设置为 Multiple,服务性能几乎不会改善,在极端情况下,服务性能甚至可能会降低。
将 InstanceContextMode 设置为 Single 并将 ConcurrencyMode 设置为 Reentrant 时的性能特征与将 ConcurrencyMode 设置为 Single 时相同。这是因为,一次仅允许一个客户端线程进入服务。
将 InstanceContextMode 设置为 PerCall 时,将为每个客户端调用创建一个新的服务实例,并在客户端调用完成时销毁该实例。由于服务实例仅在进行调用时可用,因此在调用完成时,会释放它们可能访问的所有资源。由于为每个调用分配一个新服务实例,因此会产生一些开销。但是,由于每个客户端调用都获取自己的服务实例,因此不存在同步问题。在发生大量客户端调用时,将创建大量的服务实例。遇此情形时,务必使服务实例仅分配其正常工作所需的那些资源。
如果 ConcurrencyMode 设置为 Multiple,则多个客户端调用可以通过,但开发人员需负责手动同步对共享数据的所有访问。这意味着,一次只有一个线程可以访问共享数据,从而导致访问共享数据的所有调用顺序排队等候。这违背了将 ConcurrencyMode 设置为 Multiple 的初衷。
在 InstanceContextMode 设置为 PerCall 时,ConcurrencyMode 设置对吞吐量没有影响。每个客户端调用都获取自己的服务实例,因此每个服务实例只有一个调用到其中的线程。 将 InstanceContextMode 设置为 PerSession 时,每个会话都获取自己的服务实例。有关会话的更多信息,请参见 Using Sessions(使用会话).使用 PerSession 时,必须使用支持会话的绑定。下表显示系统提供的哪些绑定支持会话。默认会话设置括在圆括号中。
绑定 | 会话(默认) |
---|---|
BasicHttpBinding |
(无) |
WSHttpBinding |
无、可靠会话、(安全会话) |
WSDualHttpBinding |
(可靠会话)、安全会话 |
WSFederationHttpBinding |
(无)、可靠会话、安全会话 |
NetTcpBinding |
(传输)、可靠会话、安全会话 |
NetNamedPipeBinding |
无、(传输) |
NetMsmqBinding |
(无)、传输 |
NetPeerTcpBinding |
(无) |
MsmqIntegrationBinding |
(无) |
BasicHttpContextBinding |
(无) |
NetTcpContextBinding |
(传输)、可靠会话、安全会话 |
WSHttpContextBinding |
无、可靠会话、(安全会话) |
有关哪些系统定义的绑定支持会话的更多信息,请参见系统提供的绑定和配置系统提供的绑定。在使用 PerSession 时,默认情况下每个客户端代理都获取自己的专用服务实例。来自该代理的所有调用都由同一服务实例处理。单个客户端可以创建两个不同的代理,在这种情况下将创建两个服务实例。两个代理可以共享同一会话。有关更多信息,请参见,请参见 InstanceContextSharing 示例。
如果 InstanceContextMode 设置为 PerSession 且 ConcurrencyMode 设置为 Single,则每个代理都获取自己的服务实例。由于一次仅允许一个线程进入服务实例,因此单个代理发出的对服务实例的所有并发调用将顺序排队等候。由于一次仅允许一个线程进入服务实例,因此在该线程退出之前,对同一代理发出的所有其他调用都将阻塞。如果有大量客户端同时发出调用,这将导致瓶颈。
如果 InstanceContextMode 设置为 PerSession 且 ConcurrencyMode 设置为 Multiple,则每个代理都获取自己的服务实例,且允许并发调用进入服务实例。因此,开发人员必须手动同步对共享数据的所有访问。同样,这会导致对共享数据的访问顺序排队等候,并降低性能,其原因与将 ConcurrencyMode 设置为 Single 并且对同一代理发出多个调用时相同。需要特别注意的是,仅当单一客户端异步执行其请求时,或者当多个客户端共享一个会话时,该服务才会同时处理多个调用。
对于轻型服务实例,使用 Single 与使用 PerCall 之间的吞吐量差异不是很大(约为 8%)。
性能数据和测试
用于收集本文档中所讨论的性能数据的计算机配置为:一台服务器和四台客户端计算机,它们通过两个 1 Gbps 以太网网络接口连接。服务器为四处理器的 AMD 64 2.2 GHz 计算机,运行 Windows Server 2008。每台客户端计算机都是双处理器 AMD 2.2GHz x86 计算机,运行的操作系统与服务器相同。系统 CPU 利用率保持在接近 100%。
四台客户端计算机用于向 WCF 服务发出调用。向服务发出的每个调用将 1 到 100 个对象传递给服务操作,具体取决于正在执行的测试。每个对象都包含一个订单、有关采购者的信息和要采购的项。
测试重点是 WCF 技术的服务器吞吐量。这定义为每秒完成的操作数。一个操作包括客户端向服务发送请求以及从服务接收答复。在此测试中,服务执行生成答复所需的最少量(可忽略不计)处理。 一个要了解的重点是,业务逻辑决定构造良好的 SOA 解决方案中的服务成本。通过省去服务上的业务逻辑处理,仅度量消息传递基础结构的成本。有些测试要求实现服务的类执行某些其他处理。对于这些测试,在服务类的构造函数中放置了一些逻辑,以生成一组质数。每个测试运行 3 次,然后计算吞吐量的平均值。
下面的代码演示由用于生成性能数据的 WCF 服务实现的接口。
[ServiceContract]
public interface ITestContract
{
[OperationContract]
Order[] GetOrders(int NumOrders);
}
[DataContract]
public class OrderLine
{
[DataMember]
public int ItemID;
[DataMember]
public int Quantity;
}
[DataContract]
public class Order
{
public Order()
{
}
[DataMember]
public int CustomerID;
[DataMember]
public string ShippingAddress1;
[DataMember]
public string ShippingAddress2;
[DataMember]
public string ShippingCity;
[DataMember]
public string ShippingState;
[DataMember]
public string ShippingZip;
[DataMember]
public string ShippingCountry;
[DataMember]
public string ShipType;
[DataMember]
public OrderLine[] orderItems;
[DataMember]
public string CreditCardType;
[DataMember]
public string CreditCardNumber;
[DataMember]
public DateTime CreditCardExpiration;
[DataMember]
public string CreditCardName;
}
}
下面的代码演示服务的实现。
[ServiceBehavior]
public class LightWeightService : ITestContract
{
static Order[] orders;
int upperLimit = 10000;
static bool CtorWork = false;
public LightWeightService()
{
if (CtorWork)
DoSomething();
}
void DoSomething()
{
// Some work done here
}
public static void Initialize(int messageSize)
{
orders = new Order[messageSize];
for (int i = 0; i < messageSize; i++)
{
Order order = new Order();
OrderLine[] lines = new OrderLine[2];
lines[0] = new OrderLine();
lines[0].ItemID = 1;
lines[0].Quantity = 10;
lines[1] = new OrderLine();
lines[1].ItemID = 2;
lines[1].Quantity = 5;
order.orderItems = lines;
order.CustomerID = 100;
order.ShippingAddress1 = "012345678901234567890123456789";
order.ShippingAddress2 = "012345678901234567890123456789";
order.ShippingCity = "0123456789";
order.ShippingState = "0123456789012345";
order.ShippingZip = "12345-1234";
order.ShippingCountry = "United States";
order.ShipType = "UPS";
order.CreditCardType = "VISA";
order.CreditCardNumber = "0123456789012345";
order.CreditCardExpiration = DateTime.UtcNow;
order.CreditCardName = "01234567890123456789";
orders[i] = order;
}
}
public Order[] GetOrders(int NumOrders)
{
return orders;
}
}
服务自承载在控制台应用程序中。下面的代码演示如何承载服务。
public static void Main(string[] args)
{
LightWeightService.Initialize(100);
WSHttpBinding binding = new WSHttpBinding(SecurityMode.None, false);
((WSHttpBinding)binding).MaxReceivedMessageSize = 2 * 1024 * 1024;
((WSHttpBinding)binding).ReaderQuotas.MaxArrayLength = 2 * 1024 * 1024;
ServiceHost serviceHost = new ServiceHost(typeof(LightWeightService));
ServiceBehaviorAttribute sba = serviceHost.Description.Behaviors.Find<ServiceBehaviorAttribute>();
if (sba == null)
{
sba = new ServiceBehaviorAttribute();
}
sba.ConcurrencyMode = ConcurrencyMode.Single;
sba.InstanceContextMode = InstanceContextMode.Single;
serviceHost.AddServiceEndpoint(typeof(ITestContract), binding, "https://localhost:8000/PerfTest");
serviceHost.Open();
Console.WriteLine("Service is running...");
Console.WriteLine("Press Enter to terminate service");
Console.ReadLine();
}
客户端应用程序也是一个控制台应用程序。下面的代码演示如何实现客户端。
static void Main(string[] args)
{
WSHttpBinding binding;
ITestContract proxy;
ChannelFactory<ITestContract> factory;
binding = new WSHttpBinding(SecurityMode.None, false);
factory = new ChannelFactory<ITestContract>(binding, new EndpointAddress("https://localhost:8000/PerfTest"));
proxy = factory.CreateChannel();
proxy.GetOrders(100);
}
下图说明在 InstanceContextMode 设置为 Single 或 PerCall 且 ConcurrencyMode 设置为 Single 时,对象数如何影响服务的吞吐量。
下图说明 InstanceContextMode 和 ConcurrencyMode 的各种设置组合对吞吐量的影响。