程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WCF後續之旅(10) 通過WCF Extension實現以對象池的方式創建Service Instance

WCF後續之旅(10) 通過WCF Extension實現以對象池的方式創建Service Instance

編輯:關於.NET

我們知道WCF有3種典型的對service instance進行實例化的方式,他們分別與WCF的三種InstanceContextMode相匹配,他們分別是PerCall,PerSession和Single。PerCall為每次service invocation創建一個新的service instance; 而PerSession則讓一個service instance處理來自通過各Session(一般是同一個proxy對象)的調用請求;而Single則是用同一個service instance來處理所有的調用請求。SOA的一個原則是創建無狀態的service(stateless service),PerCall應該是我們經常使用的實例化方式,盡管PerSession是默認的InstanceContextMode。

但是對於PerCall這種實例化方式來說,為每次service請求都創建新的service instance,有時候顯得有點極端,頻繁的對象創建會對系統的性能造成一定的影響。我們能夠以池的機制(Pooling)進行對象的獲取和創建呢:當service調用請求抵達service端,先試圖從池中獲取一個沒有被使用的service instance,如何找到,直接獲取該對象;否則創建新的對象。當service instance對象執行完畢,將對象釋放到池中以供下次service 調用使用。

1、實現原理

我們今天就來試著實現這樣的service instance提供機制。主要的實現原理是:讓所有的service實現一個公共的interface(IPooledObject),定義了IsBusy的屬性表明該對象當前是否正在被使用;為每個service type維護一個weak reference列表,每個weak reference對應一個確定的service instance,我們姑且將該weak reference列表成為該service type對應的對象池(object pool);為了處理service的調用需要提供一個確定的service instance的時候,遍歷對象池,通過weak reference的Target屬性找出一個可用的service instance(IsBusy=false)。如何順利找到這樣的service instance,則將其從對象池取出,將IsBusy屬性設為true;如何沒有找到,則通過反射創建一個新的service instance,將IsBusy設為true,同時利用weak reference將其包裝,並將該weak reference加入到對象池中,最後返回該service instance用於處理service 調用。當service 調用結束,不是直接將其dispose掉,而是將其釋放回對象池,供後續的service調用使用。

由於我們通過weak reference來實現對象池,weak reference引用的service instance是可以被GC回收的,這樣做的好處是充分利用的GC的垃圾回收功能,避免不需要的service instance常駐內容,帶來不必要的內存壓力。此外,正是因為weak reference引用的service instance是可以被GC回收,我們需要一個後台的任務定期地將已經被回收的weak reference清除掉。

和本系列前兩篇文章(WCF和Unity Appliation Block集成;WCF和Policy Injection Application Block集成)一樣,涉及的是service instance的提供的問題,所以,我們也是通過自定義InstanceProvider來實現以對象池的機制創建service instance的目的。

2、PooledInstnaceProvider的創建

在創建我們自定義的InstanceProvider之前,我們先來介紹幾個輔助的class:

I、IPooledObject

namespace Artech.WCFExtensions
{
  public interface IPooledObject
  {
    bool IsBusy
    { get; set; }
  }
}

由於我們要判斷service instance是否可用,我們讓所有的service type實現IPooledObject interface。IPooledObject 僅僅定義一個bool類型的屬性:IsBusy。通過該屬性判斷service instance是否正在被使用。

II、WeakReferenceCollection和WeakReferenceDictionary

namespace Artech.WCFExtensions
{
  public class WeakReferenceCollection:List<WeakReference>
  {}

  public class WeakReferenceDictionary : Dictionary<Type, WeakReferenceCollection>
  {}
}

WeakReferenceCollection僅僅是WeakReference的列表,WeakReferenceDictionary 則是key為Type,value為WeakReferenceCollection的dictionary。在提供service instance的時候,就是根據service type為key找到對應的WeakReferenceCollection。

III、PooledInstanceLocator

namespace Artech.WCFExtensions
{
  public static class PooledInstanceLocator
  {
    internal static WeakReferenceDictionary ServiceInstancePool
    { get; set; }

    static PooledInstanceLocator()
    {
      ServiceInstancePool = new WeakReferenceDictionary();
    }

    public static IPooledObject GetInstanceFromPool(Type serviceType)
    {
      if(!serviceType.GetInterfaces().Contains(typeof(IPooledObject)))
      {
        throw new InvalidCastException("InstanceType must implement Artech.WCFExtensions.IPooledInstance");
      }

      if (!ServiceInstancePool.ContainsKey(serviceType))
      {
        ServiceInstancePool[serviceType] = new WeakReferenceCollection();
      }

      WeakReferenceCollection instanceReferenceList = ServiceInstancePool[serviceType] ;

      lock (serviceType)
      {
        IPooledObject serviceInstance =null;
        foreach (WeakReference weakReference in instanceReferenceList)
        {
          serviceInstance = weakReference.Target as IPooledObject;
          if (serviceInstance != null && !serviceInstance.IsBusy)
          {
            serviceInstance.IsBusy = true;
            return serviceInstance;
          }
        }

        serviceInstance = Activator.CreateInstance(serviceType) as IPooledObject;
        serviceInstance.IsBusy = true;
        instanceReferenceList.Add(new WeakReference(serviceInstance));
        return serviceInstance;
      }
    }

    public static void Scavenge()
    {
      foreach (Type serviceType in ServiceInstancePool.Keys)
      {
        lock (serviceType)
        {
          WeakReferenceCollection instanceReferenceList = ServiceInstancePool[serviceType];
          for (int i = instanceReferenceList.Count - 1; i > -1; i--)
          {
            if (instanceReferenceList[i].Target == null)
            {
              instanceReferenceList.RemoveAt(i);
            }
          }

        }
      }
    }

    public static void ReleaseInstanceToPool(IPooledObject instance)
    {
      instance.IsBusy = false;
    }
  }
}

PooledInstanceLocator實現了3基於對象池的功能:從對象池中獲取對象(GetInstanceFromPool);將對象釋放到池中(ReleaseInstanceToPool);清理被GC回收的weak reference(Scavenge)。代碼很簡單,我就不再一一介紹了。有一點需要注意的,由於PooledInstanceLocator工作在一個多線程環境下,保證線程的同步時最重要的。在本例中為了簡便,我直接對service type對象進行枷鎖,由於本例比較簡單,不會引起什麼問題。在實際的項目開發中,如何對Type對象進行加鎖就需要三思了,因為type對象一個全局對象(可以參考的我的文章:What is type in managed heap),對其加鎖很容易引起死鎖。

IV、自定義InstanceProvider:PooledInstanceProvider

有了PooledInstanceLocator,我們的InstanceProvider就顯得很簡單了:

namespace Artech.WCFExtensions
{
  class PooledInstanceProvider:IInstanceProvider
  {
     #region IInstanceProvider Members

    public object GetInstance(InstanceContext instanceContext, Message message)
    {
      return PooledInstanceLocator.GetInstanceFromPool(instanceContext.Host.Description.ServiceType);
    }

    public object GetInstance(InstanceContext instanceContext)
    {
      return this.GetInstance(instanceContext, null);
    }

    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
      PooledInstanceLocator.ReleaseInstanceToPool(instance as IPooledObject);
    }

    #endregion
  }
}

在GetInstance和ReleaseInstance,直接調用調用PooledInstanceLocator的GetInstanceFromPool和ReleaseInstanceToPool。

V、為自定義InstanceProvider定義Behavior

對於使用自定義InstanceProvider,我們一般可以通過Contract Behavior和Endpoint Behavior來實現,由於定義Behavior不是本篇文章的重點,在這裡我僅僅通過Contract Behavior進行擴展這一種方式。

namespace Artech.WCFExtensions
{
  public class PooledInstanceBehaviorAttribute:Attribute,IContractBehavior,IContractBehaviorAttribute
  {

    #region IContractBehavior Members

    public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {}

    public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {}

    public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
      dispatchRuntime.InstanceProvider = new PooledInstanceProvider();
    }

    public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
    {}

    #endregion

    #region IContractBehaviorAttribute Members

    public Type TargetContract
    {
      get { return null; }
    }

    #endregion
  }
}

再ApplyDispatchBehavior中將DispatchRuntime 的InstanceProvider 設置成我們定義的PooledInstanceProvider就可以了。

3、將PooledInstanceProvider應用到WCF應用中

現在我們就創建一個簡單的WCF應用將看看我們自定義的InstanceProvider能給我們帶來什麼。我們照例創建如下圖一樣的4層結構:

I、Contract:Contracts.IService

namespace Contracts
{
  [ServiceContract]
  [PooledInstanceBehavior]
  public interface IService : IPooledObject
  {
    [OperationContract(IsOneWay =true)]
    void DoSomething();
  }
}

通過custom attribute的方式將PooledInstanceBehaviorAttribute應用到service conntract。Iservice繼承我們定義的IPooledObject interface。

II、Service:Services.Service

