在Start with Database Connection Pool一文中把DAL从Client转移到了Server,从而获得更好的scalability。此时我们迈出了分布式的第一步,如果更进一步,把BLL也分布到Server上,对我们又将产生怎样的影响呢?
从图中可以看到一个显而易见的优点,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的优势就凸现出来了。
如上图所示, 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的要求。
上述的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创建出一个新的对象,请求被处理后,销毁对象。
具体过程如图所示,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 }
可以看出运行结果和文中描述的是一致的。
.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的代码不变,运行结果如下:
可以看出对象的创建是在调用方法时发生的,而不是一开始就在池中连续创建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 }
从中可以看出最后三次调用分别使用了不同的对象。
总结:
本文主要介绍了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#
相关文章: