高影响力的Web层群集,第一部分:利用JavaGroups扩展

随着J2EE平台的日益成熟,为了在Web层上扩展Web服务及应用,可以在联网的群集配置中 部署廉价服务器(commodityservers )。这些廉价服务器通过廉价的LAN硬件相互连接在一 起,可以提供成本合适的群集解决方案。最后一个群集难题在于软件方面。在本系列文章中 ,SingLi分析了三种可以允许高影响力Web层群集的开放源代码软件基础,首先介绍 JavaGroups。

在 Internet 上,基于J2EE的Web应用及Web服务的流行,将同时处理上千个(或者更多) 用户的需求推向前台。在许多商业部署中,这不再是一个“未来可用的”奢侈品 ,而是成为必需品。在这种竞争的商业环境中,一个在线商店如果顾客一多就挂起或崩溃, 它是不会生存下去的。尽管针对 J2EE 模型的事务层(用于数据库、事务监听器、消息队列 ,等等)的可扩展解决方案已经广泛可用,但是用于在Web层扩展 Web 应用或服务的解决方 案还刚刚浮现。在该系列文章中,我们将要看几个可应用于在 Web 层扩展应用的软件技术。 每种技术都采用一种不同的方法,并且解决一个稍微不同的问题集合。在这第一篇文章中, 我们将探讨一种流行的开放源代码的分布式通信基础,那就是 JavaGroups。

在Web层扩展应用

扩展Web层有多种经过检验而可靠的方法。增加一个服务处理的并发会话的数量最直观的 方法是向应用服务器增加资源。这些资源包括内存与磁盘空间(存储资源)和 CPU (计算资源) 。图 1 演示了可扩展性的这一单机器方法:

图 1. 在单个 SMP 服务器上扩展 Web 层

该方法的障碍在于处理器使用的地址空间上的限制和廉价对称多处理器(symmetric multiprocessor,SMP)硬件上的限制(合理的成本)。服务器配置超过四个处理器,就可能需 要专用的或定制的硬件来处理资源负载,并且对系统的掌握和维护很快就变得昂贵起来。这 些限制给我们利用单服务器解决方案在 Web 层上所能处理的用户会话数量施加了一个实际的 上限。

除了在会话方面的限制以外,由于它的单点故障,单服务器解决方案通常也不是一种健壮 的解决方案。可用性是不连续的,因为当单个服务器出现故障时,服务就是不可用的。尽管 该问题有多种可行的技术上的解决方案(比如可热切换的、冗余的备份资源),但是这些解决 方案都非常昂贵。

微型和小型计算机系统厂商早就已将 群集作为可伸缩性问题的一个可行的解决方案。群 集允许一组服务器 (通常是松散耦合的)逻辑上作为单个服务器运行。群集的优点包括:

消除了单点故障。

高服务可用性(如果群集中的多个服务器可以处理同一服务)。

通过将请求转移给处理同一服务的负载最少的服务器所带来的负载均衡。

近来,由于许多利好因素,群集已经“纳入主流”:

J2EE Web层容器 (应用服务器) 技术终趋成熟,并且它们的状态管理和操作模型也得到了 较好的规范和理解。通过在一群服务器之间复制 Web 层容器的状态,您可以实现可伸缩的服 务解决方案。

廉价的基于 PC 的服务器的成本达到了历史较低水平 (而每台服务器的 CPU 处理能力则 持续增长),使得群集的成本比以前更加可以承受。

高速的基于 LAN 的互连广泛可用并且便宜。同时,套接字、TCP/IP 和更高级别的网络 API 使得编程需求非常简单。现在您使用用于群集的基于 LAN 的互连来取代专用的硬件连接 /总线互连。

开放源代码 Linux 操作系统的广泛采用,甚至使得定制的群集解决方案可以以非专用的 方式实现、维护和维持下去。

尽管这些 LAN 连接的、基于廉价服务器的群集解决方案,通常不具有那些良好校准的、 精确调整的、定制设计的专用系统那么强硬的保证,但是它们确实为实现可伸缩的、可用的 、负载平衡的系统提供了一种高性价比的解决方案。图 2 演示了该基于廉价硬件的解决方案 的拓扑结构:

图 2. 利用联网的服务器群集扩展 Web 层

当然,有了适当的硬件只是解决方案的一半。不要为每个特定的应用编写定制的网络通信 软件,理想情况是为创建群集解决方案找到一些通用的“胶水”软件。这是实际 研究的一个新兴领域,并且是使廉价群集解决方案成为现实的决定性因素。在讨论 JavaGroups (开放源代码的分布式通信基础) 如何提供这种胶水之前,我们首先很好地来了 解一下 Web 层扩展问题。

了解 Web 层扩展问题

设想一个购物者在一个在线商店里。她已经浏览了商品目录,并将几件商品放到了她的购 物车中。典型的购物车实现在服务器上管理一个会话。这个会话的关键字要么作为一个 cookie存储在客户端浏览器上,要么存储为附加了会话 ID 信息的新的 URL。来自她的浏览 器的后续请求将会发送回这一会话 ID,使得服务器可以跟踪她的会话。许多购物者可能会同 时在线,而在线商店服务必须管理所有的会话。在我们的场景中,我们假设这些会话是非持 久的,并且假设它们由在线商店服务存储在内存中。

扩展问题是,如果在线商店站点实际上由一群机器来服务,对特定会话的连续请求必须都 被定向到同一台机器 (因为该会话只存储在这一台机器上)。通过具体化会话以及在一群服务 器之间复制会话,群集中的所有服务器都可以为任何复制的会话取得传进来的购物者请求。

完全有可能编写我们自己定制的网络软件来处理这样的会话复制。然而由于网络硬件故障 的可能性,这样的软件很难编写、测试和维护。有幸的是,JavaGroups 为群集中的会话复制 提供了马上可以部署的解决方案。

为了理解这一复制是如何工作的,以及为什么有些开放源代码的应用服务器选择了 JavaGroups 来进行会话复制,让我们更详细地来了解一下 JavaGroups。

JavaGroups 体系结构

JavaGroups 是一个软件工具包 (API 库),用于设计、实现和实验分布式的系统解决方案 (更确切地说,在理论领域中叫做 进程组通信)。JavaGroups 的体系结构分为两个相关的部 分,如图 3 所示。一个叫做 通道的 Java API 抽象是两个部分相分离的边界。

图 3. JavaGroups 概念上的体系结构

这一边界也分离了潜在的 JavaGroups 用户的两个截然不同的角色: 分布式的应用开发 者和 协议实现者。

JavaGroups 用户

在通道边界的上部是分布式的应用开发者,他们将使用 JavaGroups 作为基础来执行分布 式的操作。事实上,这就是我们的角色——我们是分布式的应用开发者,将使用 JavaGroups 来实现 Web 层群集。

在通道边界的下部,JavaGroups支持一个灵活的、可堆叠的、可运行时配置的、100%纯 Java 协议的栈框架。这对于通信协议的实验者、设计者和实现者来说是想法变成现实的地方 。使用该框架,您能够以一两页 Java 代码编写和测试一个适度复杂的协议实现,并且易于 调试、维护和改进。在协议框架级别编程超出了本系列文章的范畴,但是感兴趣的读者可以 参考 参考资料部分的”JavaGroups 用户指南” 。

虚拟同步与随机广播

JavaGroups 微协议的基本集合中包括 JChannel 实现,为协议栈的服务质量提供了非常 强大的保证。组管理服务(group management service,GMS)基于虚拟同步模型 (参见 参考 资料,以找到一本关于该主题的参考书)。每个成员根据时间顺序安装一个序列的视图 (成员 关系列表) ,因而保证能接收到视图之间的同一组消息。一个视图中发送的任何消息也能保 证在该视图中接收到。尽管对于小的成员关系是稳定的,但是这种实现对于非常大的成员关 系却缺乏可伸缩性。事实上,JavaGroups 中的虚拟同步实现对于大的组成员关系是相当有问 题的。

