
文中转载自微信公众号「无敌码农」,作者无敌码农。转截文中请联络无敌码农微信公众号。
大家好!我是"无敌码农",今日要和大伙儿共享的是在现实工作上“怎样高雅地自定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监管指标值的历程中,可以将是不是汇报指标值的决定权交到开发者,而从便捷性视角而言,可以根据注释的方法完成。例如:
packagecom.wudimanong.monitor.metrics.annotation;importjava.lang.annotation.ElementType;importjava.lang.annotation.Inherited;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inheritedpublic@interfaceTp{Stringdescription()default"";}
如上所显示编码,大家界定了一个用以标明汇报记时器指标值种类的注释,假如想统计分析插口的想TP90、TP99那样的分位值指标值,那麼就可以根据该注释标明。此外,还能够界定汇报别的指标值种类的注释,例如:
packagecom.wudimanong.monitor.metrics.annotation;importjava.lang.annotation.ElementType;importjava.lang.annotation.Inherited;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inheritedpublic@interfaceCount{Stringdescription()default"";}
如上所显示,大家界定了一个用以汇报计数种类指标值的注释!假如要统计分析插口的均值反应时间、插口的要求量之类的指标值,那麼可以利用该注释标明!
而假如感觉各自界定不一样指标值种类的注释较为不便,针对一些插口以上各种各样指标值种类都期待汇报到Prometheus,那麼还可以理解一个通用性注释,用以与此同时汇报好几个指标值种类,例如:
packagecom.wudimanong.monitor.metrics.annotation;importjava.lang.annotation.ElementType;importjava.lang.annotation.Inherited;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inheritedpublic@interfaceMonitor{Stringdescription()default"";}
总而言之,不论是分离界定特殊指标值注释或是界定一个通用性的指标值注释,其总体目标全是期待以更灵敏的方法来拓展Spring Boot微服务架构运用的监管指标值种类。
自定监管指标值注释AOP代理商逻辑性完成
上边大家灵便界定了汇报不一样指标值种类的注释,而以上注释的主要完成逻辑性,可以根据界定一个通用性的AOP代理商类来完成,实际完成编码如下所示:
packagecom.wudimanong.monitor.metrics.aop;importcom.wudimanong.monitor.metrics.Metrics;importcom.wudimanong.monitor.metrics.annotation.Count;importcom.wudimanong.monitor.metrics.annotation.Monitor;importcom.wudimanong.monitor.metrics.annotation.Tp;importio.micrometer.core.instrument.Counter;importio.micrometer.core.instrument.MeterRegistry;importio.micrometer.core.instrument.Tag;importio.micrometer.core.instrument.Tags;importio.micrometer.core.instrument.Timer;importjava.lang.reflect.Method;importjava.util.function.Function;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.reflect.MethodSignature;importorg.springframework.stereotype.Component;@Aspect@ComponentpublicclassMetricsAspect{/***Prometheus指标管理*/privateMeterRegistryregistry;privateFunction<ProceedingJoinPoint,Iterable<Tag>>tagsBasedOnJoinPoint;publicMetricsAspect(MeterRegistryregistry){this.init(registry,pjp->Tags.of(newString[]{"class",pjp.getStaticPart().getSignature().getDeclaringTypeName(),"method",pjp.getStaticPart().getSignature().getName()}));}publicvoidinit(MeterRegistryregistry,Function<ProceedingJoinPoint,Iterable<Tag>>tagsBasedOnJoinPoint){this.registry=registry;this.tagsBasedOnJoinPoint=tagsBasedOnJoinPoint;}/***对于@Tp指标配备注释的逻辑性完成*/@Around("@annotation(com.wudimanong.monitor.metrics.annotation.Tp)")publicObjecttimedMethod(ProceedingJoinPointpjp)throwsThrowable{Methodmethod=((MethodSignature)pjp.getSignature()).getMethod();method=pjp.getTarget().getClass().getMethod(method.getName(),method.getParameterTypes());Tptp=method.getAnnotation(Tp.class);Timer.Samplesample=Timer.start(this.registry);StringexceptionClass="none";try{returnpjp.proceed();}catch(Exceptionex){exceptionClass=ex.getClass().getSimpleName();throwex;}finally{try{StringfinalExceptionClass=exceptionClass;//建立界定计数,并设定指标的Tags信息内容(名字可以自定)Timertimer=Metrics.newTimer("tp.method.timed",builder->builder.tags(newString[]{"exception",finalExceptionClass}).tags(this.tagsBasedOnJoinPoint.apply(pjp)).tag("description",tp.description()).publishPercentileHistogram().register(this.registry));sample.stop(timer);}catch(Exceptionexception){}}}/***对于@Count指标配备注释的逻辑性完成*/@Around("@annotation(com.wudimanong.monitor.metrics.annotation.Count)")publicObjectcountMethod(ProceedingJoinPointpjp)throwsThrowable{Methodmethod=((MethodSignature)pjp.getSignature()).getMethod();method=pjp.getTarget().getClass().getMethod(method.getName(),method.getParameterTypes());Countcount=method.getAnnotation(Count.class);StringexceptionClass="none";try{returnpjp.proceed();}catch(Exceptionex){exceptionClass=ex.getClass().getSimpleName();throwex;}finally{try{StringfinalExceptionClass=exceptionClass;//建立界定计数,并设定指标值的Tags信息内容(名字可以自定)Countercounter=Metrics.newCounter("count.method.counted",builder->builder.tags(newString[]{"exception",finalExceptionClass}).tags(this.tagsBasedOnJoinPoint.apply(pjp)).tag("description",count.description()).register(this.registry));counter.increment();}catch(Exceptionexception){}}}/***对于@Monitor通用性指标值配备注释的逻辑性完成*/@Around("@annotation(com.wudimanong.monitor.metrics.annotation.Monitor)")publicObjectmonitorMethod(ProceedingJoinPointpjp)throwsThrowable{Methodmethod=((MethodSignature)pjp.getSignature()).getMethod();method=pjp.getTarget().getClass().getMethod(method.getName(),method.getParameterTypes());Monitormonitor=method.getAnnotation(Monitor.class);StringexceptionClass="none";try{returnpjp.proceed();}catch(Exceptionex){exceptionClass=ex.getClass().getSimpleName();throwex;}finally{try{StringfinalExceptionClass=exceptionClass;//记时器MetricTimertimer=Metrics.newTimer("tp.method.timed",builder->builder.tags(newString[]{"exception",finalExceptionClass}).tags(this.tagsBasedOnJoinPoint.apply(pjp)).tag("description",monitor.description()).publishPercentileHistogram().register(this.registry));Timer.Samplesample=Timer.start(this.registry);sample.stop(timer);//计数MetricCountercounter=Metrics.newCounter("count.method.counted",builder->builder.tags(newString[]{"exception",finalExceptionClass}).tags(this.tagsBasedOnJoinPoint.apply(pjp)).tag("description",monitor.description()).register(this.registry));counter.increment();}catch(Exceptionexception){}}}}
以上编码详细的达到了之前大家界定的指标值配备注释的逻辑性,在其中对于@Monitor注释的逻辑性便是@Tp和@Count注释逻辑性的融合。假如还必须界定别的指标值种类,可以在这个基础上再次拓展!
必须留意,在以上逻辑性完成中对“Timer”及“Counter”等指标值种类的搭建这儿并没同时应用“micrometer-registry-prometheus”依赖包中的搭建目标,反而是根据自定的Metrics.newTimer()那样的方法完成,其关键意义是想要以更简约、灵便的形式去完成标准的汇报,其编码界定如下所示:
packagecom.wudimanong.monitor.metrics;importio.micrometer.core.instrument.Counter;importio.micrometer.core.instrument.Counter.Builder;importio.micrometer.core.instrument.DistributionSummary;importio.micrometer.core.instrument.Gauge;importio.micrometer.core.instrument.MeterRegistry;importio.micrometer.core.instrument.Timer;importio.micrometer.core.lang.NonNull;importjava.util.function.Consumer;importjava.util.function.Supplier;importorg.springframework.beans.BeansException;importorg.springframework.context.ApplicationContext;importorg.springframework.context.ApplicationContextAware;publicclassMetricsimplementsApplicationContextAware{privatestaticApplicationContextcontext;@OverridepublicvoidsetApplicationContext(@NonNullApplicationContextapplicationContext)throwsBeansException{context=applicationContext;}publicstaticApplicationContextgetContext(){returncontext;}publicstaticCounternewCounter(Stringname,Consumer<Builder>consumer){MeterRegistrymeterRegistry=context.getBean(MeterRegistry.class);returnnewCounterBuilder(meterRegistry,name,consumer).build();}publicstaticTimernewTimer(Stringname,Consumer<Timer.Builder>consumer){returnnewTimerBuilder(context.getBean(MeterRegistry.class),name,consumer).build();}}
以上编码根据连接Spring器皿前后文,获得了MeterRegistry案例,并借此来搭建像Counter、Timer那样的指标值种类目标。而这儿往往将获得方式界定为静态数据的,主要是有利于在项目编码中开展引入!
而在以上编码中涵盖的CounterBuilder、TimerBuilder构造器编码界定各自如下所示:
packagecom.wudimanong.monitor.metrics;importio.micrometer.core.instrument.Counter;importio.micrometer.core.instrument.Counter.Builder;importio.micrometer.core.instrument.MeterRegistry;importjava.util.function.Consumer;publicclassCounterBuilder{privatefinalMeterRegistrymeterRegistry;privateCounter.Builderbuilder;privateConsumer<Builder>consumer;publicCounterBuilder(MeterRegistrymeterRegistry,Stringname,Consumer<Counter.Builder>consumer){this.builder=Counter.builder(name);this.meterRegistry=meterRegistry;this.consumer=consumer;}publicCounterbuild(){consumer.accept(builder);returnbuilder.register(meterRegistry);}}
以上编码为CounterBuilder构造器代码!TimerBuilder构造器编码如下所示:
packagecom.wudimanong.monitor.metrics;importio.micrometer.core.instrument.MeterRegistry;importio.micrometer.core.instrument.Timer;importio.micrometer.core.instrument.Timer.Builder;importjava.util.function.Consumer;publicclassTimerBuilder{privatefinalMeterRegistrymeterRegistry;privateTimer.Builderbuilder;privateConsumer<Builder>consumer;publicTimerBuilder(MeterRegistrymeterRegistry,Stringname,Consumer<Timer.Builder>consumer){this.builder=Timer.builder(name);this.meterRegistry=meterRegistry;this.consumer=consumer;}publicTimerbuild(){this.consumer.accept(builder);returnbuilder.register(meterRegistry);}}
往往还特意将构造器编码独立界定,关键是以编码的雅致性考虑到!假如涉及到别的指标值种类的结构,还可以根据相似的方式 开展拓展!
自定指标值注释配备类
在以上编码中大家早已界定了好多个自定指标值注释以及完成逻辑性编码,为了更好地使其在Spring Boot环境中运行,还必须撰写如下所示配备类,编码如下所示:
packagecom.wudimanong.monitor.metrics.config;importcom.wudimanong.monitor.metrics.Metrics;importio.micrometer.core.instrument.MeterRegistry;importorg.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;importorg.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.core.env.Environment;@ConfigurationpublicclassCustomMetricsAutoConfiguration{@Bean@ConditionalOnMissingBeanpublicMeterRegistryCustomizer<MeterRegistry>meterRegistryCustomizer(Environmentenvironment){returnregistry->{registry.config().commonTags("application",environment.getProperty("spring.application.name"));};}@Bean@ConditionalOnMissingBeanpublicMetricsmetrics(){returnnewMetrics();}}
以上配备编码主要是承诺了汇报Prometheus指标值信息内容中所随身携带的运用名字,并对自定了Metrics类开展了Bean配备!
业务流程编码的应用方法及实际效果
下面大家演试在项目编码中假如要汇报Prometheus监管指标值应该怎么写,详细如下:
packagecom.wudimanong.monitor.controller;importcom.wudimanong.monitor.metrics.annotation.Count;importcom.wudimanong.monitor.metrics.annotation.Monitor;importcom.wudimanong.monitor.metrics.annotation.Tp;importcom.wudimanong.monitor.service.MonitorService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/monitor")publicclassMonitorController{@AutowiredprivateMonitorServicemonitorServiceImpl;//监管指标值注释应用//@Tp(description="/monitor/test")//@Count(description="/monitor/test")@Monitor(description="/monitor/test")@GetMapping("/test")publicStringmonitorTest(@RequestParam("name")Stringname){monitorServiceImpl.monitorTest(name);return"监管精品工程测试接口回到->OK!";}}
如以上编码所显示,在具体的业务流程程序编写中就可以非常简单的根据注释来配备插口所提交的Prometheus监管指标值了!这时在当地运行程序流程,可以根据浏览微服务架构运用的“/actuator/prometheus”指标值收集节点来查询有关指标值,如下图所示:

拥有这种自定汇报的监管指标值,那麼Promethues在收集后,大家就可以根据像Grafana那样的数据分析工具,来搭建起多层次页面友善地监管主视图了,例如以TP90/TP99为例子:

如上所显示,在Grafana中可以与此同时界定好几个PromeQL来定为不一样的监管指标值信息内容,这儿大家各自根据Prometheus所供应的“histogram_quantile”函数公式统计分析了插口方式“monitorTest”的TP90及TP95分位值!而所应用的指数便是自定的“tp_method_timed_xx”指标值种类!
续篇
以上便是我近期在工作上封装形式的一组有关Prometheus自定监管指标值的SDK编码,在现实工作上可以将其封死为Spring Boot Starter依靠的方式,进而更强的被Spring Boot新项目集成化!至此我已经不遗余力的将近期二天的工作成效共享给大伙儿了,也期待诸位朋友可以多多的关注点赞适用,多多的分享散播!