参看原文
提纲:
━━━━
一、深入了解应用服务器
二、JDBC
三、EJB
四、Servlet和JSP
五、Java消息服务
六、Java事务API(JTA)
七、其他因素
━━━━
正文:
━━━━
在前面两篇文章中,我们界定了性能优化的意义(即提高并发用户数量、吞吐量和可靠性),定义了优化应用和应用服务器要采用的方法。现在我们要深入应用服务器,看看有哪些因素可以调整,以及调整这些因素会对应用性能产生什么影响。具体地说,我们要深入J2EE规范,结合应用服务器的实践知识,找出优化性能的办法。
一、深入了解应用服务器
当前的大多数应用服务器遵从J2EE 1.3规范,也即它们满足J2EE
1.3规范定义的所有需求。虽然J2EE规范只有一个,但本文提到的J2EE规范一般指一组相关的规范,因为J2EE规范涉及其他许多“应用编程接口”,而这些编程接口又有各自的规范。
首先我们来看看J2EE 1.3规范,它的官方地址是:http://java.sun.com/j2ee/download.html。从J2EE规范的官方网站可以看到,当前最新的J2EE规范是J2EE
1.4 Beta 2 Implementation,不过下面我们要讨论的J2EE规范仍旧是1.3,因为当前的应用服务器还不支持1.4。
J2EE规范第六章定义了必须支持的组件及其版本,表一是其概况。
表一 J2EE规范:组件及其版本
组件 版本
JDBC 2.0
Enterprise JavaBeans(EJB) 2.0
Servlet 2.3
JavaServer Pages (JSP) 1.2
Java Messaging Service(JMS) 1.0
Java Transaction API (JTA) 1.0
JavaMail 1.2
J2EE Connection Architecture (JCA) 1.0
Authentication and Authorization (JAAS) 1.0
|
在J2EE规范的官方网站上可以找到上述所有技术规范的链接。下面我们将从性能的角度出发,看看它们各自的要点所在。
由于每一个应用服务器都必须支持上述API集,所以我们首先可以从一个抽象的层次来观察一个应用服务器,了解有哪些性能因素可供调整。这样,当我们看到一个具体的应用服务器时,只要找出它是如何实现规范要求的各项技术就可以了。
二、JDBC
对于数据库连接,所有的应用服务器都必须提供缓冲池机制。在应用程序中创建数据库连接是一项开销很大的操作,通常要耗费0.5到2秒的时间。因此,应用服务器缓冲了数据库连接,使得不同的应用程序、同一应用程序内的多个线程能够共享一组数据库连接,避免每次需要数据库连接时都从头开始创建连接。
通过连接池使用数据库连接的一般过程为:当某个线程需要访问数据库时,它向数据库连接池请求一个连接,然后用连接池返回的连接执行数据库操作(例如SELECT或UPDATE、DELETE命令),操作结束后再把数据库连接对象返回给连接池,以便其他组件使用该连接。由于J2EE应用支持多个并发用户,连接池的规模会极大地影响应用的性能。例如,如果一个应用的90%请求都需要访问数据库,且应用必须支持500个用户同时访问,则连接池也应当准备数量庞大的连接。
每一个应用都有不同的数据库访问要求,因此如何调整连接池中连接的数量也必须根据应用的具体情况确定。在优化应用性能的实践中,必须时刻牢记的一条重要原则是:很多时候,JDBC连接池的规模往往是对应用的整体性能表现影响最大的因素之一。
三、EJB
EJB提供J2EE应用的中间层组件,它有四种类型:实体Bean,有状态会话Bean,无状态会话Bean,消息驱动的Bean。
实体Bean和有状态会话Bean都要维持某种类型的状态信息。实体Bean可能代表数据库或查询结果的一个行,但不管怎样,从面向对象的角度来看实体Bean也是一个对象。另一方面,有状态会话Bean代表的是一种临时存储机制,它保存的数据可以跨越单个请求的范围,但不是永久保存的。一般地,在Web应用中,有状态会话Bean的生命周期总是与用户的HTTP会话相关联的。由于有状态会话Bean的本质是用来维持状态,应用服务器必须为它们提供某种缓冲机制。一些简易的应用服务器用一个单一的缓冲区来保存所有的实体Bean和有状态会话Bean,但高级一些的应用服务器会提供更复杂、完善的缓冲机制。
缓冲区的大小是预先设定的,它能够保存的对象数量也有一定的上限。当出现对某个对象的请求时,系统搜索缓冲区查找当前请求的对象:如果在缓冲区中找到了对象,就直接从内存把它返回给调用者;如果找不到,则必须从永久存储设备(例如数据库或文件系统)读取对象,把它放入缓冲区,再返回给调用者。如果缓冲区已满,处理经过就要复杂多了:缓冲区管理器必须从缓冲区移出某个对象(例如,最近最少使用的对象),以便为新的对象腾出空间。用EJB的术语来说,把某个对象移出缓冲区的操作称为钝化(passivating),把对象装入缓冲区的操作称为激活(activating)。如果一个系统中钝化操作和激活操作过于频繁,很可能导致缓冲区管理器消耗在读/写持久性存储设备上的时间远远超过其实际服务于请求的时间,这种现象称为系统颠簸(thrashing);反之,如果缓冲区管理器能够在缓冲区中找到对象并直接把它返回给用户,系统性能当然会大大提高,这种现象称为缓冲区命中(cache
hit)。
调整缓冲区的时候,应当记住调整的目标是尽可能提高缓冲区命中的次数,尽可能减少系统颠簸现象。要达到该目标,首先必须深入、全面地理解应用的对象模型以及每一个对象的使用情况。
无状态会话Bean和消息驱动的Bean不能维持任何状态信息。如果一个进程在第一次操作中请求一个无状态会话Bean,在另一次操作中又请求同一类型的无状态会话Bean,系统不保证该进程会收到同一个Bean实例。这一机制带来很多方便,因为Bean管理器不必管理业务进程及其所属Bean的交互过程,它只要在请求出现时提供适当类型的Bean实例就可以了。
为了给来自业务过程的请求提供流畅的服务,Bean缓冲池必须足够大,否则的话,业务过程可能要等待一段时间才能获得Bean实例来完成其操作。如果缓冲池太小,有可能会出现许多业务过程等待Bean实例的情形;如果缓冲池太大,它会占用超出其实际需求的系统资源。
无状态会话Bean和消息驱动的Bean还有一个很有用的特点,应用服务器可以预先装入它们,而不必等到请求出现时才把它们装入缓冲池。因此,对于无状态会话Bean和消息驱动的Bean来说,两个很重要的性能选项就是缓冲池的大小和预先装入缓冲池的Bean的数量。
四、Servlet和JSP
Servlet和JSP各有独立的规范,但在运行时装入内存的实际上只有Servlet,因为JSP会被自动转换成Servlet。Servlet和JSP本身也不能在多个请求之间维持状态信息,因此应用服务器可以方便地把它们放入缓冲池,就性能而言,可供调整的两个最重要的选项也是缓冲池的大小和预先装入缓冲池的Servlet的数量。
由于JSP在装入内存之前要先经过转换和编译的工序,大多数应用服务器支持一种在部署之前预先编译JSP的机制,避免JSP页面第一次被调用时可能会出现的延迟。
Servlet(以及JSP)支持四种不同的数据保存范围(或内存区域):
◆ Page:保存在这里的数据只在单个页面范围有效。
◆ Request:保存在这里的数据在单个请求的范围内有效(在返回应答给调用者之前,数据在Servlet、JSP页面之间传递)。
◆ Session:保存在这里的数据在用户会话期间一直有效(数据的有效范围跨越多个请求,直至请求超时或数据被显式地清除)。
◆ Application:保存在这里的数据是全局性数据,对应用的所有Servlet和JSP都有效,除非数据被显式地清除,或者Servlet容器重新启动。
在Servlet/JSP编程中,数据存储位置的选择是非常重要的,它将极大地影响应用占用的内存总量。四种范围之中,尤以session范围对内存占用的影响最大:保存在session范围中的数据将按照并发用户数量的倍数占用内存。例如,如果应用在session范围中保存了10
KB数据,有500用户在运行应用,那么这些数据实际占用的内存将达到5
MB。仅仅占用5 MB内存当然不会使应用出现资源紧张的情形,但考虑一下,假设原来的500个用户离开了,又来了另外500个用户,这时内存占用就将达到10
MB,这种情形还有可能进一步发展下去,导致内存耗用越来越大。
HTTP是一种无状态的协议,也就是说,当客户程序连接到服务器,发出一个请求,服务器作出应答,连接就结束了。应用服务器不知道用户实际离开网站和结束会话的时间,它使用的是一种超时机制,也就是定义一个时间,如果用户在该时间范围内没有再次向服务器发送请求,则该会话就被清除。会话超时时间的设置依赖于应用、用户和要用多少内存来保留用户的会话,既不能使动作慢的用户频繁地重新建立会话,又要避免超时时间设置得过长,白白耗费内存来保留那些实际上不再需要的会话。
五、Java消息服务
JMS服务器为应用程序执行异步操作带来了方便。随着EJB
2.0的出现,EJB家族中增加了一种消息驱动的Bean——这是一种无状态的Bean,代表着由JMS消息初始化的业务过程。一个程序把消息放入JMS的目标(destination),目标可以是一个主题(Topic)也可以是一个队列(Queue),另一个程序从目标提取消息,然后根据消息的内容执行相应的业务操作。
消息由JMS服务器管理,应用服务器通常会定下某种限制,或者限制同一时刻驻留在服务器上的消息的数量,或者限制允许消息占用的总空间。制定这些上限值必须遵照一定的原则,即在内存消耗和为JMS订阅者提供流畅的服务之间取得平衡。如果限制值太小,有可能丢失消息;如果限制值太大,它可能占用过多的系统资源,从而拖累整个系统的性能。
除了存储空间之外,JMS服务器还有其他一些可调整的性能因素,包括:消息传递模式(持久化或者非持久化),存活时间(定义消息的过期时间),事务状态,消息回执。
虽然有许多可以调整的性能因素,但最基本的问题其实只有一个:对于业务处理过程而言,这些消息的重要程度如何?如果丢失其中的一、二个消息,是否会出现问题?显然,越是不在乎消息是否真正到达目的地,提高性能的可能性就越大——但这一切最终要根据业务需求决定。
六、Java事务API(JTA)
在应用服务器之内,组件是可以事务化的。也就是说,如果一个事务的某个操作步骤失败,则整个事务被回退,所有参与该事务的组件也被回退。J2EE定义了事务的不同等级,组件参与事务越深入,开销也越大,同时业务处理过程的可靠性也就越高。EJB允许为每个方法定义参与事务的等级,包括:
◆ Not Supported:该方法不支持事务,必须排除在任何已有的事务之外。
◆ Required:必须有事务支持。如果已有一个事务,该方法将使用已有的事务;否则,必须为该方法新建一个事务。
◆ Supports:事务不是必需的,但如果已有一个事务,该方法将参与事务。
◆ Requires New:必须有一个新建的事务。不管是否存在已定义的事务,都必须为该方法新建一个事务。
◆ Mandatory:强制的——必须有一个事务,而且必须把它传递给该方法。如果方法被调用时事务还不存在,系统将抛出异常。
◆ Never:禁用事务。如果调用该方法时存在一个事务,方法将抛出一个异常。
每一种事务等级的含义都很明显,其性能影响也可以从上面的说明大约看出:Supported影响最小,性能最佳,代价是可能丢失数据;Required比较安全,开销也略大;Requires
New也许是开销最大的事务等级。
七、其他因素
前文已涉及了各个J2EE规范中可找到的大多数影响性能的因素,除此之外,还有三个伴随着应用服务器而来的因素会极大地影响性能。
由于应用服务器能够同时为多个并发的用户请求提供服务,而创建线程又是一项昂贵的操作,所以应用服务器必须通过一个线程池来处理各种请求。一些应用服务器把这个线程池一分为二:其中一个用来处理进入的请求,把请求放入队列;另一个从队列获取线程,然后执行调用者要求执行的处理任务。不管具体的实现方式是哪一种,线程池的大小限制了应用服务器的工作量,所以必须找出一个最佳的平衡点——如果超越这个平衡点,则线程的上下文切换(将CPU依次分配给各个线程)将产生大量开销,从而影响性能。
另一个必须考虑的性能因素是应用服务器占用的堆栈大小。我们已经看到,应用服务器需要操作和管理各种缓冲区、池、会话、线程以及应用本身的代码,因此分配给应用服务器的内存总量将极大地影响其整体性能。一个简单的原则是:在任何一台机器上,将所有可支配的内存全部分配给应用服务器。
最后必须考虑的一个问题是调整应用服务器的垃圾收集机制。随着各种数据不断地被装入内存,应用服务器的堆栈很快就会被填满——这时候就要垃圾收集器来释放不再使用的资源。当前的大多数应用服务器都带有Sun
JDK 1.3.x,它定义了两种垃圾收集策略:minor和major。
minor垃圾收集策略通过一个名为copying的过程执行,效率较高;而major垃圾收集策略则通过一个名为mark
compact的过程执行,对虚拟机的工作压力较大。堆栈根据对象的“年龄”分两个区:第一个是young
generation,这里的对象创建之后很快就会被删除;第二个是old
generation,在这里对象被保存到比较稳固的内存区域。young
generation通过copying执行minor垃圾收集,而old generation通过mark
compact来执行major垃圾收集。因此,在调整垃圾收集机制时,我们的目标应该是调整两个分区的大小,尽可能多用minor垃圾收集策略,少用major垃圾收集策略。
结束语:本文介绍了许多影响应用性能表现的因素。我们从J2EE规范出发,历数规范要求的各个API,分析其可调整的性能选项。现在,你可以根据本文介绍的基础知识,继续深入了解和优化应用、应用服务器了。
|