Utilizar a redundância geográfica para criar aplicações de elevada disponibilidade

As infraestruturas baseadas na cloud, como o Armazenamento do Azure, fornecem uma plataforma durável e de elevada disponibilidade para alojar dados e aplicações. Os programadores de aplicações baseadas na cloud têm de considerar cuidadosamente como tirar partido desta plataforma para maximizar essas vantagens para os seus utilizadores. O Armazenamento do Azure oferece opções de georredundância para garantir uma elevada disponibilidade mesmo durante uma indisponibilidade regional. As contas de armazenamento configuradas para replicação georredundante são replicadas de forma síncrona na região primária e replicadas de forma assíncrona para uma região secundária a centenas de quilómetros de distância.

O Armazenamento do Azure oferece duas opções para a replicação georredundante: Armazenamento georredundante (GRS) e Armazenamento com georredundância entre zonas (GZRS). Para utilizar as opções de redundância geográfica do Armazenamento do Microsoft Azure, certifique-se de que a sua conta de armazenamento está configurada para armazenamento georredundante com acesso de leitura (RA-GRS) ou armazenamento georredundante com acesso de leitura (RA-GZRS). Caso contrário, pode saber mais sobre como alterar o tipo de replicação da conta de armazenamento.

Este artigo mostra como criar uma aplicação que continuará a funcionar, ainda que numa capacidade limitada, mesmo quando existe uma falha significativa na região primária. Se a região primária ficar indisponível, a aplicação pode mudar de forma totalmente integrada para realizar operações de leitura na região secundária até que a região primária volte a responder.

Considerações de design de aplicações

Pode estruturar a sua aplicação para lidar com falhas transitórias ou interrupções significativas ao ler a partir da região secundária quando existe um problema que interfere com a leitura da região primária. Quando a região primária estiver novamente disponível, a sua aplicação pode voltar à leitura a partir da região primária.

Tenha em atenção estas considerações-chave ao conceber a sua aplicação para disponibilidade e resiliência com RA-GRS ou RA-GZRS:

  • Uma cópia só de leitura dos dados armazenados na região primária é replicada de forma assíncrona numa região secundária. Esta replicação assíncrona significa que a cópia só de leitura na região secundária é eventualmente consistente com os dados na região primária. O serviço de armazenamento determina a localização da região secundária.

  • Pode utilizar as bibliotecas de cliente do Armazenamento do Microsoft Azure para efetuar pedidos de leitura e atualização no ponto final da região primária. Se a região primária não estiver disponível, pode redirecionar automaticamente os pedidos de leitura para a região secundária. Também pode configurar a sua aplicação para enviar pedidos de leitura diretamente para a região secundária, se assim o desejar, mesmo quando a região primária está disponível.

  • Se a região primária ficar indisponível, pode iniciar uma ativação pós-falha de conta. Quando efetua a ativação pós-falha para a região secundária, as entradas DNS que apontam para a região primária são alteradas para apontarem para a região secundária. Após a conclusão da ativação pós-falha, o acesso de escrita é restaurado para contas GRS e RA-GRS. Para obter mais informações, veja Recuperação após desastre e ativação pós-falha da conta de armazenamento.

Trabalhar com dados eventualmente consistentes

A solução proposta pressupõe que é aceitável devolver dados potencialmente obsoletos à aplicação de chamada. Uma vez que os dados na região secundária são eventualmente consistentes, é possível que a região primária fique inacessível antes de uma atualização para a região secundária ter terminado a replicação.

Por exemplo, suponha que o cliente submete uma atualização com êxito, mas a região primária falha antes de a atualização ser propagada para a região secundária. Quando o cliente pede para ler os dados de volta, recebe os dados obsoletos da região secundária em vez dos dados atualizados. Ao conceber a sua aplicação, tem de decidir se este comportamento é ou não aceitável. Se for, também tem de considerar como notificar o utilizador.

Mais adiante neste artigo, irá saber mais sobre como lidar com dados eventualmente consistentes e como verificar a propriedade Hora da Última Sincronização para avaliar eventuais discrepâncias entre os dados nas regiões primária e secundária.

Processar serviços separadamente ou todos em conjunto

Embora seja improvável, é possível que um serviço (blobs, filas, tabelas ou ficheiros) fique indisponível enquanto os outros serviços ainda estão totalmente funcionais. Pode processar as repetições para cada serviço separadamente ou pode processar as repetições genericamente para todos os serviços de armazenamento em conjunto.

Por exemplo, se utilizar filas e blobs na sua aplicação, pode decidir colocar código separado para processar erros retráveis para cada serviço. Dessa forma, um erro do serviço blob só afetará a parte da sua aplicação que lida com blobs, deixando as filas a continuar em execução normalmente. No entanto, se decidir processar todas as repetições do serviço de armazenamento em conjunto, os pedidos para os serviços de blobs e filas serão afetados se qualquer um dos serviços devolver um erro repetitivo.

Em última análise, esta decisão depende da complexidade da sua aplicação. Pode preferir lidar com falhas por serviço para limitar o impacto das repetições. Em alternativa, pode decidir redirecionar os pedidos de leitura de todos os serviços de armazenamento para a região secundária quando detetar um problema com qualquer serviço de armazenamento na região primária.

Executar a aplicação no modo só de leitura

Para se preparar eficazmente para uma falha na região primária, a aplicação tem de conseguir processar pedidos de leitura falhados e pedidos de atualização falhados. Se a região primária falhar, os pedidos de leitura podem ser redirecionados para a região secundária. No entanto, os pedidos de atualização não podem ser redirecionados porque os dados replicados na região secundária são só de leitura. Por este motivo, tem de estruturar a sua aplicação para poder ser executada no modo só de leitura.

Por exemplo, pode definir um sinalizador que é verificado antes de quaisquer pedidos de atualização serem submetidos para o Armazenamento do Azure. Quando um pedido de atualização é recebido, pode ignorar o pedido e devolver uma resposta adequada ao utilizador. Pode até optar por desativar determinadas funcionalidades até que o problema seja resolvido e notificar os utilizadores de que as funcionalidades estão temporariamente indisponíveis.

Se decidir processar erros para cada serviço separadamente, também terá de lidar com a capacidade de executar a sua aplicação no modo só de leitura por serviço. Por exemplo, pode configurar sinalizadores só de leitura para cada serviço. Em seguida, pode ativar ou desativar os sinalizadores no código, conforme necessário.

A capacidade de executar a sua aplicação no modo só de leitura também lhe permite garantir funcionalidades limitadas durante uma atualização da aplicação principal. Pode acionar a execução da aplicação no modo só de leitura e apontar para o datacenter secundário, garantindo que ninguém está a aceder aos dados na região primária enquanto faz atualizações.

Processar atualizações ao executar no modo só de leitura

Existem várias formas de processar pedidos de atualização quando são executados no modo só de leitura. Esta secção centra-se em alguns padrões gerais a considerar.

  • Pode responder ao utilizador e notificá-lo de que os pedidos de atualização não estão atualmente a ser processados. Por exemplo, um sistema de gestão de contactos pode permitir que os utilizadores acedam às informações de contacto, mas não efetuem atualizações.

  • Pode colocar as atualizações em fila noutra região. Neste caso, escreveria os seus pedidos de atualização pendentes numa fila numa região diferente e, em seguida, processaria esses pedidos depois de o datacenter primário voltar a ficar online. Neste cenário, deve informar o utilizador de que o pedido de atualização está em fila de espera para processamento posterior.

  • Pode escrever as atualizações numa conta de armazenamento noutra região. Quando a região primária voltar a ficar online, pode intercalar essas atualizações nos dados primários, consoante a estrutura dos dados. Por exemplo, se estiver a criar ficheiros separados com um carimbo de data/hora no nome, pode copiar esses ficheiros novamente para a região primária. Esta solução pode aplicar-se a cargas de trabalho como dados de registo e IoT.

Processar repetições

As aplicações que comunicam com serviços em execução na cloud têm de ser sensíveis a eventos não planeados e a falhas que possam ocorrer. Estas falhas podem ser transitórias ou persistentes, desde uma perda momentânea de conectividade a uma falha significativa devido a um desastre natural. É importante conceber aplicações na cloud com o processamento de repetições adequado para maximizar a disponibilidade e melhorar a estabilidade geral da aplicação.

Pedidos de leitura

Se a região primária ficar indisponível, os pedidos de leitura podem ser redirecionados para o armazenamento secundário. Conforme indicado anteriormente, tem de ser aceitável que a sua aplicação leia dados obsoletos potencialmente. A biblioteca de cliente do Armazenamento do Azure oferece opções para processar repetições e redirecionar pedidos de leitura para uma região secundária.

Neste exemplo, o processamento de repetições do armazenamento de Blobs é configurado na BlobClientOptions classe e será aplicado ao BlobServiceClient objeto que criamos com estas opções de configuração. Esta configuração é uma abordagem primária e secundária , na qual as repetições de pedidos de leitura da região primária são redirecionadas para a região secundária. Esta abordagem é melhor quando se espera que as falhas na região primária sejam temporárias.

string accountName = "<YOURSTORAGEACCOUNTNAME>";
Uri primaryAccountUri = new Uri($"https://{accountName}.blob.core.windows.net/");
Uri secondaryAccountUri = new Uri($"https://{accountName}-secondary.blob.core.windows.net/");

// Provide the client configuration options for connecting to Azure Blob storage
BlobClientOptions blobClientOptions = new BlobClientOptions()
{
    Retry = {
        // The delay between retry attempts for a fixed approach or the delay
        // on which to base calculations for a backoff-based approach
        Delay = TimeSpan.FromSeconds(2),

        // The maximum number of retry attempts before giving up
        MaxRetries = 5,

        // The approach to use for calculating retry delays
        Mode = RetryMode.Exponential,

        // The maximum permissible delay between retry attempts
        MaxDelay = TimeSpan.FromSeconds(10)
    },

    // If the GeoRedundantSecondaryUri property is set, the secondary Uri will be used for 
    // GET or HEAD requests during retries.
    // If the status of the response from the secondary Uri is a 404, then subsequent retries
    // for the request will not use the secondary Uri again, as this indicates that the resource 
    // may not have propagated there yet.
    // Otherwise, subsequent retries will alternate back and forth between primary and secondary Uri.
    GeoRedundantSecondaryUri = secondaryAccountUri
};

// Create a BlobServiceClient object using the configuration options above
BlobServiceClient blobServiceClient = new BlobServiceClient(primaryAccountUri, new DefaultAzureCredential(), blobClientOptions);

Se determinar que é provável que a região primária fique indisponível durante um longo período de tempo, pode configurar todos os pedidos de leitura para apontarem para a região secundária. Esta configuração é uma abordagem apenas secundária . Conforme abordado anteriormente, precisará de uma estratégia para processar pedidos de atualização durante este período de tempo e uma forma de informar os utilizadores de que apenas os pedidos de leitura estão a ser processados. Neste exemplo, criamos uma nova instância da qual utiliza o ponto final da BlobServiceClient região secundária.

string accountName = "<YOURSTORAGEACCOUNTNAME>";
Uri primaryAccountUri = new Uri($"https://{accountName}.blob.core.windows.net/");
Uri secondaryAccountUri = new Uri($"https://{accountName}-secondary.blob.core.windows.net/");

// Create a BlobServiceClient object pointed at the secondary Uri
// Use blobServiceClientSecondary only when issuing read requests, as secondary storage is read-only
BlobServiceClient blobServiceClientSecondary = new BlobServiceClient(secondaryAccountUri, new DefaultAzureCredential(), blobClientOptions);

Saber quando mudar para o modo só de leitura e apenas para pedidos secundários faz parte de um padrão de estrutura arquitetônico chamado padrão disjuntor automático, que será discutido numa secção posterior.

Atualizar pedidos

Os pedidos de atualização não podem ser redirecionados para o armazenamento secundário, que é só de leitura. Conforme descrito anteriormente, a sua aplicação tem de conseguir processar pedidos de atualização quando a região primária está indisponível.

O padrão de Disjuntor Automático também pode ser aplicado aos pedidos de atualização. Para lidar com erros de pedidos de atualização, pode definir um limiar no código, como 10 falhas consecutivas, e controlar o número de falhas dos pedidos para a região primária. Assim que o limiar for atingido, pode mudar a aplicação para o modo só de leitura para que os pedidos de atualização para a região primária deixem de ser emitidos.

