SlideShare a Scribd company logo
1 of 32
Download to read offline
揭开 J2EE 集群的面纱
    WANG YU
目录
1.      前言 ...................................................................................................................................................... 4
2.      基本术语.................................................................................................................................. 4
        基本术语
     2.1.        可扩展性 ......................................................................................................................... 4
     2.2.        高可用性 ......................................................................................................................... 4
     2.3.        负载均衡 ......................................................................................................................... 5
     2.4.        容错 ................................................................................................................................ 5
     2.5.        失败转移 ......................................................................................................................... 5
     2.6.        幂等方法 ......................................................................................................................... 5
3.               集群?
        什么是 J2EE 集群? ................................................................................................................ 5
4.          层集群实现..................................................................................................................... 8
        WEB 层集群实现
     4.1.     W EB 层负载均衡 ............................................................................................................. 9
     4.2.     HTTP 会话的失败转移 .................................................................................................. 10
        4.2.1. 数据库持久方案 ........................................................................................................ 11
        4.2.2. 内存复制方案 ........................................................................................................... 12
        4.2.3. Tomcat 的方案:多服务器复制 ................................................................................ 12
        4.2.4. WebLogic, WebSphere, JBoss 的方案:结对服务器(Paired servers)复制......... 13
        4.2.5. IBM 的方案:集中状态服务器 .................................................................................. 15
        4.2.6. Sun 的方案:专用服务器 ......................................................................................... 16
        4.2.7. 性能问题................................................................................................................... 16
            4.2.7.1.          何时备份会话 ............................................................................................................... 16
            4.2.7.2.          备份颗粒度................................................................................................................... 17
        4.2.8.       其他的失败转移实现................................................................................................. 18
            4.2.8.1.          JRun:使用 JINI .......................................................................................................... 18
            4.2.8.2.          Tangosol:分布式缓存 ................................................................................................ 18

5.           集群实现....................................................................................................................... 19
        JNDI 集群实现
     5.1.        共享全局 JNDI 树.......................................................................................................... 19
     5.2.        独立的 JNDI.................................................................................................................. 20
     5.3.        中央集中 JNDI .............................................................................................................. 21
     5.4.        初始化对 JNDI 服务器的访问........................................................................................ 21
6.      EJB 集群实现........................................................................................................................ 21
            集群实现
     6.1.     SMART STUB .................................................................................................................. 22
     6.2.     IIOP 运行库................................................................................................................... 24
     6.3.     拦截代理 ....................................................................................................................... 24
     6.4.     EJB 的集群支持 ............................................................................................................ 25
        6.4.1. EJBHome Stub 的集群支持 ..................................................................................... 25
        6.4.2. EJBObject Stub 的集群支持..................................................................................... 26
7.             和数据库连接的集群支持...................................................................................... 26
        对于 JMS 和数据库连接的集群支持
8.      关于 J2EE 集群的神话 .......................................................................................................... 27
     8.1.     失败转移能够彻底避免错误 -- 否定! ........................................................................... 27
     8.2.     单机应用可以透明的迁移到集群环境 -- 否定!............................................................. 28
        8.2.1. HTTP 会话................................................................................................................ 28
        8.2.2. 缓存 .......................................................................................................................... 28
        8.2.3. 静态变量................................................................................................................... 28
8.2.4. 外部资源................................................................................................................... 29
        8.2.5. 特殊的服务 ............................................................................................................... 29
     8.3.     分布式结构比单一结构更灵活 – 未必............................................................................ 29
9.      总结 ....................................................................................................................................... 32
10.         关于作者 ........................................................................................................................... 32
11.             :中英文对照表...................................................................................................... 32
            附录 A:中英文对照表
1. 前言

    现在有越来越多的关键应用和大型应用是基于 J2EE 来创建的,像银行系统和帐单系统这些关
键应用要求有很高的可用性,而 Google 和 Yahoo 这样的大型应用就需要很好的可扩展性。在如今
这个联系越来越紧密的世界,高可用性和良好的可扩展性的重要性日益突出。例如在 1999 年 6 月
份,eBay 的服务停止了 22 个小时,导致大约 230 万的拍卖被中断,eBay 的股票也随之下降了
9.2 个百分点。
    J2EE 集群就是一种能够提供高可用性、可扩展性以及容错性的流行技术。但是由于在 J2EE
规范中没有对集群做出规范,各个 J2EE 厂商就使用不同的方式来实现集群,这样就给系统架构师
和开发人员带来了很多麻烦。下面就是常见的一些问题:
•   为什么带有集群支持的商业 J2EE 服务器产品如此昂贵?(是无集群支持产品的 10 倍)
•   为什么在单机环境下创建的应用在集群环境中无法正常运行?
•   为什么我的应用在集群环境下运行的非常慢,但是在单机模式下却没有这个问题?
•   为什么我的集群应用在向其他厂商的服务器迁移时会失败?
要理解为什么会有这些限制,最好的方法就是研究它的实现,以揭开 J2EE 集群的面纱。


2. 基本术语

    在我们开始讨论对于集群不同的实现之前,我想,了解一下集群技术的一些基本概念还是很有
意义的。希望本章不单单是告诉你这些概念和设计问题,也同时能够为你勾勒一下不同 J2EE 集群
实现的框架以便于理解。


2.1. 可扩展性
    在一些大型系统中,很难提前预知最终用户的数量以及他们的使用行为,所以,可扩展性就是
指一个系统能够快速适应用户数量的增加。提高服务器处理能力的最直接的方法就是增加硬件资
源,例如 CPU、内存或者硬盘等。集群是解决这个问题的另外一种方式,它使得一组服务器共同
分担繁重的任务,但对于最终用户来说就像一台服务器。


2.2. 高可用性
    通过向单机添加硬件来扩展系统能力的方案并不可靠,因为单一的服务器存在一个单点故障。
像银行系统、帐单系统这样的关键应用甚至连一分钟的停机都不能容许,它们需要在任何时间都是
可用的,并且要能够保证响应速度。集群技术就可以满足这个要求,它通过加入冗余服务器使得在
一个服务器出错而停止服务的时候,这些冗余的服务器可以继续服务。


2.3. 负载均衡
  负载均衡是集群的另外一个关键技术,它通过将请求分发到不同的服务器来达到高可用性和高
效的处理能力。负载均衡器可以是一个 servlet,也可以是一个插件(例如 Linux 上的 ipchains),
甚至还可以是一个比较昂贵的内嵌了 SSL 支持的硬件产品。为了能够分发请求,负载均衡器还需
要做一些重要的工作,例如使用“会话粘滞”技术以确保来自同一个用户的请求会被转发到同一个
服务器;使用“健康检查”(或者“心跳监听”)技术来防止将请求转发到一个失败的服务器;有
时候负载均衡器还将参与“失败转移”的工作。


2.4. 容错
  高可用的数据并不必是严格正确的数据。在 J2EE 集群中,当一个服务器实例失败了,在集群
中冗余的服务器就可以处理新到的请求,这样就保证了服务依然可用。但是在服务器失败的那一
刻,正在被处理的请求就可能无法得到正确的数据。那么,带有容错功能的集群就可以确保请求所
得到的数据是正确的,哪怕是服务器端出现了错误。


2.5. 失败转移
  在集群中,失败转移是实现容错的一个关键技术。当最初的节点失败之后,在集群中选择另外
一个节点来完成处理。失败转移到其他节点可以通过编码实现,也可以由平台自动实现。


2.6. 幂等方法
  如果一个方法使用同样的参数进行多次调用所得到的结果都一样,也就是说对于该方法的调用
次数不影响系统,那么这个方法就叫做“幂等方法”。例如“getUsername()”就是一个幂等方
法,而“deleteFile()”就不是幂等的。在讨论 HTTP 会话失败转移和 EJB 的失败转移时,幂等方
法是一个很重要的概念。




3. 什么是 J2EE 集群?
            集群?

  很天真的问题,不是吗?不过我还是要通过一些文字和图表来解释一下。通常来讲,J2EE 集
群包含了“负载均衡”和“失败转移”。
图表 1 负载均衡



  如图表 1 所示,负载均衡就是指,在同一时刻,有很多客户端对目标对象进行请求,负载均衡
器就位于调用者和被调用着之间,它将请求分发到具有同样功能的冗余对象去,这样就实现了高可
用性和高性能。




图表 2 失败转移


  如图表 2 所示,失败转移和负载均衡的工作方式有所不同。通常情况下,客户端对象的调用都
是成功的,当服务器端被调用对象失败之后,失败转移系统能够检测到这个失败,并且把后继的请
求转发到冗余的、可用的对象。这样就达到了容错的目的。
  如果你还想对 J2EE 集群做更多的了解,你可能会问“哪些类型的对象可以被集群?”或者
“负载均衡和失败转移在我代码的什么地方发生?”,这些都是了解 J2EE 集群原理的好问题。实
际上,并不是所有的对象都能够被集群,也不是代码的任何地方都可以进行负载均衡和失败转移。
我们来看看下面的例子:
图表 3 代码示例

  当 instance1 失败的时候,类 A 中的方法“business()”会不会负载均衡和失败转移到其他的
类 B 的实例上去?不,我可不这么认为。为了能够进行负载均衡和失败转移,在调用者和被调用者
之间必须要有一个拦截器,这个拦截器能够将调用请求转移到其他的对象。类 A 和类 B 的对象实
例在同一个 JVM 中运行,并且联系密切,在这些方法的调用中很难加入分发逻辑。
那么,哪些类型的对象可以被集群? - 只有那些可以部署在分布拓扑上的组件才可以被集群。
那么,负载均衡和失败转移可以发生在什么地方? - 只有在调用一个分布对象上的方法的时候。
图表 4 分布式环境


  在一个分布式环境中,如图表 4 所示,调用者和被调用者在不同的运行容器中,并且有明显的
分界线将他们分隔开来,例如不同的 JVM,不同的主机,或者不同的进程。
  当目标对象被客户端调用的时候,目标对象运行在自己的容器中(这就是为什么叫做分布
式)。客户端和对象服务器之间通过常见的网络协议进行通信。这种特点就使得有机会对于调用的
路由进行干预,从而实现负载均衡和失败转移。
  如图表 4 中所示,浏览器通过 HTTP 协议请求远端的一个 JSP,这个 JSP 在 Web 服务器中运
行。对于浏览器而言,它只关心结果,不关心如何执行的。在这种情况下,就可以在调用者和被调
用者之间插入一些东西来实现负载均衡和失败转移。在 J2EE 中,分布式技术包括:JSP/Servlet,
JDBC, EJB, JNDI, JMS, Web Service 等。在这些分布式调用中,可以进行负载均衡和失败转移。
在后面的章节我们会进行详细的讨论。




4. Web 层集群实现

   Web 层的集群是非常重要的,也是 J2EE 集群的基础。Web 层集群的技术包括:Web 负载均
衡和 HTTP 会话的失败转移。
4.1. Web 层负载均衡
  有很多种方式都可以实现 Web 层的负载均衡,一般来说,负载均衡器位于浏览器和 Web 服务
器之间,如图表 5 所示。




图表 5 Web 负载均衡

  负载均衡器可以是 F5 这样的硬件,也可以是一个带有负载均衡插件的 Web 服务器,带有
ipchains 的 Linux 也可以作为负载均衡器。无论是使用什么样的技术,负载均衡器都有以下特征:
  •   实现负载均衡算法
      当客户端请求到来的时候,负载均衡器能够决定把这个请求转发到后台的哪个服务器实
      例。时下比较流行的算法有:轮循算法,随机算法和权重算法。负载均衡器总是试图让每
      个服务器实例分担等同的压力,但是以上的三种算法都不能完美的、精确的达到这个目
      的。一些比较尖端的负载均衡器能够在转发请求之前去探测一下服务器实例的负载。
  •   健康检查
      一旦某一个服务器实例停止工作,那么负载均衡器应该能够检测到并且不再把请求转发到
      这个服务器实例。同样,当这个失败的服务器重新开始工作的时候,负载均衡器也要能够
      检测到,并且开始向它转发请求。
  •   会话粘滞
      几乎所有的 Web 应用都会有一些会话状态。简单的可能记录了你是否登录,复杂一点的可
      能记录了购物车的内容。因为 HTTP 协议本身是无状态的,所以会话状态就需要记录在某
      个地方,并且和你的浏览器关联,以便于下次请求的时候能够很方便的取出来。当进行负
      载均衡的时候,对于某一个确定的会话来说,把请求转发到上一次它所请求到的服务器实
      例是一个很好的选择,否则的话,可能会导致应用不能正常工作。
因为会话状态是存储在某个 Web 服务器实例的内存中的,所以对于负载均衡器来说,“会话
粘滞”的特征是非常重要的。但是,如果某个服务器实例由于种种原因导致失败,那么在这个服务
器实例上的会话状态就会全部丢失。负载均衡器能够检测到这个错误并且不再把请求转发到这个服
务器实例,但是由于会话状态的丢失,可能会引发其他错误。这就是为什么要有“会话失败转移”
功能的原因。




4.2. HTTP 会话的失败转移
  基本上,时下主流的 J2EE 厂商的集群产品中都实现了 HTTP 会话的失败转移,这样才能够确
保在服务器实例失败的时候,能够在不丢失任何会话状态的前提下继续处理客户端的请求。如图表
6 所示,当客户端浏览器请求一个有状态的 Web 应用的时候(步骤 1,2),Web 应用可能就在内
存中存储了会话状态,以便后继的使用,同时向浏览器发回一个能够唯一确定这个会话的标识(步
骤 3)。浏览器把这个会话标识用“Cookie”机制存储起来,下一次再请求这个 Web 应用的时候
把会话标识再发回给服务器实例。为了能够进行 HTTP 会话的失败转移,会话对象会被备份到某个
地方(步骤 4),以保证服务器实例失败的时候会话状态不会丢失。负载均衡器检测到这个服务器
实例失败之后(步骤 5,6),就会把请求转发到其他的服务器实例,当然了,这些实例上都部署
了同样的应用(步骤 7)。因为在之前已经把会话状态进行了备份,服务器实例就可以将会话状态
加载回来,然后正确的处理请求。




图表 6 HTTP 会话失败转移


在 HTTP 会话失败转移的实现中,要考虑以下问题:
  •   全局 HTTP 会话标识
      如上所述,全局 HTTP 会话标识是在 Web 服务器内存中会话的唯一标识。在 J2EE 中,会
      话标识的生成是依赖于 JVM 的。一个 JVM 实例可以运行多个 Web 应用实例,一个 Web
应用实例可以持有多个会话。要访问 JVM 中对应的会话对象,会话标识正是关键所在。在
        HTTP 会话失败转移的实现中,必须要求不同的 JVM 不能产生两个相同的会话标识。因为
        在失败转移发生的时候,一个 JVM 中的会话对象会在其他的 JVM 中备份和恢复使用。所
        以,必须建立一个全局 HTTP 会话标识的机制。
    •   如何备份会话数据
        如何备份会话数据也是使得一个 J2EE 服务器产品不同于其他产品的关键因素。不同的厂
        商的实现方式不同,在下面的章节会进行详细的介绍。
    •   备份的周期和颗粒度
        HTTP 会话状态的备份是会带来性能的消耗的,例如 CPU,网络带宽,IO 等。因此,备份
        的周期和粒度对于性能是很有影响的。


4.2.1. 数据库持久方案
    几乎所有的 J2EE 集群都允许你通过 JDBC 接口将会话状态存储到关系数据库中。如图表 7 所
示,这种方案就是在合适的时间让服务器的实例将会话数据进行序列化,然后存储到数据库中。当
失败转移发生的时候,另外的可用的服务器实例接替失败的服务器实例,并且从数据库中将会话状
态恢复加载进来。这种方案的关键点就是对象的可序列化,这使得内存中的会话状态是可持久的、
可传输的。关于 Java 对象序列化的更多信息,请参考:
http://java.sun.com/j2se/1.5.0/docs/guide/serialization/index.html




图表 7 将会话状态备份到数据库


    数据库事务是比较消耗资源的,所以,这种方案的最大的缺点就是当会话中的数据量大的时候
受到了性能的限制。大部分使用数据库持久的应用服务器都提倡尽量减少会话中对象的数量,这样
就使应用的设计和框架受到了限制,尤其是当需要在会话中缓存用户数据的情况下。
当然了,数据库备份方案还是有优点的:
•   易于实现。将请求处理和会话备份分离开来使得集群更健壮、更易于管理。
   •   失败可以转移到其他的主机,因为数据库是可以共享使用的。
   •   即使整个集群都失败了,会话数据都可以留存下来。


4.2.2. 内存复制方案
    由于数据库持久方案存在性能方面的问题,所以很多应用服务器(Tomcat, JBoss, WebLogic,
Websphere)提供了另外的方案:内存复制。




图表 8 会话状态的内存复制


    基于内存复制的方案在备用服务器实例的内存中持久会话信息,而不是在数据库中进行持久化
(如图表 8 所示)。由于有很高的处理性能,这种方案非常流行。与数据库处理效率相比,在原始
服务器和备份服务器之间直接进行网络通讯的消耗是很小的,是轻量的。另外一点就是,在这种方
案中,省去了会话数据“恢复”的阶段,因为会话信息已经在备份服务器的内存中存在了。
“JavaGroups”是 Tomcat 集群和 JBoss 集群的通讯层,它是一个提供可靠的组通讯和管理的工
具包。它提供的核心的技术例如“组关系协议”(Group membership protocols)和“消息组播”
(Message multicase)对于集群的运行是非常有帮助的。关于“JavaGroups”的更多信息可以参
考:
http://www.jgroups.org/javagroupsnew/docs/index.html




              的方案:多服务器复制
4.2.3. Tomcat 的方案:多服务器复制
    内存复制的方案有很多的变化形式。第一种方法就是在集群中所有的节点进行复制。Tomcat 5
就是用这种方案实现的。
图表 9 多服务器复制


  如图表 9 所示,当一个服务器实例上的会话状态发生了变化,就会在所有的服务器实例上生成
会话数据的备份。当一个服务器实例失败之后,负载均衡器就可以选择任何一个实例作为失败实例
的备份。但是这种方案存在一定的性能限制,当集群中的服务器实例很多的时候,网络通讯的消耗
就不能被忽略了,网络通讯的加重可能会成为一个性能瓶颈。




                                  的方案:结对服务器(
4.2.4. WebLogic, WebSphere, JBoss 的方案:结对服务器(Paired
        servers)复制
               )
  考虑到性能以及可扩展性,WebLogic, WebSphere, JBoss 都提供了内存复制的另外一种方
式:每个服务器实例都选择任意一个服务器实例作为它的备份服务器,并且将会话数据复制到这个
备份服务器实例。如图表 10 所示:
图表 10 结对服务器复制

  在这种情况下,每个服务器实例都选择一个服务器实例作为备份,而不是选择全部的其他实
例。这种方案的优点就是,即是集群中加入了很多节点,也不会带来性能上的影响。


虽然这种方案有良好的性能和可扩展性,它还是存在一些限制的:
  •   使负载均衡器更加复杂。当一个服务器实例失败之后,负载均衡器一定要知道和这个失败
      实例结对的实例是哪个。这样就减小了均衡器的范围,并且在某些硬件环境下无法满足这
      个需求。
  •   在处理请求的同时要进行复制。这样就降低了请求处理的能力,因为在处理请求的同时还
      要分配出硬件资源来进行复制。
  •   如果没有发生失败转移,那么在备份服务器实例上存储会话数据的内存就造成了浪费。也
      有可能加重 JVM 进行垃圾收集的负载。
  •   集群中的节点组成了结对服务器。所以,当主服务器实例失败之后,后继的请求都会转发
      到结对服务器实例,那么对于结对服务器而言,它就需要处理双倍的请求,可能会因此引
      发性能瓶颈。
  为了克服以上的限制,很多厂商都进行了工作。为了克服上面所列的第 4 点,WebLogic 为每
个会话进行结对,而不是每个服务器实例。在一个服务器实例失败的时候,它的会话被分散到不同
的服务器实例,使得基本上负载还是分散的。
的方案:
4.2.5. IBM 的方案:集中状态服务器
  对于内存复制,Websphere 提供了另外一种可选的方案:将会话信息备份到一台集中服务
器。如图表 11 所示:




图表 11 集中服务器复制


  这种方案和数据库方案很类似。区别就在于一个专用的“会话备份服务器”替代了数据库服务
器,可以说是整合了数据库复制方案和内存复制方案的优点。
  •   将请求处理和会话备份处理独立在不同的进程,使得集群更加健壮。
  •   所有的会话信息都备份到同一台服务器,避免了其他服务器的内存浪费。
  •   由于会话信息在集中服务器上,这个服务器又是被集群中所有节点共享的,所以失败可以
      转移到任意服务器实例,因此大部分的负载均衡器都可以使用,并且在失败发生的时候,
      能够保证负载的平均。
  •   在应用服务器和会话备份服务器之间通过 Socket 通信,相对于和数据库的通信来说是轻量
      的,所以性能也更好。
  但是在失败发生之后的“恢复”阶段,这种使用集中服务器的方式在性能上比不上结对服务器
之间的直接复制,同时也增加了集中服务器的管理复杂性,并且在集中服务器本身容易形成性能瓶
颈。
的方案:
4.2.6. Sun 的方案:专用服务器




图表 12 专用服务器复制


   Sun 的 JES 应用服务器的会话失败转移方案略显不同。如图表 12 所示,从表面上看来,它和
数据库方案,因为它使用关系型数据库存储会话信息并且使用 JDBC 接口访问这些信息。但是从内
里来看,JES 所使用的数据库叫做 HADB,是专门做过优化的,几乎所有的数据都存储在内存中。
所以,你可以认为这种方案和集中服务器方案更接近。




4.2.7. 性能问题
   考虑一下,一个 Web 服务器上可以有多个 Web 应用,每一个应用又可以有大量的用户并发访
问,每个用户都会生成自己的会话来访问特定的应用。所有这些会话信息都需要被备份,以便在发
生失败转移的时候能够恢复。并且,更糟糕的是会话的信息在不停的改变:会话创建时、过期时,
会话中加入、删除、修改属性时,即时会话中的属性不发生改变,会话的最后访问时间都是随着用
户的访问而变化的(为了能够确定过期时间)。所以,在会话失败转移方面,性能是最大的问题。
所以,厂商通常都会提供一些可调整的参数来改变服务器的行为,以满足性能方面的需求。


4.2.7.1. 何时备份会话
   当客户端请求被处理之后,会话的状态就会发生改变。考虑到性能的问题,实时备份会话是不
明智的选择,选择备份周期实际上是一种均衡。如果备份动作发生的太频繁,肯定造成性能压力;
如果备份周期过长,那么在这个周期内发生的失败转移就会丢失很多会话信息。不管是数据库复制
还是内存复制,下面这些都是设定备份周期的常用选项:
  •   基于 Web-methods
      在 Web 请求结束后、向客户端给出响应之前备份会话。这种方式保证在失败发生的时候会
      话信息是完整的。
•   基于固定周期
      在一个固定的周期进行会话备份。这种方案无法保证会话信息的完整性,但是不是在每一
      次请求之后都做会话复制,在性能上不会产生大的压力。


4.2.7.2. 备份颗粒度
当备份会话的时候,如何选择要备份多少数据?下面是在不同产品中比较常见的选择:
  •   全部会话
      每次都备份全部的会话数据,这种方法能够保证为每个分布的 Web 应用都正确的存储了会
      话。这种方案比较简单,并且被内存复制方案和数据库持久方案应用。
  •   发生变化的会话
      如果一个会话发生了变化,那么它就会被完整备份。当“HTTPSession.setAttribute()”或
      者“HTTPSession.removeAttribute()”方法被调用的时候,就认为这个会话发生了改变。
      所以,在会话中的属性发生变化的时候,你必须确保调用了这些方法(例如,在 session
      中存储了一个叫做“student”的对象,当你要设置 student 对象的 name 属性时,可以这
      么写:
      Student student = (Student)session.getAttribute(“student”);
      student.setName(“Jade”);
      session.setAttribute(“student”, student); //这个很重要
      )。这个不是 J2EE 规范规定的,但是为了能够使这种方案正确工作,你必须这么做。对
      于发生变化的会话才进行备份减少了每次备份会话的数量,相对于全部会话备份方案来
      说,性能上大有提高。
  •   发生变化的属性
      在会话中的属性发生变化的时候,不是备份整个会话数据,而是仅仅备份这些变化的属
      性。这样使得需要备份的会话数据量最小化。这种方案有最好的性能和最低的网络消耗。
      为了能够使这种方案正确工作,你需要遵守一些规则:首先,每次会话状态发生改变的时
      候,调用“setAttribute()”方法,并且要确保发生变化的这个对象是可序列化的。其次,
      会话中的属性对象之间不能有交叉引用。对于会话中每个 Key 所指向的对象都应该是可序
      列化的并且是分离存储的,他们之间不能有交叉引用,否则会导致序列化和反序列化不能
      正确进行。以图表 13 举例说明,在一个集群中使用内存复制方式,会话中有一个
      “school”对象和一个“student”对象,“school”对象有一个指向“student”对象的引
      用。在某一时刻,“school”对象发生了改变并且进行复制到备份服务器,那么,在序列
      化和反序列化之后,在备份服务器上的“school”对象包括了一张完整的对象图表和一个
      指向“student”对象的引用。但是“student”对象可以被单独的修改,在“student”对
      象发生改变之后,也会复制到备份服务器。在序列化和反序列化之后,从备份服务器恢复
回来一个“student”对象,但是此时它已经和“school”对象失去连接。尽管这种方案有
        非常好的性能,但是以上的这些限制会影响到 Web 应用的设计和架构,尤其是你需要在会
        话中缓存关系复杂的用户数据的情况下。




      内存复制中的交叉引用情况
图表 13 内存复制中的交叉引用情况



4.2.8. 其他的失败转移实现
    根据在以上章节介绍的,会话备份的颗粒度对性能有很大的影响。但是,就当前的实现来讲,
无论是内存复制还是数据库持久方案,都是使用序列化技术进行对象传输,这样会影响系统性能并
且也给 Web 应用的架构和设计带来了一定的局限性。很多 J2EE 的厂商都在寻找一种轻量的解决
方案来实现集群,通过选择粒度合适的分布对象共享机制来提高集群的性能。


4.2.8.1. JRun:使用 JINI
             :
    JRun 4 提供了一种内嵌的、基于 JINI 技术的集群解决方案,JINI 是一种分布计算的技术,它
允许在单一的分布计算空间上创建“联合”的设备和软件组件。它为查找、注册、租借提供了分布
系统服务,这些都是在集群环境中很有用的。另外一种基于 JINI 的叫做“JavaSpace”的技术也提
供了对象处理、共享、合并的特征,对于集群的实现也很有价值。关于 JINI 和 JavaSpace 的更多
信息请参考:
http://java.sun.com/products/jini/2_0index.html



4.2.8.2. Tangosol:分布式缓存
                 :
Tangosol Coherence™提供的分布式数据管理平台能够嵌入到大部分主流的 J2EE 容器产品中,从
而实现集群环境。Tangosol Coherence™也提供了叫做分布式缓存的技术,该技术能够在不同的
JVM 实例之间实现高效的对象共享。关于 Tangosol 的更多信息请参考:
http://www.tangosol.com
5. JNDI 集群实现

  根据 J2EE 规范,所有的 J2EE 容器都要提供一个 JNDI 的实现。JNDI 在 J2EE 中的最角色就
是提供一个使用资源的间接层,这样就使得 J2EE 组件拥有了更好的可重用性。
  对于 J2EE 集群来说,拥有完整特征的 JNDI 集群是很重要的,比如几乎所有对 EJB 访问都是
从在 JNDI 树上查找它的 home 接口开始的。根据不同的集群架构,厂商实现 JNDI 集群的方式也
不同。


5.1. 共享全局 JNDI 树
  WebLogic 和 JBoss 都有一个全局的、共享的、集群范围的 JNDI 上下文,客户端就从这个
JNDI 上下文查找和使用对象。通过使用 IP 组播,绑定在 JNDI 上下文上的对象就可以在集群范围
内复制,即使一个服务器实例失败之后,可以在 JNDI 树上查找到绑定对象。




图表 14 共享全局 JNDI


  如图表 14 所示,共享全局 JNDI 实际上是由每个节点上的 JNDI 组成的,机群中的每个节点都
有自己的命名服务器,将其上的内容复制到集群中的其他节点。这样,每个命名服务器都拥有了其
他节点的 JNDI 树上对象的复制,这种冗余结构为全局 JNDI 树提供了高可用性。
  在实际中,集群化的 JNDI 树主要有两个作用。可以用来进行部署,一旦在一个服务器实例上
部署了 EJB 模块,或者配置了 JDBC 和 JMS 服务,JNDI 树上的对象就会被复制到其他的服务器
实例。在应用的运行过程中,你的程序可以使用 JNDI API 访问 JNDI 树,读取或者存储对象,自
定义的对象也可以被进行全局的复制(在集群范围内)。


5.2. 独立的 JNDI
   不同于 WebLogic 和 JBoss 使用共享全局 JNDI,Sun 的 JES,IBM 的 Websphere 还有其他一
些产品,都是使用独立的 JNDI,即每个服务器实例拥有自己的 JNDI。在一个采用独立 JNDI 的集
群中,一个成员服务器不知道也不关心其他服务器的存在。这是不是意味着不需要 JNDI 集群了?
由于几乎所有的 EJB 访问都是从在 JNDI 树上查找 home 接口开始的,不提供 JNDI 集群的集群基
本上就是无用的了。
   实际上,独立 JNDI 的方案只有在所有的 J2EE 应用都是类似的时候才能够提供高可用性。如
果一个集群中的实例的配置以及其上部署的应用都是相类似的,这个集群就叫做相似集群。在这种
条件下,使用特定的管理工具,称作代理,能够帮助实现高可用性。如图表 15 所示:




图表 15 独立 JNDI

   Sun JES 和 IBM Websphere 在集群的每个服务器实例上安装一个叫做节点代理的工具,当部
署 EJB 模块或者绑定其他 JNDI 服务的时候,管理控制台向所有的代理发送命令以达到和共享全局
JNDI 树同样的效果。
   但是,对于应用在运行过程中绑定到 JNDI 树、并且获取和使用的对象,独立 JNDI 无法提供
复制支持。其原因是因为 JNDI 在 J2EE 应用中的主要角色是提供一个间接层,这个间接层是为管
理外部资源而设置的,而不是为了管理运行时的数据。如果需要管理运行时数据,可以使用独立的
LDAP 服务器或者带有 HA 功能的数据库来实现。IBM 和 Sun 都提供具有集群能力的 LDAP 服务器
产品。


5.3. 中央集中 JNDI
  一些 J2EE 产品使用中央集中 JNDI 的方案,命名服务运行在一个独立的服务器上,所有的服
务器实例都向这个服务器注册 EJB 模块或者其他管理对象。
  这个命名服务器也实现了高可用性,并且对客户端来讲是透明的,所有的客户端都是在这个命
名服务下去查找对象。由于这种方案增加了安装和管理的复杂性,所以很少被采用。


5.4. 初始化对 JNDI 服务器的访问
  当客户端连接 JNDI 服务器的时候,需要知道服务器的 IP 地址和端口。如果使用全局共享
JNDI 树或者独立 JNDI 方案,都会有不止一个 JNDI 服务器。客户端应该先连接哪个?如何实现负
载均衡和失败转移?
  从技术上讲,为了能够实现负载均衡和失败转移,软件的或者硬件的负载均衡器需要位于客户
端和 JNDI 服务器之间。但是很少有厂商采用这种方式,因为还有很多更简单的解决方案:
  •   对于 Sun JES 和 JBoss,通过在“java.naming.provider.url”参数中接受由逗号分隔的多
      个 URL 来实现 JNDI 的负载均衡。例如,
      java.naming.provider.url=server1:1100,server2:1100,server3:1100,server4:1100
      客户端就会一个一个的轮循请求这些服务器,直到找到第一个可用的为止。
  •   JBoss 也提供了一种叫做“自动发现”的机制。当“java.naming.provider.url”为空的时
      候,客户端通过使用网络广播来发现一个引导性的 JNDI 服务器。




6. EJB 集群实现

  EJB 是 J2EE 技术中非常重要的一部分,同时,EJB 的集群也是 J2EE 集群实现中面对的最大
挑战。
  EJB 是为分布计算而生的,他们运行在独立的服务器上,Web 服务器组件或者富客户端应用
通过标准协议(RMI/IIOP)访问这些 EJB,你可以像调用本地对象方法一样调用远程 EJB 对象的
方法。实际上,RMI-IIOP 掩盖了你所调用的方法是在本地还是在远程,这个就叫做“本地/远程透
明性”
图表 16 EJB 调用机制


    上图表示的就是远程 EJB 调用的机制。当客户端需要使用一个 EJB 的时候,不能直接调用这
个 EJB,客户端只能调用一个叫做“stub”的本地对象,这个本地的“stub”和远程的 EJB 有相同
的接口,起到代理的作用。stub 的作用就是接受客户端的调用,并且把这个调用通过网络委派到远
程的 EJB 对象。stub 和客户端应用运行在同一个 JVM 实例中,它知道如何通过 RMI/IIOP 在网络
上找到真正的对象。关于 EJB 更多信息请参考:
http://java.sun.com/products/ejb/
我们来看看在 J2EE 代码中如何使用 EJB,并说明 EJB 集群的实现。当我们要使用 EJB,应该:
    •   从一个 JNDI 服务器上找到 EJBHome stub
    •   使用 EJBHome stub 查找或者创建一个 EJB 对象,返回 EJBObject stub
    •   通过 EJBObject stub 进行 EJB 的方法调用
    在上一章已经提及,负载均衡和失败转移可以在进行 JNDI 查找(第一步)的时候发生。那么
对于在调用 EJB stub(包括 EJBHome stub 和 EJBObject stub)过程中的负载均衡和失败转移,
主要有以下三种方式:




6.1. Smart Stub
    我们知道,客户端可以通过 stub 对象调用远程 EJB,这个 stub 对象可以从 JNDI 树上查找
到,也可能是从 web 服务器下载 stub 的类文件。所以,stub 有以下特征:
Stub 可以被动态的创建和使用,并且它的定义文件(类文件)无需存在于客户端环境的
CLASSPATH 或者 JAR 文件中。




图表 17 Smart Stub


   如图表 17 所示,BEA WebLogic 和 JBoss 就是这样实现 EJB 集群的:在 stub 代码中加入特
殊的行为,但是这些代码对于客户端而言又是透明的(客户端程序对这些代码一无所知),这就叫
做“Smart Stub”。
   Smart Stub 真的是很精妙的,它包含了一个可访问的目标实例的列表,也能够检测到目标实例
的失败,同时还包含了很复杂的负载均衡和失败转移的逻辑来分发请求。并且,如果集群拓扑发生
了变化(例如加入了新的服务器实例或者移走了服务器实例),stub 能够自动更新自己的目标列表
来反映新的拓扑,而不需要手动的重新配置。
在 stub 中来实现集群有以下优点:
   •   由于 EJB stub 在客户端环境中运行,节约了服务器的资源
   •   负载均衡器和客户端代码结合在一起,并且和客户端生命周期保持一致。这样就消除了负
       载均衡其的单点故障。如果负载均衡器坏掉了,通常客户端应用也已经停止了,这个当然
       是可以接受的
   •   Stub 可以动态的下载和更新,这样就实现了零成本维护
6.2. IIOP 运行库
   Sun JES 应用服务器使用另外的方式来实现 EJB 集群。负载均衡和失败转移的逻辑集成在
IIOP 运行库中。例如 JES 修改了“ORBSocketFactory”的实现,使它成为 cluster-aware 的,如
图标 18 所示:




图表 18 IIOP 运行库


   这个修改版的“ORBSocketFactory”中包含了完整的负载均衡和失败转移的逻辑,这样就使
得 stub 很小并且不掺杂其他代码。由于这个实现位于运行库,它比 stub 更容易获取系统资源。但
是这种方案要求客户端使用特定的运行库,当和其他的 J2EE 产品交互的时候可能会有问题。




6.3. 拦截代理
   IBM Websphere 使用 Location Service Daemon(LSD),LSD 的作用是 EJB 客户端的代
理,如图表 19 所示:
图表 19 拦截代理


  在这种方案中,EJB 客户端通过查找 JNDI 获取一个 stub,这个 stub 中包含的路由信息指向
LSD,而不是指向真正的拥有这个 EJB 的应用服务器。所以,LSD 收到客户端的请求之后,根据
其负载均衡和失败转移的逻辑将请求分发到不同的应用服务器实例。这种方案会增加集群安装和管
理的工作量。




6.4. EJB 的集群支持
  要调用 EJB 的方法,关系到两种 stub 对象:EJBHome 接口和 EJBObject 接口。所以,EJB
的负载均衡和失败转移可以在两个层面上进行:
  •   当客户端使用 EJBHome stub 查找或者创建一个 EJB 实例
  •   当客户端通过 EJBObject stub 调用 EJB 方法时


6.4.1. EJBHome Stub 的集群支持
  EJBHome 接口用来查找或者创建容器中的 EJB 实例,EJBHome Stub 是 EJBHome 接口的客
户端代理。EJBHome 接口不会持有客户端的状态信息,所以,来自不同容器的 EJBHome 接口对
于客户端来讲是没有差异的。当客户端发起一个 create()或者 find()调用的时候,Home stub 依照
负载均衡和失败转移算法在服务器复制列表中选择一个服务器实例,并把调用转发到这个实例上的
Home 接口。
6.4.2. EJBObject Stub 的集群支持
  当一个 EJBHome 接口创建一个 EJB 实例之后,向客户端返回一个 EJBObject Stub,让客户
端调用 EJB 上的业务方法。系统保留了一个列表,记录了集群中部署了这个 EJB 的、可用的服务
器实例。但是能否通过 EJBObject Stub 将调用转发到任意一个服务器实例上的 EJBObject 接口,
取决于 EJB 的类型。
  •   无状态的会话 Bean
      对于无状态的会话 Bean 来说,可能是最简单的。由于不包含任何状态信息,所有的 EJB
      实例都可以认为是无差异的。所以从 EJBObject 的方法调用可以负载均衡和失败转移到任
      何一个服务器实例。
  •   有状态的会话 Bean
      有状态的会话 Bean 的集群和无状态的就有所不同了。我们都知道,有状态的会话 Bean
      会在每一次成功的请求之后保留客户端的会话信息,从技术上讲,有状态会话 Bean 的集
      群和 HTTP 会话的集群类似。通常来讲,EJBObject Stub 不能将请求负载均衡到不同服务
      器实例,它会一直使用第一次创建 EJB 实例的服务器实例,这个服务器实例被称为“主实
      例”。在请求处理过程中,会话信息会从主实例备份到其他的服务器,一旦主实例失败,
      后备服务器就接替它的工作。
  •   实体 Bean
      从本质上来说,实体 Bean 是无状态的,但是它也能够处理有状态的请求。借助于实体
      Bean 的内在机制,所有的信息都会备份在数据库中,看起来对于实体 Bean 而言,负载均
      衡和失败转移就可以像无状态的会话 Bean 一样简单。但是实际上,大部分情况下,实体
      Bean 都不是负载均衡的,也不能进行失败转移。根据设计模式的建议,实体 Bean 是被会
      话 Bean 封装过的,因此,大部分对于实体 Bean 的访问都是通过内在的会话 Bean 访问本
      地接口实现的,而不是由客户端直接调用的。这就使得负载均衡和失败转移变得无意义
      了。




7. 对于 JMS 和数据库连接的集群支持
  在 J2EE 中,除了 JSP、Servlet、EJB、JNDI,还有其他一些分布式对象。在集群实现中,这
些对象的集群可能支持,也可能不支持。
  当前,有些数据库产品像 Oracle RAC 能够支持集群环境,可以配置多个同样的、并行的数据
库实例。 但是 JDBC 是一种强状态的协议,事务状态紧密的绑定在客户端和服务器端的会话上,
所以很难实现集群。一旦一个 JDBC 连接死掉,所有和这个连接相关的 JDBC 对象(译者注:例如
Statement, ResultSet 等)就全部死掉了,客户端需要重新建立连接。BEA WebLogic 通过使用
“JDBC 多池”(Multi Pool)来实现这个重新连接的过程。(译者注:BEA WebLogic 8 之后的版
本中,JDBC 连接池(不是“多池”)有一些用来保护的选项,设置这些选项可以在连接失败之后
重新建立,而不需要客户端的额外编码)
  大部分 J2EE 服务器都支持 JMS,但是不是完全支持。只有对于 JMS 代理才有负载均衡和失
败转移。对于 JMS 目标中的消息,几乎没有产品能够实现失败转移。




8. 关于 J2EE 集群的神话
                     否定!
8.1. 失败转移能够彻底避免错误 -- 否定!
  在 JBoss 文档中,用了整整一章来警告你:“真的需要 HTTP 会话复制吗?”没错!有时
候,没有失败转移的高可用性解决方案也是可以接受的,成本也比较低。并且,失败转移并不像你
想象中那么健壮。
  那么失败转移到底给我们带来了什么?可能有些人认为失败转移可以避免错误,你看,如果没
有失败转移,当服务器失败的时候,会话信息就丢失了,从而引发了错误;如果有的话,当服务器
失败的时候,会话信息能够从备份服务器恢复,请求可以被其他的服务器实例正确处理。可能你说
的是对的,但是这是需要前提条件的。
  请注意在定义“失败转移”的时候,也定义了它的前提条件:“在方法调用之间”。如果你对
于远程对象有两次成功的调用,失败转移仅会出现在第一次调用成功之后和第二次调用之前。
那么,如果在服务器处理请求的过程中失败了会如何?答案是:处理会被中止,大部分情况下客户
端会收到错误消息,除非这个方法是幂等的(关于“幂等”请参考“基本术语”一章)。只有这个
方法是幂等的条件下,某些聪明的负载均衡器会尝试将请求失败转移到其他实例。
  为什么“幂等”如此重要?因为客户端不知道处理到什么地方了,也不知道何时发生的失败。