namespace Services
{

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Service:IService
  {
    static int count;

    public Service()
    {
      Interlocked.Increment(ref count);
      Console.WriteLine("{0}: Service instance is constructed!", count);
    }

#region IService Members

    public void DoSomething()
    {}

    #endregion

    #region IPooledInstance Members

    public bool IsBusy
    {get;set;}

    #endregion
  }
}

我們應用PerCall InstanceContextMode。由於我們需要檢測的是service instance的創建,所以我們通過下面的代碼判斷service instance創建的次數。

public Service()
{
   Interlocked.Increment(ref count);
   Console.WriteLine("{0}: Service instance is constructed!", count);
}

III、Hosting

namespace Hosting
{
  class Program
  {
    static Timer ScavengingTimer;

    static void Main(string[] args)
    {
      using (ServiceHost host = new ServiceHost(typeof(Service)))
      {
        host.Opened += delegate
        {
          Console.WriteLine("Service has been started up!");
        };

        host.Open();

        ScavengingTimer = new Timer(delegate
          {
            PooledInstanceLocator.Scavenge();
          }, null, 0, 5000);

        Console.Read();

      }
    }
  }
}

除了對service進行Host之外,Main()方法還通過一個Timer對象實現對對象池的清理工作(調用PooledInstanceLocator.Scavenge();),時間間隔是5s。

下面是configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Services.Service">
        <endpoint binding="basicHttpBinding" contract="Contracts.IService" />
        <host>
          <baseAddresses>
            <add baseAddress="http://127.0.0.1/service" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>

IV、Client:Clients.

namespace Clients
{
  class Program
  {
    static void Main(string[] args)
    {
      using (ChannelFactory<IService> channelFactory = new ChannelFactory<IService>("service"))
      {
        for (int i = 1; i <= 10; i++)
        {
          Console.WriteLine("{0}: invocate service!", i);
          channelFactory.CreateChannel().DoSomething();
          Thread.Sleep(1000);
        }
      }

      Console.Read();
    }
  }
}

在上面的代碼中,我們通過for循環進行了10次service調用。每次間隔1s.

我們看看運行的結果,這是client端的運行結果:

這是service端的結果:

可見service instance只創建了一次。因為方法執行太快,方法結束後service instance馬上釋放到對象池中,後續的調用一直使用的是同一個service instance。

然後我們把IService 的PooledInstanceBehavior注釋掉。

namespace Contracts
{
  [ServiceContract]
  //[PooledInstanceBehavior]
  public interface IService : IPooledObject
  {
    [OperationContract(IsOneWay =true)]
    void DoSomething();
  }
}

再次運行程序,service端將會得到下面的輸出結果:

可見在沒有運用PooledInstanceBehavior情況下,service instance的創建真正使“PerCall”。我們將PooledInstanceBehavior重新加上,然後通過在DoSomething方法中加上下面的代碼延長該方法執行的時間:

namespace Services
{
  [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
  public class Service:IService
  {

    #region IService Members

    public void DoSomething()
    {
      Thread.Sleep(2000);
    }

    #endregion
  }
}

再次運行程序,service端的運行結果如下圖所示

由於我們將DoSomething方法的執行延長至2s,在這種情況下,由於client端的service掉用的間隔是1s,所有當第二次service調用抵達之後,第一次創建的service instance還沒有被釋放,所以需要重新創建新的service instance。當第三次service調用時,第一個service instance已經釋放,以此類推,永遠只需要兩個service instance。這和上面的結果一致。

上面的運行結果都是在GC沒有進行垃圾回收的情況下的運行結果,如何GC參與了又會有怎樣的行為表現呢?在Hosting中,我們通過另一個Timer定期地進行垃圾回收(間隔為500ms):

namespace Hosting
{
  class Program
  {
    static Timer ScavengingTimer;
    static Timer GCTimer;
    static void Main(string[] args)
    {
      using (ServiceHost host = new ServiceHost(typeof(Service)))
      {
        host.Opened += delegate
        {
          Console.WriteLine("Service has been started up!");
        };

        host.Open();

        ScavengingTimer = new Timer(delegate
          {
            PooledInstanceLocator.Scavenge();
          }, null, 0, 5000);

        GCTimer = new Timer(delegate
          {
            GC.Collect();
          }, null, 0, 500);

        Console.Read();

      }
    }
  }
}

然後我們將serivice的DoSomething()操作執行時間縮短(比client調用service的間隔短:500ms),使得操作執行完畢後,還沒有新的請求抵達,這樣GC會將其垃圾回收。

namespace Services
{
  [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
  public class Service:IService
  {

    #region IService Members

    public void DoSomething()
    {
      Thread.Sleep(500);
    }

    #endregion
  }
}

那麼現在的輸出結果將會是這樣:

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved