存档

‘性能优化’ 分类的存档

又见OutOfMemory——一次内存溢出故障诊断全过程

2010年11月9日 admin 7 条评论

这是一个几月前的案例,问题比较典型,在分析和事后学习的过程中让我对本地内存溢出有了一定的了解。在此和大家分享。

先说一下背景,应用环境是AIX5.3+WebSphere6.0.2.37。在今年的一季度曾发生过几次OOM故障,当时通过几次内存参数优化,最后确定为“-Xgcprolicy:gencon –Xms512m –Xmx1280m –Xmn200m”,此后稳定了半年,直到此次再发生应用宕机。

赶到现场,发现profiles目录下生成有javacore和heapdump文件,Javacore的第一行“Cause of thread dump : Dump Event "systhrow" (00040000) Detail "java/lang/OutOfMemoryError" received”表明了宕机的原因是OOM,但是令我困惑的是这段内容:

0SECTION       MEMINFO subcomponent dump routine
NULL           =================================
1STHEAPFREE    Bytes of Heap Space Free: 818cb70
1STHEAPALLOC   Bytes of Heap Space Allocated: 20000000

在分配的512MB(十六进制20000000)的堆空间中,有129MB(818cb70)空闲空间,按理说,这种情况下不该发生OutOfMemory。就算有申请一个大对象,同时JVM堆的新生代由于碎片原因没有连续空间满足要求,那么应该发生堆扩展,所以此次内存溢出不是堆(Heap)溢出,GC日志的分析也支持了这一点。

既然Javacore无法得到有用信息,我把目光转向了SystemErr.log,在对应日期的地方,我发现了大量如下报错:

[8/26/10 14:12:57:860 GMT+08:00] 0000002f SystemErr     R Exception in thread "WebContainer : 1" java.lang.RuntimeException: java.lang.OutOfMemoryError: Failed to create a thread: retVal -1073741830, errno 11
[8/26/10 14:12:57:860 GMT+08:00] 0000002f SystemErr     R     at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:801)
[8/26/10 14:12:57:860 GMT+08:00] 0000002f SystemErr     R     at com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:881)
[8/26/10 14:12:57:860 GMT+08:00] 0000002f SystemErr     R     at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1497)
[8/26/10 14:12:57:860 GMT+08:00] 0000002f SystemErr     R Caused by: java.lang.OutOfMemoryError: Failed to create a thread: retVal -1073741830, errno 11
    at java.lang.Thread.startImpl(Native Method)
    at java.lang.Thread.start(Thread.java:980)
    at com.ibm.ws.util.ThreadPool.addThread(ThreadPool.java:630)
    at com.ibm.ws.util.ThreadPool$3.run(ThreadPool.java:1148)
    at com.ibm.ws.security.util.AccessController.doPrivileged(AccessController.java:63)
    at com.ibm.ws.util.ThreadPool.execute(ThreadPool.java:1146)
    at com.ibm.ws.util.ThreadPool.execute(ThreadPool.java:1040)
    at com.ibm.ws.runtime.WSThreadPool.execute(WSThreadPool.java:151)
    at com.ibm.io.async.ResultHandler.startHandler(ResultHandler.java:248)
    at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:570)
    at com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:881)
    at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1497)

在事后的学习中,我知道“Java.lang.OutOfMemoryError: unable to create native thread” 这样的异常是在说,本地内存耗尽,从而新的线程无法创建。而在当时我第一感觉是操作系统参数设置问题,之前我曾写过一篇由于nofile参数导致Too many open file的故障。于是我运行如下命令

#lsattr -El sys0 -a maxuproc
maxuproc 128 Maximum number of PROCESSES allowed per user True

    然后运行chgsys修改默认的128为1024,这里我犯了一个错误,WebSphere单个Server就是一个Java进程,错误日志里是不能创建一个thread,而非process,与Oracle会创建多个oracle进程不一样。果然两天后又出现了同样的问题。
    这一次的SystemErr日志中,除了上述的内容,还多了

[8/24/10 9:55:19:813 GMT+08:00] 00000036 SystemErr     R Exception in thread "WebContainer : 4" java.lang.RuntimeException: java.lang.OutOfMemoryError: Unable to allocate 8192 bytes of direct memory after 5 retries
[8/24/10 9:55:19:813 GMT+08:00] 00000036 SystemErr     R     at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:801)
[8/24/10 9:55:19:813 GMT+08:00] 00000036 SystemErr     R     at com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:881)
[8/24/10 9:55:19:813 GMT+08:00] 00000036 SystemErr     R     at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1497)
[8/24/10 9:55:19:813 GMT+08:00] 00000036 SystemErr     R Caused by: java.lang.OutOfMemoryError: Unable to allocate 8192 bytes of direct memory after 5 retries
    at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:197)
    at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:303)
    at com.ibm.ws.buffermgmt.impl.WsByteBufferPoolManagerImpl.allocateBufferDirect(WsByteBufferPoolManagerImpl.java:656)
    at com.ibm.ws.buffermgmt.impl.WsByteBufferPoolManagerImpl.allocateCommon(WsByteBufferPoolManagerImpl.java:570)
    at com.ibm.ws.buffermgmt.impl.WsByteBufferPoolManagerImpl.allocateDirect(WsByteBufferPoolManagerImpl.java:506)
    at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:498)
    at com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:881)
    at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1497)
Caused by: java.lang.OutOfMemoryError
    at sun.misc.Unsafe.allocateMemory(Native Method)
    at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:184)
    … 7 more

我们可以看到是由于DirectByteBuffer无法分配导致的内存溢出,而Native Method指明了这是“本地”的溢出。通过这两个关键字,我查到了IBM的一份BUG记录:PK31010: OUTOFMEMORYERROR DUE TO DIRECTBYTEBUFFER,但是我的版本已是最新,无奈继续搜寻。

Troubleshooting native memory issues这份文档中,介绍了3种在WebSphere中最常见的导致OOM的原因。其中第二个DirectByteBuffer use就是我们问题的症结。

Java 1.4 中添加的新 I/O (NIO) 类引入了一种基于通道和缓冲区来执行 I/O 的新方式。就像 Java 堆上的内存支持 I/O 缓冲区一样,NIO 添加了对直接 ByteBuffer 的支持(使用java.nio.ByteBuffer.allocateDirect() 方法进行分配),ByteBuffer 受本机内存而不是 Java 堆支持。直接 ByteBuffer 可以直接传递到本机操作系统库函数,以执行 I/O — 这使这些函数在一些场景中要快得多,因为它们可以避免在 Java 堆与本机堆之间复制数据。

    对于在何处存储直接 ByteBuffer 数据,很容易产生混淆。应用程序仍然在 Java 堆上使用一个对象来编排 I/O 操作,但持有该数据的缓冲区将保存在本机内存中,Java 堆对象仅包含对本机堆缓冲区的引用。非直接 ByteBuffer 将其数据保存在 Java 堆上的 byte[] 数组中。下图展示了直接与非直接 ByteBuffer 对象之间的区别:

     直接与非直接 java.nio.ByteBuffer 的内存拓扑结构

ByteBuffer 内存安排

    直接 ByteBuffer 对象会自动清理本机缓冲区,但这个过程只能作为 Java 堆 GC 的一部分来执行,因此它们不会自动响应施加在本机堆上的压力。GC 仅在 Java 堆被填满,以至于无法为堆分配请求提供服务时发生,或者在 Java 应用程序中显式请求它发生(不建议采用这种方式,因为这可能导致性能问题)。

    发生垃圾收集的情形可能是,本机堆被填满,并且一个或多个直接 ByteBuffers 适合于垃圾收集(并且可以被释放来腾出本机堆的空间),但 Java 堆几乎总是空的,所以不会发生垃圾收集。

摘自《理解JVM如何使用Windows和Linux上的本机内存》

解决此问题的方法,在文档中给出的是禁止异步A/O,通过在Web Container中设置参数来避免上节中所出现的由于Java堆空闲而不发生垃圾回收,导致本地堆撑满的情况。

    Servers -> Application Servers -> serverName -> Web Container Settings -> Web Container -> Custom Properties:
    Press New:
    Add the following pair:

      Name: com.ibm.ws.webcontainer.channelwritetype
      Value: sync

    Press OK, and then save the configuration.

    添加此属性后应用又恢复正常,但是原先提高性能的特性反而导致内存溢出,违背了初衷,现在的做法只能算一个妥协。我会继续查找此问题的解决方法,最不济也要有个使用NIO的Best Practice。

文档中另两个容易导致本地堆OOM的原因是:

过大的堆上限

Memory layout of a vm in the os

我们知道32位机器单个进程可以访问的内存地址空间为4G,如右图所示,但实际情况下Windows系统由于内核态和用户态的划分,用户态只有2G的空间,Linux和AIX的可用空间大一点,但也在3G左右。,由于JVM实例进程寻址是一定的,所以Heap大小和Native Area此消彼长。而Native Area中有一部分就是供给线程的内存空间。

“应用程序中的每个线程都需要内存来存储器堆栈(用于在调用函数时持有局部变量并维护状态的内存区域)。每个 Java 线程都需要堆栈空间来运行。根据实现的不同,Java 线程可以分为本机线程和 Java 堆栈。除了堆栈空间,每个线程还需要为线程本地存储(thread-local storage)和内部数据结构提供一些本机内存。堆栈大小因 Java 实现和架构的不同而不同。一些实现支持为 Java 线程指定堆栈大小,其范围通常在 256KB 到 756KB 之间。”

1.5后一般线程堆栈大小为1M,“对于拥有数百个线程的应用程序来说,线程堆栈的总内存使用量可能非常大。如果运行的应用程序的线程数量比可用于处理它们的处理器数量多,效率通常很低,并且可能导致糟糕的性能和更高的内存占用”(摘自《理解JVM如何使用Windows和Linux上的本机内存》)

解决此问题的方法:设置恰当的JVM最大堆,32位不要超过1.5G;设置恰当的线程池大小(一般50~100),当线程使用较多时, 使用-Xss256k参数设置线程指定堆栈大小(IBM JDK还可以使用-Xmso ××k来设置Stack size for OS Threads 32-bit);更换64位的JDK。

第三个原因是线程池的TLS泄漏

这个现象我倒没见到过,如果你的thread dump里下面三个属性里的值有大于300,那么就要注意了

"WebContainer : 1003" (TID:0×37D62000
"Default : 338" (TID:109934D0
"TCPChannel.DCS : 303" (TID:0×4D720000

解决的方法是设置线程池的最小最大值一致。

为JVM启用大页面支持

2010年5月22日 hashei 1 条评论

最近在看《Linux服务器性能调整》,书中第九章-Linux虚存的性能问题中提到了当代计算机体系结构都支持多种页面大小。大型页面可以改善高性能计算及内存密集型应用的性能。回想起之前看IBM developmentworks上介绍websphere调优和oracle weblogic中tuning都提到了这一点,于是想记下一笔,不过网上正好看到ken Wu已经就此总结过了,于是转贴在此。红色部分为我添加的。

转自 Ken Wu`s Blog

原文链接 JVM优化之调整大内存分页(LargePage)

本文将从内存分页的原理,如何调整分页大小两节内容,向你阐述LargePage对JVM的性能有何提升作用,并在文末点明了大内分页的副作用。OK,让我们开始吧!

内存分页大小对性能的提升原理

首先,我们需要回顾一小部分计算机组成原理,这对理解大内存分页至于JVM性能的提升是有好处的。

什么是内存分页?
我们知道,CPU是通过寻址来访问内存的。32位CPU的寻址宽度是 0~0xFFFFFFFF ,计算后得到的大小是4G,也就是说可支持的物理内存最大是4G。

但在实践过程中,碰到了这样的问题,程序需要使用4G内存,而可用物理内存小于4G,导致程序不得不降低内存占用。
为了解决此类问题,现代CPU引入了 MMU(Memory Management Unit 内存管理单元)。

MMU 的核心思想是利用虚拟地址替代物理地址,即CPU寻址时使用虚址,由 MMU 负责将虚址映射为物理地址。
MMU的引入,解决了对物理内存的限制,对程序来说,就像自己在使用4G内存一样。

内存分页(Paging)是在使用MMU的基础上,提出的一种内存管理机制。它将虚拟地址和物理地址按固定大小(4K)分割成页(page)和页帧(page frame),并保证页与页帧的大小相同。

这种机制,从数据结构上,保证了访问内存的高效,并使OS能支持非连续性的内存分配。
在程序内存不够用时,还可以将不常用的物理内存页转移到其他存储设备上,比如磁盘,这就是大家耳熟能详的虚拟内存。

在上文中提到,虚拟地址与物理地址需要通过映射,才能使CPU正常工作。
而映射就需要存储映射表。在现代CPU架构中,映射关系通常被存储在物理内存上一个被称之为页表(page table)的地方。
如下图:

28728a1e-693e-4790-ac60-98d883472ec3

从这张图中,可以清晰地看到CPU与页表,物理内存之间的交互关系。

图中的page table在现代操作系统中由全局目录(PGD)-中间目录(PMD)-页表项(PTE)三层树构成,有时候不同书上图不一样但意思一样,只是画多画少。

进一步优化,引入TLB(Translation lookaside buffer,页表寄存器缓冲)
由上一节可知,页表是被存储在内存中的。我们知道CPU通过总线访问内存,肯定慢于直接访问寄存器的。
为了进一步优化性能,现代CPU架构引入了TLB,用来缓存一部分经常访问的页表内容。
如下图:

1381be32-3fea-460c-a310-04fcb15f99e3

对比 9.6 那张图,在中间加入了TLB。

为什么要支持大内存分页?
TLB是有限的,这点毫无疑问。当超出TLB的存储极限时,就会发生 TLB miss,之后,OS就会命令CPU去访问内存上的页表。如果频繁的出现TLB miss,程序的性能会下降地很快。

为了让TLB可以存储更多的页地址映射关系,我们的做法是调大内存分页大小。

如果一个页4M,对比一个页4K,前者可以让TLB多存储1000个页地址映射关系,性能的提升是比较可观的。

调整OS和JVM内存分页

在Linux和windows下要启用大内存页,有一些限制和设置步骤。

Linux:
限制:需要2.6内核以上或2.4内核已打大内存页补丁。
确认是否支持,请在终端敲如下命令:

# cat /proc/meminfo | grep Huge
HugePages_Total: 0
HugePages_Free: 0
Hugepagesize: 2048 kB

如果有HugePage字样的输出内容,说明你的OS是支持大内存分页的。Hugepagesize就是默认的大内存页size。
接下来,为了让JVM可以调整大内存页size,需要设置下OS 共享内存段最大值 和 大内存页数量。

共享内存段最大值
建议这个值大于Java Heap size,这个例子里设置了4G内存。

# echo 4294967295 > /proc/sys/kernel/shmmax

注意在32位操作系统上这个值不能超过4GB

大内存页数量

# echo 154 > /proc/sys/vm/nr_hugepages

这个值一般是 Java进程占用最大内存/单个页的大小 ,比如java设置 1.5G,单个页 10M,那么数量为  1536/10 = 154。
注意:因为proc是内存FS,为了不让你的设置在重启后被冲掉,建议写个脚本放到 init 阶段(rc.local)。

更简便的方法是

echo "vm.nr_hugepages=154" >> /etc/sysctl.conf

通过下述命令来验证设置是否生效

grep HugePages_Total /proc/meminfo

结果应该是你之前设置的数值154

 

Windows:

限制:仅支持 windows server 2003 以上server版本

操作步骤:

  1. Control Panel -> Administrative Tools -> Local Security Policy
  2. Local Policies -> User Rights Assignment
  3. 双击 “Lock pages in memory”, 添加用户和组
  4. 重启电脑

注意: 需要管理员操作。

单个页大小调整

JVM启用时加参数 -XX:LargePageSizeInBytes=10m

如果JDK是在1.5 update5以前的,还需要手动加 -XX:+UseLargePages,作用是启用大内存页支持。

——————————————————————

其实除了JVM可以使用大页面提高性能,还有一种应用更符合内存密集型的场景,那就是数据库。数据库的调优中很早就有了这部分的建议。详见

Tuning and Optimizing Red Hat Enterprise Linux for Oracle 9i and 10g Databases

当中提到

In order that an Oracle database can use Huge Pages in RHEL 4, you also need to increase the ulimit parameter "memlock" for the oracle user in/etc/security/limits.conf if "max locked memory" is not unlimited or too small, see ulimit -a or ulimit -l. For example:

oracle           soft    memlock         1048576
oracle           hard    memlock         1048576

The memlock parameter specifies how much memory the oracle user can lock into its address space. Note that Huge Pages are locked in physical memory. The memlock setting is specified in KB and must match the memory size of the number of Huge Pages that Oracle should be able to allocate. So if the Oracle database should be able to use 512 Huge Pages, then memlock must be set to at least 512 * Hugepagesize, which is on my system 1048576 KB (512*1024*2). If memlock is too small, then no single Huge Page will be allocated when the Oracle database starts.

如果limits文件中有相应设置的话,需要检查一下,避免系统没有留出足够的内存(被cache、buffer占用了)

不过作者也提到了

我们生产环境大部分java应用都没调过large page。性能瓶颈也不是在jvm上。

文章里提到的优化,仅仅是实验性质的。

优化对我们来说,是一个循序渐进的过程。我们追求的是效果明显的优化方案,而不是什么都调优一把。

按我的经验,这些一般只是锦上添花而已

两个关于JAVA性能优化的PPT

2009年12月24日 hashei 2 条评论

Tools and Tips to Diagnose Performance Issues

分析性能问题的一些工具和建议

Use JMeter as a Performance Testing Tool

性能检测工具——Jmeter介绍

案例研究: 调优 WebSphere Application Server V7 性能

2009年12月23日 hashei 2 条评论

好东西,赶紧拉下来,介绍的太完整了,从内存调整到各种池大小优化。developerworks上都难得一见的完整调优案例。

原文链接:http://www.ibm.com/developerworks/cn/websphere/techjournal/0909_blythe/0909_blythe.html

IBM WebSphere Application Server 是一种可靠的企业级应用服务器,它提供了一组核心组件、资源和服务,供开发人员在应用程序中使用。每个应用程序都具备特有的需求,并且经常采用截然不同的方式使用应用服务器的资源。为了提供高度灵活性并支持这种广泛的应用程序,WebSphere Application Server 提供了一组全面的参数来帮助您增强对应用程序的调优。

应用服务器已经为最常用的调优参数设置了默认值,以确保能为最广泛的应用程序提供开箱即用的性能改善。但是,由于任意两个应用程序都不可能采用完全相同的方式来使用应用服务器,因此无法确保一组调优参数能适用于所有应用程序。这也突显了对应用程序执行有重点的性能测试和调优的重要性。

本文将讨论在 WebSphere Application Server V7.0(和之前发行版)中最常使用的一些参数,以及对它们进行调优的方法。与其他相关文章提供的调优建议不同,本文将使用 Apache DayTrader Performance Benchmark Sample 案例研究作为本文的上下文。借助 DayTrader 应用程序,您可以清楚地确定所使用的主要服务器组件,对这些区域进行重点调优,并观察各种调优更改所带来的收益。

在继续阅读之前,需要记住关于应用服务器性能调优的一些事项:

  • 提高性能经常会牺牲应用程序或应用服务器的一些特性或功能。在计算性能调优更改时应该仔细考虑性能和特性之间的权衡。
  • 应用服务器之外的一些因素有时会影响性能,包括硬件和操作系统配置、系统中运行的进程、后端数据库资源的性能、网络延迟等等。您在自己执行性能评估时,必须将这些因素考虑在内。
  • 此处讨论的性能改善仅针对 DayTrader 应用程序,并且特定于此处描述的工作负载组成及所支持的硬件和软件栈。您通过本文介绍的调优更改实现的应用程序性能提升肯定会有所不同,并且应该通过您自己的性能测试进行评估。

阅读全文…

Investigating Out of Memory/Memory Leak Problems

2009年9月22日 hashei 6 条评论

本文原文地址为http://support.bea.com/support_news/product_troubleshooting/Investigating_Out_of_Memory_Memory_Leak_Pattern.html

但现在这个网址已经无法访问,我在Metalink找到这篇文章并与大家分享。文章发表的较早,但是OOM发生的原理和解决的方法不变。文中提到的/3GB参数和垃圾回收日志分析的方法我在《JAVA性能优化—IBM JDK JVM参数设置》和《JAVA性能优化-GC日志分析》都提到过。如果英文的看起来比较麻烦,我google到台湾人做的一份翻译JavaAPsvr_A_200411_MemoryLeak,不过我看着“记忆体”觉得更别扭。

Problem Description

Out Of Memory (OOM) – An application displays Out of Memory errors due to memory exhaustion, either in java heap or native memory.

Memory Leak – Constant memory growth in either java heap or native memory, which will eventually end up in out of memory situation. The techniques to debug the memory leak situations are the same as the out of memory situations.

Problem Troubleshooting

Please note that not all of the following items would need to be done. Some issues can be solved by only following a few of the items.

Quick Links:

Java heap This is the memory that the JVM uses to allocate java objects. The maximum value of java heap memory is specified using �Xmx flag in the java command line. If the maximum heap size is not specified, then the limit is decided by the JVM considering factors like the amount of physical memory in the machine and the amount of free memory available at that moment. It is always recommended to specify the max java heap value.

Native memory This is the memory that the JVM uses for its own internal operations. The amount of native memory heap that will be used by the JVM depends on the amount of code generated, threads created, memory used during GC for keeping java object information and temporary space used during code generation, optimization etc.

If there is a third party native module, it could also use the native memory. For example, native JDBC drivers allocate native memory.

The max amount of native memory is limited by the virtual process size limitation on any given OS and the amount of memory already committed for the java heap with -Xmx flag. For example, if the application can allocate a total of 3 GB and if the max java heap is 1 GB, then the max possible native memory is approximately 2 GB.

Process size Process size will be the sum of the java heap, native memory and the memory occupied by the loaded executables and libraries. On 32 bit operating systems, the virtual address space of a process can go up to 4 GB. Out of this 4 GB, the OS kernel reserves some part for itself (typically 1 ~ 2 GB). The remaining is available for the application.

Windows :By default, 2 GB is available for the application and 2 GB is reserved for Kernel’s use. However, on some variants of Windows, there is a /3GB switch which can be used to change this ratio such that the application gets 3 GB. More details on the /3GB switch can be found at:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/memory/base/4gt_ram_tuning.asp

RH Linux AS 2.1 : 3 GB is available for the application.

For other operating systems, please refer to the OS documentation for your configuration.

Top of Page

阅读全文…

Linux System and Performance Monitoring(总结篇)

2009年9月12日 hashei 没有评论

作者:tonnyom
原载: http://www.sanotes.net/html/y2009/393.html
版权所有。转载时必须以链接形式注明作者和原始出处及本声明。

Linux System and Performance Monitoring(总结篇)
Date: 2009.07.21
Author: Darren Hoch
译: Tonnyom[AT]hotmail.com

结束语: 这是该译文的最后一篇,在这篇中,作者提供了一个案例环境,用之前几篇所阐述的理论以及涉及到的工具,对其进行一个整体的系统性能检查.对大家更好理解系统性能监控,进行一次实战演习.
BTW:在中文技术网站上,类似内容的文章,大体是来自该作者06-07年所著论文,此译文是建立在作者为OSCON 2009重写基础上的.所以部分内容可能会存在重复雷同,特此说明下.

附录 A: 案例学习 – 性能监控之循序渐进

某一天,一个客户打电话来需要技术帮助,并抱怨平常15秒就可以打开的网页现在需要20分钟才可以打开.

具体系统配置如下:

RedHat Enterprise Linux 3 update 7
Dell 1850 Dual Core Xenon Processors, 2 GB RAM, 75GB 15K Drives
Custom LAMP software stack(译注:Llinux+apache+mysql+php 环境)

性能分析之步骤

1. 首先使用vmstat 查看大致的系统性能情况:

# vmstat 1 10
procs memory swap io system cpu
r b swpd free buff cache si so bi bo in cs us sy id wa
1 0 249844 19144 18532 1221212 0 0 7 3 22 17 25 8 17 18
0 1 249844 17828 18528 1222696 0 0 40448 8 1384 1138 13 7 65 14
0 1 249844 18004 18528 1222756 0 0 13568 4 623 534 3 4 56 37
2 0 249844 17840 18528 1223200 0 0 35200 0 1285 1017 17 7 56 20
1 0 249844 22488 18528 1218608 0 0 38656 0 1294 1034 17 7 58 18
0 1 249844 21228 18544 1219908 0 0 13696 484 609 559 5 3 54 38
0 1 249844 17752 18544 1223376 0 0 36224 4 1469 1035 10 6 67 17
1 1 249844 17856 18544 1208520 0 0 28724 0 950 941 33 12 49 7
1 0 249844 17748 18544 1222468 0 0 40968 8 1266 1164 17 9 59 16
1 0 249844 17912 18544 1222572 0 0 41344 12 1237 1080 13 8 65 13

分析:
1,不会是内存不足导致,因为swapping 始终没变化(si 和 so).尽管空闲内存不多(free),但swpd 也没有变化.
2,CPU 方面也没有太大问题,尽管有一些运行队列(procs r),但处理器还始终有50% 多的idle(CPU id).
3,有太多的上下文切换(cs)以及disk block从RAM中被读入(bo).
4,CPU 还有平均20% 的I/O 等待情况.

结论:
从以上总结出,这是一个I/O 瓶颈.

2. 然后使用iostat 检查是谁在发出IO 请求:

# iostat -x 1
Linux 2.4.21-40.ELsmp (mail.example.com) 03/26/2007

avg-cpu: %user %nice %sys %idle
30.00 0.00 9.33 60.67

Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util
/dev/sda 7929.01 30.34 1180.91 14.23 7929.01 357.84 3964.50 178.92 6.93 0.39 0.03 0.06 6.69
/dev/sda1 2.67 5.46 0.40 1.76 24.62 57.77 12.31 28.88 38.11 0.06 2.78 1.77 0.38
/dev/sda2 0.00 0.30 0.07 0.02 0.57 2.57 0.29 1.28 32.86 0.00 3.81 2.64 0.03
/dev/sda3 7929.01 24.58 1180.44 12.45 7929.01 297.50 3964.50 148.75 6.90 0.32 0.03 0.06 6.68

avg-cpu: %user %nice %sys %idle
9.50 0.00 10.68 79.82

Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util
/dev/sda 0.00 0.00 1195.24 0.00 0.00 0.00 0.00 0.00 0.00 43.69 3.60 0.99 117.86
/dev/sda1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
/dev/sda2 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
/dev/sda3 0.00 0.00 1195.24 0.00 0.00 0.00 0.00 0.00 0.00 43.69 3.60 0.99 117.86

avg-cpu: %user %nice %sys %idle
9.23 0.00 10.55 79.22

Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util
/dev/sda 0.00 0.00 1200.37 0.00 0.00 0.00 0.00 0.00 0.00 41.65 2.12 0.99 112.51
/dev/sda1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
/dev/sda2 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
/dev/sda3 0.00 0.00 1200.37 0.00 0.00 0.00 0.00 0.00 0.00 41.65 2.12 0.99 112.51

分析:
1,看上去只有/dev/sda3 分区很活跃,其他分区都很空闲.
2,差不多有1200 读IOPS,磁盘本身是支持200 IOPS左右(译注:参考之前的IOPS 计算公式).
3,有超过2秒,实际上没有一个读磁盘(rkb/s).这和在vmstat 看到有大量I/O wait是有关系的.
4,大量的read IOPS(r/s)和在vmstat 中大量的上下文是匹配的.这说明很多读操作都是失败的.

结论:
从以上总结出,部分应用程序带来的读请求,已经超出了I/O 子系统可处理的范围.

3. 使用top 来查找系统最活跃的应用程序

# top -d 1
11:46:11 up 3 days, 19:13, 1 user, load average: 1.72, 1.87, 1.80
176 processes: 174 sleeping, 2 running, 0 zombie, 0 stopped
CPU states: cpu user nice system irq softirq iowait idle
total 12.8% 0.0% 4.6% 0.2% 0.2% 18.7% 63.2%
cpu00 23.3% 0.0% 7.7% 0.0% 0.0% 36.8% 32.0%
cpu01 28.4% 0.0% 10.7% 0.0% 0.0% 38.2% 22.5%
cpu02 0.0% 0.0% 0.0% 0.9% 0.9% 0.0% 98.0%
cpu03 0.0% 0.0% 0.0% 0.0% 0.0% 0.0% 100.0%
Mem: 2055244k av, 2032692k used, 22552k free, 0k shrd, 18256k buff
1216212k actv, 513216k in_d, 25520k in_c
Swap: 4192956k av, 249844k used, 3943112k free 1218304k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
14939 mysql 25 0 379M 224M 1117 R 38.2 25.7% 15:17.78 mysqld
4023 root 15 0 2120 972 784 R 2.0 0.3 0:00.06 top
1 root 15 0 2008 688 592 S 0.0 0.2 0:01.30 init
2 root 34 19 0 0 0 S 0.0 0.0 0:22.59 ksoftirqd/0
3 root RT 0 0 0 0 S 0.0 0.0 0:00.00 watchdog/0
4 root 10 -5 0 0 0 S 0.0 0.0 0:00.05 events/0

分析:
1,占用资源最多的好像就是mysql 进程,其他都处于完全idle 状态.
2,在top(wa) 看到的数值,和在vmstat 看到的wio 数值是有关联的.

结论:
从以上总结出,似乎就只有mysql 进程在请求资源,因此可以推论它就是导致问题的关键.

4. 现在已经确定是mysql 在发出读请求,使用strace 来检查它在读请求什么.

# strace -p 14939

Process 14939 attached – interrupt to quit
read(29, “\3\1\237\1\366\337\1\222%\4\2012P/d”, 20) = 20
read(29, “ata1/strongmail/log/strongmail-d”…, 399) = 399
_llseek(29, 2877621036, [2877621036], SEEK_SET) = 0
read(29, “\1\1\241\366\337\1\223%\4\2012P/da”, 20) = 20
read(29, “ta1/strongmail/log/strongmail-de”…, 400) = 400
_llseek(29, 2877621456, [2877621456], SEEK_SET) = 0
read(29, “\1\1\235\366\337\1\224%\4\2012P/da”, 20) = 20
read(29, “ta1/strongmail/log/strongmail-de”…, 396) = 396
_llseek(29, 2877621872, [2877621872], SEEK_SET) = 0
read(29, “\1\1\245\366\337\1\225%\4\2012P/da”, 20) = 20
read(29, “ta1/strongmail/log/strongmail-de”…, 404) = 404
_llseek(29, 2877622296, [2877622296], SEEK_SET) = 0
read(29, “\3\1\236\2\366\337\1\226%\4\2012P/d”, 20) = 20

分析:
1,大量的读操作都在不断寻道中,说明mysql 进程产生的是随机IO.
2,看上去似乎是,某一sql 查询导致读操作.

结论:
从以上总结出,所有的读IOPS 都是mysql 进程在执行某些读查询时产生的.

5. 使用mysqladmin 命令,来查找是哪个慢查询导致的.

# ./mysqladmin -pstrongmail processlist

+—-+——+———–+————+———+——+———-+—————————————-
| Id | User | Host | db | Command | Time | State | Info
+—-+——+———–+————+———+——+———-+—————————————-
| 1 | root | localhost | strongmail | Sleep | 10 | |
| 2 | root | localhost | strongmail | Sleep | 8 | |
| 3 | root | localhost | root | Query | 94 | Updating | update `failures` set
`update_datasource`=’Y’ where database_id=’32′ and update_datasource=’N’ and |
| 14 | root | localhost | | Query | 0 | | show processlist

分析:
1,MySQL 数据库里,似乎在不断的运行table update查询.
2,基于这个update 查询,数据库是对所有的table 进行索引.

结论:
从以上总结出,MySQL里这些update 查询问题,都是在尝试对所有table 进行索引.这些产生的读请求正是导致系统性能下降的原因.

后续

把以上这些性能信息移交给了相关开发人员,用于分析他们的PHP 代码.一个开发人员对代码进行了临时性优化.某个查询如果出错了,也最多到100K记录.数据库本身考虑最多存在4百万记录.最后,这个查询不会再给数据库带来负担了.

References
• Ezlot, Phillip – Optimizing Linux Performance, Prentice Hall, Princeton NJ 2005 ISBN – 0131486829
• Johnson, Sandra K., Huizenga, Gerrit – Performance Tuning for Linux Servers, IBM Press, Upper Saddle River NJ 2005 ISBN 013144753X
• Bovet, Daniel Cesati, Marco – Understanding the Linux Kernel, O’Reilly Media, Sebastoppl CA 2006, ISBN 0596005652
• Blum, Richard – Network Performance Open Source Toolkit, Wiley, Indianapolis IN 2003, ISBN 0-471-43301-2
• Understanding Virtual Memory in RedHat 4, Neil Horman, 12/05 http://people.redhat.com/nhorman/papers/rhel4_vm.pdf
• IBM, Inside the Linux Scheduler, http://www.ibm.com/developerworks/linux/library/l-scheduler/
• Aas, Josh, Understanding the Linux 2.6.8.1 CPU Scheduler, http://josh.trancesoftware.com/linux/linux_cpu_scheduler.pdf
• Wieers, Dag, Dstat: Versatile Resource Statistics Tool, http://dag.wieers.com/home-made/dstat/