【云原生•监控】Micrometer打造SpringBoot服务的可观测能力
概述
「近两年随着云原生微服务的流行,可观测性概念火热起来受到大家的追捧。微服务将一个系统拆分成多个服务,云原生给基础设施层带来的变革进行降本增效,可以看到一个相对简单的单体系统已经变得非常复杂,想要了解下内部运行健康状况如何是比较困难的,出现问题的时候也往往让人摸不着头脑。这时候就有人提出了可观测性的概念,可观测性是个比较大的概念就像是我们开发人员有了透视能力一样一眼可以看穿系统的内部运行状况,当然这是一种比较理想的状态。」
**「基于这个背景下,目前越来越多的产品开始关注并将具有可观测能力的特性纳入到自己产品规划中。」**比如,zookeeper 之前监控是要基于 jmx-exporer 监控,从3.6.0版本开始官网显示已原生支持提供Prometheus格式指标接口(见下图),官网同时提供了 Grafana Dashboard,具体详见:zookeeper.apache.org/doc/r3.8.2/…
RabbitMQ 从 3.8.0 开始,也开始提供内置的 Prometheus、Grafana 支持,官网明确建议将该特性使用到生产环境中(见下图),监控指标说明以及官网提供的Grafana Dashboard 详见:www.rabbitmq.com/prometheus.…
还比如 SkyWalking、Apollo、Nacos 等等这些产品,最新版本都已经原生内置支持 Prometheus 监控指标。「所以,在云原生微服务时代下,可观测性会被越来越多的产品作为一项重要的基础能力进行建设。或者,我们再进行产品选型时,是否具备可观测性也是很重要的一项参考标准。」
Micrometer
而对于 Java 开发领域来说,就有这么一个可观测性神器 Micrometer ,或许你没怎么听过它名字,但是大部分产品中都有它的身影,因为,从 spring-boot-actuator 2.x 版本开始已经默认集成了 Micrometer 组件,而且 Micrometer 是和 Java 生态界大名鼎鼎的 Spring、Spring Boot 框架都是由 Pivotal 公司出品。
**「官网对Micrometer定位:Vendor-neutral application observability facade。」**通过提供抽象、强大、易用的抽象门面接口,可以轻松的对接包括Prometheus、JMX、Influxdb等在内的近20种监控系统,它的作用和 Slf4j 类似,只不过 Slf4j 关注日志,而 Micrometer 关注应用指标(application metrics)。
image-20230812164554851
❝
比如日志框架logback、log4j、log4j2、jdk-log、common-logging等,这么多日志框架很容易造成混乱,比如开发一个系统A选型使用log4j2,系统A依赖了一个第三方组件,但是第三方组件使用logback日志框架,这就会导致系统A中有log4j2和logback两种日志框架,再一个就是这两种日志框架基于各自配置输出到不同日志文件中,就会导致日志割裂不便于查看。
还存在另一个问题,系统A使用lo4j2日志框架,但是突然领导说要切换到logback日志框架上,这个需求可能是灾难性的,系统中那么多使用日志输出的地方API接口都要调整。
所以,Slf4j日志框架的出现就是为了解决上述问题。Slf4j提供抽象的日志API接口,系统只需要面向日志API接口编程,而不是具体的日志实现框架编程,Slf4j会提供一种机制和具体的日志实现框架进行绑定,比如可以绑定到logback日志框架,也可以绑定到log4j2日志框架,日志框架切换对开发人员是无感知的,上层开发人员都是基于Slf4j通用接口编程,由Slf4j屏蔽底层不同日志框架的接口实现差异。这就是**「设计原则中常说的面向接口编程。」**
❞
「所以,综合来看:Micrometer组件提供基于JVM类应用的性能监控,它提供一种类似 slf4j 门面的设计模式,基于它提供的通用接口开发的监控指标可以轻松对接多种监控系统。它是用来定标准规范的,所以需要特别关注。」
「Micrometer与Spring Boot Actuator关系」
「在spring2.x之后,Spring boot actuator直接集成Micrometer用来实现监控。但是在spring 2之前,Spring boot actuator 并没有使用Micrometer,而是类似如自己之前写的统计监控指标的sdk,使用了dropwizard-metrics。」
prometheus监控配置
spring-boot-actuator 2.x 组件默认已经集成 micrometer,所以,基于spring-boot 2.x 版本的项目开启prometheus监控非常方便,只需要如下三步:
1、添加 spring-boot-starter-actuator 依赖:
org.springframework.boot
spring-boot-starter-actuator
2、添加 micrometer-registry-prometheus 依赖:
io.micrometer
micrometer-registry-prometheus
❝
micrometer组件只是关注指标,micrometer-registry-prometheus则将指标对接prometheus监控系统。
❞
3、打开端点访问:
management:
endpoints:
web:
exposure:
include: prometheus
❝
和actuator 1.x 不同,actuator 2.x 的大多数端点默认被禁掉,需要开启才能访问。
❞
然后访问http://ip:port/actuator/prometheus就可以查看到应用监控指标,不需要任何定制开发,默认已经包含大部分常见指标,如http请求、jvm内存、gc、tomcat等等相关指标。
实现原理
基于 spring-boot 2.x 构建的项目,基本上添加依赖、打开端点开关即可使用prometheus监控,并且默认已经暴露出大部分常见指标,基本不需要任何开发即可获取到丰富的监控指标,下面就来从底层源码看下它的实现原理。
依赖关系
自动装配
spring-boot-actuator-autoconfigure 从命名规范上来说,该模块主要用于 spring-boot 自动装配,其中该模块下metrics包就是对关于指标监控相关类进行自动装配(见下图)。
从类名称上来看,这里涉及到大量指标装配,如jvm、kafka、log4j2、logback、rabbitmq、redis缓存、jdbc、web等等相关。
JVM指标
Jvm 相关指标通过 JvmMetricsAutoConfiguration 配置类自动装配,涉及到5类指标,分别对应下面5个类:
「Gc信息(JvmGcMetrics):」
- jvm_gc_memory_allocated_bytes_total:counter类型指标,在一次 GC之后到下一次 GC 之前,年轻代增加的大小,可以反应出内存分配的快慢,分配越快对 gc 压力就会越大,因为需要不停的执行 gc 操作。
- jvm_gc_memory_promoted_bytes_total:ccounter类型指标,在一次 GC之前到 GC 之后 老年代内存池正增加的大小,可以反应出 gc 晋升到老年代的内存大小。
- jvm_gc_pause_seconds:summary类型指标,gc耗时信息,jvm_gc_pause_seconds_count对应的就是gc次数,jvm_gc_pause_seconds_sum对应的就是gc耗时累积。
「Jvm内存(JvmMemoryMetrics):」
- jvm_memory_used_bytes:jvm已使用内存
- jvm_memory_committed_bytes:可供jvm使用的已提交的内存量
- jvm_memory_max_bytes: jvm最大可以申请的内存量
❝
committed是当前可使用的内存大小(包括已使用的),committed >= used。committed不足时jvm向系统申请,committed {
if (executor instanceof ThreadPoolTaskExecutor) {
monitor(registry, safeGetThreadPoolExecutor((ThreadPoolTaskExecutor) executor), beanName);
}
else if (executor instanceof ThreadPoolTaskScheduler) {
monitor(registry, safeGetThreadPoolExecutor((ThreadPoolTaskScheduler) executor), beanName);
}
});
}
注意:这里只对spring中定义的 ThreadPoolTaskExecutor、ThreadPoolTaskScheduler 这两种类型的线程池进行了指标监控,平时可能基于ThreadPoolExecutor 创建线程池方式也比较常见:
public void executorInit() { String poolName = "executor-task"; ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 50, 200, 60, TimeUnit.SECONDS, new ArrayBlockingQueue(5000), r -> new Thread(r, poolName + r.hashCode()), new ThreadPoolExecutor.DiscardOldestPolicy()); }
对于这种线程池我们也可以自定义配置类将 ExecutorService 类型的线程池也指标监控:
@Configuration public class TaskExecutorMetricsAutoConfiguration { @Autowired public void bindTaskExecutorsToRegistry(Map executors, MeterRegistry registry) { executors.forEach((beanName, executor) -> { if (executor instanceof ExecutorService) { new ExecutorServiceMetrics((ExecutorService) executor, beanName, Collections.emptyList()).bindTo(registry); } }); } }
「线程池指标:」
- executor_pool_max_threads:线程池最大线程数
- executor_queued_tasks:线程池等待队列中等待执行的排队任务数
- executor_queue_remaining_tasks:线程池等待队列剩余容量
- executor_pool_core_threads:线程池核心线程数
- executor_pool_size_threads:线程池中当前线程数
- executor_active_threads:线程池中正在干活状态的线程数
- executor_completed_tasks_total:线程池已完成的总任务数