性能测试-各环节监控

 

 

oracle 的一个实例的链接不能超过 2 万

nginx 在 7 层、F5 在 4 层,Nginx 可以根据内容分发,F5 只能识别 IP 和端口

 

mongdb 用在最常用的查询场景

redis 是 key-value 类型的数据库,最大的优势是内存上跑

页面基础原则:单页面不超过 1s

 

 

weblogic

1、xms(JVM 初始分配的堆内存)内存最大,xmx(JVM 最大允许分配的堆内存)内存最小值、xmn 是 xms 和 xmx 的新生代,是 xms 的 0.25。xmx 要大于 xms

   a. 内存大小一致最好

   b. 分配 1.5~4G 合适,因为这个和 GC 成反比,内存越大。GC 回收的频率越低,GC 会抢占 CPU,会造成系统卡顿。

   c. 新生代(xmn)对 weblogic 宕机起着很好的延缓作用。为新的请求总是会预留一定的空间;

   d. 统计 full GC 的次数(老年代满了 or 新生代里判断为老年代的内容大于老年代理可以存储的大小时)如果次数在增长,就一定说明内存出问题了。

 

2、PermSize 和 MaxPermSize

   a.PermSize=64m JVM 初始分配的非堆内存

   b.MaxPermSize=256m JVM 最大允许分配的非堆内存

   c.permSize 存放 java 编译以后的 class

   d. 满了 weblogic 就会崩,并且不会自动增大。

   e.PermSize 建议设置为 384M,默认 128M

   e.PermSize 和 MaxPermSize 指明虚拟机为 java 永久生成对象(Permanate generation)如,class 对象、方法对象这些可反射(reflective)对象分配内存限制,这些内存不包括

 

在 Heap(堆内存)区之中,MaxPermSize 过小会导致:java.lang.OutOfMemoryError: PermGen space

  

3、wls(组件漏洞)启动最小线程数 400,不能超过 1000,否则 wls 会崩

 

4、套接字复用器,请求进来的时候会在套接字复用器里面建立和分配 soket,如果启用本地 io,就可以省去建立的时间。

5、接受积压(排队长度):每次扩大长度时,以原来的 0.25 倍增长;

6、登录超时:与服务器最后一次交互到自动断开的持续时间,0.5 到 2 小时合适

7、反向 DNS 查找

       a. 千万不能点,会下降性能

       b. 通过应用反查 ip

 

8、日志

    a. 对性能影响很大,因为涉及 io

    b. 如果一定要打日志,那么可以采取异步打印,设置缓冲区,将缓存区占满后,一起打印。对性能提升很大;

 

9、keep-alive(weblogic 的一个设置项)保持长链接,长连接比短连接提升 30% 的 tps

 

 

 

Tomcat

1、共享线程池

2、同步阻塞型连接

3、异步非阻塞连接

 

 

三层缓存

应用缓存  Nginx

物理缓存  cdn

关系型数据库:acid 保持数据一致,有关联查询

非关系型数据库:key 和 value 可以不限结构的存储。redias(缓存)、mongdb(查询量比较大,比较固定,比较频繁使用)

 

 

redis

主要使用内存的技术

1、sava  对性能影响不大

2、get 的命中率(大于 99%),如果有很多找不着的连接,就会占用很多内存;

redis 提供了 INFO 这个命令,能够随时监控服务器的状态,只用 telnet 到对应服务器的端口,执行命令即可:

[html] view plain copy

  1. telnet localhost 6379  
  2. info  

在输出的信息里面有这几项和缓存的状态比较有关系:

[html] view plain copy

  1. keyspace_hits:14414110  
  2. keyspace_misses:3228654  
  3. used_memory:433264648  
  4. expired_keys:1333536  
  5. evicted_keys:1547380  

通过计算 hits 和 miss,我们可以得到缓存的命中率:14414110 / (14414110 + 3228654) = 81% ,一个缓存失效机制,和过期时间设计良好的系统,命中率可以做到 95% 以上

3、连接的使用,看是否接近 maxclients

使用命令 info 查看连接数 connected_clients:357

4、vm-enabled no 虚拟内存一般不启用

指定是否启用虚拟内存机制,默认值为 no,简单的介绍一下,VM 机制将数据分页存放,由 Redis 将访问量较少的页即冷数据 swap 到磁盘上,访问多的页面由磁盘自动换出到内存中

5、请求 100ms 以内比较好

 

6、Redis 是单线程模式,所有命令都是按照顺序执行的;

 

 

Nginx

1、解决安全问题

a、请求数足够大,5 万以上没有问题

b、反向代理,不暴露应用的 IP 地址

2、多路复用机制,epool

3、Nginx 组件配置

a、worker_processes 等于 CPU 的数量

b、Worker_connections 大一些都无所谓,65535 都没有问题,实际上 10000 左右会被用作管理

c、Server_name 主机名

d、Sendfile  on 内存复制模式,来判断是否要压缩。

e、请求缓存(二级),一级:client_header_bufer_size 可以设大一点,减少二级的分配过程。二级:large_client_header_buffes。如果报文比二级还大,就会返回 400 错误

 

 

代码

表现层:

a、用户客户端:加载权限数,目录树需要较大内存,java 里会使用懒加载

   工具

1、问题分析:httpwatch

2、出报告:yslow(评分 C 一下的要修改)

3、Dynatrace(分析 js,正常加载页面是并行的,边接受边解析边渲染。但是遇到 js 就要停止以上动作,因为 js 可能会改写解析出来的树,所以要尽量少用 js,尽量在最后加载 js)

4、降低像素,处理资源:pagetest(主要检测要处理的图片等资源)

5、优化点:a. 代码里一定要设置 cache;b. 一个页面的请求控制在 60-70 以下;c. 响应时间排序,看看慢的是不是报文大。看 accept-encoding 是否用了 gzip,如果图片多,需要压缩。看 time chart,具体哪个阶段慢?看 connection 是否 keep-alive

b、前台代码

 

逻辑层

a、工具

1、Jvsualvm

   以 jdk1.6update45(jdk1.6update45 自带的 jvisualvm) 来做说明,当然也可单独下载独立的 jvisualvm,正常安装完 jdk 后,至 jdk 的 bin 目录下,运行 jvisualvm.exe 即可,程序运行后会自动监控本机运行的 java 程序(Local 标签下,远程服务器上的 java 程序需要另行配置)

监控项总共分为 Overview,Monitor,Threads 和一个 Sampler。

1.Overview(jvm 启动参数,系统参数)

 

可以看到 eclipse 的启动参数

 

(通过这些启动参数,可以判断程序是否有内存溢出)

 

2.Monitor

 

左上:cpu 利用率,gc 状态的监控

右上:堆利用率,永久内存区的利用率

左下:类的监控

右下:线程的监控

performGC:gc 的详细运行状态

HeapDump:堆的详细状态(可以看到堆的概况,里面所有的类,还能点进具体的一个类查看这个类的状态)

 

3.Threads

 

能够显示线程的名称和运行的状态,在调试多线程时必不可少,而且可以点进一个线程查看这个线程的详细运行情况

监控服务器上的 tomcat

tomcat 的配置文件 catalina.sh 中增加:

[plain] view plain copy

  1. JAVA_OPTS="-Dcom.sun.management.jmxremote.port=9998   
  2.     -Dcom.sun.management.jmxremote.ssl=false   
  3.     -Dcom.sun.management.jmxremote.authenticate=false   
  4.     -Djava.rmi.server.hostname=192.168.58.164"  

参数说明:

[plain] view plain copy

  1. 指定了 JMX 启动的代理端口,这个端口就是 visualvm 要连接的端口(9998 端口不能被别的程序使用 netstat -an|gerp 9998)  
  2. Dcom.sun.management.jmxremote.port=9998  
  3. 指定了 JMX 是否启用 ssl  
  4. Dcom.sun.management.jmxremote.authenticate=false  
  5. 指定了 JMX 是否启用鉴权(需要用户名,密码鉴权)  
  6. Dcom.sun.management.jmxremote.authenticate=false  
  7. 指定了服务器主机名  
  8. Djava.rmi.server.hostname=192.168.58.164  

 

填写主机名:

 

右键创建一个 jmx 连接:

 

填写上端口号即可:

 

配置完成:

 

2、Jprofiler

JProfiler 是由 ej-technologies GmbH 公司开发的一款性能瓶颈分析工具 (该公司还开发部署工具)。
其特点:

  • 使用方便
  • 界面操作友好
  • 对被分析的应用影响小
  • CPU,Thread,Memory 分析功能尤其强大
  • 支持对 jdbc,noSql, jsp, servlet, socket 等进行分析
  • 支持多种模式 (离线,在线) 的分析
  • 跨平台  (图 1)

二. 数据采集

Q1. JProfiler 既然是一款性能瓶颈分析工具,这些分析的相关数据来自于哪里?
Q2. JProfiler 是怎么将这些数据收集并展现的?


(图 2)

A1. 分析的数据主要来自于下面俩部分
1. 一部分来自于 jvm 的分析接口 **JVMTI**(JVM Tool Interface) , JDK 必须 >=1.6

JVMTI is an event-based system. The profiling agent library can register handler functions for different events. It can then enable or disable selected events

例如: 对象的生命周期,thread 的生命周期等信息
2. 一部分来自于 instruments classes(可理解为 class 的重写, 增加 JProfiler 相关统计功能)
例如:方法执行时间,次数,方法栈等信息

A2. 数据收集的原理如图 2
1. 用户在 JProfiler GUI 中下达监控的指令 (一般就是点击某个按钮)
2. JProfiler GUI JVM 通过 socket(默认端口 8849),发送指令给被分析的 jvm 中的 JProfile Agent。
3. JProfiler Agent(如果不清楚 Agent 请看文章第三部分 "启动模式") 收到指令后,将该指令转换成相关需要监听的事件或者指令, 来注册到 JVMTI 上或者直接让 JVMTI 去执行某功能 (例如 dump jvm 内存)
4. JVMTI 根据注册的事件,来收集当前 jvm 的相关信息。 例如: 线程的生命周期; jvm 的生命周期;classes 的生命周期; 对象实例的生命周期; 堆内存的实时信息等等
5. JProfiler Agent 将采集好的信息保存到 ** 内存 ** 中,按照一定规则统计好 (如果发送所有数据 JProfiler GUI,会对被分析的应用网络产生比较大的影响)
6. 返回给 JProfiler GUI Socket.
7. JProfiler GUI Socket 将收到的信息返回 JProfiler GUI Render
8. JProfiler GUI Render 渲染成最终的展示效果

三. 数据采集方式和启动模式

A1. JProfier 采集方式分为两种:Sampling(样本采集) 和 Instrumentation

Sampling: 类似于样本统计, 每隔一定时间 (5ms) 将每个线程栈中方法栈中的信息统计出来。优点是对应用影响小(即使你不配置任何 Filter, Filter 可参考文章第四部分),缺点是一些数据 / 特性不能提供(例如: 方法的调用次数)

Instrumentation: 在 class 加载之前,JProfier 把相关功能代码写入到需要分析的 class 中,对正在运行的 jvm 有一定影响。优点: 功能强大,但如果需要分析的 class 多,那么对应用影响较大,一般配合 Filter 一起使用。所以一般 JRE class 和 framework 的 class 是在 Filter 中通常会过滤掉。

注: JProfiler 本身没有指出数据的采集类型,这里的采集类型是针对方法调用的采集类型 。因为 JProfiler 的绝大多数核心功能都依赖方法调用采集的数据, 所以可以直接认为是 JProfiler 的数据采集类型。

A2: 启动模式:

Attach mode
可直接将本机正在运行的 jvm 加载 JProfiler Agent. 优点是很方便,缺点是一些特性不能支持。如果选择 Instrumentation 数据采集方式,那么需要花一些额外时间来重写需要分析的 class。

Profile at startup
在被分析的 jvm 启动时,将指定的 JProfiler Agent 手动加载到该 jvm。JProfiler GUI 将收集信息类型和策略等配置信息通过 socket 发送给 JProfiler Agent,收到这些信息后该 jvm 才会启动。
在被分析的 jvm 的启动参数增加下面内容:
语法: -agentpath:[path to jprofilerti library]
【注】: 语法不清楚没关系,JProfiler 提供了帮助向导.
(图 3)

Prepare for profiling:
和 Profile at startup 的主要区别:被分析的 jvm 不需要收到 JProfiler GUI 的相关配置信息就可以启动。

Offline profiling
一般用于适用于不能直接调试线上的场景。Offline profiling 需要将信息采集内容和策略 (一些 Trigger, Trigger 请参考文章第五部分) 打包成一个配置文件(config.xml),在线上启动该 jvm 加载 JProfiler Agent 时,加载该 xml。那么 JProfiler Agent 会根据 Trigger 的类型会生成不同的信息。例如: heap dump; thread dump; method call record 等
语法:
-agentpath:/home/2080/jprofiler8/bin/Linux-x64/libjprofilerti.so=offline,id=151,config=/home/2080/config.xml
【注】: config.xml 中的每一个被分析的 jvm 的采集信息都有一个 id 来标识。
下面是使用了离线模式,并使用了每隔一秒 dump heap 的 Trigger:

  •  

四. JProfiler 核心概念

Filter: 什么 class 需要被分析。分为包含和不包含两种类型的 Filter。
(图 4)

Profiling Settings: 收据收集的策略:Sampling 和 Instrumentation,一些数据采集细节可以自定义.
(图 5)

Triggers: 一般用于 **offline** 模式,告知 JProfiler Agent 什么时候触发什么行为来收集指定信息.
(图 6)

  1.  
  2.  

Live memory: class/class instance 的相关信息。 例如对象的个数,大小,对象创建的方法执行栈,对象创建的热点。
(图 7)

  1.  
  2.  

Heap walker: 对一定时间内收集的内存对像信息进行静态分析,功能强大且使用。包含对象的 outgoing reference, incoming reference, biggest object 等
(图 8)

  1.  
  2.  

CPU views: CPU 消耗的分布及时间 (cpu 时间或者运行时间); 方法的执行图; 方法的执行统计 (最大,最小,平均运行时间等)
(图 9)

  1.  
  2.  

Thread: 当前 jvm 所有线程的运行状态,线程持有锁的状态,可 dump 线程。
(图 10)

  1.  
  2.  

Monitors & locks: 所有线程持有锁的情况以及锁的信息
(图 11)

  1.  
  2.  

Telemetries: 包含 heap, thread, gc, class 等的趋势图 (遥测视图)

  1.  

五. 实践

为了方便实践,直接以 JProfiler8 自带的一个例子来帮助理解上面的相关概念。
JProfiler 自带的例子如下:模拟了内存泄露和线程阻塞的场景:
具体源码参考: /jprofiler install path/demo/bezier


(图 12)


(图 13 Leak Memory 模拟内存泄露, Simulate blocking 模拟线程间锁的阻塞)

A1. 首先来分析下内存泄露的场景:(勾选图 13 中 Leak Memory 模拟内存泄露)
1. 在 **Telemetries-> Memory** 视图中你会看到大致如下图的场景 (在看的过程中可以间隔一段时间去执行 Run GC 这个功能):看到下图蓝色区域, 老生代在 gc 后(** 波谷 **) 内存的大小在慢慢的增加(理想情况下,这个值应该是稳定的)
(图 14)

  1.  

在 Live memory->Recorded Objects 中点击 **record allocation data** 按钮,开始统计一段时间内创建的对象信息。执行一次 **Run GC** 后看看当前对象信息的大小,并点击工具栏中 **Mark Current** 按钮 ( 其实就是给当前对象数量打个标记。执行一次 Run GC,然后再继续观察; 执行一次 Run GC,然后再继续观察...。最后看看哪些对象在不断 GC 后,数量还一直上涨的。最后你看到的信息可能和下图类似
(图 15 绿色是标记前的数量,红色是标记后的增量)

  1.  
  2.  

在 Heap walker 中分析刚才记录的对象信息
(图 16)
(图 17)

  1.  
  2.  

点击上图中实例最多的 class,右键 **Use Selected Instances->Reference->Incoming Reference**.
发现该 Long 数据最终是存放在 **bezier.BeaierAnim.leakMap** 中。
(图 18)

  1.  

在 Allocations tab 项中,右键点击其中的某个方法,可查看到具体的源码信息.
(图 19)

【注】: 到这里问题已经非常清楚了,明白了在图 17 中为什么哪些实例的数量是一样多,并且为什么内存在 fullgc 后还是回收不了 (一个 old 区的对象 leakMap,put 的信息也会进入 old 区, leakMap 如回收不掉,那么该 map 中包含的对象也回收不掉)。

A2. 模拟线程阻塞的场景 (勾选图 13 中 Simulate blocking 模拟线程间锁的阻塞)
为了方便区分线程,我将 Demo 中的 BezierAnim.java 的 L236 的线程命名为 test

public void start() {
            thread = new Thread(this, "test");
            thread.setPriority(Thread.MIN_PRIORITY);
            thread.start();
        }

正常情况下,如下图
(图 20)

勾选了 Demo 中 "Simulate blocking" 选项后,如下图 (注意看下下图中的状态图标), test 线程 block 状态明显增加了。
(图 21)

在 **Monitors & locks->Monitor History** 观察了一段时间后,会发现有 4 种发生锁的情况。

第一种:
AWT-EventQueue-0 线程持有一个 Object 的锁,并且处于 Waiting 状态。

图下方的代码提示出 Demo.block 方法调用了 object.wait 方法。这个还是比较容易理解的。 
(图 22)

第二种:
AWT-EventQueue-0 占有了 bezier.BezierAnim$Demo 实例上的锁,而 test 线程等待该线程释放。

注意下图中下方的源代码, 这种锁的出现原因是 Demo 的 blcok 方法在 AWT 和 test 线程
都会被执行,并且该方法是 synchronized.
(图 23)

第三种和第四种:
test 线程中会不断向事件 Event Dispatching Thread 提交任务,导致竞争 java.awt.EventQueue 对象锁。
提交任务的方式是下面的代码:repaint()EventQueue.invokeLater

        public void run() {
            Thread me = Thread.currentThread();
            while (thread == me) {
                repaint();
                if (block) {
                    block(false);
                }
                try {
                    Thread.sleep(10);
                } catch (Exception e) {
                    break;
                }
                EventQueue.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        onEDTMethod();
                    }
                });
            }
            thread = null;
        }


(图 24)

六. 最佳实践

  1. JProfiler 都会对一些特殊操作给予提示,这时候最好仔细阅读下说明.
  2. "Mark Current" 功能在某些场景很有效
  3. Heap walker 一般是静态分析在 Live memory->Recorder objects 的对象信息,这些信息可能会被 GC 回收掉,导致 Heap walker 中什么也没有显示出来。这种现象是正常的。
  4. 可以才工具栏中 Start Recordings 配置一次性收集的信息
  5. Filter 中 include 和 exclude 是有顺序的,注意使用下图 ** 左下方 ** 的 **“Show Filter Tree”** 来验证一下顺序  (图 25)
  6. JProfiler helper: http://resources.ej-technologies.com/jprofiler/help/doc/index.html
  7.  

七. 参考文献

JVMTI: http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html

  1.  

 

b、优化点

1、不要在嵌套中抛出异常

2、不要在异常中处理业务逻辑

3、尽量使用局部变量,局部变量比全局变量块 2 到 3 倍

 

 

 

数据层

工具

spotlight