SpringWebFluxのHTTPクライアントのWebClientですが、
使い方次第でメモリリークの可能性があるので知っていることをまとめておきます!
前提
- SpringBoot:2.7.6
- actuatorを一緒に利用
build.gradle
はこんな感じ
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.6'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'hirabay'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
tasks.named('test') {
useJUnitPlatform()
}
NG例①
@RequiredArgsConstructor
public class WebClientWrapper {
private final WebClient.Builder webClientBuilder;
public Mono<String> ng1() {
return webClientBuilder
.baseUrl("http://localhost:8080")
.build()
.get()
.uri("/api/sample?uuid=" + UUID.randomUUID().toString())
.retrieve()
.bodyToMono(String.class);
}
}
$ curl -s http://localhost:8080/actuator/prometheus | grep ^http_client
http_client_requests_seconds_count{client_name="localhost",method="GET",outcome="SUCCESS",status="200",uri="/api/sample?uuid=c54f4ea5-6ebd-4122-bacb-b6212cb7eab6",} 1.0
http_client_requests_seconds_sum{client_name="localhost",method="GET",outcome="SUCCESS",status="200",uri="/api/sample?uuid=c54f4ea5-6ebd-4122-bacb-b6212cb7eab6",} 0.003281917
http_client_requests_seconds_count{client_name="localhost",method="GET",outcome="SUCCESS",status="200",uri="/api/sample?uuid=e03dab0e-88cb-48fa-a520-ef8fa3a4db0f",} 1.0
http_client_requests_seconds_sum{client_name="localhost",method="GET",outcome="SUCCESS",status="200",uri="/api/sample?uuid=e03dab0e-88cb-48fa-a520-ef8fa3a4db0f",} 0.054425584
http_client_requests_seconds_max{client_name="localhost",method="GET",outcome="SUCCESS",status="200",uri="/api/sample?uuid=c54f4ea5-6ebd-4122-bacb-b6212cb7eab6",} 0.003281917
http_client_requests_seconds_max{client_name="localhost",method="GET",outcome="SUCCESS",status="200",uri="/api/sample?uuid=e03dab0e-88cb-48fa-a520-ef8fa3a4db0f",} 0.0
uriにパラメータが埋め込まれており、パラメータごとにメトリクスが増えていきます。
UUIDのように毎回値が変わるような場合だと処理が行われるたびにメモリを食い潰していきます😱😱😱
NG例②
@RequiredArgsConstructor
public class WebClientWrapper {
private final WebClient.Builder webClientBuilder;
public Mono<String> ng2() {
return webClientBuilder
.baseUrl("http://localhost:8080")
.build()
.get()
.uri(uriBuilder -> uriBuilder.path("/api/sample")
.queryParam("uuid", UUID.randomUUID().toString())
.build())
.retrieve()
.bodyToMono(String.class);
}
}
builder使えばqueryParam
でパラメータ切り出してるしイケるやろ…!
$ curl -s http://localhost:8080/actuator/prometheus | grep ^http_client
http_client_requests_seconds_count{client_name="localhost",method="GET",outcome="SUCCESS",status="200",uri="/api/sample?uuid=0ce9d034-76e3-4bde-9607-1338b12e7aba",} 1.0
http_client_requests_seconds_sum{client_name="localhost",method="GET",outcome="SUCCESS",status="200",uri="/api/sample?uuid=0ce9d034-76e3-4bde-9607-1338b12e7aba",} 0.003851958
http_client_requests_seconds_count{client_name="localhost",method="GET",outcome="SUCCESS",status="200",uri="/api/sample?uuid=253e67c4-4e85-4e5c-b770-d1380a6fbceb",} 1.0
http_client_requests_seconds_sum{client_name="localhost",method="GET",outcome="SUCCESS",status="200",uri="/api/sample?uuid=253e67c4-4e85-4e5c-b770-d1380a6fbceb",} 0.06888125
http_client_requests_seconds_max{client_name="localhost",method="GET",outcome="SUCCESS",status="200",uri="/api/sample?uuid=0ce9d034-76e3-4bde-9607-1338b12e7aba",} 0.003851958
http_client_requests_seconds_max{client_name="localhost",method="GET",outcome="SUCCESS",status="200",uri="/api/sample?uuid=253e67c4-4e85-4e5c-b770-d1380a6fbceb",} 0.06888125
ダメでした。。。
uriBuilderはURLの生成を補助してくれるだけで、NG例①とやっていることは変わらないってことですね。。。
OK例
@RequiredArgsConstructor
public class WebClientWrapper {
private final WebClient.Builder webClientBuilder;
public Mono<String> ok() {
return webClientBuilder
.baseUrl("http://localhost:8080")
.build()
.get()
.uri("/api/sample?uuid={uuid}", UUID.randomUUID().toString())
.retrieve()
.bodyToMono(String.class);
}
}
$ curl -s http://localhost:8080/actuator/prometheus | grep ^http_client
http_client_requests_seconds_count{client_name="localhost",method="GET",outcome="SUCCESS",status="200",uri="/api/sample?uuid={uuid}",} 2.0
http_client_requests_seconds_sum{client_name="localhost",method="GET",outcome="SUCCESS",status="200",uri="/api/sample?uuid={uuid}",} 0.058212042
http_client_requests_seconds_max{client_name="localhost",method="GET",outcome="SUCCESS",status="200",uri="/api/sample?uuid={uuid}",} 0.055181458
メモ
uriメソッドで、第一引数にURLフォーマットを、第二引数以降に変数を指定しましょう!UriBuilerを使いたい場合
事前にuriフォーマットを生成するために使用しましょう。
public Mono<String> ok2() {
var uri = UriComponentsBuilder.fromPath("/api/sample")
.queryParam("uuid", "{uuid}")
.build().toUriString();
return webClientBuilder
.baseUrl("http://localhost:8080")
.build()
.get()
.uri(uri, UUID.randomUUID().toString())
.retrieve()
.bodyToMono(String.class);
}
SpringBoot 3.0だと・・・
NG例①だと同じくNGですが、NG例②(uriBuilderのパターン)はメモリリークの対策かuriがnoneに丸め込まれていました。
$ curl -s http://localhost:8080/actuator/prometheus | grep ^http_client
http_client_requests_active_seconds_active_count{exception="none",method="none",outcome="UNKNOWN",status="CLIENT_ERROR",uri="none",} 0.0
http_client_requests_active_seconds_duration_sum{exception="none",method="none",outcome="UNKNOWN",status="CLIENT_ERROR",uri="none",} 0.0
http_client_requests_active_seconds_max{exception="none",method="none",outcome="UNKNOWN",status="CLIENT_ERROR",uri="none",} 0.0
http_client_requests_seconds_count{error="none",exception="none",method="GET",outcome="SUCCESS",status="200",uri="none",} 3.0
http_client_requests_seconds_sum{error="none",exception="none",method="GET",outcome="SUCCESS",status="200",uri="none",} 0.060628958
http_client_requests_seconds_max{error="none",exception="none",method="GET",outcome="SUCCESS",status="200",uri="none",} 0.052780208
ただ、これだとメトリクスとしての価値が下がってしまうので、OK例のような変更をするのがオススメです!