1. 概述
性能测试往往被安排在软件开发周期的后期阶段,我们通常依赖 Java Profiler 来排查性能瓶颈。
在本教程中,我们将介绍 SPF4J(Simple Performance Framework for Java)。它提供了一套可以直接嵌入代码的 API,使得性能监控成为组件开发中不可或缺的一部分,从而实现早期性能问题的发现与定位。
2. 指标采集与可视化基础概念
在深入 SPF4J 之前,我们先通过一个简单的例子来理解指标采集与可视化的概念。
假设我们要监控某款新上线 App 在应用商店的下载量。为了便于理解,我们可以手动模拟这个过程。
2.1 指标采集
首先,我们需要确定要测量的内容。我们关心的是 下载量/分钟,因此我们会记录每分钟的下载次数。
其次,采集频率是多少?我们决定 每分钟采集一次。
最后,监控持续多久?我们设定为 持续监控一小时。
根据这些规则,我们开始实验,结束后可以得到如下数据:
时间 Cumulative 下载量 下载量/分钟
----------------------------------------------
T 497 0
T+1 624 127
T+2 676 52
...
T+14 19347 17390
T+15 19427 80
...
T+22 27195 7350
...
T+41 41321 11885
...
T+60 43395 40
前两列(时间、累计下载量)是直接观测到的值。第三列(下载量/分钟)是通过当前与前一时间点的差值得出的。
2.2 指标可视化
我们可以绘制一个时间 vs 下载量/分钟的线性图:
图中可以看到一些峰值,但由于 Y 轴使用线性刻度,低值部分看起来像是直线。
我们换用对数刻度(base 10)来绘制 log/linear 图:
此时我们能更清晰地看到低值部分,它们大致在 100 左右。线性图的平均值为 703,是因为包含了峰值。
如果我们把峰值视为异常值剔除,从 log/linear 图中可以得出结论:
✅ 平均每分钟下载量约为 100 次
3. 方法调用的性能监控
理解了如何采集和分析简单指标后,我们将其应用到一个 Java 方法中:isPrimeNumber()
。
private static boolean isPrimeNumber(long number) {
for (long i = 2; i <= number / 2; i++) {
if (number % i == 0)
return false;
}
return true;
}
SPF4J 提供了两种方式来采集指标,我们将在下文中详细介绍。
4. 环境搭建与配置
4.1 Maven 依赖配置
SPF4J 提供多个模块,我们仅需几个核心模块即可完成实验。
- spf4j-core:核心库,提供基本功能
- spf4j-aspects:基于 AspectJ 的性能监控
- spf4j-ui:图形化界面用于数据可视化
<dependency>
<groupId>org.spf4j</groupId>
<artifactId>spf4j-core</artifactId>
<version>8.6.10</version>
</dependency>
<dependency>
<groupId>org.spf4j</groupId>
<artifactId>spf4j-aspects</artifactId>
<version>8.6.10</version>
</dependency>
<dependency>
<groupId>org.spf4j</groupId>
<artifactId>spf4j-ui</artifactId>
<version>8.6.10</version>
</dependency>
4.2 输出文件配置
SPF4J 支持写入时间序列数据库(TSDB)和文本文件,我们配置如下:
public static void initialize() {
String tsDbFile = System.getProperty("user.dir") + File.separator + "spf4j-performance-monitoring.tsdb2";
String tsTextFile = System.getProperty("user.dir") + File.separator + "spf4j-performance-monitoring.txt";
LOGGER.info("\nTime Series DB (TSDB) : {}\nTime Series text file : {}", tsDbFile, tsTextFile);
System.setProperty("spf4j.perf.ms.config", "TSDB@" + tsDbFile + "," + "TSDB_TXT@" + tsTextFile);
}
4.3 Recorder 与 Source
SPF4J 核心能力是记录、聚合和保存指标,无需后期处理。主要类包括:
MeasurementRecorder
:可手动调用记录指标MeasurementRecorderSource
:用于注解方式记录指标
工厂类 RecorderFactory
提供了多种聚合方式:
createScalableQuantizedRecorder()
/Source
createScalableCountingRecorder()
/Source
createScalableMinMaxAvgRecorder()
/Source
createDirectRecorder()
/Source
我们选择 ScalableQuantized
聚合方式。
4.4 创建 Recorder
public static MeasurementRecorder getMeasurementRecorder(Object forWhat) {
String unitOfMeasurement = "ms";
int sampleTimeMillis = 1_000;
int factor = 10;
int lowerMagnitude = 0;
int higherMagnitude = 4;
int quantasPerMagnitude = 10;
return RecorderFactory.createScalableQuantizedRecorder(
forWhat, unitOfMeasurement, sampleTimeMillis, factor, lowerMagnitude,
higherMagnitude, quantasPerMagnitude);
}
参数说明:
unitOfMeasurement
:单位,如ms
sampleTimeMillis
:采样频率factor
:绘图使用的对数底数lowerMagnitude
:对数最小值(如 0 表示 10^0)higherMagnitude
:对数最大值(如 4 表示 10^4)quantasPerMagnitude
:每个量级的细分区间数
4.5 创建 Source
public static final class RecorderSourceForIsPrimeNumber extends RecorderSourceInstance {
public static final MeasurementRecorderSource INSTANCE;
static {
Object forWhat = App.class + " isPrimeNumber";
String unitOfMeasurement = "ms";
int sampleTimeMillis = 1_000;
int factor = 10;
int lowerMagnitude = 0;
int higherMagnitude = 4;
int quantasPerMagnitude = 10;
INSTANCE = RecorderFactory.createScalableQuantizedRecorderSource(
forWhat, unitOfMeasurement, sampleTimeMillis, factor,
lowerMagnitude, higherMagnitude, quantasPerMagnitude);
}
}
4.6 配置类封装
public class Spf4jConfig {
public static void initialize() { /* ... */ }
public static MeasurementRecorder getMeasurementRecorder(Object forWhat) { /* ... */ }
public static final class RecorderSourceForIsPrimeNumber extends RecorderSourceInstance { /* ... */ }
}
4.7 配置 aop.xml
使用 AspectJ 实现注解式性能监控,需配置 aop.xml
文件:
<aspectj>
<aspects>
<aspect name="org.spf4j.perf.aspects.PerformanceMonitorAspect" />
</aspects>
<weaver options="-verbose">
<include within="com..*" />
<include within="org.spf4j.perf.aspects.PerformanceMonitorAspect" />
</weaver>
</aspectj>
将该文件放在 META-INF
目录下。
5. 使用 MeasurementRecorder
5.1 指标记录
生成 100 个随机数并循环调用 isPrimeNumber()
,使用 MeasurementRecorder
记录耗时:
Spf4jConfig.initialize();
MeasurementRecorder measurementRecorder = Spf4jConfig
.getMeasurementRecorder(App.class + " isPrimeNumber");
Random random = new Random();
for (int i = 0; i < 100; i++) {
long numberToCheck = random.nextInt(999_999_999 - 100_000_000 + 1) + 100_000_000;
long startTime = System.currentTimeMillis();
boolean isPrime = isPrimeNumber(numberToCheck);
measurementRecorder.record(System.currentTimeMillis() - startTime);
LOGGER.info("{}. {} is prime? {}", i + 1, numberToCheck, isPrime);
}
5.2 运行代码
运行后输出如下:
Time Series DB (TSDB) : E:\Projects\spf4j-core-app\spf4j-performance-monitoring.tsdb2
Time Series text file : E:\Projects\spf4j-core-app\spf4j-performance-monitoring.txt
1. 406704834 is prime? false
...
9. 507639059 is prime? true
...
...
100. 841159884 is prime? false
5.3 查看结果
运行 SPF4J UI:
java -jar target/dependency-jars/spf4j-ui-8.6.9.jar
打开 spf4j-performance-monitoring.tsdb2
文件,点击 Plot,生成以下图表:
6. 使用 MeasurementRecorderSource
6.1 指标记录(注解方式)
使用注解方式可避免手动记录时间,只需在方法上添加 @PerformanceMonitor
:
@PerformanceMonitor(
warnThresholdMillis = 1,
errorThresholdMillis = 100,
recorderSource = Spf4jConfig.RecorderSourceForIsPrimeNumber.class)
private static boolean isPrimeNumber(long number) {
// ...
}
参数说明:
warnThresholdMillis
:警告阈值(毫秒)errorThresholdMillis
:错误阈值(毫秒)recorderSource
:使用的MeasurementRecorderSource
调用代码:
Spf4jConfig.initialize();
Random random = new Random();
for (int i = 0; i < 50; i++) {
long numberToCheck = random.nextInt(999_999_999 - 100_000_000 + 1) + 100_000_000;
isPrimeNumber(numberToCheck);
}
6.2 运行代码
构建并运行时需指定 Java Agent:
java -javaagent:target/dependency-jars/aspectjweaver-1.8.13.jar -jar target/spf4j-aspects-app.jar
输出日志:
Time Series DB (TSDB) : E:\Projects\spf4j-aspects-app\spf4j-performance-monitoring.tsdb2
Time Series text file : E:\Projects\spf4j-aspects-app\spf4j-performance-monitoring.txt
[DEBUG] Execution time 0 ms for execution(App.isPrimeNumber(..)), arguments [555031768]
...
[ERROR] Execution time 2826 ms for execution(App.isPrimeNumber(..)) exceeds error threshold of 100 ms, arguments [464032213]
...
6.3 查看结果
与前一种方式相同,通过 SPF4J UI 查看结果。
7. 总结
本文介绍了指标采集与可视化的基础概念,并通过 SPF4J 实现了对 isPrimeNumber()
方法的性能监控。
我们使用了两种方式:
- ✅ 手动创建
MeasurementRecorder
并调用record()
- ✅ 使用
@PerformanceMonitor
注解实现自动监控
两种方式各有优劣,适用于不同场景。同时,我们也利用 SPF4J 自带的 UI 工具对性能数据进行了可视化展示。
SPF4J 的最大优势在于其轻量、易集成、支持多种聚合方式,并能与 AOP 无缝结合,非常适合在开发阶段就集成性能监控能力。