서비스를 운영하다보면 내 서비스가 호출하는 API 호출량을 모니터링 하고 싶은 경우가 있다. 내 서비스가 외부 서비스 API를 너무 과하게 호출하고 있지는 않은지 점검할 수 있다.
WebClient를 통한 외부 호출 모니터링
Spring에서 외부 API를 호출하기 위해 대표적으로 RestTemplate과 WebClient를 제공한다. 만약 Spring에서 생성해준 Builder를 주입받아 RestTemplate, WebClient 객체를 만들었다면, 기본적으로 Metric이 수집된다. 즉, Grafana를 통해 모니터링 할 수 있다.
57. Metrics
If you need to apply customizations to specific Meter instances you can use the io.micrometer.core.instrument.config.MeterFilter interface. By default, all MeterFilter beans will be automatically applied to the micrometer MeterRegistry.Config. For example,
docs.spring.io
Metric 수집 동작원리
1. Spring Actuator
프로젝트에서 Spring Actuator를 사용중이고, WebClient가 classpath에 있다면, MetricWebClientCustomizer라는 구현체가 Bean으로 등록된다.
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({WebClient.class})
class WebClientMetricsConfiguration {
WebClientMetricsConfiguration() {
}
@Bean
@ConditionalOnMissingBean
WebClientExchangeTagsProvider defaultWebClientExchangeTagsProvider() {
return new DefaultWebClientExchangeTagsProvider();
}
@Bean
MetricsWebClientCustomizer metricsWebClientCustomizer(MeterRegistry meterRegistry, WebClientExchangeTagsProvider tagsProvider, MetricsProperties properties) {
MetricsProperties.Web.Client.ClientRequest request = properties.getWeb().getClient().getRequest();
return new MetricsWebClientCustomizer(meterRegistry, tagsProvider, request.getMetricName(), request.getAutotime());
}
}
MetricWebClientCustomizer는 WebClientCustomzier의 구현체로 WebClient.Builder 구현체 filter에 MetricsWebClientFilterFunction을 동록해주는 역할을 한다.
MetricsWebClientFilterFunction가 실질적으로 Metric을 수집한다.
2. Spring에 의한 WebClient.Builder 생성
WebClient.Builder는 WebClientAutoConfiguration에 의해 생성된다.
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebClient.class)
@AutoConfigureAfter({ CodecsAutoConfiguration.class, ClientHttpConnectorAutoConfiguration.class })
public class WebClientAutoConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public WebClient.Builder webClientBuilder(ObjectProvider<WebClientCustomizer> customizerProvider) {
WebClient.Builder builder = WebClient.builder();
customizerProvider.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder;
}
...
}
코드에서 보이듯이, WebClient.buidler()를 통해 builder 객체를 생성하고, 세팅되어 있는 WebClientCusomizer를 순회하면서 필요한 세팅들을 해준다.
그리고 이때 1번 과정에서 등록된 MetricsWebClientCustomizer에 의해 WebClient.Builder filter에 Metric을 수집하는 filter가 세팅된다.
3. WebClient.Builder 주입 받아 WebClient 생성
WebClient.Builder을 주입 받으면, 2번에서 생성된 WebClient.Builder가 사용되기 때문에 자동으로 WebClient 관련 Metric이 수집된다.
여러 Tips
WebClient Metric 비활성화
WebClient.Builder를 주입 받아 사용할때도 원하지 않는다면, WebClient 관련 Metric 수집을 비활성화 할 수 있다.
현재 내가 알고 있는 방법은 2가지이다.
1. application.yaml 파일에 설정
management.metrics.web.client.request.autotime.enabled = false
위 세팅 값이 false이면, autoTimer.isEnabled 값이 false여서 metric 수집 로직을 수행하지 않는다.
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
if (!this.autoTimer.isEnabled()) {
return next.exchange(request);
}
return next.exchange(request).as((responseMono) -> instrumentResponse(request, responseMono))
.contextWrite(this::putStartTime);
}
2. WebClientCustomizer를 구현
MetricWebFilterFunction을 filter에서 제거해주면 된다.
@Configuration
public class WebClientConfig {
@Bean
public WebClientCustomizer disableMetricsFilterForAutoWebClient() {
return builder -> builder.filters(filters ->
filters.removeIf(filter -> filter instanceof MetricsWebClientFilterFunction)
);
}
}
Metric Tag Customizing
DefaultWebClientExchangeTagsProvider 구현체를 보면 아래와 같이 수집하는 Tag 목록을 알 수 있다.
public class DefaultWebClientExchangeTagsProvider implements WebClientExchangeTagsProvider {
@Override
public Iterable<Tag> tags(ClientRequest request, ClientResponse response, Throwable throwable) {
Tag method = WebClientExchangeTags.method(request);
Tag uri = WebClientExchangeTags.uri(request);
Tag clientName = WebClientExchangeTags.clientName(request);
Tag status = WebClientExchangeTags.status(response, throwable);
Tag outcome = WebClientExchangeTags.outcome(response);
return Arrays.asList(method, uri, clientName, status, outcome);
}
}
그리고 여기서 uri Tag가 수집되는 과정을 자세히보면 아래와 같다.
public static Tag uri(ClientRequest request) {
String uri = (String) request.attribute(URI_TEMPLATE_ATTRIBUTE).orElseGet(() -> request.url().toString());
return Tag.of("uri", extractPath(uri));
}
코드에서 보면 알 수 있듯이, request attribute에 URI_TEMPLATE_ATTRIBUTE 값이 없으면, Metric이 무한정 생성될 수 있다.
(WebClient 이용시 uriBuilder를 이용하면 attribute에 URI_TEMPLATE_ATTRIBUTE 값이 세팅되지 않는다.)
이럴경우, WebClient 호출부에서 uriBuilder를 사용하는 코드를 모두 변경하는 방법도 있지만, 번거롭다면 WebClientExchangeTagsProvider 인터페이스를 구현해 내가 원하는 Tag만 남기도록 할수도 있다.
Metric Type
MetricWebClientFilterFuction에서 수집하는 Metric 타입은 Timer이기 때문에 같은 Metric에 대해
총 요청 수, 총 수행 시간, 최대 수행 시간 지표를 알 수 있다.
https://docs.micrometer.io/micrometer/reference/concepts/timers.html
private void recordTimer(Iterable<Tag> tags, Long startTime) {
this.autoTimer.builder(this.metricName).tags(tags).description("Timer of WebClient operation")
.register(this.meterRegistry).record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
}
'Spring' 카테고리의 다른 글
Application Context Refresh 전에 로깅 설정하기 (1) | 2025.02.17 |
---|---|
Spring Event UnderTheHood (0) | 2025.02.04 |
Spring @Transactional 동작원리 완벽 정리 (0) | 2024.03.02 |
Spring MVC에서 MappingJacksonValue 활용하기 (0) | 2024.03.01 |
Spring Data Redis Pub/Sub 파헤치기 (0) | 2023.06.28 |