Wednesday, January 02, 2008

(转)J2EE集群的神话

http://www.theserverside.com/tt/articles/article.tss?l=J2EEClustering

失效转移可以完全避免错误——否定

Jboss的文档中,整个章节都在警告你“你真的需要HTTP会话的复制吗?”。是的,有时没有失效转移的高可用性的解决方案也是可接受并且是廉价的。失效转移并不是你想象的那么强壮。

那么失效转移到底给你带来了什么?你可能想失效转移可以避免错误。你看,没有会话的失效转移,当一个服务器实例失效后,会话数据将丢失而导致错误。通过失效转移,会话可以从备份中恢复,而请求可以被其他服务器实例所处理,用户根本意识不到失效。这是事实,但这是有条件的!

回想一样我们定义的“失效转移”,我们定义了一个条件是失效转移是在“两个方法调用之间”发生的。这是说如果你有两个连续的对远程对象的方法调用,失效转移只会在第一调用成功后并且第二调用的请求发出前才能发生。

这样,当远程服务器在处理请求的过程中失效了会发生什么呢?答案是:多数情况处理将会停止而客户端将会看到错误信息。除非这个方法是等幂的(Idempotent),只有这个方法是等幂的,一些负载均衡器更智能,它会重试这些方法并将它失效转移到其他实例上。

为什么“等幂”重要呢,因为客户端不知道当失效发生的时候请求被执行到什么地方。是才刚刚初始化还是差不多就要完成了?客户端没法判断!如果方法不是等幂的,在相同方法上的两次调用可能会两次修改系统的状态,而使得系统出现不一致的情形。

你可能想所有在事务中的方法都是等幂的,毕竟,如果错误发生,事务将被回滚,事务状态的改变都将被复位。但事实上事务的边界可能不包括所有的远程方法调用过程。如果事务已经在服务器上提交了而返回给客户端时网络崩溃怎么办呢?客户端不知道服务器的事务是否是成功了。

在一些应用程序中,将所有的方法都做成等幂的是不可能的。这样,你只能通过失效转移减少错误,而不是避免它们。拿在线商店为例,假设每台服务器可以同时处理100个在线用户的请求,当一台服务器失效了,没有失效转移的解决方案将丢失100个用户的会话数据并激怒这些用户。而有失效转移的解决方案中,当失效发生的时候有20个用户正在处理请求,这样20个用户将被失效激怒。而其他80个用户正处于思考时间或在两个方法调用之间,这些用户可以透明地获得失效转移。这样,你就需做以下的考虑:

l 激怒20个用户和激怒100个用户造成影响的区别。

l 采用失效转移和不采用失效转移产品成本的区别

独立应用可以透明的迁移到集群结构中——否定

尽管一些供应商宣称他们的J2EE产品有这样的灵活性。不要相信他们!事实你要在开始系统设计时就要准备集群,而这将影响开发和测试的所有阶段。

Http Session

在集群环境中,如我前面提到的,使用HTTP Session有很多限制,这取决于你的应用程序服务器采用了那种会话失效转移的机制。第一个重要的限制就是所有保存的HTTP Session中的对象必须是可序列化的,这将限制设计和应用程序结构。一些设计模式和MVC框架会用HTTP Session保存一些不序列化的对象(如ServletContextEJB本地接口和WEB服 务引用),这样的设计不能在集群中工作。第二,对象的序列的反序列化对性能的影响很大,特别是数据库保存的方式。在这样的环境中,应该避免在会话中保存大 的或是众多的对象。如果你采用了内存复制的方式,如前所述你必须小心在会话中属性的交叉引用。其他在集群环境中的主要区别是在会话不管任何属性修改,你必 须调用“setAttribute()”方法。这个方法调用在独立的系统中是可选的。这个方法的目的是区别已修改的属性和那些没用到属性,这样系统可以只为失效转移备份必要的数据,从而提高性能。

缓存

我经历过的大多数J2EE项目都用了缓存来提高性能,同时流行的应用程序服务器也都提供了不同程度的缓存用来加快应用程序的速度。但这些缓存都是为那些典型的独立环境设计的,只能在单JVM实例中工作。我们需要缓存是因为一些对象很“重”,创建它需花费大量的时间和资源。因此我们维护了对象池用于重用这些对象,而不需要在后面创建。我们只有当维护缓存比创建对象更廉价时才能获得性能的提高。在集群环境,每个JVM实例都要维护一份缓存的拷贝,这些拷贝必须同步以维持所有服务器实例状态的一致性。有时这种类型的同步会比没有缓存带来更糟的性能。

Static变量

当我们设计J2EE应用程序时,在架构上经常会使用一些设计模式。这些如“Singleton”的设计模式会用到静态变量来在多对象之间共享状态。这种方式在单服务中工作得很好,但在集群环境将失效。集群中的每个实例都会在它的JVM实 例中维护一份静态变量的拷贝,这样就破坏了模式的机制。一个使用静态变量的例子就是用它来保持在线用户数。用静态变量来保存在线用户数是一个很简单的办 法,当用户进入或离开时就增加和减少它。这种方式在单服务器中绝对是好的,但在集群环境将失效。在集群中更好的方式是将所有状态保存到数据库。

外部资源

尽管J2EE规范不支持,但为各种目的仍然会用外部I/O的操作。例如,一些应用会使用文件系统来保存用户上传的文件,或是创建一个动态配置的XML文件。在集群环境是没有办法来在其他实例之间来复制这些文件的。为了在集群中工作,办法是用数据库作为外部文件的存放点,另外也可以使用SAN(存储区域网,Storage Area Network)作为存放点。

特殊服务

一 些特殊的服务只在独立的环境中才有意义,定时服务就一个很好例子,这种服务可以在一个固定的间隔时间有规律的触发。定时服务常用于执行一些自动化管理任 务。如日志文件滚动,系统数据备份,数据库一致性检查和冗余数据清理等。一些基于事件的服务也很难被迁移到集群环境中。初始化服务就是个好例子,它只在整 个系统启动时才发生。邮件通知服务也一样,它在一些警告条件下触发。

这些服务是被事件而不是被请求触发的,而且只被执行一次。这些服务使得负载均衡和失效转移在集群中没多少意义。

一些产品准备了这些服务,如Jboss使用了“集群单例设施”来协调所有实例,保证执行这些服务一次且仅有一次。基于你所选择的平台,一些特殊的服务可能会是把你的应用迁移到集群结构中的障碍。

分布式结构比并置结构更灵活——不一定

J2EE技术,尤其是EJB,天生就是用来做分布式计算。解耦业务功能,重用远程组件,这些使得多层应用非常流行。但是我们不能将所有的东西都分布。一些J2EE架构师认为Web层与EJB层并置得越近越好。这些计论后面会继续。先让我解释一下。

20 分布式结构

如图20所示,这是一个分布式结构。当请求来了,负载均衡器将请求分发到不同服务器中的不同WEB容器,如果请求包含了EJB调用,WEB容器将重发EJB调用到不同的EJB容器。这样,请求将被负载均衡和失效转移两次。

一些人看分布式结构,他们会指出:

l 第二次负载均衡没有必要,因为它不会使任务分配更平坦。每个服务器实例都有它们自己的WEB容器和EJB容器。把EJB容器用来处理来自其他实例WEB容器的请求比只在服务器内部调用并没有什么优势。

l 第二次失效转移没有必要,因为它不能是高可用性。多数供应商实现J2EE服务器都会在同一服务器中运行的WEB容器和EJB容器放在一个JVM实例中。如果EJB容器失效了,多数情况下在同一个JVM中的WEB容器也将同时失效。

l 性能将下降。想像一下对你的应用的一次调用包含一组对EJB的调用,如果你负载均衡了这些EJB,这将跨越每个服务器实例,导致许多不必要的服务器到服务器的交互。还有,如果这个方法在事务范围内,那么事务边界将包含许多服务器实例,这将严重影响性能。

实际上在运行期,多数的供应商(包括Sun JESWebLogicJboss)都会优化EJB调用机制,使请求首先选择同一个服务器中的EJB容器。这样,如图21所示,我们只在第一层(WEB层)做负载均衡,然后调用相同服务器上的服务。这种结构我们称之为并置结构。技术上说,并置结构是分布式结构的一种特例。

21 并置结构

一个有趣的问题是,既然多数的部署在运行期都演进成了并置结构,为什么不用本地接口代替远程接口,这将大提高性能。你当然可以,但是记住,当你使用本地接口后,WEB组件和EJB耦合得很紧,而方法调用也是直接的而不通过RMI/IIOP。负载均衡和失效转移分发器没有机会介入本地接口调用。“WEB+EJB”整体处理负载均衡和失效转移。

但不幸的是,在集群中使用本地接口在多数J2EE服务器中有局限性。使用本地接口的EJB是本地对象,是不可序列化的,这一个限制就使本地引用不能保存在HTTP Session中。一些产品,如Sun JES,会将本地接口区别看待,使它们可以序列化。这样就可以用在HTTP Session中。

另一个有趣的问题是,既然并置结构这么流行并且有好的性能,为什么还要分布式结构呢?这在多数情况下是有道理的,但有时分布式结构是不可替代的。

l EJB不仅被WEB容器使用,富客户端也会使用它。

l EJB组件和WEB组件需在不同的安全级别上,并需要物理分离。这样防火墙将被设置用于保护运行EJB的重要机器。

l WEB层和EJB层极端不对称使得分布式结构是更好的选择。比如,一些EJB组件非常复杂并且很消耗资源,它们只能运行在昂贵的大型服务器上,另一方面,WEB组件(HTMLJSPServlet)简单得只需廉价的PC服务器就能满足要求。在这种情况下,专门的WEB服务器可以用来接受客户端连接请求,很快处理静态数据(HTML和图像)和简单的WEB组件(JSPServlet)。大型服务器只被用来做复杂计算。这将更好的利用投资。

结论

集群与独立环境不同,J2EE供应商采用不同的方法来实现集群。如果你的项目为做到高伸缩性而使用集群,你应该在你的项目开始的时候就做准备。选择符合你的需求的正确的J2EE产品。选择正确的第三方软件和框架并确保它们能支持集群。最后设计正确的架构使得能从集群中受益而不是受害。


AppendixA: Classical cluster topology

The image “http://www.javaworld.com/jw-02-2001/images/jw-0223-extremescale7.gif” cannot be displayed, because it contains errors.


=================================================

ADDITIONAL INFORMATION

=================================================

垂直伸缩(scale vertically),应用部署在单个节点上,随着系统用户数的增多,可以增加系统的硬件资源,比如增加cpy,增加内存,增加带宽等。

水平伸缩(scale horizontally),应用可以部署在多个节点上,随着系统用户的增多,可以增加更多的处理节点,比如从一台节点增加到三台节点。 水平伸缩方式需要在每个节点上部署相同的应用,但是这种伸缩方式更加便宜,同时也提高了系统的可用性。

参考资料:http://www.theserverside.com/tt/articles/article.tss?l=ScalingYourJavaEEApplications

No comments: