Apache HttpClient Connection Pool 모니터링
서비스를 운영하다보면, 외부 api 호출하는 로직때문에 api 응답속도가 느려질때가 종종있다.
이럴때 보통 의심할 수 있는 부분이 2가지다.
첫 번째는 외부 api 자체가 응답이 느린지다. 다른 하나는 내 서비스에서 http connection을 얻는데 지연이 생기는지 확인해볼 필요가 있다.
만약 외부 api 호출시 Apache HttpClient를 사용한다면, 지금부터 말하는 내용을 활용해 모니터링 할 수 있다.
Apache HttpClient
Apache HttpComponents – HttpClient Overview
Java 애플리케이션이 HTTP 프로토콜을 통해 다른 웹 서버와 통신할 수 있도록 도와주는 클라이언트 라이브러리를 개발하고 유지하는 Apache 재단의 오픈소스 프로젝트다.
Apache HttpClient에서 HttpConnection Pool은 HttpClientConnectionManager가 담당하고 있다.
Represents a manager of persistent client connections.
The purpose of an HTTP connection manager is to serve as a factory for new HTTP connections, manage persistent connections and synchronize access to persistent connections making sure that only one thread of execution can have access to a connection at a time.
Implementations of this interface must be thread-safe. Access to shared data must be synchronized as methods of this interface may be executed from multiple threads.
HttpClientConnectionManager (Apache HttpClient 4.5.14 API)
보통 많이 사용되는 구현체는 PoolingHttpClientConnectionManager 이다. (실제로 Feign Client를 Spring 과 연동해 사용하면 HttpClient 기본 구현체로 Apache HttpClient를 사용하고 Connection Pool 관리는 PoolingHttpClientConnectionManager 구현체를 사용한다. (FeignAutoConfiguration.HttpClientFeignConfiguration - spring-cloud-openfeign-core 3.1.9 javadoc))
그리고 ConnectionPool 상태 관련된 정보들은 ConnPoolControl 인터페이스를 이용해 얻을 수 있다.
Interface to control runtime properties of a ConnPool such as maximum total number of connections or maximum connections per route allowed.
ConnPoolControl - httpcore 4.4.8 javadoc
Micrometer Integration
Micrometer에서 Http Connection Pool을 모니터링 가능하도록 지원해준다.
PoolingHttpClientConnectionManagerMetricsBinder를 사용하면, Http Connection Pool 정보를 Micrometer를 이용해 모니터링 할 수 있다.
Apache HTTP client instrumentation · Issue #533 · micrometer-metrics/micrometer
직접 개발하신 개발자께서 말하는 사용 가이드다.
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connectionManager).build();
new PoolingHttpClientConnectionManagerMetricsBinder(connectionManager, "my-pool").bindTo(registry);
만약 spring actuator를 사용하고 있다면 아래처럼 설정도 가능하다.
Production-ready Features :: Spring Boot
@Bean
public MeterBinder httpClientMetrics(HttpClientConnectionManager connectionManager) {
if (connectionManager instanceof PoolingHttpClientConnectionManager poolingHttpClientConnectionManager) {
return new PoolingHttpClientConnectionManagerMetricsBinder(poolingHttpClientConnectionManager, "apache-conn-pool");
}
return null;
}
어떤 메트릭이 수집되는지는 Micrometer의 PoolingHttpClientConnectionManagerMetricsBinder의 registerToTotalMetrics 메소드를 보면 확인할 수 있다.
private void registerTotalMetrics(MeterRegistry registry) {
Gauge.builder("httpcomponents.httpclient.pool.total.max",
connPoolControl,
(connPoolControl) -> connPoolControl.getTotalStats().getMax())
.description("The configured maximum number of allowed persistent connections for all routes.")
.tags(tags)
.register(registry);
Gauge.builder("httpcomponents.httpclient.pool.total.connections",
connPoolControl,
(connPoolControl) -> connPoolControl.getTotalStats().getAvailable())
.description("The number of persistent and available connections for all routes.")
.tags(tags).tag("state", "available")
.register(registry);
Gauge.builder("httpcomponents.httpclient.pool.total.connections",
connPoolControl,
(connPoolControl) -> connPoolControl.getTotalStats().getLeased())
.description("The number of persistent and leased connections for all routes.")
.tags(tags).tag("state", "leased")
.register(registry);
Gauge.builder("httpcomponents.httpclient.pool.total.pending",
connPoolControl,
(connPoolControl) -> connPoolControl.getTotalStats().getPending())
.description("The number of connection requests being blocked awaiting a free connection for all routes.")
.tags(tags)
.register(registry);
Gauge.builder("httpcomponents.httpclient.pool.route.max.default",
connPoolControl,
ConnPoolControl::getDefaultMaxPerRoute)
.description("The configured default maximum number of allowed persistent connections per route.")
.tags(tags)
.register(registry);
}