UML软件工程组织

实现高可用性设计的奥秘
作者:冯睿    本文选自:赛迪网  2003年01月21日

 高可用性要求一种合适的基础结构,但是,如果没有将该基础结构与J2EE应用程序设计策略进行协调平衡,性能很可能就会受损。

如果您在高可用性硬件和软件基础结构方面投资了一笔数目可观的资金,您就必须保证应用程序具备高可用性-至少应保证J2EE(Java 2平台企业版)中普遍存在的做法可保证应用程序具备高可用性。别忘了,当您将数据库和应用程序服务器的群集与群集软件进行组合时,您将会获得一个相当有效的组合。这里的群集软件包括Oracle9i数据库和Oracle9i应用程序服务器(Oracle9iAS)中的软件,这些群集软件既可以将请求自动发送到可用的服务器,还可以提供透明的应用程序失败处理。当您在J2EE体系结构中增加这类数目众多的混合状态管理机制(具有状态的Web会话和无状态Web会话,具有状态的商业组件和无状态商业组件,自动复制或备份beans等等)时,您应该同时保证具备高可用性。

不幸地是,这种普遍的做法并非完全正确。尽管服务器与相应软件的群集可以提供高可用性,但是它们却无法保证高可用性。事实上,某些高可用性功能(如J2EE的状态管理机制)的过度使用就可能会降低应用程序的性能,进而降低整体的可用性。

即便使用最好的体系结构,要真正实现高可用性,其中的关键一步就是从一开始就把高可用性考虑到应用程序的设计中来。因此,您需要从以下几个方面来特别关注应用程序的设计:

· 便捷的状态管理

投入一定时间来识别并确定应用程序的状态,然后将状态精确映射到各种可用的状态管理机制,避免过度使用某一类型的机制而导致性能降低。

· 仔细的层次设计

分析应用程序是否真正需要很多层。层次会增加复杂性、导致性能劣化并且降低可用性。

· 失败处理策略

确定并处理那些基础结构无法自动检测和修复的失败。

如果在应用程序设计的各个步骤中额外多投入一些时间,同时应用本文所描述的各种策略,那么应用程序的可用性将会显著增强。

便捷的状态管理


要确保实现高可用性,您就需要具备有效的状态恢复机制。如果一个组件失败,系统在处理新的请求之前,必须先恢复该组件所保持的状态。

J2EE提供多种便捷的状态管理机制,但是这些机制常常被过度使用,而过度使用这些机制经常带来一些不幸的结果。包括 JavaServer Pages (JSP)页中的会话范围的JavaBeans、HTTP会话对象servlets以及用于商业组件的有状态会话bean等在内的这些状态管理机制,可以识别并确定失败时必须保持的状态。所有这些机制都使用存储在内存中的Java 对象来表示应用程序的状态。在HTTP会话过程中以Java形式来保存这些对象可以为编程人员提供必要的便捷,但是却很容易会忘记进行相关备份所需的代价。

应用程序服务器(如Oracle9iAS)通过在数据库、文件或者备份服务器的有效内存中对这些存储在内存中的Java 对象的副本进行备份,就可以在必要时积极采取相应的恢复措施。对Java 对象进行备份时常常要求使用代价昂贵的 Java 串行操作。如果将许多大型的Java 对象用于状态管理(这是一种常见的应用程序设计策略),就会使J2EE 状态管理机制负载过重。因此,应用程序的性能将会劣化到极低的级别,这会让人无法接受。当然,这也与高可用性的目标背道而驰(我们认为一个速度极慢的应用程序不具有高可用性)。

作为这种 J2EE 标准方法的另一可选方法,您应该关注多数大型 Web 站点(这些站点同时又具备高可用性)所使用的模型。在该模型中,应用程序的状态主要保存在持久稳固的存储器(如数据库)中,同时在内存中进行了缓存处理以便改善响应时间(这与J2EE模型不同,J2EE模型中一开始就将状态存储在内存中,然后由应用程序服务器进行备份)。这里,最关键的不同之处就是:对于大型的Web站点模型,应用程序的写进程已投入一定时间来识别并确定应用程序状态的关键片断,同时还设计了一种有效机制(缓存),用于从应用程序对缓存进行访问。因而,中间层组件的失败并不需要恢复中间层的状态,而仅需重新启动缓存,这是因为需要时可以从后端再次导入相关的状态。

一些大型的 Web站点更进一步,它们对应用程序的状态进行分离,然后将其分别存储在稳固持久的后端存储器和客户端浏览器中。一些状态以cookie的形式保存在浏览器中,这样就隐藏了 HTML 形式的元素或 URL 参数。这种技术不仅提供了更高的可用性,而且还提高了应用程序的整体品质。如果浏览器处于未连接状态,这种技术允许终端用户很容易地重新开始连接会话。

现在很清楚了:J2EE应用程序开发人员需要预先投入一定时间来分析应用程序状态,然后根据特定策略将其映射到适当的可恢复存储器中。

接下来的一个问题就是如何确定哪些状态应该映射到后端数据库而哪些状态应该映射到浏览器。幸运地是,这个问题的答案通常是相当简单直接的,它取决于所要映射的状态信息的类型。

应用程序的状态通常可分为下面三种类型:

· 页状态:此种类型的状态在一页中是有效的,例如,搜索查询返回的一系列结果的开始索引。

· 会话状态:此种类型的状态通常在一次会话的整个过程中是有效的,例如,运行应用程序的末端用户的身份。

· 持久状态:此种类型的状态的生命周期为端用户会话,例如,购物报表或开支报告的内容。在常见的典型应用程序中,大多数应用程序状态都是这种持久状态类型的。

一旦您将应用程序的状态划分为上述三种类型,您就可以将状态映射到适当的持有器中。请您在脑中牢记:您的目标之一就是通过一种J2EE 机制使在中间层内存器中保存的状态尽可能少。

映射页状态。处理页状态的最佳方式就是将其存储隐藏形式的元素或URL参数中,这些元素或参数嵌套在该页中。因为此状态需要在该页面对浏览器可用的整个期间中始终保持,因而它并不会产生恢复问题。诸如 Oracle9iAS 的J2EE MVC 框架之类的模型查看控制器(Model View Controller)框架,使用隐藏形式的元素将页面流信息保存在每一个Web页面中。

映射会话状态。您可以保存一些会话状态,如用户身份和in cookies;身份验证系统(如Oracle9iAS Single Sign-On)就使用此种方法。然而,您需要在中间层内存中保存会话状态的其他一些部分。如果cookie 的数目过多或者内容过大,就无法进行高效操作。在这些情况下,您就应该使用之前已提及的一种 J2EE状态管理机制。把使用这些机制的情形限制到这些场合有助于保持较高的可用性。

映射持久状态。您应该非常明确地将持久状态保存在后端数据库中。您可以在中间层内存中对其进行缓存以便提高效率(Oracle9iAS 中的特殊JSP 标记库可以在 Web 缓存和 J2EE 容器中对页片断进行缓存)。然而,您可能希望数据库成为真实数据的来源以便中间层失败时避免出现恢复问题。请注意:强烈建议显式标识状态并且通过正规的数据库访问机制(如实体 beans或 Java Database Connectivity (JDBC))将状态显式存储在数据库中.

通过此种方式来映射这三种类型的状态,就可以创建状态安全的应用程序。状态安全的应用程序可以避免无状态应用程序的局限性,而可用性并不差。

仔细的层次设计


层次设计是获得可用性的另一领域。在J2EE中,通常有这样一个共识:应用程序应该由多种层次组成。然而,如果您使用过多的层次,不仅不会增强可用性,反而还可能会阻碍了可用性。

使用多种层次的原因是相当明显的。首先,仔细的多层设计有助于隔离各种故障,例如,商业组件虽然失败但并不会影响Web前端的可用性,这一点可持续至前端需要使用该组件为止。而且,在一些情况下,每一层中运行更少的逻辑可以使每一层变得更健壮。

