之前对压测的认识比较肤浅,以为就是一个循环次数很大的for循环。
接下来的程序,可以分为四个部分(本例中业务线程只有写线程组,也可以继续添加其它业务线程):
- 写线程组
- 计时线程
- 中间结果打印线程
- 主函数
代码中没写import。MetricRegistry, Snapshot, Timer类来自com.codahale.metrics。
CountDownLatch和AtomicBoolean来自java.util.concurrent。
Logger和LoggerFactory来自org.slf4j。
首先是最重要的计时线程,其中最重要的是isStop变量,这个变量会被所有业务线程和主线程共享,起到全局控制作用。
public class TimeController implements Runnable {
private waitSeconds;
public static AtomicBoolean isStop = new AtomicBoolean(false);//初始值
public TimeController(long waitSeconds) {
if(waitSeconds == 0) {
waitSeconds = Long.MAX_VALUE;
}
}
@Override
public void run() {
while(waitSeconds > 0) {
if(isStop.get() == true) {
//提示信息
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//提示信息
}
waitSeconds--;
}
isStop.compareAndSet(false, true);//第一个参数是expect,第二个参数是update
}
}
下面是写线程组中的写线程,可以替换为你所需要压力测试的代码。
public class WriteThread implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(WriteThread.class);
int threadNum;
int yourLoopTimes;//为0时代表无限循环
Timer timer;
public WriteThread(int threadNum, int yourLoopTimes, Timer timer) {
//略
}
@Override
public void run() {
int cnt = 0;
if(yourLoopTimes == 0) {
while(true) {
if(TimeController.isStop.get() == true) {
YourMain.waitThread.countDown();
return;//唯一的退出方法就是等时限结束
}
Timer.Context ctx = timer.time();
//
//你需要计时的业务逻辑
//
ctx.stop();
}
}
while(cnt < yourLoopTimes) {
cnt++;
if(TimeController.isStop.get() == true) {
YourMain.waitThread.countDown();
return;
}
Timer.Context ctx = timer.time();
//
//你需要计时的业务逻辑
//
ctx.stop();
}
}
}
下面是打印线程。
public class MetricsPrintController implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(MetricsPrintController.class);
long printInterval;
//此处应该包含所有业务线程的timer。当然,如果有的业务线程不需要输出中间测量结果,也可以不加在这
Timer writeTimer;
public MetricsPrintController(long printInterval, Timer writeTimer) {
//略
}
@Override
public void run(){
if(printInterval<=0){
return;
}
while(true) {
if(TimerController.isStop.get() == true) {
break;
}
try {
Thread.sleep(printInterval);
if(TimerController.isStop.get() == true) {
break;
}
} catch (InterruptedException e) {
//你的异常处理
}
Snapshot writeSnapshot = writeTimer.getSnapshot();
System.out.println(writeTimer.getMeanRate());
System.out.println(writeSnapshot.get99thPercentile()/(1000*1000));
}
}
}
下面是主函数。
public class YourMain {
private static final Logger LOG = LoggerFactory.getLogger(YourMain.class);
public static Timer makeTimer(Class<?> class, String name) {
MetricRegistry registry = new MetricRegistry();
Timer timer = registry.timer(MetricRegistry.name(klass, name));
}
public static void main(String[] args) {
//main的输入参数处理,略
//所有业务线程数之和,比如读线程组大小+写线程组大小,不包括计时线程和打印线程
int sumOfYourExecuteThread = 1234567;//正常来说是由main参数传入
int yourLoopTimes = 1234567;//正常来说是由main参数传入
int yourTestTime = 1234567;//正常来说是由main参数传入
int writeThreadNum = 1234567;//正常来说是由main参数传入
int printInterval = 1234567;//正常来说是由main参数传入
//
//如果还有其它业务线程组,此处每个业务线程组都要有一个Timer
Timer writeTimer = makeTimer(YourMain.class, "yourTimerName");
waitThread = new CountDownLatch(sumOfYourExecuteThread);
//-----------------------------------------------------------------------------
//启动写线程
for(int i = 0; i < writeThreadNum; i++){
WriteThread yourWriteThreadName = new WriteThread(i, yourLoopTimers, writeTimer);
Thread thread = new Thread(yourWriteThreadName, "yourWriteThreadName-" + i);
thread.start();
}
//-----------------------------------------------------------------------------
//启动计时线程
TimeController timerControllerThread = new TimeController(yourTestTime);
THread thread = new Thread(timerControllerThread, "timerControllerThread ");
thread.start();
//-----------------------------------------------------------------------------
//启动打印线程,传入所有业务线程组的timer
MetricsPrintController metricsPrintTHread = new MetricsPrintController(printInterval ,writeTimer);
Thread metricPrintThread = new Thread(metricsPrintThread, "metricsPrintThread");
metricPrintThread.start();
//-----------------------------------------------------------------------------
try {
waitTHread.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果以下语句顺利执行,说明所有业务线程通过次数结束了,以下语句顺利执行后计时线程和打印线程也会停止。如果计时线程先结束,则以下语句不会顺利执行。
boolean res = isStop.compareAndSet(false, true);//expect, update
//-----------------------------------------------------------------------------
Snapshot snapshot = writeTimer.getSnapshot();
System.out.println(writeTimer.getMeanRate());
System.out.println(snapshot.get99thPercentile()/(1000*1000));
}
}
附录
压测除了计时,更重要的其实是观察硬件资源使用,否则就只是普通的性能测试。
cpu指标
- cpu.busy
- cpu.iowait
- cpu.irq
- cpu.softirq
- cpu.switches
- cpu.system
- cpu.user
mem use指标
- mem.memused
- mem.memused.percent
- mem.swapused
- mem.swapused.percent
net指标
如果是向外发送的话,主要看net.if.out.bytes
内存使用分析
注意jmap会导致java进程挂起,线上环境慎用!
内存分析工具mat安装和使用教程:http://www.moheqionglin.com/site/blogs/84/detail.html
wget http://eclipse.stu.edu.tw/mat/1.9.0/rcp/MemoryAnalyzer-1.9.0.20190605-linux.gtk.x86_64.zip
jmap -dump:format=b,file=jmap.info PID
./ParseHeapDump.sh jmap.info org.eclipse.mat.api:suspects org.eclipse.mat.api:overview org.eclipse.mat.api:top_components
结果中会揭示最大的类,最大的对象,以及类加载器相关的信息。
最大的对象可能是一个容器类如map,最大的类可能是容器类存储的那个类。
不同类加载器的资源消耗会被统计,类如果被不同类加载器重复加载,也会被发现。
有多少软引用?有多少弱引用?有多少对象实现了finalize方法?map的碰撞率如何?
至于内存浪费,考虑到重复字符串,空集合和低填充率集合。