为了监控springboot应用程序的指标

关于最新的Spring Boot 3系列,请参阅这篇文章!

 

概述

    • springbootのアプリケーションのメトリクス監視をしたい

http.client.requests

APIのレスポンスタイムはどれくらいなのか?
APIのリクエスト数はどれくらいなのか?

http.server.requests

内部で使用しているAPIのレスポンスはどれくらいなのか

取得したメトリクスはprometheusやgrafanaで可視化!

※本記事では触れません

使用的程式庫

※摘录

buildscript {
    ext {
        springBootVersion = '2.1.1.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('org.springframework.boot:spring-boot-actuator')
    compile('org.springframework.boot:spring-boot-actuator-autoconfigure')
    compile('io.micrometer:micrometer-registry-prometheus')
}

spring-boot-starter-web

メトリクスの取得には関係なし、メトリクス取得対象のAPI作成のために使用

spring-boot-actuator

メトリクスを取得するための機能はこの中に入っている

spring-boot-actuator-autoconfigure

spring-boot-actuatorが提供している機能をDIコンテナに登録する役割

micrometer-registry-prometheus

取得したメトリクスをprometheusから読み取れる形式に変換してくれる

被监视的应用程序

只需要返回「你好,世界!」的API

@Controller
@SpringBootApplication
@Slf4j
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @GetMapping
    @ResponseBody
    public String hello() {
        return "Hello World!";
    }
}

网络服务器请求

首先要获取应用程序本身的指标!

实施

在实施过程中所需要的是

    • application.ymlに設定を記載(もちろんapplication.propertiesでも可)

 

    だけ!!!
management:
  endpoints:
    web:
      exposure:
        include: "prometheus"
      base-path: "/"
  server:
    port: 9990
  metrics:
    web:
      server:
        requests-metric-name: "http.server.requests"
    distribution:
      percentiles:
        "http.server.requests": 0.5,0.99
    • この設定をしておくとlocalhost:9990/prometheusにアクセスするとメトリクスが取得できる!

management.metrics配下の設定値はなくても動きます

requests-metric-nameはメトリクスの名前を任意のものに変更するための設定値です

percentilesを設定しておくとパーセンタイルが取得できます

この設定だと50%ileと99%ile

开始执行bootRun!一次又一次地访问(curl localhost:8080),获取指标!

$ curl localhost:9990/prometheus
# TYPE http_server_requests_seconds summary
http_server_requests_seconds{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/",quantile="0.5",} 0.0
http_server_requests_seconds{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/",quantile="0.99",} 0.0
http_server_requests_seconds_count{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/",} 7.0
http_server_requests_seconds_sum{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/",} 0.086270892

恭喜!

谁在做什么?

提供度量标准的是org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter。
主要的处理可能在这个方法中。

	private void filterAndRecordMetrics(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws IOException, ServletException {
		TimingContext timingContext = TimingContext.get(request);
		if (timingContext == null) {
			timingContext = startAndAttachTimingContext(request);
		}
		try {
			filterChain.doFilter(request, response);
			if (!request.isAsyncStarted()) {
				// Only record when async processing has finished or never been started.
				// If async was started by something further down the chain we wait
				// until the second filter invocation (but we'll be using the
				// TimingContext that was attached to the first)
				Throwable exception = (Throwable) request
						.getAttribute(DispatcherServlet.EXCEPTION_ATTRIBUTE);
				record(timingContext, response, request, exception);
			}
		}
		catch (NestedServletException ex) {
			response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
			record(timingContext, response, request, ex.getCause());
			throw ex;
		}
	}

只要这个类继承了OncePerRequestFilter,并且进行了Bean注册,它就能帮忙收集指标哦!

Bean的注册是什么?
我经常会启用beans终点并进行调查。

management:
  endpoints:
    web:
      exposure:
        include: "prometheus,beans"
      base-path: "/"
  server:
    port: 9990

在将beans添加到prometheus之后,
可以通过localhost:9990/beans获取已注册的bean列表。

webMvcMetricsFilter: {
aliases: [ ],
  scope: "singleton",
  type: "org.springframework.boot.web.servlet.FilterRegistrationBean",
  resource: "class path resource 
 [org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsAutoConfiguration.class]",
  dependencies: [
    "prometheusMeterRegistry",
    "webMvcTagsProvider"
  ]
},

嗯嗯,是WebMvcMetricsAutoConfiguration吗?

@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@ConditionalOnBean(MeterRegistry.class)

使用的是2.1.1版本,它看起来会自动帮我们汇总指标数据,因为它是这样的条件语句。
反过来说,如果安装了库就会自动汇总指标数据,所以如果不需要的话最好不要安装库,这是理所当然的。

http.client.requests 可以被放到Python中的一个库,它用于发送HTTP请求。

实施

    • 意識しなきゃいけないのはRestTemplate

RestTemplateBuilderから直接buildするときは特に意識する必要なし
独自のbuilderを使いたいときを想定して実装

customizersメソッドにBean登録されているMetricsRestTemplateCustomizerを渡すだけ

何も記載しなくてもBean登録はされてるけど、一応99%ile取得を想定してapplication.ymlも修正

management:
  endpoints:
    web:
      exposure:
        include: "prometheus,beans"
      base-path: "/"
  server:
    port: 9990
  metrics:
    web:
      server:
        requests-metric-name: "http.server.requests"
      client:
        requests-metric-name: "http.client.requests"
    distribution:
      percentiles:
        "http.server.requests": 0.5,0.99
        "http.client.requests": 0.5,0.99
@Controller
@SpringBootApplication
@Slf4j
public class Application {
    private RestTemplate restTemplate;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @GetMapping
    @ResponseBody
    public String hello() {
        return restTemplate.getForObject(URI.create("http://localhost:8080/hello"), String.class);
    }

    public Application(
            RestTemplateBuilder restTemplateBuilder,
            MetricsRestTemplateCustomizer metricsRestTemplateCustomizer
    ) {
        // デフォルトのRestTemplateBuilderを使う場合は意識する必要ない
        // this.restTemplate = restTemplateBuilder
        //         .build();

        this.restTemplate = restTemplateBuilder
                .customizers(metricsRestTemplateCustomizer)
                .build();
    }

    @GetMapping("/hello")
    @ResponseBody
    public String helloApi() {
        return "Hello World!";
    }
}

可以按照这样的方式收集度量信息。

$ curl http://localhost:9990/prometheus
# TYPE http_client_requests_seconds summary
http_client_requests_seconds{clientName="localhost",method="GET",status="200",uri="/hello",quantile="0.5",} 0.06291456
http_client_requests_seconds{clientName="localhost",method="GET",status="200",uri="/hello",quantile="0.99",} 0.06291456
http_client_requests_seconds_count{clientName="localhost",method="GET",status="200",uri="/hello",} 1.0
http_client_requests_seconds_sum{clientName="localhost",method="GET",status="200",uri="/hello",} 0.064232646

谁在做什么?

    • メトリクスを取ってくれているのはorg.springframework.boot.actuate.metrics.web.client.MetricsClientHttpRequestInterceptor。

 

    • このクラスは先ほどの実装で登録したcustomizer(MetricsRestTemplateCustomizer)が使用している

MetricsRestTemplateCustomizerはRestTemplateMetricsConfigurationによってBean登録されている
デフォルトの(Bean登録されている)RestTemplateBuilderを使う場合は、RestTemplateBuilderのコンストラクタの引数がRestTemplateCustomizerの配列になっているから、登録されるCustomizerを全部拾ってくれるのね!
conditionalはRestTemplateを見ているからRestTemplateを使っていたらcustomizerはBean登録されているのね!

@ConditionalOnClass(RestTemplate.class)

最后

取得99%的分数轻而易举,真让人吃惊。
制造这个的人真厉害啊。。。

广告
将在 10 秒后关闭
bannerAds