8

SpringBoot Metrics 监控系统(3)——搭建框架

 3 years ago
source link: https://www.jitwxs.cn/527f9dce.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Jitwxs

SpringBoot Metrics 监控系统(3)——搭建框架

Jitwxs|发表于2020-11-15|更新于2020-12-19|云原生
字数总计:1.5k|阅读时长:7分钟|阅读量:16|评论数:

本章节开始将为大家展示如何在 SpringBoot 应用中去使用 Metrics 监控。本系列使用的 SpringBoot 版本为笔者当前的最新 RELAESE 版本 2.4.0,整个 SpringBoot 2 关于这边都是大同小异,所以大家不用担心版本问题。

二、依赖包

除了常规开发 SpringBoot Web 所需要的两个包外:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

我们还需要导入 SpringBoot 的监控端点 actuator 包和 Prometheus 包:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

另外为了便于开发,我还使用了 Lombok:

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

三、Metrics Enums

对于刚刚入门使用的同学,经常会出现 Metrics 指标的管理混乱的情况。在代码里这处注册一个指标,那处注册一个,很混乱。因此有必要提供一个类,专门管理所有的 Metrics 指标,并统一注册、

这里我使用枚举类实现。当然你也可以根据自己需要使用其他方式实现。一共有以下两个类:

  • MetricsTypeEnum 枚举系统支持的所有 Metrics 类型
  • MetricsEnum 枚举所有的指标,标识了每一个指标的 name、tags、description、type 等属性

3.1 MetricsTypeEnum

public enum MetricsTypeEnum {
UNKNOWN,
COUNTER,
GAUGE,
TIMER
;
}

3.2 MetricsEnum

@Getter
@AllArgsConstructor
public enum MetricsEnum {
DEFAULT("default", null, "default description", UNKNOWN),
;

private final String name;

private final String[] tags;

private final String description;

private final MetricsTypeEnum type;
}

四、Metrics Support

首先编写一些使用 Prometheus 的 Metrics 的工具类,一共有以下几个类:

  • MetricsRegisterConfig 交于 Spring 容器管理,负责获取到 Prometheus 的全局实例对象
  • BaseMetricsUtil 基础的 Metrics 工具类,抽取一些共用方法
  • CounterMetricsUtil Counter 类型的 Metrics 工具类
  • GaugeMetricsUtil Gauge 类型的 Metrics 工具类
  • TimerMetricsUtil Timer 类型的 Metrics 工具类

4.1 MetricsRegisterConfig

import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MetricsRegisterConfig implements BeanPostProcessor {
@Value("${spring.application.name}")
private String applicationName;

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof MeterRegistry) {
MeterRegistry registry = (MeterRegistry) bean;
registry.config().commonTags("application", applicationName);

BaseMetricsUtil.meterRegistry = registry;
}

return bean;
}
}

4.2 BaseMetricsUtil

import com.github.jitwxs.metrics.enums.MetricsEnum;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.util.Assert;

public class BaseMetricsUtil {
public static MeterRegistry meterRegistry;

protected static String CLASS_NAME = new Object() {
public String getClassName() {
String clazzName = this.getClass().getName();
return clazzName.substring(0, clazzName.lastIndexOf("$"));
}
}.getClassName();

public static void basicCheck(final MetricsEnum metricsEnum) {
Assert.notNull(meterRegistry, CLASS_NAME + " meterRegistry not allow null");

String[] tags = metricsEnum.getTags();
if(tags != null && tags.length % 2 != 0) {
throw new IllegalArgumentException(CLASS_NAME + "metrics tags must appear in pairs");
}
}
}

4.3 CounterMetricsUtil

import com.github.jitwxs.metrics.enums.MetricsEnum;
import io.micrometer.core.instrument.Counter;
import org.springframework.util.Assert;

import java.util.HashMap;
import java.util.Map;

public class CounterMetricsUtil extends BaseMetricsUtil {
private static final Map<MetricsEnum, Counter> REGISTER_MAP = new HashMap<>();

public static void register(MetricsEnum metricsEnum) {
basicCheck(metricsEnum);
Assert.isTrue(!REGISTER_MAP.containsKey(metricsEnum), "this metrics already register");

Counter counter = Counter.builder(metricsEnum.getName())
.tags(metricsEnum.getTags())
.description(metricsEnum.getDescription())
.register(meterRegistry);

REGISTER_MAP.put(metricsEnum, counter);
}

public static void increment(MetricsEnum metricsEnum) {
increment(metricsEnum, 1.0);
}

public static void increment(MetricsEnum metricsEnum, double value) {
Counter counter = REGISTER_MAP.get(metricsEnum);
if(counter != null) {
counter.increment(value);
}
}
}

