当前位置: 首页 > news >正文

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

背景

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

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

 

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: trueendpoints: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*/@BeanMeterRegistryCustomizer<MeterRegistry> metricsConfig() {return registry -> registry.config().namingConvention(new NamingConvention() {@Overridepublic 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);}}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {getRegistry();//记录接口开始调用的时间startTime = System.currentTimeMillis();return HandlerInterceptor.super.preHandle(request, response, handler);}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}@Overridepublic 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>() {@Overridepublic double applyAsDouble(GaugeNumber value) {return value.getPercent(key);}});HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}// gauge监控某个对象,所以用内部类替代,然后根据tag标签区分对应的成功率;key 为 method + uriclass 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 {@Overridepublic 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 {@Autowiredprivate 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
}

相关文章:

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

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

深入解析 Lombok 的实现原理:以 @Builder 为例的实战演示(三)

文章目录 Lombok 的实现原理概述以 Builder 为例&#xff1a;解析 Lombok 如何生成 Builder 模式示例代码&#xff1a;没有 Lombok 的 Builder 模式使用 Lombok 的 Builder 简化代码 Lombok 如何实现 Builder&#xff1a;源码解析案例演示&#xff1a;自定义构造逻辑Lombok 的代…...

SEO基础:什么是SERP?【百度SEO专家】

SEO基础&#xff1a;什么是SERP&#xff1f; 大家好&#xff0c;我是林汉文&#xff08;百度SEO专家&#xff09;&#xff0c;在进行SEO&#xff08;搜索引擎优化&#xff09;时&#xff0c;理解SERP是一个非常重要的基础概念。那么&#xff0c;究竟什么是SERP呢&#xff1f;本…...

HTML5教程(一)- 网页与开发工具

1. 什么是网页 网页 基于浏览器阅读的应用程序&#xff0c;是数据&#xff08;文本、图像、视频、声音、链接等&#xff09;展示的载体常见的是以 .html 或 .htm 结尾的文件 网站 使用 HTML 等制作的用于展示特定内容相关的网页集合。 2. 网页的组成 浏览器 代替用户向服务…...

Java进阶篇设计模式之二 ----- 工厂模式

前言 在上一篇中我们学习了单例模式&#xff0c;介绍了单例模式创建的几种方法以及最优的方法。本篇则介绍设计模式中的工厂模式&#xff0c;主要分为简单工厂模式、工厂方法和抽象工厂模式。 简单工厂模式 简单工厂模式是属于创建型模式&#xff0c;又叫做静态工厂方法模式。…...

考研篇——数据结构王道3.2.2_队列的顺序实现

目录 1.实现方式说明2.代码实现2.12.1.1 代码12.1.2 代码22.1.3 代码3 2.22.2.1 代码42.2.5 代码52.2.6 代码6 总结 1.实现方式说明 多在选择题中考察 队尾指针&#xff08;rear&#xff09;有两种指向方式&#xff1a; 队尾指针指向队尾元素的位置&#xff0c;队尾指针指向…...

从零开始理解 Trie 树:高效字符串存储与查找的利器【自动补全、拼写检查】

题目分析 这道题让我们实现一个 Trie 类&#xff08;也称为前缀树&#xff09;&#xff0c;以便高效地插入和查询字符串。前缀树是一种特殊的树形数据结构&#xff0c;适用于快速存储和检索字符串数据集中的键&#xff0c;比如实现 自动补全 和 拼写检查。 题目要求 Trie 类…...

关于sse、websocket与流式渲染

一、SSE是什么&#xff1f; 网络中的 SSE (Server-Sent Events) 是一种服务器向浏览器单向推送数据的机制&#xff0c;常用于需要实时更新的数据传输&#xff0c;如新闻推送、股票行情、聊天应用等。 SSE 的特点&#xff1a; 单向通信&#xff1a;服务器向客户端推送数据&…...

Python 语法与数据类型详解

Python 语法与数据类型详解 Python 以其简洁易读的语法和丰富多样的数据类型在编程领域占据重要地位。深入理解 Python 的语法和数据类型是掌握这门语言的关键。 一、Python 语法概述 &#xff08;一&#xff09;缩进规则 Python 独特的缩进规则是其语法的重要特征之一。与…...

LeetCode题练习与总结:扁平化嵌套列表迭代器--341

一、题目描述 给你一个嵌套的整数列表 nestedList 。每个元素要么是一个整数&#xff0c;要么是一个列表&#xff1b;该列表的元素也可能是整数或者是其他列表。请你实现一个迭代器将其扁平化&#xff0c;使之能够遍历这个列表中的所有整数。 实现扁平迭代器类 NestedIterato…...

51单片机快速入门之 AD(模数) DA(数模) 转换 2024/10/25

