SpringBoot - 构建监控体系02_定义度量指标和 Actuator 端点

简介: SpringBoot - 构建监控体系02_定义度量指标和 Actuator 端点

20210516092951966.png

Pre


SpringBoot - 构建监控体系01_使用 Actuator 组件实现及扩展系统监控 我们引入了 Spring Boot Actuator 组件来满足 Spring Boot 应用程序的系统监控功能,并重点介绍了如何扩展常见的 Info 和 Health 监控端点的实现方法。


这一讲我们继续讨论如何扩展 Actuator 端点,但更多关注与度量指标相关的内容。同时,我们还将给出如何创建自定义 Actuator 的实现方法,以便应对默认端点无法满足需求的应用场景。


Actuator 中的度量指标


对于系统监控而言,度量是一个很重要的维度。

在 Spring Boot 2.X 版本中,Actuator 组件主要使用内置的 Micrometer 库实现度量指标的收集和分析。


Micrometer 度量库


Micrometer 是一款监控指标的度量类库,为 Java 平台上的性能数据收集提供了一套通用的 API。在应用程序中,我们只使用 Micrometer 提供的通用 API 即可收集度量指标。

下面我们先来简要介绍 Micrometer 中包含的几个核心概念。


Meter接口


首先我们需要介绍的是计量器 Meter,它是一个接口,代表的是需要收集的性能指标数据。关于 Meter 的定义如下:

public interface Meter extends AutoCloseable {
    //Meter 的唯一标识,是名称和标签的一种组合
    Id getId();
    //一组测量结果
  Iterable<Measurement> measure();
  //Meter 的类型枚举值
    enum Type {
        COUNTER,
        GAUGE,
        LONG_TASK_TIMER,
        TIMER,
        DISTRIBUTION_SUMMARY,
        OTHER
   }
}


通过上述代码,我们注意到 Meter 中存在一个 Id 对象,该对象的作用是定义 Meter 的名称和标签。从 Type 的枚举值中,我们不难看出 Micrometer 中包含的所有计量器类型。


接下来我们先说明两个概念。


Meter 的名称:对于计量器来说,每个计量器都有自己的名称,而且在创建时它们都可以指定一系列标签。

Meter 的标签:标签的作用在于监控系统可以通过这些标签对度量进行分类过滤。


计量器类型


在日常开发过程中,常用的计量器类型主要分为计数器 Counter、计量仪 Gauge 和计时器 Timer 这三种。


Counter:这个计量器的作用和它的名称一样,就是一个不断递增的累加器,我们可以通过它的 increment 方法实现累加逻辑。


Gauge:与 Counter 不同,Gauge 所度量的值并不一定是累加的,我们可以通过它的 gauge 方法指定数值。


Timer:这个计量器比较简单,就是用来记录事件的持续时间。


如何创建这些计量器


既然我们已经明确了常用的计量器及其使用场景,那么如何创建这些计量器呢?

在 Micrometer 中,我们提供了一个计量器注册表 MeterRegistry,它主要负责创建和维护各种计量器。关于 MeterRegistry 的创建方法如下代码所示:

public abstract class MeterRegistry implements AutoCloseable {
    protected abstract <T> Gauge newGauge(Meter.Id id, @Nullable T obj, ToDoubleFunction<T> valueFunction);
    protected abstract Counter newCounter(Meter.Id id);
    protected abstract Timer newTimer(Meter.Id id, DistributionStatisticConfig distributionStatisticConfig, PauseDetector pauseDetector);
}

以上代码只是创建 Meter 的一种途径,从中我们可以看到 MeterRegistry 针对不同的 Meter 提供了对应的创建方法。


而创建 Meter 的另一种途径是使用某个 Meter 的具体 builder 方法。以 Counter 为例,它的定义中包含了一个 builder 方法和一个 register 方法,如下代码所示:

public interface Counter extends Meter {
    static Builder builder(String name) {
        return new Builder(name);
  }
    default void increment() {
        increment(1.0);
  }
  void increment(double amount);
  double count();
    @Override
    default Iterable<Measurement> measure() {
        return Collections.singletonList(new Measurement(this::count, Statistic.COUNT));
  }
    public Counter register(MeterRegistry registry) {
        return registry.counter(new Meter.Id(name, tags, baseUnit, description, Type.COUNTER));
    }
}

注意到最后的 register 方法就是将当前的 Counter 注册到 MeterRegistry 中,因此我们需要创建一个 Counter。通常,我们会采用如下所示代码进行创建。

Counter counter = Counter.builder("counter1")
        .tag("tag1", "value1")
        .register(registry);



扩展 Metrics 端点


了解了 Micrometer 框架的基本概念后,接下来我们回到 Spring Boot Actuator,一起来看看它提供的专门针对度量指标管理的 Metrics 端点。


在 Spring Boot 中,它为我们提供了一个 Metrics 端点用于实现生产级的度量工具。访问 actuator/metrics 端点后,我们将得到如下所示的一系列度量指标。


20210530191254475.png


以上代码中涉及的指标包括常规的系统内存总量、空闲内存数量、处理器数量、系统正常运行时间、堆信息等,如果引用了数据库,也也包含我们引入 JDBC 和 HikariCP 数据源组件之后的数据库连接信息等。


此时,如果我们想了解某项指标的详细信息,在 actuator/metrics 端点后添加对应指标的名称即可。


例如我们想了解当前内存的使用情况,就可以通过 actuator/metrics/jvm.memory.used 端点进行获取,如下代码所示。


20210530191412165.png

自定义 Metrics 指标


前面介绍 Micrometer 时,我们已经提到 Metrics 指标体系中包含支持 Counter 和 Gauge 这两种级别的度量指标。


通过将 Counter 或 Gauge 注入业务代码中,我们就可以记录自己想要的度量指标。其中,Counter 用来暴露 increment() 方法,而 Gauge 用来提供一个 value() 方法。


下面我们以 Counter 为例介绍在业务代码中嵌入自定义 Metrics 指标的方法,如下代码所示:

 package com.artisan.admin;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.springframework.stereotype.Component;
/**
 * @author 小工匠
 * @version 1.0
 * @description: 
 * @date 2021/5/30 19:16
 * @mark: show me the code , change the world
 */
@Component
public class CounterService {
    public CounterService() {
        Metrics.addRegistry(new SimpleMeterRegistry());
    }
    public void counter(String name, String... tags) {
        Counter counter = Metrics.counter(name, tags);
        counter.increment();
    }
}


在这段代码中,我们构建了一个公共服务 CounterService,并开放了一个 Counter 方法供业务系统进行使用。当然,你也可以自己实现类似的工具类完成对各种计量器的封装。


另外,Micrometer 还提供了一个 MeterRegistry 工具类供我们创建度量指标。因此,我们也十分推荐使用 MeterRegistry 对各种自定义度量指标的创建过程进行简化。


使用 MeterRegistry

比如我们希望系统每创建一个客服工单,就对所创建的工单进行计数,并作为系统运行时的一项度量指标,该效果的实现方式如下代码所示:

@Service
public class CustomerTicketService {
    @Autowired
    private MeterRegistry meterRegistry;
  public CustomerTicket generateCustomerTicket(Long accountId, String orderNumber) {
       CustomerTicket customerTicket = new CustomerTicket();
        meterRegistry.summary("customerTickets.generated.count").record(1);
        return customerTicket;
    }   
}


在上述 generateCustomerTicket 方法中,通过 MeterRegistry 我们实现了每次创建 CustomerTicket 时自动添加一个计数的功能。


而且,MeterRegistry 还提供了一些类工具方法用于创建自定义度量指标。这些类工具方法除了常规的 counter、gauge、timer 等对应具体 Meter 的工具方法之外,还包括上述代码中的 summary 方法,且 Summary 方法返回的是一个 DistributionSummary 对象,关于它的定义如下代码所示:


public interface DistributionSummary extends Meter, HistogramSupport {
    static Builder builder(String name) {
        return new Builder(name);
    }
    //记录数据
    void record(double amount);
    //记录操作执行的次数
    long count();
    //记录数据的数量
    double totalAmount();
    //记录数据的平均值
    default double mean() {
        return count() == 0 ? 0 : totalAmount() / count();
    }
    //记录数据的最大值
  double max();
}

因为 DistributionSummary 的作用是记录一系列的事件并对这些事件进行处理,所以在 CustomerTicketService 中添加的meterRegistry.summary("customertickets.generated.count").record(1) 这行代码相当于每次调用 generateCustomerTicket 方法时,我们都会对这次调用进行记录。


现在访问 actuator/metrics/customertickets.generated.count 端点,我们就能看到如下所示的随着服务调用不断递增的度量信息。

{
     "name":"customertickets.generated.count",
     "measurements":[
         {
             "statistic":"Count",
             "value":1
         },
         {
             "statistic":"Total",
             "value":19
         }
     ] 
 }

显然,通过 MeterRegistry 实现自定义度量指标的使用方法更加简单。这里,你也可以结合业务需求尝试该类的不同功能。


接下来我们再来看一个相对比较复杂的使用方式。在 customer-service 中,我们同样希望系统存在一个度量值,该度量值用于记录所有新增的 CustomerTicket 个数,这次的示例代码如下所示:

 @Component
public class CustomerTicketMetrics extends  AbstractRepositoryEventListener<CustomerTicket> {
    private MeterRegistry meterRegistry;
    public CustomerTicketMetrics(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    @Override
    protected void onAfterCreate(CustomerTicket customerTicket) {                          meterRegistry.counter("customerTicket.created.count").increment();  
  }
}


首先,这里我们使用了 MeterRegistry 的 Counter 方法初始化一个 counter,然后调用它的 increment 方法增加度量计数(这部分内容我们已经很熟悉了)。


注意到这里,我们同时还引入了一个 AbstractRepositoryEventListener 抽象类,这个抽象类能够监控 Spring Data 中 Repository 层操作所触发的事件 RepositoryEvent,例如实体创建前后的 BeforeCreateEvent 和 AfterCreateEvent 事件、实体保存前后的 BeforeSaveEvent 和 AfterSaveEvent 事件等。


针对这些事件,AbstractRepositoryEventListener 能捕捉并调用对应的回调函数。关于 AbstractRepositoryEventListener 类的部分实现如下代码所示:

public abstract class AbstractRepositoryEventListener<T> implements ApplicationListener<RepositoryEvent> {
    public final void onApplicationEvent(RepositoryEvent event) {
        Class<?> srcType = event.getSource().getClass();
        if (event instanceof BeforeSaveEvent) {
            onBeforeSave((T) event.getSource());
        } else if (event instanceof BeforeCreateEvent) {
            onBeforeCreate((T) event.getSource());
        } else if (event instanceof AfterCreateEvent) {
            onAfterCreate((T) event.getSource());
        } else if (event instanceof AfterSaveEvent) {
            onAfterSave((T) event.getSource());
              }
  }
}


在这段代码中,我们可以看到 AbstractRepositoryEventListener 直接实现了 Spring 容器中的 ApplicationListener 监听器接口,并在 onApplicationEvent 方法中根据所传入的事件类型触发了回调函数。


以案例中的需求场景为例,我们可以在创建 Account 实体之后执行度量操作。也就是说,可以把度量操作的代码放在 onAfterCreate 回调函数中,正如案例代码中所展示那样。


现在我们执行生成客户工单操作,并访问对应的 Actuator 端点,同样可以看到度量数据在不断上升。


自定义 Actuator 端点


在日常开发过程中,扩展现有端点有时并不一定能满足业务需求,而自定义 Spring Boot Actuator 监控端点算是一种更灵活的方法。


假设我们需要提供一个监控端点以获取当前系统的用户信息和计算机名称,就可以通过一个独立的 MySystemEndPoint 进行实现,如下代码所示

@Configuration
@Endpoint(id = "mysystem", enableByDefault=true)
public class MySystemEndpoint { 
    @ReadOperation
    public Map<String, Object> getMySystemInfo() {
        Map<String,Object> result= new HashMap<>();
        Map<String, String> map = System.getenv();
        result.put("username",map.get("USERNAME"));
        result.put("computername",map.get("COMPUTERNAME"));
        return result;
    }
}

注意,这里我们引入了一个新的注解 @Endpoint,该注解定义如下代码所示:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Endpoint {
    //端点 id
    String id() default "";
    //是否默认启动标志位
    boolean enableByDefault() default true;
}


这段代码中的 @Endpoint 注解主要用于设置端点 id 及是否默认启动的标志位。且在案例中,我们指定了 id 为“mysystem”,enableByDefault 标志为 true。


事实上,在 Actuator 中也存在一批类似 @Endpoint 的端点注解。其中被 @Endpoint 注解的端点可以通过 JMX 和 Web 访问应用程序,对应的被 @JmxEndpoint 注解的端点只能通过 JMX 访问,而被 @WebEndpoint 注解的端点只能通过 Web 访问。


在示例代码中,我们还看到了一个 @ReadOperation 注解,该注解作用于方法,用于标识读取数据操作


在 Actuator 中,除了提供 @ReadOperation 注解之外,还提供 @WriteOperation 和 @DeleteOperation 注解,它们分别对应写入操作和删除操作。


现在,通过访问 http://localhost:8080/actuator/mysystem,我们就能获取如下所示监控信息。

20210530202618954.png

有时为了获取特定的度量信息,我们需要对某个端点传递参数,而 Actuator 专门提供了一个 @Selector 注解标识输入参数,示例代码如下所示:

@Configuration
@Endpoint(id = "account", enableByDefault = true)
public class AccountEndpoint {
    @Autowired
    private AccountRepository accountRepository;    
    @ReadOperation
    public Map<String, Object> getMySystemInfo(@Selector String arg0) {
        Map<String, Object> result = new HashMap<>();
        result.put(accountName, accountRepository.findAccountByAccountName(arg0));
        return result;
    }
}


这段代码的逻辑非常简单,就是根据传入的 accountName 获取用户账户信息。

这里请注意,通过 @Selector 注解,我们就可以使用http://localhost:8080/actuator/ account/account1 这样的端口地址触发度量操作了。


小结


度量是我们观测一个应用程序运行时状态的核心手段。我们介绍了 Spring Boot 中新引入的 Micrometer 度量库,以及该库中提供的各种度量组件。


同时还基于 Micrometer 中的核心工具类 MeterRegistry 完成了在业务系统中嵌入度量指标的实现过程。最后,我们还简要介绍了如何自定义一个 Actuator 端点的开发方法。


相关文章
|
7天前
|
Java API 微服务
【Spring Boot系列】通过OpenAPI规范构建微服务服务接口
【4月更文挑战第5天】通过OpenAPI接口构建Spring Boot服务RestAPI接口
|
3月前
|
Dubbo Java 应用服务中间件
Spring Boot Dubbo 构建分布式服务
Spring Boot Dubbo 构建分布式服务
47 0
|
4天前
|
存储 Java 时序数据库
【SpringBoot系列】微服务监测(常用指标及自定义指标)
【4月更文挑战第6天】SpringBoot微服务的监测指标及自定义指标讲解
|
2月前
|
消息中间件 NoSQL Java
Redis Streams在Spring Boot中的应用:构建可靠的消息队列解决方案【redis实战 二】
Redis Streams在Spring Boot中的应用:构建可靠的消息队列解决方案【redis实战 二】
222 1
|
9天前
|
Java 应用服务中间件 Maven
【SpringBoot系列】第一课:构建一个简单的SpringBoot应用程序
【4月更文挑战第3天】构建第一个SpringBoot应用程序
|
21天前
|
Java Spring
springboot单类集中定义线程池
该内容是关于Spring中异步任务的配置和使用步骤。首先,在启动类添加`@EnableAsync`注解开启异步支持。然后,自定义线程池类`EventThreadPool`,设置核心和最大线程数、存活时间等参数。接着,将线程池bean注入到Spring中,如`@Bean(&quot;RewardThreadPool&quot;)`。最后,在需要异步执行的方法上使用`@Async`注解,例如在一个定时任务类中,使用`@Scheduled(cron = &quot;...&quot;)`和`@Async`结合实现异步定时任务。
16 2
|
28天前
|
Prometheus 监控 Cloud Native
Spring Boot 应用可视化监控
Spring Boot 应用可视化监控
17 0
|
1月前
|
Java API Maven
使用Java和Spring Boot构建RESTful API
使用Java和Spring Boot构建RESTful API
16 0
|
1月前
|
监控 druid Java
Spring Boot3整合Druid(监控功能)
Spring Boot3整合Druid(监控功能)
57 1
|
2月前
|
监控 Java
HeartBeat监控springboot服务状态
HeartBeat监控springboot服务状态