方法刚刚初始化?还是已经完成了?客户端无从知晓。如果方法不是幂等的,那么调用这个方法两
次就会改变系统状态两次,从而导致不一致的状态。
  你可能会认为在一个事务中的全部方法都是幂等的。毕竟,当失败发生的时候,事务会回滚,
事务过程中对系统状态所做的改变都回撤销。事实上,事务的界限可能无法包含所有的远程方法调
用的边缘。如果事务在服务器端已经提交,在返回客户端的过程中客户端宕了情况会如何?客户端
不知道服务器的事务是否成功了。
  要想使应用中所有的方法都是幂等的是不可能的。所以,使用失败转移只能减少错误,但是无
法避免!以在线商店的应用为例,假定在任何时候一个服务器实例都可以处理 100 个在线用户,当
一个服务器失败之后,如果没有失败转移,所有的会话信息就会丢失,让这 100 个用户感到恼火。
但是如果使用了失败转移,可能只有 20 个正在处理中的用户能够觉察到失败的发生并且感到恼
火,另外 80 个用户可能正处在两次请求之间的思考时刻,这些用户的会话被透明的进行了失败转
移。所以,可以这样进行权衡:
  •   20 个用户和 100 个用户之间的不同影响
  •   使用失败转移和不使用失败转移产品的成本




                         否定!
8.2. 单机应用可以透明的迁移到集群环境 -- 否定!
  虽然一些厂商都在宣扬他们产品的弹性,但是不要相信他们。事实上,你需要从系统设计时就
应该考虑到集群,甚至还要影响到开发和测试阶段。


8.2.1. HTTP 会话
  我们前面提到过,在集群环境中,对于 HTTP 会话的使用有很多的限制,这取决于你所用的服
务器的 HTTP 会话失败转移机制。最重要的限制就是会话中的对象必须是可序列化的,这就限制了
应用的设计。一些设计模式或者 MVC 框架会在 HTTP 会话中存储一些不可序列化的对象(例如
Servlet 上下文,本地 EJB 接口,Web Service 的引用等),这种设计在集群环境下无法正常工
作。其次,对象的序列化和反序列化都是很消耗性能的操作,尤其是在使用数据库持久的方案中。
所以,要避免在会话中存放大尺寸的或者大量的对象。如果你已经选择了内存复制方案,那么需要
注意会话属性中的对象不能有交叉引用。集群环境下另一个不同就是,只要会话中的属性发生了变
化,就必须调用“setAttribute()”方法,这个调用在单机的应用中不是必须的。在集群中这样调用
的目的是能够区分发生改变的属性和未改变的属性,在备份的时候只复制改变的属性,就能够提高
性能。


8.2.2. 缓存
  据我的经验,大部分 J2EE 应用都使用了对象缓存机制来提高性能,并且主流的应用服务器产
品都提供了不同程度的缓存的机制以提高应用性能。但是这些缓存都是为单机环境提供的,只能在
同一个 JVM 实例中工作。我们之所以需要缓存机制,是因为有些对象比较庞大,创建一个新的实
例会有很大的消耗,所以使用一个对象池来重用这个对象实例而不是创建一个新的。只有在缓存维
护的成本低于重新创建对象实例成本的时候,才能够得到性能上的提升。在集群环境中,每个
JVM 实例都要维护自己的对象缓存池,并且要和其他实例同步以保证一致性,有时候这种同步的
性能还不如不使用对象缓存机制。




8.2.3. 静态变量
  在当前的 J2EE 应用中,在架构中使用设计模式是很流行的。某些设计模式,例如
“Singleton”,会使用静态变量来实现多个对象之间的状态共享。这种方案在单机模式下运作正
常,但是在集群中会失败。集群中每个服务器实例都会在自己的 JVM 实例中保持一份静态变量的
复制,这样就打破了设计模式的机制。举例说明静态变量的使用:计算在线用户数量,简单的方式
就是使用一个静态变量,用户登录或者注销的时候增加或者减少这个变量的值,这个应用在单机模
式下工作正常,但是在集群环境中会失败。如果要使这种方案工作正常,可以考虑使用数据库存
储。




8.2.4. 外部资源
  虽然 J2EE 规范中不建议使用外部资源,但是出于各种目的,外部 I/O 操作还是不可避免的。
例如,一些应用使用文件系统存储用户上传的文件,或者创建动态的 XML 配置文件。在集群应用
中,服务器无法将这些文件复制到其他实例。所以,就需要借助数据库存放这些文件,或者使用
SAN 来作为文件的存储点。




8.2.5. 特殊的服务
  有一些服务只有在单机的环境下才有意义,例如时间服务:基于固定周期的事件。时间服务通
常用来进行自动的管理工作,例如日志文件的滚动、系统数据备份、数据库一致性检查以及冗余数
据的清理等等。一些基于事件的服务也无法移植到集群环境,例如在系统开始时候的初始化服务。
邮件通知服务也是由某种警告条件触发的服务。
  这些服务都是由特定的事件触发的而不是由请求调用的,并且只能执行一次。这样的服务使得
集群中的负载均衡和失败转移就没有什么意义了。
  有些服务器产品为这种服务做了一些准备,例如 JBoss 就使用一个叫做“集群的单一工具”来
定位这样的服务来保证服务执行一次并且仅仅执行一次。基于你选择的平台,这些服务器可能成为
向集群环境迁移的障碍。




8.3. 分布式结构比单一结构更灵活 – 未必

  J2EE 技术,特别是 EJB,就是为分布计算而生。松耦合的业务方法、可重用的远程组件使得
多层应用渐成流行之势。但是我们不会把所有东西都做成分布式的。一些 J2EE 架构师认为将 Web
层和 EJB 层结合得更近一些可能会更好,这种讨论也一直在继续。
图表 20 分布式结构


  如图表 20 所示,这是一个分布式的结构。当请求到来的时候,负载均衡器将请求转发到不同
服务器实例上的 Web 容器。如果这个请求包含了对 EJB 的调用,那么这个对 EJB 的调用将会重新
分发到不同的 EJB 容器。这样,请求就被负载均衡和失败转移了两次。
有些人并不看好分布式结构,他们指出:
  •   第二次的负载均衡是没有必要的,因为它不能均匀分配任务。每个服务器都有自己的 Web
      容器和 EJB 容器,让 EJB 容器处理来自其他服务器 Web 容器的请求相比于处理自己 Web
      容器中的请求来说,没有显出任何的优点。
  •   第二次失败转移是没有必要的,因为它不能提高可用性。大部分服务器产品的 Web 容器和
      EJB 容器在同一个 JVM 实例中运行,如果 EJB 容器失败了,大部分情况下,Web 容器也
      失败了。
  •   降低了性能。假如在应用中的一个方法调用多个 EJB,在这些 EJB 上进行了负载均衡,那
      么每次请求都被分散到不同的服务器实例,这样就产生了不必要的、跨服务器交互。并
      且,如果这个方法中有事务,那么这个事务边界就需要包含多个服务器实例,这是非常影
      响性能的。
  在实际的运行环境中,很多厂商(Sun JES, WebLogic, JBoss)对于 EJB 的负载均衡都做了
优化,优先选择同一个服务器上的 EJB。这样,如图表 21 所示,负载均衡仅仅在第一层(Web 容
器)进行,接下来的请求就在同一个服务器处理了。这叫做“搭配结构”。搭配结构属于分布式的
一种特殊形式。
图表 21 搭配结构


  一个有意思的问题是,既然很多应用在运行的时候都是搭配结构的,为什么不使用本地接口代
替远程接口?这样能够大幅度提高性能。当然可以这么干!但是,当使用本地接口的时候,Web
组件和 EJB 就是紧耦合的了,使用的是直接调用而不是通过 RMI/IIOP。负载均衡器没有机会对本
地接口的调用进行干涉,所以,“Web+EJB”会按照一个整体进行负载均衡和失败转移。
  但是不幸的是,在大部分服务器的集群环境中,使用本地接口依然会受到很多限制。EJB 是拥
有本地接口的本地对象,它是不可序列化的。所以,其限制就是不能在 HTTP 会话中存储本地引
用。有些产品,像 Sun JES 把本地接口以不同的方式处理,使它成为可序列化的,这样就可以在
HTTP 会话中存储本地引用了。
  令一个有趣的问题是,既然搭配结构是这么流行并且有更好的性能,为什么还需要分布式结构
呢?很多时候,事情的发生总是有原因的,有些情况下,分布式结构是无法被取代的:
  •   不仅 Web 应用使用 EJB,其他富客户端应用也可以使用 EJB
  •   Web 组件和 EJB 组件可能出于不同的安全层次,可能在物理上就是分离的。所以,可以使
      用防火墙来保护运行重要 EJB 的主机。
  •   Web 和 EJB 极度不对称的情况下,分布式结构是更好的选择。例如,一些 EJB 是非常复
      杂的,对资源的消耗也很多,就可以让它们运行在性能更好的主机上;同时,Web 层组件
      (html, JSP, Servlet)都是比较简单的,使用便宜的 PC 服务器就可以满足需求。这样,
      一个独立的 Web 服务器来接受客户端的请求,快速的提供静态的(HTML 和图像)或者简
      单的 Web 组件(JSP 和 Servlet)服务。性能良好的 EJB 服务器用来进行复杂的计算,这
      就更好的利用了投资。
9. 总结
    集群环境不同于单机环境,J2EE 厂商的实现也各不相同。你应该在项目一开始就做好集群的
准备,以建立大规模的应用。选择能够满足需求的合适的服务器,同时也要选择适用于集群环境的
第三方软件或者框架,然后设计一个适当的框架,让你真正从集群中受益,而不是折磨。




10. 关于作者
    Wang Yu, 技术工程师,技术架构顾问,在 Sub Microsystems 的 GPE 组工作。负责本地 ISV
支持,致力于重要 Java 技术 J2EE,EJB,JSP/Servlet,JMS,Web Service 的咨询和传播。邮件
地址:yu.wang@sun.com




11. 附录 A:中英文对照表
        :
英文                      中文                 备注
Scalability             可扩展性,可伸缩性
Session Stickiness      会话粘滞
Failover                失败转移
Fail Tolerance          容错
Idempotent              幂等的
Round-Robin             轮循
Session                 会话
Global HTTPSession ID   全局 HTTP 会话标识
Paired Server           结对服务器
Collocated Structure    搭配结构




翻译后记:
花了好多天的时间,终于把这篇文章翻译完了。首先向原作者致敬(别告我侵权噢☺)!可以说,
这篇文章是我看到的关于 J2EE 集群的最完整的阐述,并且也很客观,指出了集群的优点和需要注
意的事项。我不是学习语言专业的,英语也一般般,所以有些地方可能翻译的不到位。同时在翻译
的过程中比较保守,有些语句可能翻译得比较晦涩,觉得无法准确理解的地方可以参考原作。还望
大家对于其中的疏漏、错误之处海涵!

More Related Content

Viewers also liked

Primer Paquete Económico 2017 Zacatecas (2/9)
Primer Paquete Económico 2017 Zacatecas (2/9)Primer Paquete Económico 2017 Zacatecas (2/9)
Primer Paquete Económico 2017 Zacatecas (2/9)Zacatecas TresPuntoCero
 
Gfpi f-019 guia de aprendizaje 01 tda orientar fpi
Gfpi f-019 guia de aprendizaje 01 tda orientar fpiGfpi f-019 guia de aprendizaje 01 tda orientar fpi
Gfpi f-019 guia de aprendizaje 01 tda orientar fpilisbet bravo
 
De Reis van de Heldin december 2015
De Reis van de Heldin december 2015De Reis van de Heldin december 2015
De Reis van de Heldin december 2015Peter de Kuster
 
JULIOPARI - Elaborando un Plan de Negocios
JULIOPARI - Elaborando un Plan de NegociosJULIOPARI - Elaborando un Plan de Negocios
JULIOPARI - Elaborando un Plan de NegociosJulio Pari
 
El emprendedor y el empresario profesional cert
El emprendedor y el empresario profesional certEl emprendedor y el empresario profesional cert
El emprendedor y el empresario profesional certMaestros Online
 
Onderzoeksrapport acrs v3.0_definitief
Onderzoeksrapport acrs v3.0_definitiefOnderzoeksrapport acrs v3.0_definitief
Onderzoeksrapport acrs v3.0_definitiefrloggen
 
Como hacer un plan de negocios
Como hacer un plan de negociosComo hacer un plan de negocios
Como hacer un plan de negociosXPINNERPablo
 
Schrijven voor het web
Schrijven voor het webSchrijven voor het web
Schrijven voor het webSimone Levie
 
Evidence: Describing my kitchen. ENGLISH DOT WORKS 2. SENA.
Evidence: Describing my kitchen. ENGLISH DOT WORKS 2. SENA.Evidence: Describing my kitchen. ENGLISH DOT WORKS 2. SENA.
Evidence: Describing my kitchen. ENGLISH DOT WORKS 2. SENA... ..
 
Estrategias competitivas básicas
Estrategias competitivas básicasEstrategias competitivas básicas
Estrategias competitivas básicasLarryJimenez
 
2. describing cities and places. ENGLISH DOT WORKS 2. SENA. semana 4 acitivda...
2. describing cities and places. ENGLISH DOT WORKS 2. SENA. semana 4 acitivda...2. describing cities and places. ENGLISH DOT WORKS 2. SENA. semana 4 acitivda...
2. describing cities and places. ENGLISH DOT WORKS 2. SENA. semana 4 acitivda..... ..
 
3.Evidence: Getting to Bogota.ENGLISH DOT WORKS 2. SENA.semana 4 actividad 3.
3.Evidence: Getting to Bogota.ENGLISH DOT WORKS 2. SENA.semana 4 actividad 3.3.Evidence: Getting to Bogota.ENGLISH DOT WORKS 2. SENA.semana 4 actividad 3.
3.Evidence: Getting to Bogota.ENGLISH DOT WORKS 2. SENA.semana 4 actividad 3... ..
 

Viewers also liked (20)

结网
结网结网
结网
 
Proyecto Formativo
Proyecto FormativoProyecto Formativo
Proyecto Formativo
 
Primer Paquete Económico 2017 Zacatecas (2/9)
Primer Paquete Económico 2017 Zacatecas (2/9)Primer Paquete Económico 2017 Zacatecas (2/9)
Primer Paquete Económico 2017 Zacatecas (2/9)
 
Gfpi f-019 guia de aprendizaje 01 tda orientar fpi
Gfpi f-019 guia de aprendizaje 01 tda orientar fpiGfpi f-019 guia de aprendizaje 01 tda orientar fpi
Gfpi f-019 guia de aprendizaje 01 tda orientar fpi
 
De Reis van de Heldin december 2015
De Reis van de Heldin december 2015De Reis van de Heldin december 2015
De Reis van de Heldin december 2015
 
JULIOPARI - Elaborando un Plan de Negocios
JULIOPARI - Elaborando un Plan de NegociosJULIOPARI - Elaborando un Plan de Negocios
JULIOPARI - Elaborando un Plan de Negocios
 
El emprendedor y el empresario profesional cert
El emprendedor y el empresario profesional certEl emprendedor y el empresario profesional cert
El emprendedor y el empresario profesional cert
 
Geheugen verbeteren
Geheugen verbeterenGeheugen verbeteren
Geheugen verbeteren
 
De impact van adhd
De impact van adhdDe impact van adhd
De impact van adhd
 
PMP Sonora Saludable 2010 2015
PMP Sonora Saludable 2010   2015  PMP Sonora Saludable 2010   2015
PMP Sonora Saludable 2010 2015
 
Onderzoeksrapport acrs v3.0_definitief
Onderzoeksrapport acrs v3.0_definitiefOnderzoeksrapport acrs v3.0_definitief
Onderzoeksrapport acrs v3.0_definitief
 
Como hacer un plan de negocios
Como hacer un plan de negociosComo hacer un plan de negocios
Como hacer un plan de negocios
 
Schrijven voor het web
Schrijven voor het webSchrijven voor het web
Schrijven voor het web
 
Evidence: Describing my kitchen. ENGLISH DOT WORKS 2. SENA.
Evidence: Describing my kitchen. ENGLISH DOT WORKS 2. SENA.Evidence: Describing my kitchen. ENGLISH DOT WORKS 2. SENA.
Evidence: Describing my kitchen. ENGLISH DOT WORKS 2. SENA.
 
Estrategias competitivas básicas
Estrategias competitivas básicasEstrategias competitivas básicas
Estrategias competitivas básicas
 
Cápsula 1. estudios de mercado
Cápsula 1. estudios de mercadoCápsula 1. estudios de mercado
Cápsula 1. estudios de mercado
 
Rodriguez alvarez
Rodriguez alvarezRodriguez alvarez
Rodriguez alvarez
 
2. describing cities and places. ENGLISH DOT WORKS 2. SENA. semana 4 acitivda...
2. describing cities and places. ENGLISH DOT WORKS 2. SENA. semana 4 acitivda...2. describing cities and places. ENGLISH DOT WORKS 2. SENA. semana 4 acitivda...
2. describing cities and places. ENGLISH DOT WORKS 2. SENA. semana 4 acitivda...
 
Capacitacion y adiestramiento
Capacitacion y adiestramientoCapacitacion y adiestramiento
Capacitacion y adiestramiento
 
3.Evidence: Getting to Bogota.ENGLISH DOT WORKS 2. SENA.semana 4 actividad 3.
3.Evidence: Getting to Bogota.ENGLISH DOT WORKS 2. SENA.semana 4 actividad 3.3.Evidence: Getting to Bogota.ENGLISH DOT WORKS 2. SENA.semana 4 actividad 3.
3.Evidence: Getting to Bogota.ENGLISH DOT WORKS 2. SENA.semana 4 actividad 3.
 

揭开J2 Ee集群的面纱

  • 2. 目录 1. 前言 ...................................................................................................................................................... 4 2. 基本术语.................................................................................................................................. 4 基本术语 2.1. 可扩展性 ......................................................................................................................... 4 2.2. 高可用性 ......................................................................................................................... 4 2.3. 负载均衡 ......................................................................................................................... 5 2.4. 容错 ................................................................................................................................ 5 2.5. 失败转移 ......................................................................................................................... 5 2.6. 幂等方法 ......................................................................................................................... 5 3. 集群? 什么是 J2EE 集群? ................................................................................................................ 5 4. 层集群实现..................................................................................................................... 8 WEB 层集群实现 4.1. W EB 层负载均衡 ............................................................................................................. 9 4.2. HTTP 会话的失败转移 .................................................................................................. 10 4.2.1. 数据库持久方案 ........................................................................................................ 11 4.2.2. 内存复制方案 ........................................................................................................... 12 4.2.3. Tomcat 的方案:多服务器复制 ................................................................................ 12 4.2.4. WebLogic, WebSphere, JBoss 的方案:结对服务器(Paired servers)复制......... 13 4.2.5. IBM 的方案:集中状态服务器 .................................................................................. 15 4.2.6. Sun 的方案:专用服务器 ......................................................................................... 16 4.2.7. 性能问题................................................................................................................... 16 4.2.7.1. 何时备份会话 ............................................................................................................... 16 4.2.7.2. 备份颗粒度................................................................................................................... 17 4.2.8. 其他的失败转移实现................................................................................................. 18 4.2.8.1. JRun:使用 JINI .......................................................................................................... 18 4.2.8.2. Tangosol:分布式缓存 ................................................................................................ 18 5. 集群实现....................................................................................................................... 19 JNDI 集群实现 5.1. 共享全局 JNDI 树.......................................................................................................... 19 5.2. 独立的 JNDI.................................................................................................................. 20 5.3. 中央集中 JNDI .............................................................................................................. 21 5.4. 初始化对 JNDI 服务器的访问........................................................................................ 21 6. EJB 集群实现........................................................................................................................ 21 集群实现 6.1. SMART STUB .................................................................................................................. 22 6.2. IIOP 运行库................................................................................................................... 24 6.3. 拦截代理 ....................................................................................................................... 24 6.4. EJB 的集群支持 ............................................................................................................ 25 6.4.1. EJBHome Stub 的集群支持 ..................................................................................... 25 6.4.2. EJBObject Stub 的集群支持..................................................................................... 26 7. 和数据库连接的集群支持...................................................................................... 26 对于 JMS 和数据库连接的集群支持 8. 关于 J2EE 集群的神话 .......................................................................................................... 27 8.1. 失败转移能够彻底避免错误 -- 否定! ........................................................................... 27 8.2. 单机应用可以透明的迁移到集群环境 -- 否定!............................................................. 28 8.2.1. HTTP 会话................................................................................................................ 28 8.2.2. 缓存 .......................................................................................................................... 28 8.2.3. 静态变量................................................................................................................... 28
  • 3. 8.2.4. 外部资源................................................................................................................... 29 8.2.5. 特殊的服务 ............................................................................................................... 29 8.3. 分布式结构比单一结构更灵活 – 未必............................................................................ 29 9. 总结 ....................................................................................................................................... 32 10. 关于作者 ........................................................................................................................... 32 11. :中英文对照表...................................................................................................... 32 附录 A:中英文对照表
  • 4. 1. 前言 现在有越来越多的关键应用和大型应用是基于 J2EE 来创建的,像银行系统和帐单系统这些关 键应用要求有很高的可用性,而 Google 和 Yahoo 这样的大型应用就需要很好的可扩展性。在如今 这个联系越来越紧密的世界,高可用性和良好的可扩展性的重要性日益突出。例如在 1999 年 6 月 份,eBay 的服务停止了 22 个小时,导致大约 230 万的拍卖被中断,eBay 的股票也随之下降了 9.2 个百分点。 J2EE 集群就是一种能够提供高可用性、可扩展性以及容错性的流行技术。但是由于在 J2EE 规范中没有对集群做出规范,各个 J2EE 厂商就使用不同的方式来实现集群,这样就给系统架构师 和开发人员带来了很多麻烦。下面就是常见的一些问题: • 为什么带有集群支持的商业 J2EE 服务器产品如此昂贵?(是无集群支持产品的 10 倍) • 为什么在单机环境下创建的应用在集群环境中无法正常运行? • 为什么我的应用在集群环境下运行的非常慢,但是在单机模式下却没有这个问题? • 为什么我的集群应用在向其他厂商的服务器迁移时会失败? 要理解为什么会有这些限制,最好的方法就是研究它的实现,以揭开 J2EE 集群的面纱。 2. 基本术语 在我们开始讨论对于集群不同的实现之前,我想,了解一下集群技术的一些基本概念还是很有 意义的。希望本章不单单是告诉你这些概念和设计问题,也同时能够为你勾勒一下不同 J2EE 集群 实现的框架以便于理解。 2.1. 可扩展性 在一些大型系统中,很难提前预知最终用户的数量以及他们的使用行为,所以,可扩展性就是 指一个系统能够快速适应用户数量的增加。提高服务器处理能力的最直接的方法就是增加硬件资 源,例如 CPU、内存或者硬盘等。集群是解决这个问题的另外一种方式,它使得一组服务器共同 分担繁重的任务,但对于最终用户来说就像一台服务器。 2.2. 高可用性 通过向单机添加硬件来扩展系统能力的方案并不可靠,因为单一的服务器存在一个单点故障。 像银行系统、帐单系统这样的关键应用甚至连一分钟的停机都不能容许,它们需要在任何时间都是
  • 5. 可用的,并且要能够保证响应速度。集群技术就可以满足这个要求,它通过加入冗余服务器使得在 一个服务器出错而停止服务的时候,这些冗余的服务器可以继续服务。 2.3. 负载均衡 负载均衡是集群的另外一个关键技术,它通过将请求分发到不同的服务器来达到高可用性和高 效的处理能力。负载均衡器可以是一个 servlet,也可以是一个插件(例如 Linux 上的 ipchains), 甚至还可以是一个比较昂贵的内嵌了 SSL 支持的硬件产品。为了能够分发请求,负载均衡器还需 要做一些重要的工作,例如使用“会话粘滞”技术以确保来自同一个用户的请求会被转发到同一个 服务器;使用“健康检查”(或者“心跳监听”)技术来防止将请求转发到一个失败的服务器;有 时候负载均衡器还将参与“失败转移”的工作。 2.4. 容错 高可用的数据并不必是严格正确的数据。在 J2EE 集群中,当一个服务器实例失败了,在集群 中冗余的服务器就可以处理新到的请求,这样就保证了服务依然可用。但是在服务器失败的那一 刻,正在被处理的请求就可能无法得到正确的数据。那么,带有容错功能的集群就可以确保请求所 得到的数据是正确的,哪怕是服务器端出现了错误。 2.5. 失败转移 在集群中,失败转移是实现容错的一个关键技术。当最初的节点失败之后,在集群中选择另外 一个节点来完成处理。失败转移到其他节点可以通过编码实现,也可以由平台自动实现。 2.6. 幂等方法 如果一个方法使用同样的参数进行多次调用所得到的结果都一样,也就是说对于该方法的调用 次数不影响系统,那么这个方法就叫做“幂等方法”。例如“getUsername()”就是一个幂等方 法,而“deleteFile()”就不是幂等的。在讨论 HTTP 会话失败转移和 EJB 的失败转移时,幂等方 法是一个很重要的概念。 3. 什么是 J2EE 集群? 集群? 很天真的问题,不是吗?不过我还是要通过一些文字和图表来解释一下。通常来讲,J2EE 集 群包含了“负载均衡”和“失败转移”。
  • 6. 图表 1 负载均衡 如图表 1 所示,负载均衡就是指,在同一时刻,有很多客户端对目标对象进行请求,负载均衡 器就位于调用者和被调用着之间,它将请求分发到具有同样功能的冗余对象去,这样就实现了高可 用性和高性能。 图表 2 失败转移 如图表 2 所示,失败转移和负载均衡的工作方式有所不同。通常情况下,客户端对象的调用都 是成功的,当服务器端被调用对象失败之后,失败转移系统能够检测到这个失败,并且把后继的请 求转发到冗余的、可用的对象。这样就达到了容错的目的。 如果你还想对 J2EE 集群做更多的了解,你可能会问“哪些类型的对象可以被集群?”或者 “负载均衡和失败转移在我代码的什么地方发生?”,这些都是了解 J2EE 集群原理的好问题。实 际上,并不是所有的对象都能够被集群,也不是代码的任何地方都可以进行负载均衡和失败转移。 我们来看看下面的例子:
  • 7. 图表 3 代码示例 当 instance1 失败的时候,类 A 中的方法“business()”会不会负载均衡和失败转移到其他的 类 B 的实例上去?不,我可不这么认为。为了能够进行负载均衡和失败转移,在调用者和被调用者 之间必须要有一个拦截器,这个拦截器能够将调用请求转移到其他的对象。类 A 和类 B 的对象实 例在同一个 JVM 中运行,并且联系密切,在这些方法的调用中很难加入分发逻辑。 那么,哪些类型的对象可以被集群? - 只有那些可以部署在分布拓扑上的组件才可以被集群。 那么,负载均衡和失败转移可以发生在什么地方? - 只有在调用一个分布对象上的方法的时候。
  • 8. 图表 4 分布式环境 在一个分布式环境中,如图表 4 所示,调用者和被调用者在不同的运行容器中,并且有明显的 分界线将他们分隔开来,例如不同的 JVM,不同的主机,或者不同的进程。 当目标对象被客户端调用的时候,目标对象运行在自己的容器中(这就是为什么叫做分布 式)。客户端和对象服务器之间通过常见的网络协议进行通信。这种特点就使得有机会对于调用的 路由进行干预,从而实现负载均衡和失败转移。 如图表 4 中所示,浏览器通过 HTTP 协议请求远端的一个 JSP,这个 JSP 在 Web 服务器中运 行。对于浏览器而言,它只关心结果,不关心如何执行的。在这种情况下,就可以在调用者和被调 用者之间插入一些东西来实现负载均衡和失败转移。在 J2EE 中,分布式技术包括:JSP/Servlet, JDBC, EJB, JNDI, JMS, Web Service 等。在这些分布式调用中,可以进行负载均衡和失败转移。 在后面的章节我们会进行详细的讨论。 4. Web 层集群实现 Web 层的集群是非常重要的,也是 J2EE 集群的基础。Web 层集群的技术包括:Web 负载均 衡和 HTTP 会话的失败转移。
  • 9. 4.1. Web 层负载均衡 有很多种方式都可以实现 Web 层的负载均衡,一般来说,负载均衡器位于浏览器和 Web 服务 器之间,如图表 5 所示。 图表 5 Web 负载均衡 负载均衡器可以是 F5 这样的硬件,也可以是一个带有负载均衡插件的 Web 服务器,带有 ipchains 的 Linux 也可以作为负载均衡器。无论是使用什么样的技术,负载均衡器都有以下特征: • 实现负载均衡算法 当客户端请求到来的时候,负载均衡器能够决定把这个请求转发到后台的哪个服务器实 例。时下比较流行的算法有:轮循算法,随机算法和权重算法。负载均衡器总是试图让每 个服务器实例分担等同的压力,但是以上的三种算法都不能完美的、精确的达到这个目 的。一些比较尖端的负载均衡器能够在转发请求之前去探测一下服务器实例的负载。 • 健康检查 一旦某一个服务器实例停止工作,那么负载均衡器应该能够检测到并且不再把请求转发到 这个服务器实例。同样,当这个失败的服务器重新开始工作的时候,负载均衡器也要能够 检测到,并且开始向它转发请求。 • 会话粘滞 几乎所有的 Web 应用都会有一些会话状态。简单的可能记录了你是否登录,复杂一点的可 能记录了购物车的内容。因为 HTTP 协议本身是无状态的,所以会话状态就需要记录在某 个地方,并且和你的浏览器关联,以便于下次请求的时候能够很方便的取出来。当进行负 载均衡的时候,对于某一个确定的会话来说,把请求转发到上一次它所请求到的服务器实 例是一个很好的选择,否则的话,可能会导致应用不能正常工作。
  • 10. 因为会话状态是存储在某个 Web 服务器实例的内存中的,所以对于负载均衡器来说,“会话 粘滞”的特征是非常重要的。但是,如果某个服务器实例由于种种原因导致失败,那么在这个服务 器实例上的会话状态就会全部丢失。负载均衡器能够检测到这个错误并且不再把请求转发到这个服 务器实例,但是由于会话状态的丢失,可能会引发其他错误。这就是为什么要有“会话失败转移” 功能的原因。 4.2. HTTP 会话的失败转移 基本上,时下主流的 J2EE 厂商的集群产品中都实现了 HTTP 会话的失败转移,这样才能够确 保在服务器实例失败的时候,能够在不丢失任何会话状态的前提下继续处理客户端的请求。如图表 6 所示,当客户端浏览器请求一个有状态的 Web 应用的时候(步骤 1,2),Web 应用可能就在内 存中存储了会话状态,以便后继的使用,同时向浏览器发回一个能够唯一确定这个会话的标识(步 骤 3)。浏览器把这个会话标识用“Cookie”机制存储起来,下一次再请求这个 Web 应用的时候 把会话标识再发回给服务器实例。为了能够进行 HTTP 会话的失败转移,会话对象会被备份到某个 地方(步骤 4),以保证服务器实例失败的时候会话状态不会丢失。负载均衡器检测到这个服务器 实例失败之后(步骤 5,6),就会把请求转发到其他的服务器实例,当然了,这些实例上都部署 了同样的应用(步骤 7)。因为在之前已经把会话状态进行了备份,服务器实例就可以将会话状态 加载回来,然后正确的处理请求。 图表 6 HTTP 会话失败转移 在 HTTP 会话失败转移的实现中,要考虑以下问题: • 全局 HTTP 会话标识 如上所述,全局 HTTP 会话标识是在 Web 服务器内存中会话的唯一标识。在 J2EE 中,会 话标识的生成是依赖于 JVM 的。一个 JVM 实例可以运行多个 Web 应用实例,一个 Web
  • 11. 应用实例可以持有多个会话。要访问 JVM 中对应的会话对象,会话标识正是关键所在。在 HTTP 会话失败转移的实现中,必须要求不同的 JVM 不能产生两个相同的会话标识。因为 在失败转移发生的时候,一个 JVM 中的会话对象会在其他的 JVM 中备份和恢复使用。所 以,必须建立一个全局 HTTP 会话标识的机制。 • 如何备份会话数据 如何备份会话数据也是使得一个 J2EE 服务器产品不同于其他产品的关键因素。不同的厂 商的实现方式不同,在下面的章节会进行详细的介绍。 • 备份的周期和颗粒度 HTTP 会话状态的备份是会带来性能的消耗的,例如 CPU,网络带宽,IO 等。因此,备份 的周期和粒度对于性能是很有影响的。 4.2.1. 数据库持久方案 几乎所有的 J2EE 集群都允许你通过 JDBC 接口将会话状态存储到关系数据库中。如图表 7 所 示,这种方案就是在合适的时间让服务器的实例将会话数据进行序列化,然后存储到数据库中。当 失败转移发生的时候,另外的可用的服务器实例接替失败的服务器实例,并且从数据库中将会话状 态恢复加载进来。这种方案的关键点就是对象的可序列化,这使得内存中的会话状态是可持久的、 可传输的。关于 Java 对象序列化的更多信息,请参考: http://java.sun.com/j2se/1.5.0/docs/guide/serialization/index.html 图表 7 将会话状态备份到数据库 数据库事务是比较消耗资源的,所以,这种方案的最大的缺点就是当会话中的数据量大的时候 受到了性能的限制。大部分使用数据库持久的应用服务器都提倡尽量减少会话中对象的数量,这样 就使应用的设计和框架受到了限制,尤其是当需要在会话中缓存用户数据的情况下。 当然了,数据库备份方案还是有优点的:
  • 12. 易于实现。将请求处理和会话备份分离开来使得集群更健壮、更易于管理。 • 失败可以转移到其他的主机,因为数据库是可以共享使用的。 • 即使整个集群都失败了,会话数据都可以留存下来。 4.2.2. 内存复制方案 由于数据库持久方案存在性能方面的问题,所以很多应用服务器(Tomcat, JBoss, WebLogic, Websphere)提供了另外的方案:内存复制。 图表 8 会话状态的内存复制 基于内存复制的方案在备用服务器实例的内存中持久会话信息,而不是在数据库中进行持久化 (如图表 8 所示)。由于有很高的处理性能,这种方案非常流行。与数据库处理效率相比,在原始 服务器和备份服务器之间直接进行网络通讯的消耗是很小的,是轻量的。另外一点就是,在这种方 案中,省去了会话数据“恢复”的阶段,因为会话信息已经在备份服务器的内存中存在了。 “JavaGroups”是 Tomcat 集群和 JBoss 集群的通讯层,它是一个提供可靠的组通讯和管理的工 具包。它提供的核心的技术例如“组关系协议”(Group membership protocols)和“消息组播” (Message multicase)对于集群的运行是非常有帮助的。关于“JavaGroups”的更多信息可以参 考: http://www.jgroups.org/javagroupsnew/docs/index.html 的方案:多服务器复制 4.2.3. Tomcat 的方案:多服务器复制 内存复制的方案有很多的变化形式。第一种方法就是在集群中所有的节点进行复制。Tomcat 5 就是用这种方案实现的。
  • 13. 图表 9 多服务器复制 如图表 9 所示,当一个服务器实例上的会话状态发生了变化,就会在所有的服务器实例上生成 会话数据的备份。当一个服务器实例失败之后,负载均衡器就可以选择任何一个实例作为失败实例 的备份。但是这种方案存在一定的性能限制,当集群中的服务器实例很多的时候,网络通讯的消耗 就不能被忽略了,网络通讯的加重可能会成为一个性能瓶颈。 的方案:结对服务器( 4.2.4. WebLogic, WebSphere, JBoss 的方案:结对服务器(Paired servers)复制 ) 考虑到性能以及可扩展性,WebLogic, WebSphere, JBoss 都提供了内存复制的另外一种方 式:每个服务器实例都选择任意一个服务器实例作为它的备份服务器,并且将会话数据复制到这个 备份服务器实例。如图表 10 所示:
  • 14. 图表 10 结对服务器复制 在这种情况下,每个服务器实例都选择一个服务器实例作为备份,而不是选择全部的其他实 例。这种方案的优点就是,即是集群中加入了很多节点,也不会带来性能上的影响。 虽然这种方案有良好的性能和可扩展性,它还是存在一些限制的: • 使负载均衡器更加复杂。当一个服务器实例失败之后,负载均衡器一定要知道和这个失败 实例结对的实例是哪个。这样就减小了均衡器的范围,并且在某些硬件环境下无法满足这 个需求。 • 在处理请求的同时要进行复制。这样就降低了请求处理的能力,因为在处理请求的同时还 要分配出硬件资源来进行复制。 • 如果没有发生失败转移,那么在备份服务器实例上存储会话数据的内存就造成了浪费。也 有可能加重 JVM 进行垃圾收集的负载。 • 集群中的节点组成了结对服务器。所以,当主服务器实例失败之后,后继的请求都会转发 到结对服务器实例,那么对于结对服务器而言,它就需要处理双倍的请求,可能会因此引 发性能瓶颈。 为了克服以上的限制,很多厂商都进行了工作。为了克服上面所列的第 4 点,WebLogic 为每 个会话进行结对,而不是每个服务器实例。在一个服务器实例失败的时候,它的会话被分散到不同 的服务器实例,使得基本上负载还是分散的。
  • 15. 的方案: 4.2.5. IBM 的方案:集中状态服务器 对于内存复制,Websphere 提供了另外一种可选的方案:将会话信息备份到一台集中服务 器。如图表 11 所示: 图表 11 集中服务器复制 这种方案和数据库方案很类似。区别就在于一个专用的“会话备份服务器”替代了数据库服务 器,可以说是整合了数据库复制方案和内存复制方案的优点。 • 将请求处理和会话备份处理独立在不同的进程,使得集群更加健壮。 • 所有的会话信息都备份到同一台服务器,避免了其他服务器的内存浪费。 • 由于会话信息在集中服务器上,这个服务器又是被集群中所有节点共享的,所以失败可以 转移到任意服务器实例,因此大部分的负载均衡器都可以使用,并且在失败发生的时候, 能够保证负载的平均。 • 在应用服务器和会话备份服务器之间通过 Socket 通信,相对于和数据库的通信来说是轻量 的,所以性能也更好。 但是在失败发生之后的“恢复”阶段,这种使用集中服务器的方式在性能上比不上结对服务器 之间的直接复制,同时也增加了集中服务器的管理复杂性,并且在集中服务器本身容易形成性能瓶 颈。
  • 16. 的方案: 4.2.6. Sun 的方案:专用服务器 图表 12 专用服务器复制 Sun 的 JES 应用服务器的会话失败转移方案略显不同。如图表 12 所示,从表面上看来,它和 数据库方案,因为它使用关系型数据库存储会话信息并且使用 JDBC 接口访问这些信息。但是从内 里来看,JES 所使用的数据库叫做 HADB,是专门做过优化的,几乎所有的数据都存储在内存中。 所以,你可以认为这种方案和集中服务器方案更接近。 4.2.7. 性能问题 考虑一下,一个 Web 服务器上可以有多个 Web 应用,每一个应用又可以有大量的用户并发访 问,每个用户都会生成自己的会话来访问特定的应用。所有这些会话信息都需要被备份,以便在发 生失败转移的时候能够恢复。并且,更糟糕的是会话的信息在不停的改变:会话创建时、过期时, 会话中加入、删除、修改属性时,即时会话中的属性不发生改变,会话的最后访问时间都是随着用 户的访问而变化的(为了能够确定过期时间)。所以,在会话失败转移方面,性能是最大的问题。 所以,厂商通常都会提供一些可调整的参数来改变服务器的行为,以满足性能方面的需求。 4.2.7.1. 何时备份会话 当客户端请求被处理之后,会话的状态就会发生改变。考虑到性能的问题,实时备份会话是不 明智的选择,选择备份周期实际上是一种均衡。如果备份动作发生的太频繁,肯定造成性能压力; 如果备份周期过长,那么在这个周期内发生的失败转移就会丢失很多会话信息。不管是数据库复制 还是内存复制,下面这些都是设定备份周期的常用选项: • 基于 Web-methods 在 Web 请求结束后、向客户端给出响应之前备份会话。这种方式保证在失败发生的时候会 话信息是完整的。
  • 17. 基于固定周期 在一个固定的周期进行会话备份。这种方案无法保证会话信息的完整性,但是不是在每一 次请求之后都做会话复制,在性能上不会产生大的压力。 4.2.7.2. 备份颗粒度 当备份会话的时候,如何选择要备份多少数据?下面是在不同产品中比较常见的选择: • 全部会话 每次都备份全部的会话数据,这种方法能够保证为每个分布的 Web 应用都正确的存储了会 话。这种方案比较简单,并且被内存复制方案和数据库持久方案应用。 • 发生变化的会话 如果一个会话发生了变化,那么它就会被完整备份。当“HTTPSession.setAttribute()”或 者“HTTPSession.removeAttribute()”方法被调用的时候,就认为这个会话发生了改变。 所以,在会话中的属性发生变化的时候,你必须确保调用了这些方法(例如,在 session 中存储了一个叫做“student”的对象,当你要设置 student 对象的 name 属性时,可以这 么写: Student student = (Student)session.getAttribute(“student”); student.setName(“Jade”); session.setAttribute(“student”, student); //这个很重要 )。这个不是 J2EE 规范规定的,但是为了能够使这种方案正确工作,你必须这么做。对 于发生变化的会话才进行备份减少了每次备份会话的数量,相对于全部会话备份方案来 说,性能上大有提高。 • 发生变化的属性 在会话中的属性发生变化的时候,不是备份整个会话数据,而是仅仅备份这些变化的属 性。这样使得需要备份的会话数据量最小化。这种方案有最好的性能和最低的网络消耗。 为了能够使这种方案正确工作,你需要遵守一些规则:首先,每次会话状态发生改变的时 候,调用“setAttribute()”方法,并且要确保发生变化的这个对象是可序列化的。其次, 会话中的属性对象之间不能有交叉引用。对于会话中每个 Key 所指向的对象都应该是可序 列化的并且是分离存储的,他们之间不能有交叉引用,否则会导致序列化和反序列化不能 正确进行。以图表 13 举例说明,在一个集群中使用内存复制方式,会话中有一个 “school”对象和一个“student”对象,“school”对象有一个指向“student”对象的引 用。在某一时刻,“school”对象发生了改变并且进行复制到备份服务器,那么,在序列 化和反序列化之后,在备份服务器上的“school”对象包括了一张完整的对象图表和一个 指向“student”对象的引用。但是“student”对象可以被单独的修改,在“student”对 象发生改变之后,也会复制到备份服务器。在序列化和反序列化之后,从备份服务器恢复
  • 18. 回来一个“student”对象,但是此时它已经和“school”对象失去连接。尽管这种方案有 非常好的性能,但是以上的这些限制会影响到 Web 应用的设计和架构,尤其是你需要在会 话中缓存关系复杂的用户数据的情况下。 内存复制中的交叉引用情况 图表 13 内存复制中的交叉引用情况 4.2.8. 其他的失败转移实现 根据在以上章节介绍的,会话备份的颗粒度对性能有很大的影响。但是,就当前的实现来讲, 无论是内存复制还是数据库持久方案,都是使用序列化技术进行对象传输,这样会影响系统性能并 且也给 Web 应用的架构和设计带来了一定的局限性。很多 J2EE 的厂商都在寻找一种轻量的解决 方案来实现集群,通过选择粒度合适的分布对象共享机制来提高集群的性能。 4.2.8.1. JRun:使用 JINI : JRun 4 提供了一种内嵌的、基于 JINI 技术的集群解决方案,JINI 是一种分布计算的技术,它 允许在单一的分布计算空间上创建“联合”的设备和软件组件。它为查找、注册、租借提供了分布 系统服务,这些都是在集群环境中很有用的。另外一种基于 JINI 的叫做“JavaSpace”的技术也提 供了对象处理、共享、合并的特征,对于集群的实现也很有价值。关于 JINI 和 JavaSpace 的更多 信息请参考: http://java.sun.com/products/jini/2_0index.html 4.2.8.2. Tangosol:分布式缓存 : Tangosol Coherence™提供的分布式数据管理平台能够嵌入到大部分主流的 J2EE 容器产品中,从 而实现集群环境。Tangosol Coherence™也提供了叫做分布式缓存的技术,该技术能够在不同的 JVM 实例之间实现高效的对象共享。关于 Tangosol 的更多信息请参考: http://www.tangosol.com
  • 19. 5. JNDI 集群实现 根据 J2EE 规范,所有的 J2EE 容器都要提供一个 JNDI 的实现。JNDI 在 J2EE 中的最角色就 是提供一个使用资源的间接层,这样就使得 J2EE 组件拥有了更好的可重用性。 对于 J2EE 集群来说,拥有完整特征的 JNDI 集群是很重要的,比如几乎所有对 EJB 访问都是 从在 JNDI 树上查找它的 home 接口开始的。根据不同的集群架构,厂商实现 JNDI 集群的方式也 不同。 5.1. 共享全局 JNDI 树 WebLogic 和 JBoss 都有一个全局的、共享的、集群范围的 JNDI 上下文,客户端就从这个 JNDI 上下文查找和使用对象。通过使用 IP 组播,绑定在 JNDI 上下文上的对象就可以在集群范围 内复制,即使一个服务器实例失败之后,可以在 JNDI 树上查找到绑定对象。 图表 14 共享全局 JNDI 如图表 14 所示,共享全局 JNDI 实际上是由每个节点上的 JNDI 组成的,机群中的每个节点都 有自己的命名服务器,将其上的内容复制到集群中的其他节点。这样,每个命名服务器都拥有了其 他节点的 JNDI 树上对象的复制,这种冗余结构为全局 JNDI 树提供了高可用性。 在实际中,集群化的 JNDI 树主要有两个作用。可以用来进行部署,一旦在一个服务器实例上 部署了 EJB 模块,或者配置了 JDBC 和 JMS 服务,JNDI 树上的对象就会被复制到其他的服务器
  • 20. 实例。在应用的运行过程中,你的程序可以使用 JNDI API 访问 JNDI 树,读取或者存储对象,自 定义的对象也可以被进行全局的复制(在集群范围内)。 5.2. 独立的 JNDI 不同于 WebLogic 和 JBoss 使用共享全局 JNDI,Sun 的 JES,IBM 的 Websphere 还有其他一 些产品,都是使用独立的 JNDI,即每个服务器实例拥有自己的 JNDI。在一个采用独立 JNDI 的集 群中,一个成员服务器不知道也不关心其他服务器的存在。这是不是意味着不需要 JNDI 集群了? 由于几乎所有的 EJB 访问都是从在 JNDI 树上查找 home 接口开始的,不提供 JNDI 集群的集群基 本上就是无用的了。 实际上,独立 JNDI 的方案只有在所有的 J2EE 应用都是类似的时候才能够提供高可用性。如 果一个集群中的实例的配置以及其上部署的应用都是相类似的,这个集群就叫做相似集群。在这种 条件下,使用特定的管理工具,称作代理,能够帮助实现高可用性。如图表 15 所示: 图表 15 独立 JNDI Sun JES 和 IBM Websphere 在集群的每个服务器实例上安装一个叫做节点代理的工具,当部 署 EJB 模块或者绑定其他 JNDI 服务的时候,管理控制台向所有的代理发送命令以达到和共享全局 JNDI 树同样的效果。 但是,对于应用在运行过程中绑定到 JNDI 树、并且获取和使用的对象,独立 JNDI 无法提供 复制支持。其原因是因为 JNDI 在 J2EE 应用中的主要角色是提供一个间接层,这个间接层是为管 理外部资源而设置的,而不是为了管理运行时的数据。如果需要管理运行时数据,可以使用独立的
  • 21. LDAP 服务器或者带有 HA 功能的数据库来实现。IBM 和 Sun 都提供具有集群能力的 LDAP 服务器 产品。 5.3. 中央集中 JNDI 一些 J2EE 产品使用中央集中 JNDI 的方案,命名服务运行在一个独立的服务器上,所有的服 务器实例都向这个服务器注册 EJB 模块或者其他管理对象。 这个命名服务器也实现了高可用性,并且对客户端来讲是透明的,所有的客户端都是在这个命 名服务下去查找对象。由于这种方案增加了安装和管理的复杂性,所以很少被采用。 5.4. 初始化对 JNDI 服务器的访问 当客户端连接 JNDI 服务器的时候,需要知道服务器的 IP 地址和端口。如果使用全局共享 JNDI 树或者独立 JNDI 方案,都会有不止一个 JNDI 服务器。客户端应该先连接哪个?如何实现负 载均衡和失败转移? 从技术上讲,为了能够实现负载均衡和失败转移,软件的或者硬件的负载均衡器需要位于客户 端和 JNDI 服务器之间。但是很少有厂商采用这种方式,因为还有很多更简单的解决方案: • 对于 Sun JES 和 JBoss,通过在“java.naming.provider.url”参数中接受由逗号分隔的多 个 URL 来实现 JNDI 的负载均衡。例如, java.naming.provider.url=server1:1100,server2:1100,server3:1100,server4:1100 客户端就会一个一个的轮循请求这些服务器,直到找到第一个可用的为止。 • JBoss 也提供了一种叫做“自动发现”的机制。当“java.naming.provider.url”为空的时 候,客户端通过使用网络广播来发现一个引导性的 JNDI 服务器。 6. EJB 集群实现 EJB 是 J2EE 技术中非常重要的一部分,同时,EJB 的集群也是 J2EE 集群实现中面对的最大 挑战。 EJB 是为分布计算而生的,他们运行在独立的服务器上,Web 服务器组件或者富客户端应用 通过标准协议(RMI/IIOP)访问这些 EJB,你可以像调用本地对象方法一样调用远程 EJB 对象的 方法。实际上,RMI-IIOP 掩盖了你所调用的方法是在本地还是在远程,这个就叫做“本地/远程透 明性”
  • 22. 图表 16 EJB 调用机制 上图表示的就是远程 EJB 调用的机制。当客户端需要使用一个 EJB 的时候,不能直接调用这 个 EJB,客户端只能调用一个叫做“stub”的本地对象,这个本地的“stub”和远程的 EJB 有相同 的接口,起到代理的作用。stub 的作用就是接受客户端的调用,并且把这个调用通过网络委派到远 程的 EJB 对象。stub 和客户端应用运行在同一个 JVM 实例中,它知道如何通过 RMI/IIOP 在网络 上找到真正的对象。关于 EJB 更多信息请参考: http://java.sun.com/products/ejb/ 我们来看看在 J2EE 代码中如何使用 EJB,并说明 EJB 集群的实现。当我们要使用 EJB,应该: • 从一个 JNDI 服务器上找到 EJBHome stub • 使用 EJBHome stub 查找或者创建一个 EJB 对象,返回 EJBObject stub • 通过 EJBObject stub 进行 EJB 的方法调用 在上一章已经提及,负载均衡和失败转移可以在进行 JNDI 查找(第一步)的时候发生。那么 对于在调用 EJB stub(包括 EJBHome stub 和 EJBObject stub)过程中的负载均衡和失败转移, 主要有以下三种方式: 6.1. Smart Stub 我们知道,客户端可以通过 stub 对象调用远程 EJB,这个 stub 对象可以从 JNDI 树上查找 到,也可能是从 web 服务器下载 stub 的类文件。所以,stub 有以下特征:
  • 23. Stub 可以被动态的创建和使用,并且它的定义文件(类文件)无需存在于客户端环境的 CLASSPATH 或者 JAR 文件中。 图表 17 Smart Stub 如图表 17 所示,BEA WebLogic 和 JBoss 就是这样实现 EJB 集群的:在 stub 代码中加入特 殊的行为,但是这些代码对于客户端而言又是透明的(客户端程序对这些代码一无所知),这就叫 做“Smart Stub”。 Smart Stub 真的是很精妙的,它包含了一个可访问的目标实例的列表,也能够检测到目标实例 的失败,同时还包含了很复杂的负载均衡和失败转移的逻辑来分发请求。并且,如果集群拓扑发生 了变化(例如加入了新的服务器实例或者移走了服务器实例),stub 能够自动更新自己的目标列表 来反映新的拓扑,而不需要手动的重新配置。 在 stub 中来实现集群有以下优点: • 由于 EJB stub 在客户端环境中运行,节约了服务器的资源 • 负载均衡器和客户端代码结合在一起,并且和客户端生命周期保持一致。这样就消除了负 载均衡其的单点故障。如果负载均衡器坏掉了,通常客户端应用也已经停止了,这个当然 是可以接受的 • Stub 可以动态的下载和更新,这样就实现了零成本维护
  • 24. 6.2. IIOP 运行库 Sun JES 应用服务器使用另外的方式来实现 EJB 集群。负载均衡和失败转移的逻辑集成在 IIOP 运行库中。例如 JES 修改了“ORBSocketFactory”的实现,使它成为 cluster-aware 的,如 图标 18 所示: 图表 18 IIOP 运行库 这个修改版的“ORBSocketFactory”中包含了完整的负载均衡和失败转移的逻辑,这样就使 得 stub 很小并且不掺杂其他代码。由于这个实现位于运行库,它比 stub 更容易获取系统资源。但 是这种方案要求客户端使用特定的运行库,当和其他的 J2EE 产品交互的时候可能会有问题。 6.3. 拦截代理 IBM Websphere 使用 Location Service Daemon(LSD),LSD 的作用是 EJB 客户端的代 理,如图表 19 所示:
  • 25. 图表 19 拦截代理 在这种方案中,EJB 客户端通过查找 JNDI 获取一个 stub,这个 stub 中包含的路由信息指向 LSD,而不是指向真正的拥有这个 EJB 的应用服务器。所以,LSD 收到客户端的请求之后,根据 其负载均衡和失败转移的逻辑将请求分发到不同的应用服务器实例。这种方案会增加集群安装和管 理的工作量。 6.4. EJB 的集群支持 要调用 EJB 的方法,关系到两种 stub 对象:EJBHome 接口和 EJBObject 接口。所以,EJB 的负载均衡和失败转移可以在两个层面上进行: • 当客户端使用 EJBHome stub 查找或者创建一个 EJB 实例 • 当客户端通过 EJBObject stub 调用 EJB 方法时 6.4.1. EJBHome Stub 的集群支持 EJBHome 接口用来查找或者创建容器中的 EJB 实例,EJBHome Stub 是 EJBHome 接口的客 户端代理。EJBHome 接口不会持有客户端的状态信息,所以,来自不同容器的 EJBHome 接口对 于客户端来讲是没有差异的。当客户端发起一个 create()或者 find()调用的时候,Home stub 依照 负载均衡和失败转移算法在服务器复制列表中选择一个服务器实例,并把调用转发到这个实例上的 Home 接口。
  • 26. 6.4.2. EJBObject Stub 的集群支持 当一个 EJBHome 接口创建一个 EJB 实例之后,向客户端返回一个 EJBObject Stub,让客户 端调用 EJB 上的业务方法。系统保留了一个列表,记录了集群中部署了这个 EJB 的、可用的服务 器实例。但是能否通过 EJBObject Stub 将调用转发到任意一个服务器实例上的 EJBObject 接口, 取决于 EJB 的类型。 • 无状态的会话 Bean 对于无状态的会话 Bean 来说,可能是最简单的。由于不包含任何状态信息,所有的 EJB 实例都可以认为是无差异的。所以从 EJBObject 的方法调用可以负载均衡和失败转移到任 何一个服务器实例。 • 有状态的会话 Bean 有状态的会话 Bean 的集群和无状态的就有所不同了。我们都知道,有状态的会话 Bean 会在每一次成功的请求之后保留客户端的会话信息,从技术上讲,有状态会话 Bean 的集 群和 HTTP 会话的集群类似。通常来讲,EJBObject Stub 不能将请求负载均衡到不同服务 器实例,它会一直使用第一次创建 EJB 实例的服务器实例,这个服务器实例被称为“主实 例”。在请求处理过程中,会话信息会从主实例备份到其他的服务器,一旦主实例失败, 后备服务器就接替它的工作。 • 实体 Bean 从本质上来说,实体 Bean 是无状态的,但是它也能够处理有状态的请求。借助于实体 Bean 的内在机制,所有的信息都会备份在数据库中,看起来对于实体 Bean 而言,负载均 衡和失败转移就可以像无状态的会话 Bean 一样简单。但是实际上,大部分情况下,实体 Bean 都不是负载均衡的,也不能进行失败转移。根据设计模式的建议,实体 Bean 是被会 话 Bean 封装过的,因此,大部分对于实体 Bean 的访问都是通过内在的会话 Bean 访问本 地接口实现的,而不是由客户端直接调用的。这就使得负载均衡和失败转移变得无意义 了。 7. 对于 JMS 和数据库连接的集群支持 在 J2EE 中,除了 JSP、Servlet、EJB、JNDI,还有其他一些分布式对象。在集群实现中,这 些对象的集群可能支持,也可能不支持。 当前,有些数据库产品像 Oracle RAC 能够支持集群环境,可以配置多个同样的、并行的数据 库实例。 但是 JDBC 是一种强状态的协议,事务状态紧密的绑定在客户端和服务器端的会话上, 所以很难实现集群。一旦一个 JDBC 连接死掉,所有和这个连接相关的 JDBC 对象(译者注:例如 Statement, ResultSet 等)就全部死掉了,客户端需要重新建立连接。BEA WebLogic 通过使用
  • 27. “JDBC 多池”(Multi Pool)来实现这个重新连接的过程。(译者注:BEA WebLogic 8 之后的版 本中,JDBC 连接池(不是“多池”)有一些用来保护的选项,设置这些选项可以在连接失败之后 重新建立,而不需要客户端的额外编码) 大部分 J2EE 服务器都支持 JMS,但是不是完全支持。只有对于 JMS 代理才有负载均衡和失 败转移。对于 JMS 目标中的消息,几乎没有产品能够实现失败转移。 8. 关于 J2EE 集群的神话 否定! 8.1. 失败转移能够彻底避免错误 -- 否定! 在 JBoss 文档中,用了整整一章来警告你:“真的需要 HTTP 会话复制吗?”没错!有时 候,没有失败转移的高可用性解决方案也是可以接受的,成本也比较低。并且,失败转移并不像你 想象中那么健壮。 那么失败转移到底给我们带来了什么?可能有些人认为失败转移可以避免错误,你看,如果没 有失败转移,当服务器失败的时候,会话信息就丢失了,从而引发了错误;如果有的话,当服务器 失败的时候,会话信息能够从备份服务器恢复,请求可以被其他的服务器实例正确处理。可能你说 的是对的,但是这是需要前提条件的。 请注意在定义“失败转移”的时候,也定义了它的前提条件:“在方法调用之间”。如果你对 于远程对象有两次成功的调用,失败转移仅会出现在第一次调用成功之后和第二次调用之前。 那么,如果在服务器处理请求的过程中失败了会如何?答案是:处理会被中止,大部分情况下客户 端会收到错误消息,除非这个方法是幂等的(关于“幂等”请参考“基本术语”一章)。只有这个 方法是幂等的条件下,某些聪明的负载均衡器会尝试将请求失败转移到其他实例。 为什么“幂等”如此重要?因为客户端不知道处理到什么地方了,也不知道何时发生的失败。 方法刚刚初始化?还是已经完成了?客户端无从知晓。如果方法不是幂等的,那么调用这个方法两 次就会改变系统状态两次,从而导致不一致的状态。 你可能会认为在一个事务中的全部方法都是幂等的。毕竟,当失败发生的时候,事务会回滚, 事务过程中对系统状态所做的改变都回撤销。事实上,事务的界限可能无法包含所有的远程方法调 用的边缘。如果事务在服务器端已经提交,在返回客户端的过程中客户端宕了情况会如何?客户端 不知道服务器的事务是否成功了。 要想使应用中所有的方法都是幂等的是不可能的。所以,使用失败转移只能减少错误,但是无 法避免!以在线商店的应用为例,假定在任何时候一个服务器实例都可以处理 100 个在线用户,当 一个服务器失败之后,如果没有失败转移,所有的会话信息就会丢失,让这 100 个用户感到恼火。 但是如果使用了失败转移,可能只有 20 个正在处理中的用户能够觉察到失败的发生并且感到恼
  • 28. 火,另外 80 个用户可能正处在两次请求之间的思考时刻,这些用户的会话被透明的进行了失败转 移。所以,可以这样进行权衡: • 20 个用户和 100 个用户之间的不同影响 • 使用失败转移和不使用失败转移产品的成本 否定! 8.2. 单机应用可以透明的迁移到集群环境 -- 否定! 虽然一些厂商都在宣扬他们产品的弹性,但是不要相信他们。事实上,你需要从系统设计时就 应该考虑到集群,甚至还要影响到开发和测试阶段。 8.2.1. HTTP 会话 我们前面提到过,在集群环境中,对于 HTTP 会话的使用有很多的限制,这取决于你所用的服 务器的 HTTP 会话失败转移机制。最重要的限制就是会话中的对象必须是可序列化的,这就限制了 应用的设计。一些设计模式或者 MVC 框架会在 HTTP 会话中存储一些不可序列化的对象(例如 Servlet 上下文,本地 EJB 接口,Web Service 的引用等),这种设计在集群环境下无法正常工 作。其次,对象的序列化和反序列化都是很消耗性能的操作,尤其是在使用数据库持久的方案中。 所以,要避免在会话中存放大尺寸的或者大量的对象。如果你已经选择了内存复制方案,那么需要 注意会话属性中的对象不能有交叉引用。集群环境下另一个不同就是,只要会话中的属性发生了变 化,就必须调用“setAttribute()”方法,这个调用在单机的应用中不是必须的。在集群中这样调用 的目的是能够区分发生改变的属性和未改变的属性,在备份的时候只复制改变的属性,就能够提高 性能。 8.2.2. 缓存 据我的经验,大部分 J2EE 应用都使用了对象缓存机制来提高性能,并且主流的应用服务器产 品都提供了不同程度的缓存的机制以提高应用性能。但是这些缓存都是为单机环境提供的,只能在 同一个 JVM 实例中工作。我们之所以需要缓存机制,是因为有些对象比较庞大,创建一个新的实 例会有很大的消耗,所以使用一个对象池来重用这个对象实例而不是创建一个新的。只有在缓存维 护的成本低于重新创建对象实例成本的时候,才能够得到性能上的提升。在集群环境中,每个 JVM 实例都要维护自己的对象缓存池,并且要和其他实例同步以保证一致性,有时候这种同步的 性能还不如不使用对象缓存机制。 8.2.3. 静态变量 在当前的 J2EE 应用中,在架构中使用设计模式是很流行的。某些设计模式,例如 “Singleton”,会使用静态变量来实现多个对象之间的状态共享。这种方案在单机模式下运作正
  • 29. 常,但是在集群中会失败。集群中每个服务器实例都会在自己的 JVM 实例中保持一份静态变量的 复制,这样就打破了设计模式的机制。举例说明静态变量的使用:计算在线用户数量,简单的方式 就是使用一个静态变量,用户登录或者注销的时候增加或者减少这个变量的值,这个应用在单机模 式下工作正常,但是在集群环境中会失败。如果要使这种方案工作正常,可以考虑使用数据库存 储。 8.2.4. 外部资源 虽然 J2EE 规范中不建议使用外部资源,但是出于各种目的,外部 I/O 操作还是不可避免的。 例如,一些应用使用文件系统存储用户上传的文件,或者创建动态的 XML 配置文件。在集群应用 中,服务器无法将这些文件复制到其他实例。所以,就需要借助数据库存放这些文件,或者使用 SAN 来作为文件的存储点。 8.2.5. 特殊的服务 有一些服务只有在单机的环境下才有意义,例如时间服务:基于固定周期的事件。时间服务通 常用来进行自动的管理工作,例如日志文件的滚动、系统数据备份、数据库一致性检查以及冗余数 据的清理等等。一些基于事件的服务也无法移植到集群环境,例如在系统开始时候的初始化服务。 邮件通知服务也是由某种警告条件触发的服务。 这些服务都是由特定的事件触发的而不是由请求调用的,并且只能执行一次。这样的服务使得 集群中的负载均衡和失败转移就没有什么意义了。 有些服务器产品为这种服务做了一些准备,例如 JBoss 就使用一个叫做“集群的单一工具”来 定位这样的服务来保证服务执行一次并且仅仅执行一次。基于你选择的平台,这些服务器可能成为 向集群环境迁移的障碍。 8.3. 分布式结构比单一结构更灵活 – 未必 J2EE 技术,特别是 EJB,就是为分布计算而生。松耦合的业务方法、可重用的远程组件使得 多层应用渐成流行之势。但是我们不会把所有东西都做成分布式的。一些 J2EE 架构师认为将 Web 层和 EJB 层结合得更近一些可能会更好,这种讨论也一直在继续。
  • 30. 图表 20 分布式结构 如图表 20 所示,这是一个分布式的结构。当请求到来的时候,负载均衡器将请求转发到不同 服务器实例上的 Web 容器。如果这个请求包含了对 EJB 的调用,那么这个对 EJB 的调用将会重新 分发到不同的 EJB 容器。这样,请求就被负载均衡和失败转移了两次。 有些人并不看好分布式结构,他们指出: • 第二次的负载均衡是没有必要的,因为它不能均匀分配任务。每个服务器都有自己的 Web 容器和 EJB 容器,让 EJB 容器处理来自其他服务器 Web 容器的请求相比于处理自己 Web 容器中的请求来说,没有显出任何的优点。 • 第二次失败转移是没有必要的,因为它不能提高可用性。大部分服务器产品的 Web 容器和 EJB 容器在同一个 JVM 实例中运行,如果 EJB 容器失败了,大部分情况下,Web 容器也 失败了。 • 降低了性能。假如在应用中的一个方法调用多个 EJB,在这些 EJB 上进行了负载均衡,那 么每次请求都被分散到不同的服务器实例,这样就产生了不必要的、跨服务器交互。并 且,如果这个方法中有事务,那么这个事务边界就需要包含多个服务器实例,这是非常影 响性能的。 在实际的运行环境中,很多厂商(Sun JES, WebLogic, JBoss)对于 EJB 的负载均衡都做了 优化,优先选择同一个服务器上的 EJB。这样,如图表 21 所示,负载均衡仅仅在第一层(Web 容 器)进行,接下来的请求就在同一个服务器处理了。这叫做“搭配结构”。搭配结构属于分布式的 一种特殊形式。
  • 31. 图表 21 搭配结构 一个有意思的问题是,既然很多应用在运行的时候都是搭配结构的,为什么不使用本地接口代 替远程接口?这样能够大幅度提高性能。当然可以这么干!但是,当使用本地接口的时候,Web 组件和 EJB 就是紧耦合的了,使用的是直接调用而不是通过 RMI/IIOP。负载均衡器没有机会对本 地接口的调用进行干涉,所以,“Web+EJB”会按照一个整体进行负载均衡和失败转移。 但是不幸的是,在大部分服务器的集群环境中,使用本地接口依然会受到很多限制。EJB 是拥 有本地接口的本地对象,它是不可序列化的。所以,其限制就是不能在 HTTP 会话中存储本地引 用。有些产品,像 Sun JES 把本地接口以不同的方式处理,使它成为可序列化的,这样就可以在 HTTP 会话中存储本地引用了。 令一个有趣的问题是,既然搭配结构是这么流行并且有更好的性能,为什么还需要分布式结构 呢?很多时候,事情的发生总是有原因的,有些情况下,分布式结构是无法被取代的: • 不仅 Web 应用使用 EJB,其他富客户端应用也可以使用 EJB • Web 组件和 EJB 组件可能出于不同的安全层次,可能在物理上就是分离的。所以,可以使 用防火墙来保护运行重要 EJB 的主机。 • Web 和 EJB 极度不对称的情况下,分布式结构是更好的选择。例如,一些 EJB 是非常复 杂的,对资源的消耗也很多,就可以让它们运行在性能更好的主机上;同时,Web 层组件 (html, JSP, Servlet)都是比较简单的,使用便宜的 PC 服务器就可以满足需求。这样, 一个独立的 Web 服务器来接受客户端的请求,快速的提供静态的(HTML 和图像)或者简 单的 Web 组件(JSP 和 Servlet)服务。性能良好的 EJB 服务器用来进行复杂的计算,这 就更好的利用了投资。
  • 32. 9. 总结 集群环境不同于单机环境,J2EE 厂商的实现也各不相同。你应该在项目一开始就做好集群的 准备,以建立大规模的应用。选择能够满足需求的合适的服务器,同时也要选择适用于集群环境的 第三方软件或者框架,然后设计一个适当的框架,让你真正从集群中受益,而不是折磨。 10. 关于作者 Wang Yu, 技术工程师,技术架构顾问,在 Sub Microsystems 的 GPE 组工作。负责本地 ISV 支持,致力于重要 Java 技术 J2EE,EJB,JSP/Servlet,JMS,Web Service 的咨询和传播。邮件 地址:yu.wang@sun.com 11. 附录 A:中英文对照表 : 英文 中文 备注 Scalability 可扩展性,可伸缩性 Session Stickiness 会话粘滞 Failover 失败转移 Fail Tolerance 容错 Idempotent 幂等的 Round-Robin 轮循 Session 会话 Global HTTPSession ID 全局 HTTP 会话标识 Paired Server 结对服务器 Collocated Structure 搭配结构 翻译后记: 花了好多天的时间,终于把这篇文章翻译完了。首先向原作者致敬(别告我侵权噢☺)!可以说, 这篇文章是我看到的关于 J2EE 集群的最完整的阐述,并且也很客观,指出了集群的优点和需要注 意的事项。我不是学习语言专业的,英语也一般般,所以有些地方可能翻译的不到位。同时在翻译 的过程中比较保守,有些语句可能翻译得比较晦涩,觉得无法准确理解的地方可以参考原作。还望 大家对于其中的疏漏、错误之处海涵!