WCF学习之:利用Throttling提高服务器性能

    如果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;
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 class MyResource
    
{
        
public Bitmap bitmap;
        
public Graphics graphics;
        
public bool usable;
        
public int resourceIndex;

        
public MyResource(int index)
        
{
            bitmap 
= new Bitmap(500500);
            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;
            }

        }

    }
ResourceCollection资源池的构建函数里按照容量添加资源对象
CatchResource()方法用于获取一个可用资源来响应请求
ReleaseResource(int index)方法释放一个资源,使它为其它请求服务

3、服务实现
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    
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), 00498498);
                Font drawFont 
= new Font("Arial"30);
                SolidBrush drawBrush 
= new SolidBrush(Color.Red);
                resource.graphics.DrawString(str, drawFont, drawBrush, 
100100);

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

    }
实例模式设为InstanceContextMode = InstanceContextMode.Single,保证只有一个服务实例,该实例是在服务Host的时候创建的。响应DrawImg(string str)请求的时候catch一个可用Graphic对象资源用来绘制,绘制完了把它Release释放掉。为了更好展现请求状态,在每次DrawImg时让线程等待3秒。

4、Host服务
using System;
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:
<?xml version="1.0" encoding="utf-8" ?>
<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>
注意,这里的<serviceThrottling maxConcurrentCalls="3"/>设置为跟服务实现里的resourceCollection = new ResourceCollection(3)的值一致,并发数和池大小相等才能保证每个请求都有可用的资源。
5、client调用服务
using System;
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);
        }

    }

}

创建10个线程模拟10个并发请求,把服务器绘制返回的图保存到本地。
Configuration:
<?xml version="1.0" encoding="utf-8" ?>
<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
posted on 2008-04-17 22:24  tloner  阅读(899)  评论(0编辑  收藏  举报