4.基于AOP实现埋点采集上报

一灰灰blogSpringBoot中间件PrometheusPrometheus约 1571 字大约 5 分钟

前面几篇文章介绍了SpringBoot继承Prometheus实现埋点上报,基本上可以非常简单的实现采样收集,对于由SpringBoot搭建的web应用,甚至是可以说是引入依赖,简单的配置下,剩下的啥也不用管,就可以配置一个REST应用的监控大盘

接下来我们通过AOP的方式,来定义一个自定义数据采集的功能,用于实现一些上面覆盖不到的场景(如应用内的定时任务执行情况,三方接口请求监控等)

I. 方案确定与环境搭建

1.metric选择

通过前面的几篇文章,至少我们会了解到Prometheus的四种Metric,对于自定义的数据采集,根据最终希望监控的指标(每秒请求数 qps, 响应耗时 rt, 可用率 sla, 请求分布),我们这里选择Histogram

  • 通过histogram指标中的 count 值来计算qps
  • 通过 sum / count 来计算rt
  • 通过成功数 / 总请求数计算可用率sla
  • 通过histogram的bucket分布来查看请求分布

2.项目依赖

本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

其核心pom依赖,主要是下面几个包

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

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

    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.2.1.RELEASE</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

3. 配置信息

其次是配置文件,注册下Prometheus的相关信息

spring:
  application:
    name: spring-prometheus-metric
management:
  endpoints:
    web:
      exposure:
        include: "*"
  metrics:
    tags:
      application: ${spring.application.name}

上面配置中,有两个关键信息,前面博文也有介绍,这里简单说明

  • management.endpoints.web.exposure.include 这里指定所有的web接口都会上报
  • metrics.tags.application 这个应用所有上报的metrics 都会带上application这个标签

配置完毕之后,会提供一个 /actuator/prometheus的端点,供prometheus来拉取Metrics信息

II. AOP切面实现埋点上报

1. 切面实现类

通过切面来拦截目标类的执行,选择三个关键指标

  • service: 表示具体执行的类
  • method:执行的方法
  • err: true 表示执行异常/else 表示执行正常

直接使用Histogram来实现数据采集上报,直接使用前面博文* 【中间件】Prometheus自定义埋点姿势二 | 一灰灰Blogopen in new window 使用姿势即可

@Aspect
@Component
public class MetricAop {
    @Autowired
    private MeterRegistry meterRegistry;

    @Pointcut("execution(public * com.git.hui.demo.prometheus.service.*.*(..))")
    public void point() {
    }

    /**
     * 拦截Service共有方法,上报接口执行情况到Prometheus
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("point()")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        String service = joinPoint.getTarget().getClass().getSimpleName();
        String method = joinPoint.getSignature().getName();

        Timer.Sample sample = Timer.start();
        boolean hasError = false;
        try {
            return joinPoint.proceed();
        } catch (Throwable e) {
            hasError = true;
            throw e;
        } finally {
            Timer timer = Timer.builder("micro_service_histogram")
                    .minimumExpectedValue(Duration.ofMillis(1))
                    .maximumExpectedValue(Duration.ofMinutes(3))
                    .sla(Duration.ofMillis(10), Duration.ofMillis(50), Duration.ofMillis(100), Duration.ofMillis(300), Duration.ofMillis(1000))
                    .tags(Tags.of("service", service, "method", method, "err", String.valueOf(hasError)))
                    .register(meterRegistry);
            sample.stop(timer);
        }
    }
}

2. 测试方法

这里写两个简单的Service类,作为收集采样的目标

@Service
public class DemoService {
    private Random random = new Random();

    // 用于控制方法的执行耗时
    private void trySleep() {
        try {
            Thread.sleep(random.nextInt(50));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public int add(int a, int b) {
        trySleep();
        return a + b;
    }

    public int sub(int a, int b) {
        trySleep();
        return a - b;
    }

    public int divide(int a, int b) {
        trySleep();
        return a / b;
    }
}


@Service
public class HelloService {
    private Random random = new Random();

    private void trySleep() {
        try {
            Thread.sleep(random.nextInt(100) + 100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public String hello(String name) {
        trySleep();
        return "hello: " + name;
    }

    public String welcome(String name) {
        trySleep();
        return "welcome: " + name;
    }
}

然后我们通过定时任务来不断的访问上面的服务方法,用来模拟请求场景

@Autowired
private DemoService demoService;
@Autowired
private HelloService helloService;
private Random random = new Random();

private void call(Runnable runnable, CountDownLatch latch) {
    new Thread(() -> {
        try {
            runnable.run();
        } finally {
            latch.countDown();
        }
    }).start();
}

// 100ms请求一次,基本上接口的qps在10左右
@Async("main")
@Scheduled(fixedDelay = 100)
public void doDemoCall() {
    CountDownLatch latch = new CountDownLatch(3);
    call(() -> demoService.add(random.nextInt(10), random.nextInt(30)), latch);
    call(() -> demoService.sub(random.nextInt(10), random.nextInt(30)), latch);
    // 注意这个divide,分母是可能存在为0的哦
    call(() -> demoService.divide(random.nextInt(10), random.nextInt(30)), latch);
    latch.countDown();
}

@Async
@Scheduled(fixedDelay = 100)
public void doHelloCall() {
    CountDownLatch latch = new CountDownLatch(2);
    call(() -> helloService.hello("YiHui " + random.nextInt(30)), latch);
    call(() -> helloService.welcome("YiHui " + random.nextInt(30)), latch);
    latch.countDown();
}

3. 启动类

最后就是在启动类中注册一下MeterRegistryCustomizer,为所有的metric携带上application标签

@EnableScheduling
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }

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

应用启动之后,就可以通过http://m-162d9nnes031u:8080/actuator/prometheus来查看收集的采样信息了,如

4. 业务大盘配置

上面基本即实现了一个基于aop的采样收集,接下来重点就是如何将这些收集上来的数据,可视化配置起来

借助Grafana无疑是个比较推荐的case,下一篇博文将详细介绍如何针对上面收集的信息进行友好的配置,欢迎有兴趣的小伙伴关注吐槽

III. 不能错过的源码和相关知识点

0. 项目源码

系列博文:

1. 微信公众号: 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

一灰灰blog
一灰灰blog
Loading...