zl程序教程

您现在的位置是:首页 >  云平台

当前栏目

3W字干货深入分析基于Micrometer和Prometheus实现度量和监控的方案(下)

监控 实现 基于 方案 干货 Prometheus 深入分析 度量
2023-09-27 14:25:57 时间
最近线上的项目使用了spring-actuator做度量统计收集,使用Prometheus进行数据收集,Grafana进行数据展示,用于监控生成环境机器的性能指标和业务数据指标。一般,我们叫这样的操作为 埋点 。SpringBoot中的依赖spring-actuator中集成的度量统计API使用的框架是Micrometer,官网是micrometer.io。在实践中发现了业务开发者滥用了Micrometer的度量类型Counter,导致无论什么情况下都只使用计数统计的功能。这篇文章就是基于Micrometer分析其他的度量类型API的作用和适用场景。
基于SpirngBoot、Prometheus、Grafana集成


集成了Micrometer框架的JVM应用使用到Micrometer的API收集的度量数据位于内存之中 因此 需要额外的存储系统去存储这些度量数据 需要有监控系统负责统一收集和处理这些数据 还需要有一些UI工具去展示数据 「一般情况下大佬或者老板只喜欢看炫酷的仪表盘或者动画」。常见的存储系统就是时序数据库 主流的有Influx、Datadog等。比较主流的监控系统 主要是用于数据收集和处理 就是Prometheus 一般叫普罗米修斯 下面就这样叫吧 。而展示的UI目前相对用得比较多的就是Grafana。另外 Prometheus已经内置了一个时序数据库的实现 因此 在做一套相对完善的度量数据监控的系统只需要依赖目标JVM应用 Prometheus组件和Grafana组件即可。下面花一点时间从零开始搭建一个这样的系统 之前写的一篇文章基于Windows系统 操作可能跟生产环境不够接近 这次使用CentOS7。


SpirngBoot中使用Micrometer


SpringBoot中的spring-boot-starter-actuator依赖已经集成了对Micrometer的支持 其中的metrics端点的很多功能就是通过Micrometer实现的 prometheus端点默认也是开启支持的 实际上actuator依赖的spring-boot-actuator-autoconfigure中集成了对很多框架的开箱即用的API 其中prometheus包中集成了对Prometheus的支持 使得使用了actuator可以轻易地让项目暴露出prometheus端点 使得应用作为Prometheus收集数据的客户端 Prometheus 服务端软件 可以通过此端点收集应用中Micrometer的度量数据。


微信截图_20220513114835.pngjvm-m-1.png

我们先引入spring-boot-starter-actuator和spring-boot-starter-web 实现一个Counter和Timer作为示例。依赖


 dependencyManagement 

 dependencies 

 dependency 

 groupId org.springframework.boot /groupId 

 artifactId spring-boot-dependencies /artifactId 

 version 2.1.0.RELEASE /version 

 type pom /type 

 scope import /scope 

 /dependency 

 /dependencies 

 /dependencyManagement 

 dependencies 

 dependency 

 groupId org.springframework.boot /groupId 

 artifactId spring-boot-starter-web /artifactId 

 /dependency 

 dependency 

 groupId org.springframework.boot /groupId 

 artifactId spring-boot-starter-actuator /artifactId 

 /dependency 

 dependency 

 groupId org.springframework.boot /groupId 

 artifactId spring-boot-starter-aop /artifactId 

 /dependency 

 dependency 

 groupId org.projectlombok /groupId 

 artifactId lombok /artifactId 

 version 1.16.22 /version 

 /dependency 

 dependency 

 groupId io.micrometer /groupId 

 artifactId micrometer-registry-prometheus /artifactId 

 version 1.1.0 /version 

 /dependency 

 /dependencies 

复制代码


接着编写一个下单接口和一个消息发送模块 模拟用户下单之后向用户发送消息


//实体

 Data

public class Message {

 private String orderId;

 private Long userId;

 private String content;

 Data

public class Order {

 private String orderId;

 private Long userId;

 private Integer amount;

 private LocalDateTime createTime;

//控制器和服务类

 RestController

public class OrderController {

 Autowired

 private OrderService orderService;

 PostMapping(value /order )

 public ResponseEntity Boolean createOrder( RequestBody Order order) {

 return ResponseEntity.ok(orderService.createOrder(order));

 Slf4j

 Service

public class OrderService {

 private static final Random R new Random();

 Autowired

 private MessageService messageService;

 public Boolean createOrder(Order order) {

 //模拟下单

 try {

 int ms R.nextInt(50) 50;

 TimeUnit.MILLISECONDS.sleep(ms);

 log.info( 保存订单模拟耗时{}毫秒... , ms);

 } catch (Exception e) {

 //no-op

 //记录下单总数

 Metrics.counter( order.count , order.channel , order.getChannel()).increment();

 //发送消息

 Message message new Message();

 message.setContent( 模拟短信... 

 message.setOrderId(order.getOrderId());

 message.setUserId(order.getUserId());

 messageService.sendMessage(message);

 return true;

 Slf4j

 Service

public class MessageService implements InitializingBean {

 private static final BlockingQueue Message QUEUE new ArrayBlockingQueue (500);

 private static BlockingQueue Message REAL_QUEUE;

 private static final Executor EXECUTOR Executors.newSingleThreadExecutor();

 private static final Random R new Random();

 static {

 REAL_QUEUE Metrics.gauge( message.gauge , Tags.of( message.gauge , message.queue.size ), QUEUE, Collection::size);

 public void sendMessage(Message message) {

 try {

 REAL_QUEUE.put(message);

 } catch (InterruptedException e) {

 //no-op

 Override

 public void afterPropertiesSet() throws Exception {

 EXECUTOR.execute(() - {

 while (true) {

 try {

 Message message REAL_QUEUE.take();

 log.info( 模拟发送短信,orderId:{},userId:{},内容:{},耗时:{}毫秒 , message.getOrderId(), message.getUserId(),

 message.getContent(), R.nextInt(50));

 } catch (Exception e) {

 throw new IllegalStateException(e);

//切面类

 Component

 Aspect

public class TimerAspect {

 Around(value execution(* club.throwable.smp.service.*Service.*(..)) )

 public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

 Signature signature joinPoint.getSignature();

 MethodSignature methodSignature (MethodSignature) signature;

 Method method methodSignature.getMethod();

 Timer timer Metrics.timer( method.cost.time , method.name , method.getName());

 ThrowableHolder holder new ThrowableHolder();

 Object result timer.recordCallable(() - {

 try {

 return joinPoint.proceed();

 } catch (Throwable e) {

 holder.throwable 

 return null;

 if (null ! holder.throwable) {

 throw holder.throwable;

 return result;

 private class ThrowableHolder {

 Throwable throwable;

复制代码


yaml的配置如下


server:

 port: 9091

management:

 server:

 port: 10091

 endpoints:

 web:

 exposure:

 include: * 

 base-path: /management 

复制代码


注意多看spring官方文档关于Actuator的详细描述 在SpringBoot2.x之后 配置Web端点暴露的权限控制和SpringBoot1.x有很大的不同。总结一下就是 除了shutdown端点之外 其他端点默认都是开启支持的 「这里仅仅是开启支持 并不是暴露为Web端点 端点必须暴露为Web端点才能被访问」 禁用或者开启端点支持的配置方式如下


management.endpoint.${端点ID}.enabled true/false

复制代码


可以查看actuator-api文档查看所有支持的端点的特性 这个是2.1.0.RELEASE版本的官方文档 不知道日后链接会不会挂掉。端点只开启支持 但是不暴露为Web端点 是无法通过http://{host}:{management.port}/{management.endpoints.web.base-path}/{endpointId}访问的。暴露监控端点为Web端点的配置是


management.endpoints.web.exposure.include info,health

management.endpoints.web.exposure.exclude prometheus

复制代码


management.endpoints.web.exposure.include用于指定暴露为Web端点的监控端点 指定多个的时候用英文逗号分隔。

management.endpoints.web.exposure.exclude用于指定不暴露为Web端点的监控端点 指定多个的时候用英文逗号分隔。management.endpoints.web.exposure.include默认指定的只有info和health两个端点 我们可以直接指定暴露所有的端点 management.endpoints.web.exposure.include * 如果采用YAML配置 「记得要在星号两边加上英文单引号」。暴露所有Web监控端点是一件比较危险的事情 如果需要在生产环境这样做 请务必先确认http://{host}:{management.port}不能通过公网访问 也就是监控端点访问的端口只能通过内网访问 这样可以方便后面说到的Prometheus服务端通过此端口收集数据 。


Prometheus的安装和配置


Prometheus目前的最新版本是2.5 鉴于笔者当前没深入玩过Docker 这里还是直接下载它的压缩包解压安装。


wget https://github.com/prometheus/prometheus/releases/download/v2.5.0/prometheus-2.5.0.linux-amd64.tar.gz

tar xvfz prometheus-*.tar.gz

cd prometheus-*

复制代码


先编辑解压出来的目录下的Prometheus配置文件prometheus.yml 主要修改scrape_configs节点的属性


scrape_configs:

 # The job name is added as a label job job_name to any timeseries scraped from this config.

 - job_name: prometheus 

 # metrics_path defaults to /metrics 

 # scheme defaults to http .

 # 这里配置需要拉取度量信息的URL路径 这里选择应用程序的prometheus端点

 metrics_path: /management/prometheus

 static_configs:

 # 这里配置host和port

 - targets: [ localhost:10091 ]

复制代码


配置拉取度量数据的路径为localhost:10091/management/metrics 此前记得把前一节提到的应用在虚拟机中启动。接着启动Prometheus应用


# 可选参数 --storage.tsdb.path 存储数据的路径 默认路径为./data

./prometheus --config.file prometheus.yml

复制代码


Prometheus引用的默认启动端口是9090 启动成功后 日志如下


微信截图_20220513114853.png

jvm-m-2.png

此时 访问http://${虚拟机host}:9090/targets就能看到当前Prometheus中执行的Job


微信截图_20220513114902.png

jvm-m-3.png

访问http://${虚拟机host}:9090/graph可以查找到我们定义的度量Meter和spring-boot-starter-actuator中已经定义好的一些关于JVM或者Tomcat的度量Meter。我们先对应用的/order接口进行调用 然后查看一下监控前面在应用中定义的order_count_total和method_cost_time_seconds_sum


微信截图_20220513114908.png

jvm-m-4.png


微信截图_20220513114915.pngjvm-m-5.png


可以看到 Meter的信息已经被收集和展示 但是显然不够详细和炫酷 这个时候就需要使用Grafana的UI做一下点缀。


Grafana的安装和使用


Grafana的安装过程如下


wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.3.4-1.x86_64.rpm 

sudo yum localinstall grafana-5.3.4-1.x86_64.rpm

复制代码


安装完成后 通过命令service grafana-server start启动即可 默认的启动端口为3000 通过http://${host}:3000访问即可。初始的账号密码都为admin 权限是管理员权限。接着需要在Home面板添加一个数据源 目的是对接Prometheus服务端从而可以拉取它里面的度量数据。数据源添加面板如下


微信截图_20220513114923.png

jvm-m-6.png


其实就是指向Prometheus服务端的端口就可以了。接下来可以天马行空地添加需要的面板 就下单数量统计的指标 可以添加一个Graph的面板


微信截图_20220513114930.png

jvm-m-7.png


配置面板的时候 需要在基础 General 中指定Title


微信截图_20220513114938.png

jvm-m-9.png


接着比较重要的是Metrics的配置 需要指定数据源和Prometheus的查询语句


微信截图_20220513114946.png

jvm-m-8.png


最好参考一下Prometheus的官方文档 稍微学习一下它的查询语言PromQL的使用方式 一个面板可以支持多个PromQL查询。前面提到的两项是基本配置 其他配置项一般是图表展示的辅助或者预警等辅助功能 这里先不展开 可以去Grafana的官网挖掘一下使用方式。然后我们再调用一下下单接口 过一段时间 图表的数据就会自动更新和展示


微信截图_20220513114953.png

jvm-m-10.png


接着添加一下项目中使用的Timer的Meter 便于监控方法的执行时间 完成之后大致如下


微信截图_20220513115001.png

jvm-m-11.png


上面的面板虽然设计相当粗糙 但是基本功能已经实现。设计面板并不是一件容易的事 如果有需要可以从Github中搜索一下grafana dashboard关键字找现成的开源配置使用或者二次加工后使用。


小结


常言道 工欲善其事 必先利其器。Micrometer是JVM应用的一款相当优异的度量框架 它提供基于Tag和丰富的度量类型和API便于多维度地进行不同角度度量数据的统计 可以方便地接入Prometheus进行数据收集 使用Grafana的面板进行炫酷的展示 提供了天然的spring-boot体系支持。但是 在实际的业务代码中 度量类型Counter经常被滥用 一旦工具被不加思考地滥用 就反而会成为混乱或者毒瘤。因此 这篇文章就是对Micrometer中的各种Meter的使用场景基于个人的理解做了调研和分析 后面还会有系列的文章分享一下这套方案在实战中的经验和踩坑经历。


参考资料

https://micrometer.io/docshttps://grafana.comhttps://prometheus.io


本文完 To be continue c-10-d n-e-20181102 最近有点忙 没办法经常更新



实践教程之基于Prometheus+Grafana的PolarDB-X监控体系 PolarDB-X 为了方便用户体验,提供了免费的实验环境,您可以在实验环境里体验 PolarDB-X 的安装部署和各种内核特性。除了免费的实验,PolarDB-X 也提供免费的视频课程,手把手教你玩转 PolarDB-X 分布式数据库。
Prometheus+SpringBoot应用监控全过程详解 1. Prometheus是什么 Prometheus是一个具有活跃生态系统的开源系统监控和告警工具包。一言以蔽之,它是一套开源监控解决方案
统一观测丨使用 Prometheus 监控云原生网关,我们该关注哪些指标? MSE 云原生网关默认提供了丰富的 Metrics 指标大盘,配合阿里云 Prometheus 监控提供开箱即用的完整可观测性能力,能够帮助用户快捷、高效的搭建自身的微服务网关与对应的可观测体系。
统一观测丨如何使用 Prometheus 监控 MySQL 数据库的瓶颈往往也是整个系统的瓶颈,其重要性不言而喻,所以对于 MySQL 的监控必不可少,及时发现 MySQL 运行中的异常,可以有效提高系统的可用性和用户体验。因此,观测 MySQL 关键指标,实时关注数据库的可用性与性能,成为运维团队的重要任务。
Elasticsearch全观测技术解析与应用(构建日志、指标、APM统一观测平台) 立即下载