任何在市场上成功的产品都拥有良好的性能。虽然成为象WebLogic Server这样广泛使用的产品需要具备很多特性,但性能绝对是必不可少的。
良好的编程习惯在帮助应用运行方面起了很大的作用,但是仅有它们还是不够的。应用服务器必须能够在多种硬件和操作系统之间移植,必须具备通用性以便处理范围更广的应用类型。这就是为什么应用服务器都提供了丰富的调试“按钮”的原因,通过调整这些“按钮”,能够使服务器更适合运行环境以及应用程序。
本文针对WebLogic讨论了其中的某些调试参数,不过并未将所有可调整的属性全部列出。此外,在将此处推荐的方法运用到产品环境之前,建议您先在测试环境中对它们测试一番。
性能监控及瓶颈发现
性能调试的第一步是孤立“危险区域”。性能瓶颈可以存在于整个系统的任一部分――网络、数据库、客户端或应用服务器。重要的是首先确定哪个系统组件引起了性能问题,调试错了组件可能会使情况更糟。
WebLogic Server为系统治理员提供了治理控制台和命令行工具两种方式监控系统性能。服务器端有叫作mbean的集合,用于搜集诸如线程消耗情况、资源剩余情况、缓存使用情况等信息。控制台和命令行治理器都可以从服务器将这些信息调用出来。图1的屏幕快照就显示了EJB容器中缓存的使用和剩余情况,这是控制台提供的性能监控的选项之一。
代码分析器也是应用代码用以探测自身性能瓶颈的另一种有效的工具。有几个很好的代码分析器,如:Wily Introscope, Jprobe, Optimizelt。
EJB 容器
EJB容器中最昂贵的操作当然是数据库调用――装载和存储实体bean。容器也因此提供了各种各样的参数以便减少数据库的访问次数。但不管怎样,除非是在非凡情况下,否则在每个bean的每次交易中,至少都得有一次装载操作和一次存储操作。这些非凡情况是:
1. Bean是只读的。此时,bean只需在第一次访问时装载一次,从来不需要存储操作。当然,假如超出参数read-timeout-seconds的设置,bean将被再次装载。
2. Bean 有专门的或积极的并发策略,且参数db-is-shared 设置为假。此参数在WebLogic Server 7.0中被重新命名为cache-between-transactions。参数db-is-shared 设置为假相当于参数cache-between-transactions设置为真。
3. Bean在交易中未被修改过,此时,容器会将存储操作优化掉。
假如不属于上述任何一种情况,则code path中的每个实体bean在每次交易时,至少会被装载和存储一次。有些特征能够减少数据库的调用或者降低数据库调用的开销,如:高速缓存技术、域(field)分组、并发策略以及紧密关联缓存(eager relationship caching)等,其中的某些特征是WebLogic Server 7.0新增的。
高速缓存:实体bean缓存空间的大小由weblogic-ejb-jar.xml中的参数max-beans-in-cache定义。容器在交易中第一次装载bean时是从数据库调用的,此时bean也被放在缓存中。假如缓存的空间太小,有些bean就被滞留在数据库中。这样,假如不考虑前面提到的前两种非凡情况的话,这些bean在下次调用时就必须重新从数据库装载。从缓存调用bean也意味着此时不必调用setEntityContext()。假如bean的要害(主)键是组合域或者比较复杂,也能省却设置它们的时间。
域分组:域分组是对于查找方法指定从数据库加载的域。假如实体bean与一个较大的BLOB域(比方说,一幅图像)相联系,且很少被访问,则可以定义一个将此域排除在外的域组,该域组与一个查找方法相关联,这样查找时,BLOB域即不会被装载。这种特征只对EJB2.0的bean 适用。
并发策略:在WebLogic Server 7.0中,容器提供了四种并发控制机制。它们是独占式、数据库式、积极式和只读式。并发策略与交易进行时的隔离级别紧密相关。并发控制并不是真正意义上的提高性能的措施,它的主要目的是确保实体bean所表示的数据的一致性,该一致性由bean的部署器所强制要求。无论如何,某些控制机制使得容器处理请求的速度比其它的要快一些,但这种快速是以牺牲数据的一致性为代价的。
最严格的并发策略是独占式,利用非凡主键对bean的访问是经过系列化的,因此每次只能有一个交易对bean进行访问。这虽然在容器内提供了很好的并发控制,但性能受到限制。在交易之间答应互为缓存的时候,这种方法很有用,但在集群环境中不能使用,此时,装载操作被优化掉,因此可能导致丧失并行性。
数据库式的并发策略不同于数据库的并发控制。实体bean在容器中并未被锁定,答应多个交易对相同的实体bean并发操作,因此能够提高性能。当然,这样对隔离的级别也许要求较高,以便确保数据的一致性。
积极式并发策略与数据库的并发控制也不同。不同之处在于对数据一致性的检查发生在对已设定的更新操作进行存储时而非在装载时将整行锁定。假如应用内对同一个bean访问的冲突不是很激烈的情况下,本策略比数据库式的策略要快一些,虽然两个提供了相同的数据一致性保护级别。但是在有冲突发生的时候,本策略要求调用者要重新发起调用。本特征也只对EJB 2.0 适用。
只读式策略只能用于只读bean。Bean只在应用第一次访问时或者超出参数read-timeout-seconds所指定的值时才被装载。Bean从来不需要被存储。当基本数据改变时,也会通过read-mostly格式通知bean,从而引起重新装载。
紧密关联缓存: 假如两个实体bean, bean A 和bean B 在CMR(容器关系治理)内关联,两个在同一个交易中被访问,且由同样的数据库调用装载,我们称为紧密关联缓存。这是WebLogic Server 7.0的新特征,同样只适用于EJB2.0。
除了上面列出的通过优化容器内对数据库的访问从而达到性能增加的特征外,另有一些在容器之外,针对会话bean和实体bean的参数能够帮助提升性能。
缓冲池和高速缓存是EJB容器为提高会话bean和实体bean性能所提供的主要特征。然而,这些方法并非对所有类型的bean适用。它们的消极面是对内存要求较高,虽然这不是主要的问题。缓冲池适用于无状态会话bean(SLSB),消息驱动bean(MDB)以及实体bean。一旦为SLSB和MDB设定了缓冲池的大小,这些bean的许多实例就会被创建并被放到缓冲池中,setSessionContext()/setMessageDriveContext()方法会被调用。为这些bean设置的缓冲池的大小不必超过所配置的执行线程数(事实上,要求比此数要小)。假如方法setSessionContext()要做任何开销昂贵的操作的话,此时JNDI查询已经完成,使用缓冲池中的实例方法调用将会加快速度。对实体bean来说,在完成setEntityContext()方法调用之后,缓冲池与bean的匿名实例相连(没有主键)。这些实例可以被查询操作所使用,查询方法从缓冲池中取出一个实例,为其指定一个主键,然后从数据库中装载相应的bean。
高速缓存适用于有状态会话bean(SFSB)和实体bean。实体bean已经在前面讨论过。对于SFSB,缓存能够避免向硬盘串行化的操作。串行化到硬盘的操作非常昂贵,绝对应该避免。用于SFSB的缓存大小可以比连接到服务器的并发客户端数略微大些,这是由于仅当缓存被占用了85%以后,容器才会设法将bean滞留在数据库中待命。假如缓存大于实际所需,则容器不会通过缓存花费时间将bean待命。
EJB容器提供了两种方法进行bean-to-bean 和 Web-tier-to-bean的调用操作:传值调用和传送地址调用。假如bean处在同一个应用之中,则缺省情况下,用的是传送地址的方法,这比传值要快一些。传送地址的方法一般不应被禁止,除非有充足的理由要强制这样做。强制使用传送地址的另一种做法是使用本地接口。在WebLogic Server 7.0中引入了另一个特征是对有状态服务使用激活(activation)。虽然这种做法在某种程度上影响了性能,但由于对内存要求较低,因此极大地改进了扩展性。假如扩展性不值得关注,可以将参数noObjectAction传送给ejbc从而关闭激活(activation)。
JDBC
对数据库的访问来说,调试JDBC与调试EJB容器同样重要。比方说设置连接池的大小――连接池应大到足以容纳所有线程对连接的要求。假如所有对数据库的访问能够在缺省的执行队列中得以实现,则连接数应为执行队列中的线程数,比读取socket的线程(缺省执行队列中用来读取进入请求的线程)数要少。为了避免在运行期间对连接进行创建和删除,可在初始时即将连接池设置为其最大容量。假如可能的话,应确保参数TestConnectionsOnReserve被设置为假(false,这是缺省设置)。假如此参数设置为真(true),则在连接被分配给调用者之前,都要经过测试,这会额外要求与数据库的反复连接。
另一个重要的参数是PreparedStatementCacheSize。每个连接都为宏语句设一个静态的缓存,大小由JDBC连接池配置时指定。缓存是静态的,时刻牢记这一点非常重要。这意味着假如缓存的大小是n的话,则只有放在缓存中的前n条语句得到执行。确保昂贵的SQL语句享受到缓存的方法是用一个启动类将这些语句存放到缓存中。尽管缓存技术从很大程度上改进了性能,但也不能盲目使用它。假如数据库的格式有了变化,那么在不重新启动服务器的情况下,无法使缓存中的语句失效或者是用新的进行替换。当然,缓存中的语句会使数据库中的光标得以保留。
对于WebLogic Server 7.0来说,由于jDriver性能的改进已使其速度远远快于Oracle的廋驱动程序,尤其对于要完成大量SELECT操作的应用来说就更是如此。这可以从HP提交的利用WebLogic Server 7.0 Beta版的两份Ecperf结果得到证实(http://ecperf.theserverside.com/ecperf/index.jsp?page=results/top_ten_price_performance)。
JMS
JMS子系统提供了很多的调试参数。JMS消息是由称为JMSDispatcher的独立执行队列处理的。因此,JMS子系统既不会由于运行在缺省或者其它执行队列中的应用因争夺资源而导致“营养匮乏”,反过来也不会抢夺其它应用的资源。对JMS来说,大多数的调试参数都是在服务的质量上进行折衷处理。如,利用文件式持续性目的地(file-persistent destnation)禁止同步写操作(通过设置特性: -Dweblogic.JMSFileStore.SynchronousWritesEnabled =false)以后会引起性能急剧提高,但同时也会冒着丢失消息或者重复接收消息的风险。类似地,利用多点传送发送消息会提升性能,同时也会有消息半途丢失的危险。
消息确认间隔不应设置得过短――发送确认的比率越大,处理消息的速度可能会越慢。同时,假如设置得过大,则意味着系统发生故障时,消息会丢失或者被重复发送。
一般说来,应在单个服务器上对多个JMS目的地进行配置,而不是将它们分散在多个JMS服务器,除非不再需要扩展。
关闭消息页面调度(paging)可能会提高性能,但会影响可扩展性。假如打开消息页面调度(paging),则需要额外的I/O操作以便将消息串行化到硬盘,在必要的时候再读进来,但同时也降低了对内存的要求。
一般来说,异步过程比同步过程更好操作,更易于调节。
Web容器
Web层在应用中更多的是用来生成表达逻辑。广泛使用的体系结构是从应用层读取数据,然后使用servlet和JSP生成动态内容,其中应用层一般由EJB组成。在这种结构中,servlet 和JSP保留对EJB的引用,以防它们与数据库或数据源直接对话。将这些引用保存起来是个不错的主意。假如JSP和servlet没有和EJB部署在同一台应用服务器上,则利用JNDI进行查询的费用是很昂贵的。
JSP缓存标记符可以用于存储JSP页面内的数据。这些标记符都支持对缓存的输入和输出。对缓存的输出涉及到标记符内的代码所生成的内容,对缓存的输入涉及到标记符内的代码对变量的赋值。假如不希望Web层频繁变化,则可以通过将ServletReloadCheckSecs 设置为-1,从而关闭自动装载(auto-reloading)功能。使用这种方法以后,服务器将不再轮询Web层是否有变化,假如JSP和servlet的数量很多,则效果是非常明显的。
这里也建议不要在HTTP会话中储存过多的信息。假如信息是必须的,可以考虑使用有状态会话bean来替代。
JVM调试
如今的大多数JVM具有自主调节功能,因为它们能够探测到代码中的危险区域并对它们进行优化。开发和部署人员能够考虑的调试参数大概就是堆设置了。设置这些并没有一般的规则。JVM一般堆空间,按新空间或保留空间一般设置为整个堆空间的三分之一或一半组织。整个堆空间不能指定得过大以致于无法支持并发的内存垃圾回收(GC)处理。在这种设置环境中,假如堆太大的话,垃圾回收的间隔应设为一分钟或更长。最后,需要引起注重的是这些设置在很大程度上依靠于部署在服务器上的应用使用内存的模式。有关调试JVM的其它信息可以参考:
http://edocs.bea.com/wls/docs70/perform/JVMTuning.Html1104200。
服务器调试
除了由各个子系统提供的调试参数以外,还有适用于服务器的参数能够帮助提升性能。其中最重要的是配置线程数和执行队列数。增加线程数并非总能奏效,仅当下列情况成立时再考虑使用这种方法:预定的吞吐量没有达到;等待队列(未开始处理的请求)过长;CPU仍有剩余。当然,这样做并不一定能改善性能。CPU使用率低可能是由于对服务器的其它资源竞争所致,如,没有足够的JDBC连接。当改变线程数时应考虑到这些类似的因素。
在WebLogic Server 7.0中,提供了配置多个执行队列的功能,并且能够在部署中定义处理非凡的EJB或JSP/servlet请求的执行队列。要做到这些,只要在运行weblogic.ejbc时将标志-dispatchPolicy <队列名称> 传送给bean 即可。对于JSP/servlet,可将设置Web应用的weblogic部署描述符中初始化参数(init-param) wl-dispatch-policy的值设为执行队列的名字即可。有时应用中的某些bean/JSP对操作的响应时间比其它的要长,此时,可以对这些bean/JSP设置单独的执行队列。至于队列的大小,要达到最好的性能,还取决于经验。
另一个比较大的问题是决定在何种情况下应该使用WebLogic性能包(http://e-docs.bea.com/wls/docs70/perform/WLSTuning.html - 1112119)。假如socket数不太多(每个服务器上都有一个socket用于客户端JVM的远程方法调用连接),而且总是忙于读取从客户端发送过来的请求数据,那么此时使用性能包恐怕不会有明显的改进。也有可能不用性能包会导致相似或更好的结果,这取决于JVM在处理网络I/O时的具体实现。
Socket读取线程取自缺省执行队列。在Windows 环境下,每个CPU有两个socket读取线程,在solaris环境下,共有三个socket用于本地输入输出(native I/O)。对于Java 输入输出(I/O),读取线程数由配置文件config.xml中的参数PercentSocketReaderThreads 进行设置。它的缺省值是33%, 上限是50%,这是显而易见的,因为假如没有线程用于处理请求,则同样不会有更多的读取线程啦。对于Java I/O,应使读取线程数尽量接近客户端连接数,因为在等待请求时,Java I/O会阻塞。这也是为什么当客户端的连接数增加时,线程数不能一直同等增加的原因。
结论
我们上面只讨论了调试服务器的部分方法。需要记住的是,一个设计蹩脚,编写欠佳的应用,通常都不会有好的性能表现,无论对服务器及其参数如何调试。贯穿应用开发周期的各个阶段――从设计到部署,性能始终应该是考虑的要害因素。经常发生的情况是性能被放在了功能之后,等到发现了问题再去修改,已经很困难了。有关WebLogic Server 性能调试的其它信息可以参考:http://e-docs.bea.com/wls/docs70/perform/index.html。