1. 简介
本文将介绍如何在Spring Boot应用中通过Spring Boot Actuator和Prometheus来监控任意API接口的调用耗时情况。通过Actuator提供度量metrics功能,我们能够结合AOP轻松实现API接口运行时性能指标。结合Prometheus这一强大的监控系统,我们能够实时地查看和分析API接口的调用耗时,进而评估应用的性能状况。
2. 环境准备
2.1 依赖管理
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
引入Prometheus依赖包,将所有的指标数据导出为Prometheus格式。Prometheus会通过actuator接口拉取数据。同时还会注册一个/prometheus接口。
配置文件
management:
endpoints:
web:
base-path: /ac
exposure:
include: '*'
暴露所有的端点。
3. 实战案例
本案例统计Controller接口的耗时情况,为了简单我们通过注解的方式标注需要统计的方法。
3.1 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Monitor {
// 作用是为不同的接口打标记
String[] tags() default {} ;
}
其实你还可以定义比如:指标名的属性,这样可以为不同的Controller做统计。
3.2 定义切面
该切面的作用用来拦截所有使用了@Monitor注解的Controller方法,通过环绕通知进行计时处理。
@Component
@Aspect
public class MonitorAspect {
private final MeterRegistry meterRegistry ;
// 度量名称(你可以通过注解自定义)
private static final String API_TIMER_METER_NAME = "myapp.api.timer" ;
public MonitorAspect(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry ;
}
@Pointcut("@annotation(monitor)")
private void pcMonitor(Monitor monitor) {} ;
@Around("pcMonitor(monitor)")
public Object around(ProceedingJoinPoint pjp, Monitor monitor) throws Throwable {
Timer.Sample sample = Timer.start(this.meterRegistry) ;
String[] tags = monitor.tags() ;
Object ret = null ;
Throwable ex = null ;
try {
ret = pjp.proceed() ;
} catch (Throwable th) {
ex = th ;
throw th ;
} finally {
List<String> listTags = new ArrayList<>() ;
listTags.addAll(Arrays.asList(tags)) ;
// 出现异常也会将异常名称打入tag
if (Objects.nonNull(ex)) {
listTags.add(ex.getClass().getSimpleName()) ;
}
Timer timer = meterRegistry.timer(API_TIMER_METER_NAME, listTags.toArray(new String[0])) ;
sample.stop(timer) ;
}
return ret ;
}
}
以上切面非常的简单,统计方法执行的耗时情况。
3.3 定义接口
@Service
public class UserService {
private static final List<User> DATAS = List.of(
new User(1L, "张三", "男", 22),
new User(2L, "李四", "男", 23),
new User(3L, "王五", "女", 22),
new User(4L, "赵六", "男", 32)) ;
public List<User> queryUsers() {
sleep(2000);
return DATAS ;
}
public User queryById(Long id) {
sleep(1000);
return DATAS.stream().filter(user -> user.getId() == id).findFirst().orElse(null) ;
}
private void sleep(int time) {
// 模拟耗时
try {
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(time)) ;
} catch (InterruptedException e) {}
}
}
Controller接口
@Monitor(tags = {"UserController", "list"})
@GetMapping("")
public List<User> list() {
return this.userService.queryUsers() ;
}
@Monitor(tags = {"UserController", "ById"})
@GetMapping("/{id}")
public User queryById(@PathVariable Long id) {
return this.userService.queryById(id) ;
}
注意:这里的注解属性tags必须是偶数,因为内部会通过提供的字符串tag组装成Tag对象,而Tag对象需要Key/Value。
以上是所需的所有代码,接下来进行测试。
分别访问上面的两个接口。
图片
图片
分别多次访问上面的接口,通过/ac/metrics/myapp.api.timer查看指标信息。
图片
通过Prometheus查看图形走势。
图片
通过图表方式,查看到每个接口在不同时刻的请求耗时情况。