Monday, 20 May 2024

HttpClient 5: Connection Pooling example

What is connection pooling?

Connection pooling refers to the practice of reusing existing HTTP connections instead of creating a new connection every time a request is made to a server. This improves performance and reduces overhead by avoiding the overhead of establishing a new connection for each request. Here's how it works:

 

Pool of Connections: Apache http client5 provides PoolingHttpClientConnectionManager class that maintains a pool of reusable HTTP connections for different targets (routes) accessed by your application.

 

Reusing Connections: When your application needs to make an HTTP request, the connection manager checks the pool for an available connection to the target server. If a suitable connection exists in the pool, it's leased out for the request instead of creating a new one.

 

Improved Performance: Reusing existing connections avoids the overhead of creating new connections for each request. This can significantly improve performance, especially for applications that make frequent HTTP requests to the same servers.

 

Reduced Server Load: Connection pooling reduces the load on the server by keeping connections open for reuse. This is because creating new connections can be resource-intensive for the server.

 

Example

private static HttpClientConnectionManager getConnectionManager() throws Exception {

	// setting up an SSLContext with custom trust management, where it will trust
	// any certificate presented by the server
	SSLContext sslContext = SSLContexts.custom()
			.loadTrustMaterial((X509Certificate[] chain, String authType) -> true).build();

	final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,
			NoopHostnameVerifier.INSTANCE);

	ConnectionConfig connectionConfig = ConnectionConfig.custom().setConnectTimeout(10, TimeUnit.SECONDS)
			.setSocketTimeout(10, TimeUnit.SECONDS).setTimeToLive(180, TimeUnit.SECONDS)
			.setValidateAfterInactivity(30, TimeUnit.SECONDS).build();

	PoolingHttpClientConnectionManager manager = PoolingHttpClientConnectionManagerBuilder.create()
			.setPoolConcurrencyPolicy(PoolConcurrencyPolicy.LAX).setConnPoolPolicy(PoolReusePolicy.LIFO)
			.setDefaultConnectionConfig(connectionConfig).setSSLSocketFactory(sslsf).setMaxConnTotal(500)
			.setMaxConnPerRoute(30).build();

	return manager;

}

Once a HttpClientConnectionManager instance is defined, we can define HttpClient using this.

RequestConfig.Builder builder = RequestConfig.custom();
builder.setResponseTimeout(5, TimeUnit.SECONDS);
RequestConfig requestConfig = builder.build();

CloseableHttpClient httpClient = HttpClientBuilder
.create()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(getConnectionManager())
.build();

 

What is PoolConcurrencyPolicy?

In the context of HTTP client PoolingHttpClientConnectionManager, PoolConcurrencyPolicy refers to how the manager handles requests when the maximum limit of connections in the pool is reached. There are typically two main policies: LAX and STRICT. Let's dive into what each policy means and when to use them:

 

LAX:

In LAX mode, when the maximum limit of connections in the pool is reached, the PoolingHttpClientConnectionManager does not strictly enforce the limit. Instead, it allows the creation of additional connections beyond the specified maximum limit, up to a certain point. The LAX mode is more lenient and flexible. It allows the system to adapt to transient spikes in the number of concurrent requests without rejecting connections outright. However, excessive use of LAX mode might lead to resource exhaustion if not managed carefully, as it can potentially allow too many connections to be established, leading to increased memory consumption and other resource constraints. Use LAX mode when you prioritize flexibility and resilience in handling peak loads and are willing to tolerate temporary increases in resource usage.

 

In summary, LAX offers higher concurrency but might exceed configured limits momentarily.

 

 

STRICT:

In STRICT mode, when the maximum limit of connections in the pool is reached, the PoolingHttpClientConnectionManager strictly enforces the limit. Any subsequent requests for connections beyond the maximum limit are blocked until existing connections are released back to the pool. The STRICT mode ensures that the number of active connections never exceeds the specified maximum limit, thereby preventing resource exhaustion and ensure more predictable resource utilization.

 

However, in STRICT mode, there's a risk of rejecting connections during peak loads if the maximum limit is reached, potentially leading to increased latency or connection failures for clients. Use STRICT mode when you prioritize resource control and predictability, and you want to ensure that the system operates within predefined resource constraints even during peak loads.

 

In summary, STRICT enforces strict limits, potentially causing blocking during peak loads. Choosing between LAX and STRICT modes depends on your specific requirements and priorities:

 

a.   If your application needs to handle fluctuating loads and you prioritize flexibility and resilience over strict resource control, LAX mode might be more suitable.

b.   If your application operates in an environment with strict resource constraints or requires predictable resource utilization, STRICT mode is more appropriate to ensure that the system doesn't exceed predefined limits.

 

In summary, PoolConcurrencyPolicy determines how PoolingHttpClientConnectionManager handles connections when the maximum limit is reached, with LAX mode offering flexibility and resilience, while STRICT mode ensures strict resource control and predictability. Choose the appropriate mode based on your application's requirements and priorities regarding resource utilization and handling of peak loads.

 

PoolReusePolicy

In Apache HTTP client 5, PoolReusePolicy determines how connections are reused from the connection pool. There are two main policies: LIFO (Last In, First Out) and FIFO (First In, First Out). Let's break down what each policy means and when to use them:

 

a.   LIFO (Last In, First Out)

In LIFO mode, the most recently used connections are the first ones to be reused from the pool. When a request is made, the connection manager prioritizes the connections that were most recently added or used, regardless of how long they've been idle in the pool.

 

b.   FIFO (First In, First Out)

In FIFO mode, the connections are reused in the order they were added to the pool. The oldest connections in the pool are the first ones to be reused. When a request is made, the connection manager prioritizes connections based on how long they've been idle in the pool, with the oldest connections being reused first.

 

In summary, PoolReusePolicy determines how connections are reused from the connection pool in Apache HTTP client 5, with LIFO prioritizing recent connections and FIFO prioritizing older connections. Choose the appropriate policy based on your application's requirements for performance, fairness, and connection lifespan.

 

Use LIFO policy to reuse as few connections as possible making it possible for connections to become idle and expire. Use FIFO policy to reuse all connections equally preventing them from becoming idle and expiring.

 

Find the below working application.

 

ConnectionPoolDemo.java

 

package com.sample.app.httpclient;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.net.ssl.SSLContext;

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
import org.apache.hc.core5.pool.PoolReusePolicy;
import org.apache.hc.core5.ssl.SSLContexts;

public class ConnectionPoolDemo {

	private static String inputStreamToString(InputStream inputStream, Charset charSet) throws IOException {

		try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, charSet))) {
			return reader.lines().collect(Collectors.joining("\n"));
		}

	}

	private static HttpClientResponseHandler<String> responseHandler = new HttpClientResponseHandler<String>() {
		@Override
		public String handleResponse(final ClassicHttpResponse response) throws IOException, ParseException {

			HttpEntity httpEntity = response.getEntity();

			try (InputStream is = httpEntity.getContent()) {
				return inputStreamToString(is, StandardCharsets.UTF_8);
			}

		}
	};

	private static HttpClientConnectionManager getConnectionManager() throws Exception {

		// setting up an SSLContext with custom trust management, where it will trust
		// any certificate presented by the server
		SSLContext sslContext = SSLContexts.custom()
				.loadTrustMaterial((X509Certificate[] chain, String authType) -> true).build();

		final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,
				NoopHostnameVerifier.INSTANCE);

		ConnectionConfig connectionConfig = ConnectionConfig.custom().setConnectTimeout(10, TimeUnit.SECONDS)
				.setSocketTimeout(10, TimeUnit.SECONDS).setTimeToLive(180, TimeUnit.SECONDS)
				.setValidateAfterInactivity(30, TimeUnit.SECONDS).build();

		PoolingHttpClientConnectionManager manager = PoolingHttpClientConnectionManagerBuilder.create()
				.setPoolConcurrencyPolicy(PoolConcurrencyPolicy.LAX).setConnPoolPolicy(PoolReusePolicy.LIFO)
				.setDefaultConnectionConfig(connectionConfig).setSSLSocketFactory(sslsf).setMaxConnTotal(500)
				.setMaxConnPerRoute(30).build();

		return manager;

	}

	public static void main(String[] args) throws Exception {

		RequestConfig.Builder builder = RequestConfig.custom();
		builder.setResponseTimeout(5, TimeUnit.SECONDS);
		RequestConfig requestConfig = builder.build();

		CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig)
				.setConnectionManager(getConnectionManager()).build();

		ClassicHttpRequest request = new HttpGet("http://localhost:8080/api/v1/employees/1");
		String response = httpClient.execute(request, responseHandler);
		System.out.println(response);
	}

}

 

 

Previous                                                    Next                                                    Home

No comments:

Post a Comment