一切皆可抽象

大而无形 庖丁解牛 厚积薄发 涤虑玄览
   ::  ::  ::  ::  :: 管理
原来地址
http://blog.csdn.net/jaminwm/archive/2007/03/26/1541767.aspx


调优背景

HBCZT信息中心使用 IBM X366 服务器 Windows2003 运行其基于 J2EE1.4 技术的应用系统。另外运行一个基于 COM 技术的数据采集应用程序。该程序客户端读入用户填写的 xls 格式表格文件信息,并通过该程序将 XLS 内容封装成为 XML 并打包 ZIP 后发送到数据采集程序的服务器端,服务器端接受到文件后,对该 ZIP 包进行解包、并对解包后的 XML 信息进行解析、使用 SQL 逐条将记录插入到 Oracle 数据库中。数据库连接池已经设置为 20 ,但批量数据插入数据库的时候(数据量至少 500000 条记录,一般情况 5000000 条记录)导致数据库异常缓慢。客户希望找到系统瓶颈,并提出相应性能调优建议。

l         总体思路

硬件调优、操作系统调优,数据库调优 略!我们假设都已经是最佳状态。由于本人负责 WebLogic 部分的调优,所以以下思路与内容均为 WebLogic 方面。特此说明

J2EE 应用架构环境下的系统调优,首先我们一般会从应用程序出发,去审核代码,做到代码级的优化,然后再调整应用服务器 (BEA WebLogic8.1) 和数据库 (Oracle9i) 的参数,最后当然是调整操作系统和网络的性能 ( 包括硬件升级 ) 。这是一种 MDA 的先进做法。诚然,在这样一个政务项目中,不可能完全按照这个思路来做,我们把目标首先定位在应用系统所在的应用服务器 (BEA WebLogic8.1) 上,通过对 BEA WebLogic8.1 的参数进行设置,使 WebLogic8.1 能够在最优化的环境中去运行其系统,然后对 ORACLE 数据的参数进行优化设置,最后进行性能测试再找出导致性能瓶颈所在的 SQL 代码或 JAVA 程序,考量其修改的可行性,并进行最终问题优先级认定,与瓶颈模块进行协商解决性能问题。当然,一般情况下我见过的案例都是出现了性能问题后才想到调优,而且一般都是先进行系统参数调整,实在解决不了才会对代码进行检查 . 实际上,我们应当将代码级的调优放在应用设计时来做,测试生产时修改代码将是一件极其痛苦的事情。

下表为一般性 J2EE 性能调优的参照情况一览表,供参考。

毛病

描述

症状

原因或治法

线性内存泄漏

每单位 ( 每事务、每用户等 ) 泄漏造成内存随着时间或负载线性增长。这会随着时间或负载增长降低系统性能。只有重启才有可能恢复。

随着时间越来越慢
随着负载越来越慢

虽然可能有多种外部原因,但最典型的是与资源泄漏有关 ( 例如,每单位数据的链表存储,或者没有回收的回收 / 增长缓冲区 )

指数方式内存泄漏

双倍增长策略的泄漏造成系统内存消耗表现为时间的指数曲线

随着时间越来越慢
随着负载越来越慢

通常是由于向集合 (Vector HashMap) 中加入永远不删除的元素造成的。

糟糕的编码:无限循环

线程在 while(true) 语句以及类似的语句里阻塞。

可以预见的锁定

您需要对循环进行大刀阔斧的删剪。

资源泄漏

JDBC 语句, CICS 事务网关连接,以及类似的东西被泄漏了,造成对 Java 桥接层和后端系统的影响。

随着时间越来越慢
可以预见的锁定
突然混乱

通常情况下,这是由于遗漏了 finally 块,或者更简单点,就是忘记用 close() 关闭代表外部资源的对象所造成的。

外部瓶颈问题

后端或者其他外部系统(如鉴权)越来越慢,同样减缓了 J2EE 应用服务器和应用程序

持续缓慢
随着负载越来越慢

咨询专家(负责的第三方或者系统管理员),获取解决外部瓶颈问题的方法。

外部系统

J2EE 应用程序通过太大或太多的请求滥用后端系统。

持续缓慢
随着负载越来越慢

清除冗余的工作请求 ,成批处理相似的工作请求,把大的请求分解成若干个更小的请求,调整工作请求或后端系统 ( 例如,公共查询关键字的索引 ) 等。

糟糕的编码: CPU 密集的组件

这是 J2EE 世界中常见的感冒。一些糟糕的代码或大量代码之间一次糟糕的交互,就挂起了 CPU ,把吞吐速度减慢到爬行的速度。

持续缓慢
随着负载越来越慢

典型的解决方案就是数据高速缓存或者性能计数。

中间层问题

实现得很糟糕的桥接层 (JDBC 驱动程序,到传统系统的 CORBA 连接 ) ,由于对数据和请求不断的排列、解除排列,从而把所有通过它的流量减慢到爬行速度。这个毛病在早期阶段很容易与外部瓶颈混淆。

持续缓慢
随着负载越来越慢

检查桥接层和外部系统的版本兼容性。如果有可能,评估不同的桥接供应商。如果重新规划架构,有可能完全不需要桥接。

内部资源瓶颈:过度使用或分配不足

内部资源 ( 线程、放入池的对象 ) 变得稀缺。是在正确使用的情况下加大负载时出现过度使用还是因为泄漏?

随着负载越来越慢
零星的挂起或异常错误

分配不足:根据预期的最大负载提高池的最大尺寸。过度使用:请参阅外部系统的过度使用。

不停止的重试

这包括对失败请求连续的 ( 或者在极端情况下无休止的 ) 重试。

可以预见的锁定
突然混乱

可能就是后端系统完全宕机。在这里,可用性监控会有帮助,或者就是把尝试与成功分开。

线程:阻塞点

线程在过于积极的同步点上备份,造成交通阻塞。

随着负载越来越慢
零星的挂起或异常错误
可以预见的锁定
突然混乱

可能同步是不必要的 ( 只要重新设计 ) ,或者比较外在的锁定策略 ( 例如,读 / 写锁 ) 也许会有帮助。

线程:死锁 / 活动锁

最普遍,这是您基本的“获得顺序”的问题。

突然混乱

处理选项包括:主锁,确定的获得顺序,以及银行家算法。

 

l         调优建议

通过分析其配置。我们发现 JDBC 连接池存在性能问题。

WebLogic 中就大量使用了池 :JDBC Connection Pool Socket Pool Object Pool Thread Pool I/O 操作中, buffer 是必须的,特别是对大文件的操作,不然容易造成内存溢出。字节操作最快,所以尽可能采用 write(byte[]) Buffered FileOutputStream Buffered FileWriter 要快,因为 FileWriter 需要 Unicode Byte 的转换。 JDBC 建议使用 buffer cache 。为 HttpServletResponse 设置 buffersize ,使用 wl-cache ,缓存在 JNDI 树上获取的对象等等。
  此外,使用 JDK 1.4 的非阻塞 I/O 对性能也有很大提高。
   JDBC 代码调优最大的原则就是使用 WebLogic 的连接池,而不是自己直连数据库。在我接触的很多自己实现连接池的项目中,大部分遇到死锁和连接泄漏的问题,最后得不得修改代码。而 WebLogic 提供了功能强大,性能良好的数据库连接池,我们要做的只是封装一个连接管理类,从 JNDI 树上获取数据源并缓存,得到连接,并提供一系列关闭数据库资源的方法。

对任何资源使用的原则是用完即关,不管是数据库资源、上下文环境,还是文件。数据库资源的泄漏极易造成内存泄漏,乃至系统崩溃。在使用完数据库资源后依次关闭 ResultSet Statement Connection ,而在一个数据库连接多次进行数据库操作时要特别注意 ResultSet Statement 依次关闭。

由于获取连接时默认自动提交方式,使用 connection.setAutoCommit(false) 关闭自动提交,使用 PreparedStatement ,批量更新,业务复杂或者大数据量操作时使用存储过程,尽量使用 RowSet ,此外设置记录集读取缓存 FetchSize 和设置记录集读取方向 FetchDirection 对性能也有一定的提高。

Servlet 代码调优比较简单:在 Servlet 之间跳转时, forward sendRedirect 更有效;设置 HttpServletResponse 缓冲区,如: response.setBufferSize(20000); init() 方法里缓存静态数据,而在 destroy() 中释放它;建议在 Servlet 里使用 ServletOutputStream 输出图片等对象;避免在 Servlet Jsp 中定界事务等。

