声明:
1.本节以Prometheus作为监控平台来监控,所以Micrometer的实现模块选择的也是Prometheus的
2.本节主要展示效果,具体写法请阅读参考文档列出的文章
一、如何在Springboot项目中使用Micrometer
spring boot actuator中配置了与Micrometer相关的自动配置,只要添加Mircrometer的具体实现模块的依赖即可,配置完成后,运行程序,即可在/actuator/prometheus
路径下看到输出的metric信息
- 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.2.1</version>
</dependency>
如果在运行时会出现找不到方法之类的异常,请适当降低依赖micrometer-registry-prometheus的版本
- 配置spring boot actuator
management:
endpoints:
web:
exposure:
include: "health,info,prometheus"
二、注册表
Micrometer中核心两个内容便是meter和registry,注册表用于将各种meter注册到自身中进行管理,而meter就是各种监控的工具,比如计数器,计量器,计时器等等。不同的监控系统会有不同的注册表实现,其基类都是MeterRegistry,对应于Prometheus的注册表是PrometheusMeterRegistry。
Spring boot中只要你配置好了上节所述的内容后,就会自动生成一个MeterRegistry类型的bean,你只需要在需要使用它的地方注入即可,如果你需要自定义MeterRegistry的各种配置,比如公共标签(所有该项目的监控信息都会加上一个相同的标签)等等,你可以配置一个类型为MeterRegistryCustomizer<MeterRegistry>的bean,来进行一些自定义的操作:
@Bean
@ConditionalOnBean({MtkMeterRegistryAutoConfiguration.class})
@Autowired
public MeterRegistryCustomizer<MeterRegistry> meterRegistryCustomizer(MtkMeterRegistryConfiguration mtkMeterRegistryConfiguration){
return mtkMeterRegistryConfiguration::apply;
}
三、计数器
计数器用来表示计数类监控项目,比如“控制器的访问次数”,“方法的调用次数”等等,这类监控信息都是只增不减的,且和次数有关。
- 创建一个计数器,对访问页面"/meter/greet"的次数进行计数
- 控制器
/** * 每访问一次该页面,green_count计数器+1 */ @RequestMapping("/greet") public String greet(){ return "greet count: " + metricTemplate.counterAdd("greet.count"); }
- MetricTemplate#counterAdd
public double counterAdd(String counterName , String... tags){ return counterAdd(counterName , 1 , tags); } public double counterAdd(String counterName , double increment ,String... tags){ Counter counter = meterRegistry.counter(counterName, tags); counter.increment(increment); return counter.count(); }
- 访问
localhost:8080/meter/greet
5次 - 访问
localhost:8080/actuator/prometheus
搜索"greet_count"
# HELP greet_count_total # TYPE greet_count_total counter greet_count_total{project="mtk-micrometer",} 5.0
四、计量器
计量器用于持续计量类的任务,比如“集合长度”、“加载了类的个数”、“最大访问时间”等等。
- 创建一个计量器,对一个集合的长度进行计量,每访问
/meter/random
一次,这个集合长度就会加一- 控制器
@Autowired public MeterTest(MetricTemplate metricTemplate){ this.metricTemplate = metricTemplate; //计量器初始化 this.metricTemplate.gaugeSet("random.list.gauge" , target -> ((List)target).size() , randomIntList); } /** *每访问一次,生成一个随机值,并存入randomIntList * @return 返回随机产生的值 */ @RequestMapping("/random") public int randomNumber(){ int randomNumber = (int) (Math.random() * 100); randomIntList.add(randomNumber); return randomNumber; }
- MetricTemplate#gaugeSet
public void gaugeSet(String gaugeName , ToDoubleFunction<Object> f , Object target , String... tags){ ArrayList<Tag> tagList = new ArrayList<>(); if(tags.length %2 == 1) throw new IllegalArgumentException("Tags length is not correctly, length: " + tags.length); for(int i = 0 ; i < tags.length ; i+=2){ tagList.add(Tag.of(tags[i] , tags[i+1])); } meterRegistry.gauge(gaugeName , tagList , target , f); }
- 访问
localhost:8080/meter/random
- 访问
localhost:8080/actuator/prometheus
搜索"random_list_gauge"
# HELP random_list_gauge # TYPE random_list_gauge gauge random_list_gauge{project="mtk-micrometer",} 5.0
- Tips
- 不要用计量器去测量没有测量上下限的玩意儿
- 不要用计量器做计数器做的事
- 计量器中对测量对象的引用使用的都是弱引用(
WeakReference
),所以不会影响到垃圾收集,但因此若是目标对象被垃圾收集了,则计量器的显示结果将会是NaN
- 不要用计量器去计量装箱类型的数值,因为它们是不可变的,尝试去重新为新值注册同名计量器也是不允许的,因为注册表只可存在一份同(名、标签)的Meter
五、计时器
计时器用于计时类监控,比如“某个线程的执行时间”,“某个操作的执行时间”。计时器有两种,一种是普通计时器,一种是长任务计时器,前者会在计时的任务结束后将计时器注册到注册表中,后者则可以实时显示任务执行了多久,即使任务还没执行完。需要注意的是,在使用record(Runnable runnable)
方法对Runnable进行计时时,并不会去启动一个线程,而是执行Runnable中的run方法,并对该方法的执行时间进行计时。
- 创建一个计时器,该计时器会对一个新启动的线程进行计时,该线程一共运行5s,访问
/meter/timer
就会触发- 控制器
/** * 访问该页面后会启动一个执行5秒的线程,并用timer计算执行的时间 */ @RequestMapping("/timer") public void timerTest(){ Meter.Id meterIdentity = metricTemplate.startTimer("timer.test"); new Thread(() -> { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } metricTemplate.stopTimer(meterIdentity); }).start(); }
- MetricTemplate#startTimer和MetricTemplate#stopTimer
public Meter.Id startTimer(String timerName , String... tags){ Tags meterTags = Tags.of(tags); Meter.Id meterIdentity = new Meter.Id(timerName , meterTags , null , null , null); Timer.Sample sample = Timer.start(); sampleMap.put(meterIdentity , sample); return meterIdentity; } public void stopTimer(Meter.Id meterIdentity){ Timer.Sample sample = sampleMap.remove(meterIdentity); sample.stop(meterRegistry.timer(meterIdentity.getName() , meterIdentity.getTags())); }
- 访问
localhost:8080/meter/timer
触发线程 - 5秒后,访问
localhost:8080/meter/prometheus
搜索"timer_test"
# HELP timer_test_seconds # TYPE timer_test_seconds summary timer_test_seconds_count{project="mtk-micrometer",} 1.0 timer_test_seconds_sum{project="mtk-micrometer",} 5.017332 # HELP timer_test_seconds_max # TYPE timer_test_seconds_max gauge timer_test_seconds_max{project="mtk-micrometer",} 5.017332
- 使用
@Timed
注解快速为一个方法设定时间监控- 由于
@Timed
是基于Aop的,故你需要有AspectJ
依赖,而且@Timed注解默认是无效的,你需要添加一个timedAspect
来开启这个功能
@Bean @Autowired @ConditionalOnMissingBean public TimedAspect timedAspect(MeterRegistry meterRegistry){ return new TimedAspect(); }
- 然后你就可以在任意方法上使用
@Timed
注解来开启时间监控功能了
@Timed public void sleep5SecondsFunc(){ try { Thread.currentThread().sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } }
- 结果:
# HELP method_timed_seconds # TYPE method_timed_seconds summary method_timed_seconds_count{class="cn.mutsuki.micrometer.service.ExampleService",exception="none",method="sleep5SecondsFunc",project="mtk-micrometer",} 1.0 method_timed_seconds_sum{class="cn.mutsuki.micrometer.service.ExampleService",exception="none",method="sleep5SecondsFunc",project="mtk-micrometer",} 5.0255475 # HELP method_timed_seconds_max # TYPE method_timed_seconds_max gauge method_timed_seconds_max{class="cn.mutsuki.micrometer.service.ExampleService",exception="none",method="sleep5SecondsFunc",project="mtk-micrometer",} 5.0255475
- 由于
六、Meter的命名
监控系统会根据得到的监控信息的名称来进行一定的分析,所以如果你的命名毫无章法,那么可能会削弱一些监控的性能,Micrometer通过一个名为NamingConvention
的接口来规定命名的规则。比如你在程序中使用了java.jvm.memory
来命名一个Meter,而你使用的NamingConvention
的实现类是针对于Prometheus的,那么监控信息的输出结果上,该Meter对应的信息的名字会是java_jvm_memory
。Micrometer默认的命名规则,是以句点进行分割。下面给出同一个名字在不同监控系统的本地化命名:
原命名:
http.server.requests
Prometheus :http_server_requests_duration_seconds
Atlas :httpServerRequests
Graphite :http.server.requests
InfluxDB :http_server_requests
七、Meter过滤器
Meter过滤器会根据一定的规则来判断一个Meter是否应该被注入到注册表中,以及何时注入到注册表中,也可以用来对匹配的Meter进行一些额外的配置,比如添加前缀等等。除此之外,Timer
和DistributionSummary
这两种Meter还包含了额外的统计数据的相关配置,这也可以使用过滤器来实现一次性的配置。
- 如何创建一个过滤器?
创建一个过滤器有两种方式,第一种是创建实现MeterFilter
接口的类,第二种是使用MeterFilter
自带的多种静态方法。 -
MeterFilter
中核心的几个方法如下:-
MeterFilterReply accept(Id id)
: 根据输入的Meter.Id
来判断该Meter注入到注册表的策略 -
Id map(Id id)
: 根据输入的Meter.Id
来返回一个新的Meter.Id
代替旧的 -
DistributionStatisticConfig configure(Id id, DistributionStatisticConfig config)
: 统计数据的相关配置(Timer
和DistributionSummary
适用)
-
-
MeterFilterReply
是一个枚举,用来表示注册的策略,它一共有三种值:-
DENY
不允许该Meter注册 -
NEUTRAL
如果没有其他过滤器返回DENY
则注册 -
ACCEPT
立即注册,不用管其后是否有其他的过滤器
-
-
MeterFilter
提供的一些易用的方法-
MeterFilter accept()
返回具有通过规则的过滤器 -
MeterFilter deny()
返回具有拒绝规则的过滤器 -
MeterFilter accept(final Predicate<Id> iff)
/MeterFilter deny(final Predicate<Id> iff)
根据Predicate
语句的结果来是通过/拒绝(返回true),还是中立(返回false) -
MeterFilter denyUnless(final Predicate<Id> iff)
就是MeterFilter deny(final Predicate<Id> iff)
结果的反过来 -
MeterFilter commonTags(final Iterable<Tag> tags)
给所有Meter添加公共的标签 -
acceptNameStartsWith(String prefix)
/denyNameStartsWith(String prefix)
根据Meter的名字前缀来判断是通过还是拒绝 -
MeterFilter maximumAllowableMetrics(final int maximumTimeSeries)
如果注册的Meter达到参数指定的上限,则无法注册 -
MeterFilter renameTag(final String meterNamePrefix, final String fromTagKey, final String toTagKey)
如果Meter的名字是以meterNamePrefix
为前缀,且标签中有fromTagKey
的标签,则将该标签改为toTagKey
,标签对应的值保持不变 -
MeterFilter ignoreTags(final String... tagKeys)
将所有Meter的与tagKeys
匹配的标签去除掉
-
-
MeterFilter
可以串起来使用,如下例是个白名单的应用示例:
registry.config()
.meterFilter(MeterFilter.acceptNameStartsWith("http"))
.meterFilter(MeterFilter.deny()); (1)
当Meter的名字为“http”开头时才允许该Meter被使用
参考文档:
[1] 使用 Micrometer 记录 Java 应用性能指标
[2] Micrometer官方文档
网友评论