作者:Steven Haines;编译:仙人掌工作室
在前面两篇文章中,我们界定了性能优化的意义(即提高并发用户数量、吞吐量和可靠性),定义了优化应用和应用服务器要采用的方法。现在我们要深入应用服务器,看看有哪些因素可以调整,以及调整这些因素会对应用性能产生什么影响。具体地说,我们要深入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的目标