内容


IBM WebSphere 开发者技术期刊

自定义门户代码的性能注意事项

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: IBM WebSphere 开发者技术期刊

敬请期待该系列的后续内容。

此内容是该系列的一部分:IBM WebSphere 开发者技术期刊

敬请期待该系列的后续内容。

摘自 IBM WebSphere 开发者技术期刊

引言

本文提供了一些常规指导,以创建能够正常执行的 IBM WebSphere Portal 自定义代码。自定义代码不仅指 Portlet(虽然它们是最常见的门户编程模型),也包括 WebSphere Portal 的主题和外观代码。由于这些组件均采用与 Portlet 相同的技术实现,所以很多相同的性能注意事项对它们也同样适用。

对于 Portlet,本文主要集中讨论遵循 Java™ Portlet 规范 JSR 168 及 WebSphere Portal 中的对应实现的标准化 Portlet。本文基于 WebSphere Portal V5.1 或更高版本,不过本文中所给出的准则和大部分建议永远适用,而不受所运行的 WebSphere Portal 版本的影响。

本文将说明如何设置和使用 Porlet 应用程序的部署参数以优化门户和 Portlet 的性能,因为这是创建自定义门户代码的最后一步。不过 WebSphere Portal 的总体优化(即创建并部署自定义代码后执行的管理操作)将不在本文中讨论。另一个文档对 WebSphere Portal 性能优化进行了说明。该文档与本文档共同提供了门户与性能方面不错的参考资料。

本文旨在供参与构建门户应用程序并希望提高对与自定义代码相关的潜在性能问题的理解的程序员、设计人员和架构师使用。

WebSphere Portal 环境概述

IBM WebSphere Portal 构建于 the IBM WebSphere Application Server 产品之上。因此,自定义门户代码的编程环境具有三重特征,其对应的重要含义如下:

  • WebSphere Portal 及其所有组件均为基于 Java 的程序。

    因此,总的来说,应该遵循编写高性能 Java 代码的最佳实践。

  • WebSphere Portal 是运行于应用程序服务器平台上的 J2EE 应用程序。

    J2EE 包含多线程技术;J2EE 容器通常采用每个请求一个线程的方法处理请求负荷。对于使用此机制必然涉及到的任何实现或性能注意事项都应该加以注意。

  • WebSphere Portal 提供了 API 以扩展门户功能。

    可以采用很多方法对任务进行编程。应该首先考虑影响性能的差异。

下一部分中将介绍一些关于门户编程环境不同部分的一般性能注意事项。

Java

显然,本部分并不会提供处理 Java 性能的全部技术。我们仅在此处给出在我们认为进行 WebSphere Portal 开发时最有用的相关事项,并提供一些可帮助深入了解 Java 性能的参考资料(请参阅参考资料)。

基本 Java 性能

在这一部分中,我们将讨论一些应用到大部分 Java 的一般性能项。尽管这些建议可能并不会带来大幅度的性能提高,但可以使您对在开发阶段底层程序执行性能的重要性有所认识。

  • 当需要修改字符串时,使用 java.lang.StringBuffers 实例,而不要使用 java.lang.String 实例。

    在 Java 中,String 对象是不可变的,而 StringBuffer 对象是可变的。无论何时将文本附加到 String 或从中删除,实际上都将创建一个新对象,并将旧对象丢弃。因此我们首选以下的方式:

    StringBuffer sb = new StringBuffer("Hello ");
    sb.append(var).append(" World");

    以此为基础的字符串联结操作:

    String s = "Hello" + var + " World";

    有时可以通过设置 StringBuffer 的初始容量进一步提高性能;该类的设计使其可以在不能保存全部数据时自动扩大容量。不过此处有性能损失,因为 StringBuffer 必须透明地增加其大小和对数据移位。例如,如果将 StringBuffer 作为收集参数(即将向其添加越来越多的数据)使用,应该在对其进行初始化之前计算恰当的缓冲区大小,以使其永远都不需要增加大小。

  • 避免在服务器端程序中进行开销很大的 I/O 操作。

    在 I/O 操作期间至少会阻塞当前线程;如果其他线程也必须等待磁盘,则系统响应时间将会迅速增大。除非在执行日志记录功能(例如,在记录异常或站点访问信息),否则 WebSphere Portal 自己不会引起任何磁盘访问。我们将在后面对 I/O 进行进一步讨论。

  • 尽可能减少同步代码块的数量和长度。

    synchronized 关键字每次仅允许一个线程进入代码块。同步代码块所需的执行时间越长,其他线程等待进入该代码块的时间就越长。我们将在后面对同步进行进一步讨论。

  • 避免开销巨大的计算和方法调用。

    例如,使用 System.currentTimeMillis() 检索当前时间信息开销就相当大。如果确实需要时间信息,请确定是需要当前准确的时间,还是(例如)准确到最近的秒数就足够了。如果在代码路径中有很多获取时间的调用,但并非一定要毫秒级的准确度,可以采用替换方法,即确定请求开始的时间,然后直接在请求期间使用该信息。

  • 限制异常的使用。

    通常,应将 Java 中的异常用于指示错误情况。不要使用异常指示操作成功,这主要是因为 JVM 创建异常堆栈跟踪非常费时,而且在 WebSphere Portal 系统中的跟踪深度会很深。

  • 使用 Java Reflection API 时需要谨慎。

    此 API 为动态代码执行增加了功能强大的选项,但就方法执行时间而言,获得这种灵活性会导致严重的性能损失。通常,应尽力避免在门户代码中使用 Java Reflection API。不过,如果有必要进行反射调用,则应尽量将其放置在初始方法中,以使其在每个请求期间都不会执行。

内存使用和垃圾生成

