How to increase Spring WebClient concurrent requests limit? - java

I have 2 microservices. First one is simply echo service which returns after 3 seconds delay. Second one calls first over spring webclient. When I simulate 501 concurrent users to request second microservice, first 500 users get response in 3 seconds and the last one gets response in 6 seconds. Means spring webclient supports only 500 concurrent connections by default. Corresponding value is configurable in apache client. But I can't find how to configure it in spring webclient.
Similar situation with WebSocket, it supports only 256 concurrent connections. How to configure it?
Tests are done with jMeter. If I test first microservice directly - there is no limit.
public TestController(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("http://localhost:8081").build();
}
#GetMapping("/test1")
public Mono<String> test1() {
return webClient
.get().uri("/test/HTTP")
.retrieve().bodyToMono(String.class);
}

Thanks Mark, solution from you link works.
Either this:
HttpClient httpClient = HttpClient.create(ConnectionProvider
.elastic("myConnectionProvider"));
this.webClient = webClientBuilder.clientConnector(new ReactorClientHttpConnector(httpClient))
.baseUrl("http://localhost:8081").build();
Or that:
HttpClient httpClient = HttpClient.create(ConnectionProvider
.fixed("myConnectionProvider", 1000));
this.webClient = webClientBuilder.clientConnector(new ReactorClientHttpConnector(httpClient))
.baseUrl("http://localhost:8081").build();

Related

how to set a timeout to a java httpClient per request

I use a httpClient of apache in java to call REST APIs which is configured as below in a static block of class, what I want is to change the connection timeout per request. is it possible? how?
static {
PoolingHttpClientConnectionManager pool = new PoolingHttpClientConnectionManager();
pool.setMaxTotal(ChatSettings.HTTP_CLIENT_THREAD_POOL_SIZE);
httpClient = HttpClients
.custom()
.setConnectionManager(pool)
.setDefaultRequestConfig(
RequestConfig
.custom()
.setConnectTimeout(DEFAULT_HTTP_TIMEOUT)
.setSocketTimeout(DEFAULT_HTTP_TIMEOUT)
.build()
)
.build();
}
One thing you can implement for sure is to follow this example: https://github.com/apache/httpcomponents-client/blob/5.1.x/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientAbortMethod.java
It demonstrates how a client can fire a request, but eventually change it's mind and tell the server it no longer wants to receive the response.
Combine this with a 'watchdog' thread that fires after some time/when the time runs out.

Java Specific webclient connect timeout per request

I have to call different urls with different connection timeout with webclient. But i found that we can set connect timeout globally only and not per resquest .. what to do please to set this timeout on a request without creating a new weblient each time.
You need to instantiate one webclient per url. For each webclient, you can set the connection timeout :
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
WebClient client = WebClient.builder()
.baseUrl("http://yourendpoint:8080")
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
Then reuse this webclient for all the calls to the configured url.

Default read and connection timeouts for reactor-netty HttpClient

I know that a read and a connection timeouts can be configured in reactor-netty HttpClient, like:
public WebClient xsdWebClient() {
HttpClient httpClient = createHttpClient(config.getConnectionTimeout(), config.getReadTimeout());
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient.followRedirect(true)))
.baseUrl(config.getHost())
.build();
}
private static HttpClient createHttpClient(int connectionTimeout, int readTimeout) {
return HttpClient.create()
.option(
ChannelOption.CONNECT_TIMEOUT_MILLIS,
(int) TimeUnit.SECONDS.toMillis(connectionTimeout))
.doOnConnected(c -> c.addHandlerLast(new ReadTimeoutHandler(readTimeout)));
}
But what are the default read and connection timeouts for reactor-netty HttpClient?
I would recommend you to use the response timeout configuration provided by Reactor Netty instead of ReadTimeoutHandler. You can configure the response timeout either globally on HttpClient level for all requests or per request.
The default values for various timeouts provided by Reactor Netty you can find in the reference documentation.
Referring to answer given by one of the devs of reactor-netty, the read default time is 10 seconds.