Como implementar o padrão de Disjuntor Automático

O processamento de falhas que podem demorar um período variável de tempo a recuperar faz parte de um padrão de conceção arquitetónico chamado padrão disjuntor automático. A implementação adequada deste padrão pode impedir que uma aplicação tente executar repetidamente uma operação com probabilidade de falhar, melhorando assim a estabilidade e resiliência da aplicação.

Um aspeto do padrão de Disjuntor Automático é identificar quando existe um problema contínuo com um ponto final primário. Para fazer esta determinação, pode monitorizar a frequência com que o cliente encontra erros retráveis. Uma vez que cada cenário é diferente, tem de determinar um limiar adequado a utilizar para a decisão de mudar para o ponto final secundário e executar a aplicação no modo só de leitura.

Por exemplo, pode decidir efetuar o comutador se existirem 10 falhas consecutivas na região primária. Pode controlar esta situação ao manter uma contagem das falhas no código. Se ocorrer um êxito antes de atingir o limiar, defina a contagem como zero. Se a contagem atingir o limiar, mude a aplicação para utilizar a região secundária para pedidos de leitura.

Como abordagem alternativa, pode decidir implementar um componente de monitorização personalizado na sua aplicação. Este componente pode enviar continuamente um ping ao ponto final de armazenamento primário com pedidos de leitura triviais (como ler um blob pequeno) para determinar o estado de funcionamento. Esta abordagem ocuparia alguns recursos, mas não uma quantidade significativa. Quando for detetado um problema que atinge o limiar, mudaria para o modo só de leitura e só de leitura secundários . Neste cenário, quando o ping do ponto final de armazenamento primário for novamente bem-sucedido, pode voltar à região primária e continuar a permitir atualizações.

O limiar de erro utilizado para determinar quando fazer a mudança pode variar de serviço para serviço na sua aplicação, pelo que deve considerar torná-los parâmetros configuráveis.

Outra consideração é como lidar com várias instâncias de uma aplicação e o que fazer quando deteta erros retráveis em cada instância. Por exemplo, pode ter 20 VMs em execução com a mesma aplicação carregada. Processa cada instância separadamente? Se uma instância começar a ter problemas, pretende limitar a resposta apenas a essa instância? Em alternativa, quer que todas as instâncias respondam da mesma forma quando uma instância tem um problema? Processar as instâncias separadamente é muito mais simples do que tentar coordenar a resposta entre elas, mas a sua abordagem dependerá da arquitetura da sua aplicação.

Processamento de dados eventualmente consistentes

O armazenamento georredundante funciona através da replicação de transações da região primária para a região secundária. O processo de replicação garante que os dados na região secundária são eventualmente consistentes. Isto significa que todas as transações na região primária serão eventualmente apresentadas na região secundária, mas que poderá haver um atraso antes de aparecerem. Também não há garantias de que as transações chegarão à região secundária pela mesma ordem em que foram originalmente aplicadas na região primária. Se as suas transações chegarem à região secundária fora de ordem, poderá considerar que os seus dados na região secundária estão num estado inconsistente até que o serviço o recupere.

O exemplo seguinte para o armazenamento de Tabelas do Azure mostra o que pode acontecer quando atualiza os detalhes de um funcionário para torná-lo membro da função de administrador. Para efeitos deste exemplo, isto requer que atualize a entidade de colaborador e atualize uma entidade de função de administrador com uma contagem do número total de administradores. Repare como as atualizações são aplicadas fora de ordem na região secundária.

Time Transação Replicação Hora da Última Sincronização Resultado
T0 Transação A:
Inserir funcionário
entidade na primária
Transação A inserida na primária,
ainda não replicado.
T1 Transação A
replicado para
secundário
T1 Transação A replicada para secundária.
Hora da Última Sincronização atualizada.
T2 Transação B:
Atualizar
entidade employee
na primária
T1 Transação B escrita na primária,
ainda não replicado.
T3 Transação C:
Atualizar
administrador
entidade de função no
principal
T1 Transação C escrita para primária,
ainda não replicado.
T4 Transação C
replicado para
secundário
T1 Transação C replicada para secundária.
LastSyncTime não atualizado porque
a transação B ainda não foi replicada.
T5 Ler entidades
da secundária
T1 Obtém o valor obsoleto para o colaborador
entidade porque a transação B não
replicado ainda. Obtém o novo valor para
entidade de função de administrador porque C tem
replicado. A Hora da Última Sincronização ainda não
foi atualizado porque a transação B
não foi replicado. Pode ver o
a entidade de função de administrador é inconsistente
porque a data/hora da entidade é posterior
a Hora da Última Sincronização.
T6 Transação B
replicado para
secundário
T6 T6 – Todas as transações através de C têm
foi replicado, Hora da Última Sincronização
é atualizado.

Neste exemplo, suponha que o cliente muda para a leitura a partir da região secundária em T5. Pode ler com êxito a entidade de função de administrador neste momento, mas a entidade contém um valor para a contagem de administradores que não é consistente com o número de entidades de colaboradores que estão marcadas como administradores na região secundária neste momento. O cliente pode apresentar este valor, com o risco de as informações ser inconsistentes. Em alternativa, o cliente pode tentar determinar que a função de administrador está num estado potencialmente inconsistente porque as atualizações ocorreram fora de ordem e, em seguida, informar o utilizador deste facto.

Para determinar se uma conta de armazenamento tem dados potencialmente inconsistentes, o cliente pode verificar o valor da propriedade Hora da Última Sincronização . A Hora da Última Sincronização indica-lhe a hora em que os dados na região secundária foram consistentes pela última vez e quando o serviço aplicou todas as transações anteriores a esse ponto anterior no tempo. No exemplo apresentado acima, depois de o serviço inserir a entidade de colaborador na região secundária, a hora da última sincronização é definida como T1. Permanece em T1 até que o serviço atualize a entidade de colaborador na região secundária quando estiver definida como T6. Se o cliente obter a hora da última sincronização quando lê a entidade em T5, pode compará-la com o carimbo de data/hora na entidade. Se o carimbo de data/hora na entidade for posterior à hora da última sincronização, a entidade estará num estado potencialmente inconsistente e poderá tomar as medidas adequadas. A utilização deste campo requer que saiba quando a última atualização para a primária foi concluída.

Para saber como verificar a hora da última sincronização, consulte Verificar a propriedade Hora da Última Sincronização de uma conta de armazenamento.

Testar

É importante testar que a sua aplicação se comporta conforme esperado quando encontra erros retráveis. Por exemplo, tem de testar que a aplicação muda para a região secundária quando deteta um problema e, em seguida, muda para trás quando a região primária fica novamente disponível. Para testar corretamente este comportamento, precisa de uma forma de simular erros retráveis e controlar a frequência com que ocorrem.

Uma opção é utilizar o Fiddler para intercetar e modificar respostas HTTP num script. Este script pode identificar as respostas provenientes do ponto final primário e alterar o código de estado HTTP para um que a biblioteca de cliente de Armazenamento reconhece como um erro retráctil. Este fragmento de código mostra um exemplo simples de um script do Fiddler que interceta respostas a pedidos de leitura na tabela employeedata para devolver um estado 502:

static function OnBeforeResponse(oSession: Session) {
    ...
    if ((oSession.hostname == "\[YOURSTORAGEACCOUNTNAME\].table.core.windows.net")
      && (oSession.PathAndQuery.StartsWith("/employeedata?$filter"))) {
        oSession.responseCode = 502;
    }
}

Pode expandir este exemplo para intercetar um leque mais alargado de pedidos e alterar apenas o responseCode em alguns deles para simular melhor um cenário do mundo real. Para obter mais informações sobre como personalizar scripts do Fiddler, consulte Modificar um Pedido ou Resposta na documentação do Fiddler.

Se tiver configurado limiares configuráveis para mudar a sua aplicação para só de leitura, será mais fácil testar o comportamento com volumes de transações não de produção.


Passos seguintes

Para obter um exemplo completo que mostre como fazer o comutador para trás e para a frente entre os pontos finais primário e secundário, veja Exemplos do Azure – Utilizar o Padrão do Disjuntor Automático com o armazenamento RA-GRS.