使用Prometheus对微服务性能自定义指标监控

news2024/11/24 6:59:25

背景

随着云计算和容器化技术的不断发展,微服务架构逐渐成为现代软件开发的主流趋势。微服务架构将大型应用程序拆分成多个小型、独立的服务,每个服务都可以独立开发、部署和扩展。这种架构模式提高了系统的可伸缩性、灵活性和可靠性,但同时也带来了服务监控和管理的挑战。

在微服务架构中,服务之间的依赖关系变得复杂,服务数量众多,因此需要一种有效的监控和管理工具来确保系统的稳定性和可靠性。监控工具可以帮助开发人员实时了解服务的运行状态、性能指标和异常情况,从而及时发现问题并进行处理。同时,管理工具还可以提供自动化的部署、配置和扩展功能,提高开发效率和运维质量。

 

Prometheus的优势

  1. 请求、数据库查询、消息队列等应用指标。

  2. 高效数据存储:Prometheus采用时间序列数据库(TSDB)来存储监控数据,具有高效的数据压缩和查询性能。

  3. 丰富查询语言:Prometheus提供了强大的数据查询语言PromQL,可以方便地对监控数据进行过滤、聚合和计算。

  4. 灵活告警机制:Prometheus支持基于规则的告警机制,可以根据监控数据的阈值触发告警通知,支持多种告警方式,如邮件、短信、Slack等。

 springBoot集成Prometheus

 导入Pom依赖

<dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
        <version>2.2.1.RELEASE</version>
</dependency>
<dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
        <version>1.3.1</version>
</dependency>

修改springBoot配置文件

开启prometheus监控配置

management:
  endpoint:
    prometheus:
        enabled: true
  endpoints:
      web:
          exposure:
              include: 'prometheus'

修改默认的Prometheus监控度量名称

prometheus默认指标中有个http.server.requests的度量名称,记录了http请求调用情况;现在以这个为例,修改名称

新建一个@Configuration 类 PrometheusConfig

import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.config.NamingConvention;
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Collectors;

@Configuration
public class PrometheusConfig {

    /** 用于替换 Prometheus中 公共的 http.server.requests度量名替换
     * @return
     */
    @Bean
    MeterRegistryCustomizer<MeterRegistry> metricsConfig() {
        return registry -> registry.config().namingConvention(new NamingConvention() {
            @Override
            public String name(String name, Meter.Type type, String baseUnit) {
                String collect = "";
                if(name.contains("http.server.requests")){
                    collect = Arrays.stream(name.replaceAll("http.server.requests", "jiang.xiao.yu.http").split("\\.")).filter(Objects::nonNull).collect(Collectors.joining("_"));
                }else {
                    collect = Arrays.stream(name.split("\\.")).filter(Objects::nonNull).collect(Collectors.joining("_"));
                }
                return collect;
            }
        });
    }
}

自定义Prometheus监控指标

使用拦截器监控指标

利用拦截器实现所有HTTP接口的监控

利用HTTP的拦截器添加Prometheus的监控指标,首先创建一个拦截器CustomInterceptor 实现HandlerInterceptor接口,然后重写里面的 前置处理、后置处理;

import io.micrometer.core.instrument.*;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.ToDoubleFunction;

public class CustomInterceptor implements HandlerInterceptor {

    private static final String CUSTOM_KPI_NAME_TIMER = "custom.kpi.timer"; //耗时
    private static final String CUSTOM_KPI_NAME_COUNTER = "custom.kpi.counter"; //api调用次数。
    private static final String CUSTOM_KPI_NAME_SUMMARY = "custom.kpi.summary"; //汇总率
    private static MeterRegistry registry;
    private long startTime;
    private GaugeNumber gaugeNumber = new GaugeNumber();

    void getRegistry(){
        if(registry == null){
            //这里使用的时SpringUtil获取Bean,没有用@Autowired注解,Autowired会因为加载时机问题导致拿不到;SpringUtil.getBean网上实现有很多,可以自行搜索;
            registry = SpringUtil.getBean(MeterRegistry.class);
        }
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        getRegistry();
        //记录接口开始调用的时间
        startTime = System.currentTimeMillis();
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //统计调用次数
        registry.counter(CUSTOM_KPI_NAME_COUNTER,"uri", request.getRequestURI(), "method", request.getMethod(),
                "status", response.getStatus() + "", "exception", ex == null ? "" : ex.getMessage(), "outcome", response.getStatus() == 200 ? "SUCCESS" : "CLIENT_ERROR").increment();
        //统计单次耗时
        registry.timer(CUSTOM_KPI_NAME_TIMER,"uri", request.getRequestURI(), "method", request.getMethod(),
                "status", response.getStatus() + "", "exception", ex == null ? "" : ex.getMessage(), "outcome", response.getStatus() == 200 ? "SUCCESS" : "CLIENT_ERROR").record(System.currentTimeMillis() - startTime, TimeUnit.MILLISECONDS);
        //统计调用成功率,根据过滤Counter对象,获取计数
        Collection<Meter> meters = registry.get(CUSTOM_KPI_NAME_COUNTER).tag("uri", request.getRequestURI()).tag("method", request.getMethod()).meters();
        double total = 0;
        double success = 0;
        for (Meter meter : meters) {
            Counter counter = (Counter) meter;
            total += counter.count();
            String status = meter.getId().getTag("status");
            if (status.equals("200")){
                success+= counter.count();
            }
        }
        //保存对应的成功率到Map中
        String key = request.getMethod() + request.getRequestURI();
        gaugeNumber.setPercent(key, Double.valueOf(success / total * 100L));
        registry.gauge(CUSTOM_KPI_NAME_SUMMARY, Tags.of("uri", request.getRequestURI(), "method", request.getMethod()), gaugeNumber, new ToDoubleFunction<GaugeNumber>() {
            @Override
            public double applyAsDouble(GaugeNumber value) {
                return value.getPercent(key);
            }
        });
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
    // gauge监控某个对象,所以用内部类替代,然后根据tag标签区分对应的成功率;key 为 method + uri
    class GaugeNumber {
        Map<String,Double> map = new HashMap<>();

        public Double getPercent(String key) {
            return map.get(key);
        }

        public void setPercent(String key, Double percent) {
            map.put(key, percent);
        }
    }
}
注册自定义拦截器给Spring
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CustomInterceptors implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CustomInterceptor()).addPathPatterns("/**");
    }
}

大功告成,启动程序测试吧

使用AOP记录监控指标

自定义指标注解 
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodMetrics {
    String name() default "";
    String desc() default "";
    String[] tags() default {};
    //是否记录时间间隔
    boolean withoutDuration() default false;
}
切面实现
@Aspect
public class PrometheusAnnotationAspect {

    @Autowired
    private MeterRegistry meterRegistry;

    @Pointcut("@annotation(com.smac.prometheus.annotation.MethodMetrics)")
    public void pointcut() {}

    @Around(value = "pointcut()")
    public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
        Method targetMethod = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Method currentMethod = ClassUtils.getUserClass(joinPoint.getTarget().getClass()).getDeclaredMethod(targetMethod.getName(), targetMethod.getParameterTypes());
        if (currentMethod.isAnnotationPresent(MethodMetrics.class)) {
            MethodMetrics methodMetrics = currentMethod.getAnnotation(MethodMetrics.class);
            return processMetric(joinPoint, currentMethod, methodMetrics);
        } else {
            return joinPoint.proceed();
        }
    }

    private Object processMetric(ProceedingJoinPoint joinPoint, Method currentMethod, MethodMetrics methodMetrics) {
        String name = methodMetrics.name();
        if (!StringUtils.hasText(name)) {
            name = currentMethod.getName();
        }
        String desc = methodMetrics.desc();
        if (!StringUtils.hasText(desc)) {
            desc = currentMethod.getName();
        }
        //不需要记录时间
        if (methodMetrics.withoutDuration()) {
            Counter counter = Counter.builder(name).tags(methodMetrics.tags()).description(desc).register(meterRegistry);
            try {
                return joinPoint.proceed();
            } catch (Throwable e) {
                throw new IllegalStateException(e);
            } finally {
                counter.increment();
            }
        }
        //需要记录时间(默认)
        Timer timer = Timer.builder(name).tags(methodMetrics.tags()).description(desc).register(meterRegistry);
        return timer.record(() -> {
            try {
                return joinPoint.proceed();
            } catch (Throwable e) {
                throw new IllegalStateException(e);
            }
        });
    }
}
在需要记监控的地方加上这个注解
@MethodMetrics(name="sms_send",tags = {"vendor"})
public void send(String mobile, SendMessage message) throws Exception {
    //do something
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2223959.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java.9--集合

一、Collection接口 -->单列集合&#xff08;共享给大家&#xff09; .add();把给定的对象添加到当前集合中 clear();清空集合中所有的元素 remove();把给定的对象在当前集合中删除 contains();判断当前集合中是否包含给定的对象 isEmpty();判断当前集合是否为空 siz…

SQL注入之sqlilabs靶场21-30题

重点插入&#xff1a;html表 第二十一题 分析过程&#xff1a;&#xff08;没有正确的账号密码是否能拿到Cookie&#xff1f;最后注释好像只能使用#&#xff0c;--好像无法注释&#xff09; 查看源码 这里输入账号密码处被过滤了 但Cookie被base64编码了 可以从Cookie入手 …

智联招聘×Milvus:向量召回技术提升招聘匹配效率

01. 业务背景 在智联招聘平台&#xff0c;求职者和招聘者之间的高效匹配至关重要。招聘者可以发布职位寻找合适的人才&#xff0c;求职者则通过上传简历寻找合适的工作。在这种复杂的场景中&#xff0c;我们的核心目标是为双方提供精准的匹配结果。在搜索推荐场景下&#xff0c…

Ollama+Open WebUI,windows部署一个本地AI

在Ollama官网下载&#xff0c;点击DownLoad 下载完之后进行安装&#xff0c;配置环境变量&#xff0c;完成后打开CMD命令行工具测试 运行并下载模型 之后选择Open WebUI作为图形化界面 &#x1f680; Getting Started | Open WebUI 运行Docker命令 docker run -d -p 3000:80…

【Sublime Text】设置中文 最新最详细

在编程的艺术世界里&#xff0c;代码和灵感需要寻找到最佳的交融点&#xff0c;才能打造出令人为之惊叹的作品。而在这座秋知叶i博客的殿堂里&#xff0c;我们将共同追寻这种完美结合&#xff0c;为未来的世界留下属于我们的独特印记。 【Sublime Text】设置中文 最新最详细 开…

万字图文实战:从0到1构建 UniApp + Vue3 + TypeScript 移动端跨平台开源脚手架

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f343; vue-uniapp-template &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f…

团结引擎内置 AI 助手团结 Muse Chat 测试版上线!新功能怎么用?能做什么?

在开发过程中&#xff0c;快速获得精准的技术支持能够有效提高开发效率。生成式 AI 的出现为实现实时技术支持提供了新的机会。Unity 中国积极探索 AI 在开发中的应用&#xff0c;并在团结引擎 1.3.0 版本中上线了新功能&#xff1a;团结 Muse Chat。 团结 Muse Chat 是 Unity…

【linux】服务器Ubuntu20.04安装cuda11.8教程

【linux】服务器Ubuntu20.04安装cuda11.8教程 文章目录 【linux】服务器Ubuntu20.04安装cuda11.8教程到官网找到对应版本下载链接终端操作cudnn安装到官网下载下载后解压进入解压后的目录&#xff1a;将头文件复制到 /usr/local/cuda/include/ 目录&#xff1a;将库文件复制到 …

Matlab学习02-matlab中的数据显示格式及符号变量

目录 一&#xff0c;关系运算和逻辑运算 二&#xff0c;变量 三&#xff0c;数据显示格式 四&#xff0c;符号运算 1&#xff0c;创建符号变量 2&#xff0c;数值矩阵转换成符号矩阵 一&#xff0c;关系运算和逻辑运算 在matlab中&#xff0c;只要数值不是 &#xff0…

Lucas带你手撕机器学习——岭回归

岭回归&#xff08;Ridge Regression&#xff09; 一、背景与引入 在进行线性回归分析时&#xff0c;我们常常面临多重共线性的问题。多重共线性指的是自变量之间高度相关&#xff0c;这会导致回归系数的不稳定性&#xff0c;使得模型的预测能力降低。传统的线性回归通过最小…

【R + Python】iNaturalist 网站图片下载 inat api

文章目录 一、iNaturalist 简介二、R语言API&#xff1a;rinat三、示例3.1 获取观测数据3.2 绘制可视化图像函数用法 3.4 在区域网格中搜索3.5 下载图片3.51 提取图片 url3.52 下载图片: R语言3.53 下载图片: python 四、获取详细rinat包的文档 一、iNaturalist 简介 &#x1…

微服务网关Zuul

一、Zuul简介 Zuul是Netflix开源的微服务网关&#xff0c;包含对请求的路由和过滤两个主要功能。 1&#xff09;路由功能&#xff1a;负责将外部请求转发到具体的微服务实例上&#xff0c;是实现外部访问统一入口的基础。 2&#xff09;过滤功能&#xff1a;负责对请求的过程…

ReactOS系统中搜索给定长度的空间地址区间中的二叉树

搜索给定长度的空间地址区间 //搜索给定长度的空间地址区间 MmFindGap MmFindGapTopDown PVOID NTAPI MmFindGap(PMADDRESS_SPACE AddressSpace,ULONG_PTR Length,ULONG_PTR Granularity,BOOLEAN TopDown );PMADDRESS_SPACE AddressSpace,//该进程用户空间 ULONG_PTR Length,…

spring整合使用xml方式整合Druid数据源连接池

1.普通的JDBC数据库连接使用 DriverManager 来获取&#xff0c;每次向数据库建立连接的时候都要将 Connection加载到内存中&#xff0c;再验证用户名和密码(得花费0.05s&#xff5e;1s的时间)。需要数据库连接的时候&#xff0c;就向数据库要求 一个&#xff0c;执行完成后再断…

2024 年 MathorCup妈杯A题台风的分类与预测论文首发+代码分享

基于多模型方法的台风分类、路径预测及登陆后降水影响分析 摘要 台风作为全球最严重的自然灾害之一&#xff0c;具有极大的破坏性和复杂性&#xff0c;其预测和分类具有重要意义。本文基于历史台风数据&#xff0c;针对台风的特征分类、路径预测以及登陆后的降水量和风速变化…

linux网络编程5——Posix API和网络协议栈,使用TCP实现P2P通信

文章目录 Posix API和网络协议栈&#xff0c;使用TCP实现P2P通信1. socket()2. bind()3. listen()4. connect()5. accept()6. read()/write(), recv()/send()7. 内核tcp数据传输7.1 TCP流量控制7.2 TCP拥塞控制——慢启动/拥塞避免/快速恢复/快速重传 8. shutdown()9. close()9…

Jvm中的堆和栈

JVM中的堆和栈分别存放不同的数据类型和内容。 ‌栈&#xff08;Stack&#xff09;‌&#xff1a; 存储基本数据类型&#xff08;如int, char, boolean等&#xff09;和对象的引用。存储局部变量、方法调用、程序运行状态、方法返回值等。每个线程都有一个独立的线程栈&#…

【 IC每日一题】

IC每日一题 1 八股题&#xff1a;低功耗设计方法1.1 功耗类型1.1.1 动态功耗1.1.2 静态功耗 1.2 SoC低功耗设计方法1.2.1 基于时钟clock的低功耗设计1.2.2 基于电压域voltage的低功耗设计1.2.3 多阈值库1.2.4 RTL低功耗设计 2 手撕题&#xff1a;序列检测2.1 移位寄存器写法2.2…

ClickHouse 3节点集群安装

ClickHouse 简介 ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS)。 官方网站&#xff1a;https://clickhouse.com/ 项目地址&#xff1a;https://github.com/ClickHouse/ClickHouse 横向扩展集群介绍 此示例架构旨在提供可扩展性。它包括三个节点&#xff…

ffmpeg视频滤镜: 色温- colortemperature

滤镜简述 colortemperature 官网链接 》 FFmpeg Filters Documentation 这个滤镜可以调节图片的色温&#xff0c;色温值越大显得越冷&#xff0c;可以参考一下下图&#xff1a; 咱们装修的时候可能会用到&#xff0c;比如选择灯还有地板的颜色的时候&#xff0c;选暖色调还是…