性能文章>【云原生•监控】Micrometer打造SpringBoot服务的可观测能力>

【云原生•监控】Micrometer打造SpringBoot服务的可观测能力原创

317100

【云原生•监控】Micrometer打造SpringBoot服务的可观测能力

概述

「近两年随着云原生微服务的流行,可观测性概念火热起来受到大家的追捧。微服务将一个系统拆分成多个服务,云原生给基础设施层带来的变革进行降本增效,可以看到一个相对简单的单体系统已经变得非常复杂,想要了解下内部运行健康状况如何是比较困难的,出现问题的时候也往往让人摸不着头脑。这时候就有人提出了可观测性的概念,可观测性是个比较大的概念就像是我们开发人员有了透视能力一样一眼可以看穿系统的内部运行状况,当然这是一种比较理想的状态。」

「基于这个背景下,目前越来越多的产品开始关注并将具有可观测能力的特性纳入到自己产品规划中。」比如,zookeeper 之前监控是要基于 jmx-exporer 监控,从3.6.0版本开始官网显示已原生支持提供Prometheus格式指标接口(见下图),官网同时提供了 Grafana Dashboard,具体详见:https://zookeeper.apache.org/doc/r3.8.2/zookeeperMonitor.html

RabbitMQ 从 3.8.0 开始,也开始提供内置的 Prometheus、Grafana 支持,官网明确建议将该特性使用到生产环境中(见下图),监控指标说明以及官网提供的Grafana Dashboard 详见:https://www.rabbitmq.com/prometheus.html。

还比如 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)。

比如日志框架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 依赖:

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

2、添加 micrometer-registry-prometheus 依赖:

<dependency>
 <groupId>io.micrometer</groupId>
 <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

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 <= max。

「Jvm线程(JvmThreadMetrics):」

  • jvm_threads_peak_threads:线程峰值
  • jvm_threads_daemon_threads:守护线程
  • jvm_threads_live_threads:存活线程
  • jvm_threads_states_thread:不同状态的线程,state分为:new、runnable、blocked、waiting、timed-waiting、terminated

「类加载信息(ClassLoaderMetrics):」

  • jvm_classes_loaded_classes:已加载类个数
  • jvm_classes_unloaded_classes_total:已卸载类总数

日志指标

日志指标自动装配配置类主要包括两个:LogbackMetricsAutoConfiguration和Log4J2MetricsAutoConfiguration,从名字很明显是对使用率最高的两种日志实现框架logback和log4j2。

「logback指标:」

  • logback_events_total:counter类型指标,日志事件数,通过level标签标识日志级别,分为:trace、debug、info、warn、error。

「log4j2指标:」

  • log4j2_events_total:counter类型指标,日志事件数,通过level标签标识日志级别,分为:trace、debug、info、warn、error和fatal。

web指标

web指标主要分为5类,首先是 server 容器类指标,包括 tomcat 和 jetty,然后就是web应用类型是基于 servlet 还是 reactive,client 则是提供对如RestTemplate 客户端访问工具提供监控指标支持。

这里主要看下最为常见的 Tomcat 和 Servlet 相关监控指标。

tomcat指标

Tomcat 监控指标通过自动装配配置类加载,主要核心逻辑都在 TomcatMetrics 这个类中:

「GlobalRequests指标:」

  • tomcat_global_sent_bytes_total:tomcat响应请求总字节大小
  • tomcat_global_received_bytes_total:tomcat接收请求总字节大小
  • tomcat_global_error_total:请求失败量
  • tomcat_global_request_max_seconds:最大响应时间

注意,需要配置如下才能看到这块的指标:

server:
  tomcat:
    mbeanregistry:
      enabled: true

「Servlet指标:」

  • tomcat_servlet_request_seconds_sum:请求总耗时
  • tomcat_servlet_request_max_seconds:请求最大耗时
  • tomcat_servlet_error_total:counter类型指标,错误 请求量

同理,这块指标也需要开启 server.tomcat.mbeanregistry.enabled=true。

「Tomcat线程指标:」

  • tomcat_threads_current_threads:线程池中当前活着的线程数。
  • tomcat_threads_busy_threads:线程池中这些活着的线程正在干活的就是忙碌线程(busy)。
  • tomcat_threads_config_max_threads:配置的最大线程数,默认200,可通过server.tomcat.threads.max配置项调整。
  • tomcat_connections_config_max_connections:最大连接数,可通过配置server.tomcat.max-connections配置项调整。
  • tomcat_connections_current_connections:当前连接数

tomcat线程、连接配置示例:

server:
  tomcat:
    mbeanregistry:
      enabled: true
    max-connections: 2000 #最大连接数
    threads:
      min-spare: 20 #核心线程数
      max: 200 #最大线程数

servlet指标

servlet 指标就是平时使用频率较高的http请求相关指标,通过 WebMvcMetricsAutoConfiguration 自动装配注入 WebMvcMetricsFilter:

@Bean
public FilterRegistrationBean<WebMvcMetricsFilter> webMvcMetricsFilter(MeterRegistry registry,
  WebMvcTagsProvider tagsProvider)
 
{
 ServerRequest request = this.properties.getWeb().getServer().getRequest();
 WebMvcMetricsFilter filter = new WebMvcMetricsFilter(registry, tagsProvider, request.getMetricName(),
   request.getAutotime());
 FilterRegistrationBean<WebMvcMetricsFilter> registration = new FilterRegistrationBean<>(filter);
 registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
 registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
 return registration;
}

WebMvcMetricsFilter 是servlet规范中定义的 Filter,这样http请求进入到servlet容器后都会经过filter拦截,获取到请求method、uri、响应状态码、响应时间等信息,这样就可以在此埋点通过指标方式暴露出来。

「servlet指标:」

  • http_server_requests_seconds_count:http请求总次数
  • http_server_requests_seconds_sum:http请求总耗时
  • http_server_requests_seconds_max:http请求响应时间最大值

「注意:这些指标通过uri标签标识接口地址,status标识响应状态码,通过method标识GET、POST请求等。」

线程池指标

spring-boot-actuator 2.6.0 版本开始引入 TaskExecutorMetricsAutoConfiguration 自动装配类注入 ExecutorServiceMetrics 实现对线程池指标监控。线程池在工作开发中使用频率非常高,提供对线程池监控需求非常实用:

@Autowired
public void bindTaskExecutorsToRegistry(Map<String, Executor> executors, MeterRegistry registry) {
 executors.forEach((beanName, executor) -> {
  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<String, Executor> 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:线程池已完成的总任务数

 

[更多云原生可观测内容,请关注微信公众号:云原生生态实验室]

 

 

点赞收藏
云原生生态实验室

关注公众号@reactor_2020

请先登录,感受更多精彩内容
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

Redis stream 用做消息队列完美吗?

Redis stream 用做消息队列完美吗?

Netty源码解析:writeAndFlush

Netty源码解析:writeAndFlush

0
0