为了支持非常大的成员关系——这种情况下,随机广播比绝对保证更加可接受—— JavaGroups 提供了一组基于 随机广播的协议。随着成员关系的增长,这些协议是可扩展的 和稳定的。

JavaGroups 通道

通道是一个类似于套接字的实体,具有大量添加进来的值,以使我们的分布式编程更加容 易。作为分布式的应用开发者,我们在通道上面编程,而通道为我们访问 JavaGroups 提供 的丰富的协议支持提供保障。

就跟套接字一样,通道有一个与之相关的地址,并且是我们用来发送和接收数据的对象。 但是与套接字不同的是,与通道相关的地址是不透明的 (应用开发者不必知道该地址的物理 细节),并且我们从通道发送和接收的数据是消息——是比套接字的分组更高级别的实体。

为了对进程组通信有用,一个通道将与一组进程相关。每个通道都有一个与之相关的文本 化的通道名称 (有时叫做 组地址) ,并且具有相同名称的通道实例在逻辑上属于相同的组。

分布式网络中的组成员关系管理解决(或者编程)起来不是一个容易的问题。事实上,组 成员关系管理是 JavaGroup 最有价值的特性之一。通道抽象下面的协议栈可以为我们执行组 成员关系管理,在成员加入和离开组时对它们进行跟踪。您甚至可以选择使用基于虚拟同步 的算法或者是基于随机广播的算法 (参见 虚拟同步与随机广播)。

地址和通道名称一起惟一地标识一个通道实例。要使用通道实例,必须首先与之连接。一 次只可以有一个进程连接到一个通道实例。您也可以断开与一个通道实例的连接,以将它释 放给其他进程使用,也可以关闭一个通道实例以永久地禁用它。图 4 演示了通道实例如何方 便进程组通信:

图 4. 利用 JavaGroups 通道进行通信

图 4 显示了三台机器上四个通过 LAN 的通道实例。注意,所有的通道实例具有相同的通 道名称,但是具有不同的物理地址。在这种情况下,每个物理地址包含一个 , ,因而允许同一主机(一个 IP 地址)上的多个物理地址。另外注意,只有一个进 程连接到每个通道实例。四个进程全都属于同一个组。

进程组中的通信

您可以通过通道发送一个消息到组的一个特定成员 ( 单播),或者发送到组的所有成员 ( 多播)。要发送消息到组的特定成员,您需要它的地址。

既然通道为我们管理组成员关系,因此您总可以通过从通道检索成员关系列表来获得当前 “视图”中的成员。另外,您也可以在视图更改(成员关系更改通知) 发生时从通 道了解到这一更改。图 5 演示了分布式应用开发者和通道抽象之间职责的分离:

图 5. JavaGroups 通道的功能视图

可重用的通信编码模式

为了进一步简化分布式的应用编程,JavaGroups 以 Java 类的形式提供了一组经常使用 的通信编码模式。您可以使用多个这些 编程模式(也叫做 构造块) ,来代替或补充对通道抽 象的直接访问。

在 org.javagroups.blocks 包中可以找到所有这些模式。表 1 显示了最有用的构造块的 部分列表:

表 1. 编码模式构造块

类名称 描述 PullPushAdapter 减少用户在通道中检查入站消息的需要。用户注册一个侦听器,而适配器 将回调入站消息的收据或者视图的更改 MessageDispatcher 封装一个到所有成员的请求的同步发送,并关联响应的收据。可以等待第 一个响应、所有响应、特定数量的响应、大多数响应,或者一直等待到超时。通过注册一个 MessageListener ,可以使用的API将更加丰富。另外,用户可以注册一个 RequestHandler , 以处理到通道的入站请求 RpcDispatcher 位于 MessageDispatcher 的上面一层,添加远程方法调用语义到消息分配 器管理的场景。允许用户调用远程方法,并且关联来自所有或一部分成员的返回值。通过对 用户提供的服务器对象使用反射,还支持来自其他 RpcDispatcher 实例的入站远程调用。 ConnectionTable 一个 TCP 连接管理器,它使用线程池来处理入站连接。重用现有的出站 TCP 连接来发送消息

编码模式可适用于许多分布式设计,并且特别地被创建用来与 JavaGroups 通道很好地协 同工作。例如,通过对 RpcDispatcher 编程,您可以大大地减少涉及远程过程调用语义的分 布式应用所需的代码。

马上可以使用的分布式数据结构

org.javagroups.blocks 包中的其他编码模式提供完整的、马上可以使用的、高级别的分 布式数据结构,其中的一部分列表显示在表 2 中:

表 2. 高级别的分布式数据结构作为构造块

类名称 描述 ReplicatedTree 管理一个完整的分布式树数据结构,可靠地将所有更改复制到组成员。任 何成员都可以添加和删除节点 DistributedHashtable 实现一个复制的hash表,该复制的hash表将hash表的更改传播到所有组成 员 NotificationBus 自包含的构造块 (即创建自己的通道) ,它实现一个通知总线,在该通知 总线中,消费者可以为生产者发送的通知进行注册。每个组成员可以参与任何一个或两个角 色。被设计为支持一个复制缓存的实现

插入式通道实现

到目前为止,我们已经谈论了 JavaGroups 通道,就好像它是一个具体实现一样。但是, 通道实际上是 JavaGroups 中的一个抽象的类。实际上,当前的 JavaGroups 分布带有多个 通道实现,如图 6 所示:

图 6. 通道抽象和具体的实现

JChannel

EnsembleChannel 通过一个用 Java 编写的连接器访问 Ensemble (参见 参考资料), Ensemble 是一个健壮的进程组通信基础 (非Java)。

通过创建您自己的通道实现也可以扩展 JavaGroups。

利用 JavaGroups 进行编程

JavaGroups 的一个典型使用场景涉及以下步骤:

实例化一个通道并初始化必需的协议栈。

连接到通道。

开始发送消息或者处理入站消息 (有可能是在构造块的帮助下)。

断开连接并关闭通道。

假设我们使用的是 JChannel, 我们只要设置一个初始字符串就可以创建一个协议栈 (或 者也可能用到一个外部的基于 XML 的配置文件——参见 参考资料 部分中的 “JavaGroups 用户指南”链接,以了解基于 XML 配置的更多信息)。清单 1 是 这样的字符串的一个例子:

清单 1. JavaGroups 初始化的配置字符串"UDP: PING: FD(timeout=5000): STABLE:" +"VERIFY_SUSPECT(timeout=1500):MERGE:" +"NAKACK:UNICAST(timeout=5000)" +":FRAG:FLUSH:GMS:STATE_TRANSFER:" +"QUEUE"

字符串的每个组件用冒号 (:) 分开,分别指定了一个微协议。每个微协议实现一个可组 合的协议特性或质量。实际上,每个微协议由一个相同名称的 Java 类实现,并且由 JChannel 在运行时加载。许多这些微协议都可以在 org.javagroups.protocol 包中找到, 但是协议设计者可以使用任意的包名称。栈中指定的每个微协议可以有一个或多个对应的属 性,可以在初始字符串中的括号中设置这些属性。协议栈是在运行时自底往上建立的,根据 初始字符串,逐层增加微协议。

运行时可配置、可堆叠的微协议

表 3 显示了一些经常使用的微协议的描述,其中包括我们的样例初始化字符串所使用的 一个微协议。要了解单个属性细节的更多信息,请参见 参考资料部分到”JavaGroups 用户指南”的链接。

表 3. JavaGroups 微协议

微协议名称 描述 CAUSAL 组中消息的原因排序。实现使用一个矢量时钟 FD 使用 heartbeat 协议的故障检测。根据成员关系列表中的排序, Heartbeat 消息被发送到邻居成员 FD_SOCK 基于 TCP 套接字的故障检测。基于环 的 ping 被在邻居成员之间发送。 当所有成员都在同一物理主机上时工作得最好 FD_PID 使用进程 ID 的故障检测 (本地 JNI 代码,以获得所需的 PID)。只能在 同一台主机上 (一个 IP 地址) 工作 FD_PROB 使用随机算法的故障检测。组的每个成员发送 heartbeat,并维护其他成 员的 heartbeat 计数 FLOW_CONTROL 流控制实现,限制了消息收据之间发送的消息的最大数量 FLUSH 以一致的方式跨所有成员清除所有的消息。通常是在视图更改之前执行 FRAG 消息分段和重新装配(Message fragmentation and reassembly)。确保 较大的消息在沿着栈往下发送之前被分为 FRAG_SIZE大小的段。分段的消息在沿着栈往上发 送之前在接收方被重新装配 GMS 组管理服务。基于虚拟同步模型管理组成员关系 MERGEMERGE2 合并分离的子组。当网络由于故障而分离成多个部分时就形成了子组 NACKACK 实现可靠的传输。基于消息序列号请求丢失消息的重新传输。确保从每个 起始通道发出的消息的正确排序 JMS 将 Java Message Service 用于传输。与任何 JMS 实现协同工作 STATE_TRANSFER 实现状态传输协议,使得新成员可以从同等物(coordinaTor)或所有成员 获得现有的状态。需要 FLUSH 微协议在协议栈上 UNICAST 实现可靠的单播传输。请求丢失消息的重新传输,并确保发出消息的正确 排序 VIEW_ENFORCER 直到接收到第一个 VIEW_CHANGE 才丢弃消息。客户端只有在成为组成员后 才需要处理消息 STABLE 实现分布式的垃圾收集协议 (也就是说,删除所有已被所有组成员接收到 的消息) VERIFY_SUSPECT 发送消息以确保以前怀疑的成员已真正崩溃(crashed) UDP 一般用作组消息传输的最低层。IP 多播用于组广播,而 UDP 用于点到点 通信 PING 用于引导成员关系管理。使用 IP 多播 “ping” 消息来定位成 员,然后再请求这些成员加入组中

另外,JavaGroups 还支持一组基于随机广播的协议,这些协议可以扩展到非常大的成员 关系 (参见 虚拟同步与随机广播)。表 4 显示了部分列表:

表 4. 基于随机广播的微协议

协议 描述 pbcast.GMS 组管理服务,基于随机广播 (杂谈)。不需要 FLUSH pbcast.FD 基于杂谈(gossip) 的被动故障检测。不发送 heartbeat 消息 pbcast.PBCAST 实现随机广播,有规律地以杂谈的方式发送到成员关系的一个随机子集 pbcast.STABLE 实现分布式的垃圾收集协议 (也就是说,删除所有已被所有组成员接收到 的消息) pbcast.NAKACK 对丢失消息的重新传输和消息的顺序传送的否认实现 pbcast.STATE_TRANSFER 将随机广播用于状态传输实现。不需要 QUEUE

为了穿过 WAN 和防火墙,JavaGroups 也提供了微协议支持,见表 5:

表 5. 用于穿越 WAN 和防火墙的微协议

TCP 用于取代 UDP 作为最低层传输。通过 TCP 连接发送多个单播消息到成员 ,而不是发送多播消息 (不可能)。已经内置了可靠性、FIFO 排序和流控制 TCPPING 使用一组已知的成员来引导通过 TCP 的成员关系管理 TCPGOSSIP 使用一个外部杂谈 (参见 参考资料) 服务器,来为成员关系管理的引导定 位成员的初始集合 TUNNEL 当用于代替 UDP 或 TCP 作为最低层传输协议时,启用通过防火墙的隧道 技术。与防火墙外的一个 JavaGroups Router 进程协同工作

利用一个可视化购物车来探讨会话复制

为了看看我们是如何将 JavaGroups 用于会话复制的——启用 Web 层群集——我们可以 创建一个样例可视化购物车,叫做 JGCart。JGCart 表示 Web 应用服务器管理的单个会话中 的一个 x 线视图。假设每个应用服务器实例上有上百个这样的购物车。图 7 显示了该可视 化购物车的 GUI:

图 7. 可视化购物车 (JGCart) 的 GUI

购物车 GUI 和事件流

在购物车的上面是一个产品目录。通过选择标签,我们可以选择任何种类的产品。点击商 品边上的 Buy 按钮,就可以将该商品添加到下面的购物车中。购物车清楚地显示了我们已经 订购的商品——包括价格和数量——并且计算出了总价格 (单价乘以所订购的数量)。这是应 用服务器内单个购物车会话的可视化表示。在任何时候,一个应用服务器可能在内存中管理 有许多这样的会话。我们可以使用 JavaGroups 以一种易于编程和维护的方式来启用这样的 会话的复制。

图 8 显示了我们的 JGCart 应用中的 GUI 组件的层次。整个 GUI 是使用 Swing GUI 库 创建的。

图 8. JGCart 的 GUI 组件装配

GUI 的上半部分是 CatalogUI 组件。CatalogUI 是一个 JPanel 组件,其中带有一个 受 管 JTabbedPane ,这个 JTabbedPane 显示一系列 CatalogItem 组件。每个 CatalogItem 都是 JPanel 组件,各自带有一个 JButton 和两个 JLabel 组件。JButton 就是 Buy 按钮 ,而两个 JLabel 显示每个商品的描述和价格。

CatalogUI 通过提供一个 seTorderListener() 方法,支持 Buy 事件的转发。 OrderListener 接口用于每当一个 Buy 按钮被点击时转发一个来自 CatalogUI 组件的 OrderEvent。图 9 显示了事件转发动作:

图 9. JGCart 中的事件流

在图 8 中 GUI 的下半部分有一个 OrderList 组件。该组件是一个 JPanel, 其中带有 一个 受管JTable ,这个JTable在任何时候都会显示购物车的内容。受管 JTable 有一个定 制的模型 (包含显示的数据),就像由我们的 OrderTableModel 类实现的一样。该定制模型 确保我们在会话中——在 CartState 类的一个实例中——维护的状态与受管 JTable 中显示 的状态同步。通过使用 OrderTableModel.changeData() 方法,我们可以在任何时候更新模 型中的数据 (并显示这些数据) 。

编程 GUI 和连接事件流

在清单 2 中 (以红色高亮显示),我们可以看到如何通过 JGCart 类的 CreateUIandPrepChannel() 和 addOrderItem() &#160&#160 方法将 CatalogUI 组 件连接到 OrderList 组件。

注意, addOrderItem() 方法不是由于 Buy按钮点击事件而直接被调用的。相反,点击 Buy 按钮会产生一个 到群集的所有成员 (包括发送消息的成员) 的AddItemMessage 消息的 广播。实际上, addOrderItem() 方法是在该消息的处理期间,通过 MessageListener 接口 (由 JGCart 类实现)的 receive() 方法调用的。这将有效地复制购物车中的所有更改到该组 的所有成员。

如果您对所有 GUI 类的详细操作感兴趣,请参见 参考资料部分,以下载源代码。

清单 2. 在 JGCart 中创建 GUI 及转发事件private void CreateUIandPrepChannel() {  mainFrame=new JFrame();   ...   subPanel = new JPanel();   subPanel.setLayout(new GridLayout(2,1));   catUI = new CatalogUI();   catUI.addOrderListener(this);   rderModel = new OrderTableModel(Arrays.asList(data));   rdList = new OrderList(orderModel);   subPanel.add(catUI);   subPanel.add(ordList);   mainFrame.getContentPane().add("Center", subPanel);   ...    } ... private void addOrderItem(String desc, Integer price) {    cstate.addOrderItem(desc, price);   orderModel.changeData(cstate);>}

测试可视化购物车

要观察该应用的会话复制行为,请执行一下步骤:

通过使用 run.bat 批处理文件 (在代码分布的 src 目录下),在您的系统上启动 JGCart 的一个实例。您需要编辑 run.bat 文件,以指定 JavaGroups 库位于哪里。

在同一 LAN 上的另一台 PC 上启动另一个实例 (如果您不是工作在 LAN 上,则在同一台 PC 上启动另一个实例)。

在第一个实例上,单击目录,然后单击几件商品的 Buy按钮。注意,会话状态更改马上被 复制到其他机器。

图 10 演示了由&#160 JGCart 表示的两个复制的会话。注意它们之间如何保持同步 。

图 10. 复制的 JGCart 会话

如果您在 LAN 上不止有两台机器,您就可以容易地将该实验群集扩展到更多的机器—— 只要在这些机器上启动 JGCart 的新实例。

现在假设该购物车会话是运行在一个应用服务器内,而该服务器硬件崩溃了。通过关闭第 一个 JGCart 实例,我们可以模拟这一场景。当然,很容易看到,通过发送请求到会话的第 二个实例,我们可以继续购物。以群集的术语来说,这是一个 fail-over。在群集中的多台 服务器之间的硬件崩溃事件中幸免于难的能力,确保了服务的高可靠性。

实际上,即使没有系统崩溃,购物车请求也可以在任何时候被定向到服务器 A 或服务器 B,因为会话存在于这两台服务器上并可以在任何一台服务器上被更改。入站请求可以被定向 到当前具有最低工作负载的服务器 (叫做 负载平衡),这对于购物者是透明的。正如您可以 看到的,使用 JavaGroups 进行的跨一群机器的 Web 层会话复制可以提供高可用性的服务, 并且具有 fail-over 和负载平衡的可能性。

会话复制中的问题

正如我现在将要阐述的,会话复制中会有问题。启动 JGCart 的另一个实例 (在 LAN 中 的另一台机器上或者在同一台机器上)。现在,返回到初始实例,并且又添加一些商品。图 11 演示您应该看到的东西:

图 11. 不同步复制的 JGCart 会话

尽管到购物车会话中的所有添加动作仍然会被复制到第二个实例,但是这两个实例完全是 不同步的。当然,这一问题的原因是,我们已经在第一个购物车中放入了几件商品之后才启 动第二个实例。一开始我们没有遇到这个问题,是因为我们是在同一时间启动的所有复制会 话。

在群集实现中,强调群集中的机器都在同一时间启动是不合理的。实际上,我们应该能够 在任何时候添加或删除机器。这就要求新加入群集的机器能够从复制的会话那里请求“ 当前状态”。没有什么奇怪的,JavaGroup 的 JChannel 特别为此提供了一个 STATE_TRANSFER 微协议。参见 参考资料 部分,以了解有关 STATE_TRANSFER 微协议实现的 详细信息。

利用 JavaGroups 编码状态传输逻辑

为了在 JGCart 应用中加入状态传输功能,我们必须添加执行以下任务的代码:

1. 设置 Channel 选项以响应 GET_STATE 请求。默认情况下,来自 STATE_TRANSFER 协 议的任何 GET_STATE 事件不会被传播到应用,以简化典型的客户端实现。在我们的案例中, 我们想要接收 GET_STATE 事件。清单 3 显示了我们如何设置该选项 (以红色高亮显示)。这 是在 PrepareChannel() 方法内完成的。

2. 在连接到通道之后,调用 JChannel 的 getState() 方法,以从群集获得当前状态 ( 因为会话是在群集中相等地复制的,因此可以从任何成员获得当前状态),见清单 3 (以绿色 高亮显示)。同样,它也是 PrepareChannel() 方法的一部分。

清单 3. 为状态传输准备通道private void PrepareChannel() throws Exception  {  Channel=new JChannel(props);  Channel.setOpt (Channel.GET_STATE_EVENTS, Boolean.TRUE);  System.out.println("Connecting to " + groupname);  Channel.connect(groupname);  padapter =new PullPushAdapter(Channel, this, this);        Channel.getState (null,0);}

3. 从微协议栈接收到 SET_STATE 请求之后, PullPushAdapter 实例将回调 MessageListener 接口的 setState() 方法。我们需要实现该方法来设置我们的状态。清单 4 显示了这一实现。这里,我们只是设置了私有 cstate 变量,然后利用 cstate 更新 TableModel。这将自动更新 GUI 视图,因为 Swing 库例程将同步 GUI 视图和 TableModel 。

