Introdução
Este artigo demonstra como configurar diferentes estratégias de balanceamento de carga para serviços distintos em uma aplicação Spring Cloud utilizando Ribbon. O exemplo utiliza um módulo de usuário para acessar dados de controladores de outros dois módulos, aplicando estratégias de balanceamento de carga específicas para cada serviço.
Estrutura do Projeto
Vamos criar uma estrutura de projeto básica para demonstrar o conceito. O projeto consistirá em:
- Um módulo de pedido (Order)
- Um módulo de produtos (Goods)
- Um módulo de usuário (User) que consumirá os serviços dos outros dois
Configuração do Controlador de Pedidos
package com.exemplo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
/**
* Controlador responsável por manipular requisições relacionadas a pedidos
*/
@RestController
@RequestMapping("pedido")
public class ControladorPedidos {
@RequestMapping("obterPedido")
public HashMap<Object> obterPedido(){
HashMap<Object> resposta = new HashMap<>();
resposta.put("mensagem", "Resposta do serviço de pedidos do Spring Cloud!");
return resposta;
}
}
Arquivo de Configuração (application.yml)
server:
port: 9000
eureka:
client:
serviceUrl:
defaultZone: http://eureka1:3000/eureka, http://eureka2:3001/eureka, http://eureka3:3002/eureka
instance:
instance-id: pedido-2
prefer-ip-address: true
leaseRenewalIntervalInSeconds: 10
leaseExpirationDurationInSeconds: 30
spring:
application:
name: fornecedor-pedido
Configurações de Balanceamento de Carga
Para cada serviço que será consumido, criaremos uma configuração específica de balanceamento de carga.
Configuração para Serviço de Pedidos
package com.exemplo.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Configuração específica de balanceamento de carga para o serviço de pedidos
*/
@Configuration
public class ConfiguracaoBalanceamentoPedidos {
@Bean
public IRule regraBalanceamentoPedidos() {
System.out.println("Aplicando regra de balanceamento por rodízio para serviço de pedidos");
return new RoundRobinRule();
}
}
Configuração para Serviço de Produtos
package com.exemplo.config;
import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.exemplo.regras.RegraPersonalizada;
/**
* Configuração específica de balanceamento de carga para o serviço de produtos
*/
@Configuration
public class ConfiguracaoBalanceamentoProdutos {
@Bean
public IRule regraBalanceamentoProdutos() {
System.out.println("Aplicando regra de balanceamento pseudoaleatória para serviço de produtos");
return new RegraPersonalizada();
}
}
Implementação de uma Regra Personalizada
package com.exemplo.regras;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/**
* Implementação personalizada de regra de balanceamento de carga pseudoaleatória
*/
public class RegraPersonalizada extends AbstractLoadBalancerRule {
private int indiceAtual = -1;
private int ultimoIndice = -1;
private int indicePular = -1;
public Server escolher(ILoadBalancer lb, Object chave) {
if (lb == null) {
return null;
}
Servidor servidor = null;
while (servidor == null) {
if (Thread.interrupted()) {
return null;
}
List<Servidor> servidoresDisponiveis = lb.getReachableServers();
List<Servidor> todosServidores = lb.getAllServers();
int totalServidores = todosServidores.size();
if (totalServidores == 0) {
return null;
}
// Lógica pseudoaleatória: se o mesmo servidor for selecionado duas vezes,
// a terceira vez deve ser selecionado outro servidor
int indice = gerarAleatorio(totalServidores);
if (indice == indicePular) {
System.out.println("Regerando índice para evitar repetição");
indice = gerarAleatorio(totalServidores);
indicePular = -1;
}
indiceAtual = indice;
if (ultimoIndice == indiceAtual) {
indicePular = indiceAtual;
}
ultimoIndice = indiceAtual;
servidor = servidoresDisponiveis.get(indice);
if (servidor == null) {
Thread.yield();
continue;
}
if (servidor.isAlive()) {
return servidor;
}
servidor = null;
Thread.yield();
}
return servidor;
}
protected int gerarAleatorio(int totalServidores) {
return ThreadLocalRandom.current().nextInt(totalServidores);
}
@Override
public Server choose(Object chave) {
return escolher(getLoadBalancer(), chave);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// Implementação vazia
}
}
Configuração Principle da Aplicação
A classe principal da aplicação deve ser anotada para especificar qual configuarção de balanceamento de carga será usada para cada serviço.
package com.exemplo;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.Server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import com.exemplo.regras.RegraPersonalizada;
import com.exemplo.config.ConfiguracaoBalanceamentoProdutos;
import com.exemplo.config.ConfiguracaoBalanceamentoPedidos;
@SpringBootApplication
@EnableEurekaClient
// Configuração de Ribbon para diferentes serviços com diferentes estratégias de balanceamento
@RibbonClients({
@RibbonClient(name="fornecedor-produtos", configuration = ConfiguracaoBalanceamentoProdutos.class),
@RibbonClient(name="fornecedor-pedido", configuration = ConfiguracaoBalanceamentoPedidos.class),
})
public class AplicacaoPrincipal {
public static void main(String[] args) {
SpringApplication.run(AplicacaoPrincipal.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Considerações Finais
A configuração de diferentes estratégias de balanceamento de carga para serviços distnitos permite otimizar o desempenho e a disponibilidade de acordo com as necessidades específicas de cada serviço. A implementação de regras personalizadas oferece flexibilidade para atender a requisitos de negócio específicos.