研究表明,人的反应时间通常在0.2s左右,运动员0.14s已经是顶级反应了,所以响应时间在0.2秒以内通常是无感的。那么在性能测试时,需要关注哪些指标呢?
CPU:有的应用需要大量计算,他们会长时间、不间断地占用 CPU 资源,导致其他资源无法争夺到 CPU 而响应缓慢,从而带来系统性能问题。例如,代码递归导致的无限循环,正则表达式引起的回溯,JVM 频繁的 FULL GC,以及多线程编程造成的大量上下文切换等,这些都有可能导致 CPU 资源繁忙。
内存:Java 程序一般通过 JVM 对内存进行分配管理,主要是用 JVM 中的堆内存来存储 Java 创建的对象。系统堆内存的读写速度非常快,所以基本不存在读写性能瓶颈。但是由于内存成本要比磁盘高,相比磁盘,内存的存储空间又非常有限。所以当内存空间被占满,对象无法回收时,就会导致内存溢出、内存泄露等问题。
磁盘 I/O:磁盘相比内存来说,存储空间要大很多,但磁盘 I/O 读写的速度要比内存慢,虽然目前引入的 SSD 固态硬盘已经有所优化,但仍然无法与内存的读写速度相提并论。
网络:网络对于系统性能来说,也起着至关重要的作用。如果你购买过云服务,一定经历过,选择网络带宽大小这一环节。带宽过低的话,对于传输数据比较大,或者是并发量比较大的系统,网络就很容易成为性能瓶颈。
异常:Java 应用中,抛出异常需要构建异常栈,对异常进行捕获和处理,这个过程非常消耗系统性能。如果在高并发的情况下引发异常,持续地进行异常处理,那么系统的性能就会明显地受到影响。数据库:大部分系统都会用到数据库,而数据库的操作往往是涉及到磁盘 I/O 的读写。大量的数据库读写操作,会导致磁盘 I/O 性能瓶颈,进而导致数据库操作的延迟性。对于有大量数据库读写操作的系统来说,数据库的性能优化是整个系统的核心。
锁竞争:在并发编程中,我们经常会需要多个线程,共享读写操作同一个资源,这个时候为了保持数据的原子性(即保证这个共享资源在一个线程写的时候,不被另一个线程修改),我们就会用到锁。锁的使用可能会带来上下文切换,从而给系统带来性能开销。JDK1.6 之后,Java 为了降低锁竞争带来的上下文切换,对 JVM 内部锁已经做了多次优化,例如,新增了偏向锁、自旋锁、轻量级锁、锁粗化、锁消除等。而如何合理地使用锁资源,优化锁资源,就需要你了解更多的操作系统知识、Java 多线程编程基础,积累项目经验,并结合实际场景去处理相关问题。了解了上面这些基本内容,我们可以得到下面几个指标,来衡量一般系统的性能。响应时间响应时间是衡量系统性能的重要指标之一,响应时间越短,性能越好,一般一个接口的响应时间是在毫秒级。
操作数据库的时间是请求接口中最耗时的操作,是操作内存的数个数量级耗时。
计算机资源分配使用率
通常由 CPU 占用率、内存使用率、磁盘 I/O、网络 I/O 来表示资源使用率。这几个参数好比一个木桶,如果其中任何一块木板出现短板,任何一项分配不合理,对整个系统性能的影响都是毁灭性的。
负载承受能力
当系统压力上升时,你可以观察,系统响应时间的上升曲线是否平缓。这项指标能直观地反馈给你,系统所能承受的负载压力极限。例如,当你对系统进行压测时,系统的响应时间会随着系统并发数的增加而延长,直到系统无法处理这么多请求,抛出大量错误时,就到了极限。
数据库响应时间:数据库操作所消耗的时间,往往是整个请求链中最耗时的;
服务端响应时间:服务端包括 Nginx 分发的请求所消耗的时间以及服务端程序执行所消耗的时间;
网络响应时间:这是网络传输时,网络硬件需要对传输的请求进行解析等操作所消耗的时间;
客户端响应时间:对于普通的 Web、App 客户端来说,消耗时间是可以忽略不计的,但如果你的客户端嵌入了大量的逻辑处理,消耗的时间就有可能变长,从而成为系统的瓶颈。
吞吐量
在测试中,我们往往会比较注重系统接口的 TPS(每秒事务处理量),因为 TPS 体现了接口的性能,TPS 越大,性能越好。在系统中,我们也可以把吞吐量自下而上地分为两种:磁盘吞吐量和网络吞吐量。
性能测试有哪些干扰因子?
了解了上述的关键指标后,性能测试就有了相应关注点。
精准定位哪个模块或者接口的性能问题,可以微基准性能测试,即不同实现方式下的性能对比。
考虑测试环境、测试场景和测试目标,可以通过宏基准性能测试。
模拟线上环境,然后看测试场景。我们需要确定在测试某个接口时,是否有其他业务接口同时也在平行运行,造成干扰。如果有,请重视,因为你一旦忽视了这种干扰,测试结果就会出现偏差。最后看测试目标。我们的性能测试是要有目标的,这里可以通过吞吐量以及响应时间来衡量系统是否达标。不达标,就进行优化;达标,就继续加大测试的并发数,探底接口的 TPS(最大每秒事务处理量),这样做,可以深入了解到接口的性能。观察服务器的 CPU、内存以及 I/O 使用率的变化。
性能测试时,系统会运行得越来越快,后面的访问速度要比我们第一次访问的速度快上几倍。热点代码,JVM会通过即时编译器(JIT compiler,just-in-time compiler)把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,然后存储在内存中,之后每次运行代码时,直接从内存中获取。
多 JVM 情况下的影响:服务器有多个Java项目,部署在不同的 Tomcat 中,这就意味着服务器会有多个 JVM。任意一个 JVM 都拥有整个系统的资源使用权。原则上一台机器上只部署单独的一个 JVM,在做性能测试时,测试结果干扰少。所以我们应该避免环境中一台机器部署多个 JVM 的情况。
限流,对系统的入口设置最大访问限制。参考性能测试中探底接口的 TPS 。同时采取熔断措施,友好地返回没有成功的请求。
计算密集型,线程池参数调优,https://github.com/nickliuchao/threadpollsizetest,核心线程数一般设定为N+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。IO密集型2N,实际情况根据线上压力进行增加或者减少核心线程数。
千万别漏了GC日志
-XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:./gclogs
解释:
-XX:+PrintGCDateStamps 日期格式打印GC时间
-XX:+PrintGCDetails GC详细信息
-Xloggc:./gc.log GC日志 会根据系统自动修改/
jmeter测试注意事项:
线程名:IoT性能吞吐控制(带集合点。8:2读写并发) 测试任务集合1
线程名:IoT性能(带集合点。不同时进行读写)测试任务集合2
线程名:IoT性能无集合点(不带集合点)测试任务集合3
step1: 启用要测试的线程,禁用不测试的线程 只能跑一个任务,否则任务之间会彼此影响
step2:修改并发数 1)线程组下的“线程数” 2)用户自定义的变量中,sample 变量名,8:2的还要修改sample20%、sample80% 两个变量 3)用户登录数据文件位置要修改
step3:聚合报告修改运行结果保存路径 文件名后缀.jtl 【E:\项目\IoT\iot文档\username.jtl】 注意: 1、每跑一次,就改下文件名后缀,否则运行结果可能会覆盖或叠加 2、每次跑前点下清除或清楚全部按钮,把记录清除
IO测试
public class IOTypeTest implements Runnable {
//整体执行时间,包括在队列中等待的时间
Vector<Long> wholeTimeList;
//真正执行时间
Vector<Long> runTimeList;
private long initStartTime = 0;
/**
* 构造函数
* @param runTimeList
* @param wholeTimeList
*/
public IOTypeTest(Vector<Long> runTimeList, Vector<Long> wholeTimeList) {
initStartTime = System.currentTimeMillis();
this.runTimeList = runTimeList;
this.wholeTimeList = wholeTimeList;
}
/**
*IO操作
* @param number
* @return
* @throws IOException
*/
public void readAndWrite() throws IOException {
File sourceFile = new File("D:/tets.txt");
//创建输入流
BufferedReader input = new BufferedReader(new FileReader(sourceFile));
//读取源文件,写入到新的文件
String line = null;
while((line = input.readLine()) != null){
System.out.println(line);
}
//关闭输入输出流
input.close();
}
public void run() {
long start = System.currentTimeMillis();
try {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
readAndWrite();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long end = System.currentTimeMillis();
long wholeTime = end - initStartTime;
long runTime = end - start;
wholeTimeList.add(wholeTime);
runTimeList.add(runTime);
System.out.println("单个线程花费时间:" + (end - start));
}
}
CPU测试
import java.util.List;
public class CPUTypeTest implements Runnable {
//整体执行时间,包括在队列中等待的时间
List<Long> wholeTimeList;
//真正执行时间
List<Long> runTimeList;
private long initStartTime = 0;
/**
* 构造函数
* @param runTimeList
* @param wholeTimeList
*/
public CPUTypeTest(List<Long> runTimeList, List<Long> wholeTimeList) {
initStartTime = System.currentTimeMillis();
this.runTimeList = runTimeList;
this.wholeTimeList = wholeTimeList;
}
/**
* 判断素数
* @param number
* @return
*/
public boolean isPrime(final int number) {
if (number <= 1)
return false;
for (int i = 2; i <= Math.sqrt(number); i++) {
if (number % i == 0)
return false;
}
return true;
}
/**
* 計算素数
* @param number
* @return
*/
public int countPrimes(final int lower, final int upper) {
int total = 0;
for (int i = lower; i <= upper; i++) {
if (isPrime(i))
total++;
}
return total;
}
public void run() {
long start = System.currentTimeMillis();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
countPrimes(1, 1000000);
long end = System.currentTimeMillis();
long wholeTime = end - initStartTime;
long runTime = end - start;
wholeTimeList.add(wholeTime);
runTimeList.add(runTime);
System.out.println("单个线程花费时间:" + (end - start));
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Hello world!
*
*/
public class App {
// 初始化线程池
private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(8, 8, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1000), new ThreadPoolExecutor.DiscardOldestPolicy());
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
int cores = Runtime.getRuntime().availableProcessors();
int requestNum = 100;
System.out.println("CPU核数 " + cores);
List<Future<?>> futureList = new ArrayList<Future<?>>();
Vector<Long> wholeTimeList = new Vector<Long>();
Vector<Long> runTimeList = new Vector<Long>();
for (int i = 0; i < requestNum; i++) {
Future<?> future = threadPool.submit(new CPUTypeTest(runTimeList, wholeTimeList));
//Future<?> future = threadPool.submit(new IOTypeTest(runTimeList, wholeTimeList));
futureList.add(future);
}
for (Future<?> future : futureList) {
//获取线程执行结果
future.get(requestNum, TimeUnit.SECONDS);
}
long wholeTime = 0;
for (int i = 0; i < wholeTimeList.size(); i++) {
wholeTime = wholeTimeList.get(i) + wholeTime;
}
long runTime = 0;
for (int i = 0; i < runTimeList.size(); i++) {
runTime = runTimeList.get(i) + runTime;
}
System.out.println("平均每个线程整体花费时间: " +wholeTime/wholeTimeList.size());
System.out.println("平均每个线程执行花费时间: " +runTime/runTimeList.size());
}
}
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
/**
* Unit test for simple App.
*/
public class AppTest extends TestCase {
/**
* Create the test case
*
* @param testName name of the test case
*/
public AppTest( String testName ) {
super( testName );
}
/**
* @return the suite of tests being tested
*/
public static Test suite() {
return new TestSuite( AppTest.class );
}
/**
* Rigourous Test :-)
*/
public void testApp() {
assertTrue( true );
}
}