Geronimo 中的集群的历史
在一个下雪的清晨,我乘公车来到我在 Calgary 的办公室,满脑子是当天我要与 Julian(Jules) Gosnell 进行的 VoIP 电话谈话,他是 Codehaus 的 WADI 项目的创建者,Apache Geronimo 应用服务器项目的共同创立者,也是这个项目的集群解决方案的提交者中的一员。开始的时候,我对这次采访没有自信,但是当采访开始之后,就很清楚了:Jules 对这个主题很热心,采访进展迅速,持续了一个多小时。本文把重点放在这个主题的 第一篇文章 忽视了(或者简要地提到过,但是没有进行完整的介绍)的 Geronimo 集群工作的一些具体细节上。
所以,虽然雪花在我的办公室外不断飘落,飘落在英国伦敦以南 45 分钟车程的一个小房子旁边的木屋办公室外的花园里,Jules 还是从他的角度向我谈起了 Geronimo 集群的历史。
通往 WADI 之路
首先,我问 Jules 他最初是如何进入 Java™ Enterprise Edition 世界的。他提到他那时在伦敦的一家银行工作,编写 Java servlet 来替代用 C++ 编写的老系统。正是通过这个工作,他了解到 Greg Wilkins 和 Jan Bartel 高质量的开发工作,这两人是轻量级 HTTP 服务器和名为 Jetty 的 servlet 运行程序的创建者和开发人员。Jules 对 Jetty 的兴趣,使他参与了一些 JBoss 应用服务器开发工作以集成 Jetty。之后,他还参与了为 Jetty 实现集群的 Web 会话实现。就在 Geronimo 项目开始之后不久,在 2003 年 8 月,关于为这个项目实现集群功能的讨论开始了。
我问 Jules 为什么 WADI 是在 Codehaus 存储库中开发的,而不是在 Geronimo 项目的源代码树中开发的。他说:最初,James Strachan 建议构建一个通用的集群 API(ActiveCluster),以支持 Geronimo 或需要这类功能的其他系统的集群抽象。Codehaus 被选中作为一个中立于许可的基础。ActiveCluster 的目的是与既是 LGPL(较少通用公共许可)许可的又是 ASF(Apache 软件基金会)许可的实现联系起来,因而他们想在这个项目中涉及两个社区。
Jules 解释说由于他的兴趣和经验主要是在 Web 会话上,所以他的重点很快从 ActiveCluster 的基本集群问题转移到与他的兴趣领域更相关的问题上,从而导致 2004 年 4 月在 Codehaus 上创建了 WADI 项目(最初是 Web 应用程序分布式基础设施,现在是 WADI 应用程序分布式基础设施,因为 WADI 已经成为更加通用的会话管理框架。请注意传统的递归首字母缩写)。James 继续开发 ActiveCluster,而他对集群缓存的兴趣导致他在 Codehaus 上创建了 ActiveSpace 项目。Jules 说,这个项目像 WADI 一样,也利用了 ActiveCluster API。
很明显,在应用服务器领域有一些在潜心研究的队伍,这是我们在理解为什么编写了这些项目时需要考虑的因素。他们要解决什么问题呢?
|
集群策略
简而言之,集群就是用于解决许多基本问题的策略。我们介绍其中两个最重要的问题,以及如何在 Geronimo 应用服务器中解决它们。
可用性和可伸缩性问题
第一个问题的正式叫法是可用性(availability)问题。随着企业应用程序变得越来越流行,任何停机时间都变得非常值得注意。通过添加更多的计算机,在出现问题时接手工作,可以提高系统的可用性。这叫做故障转移(failover) —— 这是实现高可用性的机制。第二个问题的正式叫法是可伸缩性(scalability)问题,这个问题随着使用系统的人数增多而出现。有些计算机可能变得过载,所以如果增加更多的计算机,并在解决方案中增加负载均衡能力,让每台机器承担一部分负载,就会在整体上形成更好的响应时间。
会话状态
要让用户能够与应用程序进行交互,通常要在服务器上保存关于这个用户对应用程序已经做了什么的信息。这个信息叫做会话状态,是有用的应用程序的重要部分。例如,假设登录到一个在线商店,在购物车中添加了几个商品,想要结帐购买商品。如果没有会话状态,那么这个操作会很难处理。Jules 指出,会话状态让用户可能与远程应用程序(例如 Web 应用程序)进行对话。如果没有会话,那么所做的每个请求都与前一个请求没有关系。他说,在效果上,就像与一个人谈话时,这个人每回答一句,就把前面的谈话全部遗忘。
会话亲和力
所以,在应用程序中保存会话状态是非常重要的。通常,在集群的情况下,会话状态保存在集群中的一台计算机上,并且负载均衡器总是把用户的请求重定向到保存他(或她)的会话的机器上。传统上,这叫做会话亲和力(session affinity)。但是,这根本没有解决可用性问题。如果保存会话的计算机崩溃了,购物者将感受到糟糕的体验。许多开放源码解决方案,每次在会话发生变化时,都把会话的备份拷贝广播到集群中的其他每个节点。如果其中任何一台机器停机,会话仍然是安全的。因为可以在这些备份拷贝中找到一个,并重新激活它。
这个理论适用于小型部署(三到四个节点),但是 Jules 告诉我该方法也不能伸缩。每个额外节点都需要额外的内存来保存集群中每个节点的备份,所以很快就会用光内存。而且,对会话的每次变化都会形成对每个节点的操作,因为节点必须处理进来的消息更新,让会话的备份拷贝与变化一致。随着节点、会话和用户数量的增加,每个节点花在维持备份拷贝上的工作量也会增加,从而只留下更少的时间去为用户请求服务。这会造成性能的下降:越来越多的时间和带宽花在了在集群中维护会话备份的一致性,而不是用来处理用户请求。
静态分区
有些开放源码解决方案试图利用叫做静态分区 的策略来弥补这个可伸缩性问题。这种策略把集群分成比较小的集群,以避免达到内存和带宽的极限。但是,Jules 指出静态分区不仅配置起来更复杂,而且也会影响可用性。最后导致要管理许多比较小的集群,而比较小的集群只具有比较低的可用性,因为可用性取决于集群的大小。
动态分区
因为 Jules 亲身看到了与静态分区有关的问题,也为自己不能用开放源码组件为客户提供更好的解决方案而感到沮丧,所以 WADI 从开始时就采用了动态分区的方式。他还认识到这也是他为 Geronimo 做贡献的方式。随着 WADI 项目在 Codehaus 上的崛起(这是主项目管理员的评价),他的工作朝着解决会话状态分布问题的方向前进,形成了一个更好的集群分区解决方案,叫做动态分区。
|
WADI 解决方案
Jules 有一个问题要解决,他花了两年中相当多的时间来解决这个问题,从 2004 年 4 月他把初始的 WADI 源代码导入 Codehaus 时开始。在这两年中,WADI 的源代码从无到有发展到大约 44,000 行代码。我最初看了 Codehaus 存储库中的 WADI,对于这个项目的历史有了些理解,但是对于 Jules 表示感谢的那些帮助完成该项目的人,这个理解当然没有提供正确的认识。
他告诉我,设计从一开始,首先受到了 James Strachan 和 Hiram Chirino 的影响,因为 WADI 利用了 ActiveCluster 和 ActiveMQ;其次受到了编写 Jetty 的 Greg Wilkins 和 Jan Bartel 的影响,因为 WADI 与 Jetty 连接。Greg 的朋友 Simone Bordet(MX4J 和 LiveTribe),在项目早期就对它产生了兴趣。Gianni Scenini 与 Jules 一起为一个客户工作,他花了许多时间与 Jules 探讨 WADI 应当如何工作。在 2005 年底,Jeff Genender、Bruce Snyder、James Goodwill 和 Bill Dudney 加盟进来。就在这时,Gianny Damour 开始在 WADI 中集成 OpenEJB,从而向它提供一个有状态会话 bean 解决方案。
分布式散列映射
Jules 对我解释道:在 WADI 中,会话信息分布在集群的节点间,每个会话的位置保存在索引中。索引被作为一种名为分布式散列映射(distributed hash-map)的分布式数据库被分解并在节点间共享,这样每个节点就都可以访问每个会话的位置,这些信息要小得多,从而降低了整体的内存负载。
不用在每个节点上都拥有每个会话的一个备份,应用程序部署器(应用服务器中负责启动并运行应用程序的部分)决定它们需要的安全级别,并配置固定数量的备份(通常一个或两个)。不论集群变得多大,制作的备份数量都不超过这个数目。这就避免了前面提到的内存限制。而且,因为会话变化时需要更新的会话备份更少,所以 WADI 也避免了前面讨论的服务性能降低的问题。
在正常的操作下,由于在负载均衡器上开启了会话亲和力,所以请求总是路由到拥有相关会话的节点,而请求的处理只涉及这个节点,没有与集群相关的其他开销。但是,在许多其他情况下,无法维持会话亲和力,例如:在机器异常地崩溃时,或者再极端些,在机器完全关机时。在这些情况下,WADI 会使用它的会话位置映射,把进入的请求重新定位到会话的新位置,或者安排会话从这个新位置迁移到请求到达的节点。
在基于 WADI 的集群的操作中,随时都可以动态地添加或移除计算机。添加到集群的计算机就把它们的内存和资源添加到池中,负责整体会话位置映射的部分责任,并形成与其他集群成员的复制伙伴关系,从而使它们也可以接收会话信息的复制拷贝。这些复制的会话是针对集群中计算机意外崩溃的保险措施。机器也可以在控制下进行关机。当发生这种情况时,会话被 “疏散” 到其他幸存的计算机。在内存不足的情况下,没有使用的会话就被写入持久存储中,这样当用户在会话到期之前回到计算机上时,可以检索到这些会话。
存储复制的拷贝
为了选择把复制的拷贝存储到哪儿,采用了一种可插入的选举机制来选择复制伙伴:距离远的机器、负载轻的机器或者到保留会话的机器的网络连接更快的机器,被首选进行复制。有些启发式方案可能会花更长的时间来选择更好的复制伙伴,例如为机房中同一架子上的机器添加一个处罚因子,这样,如果整个架子上的机器因为电源故障全部停掉,会话可能已经复制到了另一个架子上的机器,因此可能幸免于难。
会话数据与业务数据有不同的特征。业务数据通常存在于数据库中,可以在需要的时候从数据库中读取出来。会话数据是临时的,通常短期存在,需要在页面的呈现过程中被迅速地访问到。会话数据必须以备份的形式迅速进行复制。Jules 告诉我 Gianny Damour 正在处理这个解决问题。
会话平等
Geronimo 有许多不同的会话 —— Web 应用程序会话、企业 JavaBean(EJB)无状态会话 bean、Web 服务会话,等等。Jules 正在与 Geronimo 项目的人员合作,把一些不同的技术与 WADI 合并,从而让 Geronimo 用同样的方式对待所有会话。它还会把相关会话组织在一起,这样,与同一用户关联的 Web 和 EJB 层可以保存在集群中的同一位置;而且,当需要移动会话数据时,相关会话将会一起移动。
保持缓存一致
与应用服务器中的集群有关的另一个领域是缓存。实体 bean 是通常保存在数据库中的数据的抽象。为了提高应用程序的性能,数据库中的数据被缓存在应用服务器中。对数据的修改被写回数据库,数据的缓存版本或者被抛弃(这样在下一个请求时再检索它们),或者缓存的数据也做修改,以保持一致。在单一计算机上,这种方式工作得很好,但是在进入集群模型时,重要的是缓存的数据要在所有节点间保持一致。这通常叫做缓存一致性问题。
所以,Geronimo 集群解决方案看起来更像 图 1 中的结构。
为了解释这个图表,先从信息源所在的底部开始。信息可以来自数据库或来自参与对等信息共享的集群中的其他节点。数据库信息使用 JCache 实现进行映射,缓存在 ActiveSpace 的集群缓存中,并通过对象关系映射提供给应用程序。在这个图的另一边,WADI 用 ActiveCluster 来管理应用服务器集群中存在的所有不同的用户会话数据,包括 Web 会话、无状态会话 bean、Web 服务会话,等等。ActiveSpace 也使用 ActiveCluster 来保持缓存内容(例如实体 bean、部署和 JNDI 目录信息)的一致性。
所以可以看出,WADI 是解决方案的一部分,而不是整体解决方案。其他项目用来满足集群的需求。还可以看出,Codehaus 在这么多项目的早期阶段中扮演了重要的角色,它允许高度关注这些项目的人们可以在创新的和友好的环境中进行开发。现在,在开发了多年之后,这些项目正在寻找自己的地位,从而在 Geronimo 项目中占据各自的位置。
|
结束语
Geronimo 的集群解决方案仍在积极地开发,但是我希望通过这篇文章,您可以看到 Geronimo 的开发人员努力解决的一些问题。可以想像 Geronimo 完成时我们将享受的好处,所以我可以列出 Geronimo 的集群功能:
最后,Jules 希望我向读者说明:开放源码软件是整个开发人员社区协作的成果,虽然这篇文章中只提到了少数几个人,但是他希望感谢更多的人对 Geronimo、WADI 和相关项目作出的贡献。