但是,使层的数目尽可能保持最低具有非常显著的优势。这使应用程序可以更容易地进行管理和监控,为那些负责实现高可用性的操作组提供巨大的帮助。而且,如果一个应用程序具有层数不多,通常就更易于设计,因而整体上就更加健壮。最后,设计一个层数不多的应用程序(例如,使用Enterprise JavaBeans (EJB) 2.0 本地接口)可以获得更优的性能,这也同时会大大增强可用性。

如果您将目标锁定为高可用性,但又无法确定选用的层数应该多一些还是少一些,请您相信,使用更少的层数,出错的可能性就会更低。

失败处理策略


与状态管理和层次设计一样,我们在失败处理方面过分依赖于常规的方法和高可用性基础结构的实际能力。因为现在的应用程序服务器能够自动检测和修复许多组件失败,我们就不必投入太多的精力,以便我们的应用程序能够处理那些不能自动修复的失败。但是我们并没有意识到:真正的高可用性则要求应用程序和应用程序服务器之间能够相互协作。

双重事务是一种常见的失败类型,这种类型的失败很难做到自动修复。终端用户两次单击Bill Me 按钮就是该类失败的一个示例。由于双重事务的产生因素在于不同层之间的通信失败,所以修复就可能会涉及一些位于应用程序服务器内嵌的修复机制范围之外的问题。请这样一种简单的情形,一个JSP 页面通过JDBC 提交事务,但是却收到了网络通信错误。那么应用程序服务器如何才能确定在通信失败之前该事务是否已经提交?应用程序服务器是否应该自动再重复先前的操作?这类问题在层次紧密的体系结构中更加难以解决,其中JSP页调用在多个分布式容器中运行的EJB。通信错误表示bean 调用根本就没有执行完毕,在这种情况下再次进行调用就非常合适。可是,相同的错误也可能是起因于这样一种情况,调用本身已取得成功但是确认并没有成功。如果出现这种情况,就不应该再次进行调用。

一些应用程序服务器(如BEA Weblogic的应用程序服务器),通过让开发人员将特殊的EJB调用识别为幂等的(这就是说即便多次提交,也只进行一次处理)来解决这些情形。我们可自动再次进行这些调用,这一点毫无疑问。但是,大多数本应该为幂等的EJB调用实际上往往不是幂等的,除非您已经通过很细致的编码实现了调用的幂等性。

这些情况中,细致的编码就等于说不仅要预料到可能出现的双重事务的具体情形而且要采取必要措施防止其出现。当终端用户单击后退按钮并再次在前一页面上提交一个订单时,就要确保使用的策略与设计者用于解决Web应用程序后退按钮问题所采用的策略类型相同。当提交一个订单时,一种可行的策略就是检查相同的订单是否已经提交和是否已经作出相应的响应。例如,如果两个事务具有相同的信用卡编号、货物数量并且它们之间的时间间隔不到五分钟,应用程序就应该不去处理第二个事务而将其忽略掉。

底线就是你需要预料到可能的失败类型,应用程序不能处理这些失败而且也无法将失败处理嵌入到应用程序中。必须警惕那些难以检测的可能的失败,如双重事务和拆分的事务(拆分的事务的含义为应用程序采用手动方式分两步提交事务,其中的一步却没有成功),确保将应用程序设计为可应对这类失败。从策略角度增强应用程序抗失败的能力,也就可以同时增强应用程序的可用性。

避免失败的美好前景


请不要浪费高可用性基础结构的内在潜力,要做到这一点,必须在头脑中时刻铭记一定要确保所设计的应用程序具有高可靠性。这里已经讨论过的策略:状态管理,层次设计以及失败处理策略,当基础结构自身无法进行修复时,这些策略将会有助于您创建可与该基础结构携手合作的应用程序,这样就可以向用户提供高可用性。





UML软件工程组织