JDBC Connection Pool 的调优受制于 WebLogic Server 线程数的设置和数据库进程数,游标的大小。通常我们在一个线程中使用一个连接,所以连接数并不是越多越好,为避免两边的资源消耗,建议设置连接池的最大值等于或者略小于线程数。同时为了减少新建连接的开销,将最小值和最大值设为一致。增加 Statement Cache Size 对于大量使用 PreparedStatement 对象的应用程序很有帮助, WebLogic 能够为每一个连接缓存这些对象,此值默认为 10 。在保证数据库游标大小足够的前提下,可以根据需要提高 Statement Cache Size 。比如当你设置连接数为 25 Cache Size 10 时,数据库可能需要打开 25*10=250 个游标。不幸的是,当遇到与 PreparedStatement Cache 有关的应用程序错误时,你需要将 Cache Size 设置为 0 。尽管 JDBC Connection Pool 提供了很多高级参数,在开发模式下比较有用,但大部分在生产环境下不需调整。这里建议最好不要设置测试表, 同时 Test Reserved Connections Test Released Connections 也无需勾上。 当然如果你的数据库不稳定,时断时续,你就可能需要上述的参数打开。

最后分析一下 JDBC 驱动程序类型的选择, Oracle 提供 thin 驱动和 oci 驱动,从性能上来讲, oci 驱动强于 thin 驱动,特别是大数据量的操作。但在简单的数据库操作中,性能相差不大。所以我建议对数据量至少 500000 条记录,一般情况 5000000 条记录的状况使用 oci 驱动。

通过分析其日志并使用 GC 资源查看。我们发现存在内存泄露的垃圾回收失败问题。

垃圾收集 (GC) 是指 JVM 释放 Java 堆中不再使用的对象所占用的内存的过程,而 Java (Heap) 是指 Java 应用程序对象生存的空间。堆大小决定了 GC 的频度和时间。堆越大, GC 频度低,速度慢。堆越小, GC 频度高,速度快。所以 GC 和堆大小是一组矛盾。为了获取理想的 Heap 堆大小,需要使用 -verbosegc 参数 (Sun jdk: -Xloggc:) 以打开详细的 GC 输出。分析 GC 的频度和时间,结合应用最大负载所需内存情况,得出堆的大小。通常情况下,我们建议使用可用内存 ( 除操作系统和其他应用程序占用之外的内存 )70-80% ,为避免堆大小调整引起的开销,设置内存堆的最小值等于最大值即 :-Xms=-Xmx 。而为了防止内存溢出,建议在生产环境堆大小至少为 256M(Platform 至少 512M) ,实际环境中 512M~1G 左右性能最佳, 2G 以上是不可取的,在调整内存时可能需要调整核心参数进程的允许最大内存数。对于 sun hp jvm ,永久域太小 ( 默认 4M) 也可能造成内存溢出,应增加参 -XX:MaxPermSize=128m 。建议设置临时域 -Xmn 的大小为 -Xmx 1/4~1/3 SurvivorRatio 8

为了获得更好的性能,建议在启动文件设置 WebLogic 为产品模式,此时 sun hp jvm JIT 引擎为 -server ,默认情况下打开 JIT 编译模式对性能也有帮助。调整 Chunk Size Chunk Pool Size 也可能对系统的吞吐量有提高。此外还需关闭显示 GC: -XX:+DisableExplicitGC

建议 Intel 平台上使用 jRockit (使用参数 -jrockit )无疑大大提高 WebLogic 性能。本系统使用 SUN JDK1.4.2_08 jRockit 支持四种垃圾收集器:分代复制收集器、单空间并发收集器、分代并发收集器和并行收集器。默认状态下, JRockit 使用分代并发收集器。要改变收集器,可使用 -Xgc: ,对应四个收集器分其他为 gencopy singlecom gencon 以及 parallel 。为得到更好的响应性能,应该使用并发垃圾回收器: -Xgc:gencon ,可使用 -Xms -Xmx 设置堆栈的初始大小和最大值,要设置护理域 -Xns -Xmx 10% 。而如果要得到更好的性能,应该选用并行垃圾回收器 :-Xgc: parallel ,由于并行垃圾回收器不使用 nursery ,不必设置 -Xns jRockit 还提供了强大的图形化监控工具 Jrockit Management Console 。可以查看 GC 性能问题。

通过实时查看并分析操作系统与 ORACLE 系统中的 I/O 信息。我们发现存在 I/O 读写占用资源率高且频繁问题。

WebLogic Server有两套套接字复用器:Java版和本地库。采用小型本地库更有效,尽量激活Enable Native IO(默认),此时UNIX默认使用CPUs+1个线程,Window下为双倍CPU。如果系统不能加载本地库,将会抛出一个异常:java.lang.UnsatisfiedLinkException,此时只能使用Java套接字复用器,可以调整socket readers 百分比,默认为33%。该参数可以在Console Server Tuning Configuration配置栏里设置。


只有注册用户登录后才能发表评论。


网站导航: