享受代码,享受人生

SOA is an integration solution. SOA is message oriented first.
The Key character of SOA is loosely coupled. SOA is enriched
by creating composite apps.
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Distributed Application --- Applying Remoting & Enterprise Service

Posted on 2006-09-08 19:43  idior  阅读(18676)  评论(7编辑  收藏  举报

Start with Database Connection Pool一文中把DAL从Client转移到了Server,从而获得更好的scalability。此时我们迈出了分布式的第一步,如果更进一步,把BLL也分布到Server上,对我们又将产生怎样的影响呢?

 

         CS3 

 

从图中可以看到一个显而易见的优点,Client非常的Thin,这样的结构特别适合于那种Client端处理能力弱的应用,比如最近很流行的Mobile应用。把复杂的业务逻辑全部交由Server端完成,Client仅仅通过远程接口传入参数并得到结果,这样Client的所需计算能力将非常的小。虽然这也算是分布式带来的好处,但是在本系列中关注的主要不是这一方面的内容。

其实将BLL分布到Server上,也会产生一个明显的缺点,原来的UIL对BLL的访问全部由Local Call变成了Remote Call, 本地访问和远程访问的性能可不是一个数量级,应用的性能将大打折扣,那么是什么原因使得我们仍然想把BLL分布到其他机器上呢?

在继续本话题的讨论之前,先要了解一个在分布式应用的重要概念--- Stateless。

如果你对J2EE有一些了解的话,你肯定听说过session beans和entity bean这两个概念(还有一个用于异步通讯的message bean)。而在所有介绍j2ee的书籍中都对Stateless Session Bean大为推崇,entity bean基本上都是被批判的角色(也是导致O/R M工具的兴起原因之一)。 那么Stateless的具体含义是什么呢?Stateless 和Stateful的区别又在何处?

Stateless从字面上是说对象没有状态。何谓状态?说白了就是对象中的成员变量。那么Stateless是说得对象没有成员变量,只有成员函数吗? 答案是否定的。这里的状态是争对object的使用者而言。任意一个客户对object的任意一次方法调用都不受之前调用的影响,也就是说每次调用object方法都是相互独立行为,彼此不产生任何影响,这样object称之为Stateless object。 这里的状态指的是对象的使用者的状态,没有状态即对象中不保存特定的某个对象的使用者的状态。 那么我们为什么要定义出这么一种类型的对象,而使它与其他对象区别开来呢?

对object方法的调用相互独立,可以想象该类型的对象可以服务于任何一个客户,服务于任何一个请求。Client A可以使用它来完成工作,Client B同样可以使用它, Client A在时间T1使用它,也可以在T2时使用它来完成工作。同一个对象可以在不同的时间服务于多个客户,这就是Stateless 对象所具有的最大优势。

那么使用Stateless object究竟能给我带来什么好处。在C/S结构中如果BLL和UIL部署在一起,那么使用Stateless 或者Stateful对象效果完全一样,因为他们都处于Client端,完全不能被其他Client重用。当BLL被部署在Server端时,Stateless对象和Stateful对象的差别就大了,Stateless对象可以被任意一个Client重用。和DB Connection Pool类似,一个Stateless对象可以为多个Client提供服务。

但是以上仅仅证明了Stateless对象在分布式的应用中优于Stateful对象,并没有很好的解释为什么要将BLL分布。

确实在C/S应用中,将BLL分布仅仅为Client减负(计算能力和内存),但是由于远程调用的反而使得应用的整体性能下降,并且将BLL分布也并未获得更强的Scalability(方便的支持更多的用户)。

但是如果将应用背景转移到B/S结构的应用中,分布BLL的优势就凸现出来了。

    BS2

 

如上图所示, UIL和BLL一起部署在Web Server中,作为Web Server的服务器,一台机器的负载是有限的,当并发Client数量激增时,Web Server将受限于有限的计算能力和存储空间,而无法满足越来越多的Client的服务请求。

而当我们把BLL从Web Server中分布到Application Server后,由于设计时考虑主要使用Stateless 的业务对象充当提供服务的Facade,此时为了满足越来越多的Client请求,一方面我们可以使用Stateless object pool来减少所需创建的对象数量,另一方面可以对分布出来的Application Server做集群,而Stateless 的业务对象做集群时不需要考虑状态的复制,这样就可以很好的满足系统对Scalability的要求。        

    BS1    

     BS3

 

上述的Stateless 业务对象,在EJB中对应了SLSB(Stateless Session Bean)的概念。EJB作为应用服务器,为SLSB提供了大量的服务,如远程调用,实例池,资源池,线程管理,声明性事务,集群等等。而在.Net的世界,就需要依靠COM,DCOM,Remoting以及EnterpriseService来帮助我们实现同样的目的。下面将通过一个小例子来展示这些功能,主要集中在远程调用,实例池,资源池。

分布式中最基础的一个功能就是实现远程调用,既然对象被分布了,那么首先你必须还能访问到它。如果能象本地对象那样访问远程对象,那就更方便了。我想搞.Net的或多或少都听说过Remoting这个概念,不过真正用到的人可能不多,大家可能觉得它比较神秘,其实这项技术就是用于实现远程方法调用的,是为分布式服务的。

何时采用Remoting?

当你需要访问远程对象(跨应用域,跨进程,跨机器)时,并且应用的两端都是采用的.Net环境,即.Net---.Net。

限于篇幅,在此不准备对Remoting做详细介绍,本文假设读者已经对Remoting有一定了解。

如果说EJB中的SLSB,是Stateless Object在Java下的具体实现的话,那么在.Net下,与之比较相近的就是Remoting中的SAO(Server Activation Object)。不过Remoting提供的服务和EJB可不能相提并论,可以说Remoting仅仅提供了远程调用的功能,不过结合EnterpriseService,在.Net的环境中你也可以享受到EJB容器所提供的诸多服务。

下面,让我们从一个Remoting的例子开始.Net分布式应用的演示。

 

    1     public interface IGetInfo

    2     {

    3         string GetInfo();

    4     }

 

 

 

   1     public class SimpleService : MarshalByRefObject, IGetInfo, IDisposable

    2     {

    3         private bool alreadyDisposed = false;

    4         private string id;

    5         public SimpleService()

    6         {

    7             id = Guid.NewGuid().ToString().Substring(20);

    8             string logMsg = string.Format("Construct Object: {0}",

    9                                         id);

   10             Console.WriteLine("SimpleService.Construct {0}", logMsg);

   11         }

   12 

   13         public string GetInfo()

   14         {

   15             Thread.Sleep(1000);

   16             return "i am simpleservice";

   17         }

   18 

   19         ~SimpleService()

   20         {

   21             Console.WriteLine(string.Format("SimpleService.Finalize {0}",id));

   22             Dispose(false);

   23         }

   24 

   25         void IDisposable.Dispose()

   26         {

   27             Dispose(true);

   28             GC.SuppressFinalize(true);

   29         }

   30 

   31         protected virtual void Dispose(bool isDisposing)

   32         {

   33             // Don't dispose more than once.

   34             if (alreadyDisposed)

   35                 return;

   36             if (isDisposing)

   37             {

   38                 // TODO: free managed resources here.

   39             }

   40             Console.WriteLine(string.Format("Disposing unmanaged resources {0}",id));

   41 

   42             // TODO: free unmanaged resources here.

   43             // Set disposed flag:

   44             alreadyDisposed = true;

   45         }

   46     }

 

 

服务端提供了一个GetInfo的方法供客户端使用,注意SimpleService实现了IDisposable接口,之所以实现它是用于演示Remoting对对象生命周期的管理。

如前所述,我们希望使用Stateless object pool来减少所需创建的对象数量,但是Remoting本身仅仅提供了远程访问的能力,它并没有支持对象池。Remoting对Stateless object 的生命周期的管理主要有Singleton和SingleCall两者,并且通常情况下会使用SingCall方式,即Client每来一个请求,Server创建出一个新的对象,请求被处理后,销毁对象。

     RemotingSingleCall

具体过程如图所示,Client在对远程对象发出请求前,对象并不存在,当Client请求到达Server后,Server创建出相应的对象,然后利用它处理请求,请求处理完之后,返回结果并立即Dispose对象,在.Net Framework下一次垃圾回收时再彻底销毁对象。由于当前虚拟机创建销毁对象的效率已经相当高,当创建对象所需资源不多(不耗过多时间,不占过多内存)时,利用该方法已经可以很好的满足需求。

下面是Server端和Client端使用Remoting来完成请求的实例代码及结果

 

    1     class Server

    2     {

    3         static void Main()

    4         {

    5             // Create a channel specifying the port #

    6             HttpChannel channel = new HttpChannel(13101);

    7             // Register the channel with the runtime remoting services

    8             ChannelServices.RegisterChannel(channel, false);

    9 

   10             // Register a type as a well-known type

   11             RemotingConfiguration.RegisterWellKnownServiceType(

   12                typeof(SimpleService), // The type to register

   13                "SimpleService.soap",                   // The well-known name

   14                WellKnownObjectMode.SingleCall   // SingleCall or Singleton

   15             );

   16 

   17             // Keep the server alive until Enter is pressed.

   18             Console.WriteLine("Server started. Press Enter to end");

   19             Console.ReadLine();

   20         }

   21     }

 

    1     class Client

    2     {

    3         static void Main(string[] args)

    4         {

    5             // Create and register the channel. The default channel ctor

    6             // does not open a port, so we can't use this to receive messages.

    7             HttpChannel channel = new HttpChannel();

    8             ChannelServices.RegisterChannel(channel, false);

    9 

   10             // Get a proxy to the remote object

   11             object remoteObj = Activator.GetObject(

   12                 typeof (IGetInfo),

   13                 "http://localhost:13101/SimpleService.soap"

   14                 );

   15             IGetInfo getInfoSvc = remoteObj as IGetInfo;

   16             string info = getInfoSvc.GetInfo();

   17             Console.WriteLine(info);

   18 

   19             //Test for multiple creat and dispose object

   20             getInfoSvc.GetInfo();

   21             getInfoSvc.GetInfo();

   22             getInfoSvc.GetInfo();

   23 

   24             Console.ReadLine();

   25         }

   26     }

 

    SingleCallConsole 

 

可以看出运行结果和文中描述的是一致的。

.Net世界的开发者总是盼望着什么时候也有一个象EJB那样的容器,确实EJB为java开发者提供了很多方便的服务,但是如果你仔细研究.Net下的COM,DCOM,EnterpriseService,MSMQ,你会发现.Net的世界同样精彩。至少EJB为Stateless Session Bean提供的服务,.Net大多也提供了方便支持,如远程调用,实例池,资源池,声明性事务。下面将介绍一下如何用ES来实现实例池,其他内容限于篇幅可能在以后做进一步介绍。

远程调用这部分的使用方法仍旧采用Remoting,代码不需要做任何改变。为了支持对象池,主要修改服务的实现,不过改动也是非常的小。下面是新的SimpleService 的实现代码:

 

    1     [ClassInterface(ClassInterfaceType.None)]

    2     [ComVisible(true)]

    3     [JustInTimeActivation()]

    4     [ObjectPooling(3, 3)]

    5     public class SimpleService : ServicedComponent, IGetInfo, IDisposable

    6     {

    7         private bool alreadyDisposed = false;

    8         private string id;

    9 

   10         // Serviced components require a default constructor.

   11         public SimpleService()

   12         {

   13             id = Guid.NewGuid().ToString().Substring(20);

   14             string logMsg = string.Format("Construct Object: {0}",

   15                                         id);

   16             Console.WriteLine("SimpleService.Construct {0}", logMsg);

   17         }

   18 

   19         public string GetInfo()

   20         {

   21             Thread.Sleep(1000);

   22             ContextUtil.DeactivateOnReturn = true;

   23             return "i am simpleservice using object pool";

   24         }

   25 

   26         protected override void Activate()

   27         {

   28             string logMsg = string.Format("Activated Object: {0}",

   29                                         id);

   30             Console.WriteLine("SimpleService.Activate {0}", logMsg);

   31         }

   32 

   33         protected override void Deactivate()

   34         {

   35             string logMsg = string.Format("Deactivated Object: {0}",

   36                                         id);

   37             Console.WriteLine("SimpleService.Deactivate {0}", logMsg);

   38         }

   39 

   40         protected override bool CanBePooled()

   41         {

   42             return true;

   43         }

   44 

   45         // finalizer:

   46         // Call the virtual Dispose method.

   47         ~SimpleService()

   48         {

   49             Console.WriteLine("SimpleService.Finalize");

   50             Dispose(false);

   51         }

   52 

   53         // Implementation of IDisposable.

   54         // Call the virtual Dispose method.

   55         // Suppress Finalization.

   56         void IDisposable.Dispose()

   57         {

   58             Dispose(true);

   59             GC.SuppressFinalize(true);

   60         }

   61 

   62         // Virtual Dispose method

   63         protected new virtual void Dispose(bool isDisposing)

   64         {

   65             // Don't dispose more than once.

   66             if (alreadyDisposed)

   67                 return;

   68             if (isDisposing)

   69             {

   70                 // TODO: free managed resources here.

   71             }

   72             Console.WriteLine("Disposing unmanaged resources");

   73 

   74             // TODO: free unmanaged resources here.

   75             // Set disposed flag:

   76             alreadyDisposed = true;

   77         }

   78     }


我们通过Attribute使用了JustInTimeActivation和ObjectPooling两个COM+服务,其中设定了ObjectPool的大小为3个对象。客户端和服务器端使用Remoting的代码不变,运行结果如下:

     objectpoolConsole

 

可以看出对象的创建是在调用方法时发生的,而不是一开始就在池中连续创建3个对象。并且可以发现尽管Client发出了4次请求,但是总共创建了3个对象,并仅仅使用了2个。处理请求的时候Activate对象,处理完后Deactivate。最后三次请求都是使用的同一个对象,那是因为在示例中的请求是顺序的,而不是并发的。下面用多线程模拟一下多客户并发请求时,对象池的使用情况。

    1     class Client

    2     {

    3         delegate string GetInfoDelegate();

    4         static void Main(string[] args)

    5         {

    6             // Create and register the channel. The default channel ctor

    7             // does not open a port, so we can't use this to receive messages.

    8             HttpChannel channel = new HttpChannel();

    9             ChannelServices.RegisterChannel(channel, false);

   10 

   11             // Get a proxy to the remote object

   12             object remoteObj = Activator.GetObject(

   13                 typeof (IGetInfo),

   14                 "http://localhost:13101/SimpleService.soap"

   15                 );

   16             IGetInfo getInfoSvc = remoteObj as IGetInfo;

   17             string info = getInfoSvc.GetInfo();

   18             Console.WriteLine(info);

   19 

   20             //Test for concurrent invoke

   21             GetInfoDelegate getInfoHandle = new GetInfoDelegate(getInfoSvc.GetInfo);

   22             getInfoHandle.BeginInvoke(null, null);

   23             getInfoHandle.BeginInvoke(null, null);

   24             getInfoHandle.BeginInvoke(null, null);

   25 

   26             Console.ReadLine();

   27         }

   28     }

 

     objectpoolConsole2 


从中可以看出最后三次调用分别使用了不同的对象。 



总结:

本文主要介绍了Stateless Object的概念,并提出将BLL分布,通过对Application Server做Cluster,以获得更好的Scalability。之后通过一个例子演示了如何在.Net环境中利用Remoting和EnterpriseService实现类似EJB中SLSB的功能。

 

参考资料:

Microsoft .Net Distributed Application
Expert One-on-One J2EE Design and Develop
Expert One on one J2EE Development Without EJB
Pattern Oriented Software Architecture-Patterns for Resource Management
Distributed .NET Programming in C#

 

相关文章:

Applying Distributed Application in .Net系列文章

Webservice 的设计和模式

Web Services Security

WS-Addressing 从理论到实践 --- SOA基础规范介绍