Configuração de Estratégias de Balanceamento de Carga Diferentes para Serviços Distintos

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.

Tags: spring-cloud Ribbon balanceamento-de-carga microserviços netflix-eureka

Publicado em 6-28 07:56