51单片机快速入门之 AD(模数) DA(数模) 转换 2024/10/25 声明:本文图片来源于网络 A模拟信号特点: 电压或者电流 缓慢上升 随着时间连续缓慢上升或下降 D数字信号特点:电压或者电流 保持一段时间的高/低电平 状态 / 突变 (高电压瞬间低电压) 数字电路中 通常将0-1v电压称…...

Typora 、 Minio and PicGo 图床搭建

流程介绍 本地安装Typora笔记工具拥有一台装有docker的服务器配置minio云图床管理控制页面下载PicGo上传工具服务器Docker环境搭建—Ubuntu系统 删除旧docker的所有依赖(非root用户) # 删除docker及安装时自动安装的所有包 sudo apt-get autoremove docker docker-ce docker…...

【计网】UDP Echo Server与Client实战:从零开始构建简单通信回显程序

目录 前言&#xff1a; 1.实现udpserver类 1.1.创建udp socket 套接字 --- 必须要做的 socket&#xff08;&#xff09;讲解 代码实现&#xff1a;​编辑 代码讲解&#xff1a; 1.2.填充sockaddr_in结构 代码实现&#xff1a; 代码解析&#xff1a; 1.3.bind sockfd和…...

微服务网关Zuul

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

BuildCTF线上赛WP

Build::CTF flag不到啊战队--WP 萌新战队&#xff0c;还请多多指教~ 目录 Build::CTF flag不到啊战队--WP Web ez!http find-the-id Pwn 我要成为沙威玛传奇 Misc what is this? 一念愚即般若绝&#xff0c;一念智即般若生 别真给我开盒了哥 四妹&#xff0c;你听…...

《使用Gin框架构建分布式应用》阅读笔记:p143-p207

《用Gin框架构建分布式应用》学习第10天&#xff0c;p143-p207总结&#xff0c;总计65页。 一、技术总结 1.auth0 本人实际工作中未遇到过&#xff0c;mark一下&#xff0c;参考&#xff1a;https://auth0.com/。 2.使用template (1)c.File() (2)router.Static() (3)rou…...

华为网络管理配置实例

目录 组网需求 数据规划 配置思路 操作步骤 结果验证 配置脚本 管理员可以通过eSight网管系统对FW进行监控和管理&#xff0c;接收FW的告警。 组网需求 如图1所示&#xff0c;某企业在网络边界处部署了FW作为安全网关&#xff0c;并部署了eSight网管系统对网络设备进行集中…...

大语言模型数据处理方法(基于llama模型)

文章目录 前言一、基于huggingface的DataCollatorForSeq2Seq方法解读1、DataCollatorForSeq2Seq方法2、batch最长序列填充3、指定长度填充二、构建大语言模型数据加工模块1、数据读取2、数据加工1、数据格式2、预训练(pretrain)数据加工3、微调(sft)数据加工①、sft数据加工…...

爱奇艺大数据多 AZ 统一调度架构

01# 导语 爱奇艺大数据技术广泛应用于运营决策、用户增长、广告分发、视频推荐、搜索、会员营销等场景&#xff0c;为公司的业务增长和用户体验提供了重要的数据驱动引擎。 多年来&#xff0c;随着公司业务的发展&#xff0c;爱奇艺大数据平台已积累了海量数据&#xff0c;这…...

【C++篇】栈的层叠与队列的流动:在 STL 的韵律中探寻数据结构的优雅之舞

文章目录 C 栈与队列详解&#xff1a;基础与进阶应用前言第一章&#xff1a;栈的介绍与使用1.1 栈的介绍1.2 栈的使用1.2.1 最小栈1.2.2 示例与输出 1.3 栈的模拟实现 第二章&#xff1a;队列的介绍与使用2.1 队列的介绍2.2 队列的使用2.2.1 示例与输出 2.3 队列的模拟实现2.3.…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中&#xff0c;我们已经大致实现了rpc服务端的各项功能代…...

Java 8 Stream API 入门到实践详解

一、告别 for 循环&#xff01; 传统痛点&#xff1a; Java 8 之前&#xff0c;集合操作离不开冗长的 for 循环和匿名类。例如&#xff0c;过滤列表中的偶数&#xff1a; List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)

船舶制造装配管理现状&#xff1a;装配工作依赖人工经验&#xff0c;装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书&#xff0c;但在实际执行中&#xff0c;工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...

GitFlow 工作模式(详解)

今天再学项目的过程中遇到使用gitflow模式管理代码&#xff0c;因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存&#xff0c;无论是github还是gittee&#xff0c;都是一种基于git去保存代码的形式&#xff0c;这样保存代码…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)

考察一般的三次多项式&#xff0c;以r为参数&#xff1a; p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]&#xff1b; 此多项式的根为&#xff1a; 尽管看起来这个多项式是特殊的&#xff0c;其实一般的三次多项式都是可以通过线性变换化为这个形式…...