实战如何优雅地⾃定义Prometheus监控指标
今天要和⼤家分享的是在实际⼯作中“如何优雅地⾃定义Prometheus监控指标”!⽬前⼤部分使⽤Spring
Boot构建微服务体系的公司,⼤都在使⽤Prometheus来构建微服务的度量指标(Metrics)类监控系统。⽽⼀般做法是通过在微服务应⽤中集成Prometheus指标采集SDK,从⽽使得Spring Boot暴露相关Metrics采集端点来实现。
但⼀般来说,Spring Boot默认暴露的Metrics数量及类型是有限的,如果想要建⽴针对微服务应⽤更丰富的监控维度(例如TP90/TP99分位值指标之类),那么还需要我们在Spring Boot默认已经打开的Metrics基础之上,配置Prometheus类库(micrometer-registry-prometheus)所提供的其他指标类型。
但怎么样才能在Spring Boot框架中以更优雅地⽅式实现呢?难道需要在业务代码中编写各种⾃定义监控指标代码的暴露逻辑吗?接下来的内容我们将通过@注解+AOP的⽅式来演⽰如何以更加优雅的⽅式来实现Prometheus监控指标的⾃定义!
⾃定义监控指标配置注解
需要说明的是在Spring Boot应⽤中,对程序运⾏信息的收集(如指标、⽇志),⽐较常⽤的⽅法是通过Spring的AOP代理拦截来实现,但这种拦截程序运⾏过程的逻辑多少会损耗点系统性能,因此在⾃定义Prometheus监控指标的过程中,可以将是否上报指标的选择权交给开发⼈员,⽽从易⽤性⾓度来说,可以通过注解的⽅式实现。例如:
package ics.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Tp {
String description() default "";
}
如上所⽰代码,我们定义了⼀个⽤于标注上报计时器指标类型的注解,如果想统计接⼝的想TP90、TP99这样的分位值指标,那么就可以通过该注解标注。除此之外,还可以定义上报其他指标类型的注解,例如:
package ics.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Count {
String description() default "";
}
如上所⽰,我们定义了⼀个⽤于上报计数器类型指标的注解!如果要统计接⼝的平均响应时间、接⼝的请求量之类的指标,那么可以通过该注解标注!
⽽如果觉得分别定义不同指标类型的注解⽐较⿇烦,对于某些接⼝上述各种指标类型都希望上报到Prometheus,那么也可以定义⼀个通⽤注解,⽤于同时上报多个指标类型,例如:
package ics.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Monitor {
String description() default "";
}
总之,⽆论是分开定义特定指标注解还是定义⼀个通⽤的指标注解,其⽬标都是希望以更灵活的⽅式来扩展Spring Boot微服务应⽤的监控指标类型。
⾃定义监控指标注解AOP代理逻辑实现
上⾯我们灵活定义了上报不同指标类型的注解,⽽上述注解的具体实现逻辑,可以通过定义⼀个通⽤的AOP代理类来实现,具体实现代码如下:
package ics.aop;
import ics.Metrics;
import ics.annotation.Count;
import ics.annotation.Monitor;
import ics.annotation.Tp;
import instrument.Counter;
import instrument.MeterRegistry;
import instrument.Tag;
import instrument.Tags;
import instrument.Timer;
import flect.Method;
import java.util.function.Function;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.flect.MethodSignature;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MetricsAspect {
/**
* Prometheus指标管理
*/
private MeterRegistry registry;
private Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint;
public MetricsAspect(MeterRegistry registry) {
this.init(registry, pjp -> Tags
.of(new String[]{"class", StaticPart().getSignature().getDeclaringTypeName(), "method",
}
public void init(MeterRegistry registry, Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint) {
this.tagsBasedOnJoinPoint = tagsBasedOnJoinPoint;
}
/**
* 针对@Tp指标配置注解的逻辑实现
*/
@Around("@annotation(ics.annotation.Tp)")
public Object timedMethod(ProceedingJoinPoint pjp) throws Throwable {
Method method = ((MethodSignature) Signature()).getMethod();
method = Target().getClass().Name(), ParameterTypes());
Tp tp = Annotation(Tp.class);
Timer.Sample sample = Timer.istry);
String exceptionClass = "none";
try {
return pjp.proceed();
} catch (Exception ex) {
exceptionClass = ex.getClass().getSimpleName();
throw ex;
} finally {
try {
String finalExceptionClass = exceptionClass;
//创建定义计数器,并设置指标的Tags信息(名称可以⾃定义)
Timer timer = wTimer("tp.method.timed",
builder -> builder.tags(new String[]{"exception", finalExceptionClass})
.tags(this.tagsBasedOnJoinPoint.apply(pjp)).tag("description", tp.description())
.publishPercentileHistogram().istry));
sample.stop(timer);
} catch (Exception exception) {
}
}
}
/**
* 针对@Count指标配置注解的逻辑实现
*/
function怎么记忆
@Around("@annotation(ics.annotation.Count)")
public Object countMethod(ProceedingJoinPoint pjp) throws Throwable {
Method method = ((MethodSignature) Signature()).getMethod();
method = Target().getClass().Name(), ParameterTypes());
Count count = Annotation(Count.class);
String exceptionClass = "none";
try {
return pjp.proceed();
} catch (Exception ex) {
exceptionClass = ex.getClass().getSimpleName();
throw ex;
} finally {
try {
String finalExceptionClass = exceptionClass;
//创建定义计数器,并设置指标的Tags信息(名称可以⾃定义)
Counter counter = wCounter("unted",
builder -> builder.tags(new String[]{"exception", finalExceptionClass})
.tags(this.tagsBasedOnJoinPoint.apply(pjp)).tag("description", count.description())
.istry));
counter.increment();
} catch (Exception exception) {
}
}
}
/**
* 针对@Monitor通⽤指标配置注解的逻辑实现
*/
@Around("@annotation(ics.annotation.Monitor)")
public Object monitorMethod(ProceedingJoinPoint pjp) throws Throwable {
Method method = ((MethodSignature) Signature()).getMethod();
method = Target().getClass().Name(), ParameterTypes());
Monitor monitor = Annotation(Monitor.class);
String exceptionClass = "none";
try {
return pjp.proceed();
} catch (Exception ex) {
exceptionClass = ex.getClass().getSimpleName();
throw ex;
} finally {
try {
String finalExceptionClass = exceptionClass;
//计时器Metric
Timer timer = wTimer("tp.method.timed",
builder -> builder.tags(new String[]{"exception", finalExceptionClass})
.
tags(this.tagsBasedOnJoinPoint.apply(pjp)).tag("description", monitor.description())
.publishPercentileHistogram().istry));
Timer.Sample sample = Timer.istry);
sample.stop(timer);
//计数器Metric
Counter counter = wCounter("unted",
builder -> builder.tags(new String[]{"exception", finalExceptionClass})
.tags(this.tagsBasedOnJoinPoint.apply(pjp)).tag("description", monitor.description())
.istry));
counter.increment();
} catch (Exception exception) {
}
}
}
}
上述代码完整的实现了前⾯我们定义的指标配置注解的逻辑,其中针对@Monitor注解的逻辑就是@Tp和@Count注解逻辑的整合。如果还需要定义其他指标类型,可以在此基础上继续扩展!
需要注意,在上述逻辑实现中对“Timer”及“Counter”等指标类型的构建这⾥并没有直接使⽤“micrometer-registry-prometheus”依赖包中的构建对象,⽽是通过⾃定义的wTimer()这样的⽅式实现,其主要⽤意是希望以更简洁、灵活的⽅式去实现指标的上报,其代码定义如下:
package ics;
import instrument.Counter;
import instrument.Counter.Builder;
import instrument.DistributionSummary;
import instrument.Gauge;
import instrument.MeterRegistry;
import instrument.Timer;
import lang.NonNull;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.springframework.beans.BeansException;
import t.ApplicationContext;
import t.ApplicationContextAware;
public class Metrics implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static ApplicationContext getContext() {
return context;
}
public static Counter newCounter(String name, Consumer<Builder> consumer) {
MeterRegistry meterRegistry = Bean(MeterRegistry.class);
return new CounterBuilder(meterRegistry, name, consumer).build();
}
public static Timer newTimer(String name, Consumer<Timer.Builder> consumer) {
return new Bean(MeterRegistry.class), name, consumer).build();
}
}
上述代码通过接⼊Spring容器上下⽂,获取了MeterRegistry实例,并以此来构建像Counter、Timer这样的指标类型对象。⽽这⾥之所以将获取⽅法定义为静态的,主要是便于在业务代码中进⾏引⽤!
⽽在上述代码中涉及的CounterBuilder、TimerBuilder构造器代码定义分别如下:
package ics;
import instrument.Counter;
import instrument.Counter.Builder;
import instrument.MeterRegistry;
import java.util.function.Consumer;
public class CounterBuilder {
private final MeterRegistry meterRegistry;
private Counter.Builder builder;
private Consumer<Builder> consumer;
public CounterBuilder(MeterRegistry meterRegistry, String name, Consumer<Counter.Builder> consumer) {
this.builder = Counter.builder(name);
}
public Counter build() {
consumer.accept(builder);
ister(meterRegistry);
}
}
上述代码为CounterBuilder构造器代码!TimerBuilder构造器代码如下:
package ics;
import instrument.MeterRegistry;
import instrument.Timer;
import instrument.Timer.Builder;
import java.util.function.Consumer;
public class TimerBuilder {
private final MeterRegistry meterRegistry;
private Timer.Builder builder;
private Consumer<Builder> consumer;
public TimerBuilder(MeterRegistry meterRegistry, String name, Consumer<Timer.Builder> consumer) {
this.builder = Timer.builder(name);
}
public Timer build() {
ister(meterRegistry);
}
}
之所以还特地将构造器代码单独定义,主要是从代码的优雅性考虑!如果涉及其他指标类型的构造,也可以通过类似的⽅法进⾏扩展!
⾃定义指标注解配置类
在上述代码中我们已经定义了⼏个⾃定义指标注解及其实现逻辑代码,为了使其在Spring Boot环境中运⾏,还需要编写如下配置类,代码如下:package fig;
import ics.Metrics;
import instrument.MeterRegistry;
import org.springframework.boot.ics.MeterRegistryCustomizer;
import org.springframework.dition.ConditionalOnMissingBean;
import t.annotation.Bean;
import t.annotation.Configuration;
import nv.Environment;
@Configuration
public class CustomMetricsAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MeterRegistryCustomizer<MeterRegistry> meterRegistryCustomizer(Environment environment) {
return registry -> {
monTags("application", Property("spring.application.name"));
};
}
@Bean
@ConditionalOnMissingBean
public Metrics metrics() {
return new Metrics();
}
}
上述配置代码主要是约定了上报Prometheus指标信息中所携带的应⽤名称,并对⾃定义了Metrics类进⾏了Bean配置!
业务代码的使⽤⽅式及效果
接下来我们演⽰在业务代码中如果要上报Prometheus监控指标应该怎么写,具体如下:
package ller;
import ics.annotation.Count;
import ics.annotation.Monitor;
import ics.annotation.Tp;
import itor.service.MonitorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/monitor")
public class MonitorController {
@Autowired
private MonitorService monitorServiceImpl;
//监控指标注解使⽤
//@Tp(description = "/monitor/test")
//@Count(description = "/monitor/test")
@Monitor(description = "/monitor/test")
@GetMapping("/test")
public String monitorTest(@RequestParam("name") String name) {
return "监控⽰范⼯程测试接⼝返回->OK!";
}
}
如上述代码所⽰,在实际的业务编程中就可以⽐较简单的通过注解来配置接⼝所上传的Prometheus监控指标了!此时在本地启动程序,可以通过访问微服务应⽤的“/actuator/prometheus”指标采集端点来查看相关指标,如下图所⽰:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。