清单 4. 在状态传输期间设置 JGCart 的状态  public void setState(Object state) {        if (state == null) {             System.out.println("-- PullPush callback:              initial SETSTATE with null arg.");             cstate = new CartState();             }          else {          // set our local state          cstate = (CartState) state;         }          orderModel.changeData(cstate);   }

4. 在接收到 GET_STATE 请求之后——一般是从加入群集的新成员处接收到——我们需要 返回当前状态。PullPushAdapter 类将回调 MessageListener 接口的 getState() 方法。清 单 5 显示我们对了这一方法的实现。我们只是复制了一份状态,然后将它作为对象返回。

清单 5. 处理 GET_STATE 请求public Object getState() { System.out.println("-- PullPush callback: GetState has   been called!"); return cstate.Copy(); }

值得注意的是,必须对状态做一份 深拷贝(每个引用对象的逐个成员的拷贝),因为状态 在线路上传输之前, STATE_TRANSFER 协议有可能保留一会状态。如果状态引用的任何对象 在这时被修改了,传输的状态就会变得不一致。实际上,因为深拷贝操作本身要花一定的时 间,所以在复制期间要防止对状态的并发访问,以确保状态操作的同步。

5. 最后,我们需要在 JGCart 启动期间调用 getState()。清单 3 中以绿色高亮显示的 一行负责这一动作,并且是 PrepareChannel() 方法的一部分。注意 getState() 调用是异 步的。它只是启动 STATE_TRANSFER 协议以从群集获得状态,但是它立即返回。 PullPushAdapter 类对 MessageListener 接口的 setState() 的回调将在以后某一时间发生 。

清单 3 到 5 中的代码都在源代码中 (参见 参考资料),但是都被注释掉了。要获得状态 传输支持功能,请删除注释并重新编译。

有了 STATE_TRANSFER 支持之后,我们就作好了再次尝试 JGCart 群集模拟的准备。首先 ,启动一个 JGCart 实例,然后添加几件商品到购物车。现在通过启动另一个 JGCart 实例 来模拟添加新机器到群集。该实例现在启动了,就像第一个实例一样,所有的商品都在购物 车中。在它启动之后,我们可以使用任何一个 JGCart 来继续购物。JavaGroups 的状态传输 协议允许我们在任何时候添加新机器到群集中。

当然,如果需要传输的状态非常大,那么将它通过线路发送给每台加入群集的新机器是不 切实际的,甚至是不可能的。当机器经常性地加入和离开群集时尤其如此。幸运的是,J2EE Servlet/JSP 容器级别的实现通常涉及到较低的成员关系计数,并且成员关系很少改变 (例 如,机器崩溃或者拿到群集外面去维护)。

廉价机器群集可以为部署 Web 应用和 Web 服务提供一个可伸缩的、高度可用的平台。然 而,这种群集所需的网络软件通常是针对于某一特定应用的,并且是难以编写和测试的。 JavaGroups 是一个开放源代码的分布式系统工具,通过提供以下马上可以部署的、高级别的 特性,对于这一难题可以有所帮助:

组成员关系管理。

基于多播和单播消息的组通信。

状态传输协议。

功能分布式数据结构。

一些可重用的、经常使用的通信编码模式组成的库。

利用 JavaGroups 的特性,我们创建了群集购物车 Web 应用(JGCart)的会话复制机制 的一个可视化演示。通过对 JGCart 进行实验,我们看到了会话复制如何才能改善 Web 层应 用的可用性和可伸缩性。

通过使用 JavaGroups 来处理群集解决方案的组通信、状态传输和数据复制等方面,设计 者可以将精力集中于其他特定于应用的需求。

本文配套源码

做对的事情比把事情做对重要。

高影响力的Web层群集,第一部分:利用JavaGroups扩展

相关文章:

你感兴趣的文章:

标签云: