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

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

在Web层扩展应用

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

图 1. 在单个 SMP 服务器上扩展 Web 层
Scaling Web tier on a single server

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

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

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

* 消除了单点故障。
* 高服务可用性(如果群集中的多个服务器可以处理同一服务)。
* 通过将请求转移给处理同一服务的负载最少的服务器所带来的负载均衡。

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

* J2EE Web层容器 (应用服务器) 技术终趋成熟,并且它们的状态管理和操作模型也得到了较好的规范和理解。通过在一群服务器之间复制 Web 层容器的状态,您可以实现可伸缩的服务解决方案。
* 廉价的基于 PC 的服务器的成本达到了历史较低水平 (而每台服务器的 CPU 处理能力则持续增长),使得群集的成本比以前更加可以承受。
* 高速的基于 LAN 的互连广泛可用并且便宜。同时,套接字、TCP/IP 和更高级别的网络 API 使得编程需求非常简单。现在您使用用于群集的基于 LAN 的互连来取代专用的硬件连接/总线互连。
* 开放源代码 Linux 操作系统的广泛采用,甚至使得定制的群集解决方案可以以非专用的方式实现、维护和维持下去。

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

图 2. 利用联网的服务器群集扩展 Web 层
Scaling Web tier with networked server clusters

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


回页首


了解 Web 层扩展问题

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

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

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

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


回页首


JavaGroups 体系结构

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

图 3. JavaGroups 概念上的体系结构
Conceptual architecture of 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 通道进行通信
Communications with JavaGroups Channel

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

进程组中的通信

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

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

图 5. JavaGroups 通道的功能视图
Functional view of JavaGroups Channel


回页首


可重用的通信编码模式

为了进一步简化分布式的应用编程,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. 通道抽象和具体的实现
Channel abstraction and concrete implementations

JChannel


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

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


回页首


利用 JavaGroups 进行编程

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

1. 实例化一个通道并初始化必需的协议栈。
2. 连接到通道。
3. 开始发送消息或者处理入站消息 (有可能是在构造块的帮助下)。
4. 断开连接并关闭通道。

假设我们使用的是 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 进程协同工作