본문 바로가기

Spring

WebClient를 이용한 외부 호출 관련 Metric 수집

 

서비스를 운영하다보면 내 서비스가 호출하는 API 호출량을 모니터링 하고 싶은 경우가 있다. 내 서비스가 외부 서비스 API를 너무 과하게 호출하고 있지는 않은지 점검할 수 있다.

 

WebClient를 통한 외부 호출 모니터링

Spring에서 외부 API를 호출하기 위해 대표적으로 RestTemplate과 WebClient를 제공한다. 만약 Spring에서 생성해준 Builder를 주입받아 RestTemplate, WebClient 객체를 만들었다면, 기본적으로 Metric이 수집된다. 즉, Grafana를 통해 모니터링 할 수 있다.

 

ref : https://docs.spring.io/spring-boot/docs/2.1.7.RELEASE/reference/html/production-ready-metrics.html#production-ready-metrics-http-clients

 

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);
}