EJB
集群的实现
EJB
是
Java EE
重要的组成部分,实现
EJB
的集群也是最具挑战的部分。
EJB
技术也来源于分布式计算,可在独立的服务器上运行。
Web
服务器组件或胖客户端能从其他计算机通过标准
RMI/IIOP
协议访问
EJB
组件。调用远程
EJB
的方法和调用本地
Java
对象的方法一样。实际上,
RMI-IIOP
完全实现了对本地
/
远程调用的透明性。
上图展示了调用远程
EJB
的机制。当客户端要调用
EJB
时,不能直接调用
EJB
,它需要调用称为
“
存根
(stub)”
的本地对象,其接口与远程对象一致,充当着远程对象代理作用。
Stub
负责本地接收方法调用并将其传递到网络另一端的远程
EJB
上去。
Stub
在客户端
JVM
中运行,并通过
RMI/IIOP
向远程网络查找真实对象。
我们通过了解如何在代码中调用
EJB
来解释
EJB
集群的实现。如欲调用
EJB
,需要:
- 从JNDI服务器中查找EJBHome存根。
- 通过EJBHome存根查找或创建EJB对象,返回EJBObject存根。
- 通过EJBObject存根调用EJB方法。
负载均衡和失败转移可在
JNDI
查找的时候发生。当通过
EJB
存根
(
无论
EJBHome
还是
EJBObject)
调用方法时,厂商已通过不同的方法实现了
EJB
的负载均衡和失败转移。
智能存根
由于客户端通过存根对象访问远程
EJB
,存根对象可通过检索
JNDI
树获得,甚至可能让客户端透明地通过下载任意一台
web
服务器上的类文件也可获得。所以,存根的特点有:
存根可以在运行时动态地或通过编程的手段生成。存根的定义,即类文件并不需要在客户端环境的
classpath
下,也不需要包含在客户端运行时的
JAR
包中
(
因为可通过下载的方法获得
)
。
如上图,
BEA WebLogic
和
JBoss
采用在存根代码中添加特定功能来实现
EJB
的集群。这些代码透明运行在客户端。这种技术称之为智能存根技术。
智能存根之所以智能是因为它包含了能访问的目标实例。它能侦测到目标实例的失败,并使用复杂的负载均衡和失败转移的逻辑将请求转发至其他目标。此外,如果集群的拓扑图发生了变化
(
例如,有新实例加入或实例移除
)
,存根能自动更新目标列表来反映最新的变化情况。
在存根中实现集群的优点如下:
- 因为EJB存根运行在客户端,所以节省了很多服务器端的资源。
- 负载均衡器整合在了客户端代码中,并与客户端生命周期息息相关。这就避免了单点负载均衡器失败的情况。如果负载均衡器失败了,客户端也极有可能失败,所以对系统并没有太大影响。
- 存根能自动动态下载更新,最大程度降低了维护量。
IIOP
运行时库
Sun JES
应用服务器通过另一途径实现
EJB
集群。负载均衡和失败转移逻辑在
IIOP
运行时库中实现。例如,
JES
修改了
ORBSocketFactory
的实现,让其支持集群,如下图。
修改过的
ORBSocketFactory
拥有执行负载均衡和失败转移的所有逻辑和算法,同时也保持了存根的简洁。因为实在运行时库中实现的,所以比起在存根中实现的办法来说它能更容易获取系统资源。但是这种方法在客户端需要指定的运行库,在与别的
Java EE
产品整合时可能会有一些麻烦。
监听代理
(Interceptor Proxy)
IBM WebSphere
引入了位置服务后台线程
(LSD
,
Location Service Daemon)
,其作为
EJB
客户端的监听代理,如下图所示。
在这种方法中,客户端从
JNDI
中查找并获得存根。存根包含了通向
LSD
的路由信息而不是直接到
EJB
驻留的应用服务器。
LSD
接收所有的请求并根据负载均衡和失败转移策略来决定将它们分别发送至哪台实例中。本方法使集群的安装和维护工作量加大。
对
EJB
的集群支持
要调用
EJB
的方法,需要两种类型的存根对象:一个是
EJBHome
接口另一个是
EJBObject
接口。这就意味着对
EJB
的负载均衡和失败转移可能在两个阶段发生:
- 当客户端使用EJBHome存根创建并查找EJB对象时
- 当客户端使用EJBObject存根来调用EJB方法时
EJBHome
存根的集群支持
EJBHome
接口用来创建或查找在
EJB
容器中的
EJB
实例,
EJBHome
存根是
EJBHome
接口的客户端代理。
EJBHome
接口不保持客户端的任何状态。所以,不同
EJB
容器的
EJBHome
接口对客户端来说都是相同的。当客户端调用
create()
或
find()
方法时,
home
存根根据负载均衡和失败转移算法从复制列表中选择一个服务器,并把对
home
接口的调用传递到那台服务器上。
EJBObject
存根的集群支持
当
EJBHome
接口创建
EJB
实例时,它将
EJBObject
存根返回客户端让用户调用
EJB
方法。系统已经有了一个集群中可用服务器的列表,在这些服务器上都部署了
EJB
组件,但是根据
EJB
的类型,不能将由
EJBObject
存根发起的方法调用发送至判断服务器实例的
EJBObject
接口。
无状态的会话
bean
是情况最简单的:正因为没有保存状态,所有
EJB
实例都可考虑为相同的,所以从
EJBObject
来的方法调用可被负载均衡或失败转移至任何参与的服务器实例上。
有状态的会话
bean
就不太一样了。有状态的会话
bean
需要保持特定客户端连续请求时的会话状态信息。总的来说,对有状态的会话
bean
实施集群和对
HTTPSession
实施集群差不多。通常,
EJBObject
存根不会将请求转发至之前服务的实例之外的其他实例;它们通常会一直使用
EJBObject
创建时的实例,可以称之为
“
主实例
”
。在处理过程中,状态信息需要从主实例备份至其他服务器。如果主实例失败了,其他备份服务器将进行接管。
实体
bean
从根本上说还是无状态的,尽管它也处理有状态的请求。实体
bean
本身将所有信息数据备份至数据库中。感觉好像对于实体
bean
来说,负载均衡和失败转移都较容易实现。但实际上,实体
bean
不是总能得到负载均衡和失败转移的。正如设计模式中的建议,实体
bean
总是被包装在会话
bean
后。因此,大多数对实体
bean
的访问发生在正在处理的会话
bean
的本地接口中,而不是远程客户端。这就让负载均衡和失败转移失去了意义。
对
JMS
和数据库连接的集群支持
在
Java EE
中,除了
JSP
、
Servlet
、
JNDI
和
EJB
之外还有其他的分布式对象。这些对象可能不能在集群实施中得到全面支持。
现在,一些数据库产品,例如
Oracle RAC
和
Sybase SDC
支持集群环境并可部署成多点同步的数据库实例。但是,
JDBC
是高度有状态的协议,其需要保存事务状态并紧密维护客户端和服务器的
socket
连接。所以,比较难实现集群。如果
JDBC
连接失败了,所有与连接相关的
JDBC
对象都会失败,而重新连接需要客户端代码的支持。
BEA WebLogic
使用
JDBC
连接池来降低重新连接的复杂度。
JMS
在大多数
Java EE
服务器中都支持,但不是完全支持。负载均衡和失败转移只在
JMS broker
上实现了,一些产品还支持
JMS
消息目的地的失败转移功能。