4.4 GaugeMetricsUtil

import com.github.jitwxs.metrics.enums.MetricsEnum;
import io.micrometer.core.instrument.Gauge;
import org.springframework.util.Assert;

import java.util.HashMap;
import java.util.Map;

public class GaugeMetricsUtil extends BaseMetricsUtil {
private static final Map<MetricsEnum, Double> REGISTER_MAP = new HashMap<>();

public static void register(MetricsEnum metricsEnum) {
basicCheck(metricsEnum);
Assert.isTrue(!REGISTER_MAP.containsKey(metricsEnum), "this metrics already register");

Gauge.builder(metricsEnum.getName(), REGISTER_MAP, e -> e.get(metricsEnum))
.tags(metricsEnum.getTags())
.description(metricsEnum.getDescription())
.register(meterRegistry);

REGISTER_MAP.put(metricsEnum, 0D);
}

public static void gauge(MetricsEnum metricsEnum, double value) {
REGISTER_MAP.put(metricsEnum, value);
}
}

4.5 TimerMetricsUtil

import java.util.concurrent.TimeUnit;

import com.github.jitwxs.metrics.enums.MetricsEnum;
import io.micrometer.core.instrument.Timer;
import org.springframework.util.Assert;

import java.util.HashMap;
import java.util.Map;

public class TimerMetricsUtil extends BaseMetricsUtil {
private static final Map<MetricsEnum, Timer> REGISTER_MAP = new HashMap<>();

public static void register(MetricsEnum metricsEnum) {
basicCheck(metricsEnum);
Assert.isTrue(!REGISTER_MAP.containsKey(metricsEnum), "this metrics already register");

Timer timer = Timer.builder(metricsEnum.getName())
.tags(metricsEnum.getTags())
.description(metricsEnum.getDescription())
.publishPercentiles(0.5, 0.8, 0.95, 0.99) // 指定百分位数
.register(meterRegistry);

REGISTER_MAP.put(metricsEnum, timer);
}

public static void record(MetricsEnum metricsEnum, long time, TimeUnit unit) {
Timer timer = REGISTER_MAP.get(metricsEnum);
if(timer != null) {
timer.record(time, unit);
}
}
}

五、程序配置

5.1 applicaiton.yaml

编辑程序的配置文件,主要是 management 相关的配置。

  • management.server.port 指定暴露的监控端点
  • management.endpoints.web.base-path SpringBoot 程序监控默认的根路径是 /actuator,我嫌它麻烦,给改成 /
  • management.endpoints.web.exposure.include SpringBoot 默认情况会将所有信息都暴露出去,这里我改成只暴露一部分,主要用的就是那个 prometheus【生产环境这些内容要么要加权限控制,要么尽量减少暴露部分,减少泄露信息的可能】
management:
server:
port: 7002
endpoints:
web:
base-path: /
exposure:
include: health, info, prometheus
spring:
application:
name: springboot_metrics

server:
port: 8080

5.2 注册 MetricsEnum

前面提到我将所有的指标都注册到了 MetricsEnum 中,因此当服务启动时,我需要将它们都注册到 Metrics 里面。

import com.github.jitwxs.metrics.enums.MetricsEnum;
import com.github.jitwxs.metrics.support.CounterMetricsUtil;
import com.github.jitwxs.metrics.support.GaugeMetricsUtil;
import com.github.jitwxs.metrics.support.TimerMetricsUtil;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class MetricsApplicationBoot implements ApplicationRunner {

@Override
public void run(ApplicationArguments args) throws Exception {
// 注册指标
for (MetricsEnum metricsEnum : MetricsEnum.values()) {
switch (metricsEnum.getType()) {
case COUNTER:
CounterMetricsUtil.register(metricsEnum);
break;
case GAUGE:
GaugeMetricsUtil.register(metricsEnum);
break;
case TIMER:
TimerMetricsUtil.register(metricsEnum);
break;
default:
break;
}
}
}
}

5.3 开启定时任务注解

另外后续我将通过定时任务的方式,去模拟 Metrics 数据的产生,因此不要忘记在启动类上方加上注解:@EnableScheduling

至此完成 SpringBoot Metrics 监控系统框架的搭建,整个项目的文件结构如下图所示,另外访问 http://127.0.0.1:7002/prometheus 应当得到形如下图的数据。

loading.gif

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK