Acompanhe os novos blogs no site www.byiorio.com.br
Objetivo do Teste
Encontrar meios de melhorar a performance para os projetos baseados em Spring Boot no quesito REQUEST vistos no dia a dia.
Desenho do Teste
O Teste consiste em juntar informações de 2 microsserviços e retornar ao cliente.
Caso de usoOs casos são baseados em situações reais e o endpoint Balance foi dividio em 4 versões.
JAVA
Endpoint: /{accountNumber}/balance
Versao 1: Usando o restTemaplte
Versão 2: Usando WebCLient bloqueante (uso do método block)
Versão 3: Usando o WebClient não bloqueante (Uso do Mono)
Versão 4: Usando o Feign Client
Link do projeto de teste
https://github.com/lucasmi/performance_test
Resttemplate
Alguns projetos ainda estão utilizando o restTemplate para fazer as comunicações internas. Neste caso uma das alterações mais significativas para melhorar a performance foi adicionar um PoolManager das conexões.
package br.com.byiorio.performance_test.infra;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestemplateConfig {
@Bean
public RestTemplate restTemplate() {
PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager();
poolingConnectionManager.setMaxTotal(2000);
poolingConnectionManager.setDefaultMaxPerRoute(2000);
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManagerShared(true)
// .setRedirectStrategy(new LaxRedirectStrategy())
// .setConnectionReuseStrategy(NoConnectionReuseStrategy.INSTANCE)
.setConnectionManager(poolingConnectionManager)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
requestFactory.setConnectTimeout(20000);
requestFactory.setConnectionRequestTimeout(20000);
requestFactory.setReadTimeout(20000);
return new RestTemplate(requestFactory);
}
}
WebClient
Alguns projetos estão usando algumas bibliotecas para chamar outros endpoints, essas bibliotecas estão fazendo o block() na requisição, e mesmo usando webclient, o block torna tudo síncrono tendo o mesmo efeito que o resttemplate.
O ideal é Retornar o Mono no método, mudando o código abaixo para o modo assíncrono mostrado mais à frente. Lembrando que abaixo está uma representação do código, na vida real os blocks são executados em outras funções internas.
public BalanceResponse usandoWebClientComBlock(Integer accountNumber) {
BalanceResponse balanceResponse = new BalanceResponse();
// Primeira Chamada
Mono<CardResponse> card = httpClientLocalhost
.get()
.uri(HTTP_LOCALHOST_9090.concat(accountNumber.toString()).concat("/card"))
.retrieve()
.bodyToMono(CardResponse.class);
CardResponse cardResponse = card.block();
if (cardResponse != null) {
balanceResponse.setCardNumber(cardResponse.getCardNumber());
}
// Segunda Chamada
Mono<StatusResponse> status = httpClientLocalhost
.get()
.uri(HTTP_LOCALHOST_9091.concat(accountNumber.toString()).concat("/status"))
.retrieve()
.bodyToMono(StatusResponse.class);
StatusResponse statusResponse = status.block();
if (statusResponse != null) {
balanceResponse.setStatus(statusResponse.getCode());
}
// Adiciona o balance
balanceResponse.setBalance(getBalance(accountNumber));
return balanceResponse;
}
Para acontecer o paralelismo é bom sempre retornar o MONO e no momento do processamento usar métodos de apoio como o Mono.zip, o ZIP inicia o processamento assim que todas as requisições paralelas foram entregues.
public Mono<BalanceResponse> usandoWebClientSemBlock(Integer accountNumber) {
// Primeira Chamada
Mono<CardResponse> card = httpClientLocalhost
.get()
.uri(HTTP_LOCALHOST_9090.concat(accountNumber.toString()).concat("/card"))
.retrieve()
.bodyToMono(CardResponse.class);
// Segunda Chamada
Mono<StatusResponse> status = httpClientLocalhost
.get()
.uri(HTTP_LOCALHOST_9091.concat(accountNumber.toString()).concat("/status"))
.retrieve()
.bodyToMono(StatusResponse.class);
// Execução em paralelo
return Mono.zip(card, status)
.map(respostas -> {
return BalanceResponse.builder()
.status(respostas.getT2().getCode())
.cardNumber(respostas.getT1().getCardNumber())
.balance(this.getBalance(accountNumber))
.build();
});
}
Feign Client
Baseado no site https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/ foram construídas as interfaces e o método abaixo, as execuções ficaram síncronas, resultando no mesmo que o restTemplate e webclient.block().
Oficialmente o Feign não tem suporte ao desenvolvimento Reativo, conforme o link https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#reactive-support, mas é possível utilizar o https://github.com/Playtika/feign-reactive para tentar o paralelismo.
public BalanceResponse usandoFeign(Integer accountNumber) {
BalanceResponse balanceResponse = new BalanceResponse();
// Primeira Chamada
CardResponse cardResponse = feignCardClient.getCard(accountNumber);
if (cardResponse != null) {
balanceResponse.setCardNumber(cardResponse.getCardNumber());
}
// Segunda Chamada
StatusResponse statusResponse = feignStatusClient.getStatus(accountNumber);
if (statusResponse != null) {
balanceResponse.setStatus(statusResponse.getCode());
}
// Adiciona o balance
balanceResponse.setBalance(getBalance(accountNumber));
return balanceResponse;
}
Conclusão
O melhor resultado para este cenário está no uso do WebClient Reativo, onde processou o dobro de requisições por causa de seu paralelismo nas chamadas.
Veja o próximo artigo de como fazer o Feign Client em paralelo, https://www.byiorio.com.br/blog/2
performance teste, spring boot, rest, api, melhorar, improove