虽然内存对于 Java 客户机软件通常不是一个突出的性能问题,但对于 J2EE 应用程序却是一个主要问题,这主要是因为企业应用程序通常由很多用户同时访问。为了使应用程序服务器高效运行,可用资源(包括内存、CPU 和带宽)均由客户机的请求共享。我们要提到三个主要内存问题:

  • 尽可能减少临时对象的数量。

    这意味着要尽可能地重用对象,而不要太频繁地创建新对象实例。创建的对象越多,JVM 垃圾回收器就必须更频繁地回收内存并会(至少部分)中断此时的请求处理。创建许多对象还容易增加堆碎片,而这会导致出现更多的垃圾回收周期。例如,不要过早创建对象:

    String logData = "Parameter 1: " + param1;
    if (logger.isLogging(DEBUG)) {
    	logger.log(logData);
    }

    在本例中,仅在对条件进行求值之后才应创建 logData。缓存和对象池技术均可以减少临时对象的创建。若要识别代码中最常导致内存分配问题的部分,请参阅工具

  • 将内存永久占有的情况保持为最低。

    不要将太多信息读入内存中;而要使用缓存保存重要的信息。有时可以针对一条信息更改数据类型。例如,数据信息可以保存在 java.util.Date 对象内或 long 变量中。与基元数据类型相比,对象通常更大,处理速度也会有些慢。它可能会依赖于邻近的 API 和数据类型首选的数据结构。通常,内存占用率越高,就会导致垃圾回收率更高,请求处理期间暂停的次数也会增加。

  • 检查应用程序以确定是否存在内存泄漏。

    内存泄漏通常出现在 Java 集合类中。例如,如果有一个 java.util.Map,在特定情况下,会将数据添加到映射中,但却永远不从其中删除。内存泄漏会导致 Java 堆保留的内存使用越来越大,随着时间的增加,垃圾回收器能释放的内存会越来越少。这样,会导致垃圾回收更频繁,而最终将使门户系统停止响应。而更糟糕的是,通常仅在长时间运行的测试中才能发现内存泄漏,不过可以使用各种工具帮助进行此类分析(请参阅工具)。

性能和可扩展性代码设计

设计和开发可伸缩性代码时,需要记住很多事项。其中最为重要的三方面是:缓存、对象池和信息预提取:

  • 缓存,存储已经计算得到的结果。

    例如,可以从后端系统检索信息,但不将每个可能的对象均从存储区复制到内存中,而仅加载其中的小部分,将其放置在缓存中。这样,该信息就对稍后的引用可用(可能在后续的另一请求中使用,甚至供另一个用户使用)。

    缓存始终采用对象映射的形式,具有大小上限。缓存还必须知道不可能再次请求某个内容的情况,以便在合适时从缓存中将其删除。这种排除操作通常由“生存时间”(TTL) 或“最近最少使用”算法确定。而且使用缓存的客户机不能保证将成功从缓存检索对象;必须首先检查对象是否存在,如果没有找到,则将创建该对象:

    Mail mail = myCache.get("myMail");
    if (mail == null) {
    	mail = readMailInformation();
    	myCache.put("myMail", mail) ;
    }
    ...

    (在某些情况下,特定于应用程序的缓存可以设计为从对客户机透明的某个数据源查找所需的数据。)

  • 使用对象池限制特定类的实例的数量。

    每个请求都需要特定类的实例,但此对象并不(也不应)需要在每个请求中重新创建。在对象创建和初始化开销很大的情况下,尤其是这样。客户机可以不接受性能命中,而从池中请求对象,然后在用完之后将其返回池中。

    PooledObject po = myPool.get();
    ...
    // use the PooledObject 
    ...
    myPool.put(po);
  • 对象池的一种简单形式就是将对象规范化。

    这意味着对象的所有不同实例在程序初始化阶段创建,将在随后重用和引用。java.lang.Boolean 类就是已规范化对象的例子。只需要有两种不同的 Boolean 对象即可(最好能作为常数访问)。同样,也可使其他对象使用一组固定的只读内部状态。

  • 只提取当前希望处理的数据,而不提取多余的数据。

    例如,在 Portlet 中,可以提供一个电子邮件列表;该 Porlet 将显示主题、日期、发件人和其他重要信息。当用户选择了特定的电子邮件时,将显示该邮件的正文。在从 Porlet 中选择特定的项之前,不需要正文,因此提前检索正文将浪费执行时间和内存资源。这种模式在很多情况下都适用。总的原则是,仅计算和检索对于当前请求和响应有直接意义的信息。

J2EE

IBM WebSphere Application Server 是 J2EE 实现,WebSphere Portal 就构建于其上。由于本部分中很多性能注意事项适用于 J2EE 运行时上下文,所以其中的很多信息除了适用于 WebSphere Application Server 之外,也适用于其他应用程序服务器和 J2EE 应用程序。下面所列出的项目在此处只进行了简单概述,将在后面进行更为详细的说明。有关更多的一般性讨论,请参阅参考资料

J2EE 标准

J2EE 标准规范包含了大量与性能相关的事项:

  • 应当使用初始方法计算所有后面将用到且不会发生更改的内容(很多 J2EE 资源都可以使用初始方法,Portlet 也可以使用此类方法)。例如,数据源等普通资源的 JNDI 程序应该仅在初始化时执行一次。此外,也应该仅在 Portlet 初始化期间读取一次来自特定只读文件的数据。可以对 Portlet 服务方法进行扫描,以发现所有对每个请求执行相同操作的代码,将其移动到初始方法中,以降低该服务方法的运行时开销。

  • EJB 和会话是 J2EE 中非常重要且功能强大的概念,但如果使用不当,二者均可能导致性能损失。例如,应用程序不应将过多的数据放置到会话中,从而减少服务器的内存占用并更快速方便地保持会话。关于 EJB 组件,应该熟悉与远程调用和本地调用等相关的不同持久类型。EJB 可以使用的某些功能会带来大的性能损失。

WebSphere Application Server

WebSphere Application Server 产品提供了各种功能,以帮助开发人员和架构师设计高性能系统。(请参阅参考资料中给出的 WebSphere Application Server 信息中心和 WebSphere Business Integration Server Foundation 信息中心)。

  • 正如前面所提到的,创建数据库连接的开销非常大。按照 J2EE 标准中的定义,应用程序服务器可以提供连接池机制,从而无需为每个传入请求重新创建连接。WebSphere Application Server 使用一些额外的性能 Helper 提供这样的连接池机制,相当于频繁执行的 SQL 语句的语句缓存。不过,如果完成数据库交互后没有及时返回连接,将回导致相当长时间内连接对其他请求不可用。通过使用 WebSphere Application Server 管理控制台,可以将连接池作为 JDBC 数据库的数据源属性进行控制,并能进行定义,例如可以定义连接池中的最少连接数和最大连接数。(请参阅 WebSphere Application Server 信息中心以了解详细信息。)

    下面所给的示例演示了多个请求对连接进行重用。在本例中,可以使用 JDBC 连接池和利用 Application Server 提供的语句缓存:

    . . . 
    public class IDontCare extends GenericPortlet {
          
       private javax.sql.DataSource ds;
       
       public void init() throws javax.portlet.PortletException {
          
          try {
             Hashtable env = new Hashtable();
             env.put( Context.INITIAL_CONTEXT_FACTORY,
                "com.ibm.ejs.ns.jndi.CNInitialContextFactory" );
             Context ctx = new InitialContext( env );
             ds = (javax.sql.DataSource)ctx.lookup( "jdbc/MYSHOES" );
             ctx.close();
          } catch (Exception any) {
             // handle exceptions here
             . . . 
          }
       }
       . . . 
       public void processAction ( 
          ActionRequest request, 
          ActionResponse response
       ) throws PortletException, IOException {
          . . . 
          try {
             Connection con = null;
             ResultSet rs = null;
             PreparedStatement pStmt = null;
             con = ds.getConnection ( dbuser, dbpasswd );
             pStmt = con.prepareStatement(
                "select * from myscheme.size_of_shoes");
             rs = pStmt.executeQuery(); 
             . . . 
             // release the resource when it is no longer used
             if (pStmt != null) pStmt.close();
             if (con   != null) con.close();
          } catch (Exception any) {
             // handle exception here
             . . . 
          } 
       }
    }
  • WebSphere Application Server 还支持对象池的常规概念,每个对象池均具有池管理器,从而为不同类类型提供对象池访问。可以查询此类对象池以获得类类型实例,如前面关于池技术的示例中所述。请参阅 WebSphere Business Integrator Server Foundation 信息中心以获得详细信息。

  • WebSphere Application Server 还提供了“一般用途”的缓存。在管理控制台中,可以定义缓存实例,应用程序可以使用这些缓存实例存储、检索和共享数据。与缺省共享动态缓存(门户使用其缓存对象)不同,缓存实例仅可由知道其 JNDI 名称的应用程序访问。DistributedMap 类是应用程序所使用的编程接口,该类允许应用程序从缓存实例获得对象和将对象放置到其中,并可以使其失效。请参阅 WebSphere Business Integrator Server Foundation 信息中心以获得详细信息 [8] (LINK)。

    如果 Portlet 使用缓存实现,它们应该在其初始阶段查找或实例化一个缓存实例,并保持该缓存的引用,以使缓存条目具有可能比单个请求长的生存期。在处理 Portlet 的操作和呈现阶段时,可以将条目放置到缓存中,并从中进行检索。Portlet 实现需要确保如果使用特定键查询时,缓存没有返回数据,应有适当的后端访问和缓存进行更新处理。另外,还要注意,为了实现设计的特定功能,可能需要限定键在缓存中的范围(如,基于用户会话)。缓存通常为自我管理的单元,根据缓存实现,可以排除条目或使其失效。请注意,出于同样的原因,缓存并不适合在多段代码间进行信息通信。缓存还应该维护一个合理的大小上限,以避免自定义代码中内存的过度使用。

门户 API

WebSphere Portal 支持两种不同的 Portlet API:

  • IBM Portlet API,该 API 对 Servlet 进行扩展。
  • JSR 168 Portlet API,该 API 由 Java Community Process (JCP) 定义。

在本文中,我们将重点讨论 JSR 168 Portlet API。

WebSphere Portal 提供了各种接口,用于将 Portlet 集成到 WebSphere Portal 环境中。因此,应该谨慎设计 Portlet,以充分利用各种门户功能。请确保采用最佳实践(请参阅参考资料中列出的最佳实践),以应用恰当的 WebSphere Portal API。

常见的实现注意事项

在本部分中,我们将讨论与主题和外观编程以及 Portlet 开发相关的性能主题。

JSP

JavaServer Page (JSP) 是 Portlet 编程的基础之一。在大多数 Portlet 中,JSP 通过使用 Model View Controller (MVC) 作为视图组件使用。JSP 由 HTML(或其他标记语言)组合和 Java 代码组成;在大多数 HTML 中,它们的处理输出也是标记语言。其最简单的形式中,JSP 不包含任何 Java 代码,但仅包含自定义标记,调用这些标记以执行非 HTML 操作。(相反地,JSP 文件中也可能不包含任何 HTML 内容)。

  • 在第一次访问 JSP 文件时,将对文件进行分析,将其转换为常规 Java Servlet 源文件,该源文件将随后编译为字节代码。因此,由于后续的两次转换(从 JSP 到 Java 源再到字节代码),第一次请求 时通常比较缓慢,但对于之后的所有请求,JSP 将同任何其他 Servlet 一样工作。

    这与其他生成 HTML 内容的方法(XML 和 XSLT)不一样。使用其他方法时,对于每次请求都必须解析 XML 和应用样式表转换。只有很好地缓存了结果而不需每次请求都重新运行转换,才能保证性能。因此,从性能的角度出发,JSP 应该优于 XML/XSLT。此外,门户基础设施还针对 JSP 进行了优化,允许方便地扩展以支持其他标记、语言和浏览器。

  • 应用程序服务器执行 JSP 的方式与执行常规 Servlet 类似。不过,JSP 编译产生的 Servlet 包含生成的代码,这些代码的性能优化程度稍逊于手动编写的代码。如果性能对于特定 JSP 非常重要,而使用生成的代码又不能达到目的,请考虑手动将标记编写到输出流中。

  • JSP 中的 Java 代码片断称为 Scriptlet。由于 JSP 将转换为 Java 源代码,因此使用 Scriptilet 并没有真正的性能损失。WebSphere Application Server 的最新版本中的某些优化将在 JSP 文件不包含任何 Scriptlet 的情况下应用。通常,不应将 Scriptlet 代码放置到 JSP 中,而应使用标记完成这些任务。

  • JSP 中可以包含其他 JSP。这意味着单个 JSP 不必对请求作出全部响应;可以将响应拆分为多个 JSP,在父 JSP 中包括其他 JSP。有两种包含方式,静态包含和动态包含:

    • 静态 JSP 包含在编译时解析。JSP 编译器会包含所引用的文件,而不包含 include 语句。此选项通常非常快,完全不会增加运行时开销。

      <%@ include file="include2.jsp" %>
    • 动态 JSP 包含在运行时解析,开销并不小。就垃圾生成和执行时间而言,解析要调度的正确 JSP 开销非常大。例如(在 JSP 中):

      <jsp:include page="include2.jsp" flush="true" %>

      JSP 中的动态包含在通过 Servlet 代码包含其他文件时使用如下语句:

      javax.servlet.RequestDispatcher

      因此,只要有可能,应该尽量使用静态包含。动态包含提供了最高的灵活性,但如果使用过于频繁,会带来巨大的性能开销。

EJB 用法

Enterprise JavaBean (EJB) 定义了一个基于组件的体系结构,用于构建可扩展的分布式多用户业务应用程序。EJB 组件设计用于封装业务逻辑,并同时将所有的复杂性隐藏在 Bean 和内置 EJB 容器服务后。

对企业应用程序频繁使用的各种功能的支持会带来一定的性能开销,在使用 EJB 时需要加以考虑。

  • Portlet 可以通过 JNDI 查询包含 EJB 引用,而 JNDI 查询在性能方面开销很大。例如,如果 Portlet 并不缓存对 EJB 主接口的引用,则每个对 EJB 的逻辑引用需要两次远程调用:一个调用命名服务,另一个调用实际的对象。为了改进这种情况,请使用缓存技术以减少或消除对 EJB 主引用的重复查询。

  • EJB 组件将公开远程接口和本地接口。依赖于位置的 EJB 将使用远程接口。方法参数和返回值将在 RMI-IIOP 上序列化,并由值返回。远程方法必须设计为能够根据 API 的使用模式满足数据需求。请使用 API 中的适合接口的使用情况的方法和数据类型粒度,以尽可能减少序列化开销。

  • 尽可能减少远程调用的数量,以减少由于代码路径中的远程调用带来的系统开销。使用会话 Bean 作为远程外观使用,对复杂交互进行包装,并减少 Portlet 和域对象间的远程调用。直接访问远程实体 Bean 的 Portlet 通常会导致多个远程方法调用。如果在此环境中使用实体 Bean,请避免给予其远程接口。作为外观的会话 Bean 将通过其本地接口访问实体 Bean,从其收集数据,然后将此信息返回发出调用的应用程序。

    当发出调用的客户机(如会话外观)与被调用的 EJB 共享同一个容器时,本地接口的概念将会有效果。使用本地接口可以消除分布式对象协议的系统开销,从而降低进程间通信开销。本地调用并不会通过通信层,所有对象均可以通过引用传递。

  • EJB 容器支持的事务管理也可以影响性能。开发了 EJB 后,程序员必须设置定义各种特征(如 EJB 的事务支持和隔离级别)的部署描述符。如果不需要事务,请将事务类型设置为 NotSupported。

  • 事务隔离级别是基础数据库将已更改但尚未提交的数据向其他事务公开的程度。为了获得最佳的性能,请使用自由隔离级别。不过,让其事务看到未提交的数据可以带来意料之外的副作用,如更新冲突和读取不一致等。有关如何设置隔离级别的说明,请参阅 WebSphere Application Server V5.1.x 信息中心

请参阅 IBM 白皮书 WebSphere Application Server Development 性能和扩展性最佳实践和 IBM 红皮书 IBM WebSphere V5.1 性能、扩展性和高可用性 WebSphere 手册系列,以获得其他建议以及关于每个建议的相关理由。

标记大小

标记大小指从门户服务器传输到客户机的完全呈现门户页面的字节数量。从门户服务器的角度来看,最重要的部分是包含结果标记的 HTML 页面的大小。也必须将其他文件(如样式表、图像或 JavaScript)传输到客户机。由于静态文件通常保存在 HTTP 服务器或代理缓存上的门户系统之外,所以,此处我们将主要讨论“真正的”HTML 标记大小。

到底出于什么原因非得关注标记大小呢?在公司的内部网内,网络带宽的问题可能会少一些,但如果用户通过调制解调器或其他低带宽网络连接到门户,大型 HTML 响应很长的下载时间可能会令人非常受不了。

让我们进行一个简单的计算。假设服务器或集群每秒钟最多能处理 100 个请求。HTML 页面大小应该为 100KB,这个值虽然看着很大,但如果在页面上有复杂的主题和若干个 Portlet,就很容易达到这个大小。对于服务器,这意味着必须提供约 10MB/sec 的速度(100 KB * 100 页面/秒)。而这个值是 100MB 的网络可以处理的最大通信流量。(以太网不可能百分之百地支持其 100MB/sec 的速度,且传入通信流量也不容忽视。对于通过 56K 调制解调器连接到门户的用户,每个页面的下载时间应在 15 秒的时间范围内!)

多大能称为太大?这个问题通常很难回答。不过,每个 HTML 页面的大小超过 100KB 可能就太大了。另外,还要记住,较小的设备对其可以处理的每个请求的标记大小有一定的限制。

构成标记大小的主要内容是主题和 Portlet 输出。由于所有门户 JSP 均可自定义,所以可以改变标记的在终端的紧凑程度。要限制标记大小,可以采取以下措施:

  • 在 JSP 中使用 JSP 注释,而不是 HTML 注释。

    JSP 编译器将删除窗体注释 <%-- ... --%>,而保留窗体注释 <!-- ... -->并将其通过网络传输。

  • 尽量减少 JSP 源文件中的空白、制表符和分行符,因为 JSP 编译器将会保留这些内容。

    这可能会降低代码的可读性。这些内容可以帮助开发布局良好的代码,但在 JSP 文件应用到生产环境前,将使用工具对其进行处理,除去其格式设置。

  • 尽量避免多次向客户机发送相同的信息。

    例如,样式定义应当放入独立的 CSS 文件中。JavaScript 代码也应如此。而且,由于这些独立的文件通常不会更改,因此可以将其缓存在浏览器或代理缓存中,从而进一步减少网络通信流量。

  • 如果您的环境设置为支持压缩,还可以使用 HTTP 压缩将压缩过的标记发送到的客户机。

    请参考 Web 服务器和客户机的文档,以获得详细信息。

日志记录、跟踪和 I/O

日志通常最终会涉及到对硬盘写入。从性能的角度而言,任何与磁盘频繁进行交互的内容都是潜在的大开销操作,因此,最好尽量减少在生产环境中使用 Java I/O 库。由于通常通过使用某些 Java 编程之下的本机库提供 I/O,因此会有一定的缺省系统开销。System.out.println 之类的操作在文件 I/O 期间会对处理进行同步,这将对性能造成很大的影响。

在开发和测试模式中,可能希望所有日志记录和调试功能均为活动状态,因为这些功能对于发现错误非常重要。在生产环境中部署应用程序时,让各种日志功能均处于打开状态并非可行的选择。最佳实践应是对日志语句加以保护,使其仅在出错和进行调试的情况下打开。可以通过使用一个最终的 Boolean 变量实现此功能,当将其值设置为 false 时,可以有效地指示编译器进行优化,不再检查和执行日志记录代码:

static final boolean LOGGING = false;
if (LOGGING) {...}

Java 语言提供了两种流:读取器/写入器和输入/输出:

  • 读取器和写入器是在 I/O 操作中支持 unicode 字符的高级接口。
  • 输入/输出流提供非常低的级别(字节级)的数据访问机制。

读取器/写入器有性能开销,因为它们旨在用于字符流,且会在后台将数据编码为字节。只要希望操作二进制数据,就应该使用输入/输出流。

为了尽可能提高 I/O 性能,应该对读取和写入操作进行缓存。如果希望写入大量来自 Portlet 的数据,通常最好采用对已缓存的数据进行部分刷新的方式,而不采用对全部数据一次性刷新的方式。另一方面,不要太频繁地刷新缓冲区。

同步与多线程

用于协调对共享对象的访问的 Java 机制称为同步。同步语句一次仅允许一个线程进入代码块。

  • 在 Portlet 的生存期中,容器会将不同线程中的服务请求发送到单个 Portlet 实例。请避免在 Portlet 中进行同步,因为同步有很大的性能影响:同步会减少并发,因为在同步块中一次仅允许运行一个线程,所有并发的线程都要进行排队。另外,Java 虚拟机会使用监视器以支持同步,管理这些监视器也有性能开销。除了性能影响之外,还可能出现死锁,而这可能导致单个 Portlet 冻结,或者甚至更糟,导致整个门户冻结。由于监视器不支持进行任何死锁处理,因此程序员应负责防止死锁的出现。

  • 在有必要进行同步的场合下,应该尽量缩小同步代码块。准确地识别哪些代码真正需要同步并尽可能少地进行同步,这非常重要。如果同步代码块不够小,应该对代码进行分析,对其重构,以使所有可以异步运行的代码均位于同步代码块之外。

  • 某些 Java J2SE 功能会间接地使用同步。Java 集合类(如 Vector 或 Hashtable)都是全面同步的。即使在单线程环境中 Java 程序也会有与线程同步相关的开销。Java 1.2 引入的较新的集合(如 ArrayList)并不进行同步。这就提供了对数据更快的访问。在需要线程安全的情况下,请使用线程安全视图。线程安全视图是包装类,该类增加了同步标准集合方法的功能。集合类的工厂方法将返回线程安全的集合,该集合由特定的集合类型的实例支持:

    List list = Collections.sychronizedList(new ArrayList());
  • 另一个非直接同步的例子就是 Java I/O 库。请尽可能少地使用 Java I/O 库方法(例如 System.out.println()),以减少不必要的性能开销。

  • 不要从 Portlet 生成非托管线程。当前 J2EE 强烈建议不要试图在容器生成新线程。实际上,J2EE 规范 6.2.1 编程限制指出:

    “如果应用程序组件包含的功能与 J2EE 系统基础结构所提供的功能相同,则会存在功能冲突和管理混乱。例如,……以管理线程……”

    不要试图生成新线程的一个实际原因是因为新线程对 J2EE 上下文没有完全访问权限。而且,新创建的非托管线程会妨碍 WebSphere Portal 实现稳定的、优化的可扩展运行时环境。因此,请使用 WebSphere Application Server 中的异步 Bean 功能(请参阅 WebSphere Application Server Enterprise V5 和编程模型扩展 WebSphere 手册系列)。异步 Bean 是一个 Java 对象或 Enterprise Bean,能够使用 J2EE 上下文提交在独立线程(异步)运行的代码。

Portlet

Portlet 编程模型允许开发人员创建特定类型的 Web 应用程序,此类应用程序可以作为客户机浏览器中若干此类应用程序的聚合视图的一部分。在 WebSphere Portal 中,此类应用程序不仅能共存在一个页面(即聚合视图)上,还能在构造该页面时彼此进行通信。因此,Portlet 的实现可以影响页面的总体性能;例如,如果特定的“关键”Portlet 驻留在页面上,则值得花精力在同一个页面上实现一些其他的关键性能 Portlet。

后端访问

在实际的门户中,完全自我依赖的 Portlet 非常少见,因为门户通常用作网站的附加内容或帮助工具。此类 Portlet 应仅在其本地代码执行路径中优化,不应对允许的门户系统带来太多的负荷。

Portlet 更为典型的用户就是提供需要访问其他数据源或事务系统的应用程序功能,除了 Portlet 的原始执行系统之外,这些数据源或事务系统也需要执行资源。数据可能会从网络上的其他后端系统检索或存储到其中。需要在总体系统设计中考虑在后端系统上可能出现的事务长度、隔离级别以及数据锁定。

请注意,单个 Portlet 可能不是后端系统的唯一客户机。事实上,在实际使用中,会有很多客户机连接到此类系统,甚至单个 Portlet 还可能同时多次访问同一个后端系统。Portlet 可能会在多个独立的服务器线程中执行其代码以响应不同的用户请求。因此,有必要对访问模式进行了解,Portlet 或其他客户机获取事务或锁定的方式可能会影响此类后端系统的平均响应时间。

如果某个 Portlet 在操作或呈现阶段需要进行密集的后端系统访问,响应时间(完成这些阶段的时间)将越来越依赖于后端系统的响应。(如果等待门户服务器外的响应以满足传入请求,将会带来延迟,此延迟不能通过优化 Portlet 代码的执行路径得到改善。)具有后端系统通信的良好设计,并了解事务行为通常可以得到更高的性能。

为了避免由于后端系统崩溃而使 Portlet(以及其所在页面)停止响应,可以在代码中加入超时机制;不过,请注意,管理和跟踪时间戳会带来一些处理开销。如果使用了 WebSphere Portal 中的并行 Porlet 呈现功能(稍后讨论),则可为并行呈现线程配置超时。

尽可能减少与此类外部后端系统的交互和数据通信流量也是不错的做法。为了实现这一点,如果信息的刷新标准允许进行缓存,Portlet 可以对信息进行缓存。这可以减少为每个传入 WebSphere Portal 请求多次获取相同数据的往返次数。这样还可以帮助降低后端系统上的负载,因为这样就无需多次提供相同的信息了。另外,如果不需要在网络上传输数据,Portlet 可能可以更快地进行呈现。

避免到后端系统的往返的另一个方法就是除了检索满足当前请求实际所需的数据外,还检索所知的将在可能的后续请求中所需的数据。不过,使用此方法时,如果知道在后续请求中将要实际需要哪些预提取数据,我们仍然建议使用普通的预提取功能。为了合理地设计此特性,需要对 Porlet 应用程序的典型用户交互非常了解。要记住,提前检索会对门户 JVM 的内存使用造成影响。(请参阅性能和可伸缩性代码设计。)此类设计方法可能需要更改后端系统的接口,但可以节约大量的处理时间,使得更改物超所值。

对于缓存,WebSphere Application Server 利用其面向的 Portlet 的 DistributedMap 接口提供了动态缓存功能。(请参阅 WebSphere Application Server 5.1 信息中心以获得更多的信息。)

会话与其他数据存储区

保持和维护 Portlet 的数据,使其生存期长于单个请求的生存期,这是一个典型的 Portlet 编程任务。通常考虑采用的第一个方法就是使用 PortletSession。从程序员的角度而言,PortletSession 使用很方便,但从应用程序服务器的角度而言,管理会话需要使用资源。如果会话包含越来越多的数据,从而要求使用更多的内存,则会进一步使问题严重化。

如果将会话配置为持久地存储在数据库中,或配置为进行内存到内存复制(即在集群化环境中为 WebSphere Portal 配置了故障转移),则该会话将在其内容更改时被序列化。

当会话数据写入到远程副本时,对会话数据进行序列化和反序列化所需的时间可能变得非常大。在非常少见的情况下,存储在会话中的某些对象可能被标记为瞬态的。这将降低会话的序列化后的大小,但不会更改内存的大小,而这对应用程序服务器处理会话的效率也有影响。

大型的会话对象会减少可用以创建和执行应用程序对象的 JVM 内存。因此,随着可用堆内存的减少而导致更频繁的垃圾回收,性能可能会降低。

另一个因素就是内存内的生存期比所需的使用时间长,因此占用 Java 堆中的空间的会话数量通常比活动用户的数量多。在 WebSphere Application Server 中可以配置会话过期时间,这个属性非常必要,可以防止在几秒钟没有活动后就要求用户再次登录的情况。会话的释放由 WebSphere Application Server 和 Portlet 容器负责。

序列化的会话大小应该小于 4KB,因为 WebSphere Application Server 能以可以接受的数据库性能开销存储此类会话,在网络上传输此类会话的时间也更少。如果会话大小超过了 32KB,数据库必须使用面向二进制大对象配置的表单元格,而如果此类会话从数据库检索或写入到数据库中,则将需要访问物理磁盘(对于大多数受支持的数据库)。

由以上分析得出的第一个结论就是,从应用程序的角度而言,应该尽可能避免创建会话。在大多数公共页面和无需身份验证的页面上,通常不需要会话。在此类页面上可以通过呈现链接与门户进行交互,而呈现链接定义为不更改服务器端的状态。门户将为每个 Portlet 维护呈现参数,以用于对该页面的所有后续请求。为了避免 JSP 缺省创建会话,应该将 JSP 中的页面会话指令设置为 false:

<@ page session="false"%>

否则,如果不存在会话,此 JSP 将创建一个会话。

以下的 Java 代码片段演示了如何确保传入会话加入现有的会话,而不是无条件地创建新会话:

PortletRequest.getPortletSession(false)

将此参数的值设置为 false 时,如果之前不存在会话,将不会创建会话。如果之前不存在会话,仅为了在其中存储数据而在 Portlet 中创建一个会话,可能并不合适。

由以上分析得到的第二个结论就是,要将会话误用作通用数据存储机制。请记住,我们的目的是尽可能使会话保持最小。如果由于 Portlet 的设计,将某些数据保存在内存中具有一定优势,则可以使用缓存。可以使用会话 ID 设置缓存条目的范围,以使会话和要保存在内存中的数据建立关联。请注意,此类缓存在呈现故障转移时不支持集群;而这有时是可以接受的折衷。如果数据可以使用其他 Portlet 可用数据重新创建,则缓存条目的会话范围要求就有待商榷。

在很多情况下,通过仅在会话中存储一个键,并使用该键作为引用以在其他数据结构中查找更大的对象,从而可以避免在会话中存储大对象。另外,可以选择使用相同信息的更紧凑的表示形式,而后将该对象放入会话中。

而且,Portlet 设计需要仔细考虑会话中实际存储的内容。会话通常仅旨在用于存储用户交互与门户应用程序的对话状态(例如,网上商店 Porltet 中的购物车的内容)。此类数据不能采用其他任何手段重新创建。在 WebSphere Portal 中,这种类型的数据处理称为会话状态

如果并不需要会话状态,Portlet 可以使用其他数据存储选项:

  • 在 Portlet 的操作阶段,可以为 Portlet 的后续呈现阶段设置呈现参数。Portlet 使用呈现参数呈现其特定于一组特定值的视图。由容器在请求间维护呈现参数,即使出现与其他 Portlet 的交互也是如此。在 WebSphere Portal 中,这种类型的数据处理称为导航状态

  • 如果需要跨多个用户会话保持数据,则可以使用 PortletPreferences API 为 Portlet 存储数据。请记住,此 API 并不能替代通用数据库。在 WebSphere Portal 中,这种类型的数据处理称为持久性状态

  • PortletConfig API 使 Portlet 可以读取其配置,该配置由开发人员通过使用 Portlet 部署描述符提供;这对于 Porltet 的所有用户均有效。

  • PortletContext API 允许存储同一应用程序中其他 Portlet 也可以访问的属性。

请考虑使用会话之外的其他选择,将其用于存储 Portlet 创建和使用的数据。避免将可以通过用户交互之外的其他源重新创建的数据复制到会话中。

呈现链接与操作链接

与对特定的 Portlet 视图寻址相比,使用呈现参数有很多优势。

如果 WebSphere Portal 检测到了 Portlet 的操作参数,则必须调用特殊的操作阶段处理,使其具有不必使用操作参数的优势。不过,请注意,处理呈现链接时一定不能更改 Portlet 的服务器端状态。要更改服务器端状态,唯一得到认可的方法就是使用操作链接,而对于事务类型的请求,操作链接是最好的选择。

使用呈现链接而不使用操作链接的例子很多。例如,假设一个报纸 Portlet 可以同使用“上一页”和“下一页”按钮显示特定的页面。逐页浏览报纸的页面不一定会更改服务器端的状态,此状态在本例中就是报纸中包含的全部信息。为了寻址报纸的下一页,将下一页的的页码编码到所显示按钮的呈现链接中就足够了。Portlet 可以根据呈现参数中所给的页码确定要呈现的页面。

此外,由于每个呈现的视图都由独立的 URL 寻址,所以,通过使用呈现链接而不使用操作链接,还可以充分利用缓存基础结构(无论是浏览器缓存还是代理缓存)。URL 是用于访问此类缓存基础结构中的特定生成视图的唯一的键。

Portlet 功能

接下来的几个部分中将讨论开发人员应该考虑的 WebSphere Portal 中可用的一些 Porlet 优化功能,这些功能可以影响所选择的实现技术。需要使用 Portlet 的部署描述符提供一些必须的设置,而且,由于这些项也是由 Portlet 开发人员提供的,因此被认为是自定义代码。

允许 Portlet 进行并行呈现

WebSphere Portal 提供了让页面上的 Portlet 并行呈现的选项。此功能并非完全“免费”的,因为需要计算资源以维护和管理呈现每个 Portlet 所使用的不同线程。

如果涉及到很多后端系统,而每个后端系统在呈现单个页面时都会产生延迟,此时使用并行 Portlet 呈现就具有一定优势。例如,假设一个门户页面包含很多 Portlet,每个 Portlet 都会访问不同的后端系统。在串行呈现模式中,从所有后端系统检索所需数据的总体延迟为各个延迟时间的总和。而在并行呈现模式中,延迟时间应为所有单个延迟时间中的最大值。

如果 Portlet 并不经常使用后端系统,由于启用并行 Portlet 呈现所带来的开销可能会比由此功能所带来的好处更大。如果页面上的 Portlet 能够独立于后端系统进行呈现,则只需要门户服务器计算机本地的 CPU 资源。这种情况下,页面呈现响应时间不会得到改进。

可以使用图形用户界面、部署描述符或 WebSphere Portal 的 XML 访问接口启用并行 Portlet 呈现。而且,还有一个相关的全局属性值,可以全面开启和关闭并行 Portlet 呈现功能。

要正确回答是否支持并行 Portlet 呈现门户这一问题,需要考虑若干事项;例如,呈现页面所涉及到的后端系统的数量、使用并行 Portlet 呈现的页面上的 Portlet 的平均数量,等等。Portlet 开发人员事先不一定能给出这些问题答案,但如果合理的话,开发人员当然事先可以确保为 Portlet 启用了并行 Portlet 呈现。

在 Portlet 容器中进行缓存

基于 Portlet 的 Web 页面是动态聚合的,因为它们能以个性化的方式提供动态内容。这个灵活性具有一定的开销。由于为了响应请求生成这些页面必须进行额外的工作,故而网站的响应时间将增加。

新的缓存技术将改善动态页面的生成和减少系统负载。WebSphere Portal 支持片断缓存(也称为 Servlet 缓存),可以使用 WebSphere Application Server 动态缓存在缓存中保存 Portlet 输出。对缓存的 Portlet 的请求将从缓存(而不是 Portlet)检索内容。可以通过在部署描述符中指定过期实现片断缓存的失效。而且,在 Portlet 的操作阶段也会使片断缓存条目失效。

激活片断缓存不需要进行费时的安装和集成工作。通过使用简单的 XML 部署描述符文件和通过使用 WebSphere Application Server 管理控制台均可以启用和禁用该缓存功能。(请参阅 WebSphere Portal 信息中心,以了解在 WebSphere Application Server 中启用 Servlet 缓存的详细信息。)

为了使用基于过期的缓存,Portlet 必须在部署描述符 portlet.xml(对于符合 JSR 168 规范的标准化 Portlet)中定义过期缓存的持续时间:

<expiration-cache>300</expiration-cache>
  • 整数定义缓存条目在缓存中存在的秒数值。

  • 值 -1 指示 Portlet 缓存永远不过期。

  • 值 0 指示为该 Portlet 禁用缓存功能。

一定不能在同一 Portlet 的所有用户间共享缓存的条目。此缓存技术是基于特定 Portlet 的特定用户的。

对于在其部署描述符中定义了过期缓存的 JSR 168 Portlet,Portlet 窗口可以在运行时通过设置 RenderResponse 中的 EXPIRATION_CACHE 属性修改过期时间,如下所示:

renderResponse.setProperty(
   RenderResponse.EXPIRATION_CACHE,
   String.valueOf(numberCrunchingCalculation())
);

对于在从后端(如 EJB 组件和数据库)计算其响应和请求数据时计算时间很长的复杂 Portlet,此方法非常有用。对于简单 Portlet,不应启用片断缓存。WebSphere Portal 将使用额外的执行资源计算机片断缓存的内部缓存键。对于简单 Portlet,由于缓存键计算比重新计算 Portlet 响应开销更大,其性能可能会降低。

对于真正动态的 Portlet,片断缓存并不适用;如,对每个请求都需要从其他数据源收集当前数据的基于实时的 Portlet 或对每个请求都会更改其响应标记的 Portlet。这将会导致大量的缓存失效,因此性能不会得到提高。所以,仅在 Portlet 的输出在更新前会在一段时间内保持有效的情况下才应该为 Portlet 启用缓存功能。

在远程缓存中进行缓存

通过独特的自适应缓存功能,WebSphere Portal 可以在门户缓存之外的缓存(称为远程缓存)中动态缓存生成的页面(如果所有页面组件均指示自身可以缓存)。如果从远程提供完全呈现的页面,就可以避免到门户服务器的往返,此类页面的响应时间可以与从静态网站提供时一样快。

有关远程缓存的全部详细信息,请参阅使用 WebSphere Portal V5.1 开发包含静态内容和动态内容的高性能网站

Portlet(以及主题)可以提供完全呈现页面的总体远程缓存信息中其所特定的远程缓存信息。远程缓存信息一个数据结构,由关于缓存范围(是否可缓存,是共享的,还是非共享的)和过期时间(内容在多长时间内为有效)的信息组成。可以通过部署描述符或 WebSphere Portal GUI 提供 Portlet 的远程缓存信息。除此之外,Portlet 还可以在呈现时为每个 Portlet 提供远程缓存信息,如下面的代码中所示:

. . . 
import com.ibm.wps.util.RemoteCacheInfo;
import javax.portlet.RenderResponse;
. . .
/* Do rendering */
public void doView(RenderRequest renderRequest, RenderResponse renderResponse)
   throws PortletException, IOException {
   /* Some code might happen here */
   . . . 
   /* Publish a dynamic expiration time during rendering */
   renderResponse.setProperty( 
      RenderResponse.EXPIRATION_CACHE, 
      String.valueOf(numberCrunchingCalculation())
   );
   /* Publish a cache scope value of "shared" during rendering *)
   renderResponse.setProperty( 
      RemoteCacheInfo.KEY_SCOPE, RemoteCacheInfo.Scope.SHARED_STRING );
   /* Some other code might happen here */
   . . . 
}

设置远程缓存信息的方式依赖于呈现的视图的“刷新”要求和范围。请注意,如果从缓存提供呈现的页面,请求可能甚至不会发送到门户服务器。

如果可以在基础结构中使用缓存,自定义 Portlet 开发人员应当考虑利用远程缓存功能。

主题与外观

在门户术语中,主题是确定门户应用程序的外观和风格的若干 JSP 集。由于主题由 JSP 组成,在 JSP 部分给出的技巧也适用于此。这一部分详细讨论了使用组成主题的 JSP 文件集可能存在的性能缺陷。

通常,主题由很多不同的文件组成,每个文件提供屏幕的特定区域的内容。尽管可以动态地包含 JSP,但通常(也建议)将 JSP 静态包含在其他 JSP 中。

由于编译时可能会将很多 JSP 包含到其他 JSP 中,所得的 Java 源代码和 Servlet 字节代码文件可能会非常大。使用大的类文件通常不会有性能问题,但由于 Java 编程语言中包含的大小限制,可能不能将 Java 源代码编译为类。例如,Java 中的方法的大小不能超过 64KB。大型的复杂主题很容易达到这个限制,而导致不再能编译。这种情况下,有三种选择:

  • 用动态包含代替一些(而非全部)静态包含。

    如 JSP 部分 (LINK) 提到的,这是用性能作为交换,以便能编译 JSP。从性能的角度而言,尽管这个方法最易于实现,但却是最不好的解决方法。

  • 尽量限制 JSP 中 Scriptlet 的使用。

    WebSphere Application Server 可以对仅调用标记处理程序的代码进行优化,而这可以有助于使文件保持在 64KB 的上限之内。

  • 清除 JSP 代码。

    这些文件通常包含并非必要的多余代码。通常删除 HTML 注释行或空白,或者将 JavaScript 代码移动到单独的文件中均可保证足够的空间。

主题有时会完成应用程序中复杂的任务。不过此时应该谨慎。请记住,对于门户的每个请求,都会呈现主题,因此不要在其中进行会给系统带来高负荷的计算工作。

在模拟门户功能时要特别谨慎。例如,主题可能会循环访问门户应用程序中的大量页面;应该对此进行筛选,仅向用户显示一个导航结构,其中仅包含主题从门户 API 请求的若干页面。这种情况下,门户中进行的很多处理都会丢失,因为之后会将其结果丢弃。此处根据门户访问控制或个性化规则进行筛选会更为有效。

此外,要尽量限制门户页面中门户资源链接的数量。门户必须生成的每个 URL 链接都会给系统带来额外的负载。如果需要具有大量链接的应用程序主题,请尝试缓存其中的一些页面,从而使其不必在每次请求时都重新计算所有链接。

主题也是 WebSphere Portal 中的远程缓存基础结构的一部分。主题的远程缓存是一组可以通过 XML 访问具体指定的元数据,如以下示例中所示:

<!-- Theme "shared" scope and 40 seconds cache expiration -->
<theme action="update" active="true" objectid="xmplTheme" 
  uniquename="wps.theme.example">  
   <parameter name="remote-cache-scope" type="string" update="set">
   SHARED
   </parameter> 
   <parameter name="remote-cache-expiry" type="string" update="set">
   40
   </parameter>     
</theme>

主题不能提供任何呈现时远程缓存信息。

WebSphere Portal 支持高性能外观的识别。这些外观非常特殊,因为它们不是基于 JSP 生成的;它们的输出是根据预编译的 Java 类创建的。当然,此类外观的可自定义性要差一些;只能对样式表信息和包含的图像进行修改。不过,如果性能是您要考虑的最重要的因素的话,就应该考虑为页面上特定的元素或特定 Portlet 启用高性能外观。(请参阅参考资料中的信息中心以了解详细信息,包括各种可帮助您编写高速外观和主题的提示。)

工具

在 WebSphere Portal 应用程序开发和验证的所有阶段均可以使用各种工具提供帮助。本部分对不同开发周期中可以使用的不同工具类别进行了说明,并提供了一些例子,以帮助您进行自定义代码的开发和分析。

开发环境

从技术角度而言,可以使用任何文本编辑器编写 Portlet、主题和外观,但使用集成开发环境(如将 IBM Rational® Application Developer 和 IBM Portal Toolkit 结合使用)要方便很多。还可以使用 Portlet 代码示例和基本门户代码片断开速入门;该开发环境还与一个门户服务器进行了集成,以便立即部署和测试代码。

性能分析工具

当代码就绪,可以部署时,需要详细了解其可能的性能问题。可以采取若干步骤(下面对此进行了总结),但性能方面有一条始终适用的一般规则:在大多数程序中,约有 80% 的执行时间都花在 20% 的应用程序代码中。这 20% 的代码位于“关键路径”上,正是这些方面值得进行性能优化。例如,Portlet 的呈现方法要比其初始方法的性能关键性更强,因为每个请求都会调用呈现方法。

  • 代码分析应在开发的早期阶段进行,或将其作为开发后的第一个性能测试。分析意味着将在方法级收集执行时信息,通常会使用 JVMPI 接口进行此项工作。分析器结果可以帮助标识应用程序的关键路径;即大部分时间所执行的代码。分析器还通常会给出关于对象创建速率和内存使用的信息。

  • 一旦将 Portlet 部署到了门户中,就应该测试 Portlet 在负载下的行为。压力或负载生成器(如 Rational Performance Tester、Rational Robot、Apache JMeter 等等)是具有成本效益的负载测试解决方案,可以帮助您准确地模拟生产负载下的系统性能。这些工具将收集大量信息,以帮助确定系统是否具有良好的性能设计,其中包括关于请求响应时间、处理器使用率等的数据。

  • 在负载测试期间,应该监视门户环境中的若干性能参数。IBM Tivoli® Performance Viewer(与 WebSphere Application Server 一起提供)可以帮助监视应用程序服务器内的资源使用情况。

  • 门户环境的许多问题都和内存有关。JVM 实现为工具提供了两类信息,以供进行性能分析:

    • 垃圾回收器的输出 verbose:gc。
    • 堆转储,发现内存泄漏时非常有用。

    在 IBM alphaWorks 中可以得到垃圾回收器输出的分析工具。而另一方面,heapRoots 则是一款强大的堆转储分析辅助工具。《IBM Java 诊断指南》也提供了处理门户的相关性能问题的有用信息。请参阅参考资料,以获得这些参考资料的链接。

开发 WebSphere Portal 代码时,通常不需要所有这些工具,但要在生产环境中推出更大的门户,必须从性能的角度对门户代码有个良好的理解。

结束语

创建自定义门户代码时,开发人员必须考虑很多方面的因素,以确保门户性能得到优化。小结如下:

  • 将精力主要放在关键代码路径的改进上。关键代码路径是处理时间长或频繁执行的代码路径。找到哪些类的哪些方法位于关键路径上。在关键路径外的优化效果相当小。

  • 要同时兼顾执行性能和内存分配。

  • 使用恰当的工具测量和分析代码,以获得最典型的用户交互。

  • 不同编码问题解决方案可能有很大的性能变化。

  • 必须全面了解处理发现的性能问题的特定实现的细节。

  • 设计自定义代码时要考虑后端访问模式。

  • 不要错误地将会话作为 Portlet 的通用数据存储区使用。可以采用更好地方法处理数据,以满足各种不同的实现要求。

  • 考虑利用 WebSphere Application Server 和 WebSphere Portal 提供的特殊功能以优化 Portlet 性能(假设目标环境也在使用相同的功能)。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere
ArticleID=94218
ArticleTitle=IBM WebSphere 开发者技术期刊: 自定义门户代码的性能注意事项
publish-date=08172005