如果WCF服务用到服务器的稀缺资源,该资源的创建和销毁极耗服务器时间和性能,这种情况如果提高服务器性能呢?
我们知道WCF的实例模式有三种:Per-Call 、Per-Session 、Singleton。我们来分析一下:第一种Per-Call ,每一个Client Call都会一个服务实例和资源对象,这样的性能是无法忍受的;第二种Per-Session ,这种情况稍为改善了服务器性能,但是海量并发请求的时候比前者好不到哪去;第三种Singleton单件模式,保证只有一个服务实例和资源对象,但是并发访问时候会发生线程冲突。
在这种情况下,资源池派上了用场,把大操作对象(资源对象)放到一个池里面,这个池是启动服务之后提供服务之前创建好的,响应客户端请求时从池里获取一个可用的资源对象,请求响应过后释放该资源,使之响应下一个请求。
关键点在于:当并发请求数大于资源对象池大小时,如何排队等候。我们来看WCF是怎么处理这种情况的。WCF有三种Service Concurrency Mode:Single、Multiple、Reentrant。默认是Single,每次只能响应一个请求,其它请求被阻塞;Multiple服务实例是多线程的,所有并发请求立刻被响应,无同步保证,但是我们可以用Throttling节流措施来限制并发请求数,使并发数刚好等于资源池的大小,其它的请求排队等候,保证每个请求都有可用资源。看下面的例子:
以GDI+为例,我们创建Graphic绘图对象池,Client请求服务器给它绘制一幅图。
1、先设计一个服务契约:
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace Tloner.Throttling.WcfServiceLibrary1
{
// 注意: 如果更改此处的接口名称“IService1”,也必须更新 App.config 中对“IService1”的引用。
[ServiceContract]
public interface IService1
{
[OperationContract]
byte[] DrawImg(string str);
}
}
2、设计一个资源类和资源池
{
public Bitmap bitmap;
public Graphics graphics;
public bool usable;
public int resourceIndex;
public MyResource(int index)
{
bitmap = new Bitmap(500, 500);
graphics = Graphics.FromImage(bitmap);
usable = true;
resourceIndex = index;
}
}
public class ResourceCollection : List < MyResource >
{
public ResourceCollection(int capacity)
{
for (int i = 0; i < capacity; i++)
{
this.Add(new MyResource(i));
}
}
public MyResource CatchResource()
{
lock (this)
{
for (int i = 0; i < this.Count; i++)
{
if (this[i].usable == true)
{
this[i].usable = false;
Console.WriteLine("catch resource index:{0}", i);
return this[i];
}
}
}
return null;
}
public void ReleaseResource(int index)
{
lock (this)
{
Console.WriteLine("release resource index:{0}", index);
this[index].usable = true;
}
}
}
CatchResource()方法用于获取一个可用资源来响应请求
ReleaseResource(int index)方法释放一个资源,使它为其它请求服务
3、服务实现
public class Service1 : IService1
{
ResourceCollection resourceCollection;
public Service1()
{
resourceCollection = new ResourceCollection(3);
Console.WriteLine("create a ResourceCollection");
Console.WriteLine("create a service instance");
}
public byte[] DrawImg(string str)
{
MyResource resource = null;
MemoryStream stream = new MemoryStream(); ;
try
{
resource = resourceCollection.CatchResource();
System.Diagnostics.Debug.Assert(resource != null, "no free resource");
Console.WriteLine("using resource index:{0}", resource.resourceIndex);
resource.graphics.Clear(Color.White);
resource.graphics.DrawRectangle(new Pen(Color.Red), 0, 0, 498, 498);
Font drawFont = new Font("Arial", 30);
SolidBrush drawBrush = new SolidBrush(Color.Red);
resource.graphics.DrawString(str, drawFont, drawBrush, 100, 100);
resource.bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
Thread.Sleep(3000);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
resourceCollection.ReleaseResource(resource.resourceIndex);
}
return stream.GetBuffer();
}
~Service1()
{
Console.WriteLine("destroy a service instance");
}
}
4、Host服务
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using Tloner.Throttling.WcfServiceLibrary1;
namespace host
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost serivceHost = new ServiceHost(typeof(Service1)))
{
serivceHost.Opened += delegate
{
Console.WriteLine("Service has begun to listen ");
};
serivceHost.Open();
Console.Read();
}
}
}
}
< configuration >
< system.web >
< compilation debug = " true " />
</ system.web >
< system.serviceModel >
< services >
< service name = " Tloner.Throttling.WcfServiceLibrary1.Service1 " behaviorConfiguration = " WcfServiceLibrary1.Service1Behavior " >
< host >
< baseAddresses >
< add baseAddress = " http://localhost:8732/Design_Time_Addresses/WcfServiceLibrary1/Service1/ " />
</ baseAddresses >
</ host >
< endpoint address = "" binding = " basicHttpBinding " contract = " Tloner.Throttling.WcfServiceLibrary1.IService1 " >
< identity >
< dns value = " localhost " />
</ identity >
</ endpoint >
< endpoint address = " mex " binding = " mexHttpBinding " contract = " IMetadataExchange " />
</ service >
</ services >
< behaviors >
< serviceBehaviors >
< behavior name = " WcfServiceLibrary1.Service1Behavior " >
< serviceThrottling maxConcurrentCalls = " 3 " />
< serviceMetadata httpGetEnabled = " True " />
< serviceDebug includeExceptionDetailInFaults = " False " />
</ behavior >
</ serviceBehaviors >
</ behaviors >
</ system.serviceModel >
</ configuration >
5、client调用服务
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.ServiceModel;
using Tloner.Throttling.WcfServiceLibrary1;
using System.ServiceModel.Channels;
using System.Drawing;
using System.IO;
namespace client
{
class Program
{
static void Main(string[] args)
{
try
{
for (int i = 0; i < 10; i++)
{
ThreadStart ts = new ThreadStart(ClientCall);
Thread th = new Thread(ts);
th.Start();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.Read();
}
static void ClientCall()
{
using (ThrottlingClient client = new ThrottlingClient("throttling"))
{
byte[] data = client.DrawImg(Thread.CurrentThread.ManagedThreadId.ToString());
MemoryStream stream = new MemoryStream(data);
Bitmap bitmap = new Bitmap(stream);
bitmap.Save(@"c:\" + Thread.CurrentThread.ManagedThreadId.ToString() + ".png", System.Drawing.Imaging.ImageFormat.Png);
Console.WriteLine("CurrentThreadID: {0}", Thread.CurrentThread.ManagedThreadId.ToString());
}
}
}
class ThrottlingClient : ClientBase<IService1>, IService1
{
public ThrottlingClient()
: base()
{ }
public ThrottlingClient(string endpointConfigurationName)
: base(endpointConfigurationName)
{ }
public ThrottlingClient(Binding binding, EndpointAddress address)
: base(binding, address)
{ }
public byte[] DrawImg(string str)
{
return this.Channel.DrawImg(str);
}
}
}
Configuration:
< configuration >
< system.serviceModel >
< bindings >
< basicHttpBinding >
< binding name = " throttlingBinding "
sendTimeout = " 00:10:00 "
transferMode = " Streamed "
maxReceivedMessageSize = " 92233720368 " >
< readerQuotas maxDepth = " 32 " maxStringContentLength = " 8192 " maxArrayLength = " 963840000 "
maxBytesPerRead = " 4096 " maxNameTableCharCount = " 16384 " />
</ binding >
</ basicHttpBinding >
</ bindings >
< client >
< endpoint address = " http://localhost:8732/Design_Time_Addresses/WcfServiceLibrary1/Service1/ " binding = " basicHttpBinding " bindingConfiguration = " throttlingBinding "
contract = " Tloner.Throttling.WcfServiceLibrary1.IService1 " name = " throttling " />
</ client >
</ system.serviceModel >
</ configuration >
我们来看看运行结果:
我们可以看到每个资源都是catch-->using-->release,资源释放之前不会被cactht和using.
也许有考虑欠周到的地方,欢迎大家给点意见,共同研究
源码下载: download