searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

JVM应用度量框架Micrometer

2023-07-14 07:01:57
95
0

对于一个健全的应用,性能指标的收集是必不可少的。通过这些指标,可以有效地对系统各方面进行监控,帮助开发及运维人员掌握系统运行状态,查找问题原因。

性能指标监控通常分2步实现:

  1. 代码中埋点,进行监控数据采集;

  2. 后台监控系统对采集的数据进行聚合计算,生成相应的图表以及进行可视化分析。

Micrometer是一款针对JVM应用的Metrics指标监控库,应用程序通过调用其提供的通用API来收集性能指标,并对接多种当前流行的监控系统,如Prometheus、Datadog。因其内部实现了对不同监控系统的适配工作,使得切换监控系统变得很容易。其设计宗旨即在提高可移植性的同时,几乎不增加指标收集活动的开销,号称监控界的Slf4j,对与SLA指标的测量非常方便。

SpringBoot 2.x版本中,spring-boot-actuator使用了Micrometer实现监控,也就是我们通常说的SpringBoot集成了Micrometer,这就使得在SpringBoot程序中使用Micrometer更加方便。

一、核心概念

1、Register:注册表

Meter是收集应用的测量指标的接口。Micrometer通过MeterRegister创建和保存Meter,每个监控系统都会有MeterRegister的实现。

  • SimpleMeterRegister:每个Meter的最新数据都可以收集到SimpleMeterRegister实例中,但是这些数据不会发布到其他系统,也就是数据是位于应用的内存中的。

  • CompositeMeterRegister:多个MeterRegister的聚合,内部维护了一个MeterRegister的列表。

  • 全局的MeterRegister:工厂类io.micrometer.core.instrument.Metrics中持有一个静态final的CompositeMeterRegistry实例globalRegistry。

如果用Spring的话,SimpleMeterRegister是自动注入的,即通过下面的方式:

@Autowired
private MeterRegistry meterRegistry;

2、Meter:计量器

MicroMeter提供了以下几种不同类型的计量器。

  • Counter(计数器):是一种单值的度量类型。准确来说,counter就是一个增量为正数的单值计数器。它的作用是记录XXX的总量或者计数值,适用于一些增长类型的统计,例如下单、支付次数、http请求总量记录等。

  • Gauge(计量仪):表示一个可以任意上下浮动的单数值度量Meter。通常用于变动的测量值,例如常用的CPU Load、Mem使用量、Metwork使用量、实时在线人数统计等。

  • Timer(计时器):适用于记录耗时比较短的事件的执行时间,通过时间分布展示事件的序列和发生频率。常用的使用常用有:记录指定方法的执行时间用于展示;记录一些任务的执行时间,从而确定某些数据来源的速率,例如消息队列的消费速率等。

  • Distribution Summary(分布摘要):主要用于跟踪事件的分布。它的使用方式和Timer十分相似,但是它的记录值并\textcolor{red}{不依赖于时间单位}。常见的使用场景:使用DistributionSummary测量服务器的有效负载值,缓存的命中率等。

二、命名

1、指标命名

Micrometer命名规则采用点分隔的小写单词。不同的监控系统有不同的命名规则,他们在对Micrometer的实现中,会把命名按照自己的规则进行转换同时清楚那些本系统无法识别的条数字符。

例如:Micrometer中名称为http.server.requests的timer在不同监控系统中的名字如下:

registry.timer("http.server.requests");
  1. Prometheus :http_server_requests_duration_seconds

  2. Atlas :httpServerRequests

  3. Graphite:http.server.requests

  4. InfluxDB:http_server_requests

遵守Micrometer的点分小写单词的命名规范,可以最大的保证度量名称在各监控系统之间的可移植性。

2、Tag命名

想要测http请求量和数据库的调用量,可以通过以下两种方式:

registry.counter("database.calls", "db", "users")
registry.counter("http.requests", "uri", "/api/users")

或者

ArrayList<Tag> tagList1 = new ArrayList<>();
ArrayList<Tag> tagList2 = new ArrayList<>();
ImmutableTag tag_exception = new ImmutableTag("db", "users");
ImmutableTag tag_request_method = new ImmutableTag("uri", "/api/users");
registry.counter("database.calls", tagList1);
registry.counter("http.requests", tagList2);

这样我们查询database.calls就可以得到所有数据库的调用量,然后进一步通过名称为db的tag来查询具体某个库的调用量,如users库的调用量。

本人更推荐第二种书写方式,因为tag的对应关系更加清晰明了。第一种方式定义的tags必须为偶数个,两个为一对tag【分别对应key与value】。

3、通用标签

通用标签是定义在register上的,运行时会被添加到每一个监控指标上,发布到监控系统。

想要为所有指标添加上application的通用标签,来使prometheus区分不同的应用,书写方式有一下两种:

@Configuration
public class MeterConfig {
    @Bean
    MeterRegistryCustomizer<MeterRegistry> configurer(@Value("${spring.application.name}") String applicationName) {
        return registry -> registry.config().commonTags("application", applicationName);
    }
}

或者在配置文件中定义:

management: #开启SpringBoot Admin的监控
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always
  metrics:
    export:
      prometheus:
        enabled: true
    tags:
      application: ${spring.application.name}  #暴露的数据中添加application label

通过management.metrics.tags来指定。

三、常用Meter简介

1、Counter

记录一个计数指标,Counter接口允许以固定的正数值递增。

根据counter建立图表或者报警时,通常需要关注的是在一个给定的时间区间内,某事件发生的比率。如一个队列,Counter可以用来衡量插入率。

【注意:】由于Timer里已经记录了一个count,因此那些记录了Timer的代码,就不需要单独增加Counter了。

使用场景:

Counter的作用是记录XXX的总量或者计数值,适用于一些增长类型的统计,例如下单、支付次数、http请求总量记录等等,通过Tag可以区分不同的场景。对于下单,可以使用不同的Tag标记不同的业务来源或者是按日期划分。对于http请求总量记录,可以使用Tag区分不同的URL。

2、Gauge

Gauge表示一个可以任意上下浮动的单数值度量Meter。Gauge通常用于变动的测量值,测量值用ToDoubleFunction参数的返回值设置,如当前的内存使用情况,同时也可以测量上下移动的”计数“,比如队列中的消息数量。官方文档中提到Gauge的典型使用场景是用于测量集合或映射的大小、或运行中的线程数。Gauge一般用于检测有自然上界的事件或者任务,而Counter一般用于无自然上界的事件或者任务的检测,所以像http请求总量计数应该使用Counter而非Gauge。

MeterRegister中提供了一些构建用于观察数值、函数、集合和映射的Gauge相关的方法:

List<String> list = registry.gauge("listGauge", Collections.emptyList(), new ArrayList<>(), List::size); 

List<String> list2 = registry.gaugeCollectionSize("listSize2", Tags.empty(), new ArrayList<>()); 

Map<String, Integer> map = registry.gaugeMapSize("mapGauge", Tags.empty(), new HashMap<>());
  • 第一个参数为自定义指标的名称

  • 第二个参数为Tags,即标签列表。上面三个方法都设置为了空

  • 第三个参数为待监测的对象,这里是一个新建的ArrayList或者HashMap

  • 最后一个参数为待监测的指标,此处是List的size或者Map的size。后续在使用list时,不需要要感知Gauge接口的存在,当list的size变化时,该变更值自然就会被记录。

通过gauge()函数返回了集合或者映射实例,使用这些集合或者映射实例就能在其size变化过程中记录这个变更值。更重要的优点是,我们不需要感知Gauge接口的存在,只需要像平时一样使用集合或者映射实力就可以了。

使用场景:

  1. 有自然(物理)上界的浮动值的监测,例如物理内存、集合、映射、数值等。

  2. 有逻辑上界的浮动值的监测,例如积压的消息、(线程池中)积压的任务等,其实本质也是集合或者映射的监测。

3、Timer

适用于记录耗时比较短的事件的执行时间,通过时间分布展示事件的序列和发生频率。所有的Timer的实现至少记录了发生的事件的数量和这些事件的总耗时,从而生成一个时间序列。Timer的基本单位基于服务端的指标而定,但是实际上我们不需要过于关注Timer的基本单位,因为Micrometer在存储生成的时间序列时会自动选择适当的基本单位。

使用场景:

  1. 记录指定的方法的执行时间用于展示;

  2. 记录一些任务的执行时间,从而确定某些数据来源的速率,例如消息队列的消费速率等。

【注意:】Timer是DistributionSummary的特化,专门用于计时类的指标。而DistributionSummary包含histogram指标,因此通过register的配置,可以为Timer暴露histogram指标。

4、DistributionSummary

主要用于跟踪事件的分布。它的使用方式和Timer十分类似,但它的记录值并不依赖与时间单位。

使用场景:

  1. 不依赖于时间单位的记录值的测量,例如服务器有效负载值、缓存的命中率等。

DistributionSummar由多个指标组成:

  • count:事件的个数,聚合指标,如响应的个数;

  • sum:总和,聚合指标,如响应大小的总和

  • histogram:分布,包含le标签用于区分bucket。例如web.response.size.historgram{le=512} = 99,表示响应大小不超过512(Byte)的响应个数是99个。一般有多个bucket,如le=128,le=256,le=512,le=1024,le=+Inf等。 每个bucket展示为一条时间序列,会得到类似下面的图。

  • percentile/quantile:百分位数,包含percentile或者全踢了标签用于区分不同的百分位。例如web.response.size.percentile{p=90) = 512,表示90%的响应大小都小于512。一般有多个percentile,如p50,p75,p90,p99。 每个百分位展示为一条时间序列,会得到类似下面的图。

  • Timer:Timer是DistributionSummary的特化,专门用于计时类的指标。

5、histogram

Meter中并未封装histogram的包装类,而是通过Timer与DistributionSummary来暴露histogram指标相关的数据。

通过配置类为特定指标收集histogram数据。

@Configuration
@Slf4j
public class MicrometerConfig {

    @Bean
    MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
        return registry -> {
            registry.config().meterFilter(
                    new MeterFilter() {
                        @Override
                        public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {
                            if (id.getType() == Meter.Type.TIMER && id.getName().matches("^(http|hystrix){1}.*")) {
                                return DistributionStatisticConfig.builder()
                                        .percentilesHistogram(true)
                                        .percentiles(0.5, 0.90, 0.95, 0.99)
                                        .sla(Duration.ofMillis(50).toNanos(),
                                                Duration.ofMillis(100).toNanos(),
                                                Duration.ofMillis(200).toNanos(),
                                                Duration.ofSeconds(1).toNanos(),
                                                Duration.ofSeconds(5).toNanos())
                                        .minimumExpectedValue(Duration.ofMillis(1).toNanos())
                                        .maximumExpectedValue(Duration.ofSeconds(5).toNanos())
                                        .build()
                                        .merge(config);
                            }
                            return config;

                        }
                    }
            );
        };
    }
}
  • id.getType() == Meter.Type.TIMER && id.getName().matches("^(http|hystrix){1}.*"):可以过滤指定的指标,为符合条件的指标收集histogram数据。

  • percentilesHistogram:暴露直方图数据指标。

  • percentiles:用于发布应用中计算出的百分比值。此处为监控95%的接口访问时间为多少。

  • sla:用于发布基于SLA定义的桶的累积直方图。对应于指标中的le标签。此处为访问时间小于0.1ms的请求有多少个;

  • minimumExpectedValue:metrics会收集所有的数据,通过指定最小最大的bucket的值,来向prometheus展示指定范围的数据。

  • maximumExpectedValue:metrics会收集所有的数据,通过指定最小最大的bucket的值,来向prometheus展示指定范围的数据。

0条评论
0 / 1000