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-web
、spring-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() {
}