Estratégias de Alta Disponibilidade em Ambientes Distribuídos

Em sistemas distribuídos, a alta disponibilidade é crucial para garantir que os serviços permaneçam acessíveis mesmo diante de falhas. Este artigo explora diversas abordagens para construir e manter a resiliência em aplicações, focando em mecanismos de tolerância a falhas, balenceamento de carga e padrões de disjuntores.

Resiliência do Dubbo em Caso de Falha do Zookeeper

Uma preocupação comum em arquiteturas baseadas em microsserviços é a dependência de um registro de serviço central, como o Zookeeper. Uma pergunta frequente em entrevistas é: "Se o Zookeeper falhar, os consumidores de serviço ainda podem invocar os provedores?" A resposta é afirmativa, graças ao cache local do Dubbo.

O Dubbo foi projetado com robustez em mente. Se o centro de monitoramento ficar offline, a operação do serviço não é afetada, embora alguns dados de telemetria possam ser perdidos. Em caso de falha do banco de dados, o registro de serviço pode continuar a fornecer listas de serviços a partir do cache, embora novos serviços não possam ser registrados. Com um cluster de registro de serviço, a falha de um nó leva à comutação automática para outro.

Mais importante ainda, se todos os nós do registro de serviço falharem, os provedores e consumidores de serviço ainda podem se comunicar usando seus caches locais. Isso garante que a funcionalidade existente não seja interrompida imediatamente. Os provedores de serviço são geralmente sem estado; a falha de um não impacta o uso geral. No entanto, se todos os provedores de um serviço específico falharem, os consumidores não conseguirão utilizá-lo e tentarão reconexões contínuas até que o serviço seja restaurado.

Para cenários onde um registro de serviço não está disponível ou para fins de depuração, o Dubbo permite a conexão direta entre o consumidor e o provedor. Isso pode ser configurado no consumidor, especificando o URL direto do provedor:

// Exemplo de configuração de referência direta
@Reference(url="dubbo://127.0.0.1:20880")
private MyService myService;

Configuração de Balanceamento de Carga em Clusters Dubbo

Em um ambiente de cluster, onde programas e funcionalidades idênticas são implantados em múltiplas máquinas, o balanceamento de carga é essencial para distribuir requisições e otimizar a utilização dos recursos. O Dubbo oferece diversas estratégias de balanceamento de carga, com a estratégia random sendo a padrão.

O objetivo do balanceamento de carga é distribuir uniformemente as requisições, evitando que alguns servidores fiquem sobrecarregados enquanto outros permanecem ociosos. Isso garante que cada servidor opere dentro de sua capacidade, prevenindo timeouts e melhorando a performance geral do sistema.

Estratégias de Balanceamento de Carga do Dubbo

As principais estratégias incluem:

1. Random LoadBalance (Aleatório)

  • Distribuição aleatória com base em pesos.
  • A probabilidade de colisão é maior em uma única janela de tempo, mas a distribuição tende a ser mais uniforme com um grande volume de chamadas.
  • Permite ajuste dinâmico do peso dos provedores.
  • Como funciona: Cada servidor tem um peso. Esses pesos são mapeados para um intervalo numérico. Um número aleatório é gerado, e o servidor cujo intervalo contém esse número é selecionado. Servidores com maior peso ocupam um intervalo maior e, portanto, têm maior probabilidade de serem escolhidos.
  • É a implementação padrão devido à sua simplicidade e eficiência, apesar de poder apresentar desequilíbrios com poucas chamadas.

2. RoundRobin LoadBalance (Round-Robin)

  • Distribuição sequencial, com pesos ajustando a proporção.
  • Pode levar a um acúmulo de requisições em provedores lentos, pois um provedor lento pode prender a fila de processamento até que sua requisição seja concluída.
  • Como funciona: As requisições são distribuídas sequencialmente para cada servidor. Com ponderação, a quantidade de requisições que cada servidor recebe é proporcional ao seu peso. Por exemplo, com pesos 5:2:1 para A, B, C, em 8 requisições, A receberia 5, B receberia 2 e C receberia 1.

3. LeastActive LoadBalance (Menor Atividade)

  • Seleciona o provedor com o menor número de chamadas ativas. Em caso de empate, escolhe aleatoriamente entre eles, considerando pesos.
  • Favorece provedores mais rápidos, pois estes completam requisições mais rapidamente, diminuindo seu contador de atividade.
  • Como funciona: Cada provedor mantém um contador de "requisições ativas". Quando uma requisição é recebida, o contador é incrementado; ao finalizar, é decrementado. Provedores mais rápidos têm seus contadores reduzidos mais rapidamente, tornando-os mais propensos a serem escolhidos para novas requisições. Pesos são aplicados em caso de empate na contagem de atividades.

4. ConsistentHash LoadBalance (Hash Consistente)

  • Garante que requisições com os mesmos parâmetros sempre sejam enviadas para o mesmo provedor.
  • Quando um provedor falha, as requisições que seriam direcionadas a ele são distribuídas para outros provedores de forma mais suave, utilizando nós virtuais, minimizando interrupções.
  • Como funciona: Hash dos nós do provedor (e nós virtuais) e dos parâmetros da requisição em um anel de hash. Uma requisição é direcionada para o primeiro nó do provedor que é maior ou igual ao hash da requisição no anel. Nós virtuais são usados para garantir uma distribuição mais uniforme e evitar desequilíbrios de dados.
  • Pode ser configurado para usar parâmetros específicos para o hash (hash.arguments) e a quantidade de nós virtuais (hash.nodes).

Configuração do Balanceamento de Carga

As estratégias de balanceamento de carga podem ser configuradas em diferentes níveis:

Nível de Serviço (Provedor)

<!-- XML -->
<dubbo:service interface="..." loadbalance="roundrobin" />

// Anotação
@Service(loadbalance="roundrobin")

Nível de Serviço (Consumidor)

<!-- XML -->
<dubbo:reference interface="..." loadbalance="roundrobin" />

// Anotação
@Reference(loadbalance="roundrobin")

Nível de Método (Provedor)

<dubbo:service interface="...">
    <dubbo:method name="myMethod" loadbalance="roundrobin"/>
</dubbo:service>

Nível de Método (Consumidor)

<dubbo:reference interface="...">
    <dubbo:method name="myMethod" loadbalance="roundrobin"/>
</dubbo:reference>

Integração com Hystrix para Disjuntores e Degradação de Serviço

Em um cenário de alta carga, a degradação de serviço é uma técnica estratégica para manter a funcionalidade principal do sistema. Ela envolve a desabilitação seletiva ou o tratamento simplificado de serviços e funcionalidades não essenciais, liberando recursos do servidor para garantir que as operações críticas continuem funcionando de forma eficiente.

O Dubbo permite a degradação de serviço configurando regras de override dinâmicas no registro. Por exemplo, para que um consumidor retorne null imediatamente sem invocar um serviço remoto:

// Exemplo de configuração para degradação de serviço
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));

  • mock=force:return+null: O consumidor retorna null diretamente, sem tentativas de chamada remota. Útil para isolar serviços não críticos.
  • mock=fail:return+null: O consumidor tenta a chamada, mas se falhar, retorna null em vez de lançar uma exceção.

Modos de Tolerância a Falhas em Clusters

Quando uma chamada em cluster falha, o Dubbo oferece várias estratégias de tolerância a falhas, com failover sendo a padrão.

1. Failover Cluster (Falha e Retentativa)

  • Se uma falha ocorrer, o sistema automaticamente tenta outros servidores.
  • Comum para operações de leitura, mas pode introduzir maior latência devido às retentativas.
  • O número de retentativas (excluindo a primeira tentativa) pode ser configurado com retries="2".

