目录
1. 性能瓶颈分析方法.......................................................... 3
1.1.1. 根据经验性能瓶颈分析方法.................................................................... 3
1.1.2. 常规性能瓶颈分析方法........................................................................... 5
1.3.1. 常见性能问题的典型表现.......................................................................... 9
1.3.2. 常见应用端性能问题及成因列表............................................................... 9
1.3.3. 常见数据库端性能问题........................................................................... 12
实战篇...................................................................... 18
2. java项目性能监控与诊断................................................... 18
2.1.1. 架构设计上的优化.................................................................................. 18
2.1.2. 代码层面上的优化.................................................................................. 22
2.4. 常见性能问题需要收集以下信息..................................................................... 83
2.4.1. 应用日志和操作系统日志........................................................................ 83
2.4.2. 服务器整体资源使用情况........................................................................ 84
3. C#项目性能监控与诊断.................................................... 103
3.2.3. SQL SERVER监控和优化思路................................................................ 119
4. 附录.................................................................... 140
4.1. 附录一 Loadrunner常用指标分析方法.......................................................... 140
4.2. 附录二 操作系统常用指标分析方法.............................................................. 143
4.2.1. Windows平台常用指标分析方法............................................................. 143
- 性能瓶颈分析方法
- 性能瓶颈分析方法
- 根据经验性能瓶颈分析方法
- 性能瓶颈分析方法
1)错误分析方法
错误分析的方法就是针对性能测试过程中错误信息的分析方法,错误信息产生的位置可以是测试工具端如loadrunner,也可以是应用服务器、数据库服务器端产生的错误日志。其基本方法就是根据错误信息,找到出错的测试脚本位置,如果是应用服务器端报出的错误,在应用服务器上查找出错原因,如果是通讯或其他错误,则需要分析可能的出错原因,逐一验证。
- 错误分析实例1:
Loadrunner报错:
Error: Failed to connect to server "99.1.73.113:8080": [10060] Connection
Error: timed out Error: Server "99.1.73.113" has shut down the connection prematurely
错误分析:
可能的错误原因:
- 应用服务器端进程死掉:需检查应用服务器端程序,检查是什么原因导致的。
- 应用服务器没有死:那么可能是应用服务器参数配置问题,如三方存管测试过程中,SNA Server配置的SNA通讯通道数为60,在每台SNA Server上的用户数超过100时就会频繁发生该错误
- 数据库连接:如果服务器端是数据库,需检查数据库连接数等参数是否设置得太小
- 错误分析实例2:
Loadrunner报错:
Error: Page download timeout (120 seconds) has expired
错误分析:
可能的错误原因:
- 超时时间设置过长,应用服务器压力太大,处理不了这么多请求,可检查应用服务器的资源消耗情况即可发现是否此类问题
- 页面中图片或Flash等内容太多,导致页面内容过大
- 程序处理过程中检查的内容过多,导致处理时间过长,以致超时
2)监控指标分析方法
对监控指标的分析,主要有三种方法:
- 分析监控指标数据:如服务器CPU利用率通常应在70%以下,专用的数据库服务器CPU利用率可接受的上限为80-85%,如果CPU利用率持续在95%以上,说明CPU是瓶颈;又如平均交易响应时间,如果该数据较大,可进一步根据时间戳分析在各阶段花费的时间,如网络时间、Web服务器花费的时间、数据库服务器花费的时间等,再进一步细分,可定位到具体的函数或语句上,找出时间花费超出通常情况的语句,分析其原因。
- 分析监控指标的变化趋势:检查指标变化趋势是否正常,有没有突然的急剧变化,如通常情况下,平均交易响应时间基本随压力的增加逐步加长(程序刚启动时,系统需要把应用装载到内存里,此时平均交易响应时间可能会长一些),如果在上升过程中,发生短时间该数据急剧降低的情况,就需要同时检查这段时间是否有错误产生或其他情况导致平均交易响应时间急剧变化。
- 分析相关指标关联程度:在发生性能问题时,通常我们监控了多种指标,在发现某个指标变化趋势异常时,可能还无法马上确定是什么原因导致的,Loadrunner提供了一种方法,可以分析其他指标变化趋势与指定指标变化趋势的关联程度,包括正相关和负相关的程度,便于我们发现问题产生的原因。如上述平均交易响应时间的变化趋势如果是由错误导致的,那么平均交易响应时间曲线就会与错误统计曲线有很强的相关性。
- 这里指的性能指标可以是操作系统的系统资源,可以是应用服务器性能指标,也可以是数据库性能指标。
监控指标分析实例:
如上图所示(loadrunner平均响应时间图),交易平均响应时间随着时间的推移,越来越大。根据我们测试经验,这种情况有可能是以下2个原因造成的:
1) 应用端内存泄露
2)数据库表数据量逐步增加,加上没有合理索引,导致响应时间越来越大;
性能分析步骤:
1. 观察loadrunner端的性能指标变化,通过指标变化情况,做出初步的假设;
2. 监控应用服务器和数据库服务器资源性能指标变化情况;
3. 如假设应用端内存泄露,则需要监控应用GC日志,通过查看GC日志,就能验证假设是否成立;
4. 如假设数据库SQL问题,则需要监控数据库SQL执行时间的变化情况,如发现某个SQL响应时间越来越长,则可以验证这个假设是成立的。
5. 提出解决方案,并修改bug。
6. 性能回归测试:证实性能问题是否消除
-
-
- 常规性能瓶颈分析方法
-
数据流分析方法是指根据交易数据的流向来确定经过每一个组件的消耗,最终定位到性能瓶颈所在。
使用 数据流分析方法需要先做以下准备工作:
1)熟悉系统架构、交易流程的走向,可以通过需求文档、设计文档和应用日志获取;
2)借助工具去定位性能问题,比如前端页面分析工具: httpwatch, yslow;后台代码分析工具( java: jconsole, Jprofiler, java yourkit profile)(.net: .net yourkit profile)
如上图所示, 表示需要监控操作系统的系统资源,从整体来确定服务器是否存在系统瓶颈,JAVA进程是否消耗过大;
表示 客户端测试工具通过测试脚本发起请求,这时需要确认客户端机器是否存在瓶颈,如存在瓶颈,则发起的压力就变小,或者出现波动。还需要关注脚本的正确性和数据的正确性,这些都跟具体的业务有一定的关系;对于一个web应用,还必须关注web前端的性能;
一个web应用是装载在web容器(websphere、JBOSS等)里的,因此web容器的性能将影响到http请求的性能。常见的web容器关注指标为线程池的大小,数据库连接池的大小,缓存参数等)
表示需要关注代码的性能,需要从几个维度去观察:代码的执行时间、代码的执行次数、消耗的内存、多线程下的性能、异常处理、日志级别
代码的底层是JVM,因此JVM的性能表现将影响到JAVA代码的性能。
操作系统引导分析方法是指把要解决的问题看作为一个系统,从操作系统层面入手分析,推算出应用程序可能出现的性能瓶颈,再深入分析出应用程序的性能,找出解决问题的可行方案的方法。
注意 相对瓶颈概念:
1)请正确评估一台应用服务器所能承受的吞吐量,比如你不能指望一台平台PC能承受应用系统1000TPS吞吐量;
2)注意LR端发送并发用户很大,交易响应时间很大, 但应用服务器CPU利用率很低的情况;
1) CPU
2) Memory
3) IO
4) NETWORK
5) 应用日志
- 错误率过高,会影响性能;
- 日志级别,会影响性能;
6) 操作系统故障
操作系统参数和故障会影响到系统的性能;
只有两种方法结合使用,才能很好定位到具体的性能瓶颈。
两种方法结合
1.先使用操作系统引导分析方法,定位出应用服务器是否有瓶颈, 如有瓶颈,根据资源树初步定位到可能发生的原因。
2.然后通过数据流分析方法 方法, 按照数据流的方向逐一检查各个组件和参数
步骤如下:
1)检查LR端问题(LR端机器是否有瓶颈, 数据是否正常, 有无其他异常情况),
1 发压力时采取逐步增加并发用户的方法, 找到系统最大的吞吐量;
2 发压力时,查看LR上的响应时间,并动手操作一下页面, 对比一下LR的响应时间和实际操作的响应时间是否匹配上;
2) 监控客户端机器系统资源, 监控服务器端系统资源,从资源使用情况,大致分析出可能的性能瓶颈;
3)检查中间件的参数配置 (如线程数,数据库连接数, JVM参数),并对其进行监控,看看是否存在瓶颈;
4)详细查看应用日志, 是否存在错误日志和查看步骤的时间戳;
5)排除外面的一些因素,最后定位代码问题,借助工具分析代码的性能;
-
-
- 对比方法
-
当使用经验和常规性能瓶颈定位方法无法定位问题时,最常用的一种方法是“对比方法”。
方法1:
通过设计不同的测试案例,执行同一个测试脚本,发现其性能表现和错误日志的规律,找出有规律的现象来;比如当并发用户较多时,系统产出错误的日志较多,这是我们可以
逐步增加并发用户数来观察系统表现;
方法2:
假设有2个类似的测试脚本,一个能满足性能指标,另一个不能满足性能指标,这时可以通过对比着2个脚本的性能表现,观察总结出一些有规律的现象来帮组定位性能瓶颈问题。
-
- 性能问题分析基本原则
- 具体问题具体分析,由于测试目的、被测系统特性、性能测试方法和性能关注点不同,性能问题的分析方法也有所不同
- 从系统到应用、从外到内进行层层剥离,缩小范围。
确认是系统级问题还是应用级问题;
确认是否外部系统问题(如加密机问题、外部webservice问题等);
确认是应用程序问题还是数据库问题。
- 通常来说,查找系统瓶颈时可按以下顺序,由易到难
服务器硬件瓶颈 -> 网络瓶颈(对局域网应用,可以不考虑)-> 服务器操作系统瓶颈-> 服务器系统应用瓶颈(参数配置,数据库,web服务器等)-> 应用瓶颈(SQL语句、数据库设计、业务逻辑、算法等)
对特定的某次性能瓶颈分析来说,并不一定需要完整分析以上内容,有时性能问题比较明显时,甚至可以直接定位到性能瓶颈。
- 范围缩小后,再分割成多个小单元,对每个小单元进行轮番压力测试,来证明或者否定是那个单元引起性能问题。
- 把事实与推测分开,总是用实际的证据来证明你的推测;
- 在没有足够证据之前,不对程序进行优化;
- 优先验证简单的假设;
- Loadrunner端没有错误并不代表真的没有错误;应用日志文件中没有错误不代表真的没有错误;
-
- 常见性能问题及成因
- 常见性能问题的典型表现
- 常见性能问题及成因
- 持续缓慢:应用程序一直特别慢,改变负载,对整体响应时间影响很少;
- 随着时间推进越来越慢:负载不变,随着时间推进越来越慢,可能到达某个阈值,系统被锁定或出现大量错误而崩溃;
- 随着负载增加越来越慢:每增加若干用户,系统明显变慢,用户离开系统,系统恢复原状;
- 零星挂起或异常错误:可能是负载或某些原因,用户看到页面无法完成并挂起,无法消除;
- 可预见的锁定:一旦出现挂起或错误,就加速出现,直到系统完全锁定。通常要重启系统才解决。
- 突然混乱:系统一直运行正常,可能是一个小时或三天之后,系统突然出项大量错误或锁定。
-
- 常见应用端性能问题及成因列表
-
性能问题 |
可能原因 |
典型表现 |
监控手段 |
原因归类 |
|||
|
CPU usr利用率较高 |
1.算法设计问题; 2.选择不恰当的类 3.JVM GC问题 |
在较低负载情况下,CPU usr利用率较高,交易响应时间较大 |
1.监控操作系统CPU利用率; 2.使用监控工具Jprofiler或yourkit Java Profiler监控那些类占用CPU高; |
CPU |
||
|
CPU usr利用率很高 |
1. 死循环 |
在几乎没有负载情况下,CPU利用率几乎满负荷 |
1.监控操作系统CPU利用率; 2.使用监控工具Jprofiler或yourkit Java Profiler监控那些类占用CPU高; |
CPU |
||
|
CPU sys利用率高 |
1.多线程超过系统CPU的负载 |
操作系统CPU sys利用率很高,甚至高于usr利用率,这是会发生上下文切换 |
1.监控操作系统CPU利用率; 2.检查线程数和操作系统的CPU个数;
|
CPU |
||
|
指数方式内存泄漏 |
1.数据库返回过多数据给应用端; 2.应用端加载过多数据; |
瞬间JVM heap暴涨,响应时间瞬间不限增大 |
1.查看JVM GC日志 |
JVM |
||
|
线性内存泄漏 |
1.集合类处理不当,导致JVM heap越来越大,没有释放。 |
随着时间越来越慢 随着负载越来越慢 |
1.查看JVM GC日志 |
JVM |
||
|
IO繁忙 |
1.日志级别问题,导致频繁写日志; 2. 使用了转速较慢的磁盘、糟糕的数据布局会给 I/O 性能带来更大的影响 |
在正常负载情况下,磁盘读写繁忙率很高 |
1. 监控操作系统磁盘利用率; 2.查看读写日志级别; |
IO |
||
|
网络瓶颈 |
1.发送和接收的报文过大,超过网络带宽容量; 2.客户端与应用服务器有延迟; 3.应用服务器与数据库有延迟; |
网络出现较大延迟时,系统性能会出现下降和波动 |
1. 监控操作系统网络传输; 2.ping客户端和应用服务器; 3.ping应用服务器与数 据库服务器 |
网络 |
||
|
线程死锁 |
1.程序设计问题; 2.多线程下获取资源顺序不一致; |
1.在正常负载下,CPU利用率非常低,交易响应时间很高 |
1.监控操作系统CPU 2.获取系统javacore文件; |
线程 |
||
|
线程阻塞 |
1.程序设计问题; 2.多线程下获取资源顺序不一致; |
1.在正常负载下,CPU利用率非常低,交易响应时间很高 |
1.监控操作系统CPU 2.获取系统javacore文件 |
线程 |
||
|
程序单线程设计 |
1.程序设计问题; |
1.在正常负载下,CPU利用率非常低 |
1.监控操作系统CPU,在单用户与多用户并发情况下 |
程序设计问题 |
||
|
程序参数设置不当 |
1. 参数设置问题 |
1.正常负载下,交易响应时间不太正常 |
1.检查线程池,连接池,缓存,流量控制,JVM参数等; |
程序设置配置 |
||
|
操作系统问题 |
1.操作系统参数设置; 2.系统资源紧张 |
1.正常负载下,交易响应时间不太正常 |
1.监控操作系统整体系统资源 |
操作系统 |
||
|
第三方包/组件引入的性能问题 |
1.第三方包本身存在性能问题; 2.使用不当,引起的性能问题 |
1.正常负载下,交易响应时间不太正常 |
1. 监控操作系统整体系统资源; 2. 查看应用日志 |
第三方包 |
||
|
内部资源瓶颈:过度使用或分配不足 |
1.JDBC连接泄露; 2.too many open files
|
1.应用日志有报错 |
1.查看应用日志 |
程序问题 |
||
|
中间件bug |
1.中间件,如Websphere,MQ bug引起性能问题 |
1.中间件日志报错 |
1.查看中间件的日志; 2.查看应用日志; |
中间件 |
||
|
前端外部瓶颈问题 |
1.前端系统较慢,导致本应用系统响应较慢 |
1.正常负载下,CPU利用率低,连接数少 |
1.查看本应用系统的流量 |
外部系统影响 |
||
|
后端外部系统瓶颈问题 |
1.后端系统较慢,本应用系统较快,拖垮后端系统 |
1.正常负载下,CPU利用率高,连接数少 |
1.查看本应用系统的流量 |
外部系统影响 |
||
|
不停止的重试 |
这包括对失败请求连续的(或者在极端情况下无休止的)重试。 |
可以预见的锁定 突然混乱 |
可能是后端系统完全当机,或者网络连接中断。 |
程序设计问题 |
||
-
-
- 常见数据库端性能问题
-
影响数据库性能的主要因素,如下图所示:
如左边箭头所示,越往上说明代码改动越大调优的成本就越高。因此在详细设计阶段应该设计好存储IO、确定操作系统、部署好表空间/缓冲池设计、表设计。
性能测试过程中,常发现的性能问题:
(1)存储IO
【图 典型的Web架构】
如上图所示,数据库端IO存储涉及到的组件较多,每一个组件出了问题,都可能导致性能问题。
在一定条件下,访问内存需要大概540个CPU时钟周期,而访问磁盘则需要20,000,000个CPU时钟周期。
系统中访问数据最薄弱的环节就是磁盘I/O存储系统,如果设计的存储I/O系统不合理,包括使用慢速磁盘,那么其他的优化工作很难有明显的效果。
存储I/O设计中最大一个原则是将I/O访问的分布最大限度地平衡在所有可以利用的物理设备上。
(2)操作系统
操作系统层面主要的性能瓶颈可以分为4类:CPU瓶颈、磁盘瓶颈、内存瓶颈、网络瓶颈。
检查AIX系统性能状态的主要目标是发现系统配置的硬件资源是否满足当前应用负载的需要,是否存在某一类型资源紧张,从而成为整个系统稳定高效运行的瓶颈。定位出系统瓶颈之后可以有针对性能地进行调整,可以根据应用特点对一些系统内核参数进行调整以解决部分性能问题,有些系统性能问题则需要通过升级硬件或调整应用来解决。
AIX提供了丰富的命令工具来检查系统性能状态,其中最重要的命令是topas/nmon,它能提供一个实时的系统主要硬件资源运行状态的监控界面。在监控界面中,通过切换不同的视图可以详细显示某一特定资源的运行状态。分析界面提供的实时监控数据,可以判断出当前系统上是否有性能问题,以及造成性能瓶颈的硬件资源,然后利用系统提供的其他工具有针对性分析某类硬件资源的运行状态。如下图为topas监控界面。
常用于优化操作系统性能参数如下:
- 文件系统的IO控制方式: 直接I/O,并发I/O; 同步I/O和异步I/O;
- 换页空间
- 网络相关参数(frc1323,tcp_sendspace,tcp_recvspace)
(3)缓冲池设计/表空间设计
数据库中的数据访问都需要经过缓冲池:读的数据需要先读到缓冲池才能提交给应用,写的数据也是先写到缓冲池才能进行IO。 缓存池是影响数据库性能最大的参数,所以必须合理地设计缓存池。
DB2缓冲池由连续的内存页组成。数据和索引页从物理存储容器中读出之后,便进入这些内存页中,并留在其中,直到DB2缓冲池管理器确定哪些数据页要用于其它数据。应用程序所请求的数据出现在内存中的命中率越大,总体性能就越好。缓冲池里的数据被重复利用,因而减少了应用程序对IO的需求。是否释放一个缓冲池脏页,这由最近被使用(LRU)原则来决定的。
创建数据库时,DB2默认会自动创建一个名为IBMDEFAULTBP的缓冲池,所有的表空间都共享该缓冲池,缓冲池的默认大小由数据库配置参数BUFFPAGE指定,也可以通过在CREATE BUFFERPOOL命令中指定SIZE关键字来覆盖该值。大型缓冲池会对查询优化产生影响,因为更多的工作可在内存中完成,而无须进行IO。
缓冲池调优的目标是帮助DB2尽可能好地利用可用于缓冲区的内存。整个缓冲区大小对DB2性能有巨大影响,这是因为大量的页可以显著地减少 I/O这一最耗时的操作。
常用缓冲池设计:
- 一个中等大小的缓冲池,用于临时表空间;
- 一个大型的缓冲池,用于数据表空间
- 一个大型的缓冲池,用于索引表空间
表空间是物理存储和逻辑存储的分界点,在逻辑层面,表空间可以存放数据表,而数据表是用户逻辑设计最基本的对象。在物理层面,表空间对应于多个物理容器。可见,表空间在物理存储和逻辑存储之间起到了承上启下的作用。
一个表空间只能与一个缓冲池相关联,而一个缓冲池则可用于多个表空间。
表空间设计原则:
- 存储使用DB2 自动存储管理(Automatic Storage)
- 按数据层次划分表空间,数据和索引分开,LOB字段、宽表存放在大表空间,频繁使用的小表使用单独表空间
- 使用数据库自动管理特性(MANAGED BY AUTOMATIC STORAGE)
- 不使用文件系统缓存(No File System Caching)
对于中等规模以上的数据库,应当考虑:
- 尽可能把应用的数据表空间、索引表空间以及相应的分区表空间分布在独立的物理卷上;
- 把存放数据的容器和数据库日志存放在不同的物理卷上;
- 如表空间使用文件系统,建议使用JFS2类型的文件系统,并且用CIO方式进行mount;
- 数据库日志文件都是顺序读写,建议采用RAID5级别;
- 大部分OLTP数据库容器都是随机读写,建议采用RAID0+1级别。OLAP建议采用RAID5。
(4)表设计
- 合理使用规范化
优良的数据库,一定是恰当的数据,在恰当的时刻被分到恰当的表中。为此,设计表的时候应该考虑规范化原则。规范化的表,即方便对数据的存取操作,也会显著简化应用程序的其他内容。
规范化不是万能的,有时候,在逻辑设计中也需要将表反规范化,从而提高性能。将一个表反规范化的意思是:修改规范化的设计,打破该表之前遵从的范式。实施反规范化是由于性能的原因。规范化只关注数据的意义,而没有考虑访问数据的应用程序的性能需求。
这种性能问题最常见的例子是连接(join)操作。通常,规范化过程最终将相关的信息放入不同的表中。于是应用程序需要从多个表中访问数据。这是通过连接多个表来完成的。连接操作可能要消耗大量的资源和时间,这取决于表的数量及这些表各自的长度。
数据冗余的代价,首先是空间代价,其次是管理代价,维护数据的完整性。它的好处是:减少查询所要连接表的个数,减少了IO和CPU的时间,加速了查询速度。
在逻辑数据库设计过程中,先按照规范化设计,然后再根据某些需求加入一定的反规范化,作为性能调优的一个选择。
- 分区表的使用:某些业务非常合适使用分区表(数据量大,同时一般只查询某个时间点或某个分行的数据),但没有采用分区表,而是采用一个普通表或多个表来实现)
(5)SQL
SQL语句的调优在性能优化工作中至关重要,甚至有人说80%以上的数据库性能问题都归结于SQL语句问题。以下列出常见的SQL问题:
- 书写select * ;对于小表,这么写问题不大;但对于大表同时有多个列的表,这么写在高并发下,性能就有明显的差别;
- 返回过多数据给应用端:在需求分析时,并没有充分考虑到数据库到应用端的数据量,过多的数据传给应用端,导致应用端内存溢出,产生core dump。
- 前台分页?后台分页: 在web应用中,使用人员往往只查看前几页的信息。开发人员在设计阶段已经意识到要对数据进行分页,但分页的方式不同会导致不一样的性能效果。 采用前台分页:即从数据库查询到的所有的数据放到应用端来分页,这个导致上面提到的问题:返回过多数据给应用端,导致内存溢出; 采用后台分页:即数据在数据库端已经分页,把分页好的少量数据传个应用端来展示。
- INSERT、UPDATE、DELETE大量的数据时,导致数据库报The transaction log for the database is full。
这种情况经常出现日终或月终程序,INSERT大量数据到历史表,并且在事实表中更新或删除大量数据。
(6)存储过程
- 临时表的使用
我们在储存过程开发中经常使用临时表,合理的使用临时表可以简化程序的编写,提供较高的执行效率,然而滥用临时表同样也会使得程序运行效率降低。
临时表通常应用在定义临时集合的场合。但是,在大部分需要临时集合的时候,我们根本就不需要定义临时表。这涉及到滥用临时表问题,很多开发人员都很容易犯这个错。当我们在一条SQL语句中只使用一次临时集合时,我们可以使用子查询或with语句来替代临时表。只有在一个存储过程中多条SQL语句使用到同一个临时集合时,我们才需要定义临时表。
当临时表的数据量比较大,需要为临时表创建索引
CREATE INDEX SESSION.I_TMP_EAC ON SESSION.TMP_EAC(EAC_NBR);
CALL SYSPROC.ADMIN_CMD('RUNSTATS ON TABLE SESSION.TMP_EAC on KEY columns with DISTRIBUTION ON KEY columns and index all allow WRITE access');
- rebind 问题
存储过程在部署的时候已经bind一个执行计划,随着时间的推移,数据、环境发生了变化,而存储过程的执行计划未变,因此可能会导致此存储过程的执行计划不是最优的,性能会出现下降,因此需要rebind更新执行计划。Rebind有3个绑定选项reopt NONE/ ONCE/ALWAYS
NONE: 默认选项,即存储过程编译好后,就把存储过程的执行计划保存在缓存中,以后都使用此方案;
ONCE:在第一次执行时,生成的存储过程的执行计划保存在缓存中,以后都使用此方案;
ALWAYS:每次执行时,都生成新的执行计划;
(7)索引
索引是从数据库中获取数据的最高效方式。60%的数据库性能问题都可以采用索引技术得以解决。索引解决性能问题的优势表现在:首先,为表中被请求的数据行提供直接指针;其次,消除了排序;最后,避免了对基表的访问,可以使用全索引扫描。
测试过程中,常发现如下性能问题:
- 缺少索引,导致全表扫描;
- 索引过多,比如一个表(OLTP系统)中存在5个以上的索引,同时索引中包含的列很多,索引容量大过表本身;过多的索引会使更新操作变慢,而且会浪费空间。一个表如果建有大量索引会影响INSERT、UPDATE、DELETE语句的性能。
- 在区分度(selectivity)不是很高的列上建索引,或对建立组合索引还是单一索引没有概念;
组合索引即多列索引,指一个索引含有多个列。一个组合索引相当于多个单列索引,如索引(ColA, ColB, ColC)至少相当于(ColA)、(ColA、ColB),(ColA、ColB、ColC)三个索引。对于组合索引,把在查询语句中最多被访问的列放在第一个位置。
(8)锁
设置合理的隔离级别
在单用户环境中,数据库的每个事务都是顺序执行的,而不会遇到与其他事务冲突。但是,在多用户环境下,多个事务常常同时执行。每个事务都有可能与其他正在运行的事务发生冲突。数据库的隔离级别是为了保证: 同时运行事务没有发生冲突。
常见于web应用,前端页面有统计查询记录的总数,同时另一用户更新或删除这些查询记录,这时就会出现锁等待,严重时会出现死锁。
Update /delete …. 锁住 select count(*) from ….
如果查询语句select count(*) from …后面加上with ur (Uncommitted Read)问题即可以解决。
(9)序列sequence
Sequence序列让DB2创建规范的自动生成数据的序列。它提供了一个由DB2生成的增量计数器。它可以独立的递增或递减地生成连续数字,不与表关联。
常见的性能问题:表中的主键(如客户号,银行支票)使用序列来生成,但未给序列合适的cache
CREATE SEQUENCE DB2INST1.SEQ_DEMO
AS INTEGER
Start with 1
Increment by 1
CACHE 100 --no cache
MAXVALUE 99999999
(10)系统维护
对表数据进行大量更改之后,逻辑上连续的数据可能被分散存放在很多个不连续的物理数据页中,因此数据库管理器必须执行更多的读操作来访问数据。另外,对某个表删除大量行后,由于表的高水位标记并不会发生变化,因此对该表全表扫描时会出现很多不必要的IO操作。在这样的情况下,您可以考虑重组表以回收浪费的空间和对数据进行重组。
表重组操作会通过整理数据碎片来减少浪费的空间,并进行重新排序以合并溢出记录,从而加快数据访问速度并最终提高查询性能。还可以指定根据特定索引来重新排序数据,以便查询能通过最少的I/O读取操作就可以访问数据。
对表数据进行大量更改将导致更新索引并使索引性能下降。索引叶子页可以变成碎片,并且索引有可能形成比所需层次要多的层次以获取的最佳性能(通常,几百万记录的索引层次一般为3,正常生产环境中索引的层次很少超过4)。所有这些问题都会产生更多I/O并导致性能下降。
下列情况都需要我们重组表或索引:
- 自上次重组表之后,对查询所访问的表进行了大量的插入、更新和删除活动;
- 对于使用具有高聚合度的索引的查询,其性能发生了明显的变化;
- 在执行RUNSTATS后,性能没有得到改善;
- REORGCHK命令指示需要重组或索引;
- 综合考虑查询性能不断降低所浪费的成本和重组表所需的成本
实战篇
- java项目性能监控与诊断
- java项目优化方法
Java项目优化分为3个层面:架构设计层面上的优化,代码层面上的优化和JVM层面上的优化。
-
-
- 架构设计上的优化
-
架构设计优化处于所有优化手段的上层,在软件开发之初,需要评估系统可能存在的各种潜在问题,并给出合理的设计方案。由于软件设计和架构对软件整体质量有决定性的影响。因此,架构上的优化对系统性能的影响也是最大的。如果说,代码优化、JVM优化都是对系统微观层面上“量”的优化,那么架构上的优化是对系统在宏观层面上“质”的优化。
【图2-1】软件架构设计过程
代码架构设计:一般会选择流行的、稳定的框架来辅助开发,在代码设计上选用合理的设计模式;
数据架构设计:根据功能和非功能需求进行数据库设计;
系统架构设计:根据业务功能的重要性和非功能性,考虑是否使用负责均衡,数据是否备份,是否需要异地灾备,考虑存储架构。
数据架构设计,系统架构设计和代码架构设计必须是相辅相成;
除了满足业务功能,架构上的设计往往是为了满足软件的非功能性。
非功能性: 性能(Performance),易用性(Usability),安全性(Security),可扩展性(Scalability),兼容性(Compatibility),可移植性(Portability),可用性(Availability),健壮性(Robustness)
可扩展性(Scalability): 指软件扩展新功能、性能的容易程度,可扩展越好表明软件适应“变化”的能力越强。
【图 2-2】 常用的软件非功能性
软件架构高性能的几个特性:
1)缓存
2)分布式
3)资源复用
4)负载均衡
5)异步
如架构上设计不合理,将满足不了业务需求的变化--扩展性,性能,安全性。
当应用服务器采用了以上高性能特性进行开发,往往应用服务器端的性能得到了解决,但性能瓶颈又转移到数据库端。
附录知识:
数据库架构扩展性:
1)增加单台服务器系统资源,如CPU、内存、磁盘、网络带宽等;
2) 分库
在系统发展的初期,通常会将各种不同的数据放在同一个数据库中,随着业务的多元化及业务系统的水平伸缩,数据库的连接数会成为稀有资源,对于这种情况,分库是个不错的选择。
分库通常按照业务领域将原来存储在同一个数据库的数据拆分到多个数据库中。例如eBay就按照其业务领域拆分为商品、用户、评价、交易等数据库,每个数据库只用处理相关业务的数据,因此可用的数据库连接数将会得到很大提升。
在银行业务中,可以根据客户号或者卡号进行分库来提高数据库处理能力。
3)读写分离
读写分离采用的方法是 当写数据库时在一个数据库上写入,而要读取时则从多个其他的数据库中读取,通常将用于写入的库成为master库,用于读取的库称为slave库。
Mysql replication支持对称复制和非对称复制两种方式。
对称复制是指从master库复制数据库到所有的slave库。Slave库的数据和master的数据保持一致。系统在读取时可以任意挑选其中一个库读取。缺点是可能会造成每个slave上的数据量非常大,从而使得slave库的硬件配置非常高,并且每次要从master复制到所有的slave,随着slave机器增加,延时现象可能会越来越严重。
非对称复制是指从master库复制部分数据到slave库,各slave库的数据可能不同,其优点在于各个slave库仅持有部分数据,可提升其读取数据的响应速度,并且每次master写入时无需复制到所有的slave,可以尽可能地减少延时现象; 缺点在于一旦slave机器出现故障,就会导致一些数据要回到master读取,同时由于每个slave的数据不同,使业务服务器在访问的时候较为复杂。
读写分离适用于读多写少,并允许一定延时的业务,对于读写比例基本相等的业务而言,如采用读写分离带来的大数据复制,会造成系统运行缓慢。
物理上横向扩展
- purescale
OLTP的集群方式,类似于OACLE RAC。
DB2 pureScale 是一种新的 DB2 可选特性,它允许您通过“双机(active-active)”配置将数据库扩展到一组服务器上,以便交付高水平的可用性和可伸缩性。在这种配置中,运行于各主机(或服务器)上的 DB2 副本可以同时读取和写入相同的数据。
共享 DB2 数据的一台或多台 DB2 服务器被称作数据共享组。数据共享组中的 DB2 服务器是该组的成员。目前,数据共享组支持的最大成员数量是 128。
无限能力: DB2 pureScale 为各种事务处理工作负载提供了几乎无限的产能。系统扩展非常简单,只需要与一个新节点连接,并发出两个简单的命令即可。DB2 pureScale 的基于集群、磁盘共享的架构通过有效利用系统资源,降低了成本。
应用程序透明: 使用DB2 pureScale,您无需改变您的应用程序代码,就可以有效地运行在多个节点上。久经验证的、可扩展的架构能够使您随需扩展您的应用程序,以满足变化的业务需求。您只需做少量改变或无需做任何改变,就能够运行为其他数据库软件编写的应用程序;DB2 为常用的语法规则和PL/SQL 语言提供了全面的支持,使从 Oracle数据库迁移到 DB2 变得比以往更轻松了。
持续的可用性: DB2 pureScale 通过在IBM Power Systems上和冗余架构中使用高可靠的IBM PowerHA pureScale技术,提供了持续的可用性。此系统能够瞬间从节点故障中恢复,立即将工作负载重新分配给其他可用的节点。
2)数据库分区
OLAP的集群方式
DB2数据库分区是 DB2企业版 DPF(Data Partitioning Feature)选件提供的,它主要用来为大规模数据处理、高并发数据访问提供支持。DB2数据库分区采用 Share-nothing 体系结构,数据库在一个非共享的环境中被分解为独立的分区,每个分区都具有自己的资源,例如内存,CPU 和磁盘以及自己的数据、索引、配置文件和事务日志。数据库分区有时称为节点或数据库节点。
数据通过 Hash 算法均允地散列到不同的分区内,每个分区只负责处理自己的数据。当用户发出 SQL 操作后,被连接的分区被称为 Coordinate Node,它负责处理用户的请求,并根据 Partition key 将用户的请求分解成多个子任务交由不同分区并行处理,最后将不同分区的执行结果经过汇总返回给用户,分区对应用来说是透明的。
DB2数据库分区提供了强大的可扩展能力。由于采用 Share-nothing 体系结构,每个分区(节点)只处理它那一部分数据,分区之间尽可能独立,这就减少了节点间共享资源的争用,允许数据库有效地伸缩以支持更大的数据规模及更多的用户访问。DB2数据库分区提供 scale up (垂直扩展)及 scale out (水平扩展)能力。
垂直扩展是通过增加机器的物理资源如 cpu、磁盘、内存来实现的;水平扩展是通过增加物理机器来实现的,DB2中,最多可以支持 1000 个分区。在规划 DB2数据库分区时,我们需要考虑是通过增加逻辑分区还是物理分区来实现扩展能力。
架构设计优化实例:
【图 2-3】项目架构演变过程
1)性能对比
查询: 架构1 (23TPS)à 架构2 (400TPS);
2) 架构改动
|
架构1 |
架构2 |
应用服务器端 |
XML报文 |
400报文 |
Jar单机模式 |
WAS负载均衡模式 |
|
Cmb_query |
CIF框架 |
|
数据库服务器端 |
单机模式 |
多机分库模式 |
存储过程优化 |
修改表结构,字段冗余,去除临时表,优化存储过程 |
-
-
- 代码层面上的优化
-
代码优化是在软件开发过程中,或者在软件开发完成后对程序代码的改进和优化。代码优化涉及诸多变成技巧,需要开发人员熟悉相关语言的API,并在合适的场景中正确使用相关的API或类库。同时,对算法、数据结构的灵活使用,也是代码优化的重要内容。
虽然代码优化是从微观上对性能进行调整,但是一个“好”的实现和一个“差”的实现对系统的影响是非常大的。比如,同样作为List的实现,LinkedList和ArrayList在随机访问上的性能可以相差几个数量级;又如,同样是文件读写的实现,使用流方式和NIO的方式,其性能可能会相差一个数量级。
-
-
- JVM 层面上的优化
-
由于Java软件是运行在Java虚拟机之上,因此对JVM进行优化能在一定程度上提升Java程序的性能。 JVM优化可以在软件开发后期或开发完成后进行。
JVM的各项参数将会直接影响Java程序的性能。比如,JVM的堆大小、垃圾回收策略等。
要进行JVM层面的优化,需要对JVM 的运行原理和基本内存结构有一定的了解。如,堆内存的机构,GC的策略等。然后,依据应用程序的特性,设置合理的JVM启动参数。
-
-
- 优化的基本方法
-
存在性能问题的系统,大部分原因是由某个系统瓶颈导致的。只要找到该性能瓶颈,分析瓶颈的形成原因,对症下药,使用合理的方法解决系统瓶颈,就能从根本上提升性能。
从系统瓶颈来分,可以分为: CPU瓶颈,IO瓶颈,内存瓶颈,网络瓶颈,线程锁等。同时大量的异常报错和外部因素也是产生性能瓶颈的原因。
根据以往项目的经验,我总结了一套瓶颈树的方法。可以通过监控操作系统和应用日志初步判断出系统可能发生瓶颈的原因。
【注意】软件的性能优化虽然能提升软件的性能,但优化过程中往往伴随着一些风险和弊端。比如,为了优化某一个代码的实现,需要重写原有的算法,这就很可能引入新的功能bug。
【图 3】CPU和线程锁瓶颈树
【图 4】内存瓶颈树
【图 5】IO瓶颈树
【图 6】网络瓶颈树
-
- 常见java代码性能问题
注: 以下实验结果是在我本机上单线程方式测试得到的结果,代码放到服务器上多线程方式运行测试结果差距更大。
-
-
- 集合类
-
List、Set、Map接口及各实现类的特性
接口 |
特性 |
实现类 |
实现类特性 |
成员要求 |
List |
线性、有序的存储容器,可通过索引访问元素 |
ArrayList |
数组实现。非同步。 |
|
Vector |
类似ArrayList,同步。 |
|
||
LinkedList |
双向链表。非同步。 |
|
||
Map |
保存键值对成员 |
HashMap |
基于哈希表的 Map 接口的实现,满足通用需求 |
任意Object对象,如果修改了equals方法,需同时修改hashCode方法 |
TreeMap |
默认根据自然顺序进行排序,或者根据创建映射时提供的 Comparator进行排序 |
键成员要求实现caparable接口,或者使用Comparator构造TreeMap。键成员一般为同一类型。 |
||
LinkedHashMap |
类似于HashMap,但迭代遍历时取得“键值对”的顺序是其插入顺序或者最近最少使用的次序 |
与HashMap相同 |
||
IdentityHashMap |
使用==取代equals()对“键值”进行比较的散列映射 |
成员通过==判断是否相等 |
||
WeakHashMap |
弱键映射,允许释放映射所指向的对象 |
|
||
ConcurrentHashMap |
线性安全的Map |
|
||
Set |
成员不能重复 |
HashSet |
为快速查找设计的Set |
元素必须定义hashCode() |
TreeSet |
保持次序的Set,底层为树结构 |
元素必须实现Comparable接口 |
||