In Spring Boot 3.4 the HttpComponentsClientHttpRequestFactoryBuilder used by RestClient does not provide the capability to add monitoring to the HttpClient's connection pool using micrometers PoolingHttpClientConnectionManagerMetricsBinder. This leads to blindness for utilization of the underlying connection pool.
Comment From: nosan
does not provide the capability to add monitoring to the HttpClient's connection pool
You can configure your own ClientHttpRequestFactoryBuilder
and PoolingHttpClientConnectionManagerMetricsBinder
beans.
@Configuration(proxyBeanMethods = false)
public class ClientHttpConfiguration {
@Bean
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
return PoolingHttpClientConnectionManagerBuilder.create().useSystemProperties().build();
}
@Bean
ClientHttpRequestFactoryBuilder<?> httpComponentsClientHttpRequestFactoryBuilder(
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager) {
return ClientHttpRequestFactoryBuilder.httpComponents()
.withHttpClientCustomizer(
(builder) -> builder.setConnectionManager(poolingHttpClientConnectionManager)
.setConnectionManagerShared(true));
}
@Bean
MeterBinder poolingHttpClientConnectionManagerMetricsBinder(
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager) {
return new PoolingHttpClientConnectionManagerMetricsBinder(poolingHttpClientConnectionManager,
"httpComponents.pool");
}
}
...
"httpcomponents.httpclient.pool.route.max.default",
"httpcomponents.httpclient.pool.total.connections",
"httpcomponents.httpclient.pool.total.max",
"httpcomponents.httpclient.pool.total.pending",
...
Unfortunately, you'll need to manually configure all the settings for PoolingHttpClientConnectionManager
, such as SocketConfig
and TlsSocketStrategy
.
Comment From: nosan
The above code configures a shared PoolingHttpClientConnectionManager
for all RestClient
. If this is not the desired behavior, consider using the following configuration:
@Configuration(proxyBeanMethods = false)
class ClientHttpConfiguration {
private final AtomicInteger connectionPoolCounter = new AtomicInteger();
private final MeterRegistry meterRegistry;
ClientHttpConfiguration(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Bean
ClientHttpRequestFactoryBuilder<?> clientHttpRequestFactoryBuilder() {
return ClientHttpRequestFactoryBuilder.httpComponents()
.withHttpClientCustomizer(
(builder) -> builder.setConnectionManager(getPoolingHttpClientConnectionManager()));
}
private PoolingHttpClientConnectionManager getPoolingHttpClientConnectionManager() {
PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
.useSystemProperties()
.build();
new PoolingHttpClientConnectionManagerMetricsBinder(connectionManager,
"pool-" + this.connectionPoolCounter.incrementAndGet()).bindTo(this.meterRegistry);
return connectionManager;
}
}
{
"name": "httpcomponents.httpclient.pool.total.connections",
"description": "The number of persistent and leased connections for all routes.",
"measurements": [
{
"statistic": "VALUE",
"value": 20
}
],
"availableTags": [
{
"tag": "state",
"values": [
"available",
"leased"
]
},
{
"tag": "httpclient",
"values": [
"pool-2",
"pool-1"
]
}
]
}
Comment From: nosan
I am curious if the HttpComponentsClientHttpRequestFactoryBuilder
could be enhanced with the following method:
public HttpComponentsClientHttpRequestFactoryBuilder withConnectionManagerPostConfigurer(
Consumer<PoolingHttpClientConnectionManager> connectionManagerPostConfigurer) {
...
}
In that case, it would be possible to have something like this:
@Configuration(proxyBeanMethods = false)
class ClientHttpConfiguration {
private final AtomicInteger counter = new AtomicInteger();
private final MeterRegistry meterRegistry;
ClientHttpConfiguration(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Bean
ClientHttpRequestFactoryBuilder<?> clientHttpRequestFactoryBuilder() {
return ClientHttpRequestFactoryBuilder.httpComponents()
.withConnectionManagerPostConfigurer(this::bindToMeterRegistry);
}
private void bindToMeterRegistry(PoolingHttpClientConnectionManager connectionManager) {
new PoolingHttpClientConnectionManagerMetricsBinder(connectionManager, "pool-" + this.counter.getAndIncrement())
.bindTo(this.meterRegistry);
}
}
I’ve prototyped some changes: https://github.com/spring-projects/spring-boot/compare/main...nosan:spring-boot:44643
This solution will not work if someone overrides PoolingHttpClientConnectionManager
via Consumer<HttpClientBuilder> httpClientCustomizer
:
ClientHttpRequestFactoryBuilder.httpComponents()
.withHttpClientCustomizer(
(builder) -> builder.setConnectionManager(new PoolingHttpClientConnectionManager()));
Comment From: nosan
I’ve prototyped some changes: https://github.com/spring-projects/spring-boot/compare/main...nosan:spring-boot:44643
I am not sure about HttpClientMetricsAutoConfiguration
; it seems a bit fragile. It works only
when no one overrides the PoolingHttpClientConnectionManager
through HttpClientBuilder
.
Otherwise, HttpComponentsClientHttpRequestFactoryBuilderMetricsPostProcessor
binds the wrong
PoolingHttpClientConnectionManager
to the MeterRegistry
which leads to NaN metrics.