2. Failfast Cluster (Falha Rápida)

  • Realiza apenas uma chamada e, se falhar, reporta o erro imediatamente.
  • Ideal para operações de escrita não idempotentes (ex: criar um novo registro).

3. Failsafe Cluster (Tolerância a Falhas)

  • Ignora exceções e erros.
  • Adequado para operações como registro de auditoria, onde a falha não deve impactar o fluxo principle.

4. Failback Cluster (Falha e Recuperação)

  • Registra requisições falhas em segundo plano e as reenvia periodicamente.
  • Usado para operações de notificação ou tarefas assíncronas que podem ser retentadas mais tarde.

5. Forking Cluster (Chamada Paralela)

  • Invoca múltiplos servidores em paralelo e retorna assim que uma das chamadas é bem-sucedida.
  • Recomendado para operações de leitura com alta exigência de tempo real, mas consome mais recursos do servidor.
  • O número máximo de chamadas paralelas pode ser definido com forks="2".

6. Broadcast Cluster (Chamada em Broadcast)

  • Chama todos os provedores em sequência. Um erro em qualquer provedor resulta em uma falha geral.
  • Típico para operações de notificação em massa, como atualização de cache ou logs em todos os provedores.

Configuração do Modo de Cluster

O modo de cluster pode ser configurado tanto no lado do provedor quanto do consumider:

<!-- Nível de Serviço -->
<dubbo:service cluster="failsafe" />
<dubbo:reference cluster="failsafe" />

<!-- Nível de Método (exemplo para retries) -->
<dubbo:reference>
	<dubbo:method name="findFoo" retries="2" />
</dubbo:reference>

Integração com Hystrix

Hystrix, parte do ecossistema Netflix OSS, oferece um poderoso mecanismo de disjuntores e isolamento para serviços. Ele é projetado para controlar pontos de acesso a sistemas remotos e bibliotecas de terceiros, proporcionando tolerância a falhas robusta contra latência e falhas.

1. Adicionar Dependência Spring Cloud Hystrix

Para integrar Hystrix em uma aplicação Spring Boot, adicione a dependência spring-cloud-starter-netflix-hystrix no pom.xml:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>1.4.4.RELEASE</version> <!-- Usar a versão compatível com seu Spring Boot -->
</dependency>

Em seguida, habilite o Hystrix na classe principal da aplicação Spring Boot com @EnableHystrix:

@SpringBootApplication
@EnableHystrix
public class ApplicationProvider {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationProvider.class, args);
    }
}

2. Configurar o Provedor (Provider) com Hystrix

No provedor de serviços Dubbo, adicione a anotação @HystrixCommand ao método que você deseja proteger. Isso fará com que as chamadas a esse método passem pelo proxy Hystrix.

@Service(version = "1.0.0")
public class ProductCatalogService implements ProductService {

    @HystrixCommand(commandProperties = {
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), // Número mínimo de requisições em um período para o disjuntor avaliar
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") // Timeout da execução
    })
    @Override
    public String getProductDetails(String productId) {
        // Simula uma falha para demonstrar o Hystrix
        throw new RuntimeException("Falha simulada no serviço de produto.");
    }
}

3. Configurar o Consumidor (Consumer) com Hystrix e Fallback

No lado do consumidor, você pode encapsular a chamada ao serviço Dubbo dentro de um método anotado com @HystrixCommand. O atributo fallbackMethod especifica um método a ser executado caso a chamada principal falhe ou o disjuntor seja acionado.

@Reference(version = "1.0.0")
private ProductService productService;

@HystrixCommand(fallbackMethod = "getDefaultProductDetails")
public String retrieveProductInformation(String productId) {
    return productService.getProductDetails(productId);
}

// Método de fallback que será chamado se o serviço falhar
public String getDefaultProductDetails(String productId) {
    return "Detalhes do produto não disponíveis. Retornando valor de fallback.";
}

Tags: dubbo Hystrix AltaDisponibilidade BalanceamentoDeCarga TolerânciaAFalhas

Publicado em 6-12 19:51 por Thomas