SpringBootアプリケーションのメトリクス監視

SpringBootで実装したアプリケーションで「メトリクスで監視しよう!」とは思ったものの、
「何を見たらいいんだ…」となっていないでしょうか?

僕も以前は同じ状況だったのですが、3年ほどかけてメトリクス監視での運用を続ける中で自分なりの正解を見つけることができました。

この記事ではその3年で学んだことを凝縮し、この記事を読んでくださった方が今すぐに実行に移せるようまとめてみました!

補足
キャパシティプランニングのために、
最大スレッド数や最大コネクション数の変更の仕方にも触れていきます!

準備

メトリクスの取得のために共通的に必要な準備です。

この記事ではprometheusで読み取ることを前提に進めます。

plugins {
	...
  // SpringBootのバージョンは「3.0.2」で
	id 'org.springframework.boot' version '3.0.2'
	id 'io.spring.dependency-management' version '1.1.0'
}

...

dependencies {
  ...
  // (必須)メトリクス収集のメインを担うライブラリ
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
  // メトリクスをprometheusで読み取れる形式で出力するためのライブラリ
	implementation 'io.micrometer:micrometer-registry-prometheus'
}
management:
  server:
    # (任意)メトリクスを出力するportを変更する
    # ※エンドポイントさえ知っていれば外から叩けてしまうのでアプリケーションのポートと別にするのがオススメ
    port: 9990
  endpoints:
    web:
      exposure:
        # 有効化したいエンドポイントを記載(ここではprometheusを追加)
        include: health, prometheus

これでアプリケーションを起動し、http://localhost:9990/actuator/prometheus にアクセスするとメトリクスを参照できます!

HTTPサーバ

spring-boot-starter-webspring-boot-starter-webfluxを使用してHTTPリクエストを受け取る場合、
デフォルトでメトリクスを取得できますが、パーセンタイルの取得のために以下の設定をしておくとよいです。

management:
  metrics:
    distribution:
      percentiles:
        # 取得するパーセンタイルの設定(この例だと50%ileと99%ile)
        "http.server.requests": 0.5,0.99
  observations:
    http:
      server:
        requests:
          # (任意)メトリクス名を指定
          name: "http.server.requests"

見るべきメトリクスはこちら!

  • http_server_requests_seconds
    • レスポンスのパーセンタイルを返すメトリクスです
    • 平均だと一部の遅いリクエストが丸め込まれてしまうため99%ile等でレスポンスを把握するのがオススメです
  • http_server_requests_seconds_count
    • 累積のHTTPリクエスト数を返すメトリクスです
    • Prometheusでいうirateのような一定期間の差分を求める関数と合わせて利用することでrpsを把握できます
    • またHTTPステータスがタグに含まれるため、エラー件数やエラー割合なんかも把握できます

Tomcat

spring-boot-starter-webを使用している場合、特に設定を変えない限りTomcatでサーバが起動しています。

Tomcatは1スレッドで1リクエストをさばく仕様のため特に「最大スレッド数」「利用中のスレッド数」は把握しておいた方が良いと思います。

メトリクスの取得方法ですが、application.yamlで設定を変更するだけです!

server:
  tomcat:
    mbeanregistry:
      # Tomcatのメトリクス収集を有効化
      enabled: true
    threads:
      # 最大スレッド数の設定(デフォルト:200)
      max: 500

見るべきメトリクスはこちら!

  • tomcat_threads_current_threads
    • tomcat_threads_busy_threadsが使用中のスレッド数を返すのに対して、使用中以外も含めて返すメトリクス
    • busyの方よりも最大でどれだけのスレッドを利用したのかの把握がしやすいです
    • ※busyの方だと最大利用時ピンポイントでメトリクス排出しないとダメ
  • tomcat_threads_config_max_threads
    • 設定上の最大スレッド数です
    • 同時に処理できるリクエスト数です

HTTPクライアント

使用するクライアントの種類によって実装方法が異なります。

RestTemplate

RestTemplateを利用している場合、RestTemplate生成時にカスタマイズが必要です!

    @Bean
    public PoolingHttpClientConnectionManager connectionManager() {
        return PoolingHttpClientConnectionManagerBuilder.create()
                .setMaxConnTotal(100)    // 最大コネクション数 ※デフォルト: 25
                .setMaxConnPerRoute(50)  // ドメインごとの最大コネクション数 ※デフォルト: 5
                .build();
    }

    @Bean
    public RestTemplate myRestTemplate(
            PoolingHttpClientConnectionManager connectionManager,
            ObservationRestTemplateCustomizer observationRestTemplateCustomizer
    ) {
        var httpClient = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .build();

        var requestFactory = new HttpComponentsClientHttpRequestFactory();
        requestFactory.setHttpClient(httpClient);

        return new RestTemplateBuilder()
                // HTTP通信のメトリクス取得のためのCustomizer
                // (Springが提供しているRestTemplateBuilder beanを使う場合は不要)
                .customizers(observationRestTemplateCustomizer)
                // カスタマイズしたHttpClientを設定する
                .requestFactory(() -> requestFactory)
                .build();

見るべきメトリクスはこちら!

  • http_client_requests_seconds_count
    • 累積のHTTPリクエスト数を返すメトリクスです
    • Prometheusでいうirateのような一定期間の差分を求める関数と合わせて利用することでrpsを把握できます
    • またHTTPステータスがタグに含まれるため、エラー件数やエラー割合なんかも把握できます
  • http_client_requests_seconds
    • レスポンスのパーセンタイルを返すメトリクスです
    • 平均だと一部の遅いリクエストが丸め込まれてしまうため99%ile等でレスポンスを把握するのがオススメです

httpclient5のコネクション数のメトリクスについては対応中のようです。
すぐに必要な場合は以下を参考に個別で実装する必要があります。

    @Bean
    public PoolingHttpClientConnectionManager connectionManager() {
        return new PoolingHttpClientConnectionManager();
    }

    @Bean
    public PoolingHttpClientConnectionManagerMetricsBinder connectionManagerMetricsBinder(
            MeterRegistry meterRegistry,
            PoolingHttpClientConnectionManager connectionManager
    ) {
        var binder = new PoolingHttpClientConnectionManagerMetricsBinder(connectionManager, "http-client");
        binder.bindTo(meterRegistry);
        
        return binder;
    }

    @Bean
    public RestTemplate myRestTemplate(PoolingHttpClientConnectionManager connectionManager) {
        HttpClient httpClient = HttpClientBuilder.create()
                .setConnectionManager(connectionManager)
                .build();
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
        requestFactory.setHttpClient(httpClient);

        return new RestTemplateBuilder()
                .requestFactory(() -> requestFactory)
                .build();
    }

WebClient

WebClientを利用している場合は、WebClientの生成時にカスタマイズが必要になります。

また、99%ileを取得するためにapplication.yamlも変更しておくとよいです!

    @Bean
    public WebClient myWebClient(ObservationWebClientCustomizer observationWebClientCustomizer) {
        var connectionProvider = ConnectionProvider.builder("sample")
                .maxConnections(100) // コネクションプール数
                .metrics(true) // コネクション数のメトリクスを有効化
                .build();

        var httpClient = HttpClient.create(connectionProvider);

        return WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .apply(observationWebClientCustomizer::customize)  // http clientのメトリクスを収集
                .build();
    }
management:
  metrics:
    distribution:
      percentiles:
        # 取得するパーセンタイルの設定(この例だと50%ileと99%ile)
        "http.client.requests": 0.5,0.99

見るべきメトリクスはこちら!

  • http_client_requests_seconds_count
    • 累積のHTTPリクエスト数を返すメトリクスです
    • Prometheusでいうirateのような一定期間の差分を求める関数と合わせて利用することでrpsを把握できます
    • またHTTPステータスがタグに含まれるため、エラー件数やエラー割合なんかも把握できます
  • http_client_requests_seconds
    • レスポンスのパーセンタイルを返すメトリクスです
    • 平均だと一部の遅いリクエストが丸め込まれてしまうため99%ile等でレスポンスを把握するのがオススメです
  • reactor_netty_connection_provider_max_connections
    • HTTPコネクションの最大値を返すメトリクスです
  • reactor_netty_connection_provider_active_connections / reactor_netty_connection_provider_total_connections
    • 利用中のコネクション数を返すメトリクスです
    • activeとtotalでどちらがいいかまで追えておらず2つ載せています
    • activeの方だとピンポイントに利用中のコネクションが多いときにメトリクス収集しないと正確に状況が把握できないのでは?と想定はしています。

番外編

SpringBootやMicromerterでサポートされていないリソースでも、
アノテーションを付与するだけでメソッド単位のメトリクスを簡単に収集することができます!

dependencies {
    ...
    // spring-boot-starter-aopを依存に追加する
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    ...
}
@Configuration
public class MetricsAutoConfiguration {
     // @Timedを利用する場合にBean登録が必要
    @Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);
    }

    // // @Countedを利用する場合にBean登録が必要
    @Bean
    public CountedAspect countedAspect(MeterRegistry registry) {
        return new CountedAspect(registry);
    }
}
    @Timed(
            // メトリクス名
            value = "timed.metrics",
            // パーセンタイルを計測したい場合に指定(例では50%ileと99%ileを指定)
            percentiles = {0.5, 0.99}
    )
    public String timedMethod() {
    }

    @Counted(
            // メトリクス名
            value = "counted.metrics"
    )
    public String counted() {
    }

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です