序言
越来越多的关键应用运行在J2EE(Java 2, Enterprise Edition)中,这些诸如银行系统和账单处理系统需要高的可用性(High Availability, HA),同时像Google和Yahoo这种大系统需要大的伸缩性。高可用性和伸缩性在今天高速增长的互连接的世界的重要性已经证实了。eBay于1999年6月停机22小时的事故,中断了约230万的拍卖,使eBay的股票下降了9.2个百分点。
J2EE集群是用来提供高可用性和伸缩性服务,同时支持容错处理的一种流行的技术。但是,由于J2EE规范缺乏对集群的支持,J2EE供应商实现集群的方法也各异。这给J2EE架构师和开发人员带来了很多困难。以下是几个常见的问题:
l 为什么带集群功能的商业J2EE服务器产品如此昂贵?(10倍于不带集群功能的产品)
l 为什么基于单服务器环境构建的应用不能在集群中运行?
l 为什么应用在集群环境中运行得很慢,但在非集群环境中却快得多?
l 为什么集群的应用移植到其他服务器中失败?
理解这些限制和要素的最佳方法是学习他们的实现方式。
基本术语
在我们讨论不同的集群实现之前,先谈谈几个概念。这有助于理解不同的J2EE集群产品不同的设计结果和概念:
伸缩性(Scalability):
在一些大的系统中,预测最终用户的数量和行为是非常困难的,伸缩性是指系统适应不断增长的用户数的能力。提高这种并发会话能力的一种最直观的方式就增加资源(CPU,内存,硬盘等),集群是解决这个问题的另一种方式,它允许一组服务器组在一起,像单个服务器一样分担处理一个繁重的任务。
高可用性(High availability):
单一服务器的解决方案并不是一个健壮方式,因为容易出现单点失效。像银行、账单处理这样一些关键的应用程序是不能容忍哪怕是几分钟的死机。它们需要这样一些服务在任何时间都可以访问并在可预期的合理的时间周期内有响应。集群方案通过在集群中增加的冗余的服务器,使得在其中一台服务器失效后仍能提供服务,从而获得高的可用性。
负载均衡(Load balancing):
负载均衡是集群的一项关键技术,通过把请求分发给不同的服务器,从而获得高可用性和较好的性能。一个负载均衡器可以是从一个简单的Servlet或Plug-Ins(例如一个Linux box利用ipchains来实现),到昂贵的内置SSL加速器的硬件。除此之外,负载均衡器还需执行一些其他的重要任务,如“会话胶粘”让一个用户会话始终存在一个服务器上,“健康检查”用于防止将请求分发到已失效的服务器上。有些负载均衡器也会参与我们下面将要谈到“失效转移”过程。
容错(Fault tolerance):
高可用性意味着对数据正确性的要求不那么高。在J2EE集群中,当一个服务器实例失效后,服务仍然是有效的,这是因为新的请求将被冗余服务器处理。但是,当一个请求在一个正在失效的服务器中处理时,可能得到不正确的结果。不管有多少个错误,容错的服务应当能确保有严格的正确的行为。
失效转移(Failover):
失效转移是集群中用来获取容错能力的另一项关键的技术。当一个结点失效后,通过选择集群中的另一个结点,处理将会继续而不会终止。转移到另一个结点可以被显式的编码,或是通过底层平台自动地透明地路由到另一个服务器。
等幂方法(Idempotent methods):
等幂方法是指这样一些方法:重复用相同的参数调用都能得到相同的结果。这些方法不会影响系统状态,可以重复调用而不用担心改变系统。例如:getUsername()就是等幂的,而deleteFile就不是。当我们讨论HTTP Session失效转移和EJB失效转移时,它是一个重要的概念。
什么是J2EE集群
一个天真的问题,不是吗?但我仍要用几句话和图来回答它。通常,J2EE集群技术包括"负载均衡"和"失效转移"。
图 1 负载均衡
如图1所示,负载均衡意味着有许多客户端向目标对象同时发出请求。负载均衡器在调用者和被调用者之间,分发请求到与原始对象相同的冗余对象中。伸缩性和高可用性就是这样得到的。
图 2 失效转移
如图2所示,失效转移与负载均衡不同。有时客户端会连续发请求到目标对象,如果请求中间目标对象失效了,失效转移系统将检测到这次失败,并将请求重定向到另一个可用的对象。通过这种方式可以获得容错能力。
如果你想知道更多的有关J2EE集群的知识,你就会问到一个基本的问题,“什么对象可以集群?”和“在我的J2EE代码中哪里会发生负载均衡和失效转移呢?”。这些都是用来理解J2EE集群的非常好的问题。实际上,并不是所有的对象都能被集群的,并且负载均衡和失效转移并不是在J2EE代码所有地方都能发生。看看下面的例子代码:
图 3 例子代码
在Class A的bussiness()方法中,instance1可以负载均衡吗?或是当其失效,可以失效转移到其他B的实例上吗?我想是不行的!对负载均衡和失效转移来说,必须要有个拦截器在调用者和被调用者之间分发或重定向请求到不同的对象上。Class A和Class B的实例是运行在一个JVM中紧密耦合的,在方法调用间加入分发逻辑非常困难。
什么类型对象可以被集群?——只有那些可以被部署到分布式拓朴结构中的组件。
在我的J2EE代码中,什么地方会有负载均衡和失效转移?——只在你调用分布式组件的方法时。
图 4 分布式对象
在如图4所示的分布式环境中,调用者和被调用者被分离在有明显边界的不同的运行容器中,这个边界可以是JVM,进程和机器。
当目标对象被客户端调用时,目标对象的功能是在容器中运行的(这就是为什么我们说它是分布式的原因)。客户端和目标对象通过标准的网络协议通信。这些特性就为一些机制提供了机会可以介入到方法调用之间实现负载均衡和失效转移。
如图4,浏览器通过HTTP协议调用JSP对象,JSP运行在WEB服务器中,浏览器只需要返回结果而不关心它是怎么运行的。在上述场景中,一些东西就可以在浏览器与WEB服务器之间实现负载均衡和失效转移的功能。在J2EE平台,分布式技术包括:JSP(Servlet),JDBC,EJB,JNDI,JMS,WEB Service等。负载均衡和失效转移就发生在这些分布式方法被调用时。在后续部分我们将详细讨论这些技术。