内部调校
本节会详细的说明一组特定的技术,协助读者能使Tomcat实例运行得更快,而且无论使 用何种操作系统或JVM皆然,在许多情形下,您都可能无法控制部署的机器的操作系统 或JVM。在这种情况下,您仍然应该遵循上节所述提供建议。不过,您应能影响Tomcat本身的变化。当在内部调校TomCat时,笔者认为下列的范畴是最佳的起点。
停用DNS查询
当Web应用程序想要记录客户端的信息时,它可以记录客户端的IP地址,或在“域名服务” (DNS)数据中査询真正的主机名,DNS査询需要耗费网络资源,并涉及多部服务器的往返响应,而这些服务器可能位于遥远的地方,也可能尚未运行,因此导致延迟,您可以关闭DNS査询,以避免这些延迟。之后,每当Web应用程序调用在HTTP请求对象 中的getRemoteHost()方法时T它只会得到IP地址。在Tomcat的文件中,您可 以在应用程序的Connector对象中设定此功能,对于常用的“java.io” (HTTP1.1)连接 器,则请使用enableLookups属性。只需在server.xml文件中找到下列部分:
(Connector port="8080" maxHttpHeaderSize="8192" maxThreads="l50" minSpareThreads="Z5" maxSpareThreads="75" enable Lookups="ture" redirectPort="8443" acceptCount="100" connectionTimeout="20000" disableUploadTimeout="true"/>
只要将enableLookups的值"true"改成"false"然后重新启动Tomcat。这样将不再使用 DNS査询,也不会有延迟了!
除非需要所有连接至网站的HTTP客户端的完整限定主机名称,否则笔者建议在实 作的网站上关闭DNS查询。请记住,稍后总可以在Tomcat外查询主机名。关闭DNS查询 不仅可以节省网络带宽、査询时间及内存,而且在因高网络流量而产生大量日志数据的网站中,还可以节省相当可观的哽盘空间。对于低流量的网站,关闭DNS查询则可能没 有什么明显效果,不过这仍不失为一个好方法,低流量网站也可能在一夜之间就变成高流量网站。
调整线程数
另一种对应用程序之Connector性能控制的方法是控制其处理的进程数。Tomcat默认使用线程池以便对传人的请求提供快速响应。Java线程(正如在其他程序语言中一样)是一种分散流控制,自己可以与操作系统及本地内存互动,伹在迸程处理中,所有线程会共享一些内存。这让开发者可以提供更细微的程序代码管理,以便快速响应许多传人的请求。
逋过更改Connector的minThreads与maxThreads的值,可以控制所分配的线程数。前面所示的值适合一般的安装,而随着网站的扩大可能需要增长,minThreads的值应该大到 能够处理最小负载量。即如果在低谷时刻,每秒可访问五次网页,而每次请求在一秒钟 之内可完成,则预先分配五个线程就够了。随后在高峰时刻,则需要分配更多线程(最 多到maxThreads所设定的数目)。为了防止流量暴增(或黑客的DoS攻让系统超出 JVM的最大内存限制,而使服务器瘫痪,因此一定要设定上限。
将这两个参数设为最佳值的最好方式是对各个参数尝试许多不同的设定值,然后以仿真 的网络流量进行测试,同是并观察响应时间与内存的占用情况,每一种机器,操作系统 与JVM的组合都可能有不同的表现,而且并非所有人的网站流量都会相同,所以没有现 成的规则来决定最小与最大的线程数。
加快JSP的编译速度
当第一次访问JSP时,此JSP会转换成Java servlet的源代码,然后必须再编译成Java的 bytecode。
用请求预编译JSP
因为JSP通常是通过Web第一次访问进行编译的,因此,在安装已更新的JSP之后,您会希望对其执行预编译,而不是等到第一个用户造访时才编译。这样处理有助于确保新 JSP在在用服务器上能像在测试机器上一样工作。
在Tomcat bin/目录有一个叫做Jspc的脚本文件,好像就足用来预先编译JSP的,不过 并非如此。它运行于JSP濂码到Java源码的转换阶段,而不是Java编译阶段,而且会在当 前目录中产生最终Java源文件,而不是在应用程序的work目录下产生Java源文件。它主 要用于调试跟踪JSP。
对任何给定的JSP文件,确保预编译的最佳方法是简单地用Web客户端访问JSP。这可以确保把文件转换成编译的servlet,然后运行,同时这也可以楕确地仿真用户访问JSP的 方式,让您看到他们访问的内容,您可以从中査找错误、纠正错误,然后重复泫处理过程。当然,这种开发周期最好是在开发环境下完成,而不是在在用服务器上完成。
在Web应用程序启动时预编译JSP
Java Servlet规范很少使用但最优秀的另一特性是在Web应用程序启动时,指定servlet容 器必须允许Web应用程序指向应被预编译的JSP页。
例如,如果想让index.jsp(在Web应用程序根目录中)在Web应用程序启动时就一直被预 编译,则在文件中可以对该文件增加一个<servlet>标签,如下所示:
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/ javaee/web-app_2_5.xsd" version="2.5"> <servlet> <servlet-name>index.jsp</servlet-name> <jsp-file>/index.jsp</jsp-file> <lood-on-startup>0</load-on-startup> </servlet> </web-app>
随后,Tomcat将在Web应用程序启动时自动为您预编译而恰恰就是对 的第一个请求将被映射为该JSP预编译的servlet类文件。
以这种方式在Web应用程序中配置预编译,意味着无论Web客户端是否请求JSP,JSP的所有编译工怍都在Web应用程序启动时完成。以这种方式在web.xml中声明的JSP网页都会被预编译。这种方法的缺陷在干Web应用程序启动的时间总是较长,因为在Web客户端能访问Web应用程序之前,必须预编译所指定的毎个网页。
在编译时使用JspC预编译JSP
在处理JSP的构建时(build-time)预编译处理过程中,需要考虑下列有效(截止本书发稿)因素:
要从Web应用程序中挤压出所有的性能,且在构建时编译的JSP相比于在Web应用 程序部署后在Tomcat中编译的JSP要快。首先,在这两种情况产生的Java类字节代码(bytecode)实际都应一样,但如果并不完全一样,则其差别也是非常细微的, 不值得改变主要的部署策略,如…定要在部署前预编译JSP。当然,Tomcat编译初 始的JSP所耗的时间通常很小,而且只在Web应用程序部署/重新部署之后才在第一次请求每个JSP页时发生这种时间占用。所有其他对JSP网页的请雀都由编译的和 加载的JSP servlet类(JSP被编译到Java servlet中)提供。但因为Web应用程序部署前编译的JSP被映射为中的URI space,所以Tomcat根据请求寻找路由的速率能比在Web应用程序运行的时候编译的JSP网页要略快。这是因为在运行的过 程中编译了JSP问页,导致最终的serWet必须通过常规URI映射起首先映射为URI space,将请求发送到JspServlet然后将该请求映射为Tomcat JspServlet对应的被 请求的JSP页。注意运行时被编译的JSP都被映射为间按的两层(两个截然不同的 映射器),且预编译的JSP只通过间接的第一层进行映射。性能差别降到两种不同 URI映射器状态之间的性能差异。最后,预编译的JSP通常运行速度要快4%,在 部署应用程序之前进行预编译会节省初始请求编译Web应用程序中毎个JSP网页的 少最时间,加上随后对每个JSP网页请求性能改善在Tomcat 4.1.x中,运行时 JSP请求映射器明显比web.xml servlet映射器要慢,且值得在部署Web应用程序之间 进行预编译u这使得在测试中访问JSP网页的速度大约加快12%。但是,对Tomcat 5.0x和更髙版本,这一差距大约降到4%甚至更少。
通过在构建或封装Web应用程序的时候预编译JSP,可以在JSP编译过程中检査JSP 语法,这意味着在部署Web应用程序之前,您至少可以自信JSP没有语法梢误。这可以避免在在线(product)服务器上部署Web应用程序之后就发现JSP语法错误, 而且是被第一个请求该网页的用户发现的。另外,在代码开发阶段发现错误允许 后,允许开发者更快速地查找并修补错误,缩短了开发周期。但这不会阻止各种 bug,因为编译的JSP仍然由运行时逻辑bug,但至少在开发环境下可以捕捉到所有语法错误。
如果在Web应用程序中包含大JSP文件,且每个文件都有点长(非常希望您不要从一个JSP网页复制并粘贴许多内容到大量其他JSP网页上T如果要这样处理,您应 改为利用包含特性的JSP),那么初始化所有组合JSP网页的编译时间明显要长。如 果是这样的话,您吋以在部署Web应用程序之前逋过预编译节省在用服务器的响应 时间。如果业务流量负载较高,那么这样处理特別有利,而且服务器响应杏甽会降 低很多,而在Web应用程序首次启动时服务器会同时对许多JSP网页实现初始化编译。
示例4-4是一个Ant构建文件,可用于构建时编译Web应用程序的JSP文件。
示例4-4:预编译的jsps.xml Ant构建文件
<project name="pre-compile-jsps" default="compile-jsp-servlets"> <!--私有属性--> <property name="webapp.dir" value="${basedir}/webapp-dir"/> <property name="tomcat.home" value="/opt/tomcat"/> <property name="jspc.pkg-prefix" value="com.mycompany"/> <property name="jspc.dir-prefix" value="com/mycompany"/> <!--编译属性--> <property name="debug" value="on"/> <property name="debuglevel" value="lines,vars,source"/> <property name="deprecation" value="on"/> <property name="encoding" value="ISO-8859-1"/> <property name="optimize" value="off"/> <property name="build.compiler" value="modern"/> <property name="source.version" value="1.5"/> <!--初始化路径--> <path id="jspc.classpath"> <fileset dir="${tomcat, home}/bin"> <include name="*.jar"/> </fileset> <fjleset dir="${tomcat. home}/server/lib"> <include name="*.jar"/> </fileset> <fileset dir=,,${tomcat.home}/common/il8n"> <include name="*.jar"/> </fileset> <fileset dir="${tomcat.home}/common/lib"> <include name="*.jar"/> </fileset> <fileset dir="${webapp.dir}/WEB-INF"> <include name="lib/*.jar"/> </fileset> <pathelement location="${webapp.dir}/WEB-INF/classes"/> <pathelement location="${art.home}/lib/ant.jar"/> <pathelement location="${java.home}/../lib/tools.jar"/> </path> <property name="jspc.classpath" refid='jspc.classpath"/> <!-- ============================================================ --> <!-- 从JSP文件产生Java源码和web.xml文件 --> <!-- ============================================================ --> <target name="generate-jsp-java-src"> <mkdir dir="${webapp.dir)/WEB-INF/jspc-src/${jspc.dir.prefix}"/> <taskdef classname="org.apache.jasper.JspC" name="jasper2"> <classpath> <path refid="jspc.classpath"/> </classpath> </taskdef> <touch file="${webapp.dir}/WEB-INF/jspc-web.xml"/> <jasper2 uriroot="${webapp.dir}" package="${jspc.pkg.prefix}" webXmlFragment="${webapp.dir}/WEB-INF/jspc-web.xml" outputDir="${webapp.dir}/WEB-INF/jspc-src/${jspcldir.prefix}" verbose="1"/> </taxget> <!-- ============================================================ --> <!-- 编译JSP servlet文件并由此产生Java类文件 --> <!-- 由JspC任务产生的源码 --> <!-- ============================================================ --> <target name="compile-jsp-servlets" depends="generate-jsp-java-src"> <mkdir dir="${webapp.dir}/WEB-INF/classes"/> <javac srcdir="${webapp.dir}/WEB-INF/jspc-src" destdir="${webapp, dir}/WEB-INF/classes" includes="**/*.java" debug="${debug}" debuglevel="${debuglevel}" deprecation="${deprecation }" encoding="${encoding}" source="${source.version}"> <classpath> <path refid=Mjspc*classpath"/> </classpath> </javac> </target> <!-- ============================================================ --> <!-- 清除所有预编译的JSP源码、类和:jspc-web.xml --> <!-- ============================================================ --> <target name="clean"> <delete dir="${webapp.dir}/WEB-INF/jspc-src"/> <delete dix="${webapp.dir}/WEB-INF/classes/${jspc.dir.prefix}"/> <delete file="${webapp.dir}/WEB-INF/jspc-web.xml"/> </target> </project>
如果将整个Ant build xml内容放到一个叫做pre-complie-jsps.xml的文件中,就可以在已 经拥有的任何文件旁对其进行测试,如果您愿意,还可以把它合并到build.xml中。