How to set and handle timeout in Spring WebClient?

Spring docs says it is required to configure http client for WebClient manually to set timeouts: https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-client-builder-reactor-timeout.
But since WebClient returns reactive Mono, it's possible (api-wise) to apply .timeout method.
Does it have the same effect?
Moreover, when one uses .timeout method, Reactor's TimeoutException is expected. Will the same error appear in the stream if configuration is done manually i.e. will doOnError(TimeoutException.class, ...) work?
My findings
Setting a timeout in a http client specific way will lead to http client specific exception i.e. WebClient doesn't wrap exceptions:
#Test
void test() {
var host = "localhost";
var endpoint = "/test";
var port = 8089;
var timeout = Duration.ofSeconds(3);
WireMockServer wireMockServer = new WireMockServer(wireMockConfig().port(8089));
wireMockServer.start();
WireMock.configureFor(host, wireMockServer.port());
WireMock.stubFor(get(urlEqualTo(endpoint))
.willReturn(aResponse().withFixedDelay((int) timeout.toMillis())));
HttpClient httpClient = HttpClient.create()
.tcpConfiguration(client ->
client.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler((int) (timeout.toSeconds() / 2)))
.addHandlerLast(new WriteTimeoutHandler((int) (timeout.toSeconds() / 2)))));
WebClient webClient = WebClient.builder()
.baseUrl(format("http://%s:%d", host, port))
.clientConnector(new ReactorClientHttpConnector(httpClient)).build();
webClient.get().uri(endpoint).retrieve().bodyToMono(Recommendation.class).block();
}
This will lead to io.netty.handler.timeout.ReadTimeoutException.
.timeout(timeout.dividedBy(2)).block() leads to regular TimeoutException (java.util.concurrent) but it's still an open question whether a web client takes care about connections afterwards (probably not).
My solution is to use http client specific configuration to ensure native and correct way to utilize connections while adding new handler that wraps http client related exception into more generic ones (or java.util.concurrent.TimeoutException) so that WebClient clients won't depend on provider exceptions.

Spring Boot: efficiently get data from REST API

I have a Spring Boot application that (among other things) gets some data from a third party JSON API (secured with OAuth), processes the result and presents it to the user. The application receives approx. 1 request each second.
Unfortunately this process is very slow at the moment (and in many cases even ends with a 503 error) and I am looking for some idea to improve the implementation. (by the way: the third party API itself does not seem to be the bottleneck as a instance of my app running on my local machine using the exact same API response very fast at the same time that the deploy instance takes very long).
For the API call I use the Apache HTTP library - or more specifically the Async HTTP Client:
this.httpClientAsync = HttpAsyncClients.custom()
.setDefaultCredentialsProvider(credsProvider) //for forward proxy
.build();
And the actual call to the API is this:
updateToken(); //get or update OAuth Token
HttpGet httpget = new HttpGet(URL);
httpget.addHeader("Authorization", "Bearer " + accessToken);
Future<HttpResponse> f = this.httpClientAsync.execute(httpget, callback);
Do you have any suggestion on how to improve the implementation?
To be honest, I don't even have an idea where the bottleneck is at the moment. Any idea on how to find out about that?
Thanks for your hints!
One more thing/update:
the Spring Controller looks something like this:
#RequestMapping(value = "/api/v1/api_data")
public DeferredResult<ResponseEntity<Map>> getAPIData() throws IOException, InterruptedException {
DeferredResult<ResponseEntity<Map>> res = new DeferredResult<>();
triggerAPICall(new FutureCallback() {
public void completed(Object o) {
(...)
res.setResult(...);
}
(...)
}
return res;
}
Furthermore, I was originally not using the async version of the HTTP client, but the blocking version. This then even slowed down the rest of the application...

Categories