RestTemplateを使ったアプリケーションを本番運用するために意識しておきたい設定とその設定方法をまとめてみました。
自分の現場で意識していることが中心になりますが、コピペでそのまま本番アプリケーションに使うようなコードを残しておきます!
依存ライブラリ
動作確認をしたbuild.gradle
の抜粋です。
plugins {
id 'java'
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-web'
implementation 'org.apache.httpcomponents.client5:httpclient5'
}
共通の設定
どのアプリケーションでも共通で意識するであろう設定の実装方法をまとめておきます。
@Configuration
public class RestTemplateAutoConfiguration {
@Bean
RestTemplate restTemplate() {
var socketConfig = SocketConfig.custom()
// read timeout(例:1000ミリ秒)
.setSoTimeout(1_000, TimeUnit.MILLISECONDS)
.build();
var connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
// keep alive timeout(例: 59秒)
.setConnectionTimeToLive(TimeValue.ofSeconds(59))
// 全ルート合算の最大接続数
.setMaxConnTotal(100)
// ルート(基本的にはドメイン)ごとの最大接続数
// !!! デフォルトが「5」で高負荷には耐えられない設定値なので注意 !!!
.setMaxConnPerRoute(100)
.setDefaultSocketConfig(socketConfig)
.build();
var httpClient = HttpClientBuilder.create()
.setConnectionManager(connectionManager)
.build();
var requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplateBuilder()
.requestFactory(() -> requestFactory)
.rootUri("http://localhost:8080")
.setConnectTimeout(Duration.ofMillis(500))
.build();
}
}
メトリクス収集
メトリクス収集をするには actuator
を依存に追加します。
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// (任意)メトリクスをprometheusと連携する場合に必要
implementation 'io.micrometer:micrometer-registry-prometheus'
...
}
追加した依存ライブラリの中にメトリクス収集をしてくれる実装がすでに用意されているため、特別な要件がない限りはそれを利用すればOK!
@Bean
RestTemplate restTemplateWithMetrics(ObservationRestTemplateCustomizer observationCustomizer) {
return new RestTemplateBuilder()
.rootUri("http://localhost:8080")
.additionalCustomizers(observationCustomizer)
.build();
}
URLの指定方法に注意!!!
メトリクスを有効にする場合、RestTemplate実行時のURLの指定方法を気を付ける必要があります。
まずはダメな例です。
var uuid = UUID.randomUUID().toString();
restTemplate.getForObject("/server?uuid=" + uuid, String.class);
メトリクスはこのようになります。
http_client_requests_seconds_count{error="none",exception="none",method="GET",outcome="SUCCESS",status="200",uri="/server?uuid=2ca3e70f-8d3a-4bcb-b7af-1fed52ed2b74",} 1.0
uriにUUIDの値が入り込んでおり、uuid分メトリクスが肥大化してしまいます。
そして肥大化したメトリクスによってOOMが引き起こされる可能性があります。
(実体験としてこの理由のOOMを経験しています)
では、どう実装すれば良いのかというと変数部分は別途引数で設定するようなことをします。
var uuid = UUID.randomUUID().toString();
restTemplateWithMetrics.getForObject("/server?uuid={uuid}", String.class, uuid);
この実装だとメトリクスは以下のようになります。
http_client_requests_seconds_count{error="none",exception="none",method="GET",outcome="SUCCESS",status="200",uri="/server?uuid={uuid}",} 1.0
uriが丸め込まれているのでメトリクスの肥大化の心配がありません!
(そしてこちらの形式の方が取得したかった形式に近いはず)