本文将介绍如何在Spring Boot应用中通过Spring Boot Actuator和Prometheus来监控任意API接口的调用耗时情况。通过Actuator提供度量metrics功能,我们能够结合AOP轻松实现API接口运行时性能指标。结合Prometheus这一强大的监控系统,我们能够实时地查看和分析API接口的调用耗时,进而评估应用的性能状况。
<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:'*'
暴露所有的端点。
本案例统计Controller接口的耗时情况,为了简单我们通过注解的方式标注需要统计的方法。
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public@interfaceMonitor {// 作用是为不同的接口打标记String[]tags()default{};}
其实你还可以定义比如:指标名的属性,这样可以为不同的Controller做统计。
该切面的作用用来拦截所有使用了@Monitor注解的Controller方法,通过环绕通知进行计时处理。
@Component@Aspectpublicclass MonitorAspect { private final MeterRegistry meterRegistry;// 度量名称(你可以通过注解自定义)private static final String API_TIMER_METER_NAME="myapp.api.timer";publicMonitorAspect(MeterRegistry meterRegistry){ this.meterRegistry=meterRegistry;}@Pointcut("@annotation(monitor)")private void pcMonitor(Monitor monitor){};@Around("pcMonitor(monitor)")publicObject 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));// 出现异常也会将异常名称打入tagif(Objects.nonNull(ex)){ listTags.add(ex.getClass().getSimpleName());} Timer timer=meterRegistry.timer(API_TIMER_METER_NAME,listTags.toArray(new String[0]));sample.stop(timer);}returnret;} }
以上切面非常的简单,统计方法执行的耗时情况。
@Servicepublicclass UserService { private static final List<User>DATAS=List.of(newUser(1L,"张三","男",22),newUser(2L,"李四","男",23),newUser(3L,"王五","女",22),newUser(4L,"赵六","男",32));publicList<User>queryUsers(){ sleep(2000);returnDATAS;}publicUserqueryById(Long id){ sleep(1000);returnDATAS.stream().filter(user->user.getId()==id).findFirst().orElse(null);} private void sleep(inttime){// 模拟耗时try { TimeUnit.MILLISECONDS.sleep(new Random().nextInt(time));} catch(InterruptedException e){} } }
@Monitor(tags={"UserController","list"})@GetMapping("")publicList<User>list(){returnthis.userService.queryUsers();}@Monitor(tags={"UserController","ById"})@GetMapping("/{id}")publicUserqueryById(@PathVariableLong id){returnthis.userService.queryById(id);}
注意:这里的注解属性tags必须是偶数,因为内部会通过提供的字符串tag组装成Tag对象,而Tag对象需要Key/Value。
以上是所需的所有代码,接下来进行测试。
分别访问上面的两个接口。
图片
图片
分别多次访问上面的接口,通过/ac/metrics/myapp.api.timer查看指标信息。
图片
通过Prometheus查看图形走势。
图片
通过图表方式,查看到每个接口在不同时刻的请求耗时情况。