Compartilhar via


Design de aplicativo de cargas de trabalho críticas no Azure

Quando você projeta um aplicativo, os requisitos de aplicativos funcionais e não funcionais são críticos. Essa área de design descreve padrões de arquitetura e estratégias de dimensionamento que podem ajudar a tornar seu aplicativo resiliente a falhas.

Importante

Este artigo faz parte da série de cargas de trabalho críticas do Azure Well-Architected Framework. Se você não estiver familiarizado com esta série, recomendamos que comece com O que é uma carga de trabalho crítica?.

Arquitetura de unidade de escala

Todos os aspectos funcionais de uma solução devem ser capazes de ser dimensionados para atender às alterações na demanda, idealmente dimensionando automaticamente em resposta à carga. Recomendamos que você use uma arquitetura de unidade de escala para otimizar a escalabilidade de ponta a ponta por meio da compartimentalização e também para padronizar o processo de adição e remoção de capacidade. Uma unidade de escala é uma unidade lógica ou função que pode ser dimensionada independentemente. Uma unidade pode ser composta de componentes de código, plataformas de hospedagem de aplicativos, os selos de implantação que abrangem os componentes relacionados e até mesmo assinaturas para dar suporte a requisitos multilocatários.

Recomendamos essa abordagem porque ela aborda os limites de escala de recursos individuais e de todo o aplicativo. Ele ajuda com cenários complexos de implantação e atualização porque uma unidade de escala pode ser implantada como uma unidade. Além disso, você pode testar e validar versões específicas de componentes em uma unidade antes de direcionar o tráfego do usuário para ela.

Suponha que seu aplicativo de missão crítica seja um catálogo de produtos online. Possui um fluxo de usuário para processar comentários e classificações de produtos. O fluxo usa APIs para recuperar e postar comentários e classificações e componentes de suporte, como um endpoint OAuth, armazenamento de dados e filas de mensagens. Os endpoints de API sem estado representam unidades funcionais granulares que devem se adaptar às alterações sob demanda. A plataforma de aplicativos subjacente também deve ser capaz de escalar adequadamente. Para evitar gargalos de desempenho, os componentes e dependências downstream também devem ser dimensionados em um grau apropriado. Eles podem ser dimensionados de forma independente, como unidades de escala separadas, ou juntos, como parte de uma única unidade lógica.

Exemplo de unidades de escala

A imagem a seguir mostra os escopos possíveis para unidades de escala. Os escopos variam de pods de microsserviço a nós de cluster e selos de implantação regional.

Diagrama que mostra vários escopos para unidades de escala.

Considerações sobre o design

  • Escopo. O escopo de uma unidade de escala, a relação entre as unidades de escala e seus componentes devem ser definidos de acordo com um modelo de capacidade. Leve em consideração os requisitos não funcionais de desempenho.

  • Limites de escala. Os limites e cotas de escala de assinatura do Azure podem ter influência no design do aplicativo, nas opções de tecnologia e na definição de unidades de escala. As unidades de escala podem ajudá-lo a ignorar os limites de escala de um serviço. Por exemplo, se um cluster do AKS em uma unidade puder ter apenas 1.000 nós, você poderá usar duas unidades para aumentar esse limite para 2.000 nós.

  • Carga esperada. Use o número de solicitações para cada fluxo de usuário, a taxa de solicitação de pico esperada (solicitações por segundo) e os padrões de tráfego diários/semanais/sazonais para informar os requisitos de escala principais. Considere também os padrões de crescimento esperados para o tráfego e o volume de dados.

  • Desempenho degradado aceitável. Determine se um serviço degradado com altos tempos de resposta é aceitável sob carga. Quando você está modelando a capacidade necessária, o desempenho necessário da solução sob carga é um fator crítico.

  • Requisitos não funcionais. Os cenários técnicos e de negócios têm considerações distintas para resiliência, disponibilidade, latência, capacidade e observabilidade. Analise esses requisitos no contexto dos principais fluxos de usuário de ponta a ponta. Você terá relativa flexibilidade no design, na tomada de decisões e nas escolhas de tecnologia em um nível de fluxo de usuário.

Recomendações de design

  • Defina o escopo de uma unidade de escala e os limites que acionarão a unidade para escalar.

  • Certifique-se de que todos os componentes do aplicativo possam ser dimensionados de forma independente ou como parte de uma unidade de escala que inclua outros componentes relacionados.

  • Defina a relação entre unidades de escala, com base em um modelo de capacidade e requisitos não funcionais.

  • Defina um selo de implantação regional para unificar o provisionamento, o gerenciamento e a operação de recursos de aplicativos regionais em uma unidade de escala heterogênea, mas interdependente. À medida que a carga aumenta, carimbos extras podem ser implantados, na mesma região do Azure ou em regiões diferentes, para dimensionar horizontalmente a solução.

  • Use uma assinatura do Azure como a unidade de escala para que os limites de escala em uma única assinatura não restrinjam a escalabilidade. Essa abordagem se aplica a cenários de aplicativos de alta escala que têm um volume de tráfego significativo.

  • Modele a capacidade necessária em torno de padrões de tráfego identificados para garantir que a capacidade suficiente seja provisionada nos horários de pico para evitar a degradação do serviço. Como alternativa, otimize a capacidade fora do horário de pico.

  • Meça o tempo necessário para fazer operações de expansão e redução para garantir que as variações naturais no tráfego não criem um nível inaceitável de degradação do serviço. Acompanhe as durações da operação de escala como uma métrica operacional.

Observação

Ao implantar em uma zona de destino do Azure, verifique se a assinatura da zona de destino é dedicada ao aplicativo para fornecer um limite de gerenciamento claro e evitar o antipadrão Vizinho Barulhento.

Distribuição global

É impossível evitar falhas em qualquer ambiente altamente distribuído. Esta seção fornece estratégias para mitigar muitos cenários de falha. O aplicativo deve ser capaz de resistir a falhas regionais e zonais. Ele deve ser implantado em um modelo ativo/ativo para que a carga seja distribuída entre todas as regiões.

Assista a este vídeo para obter uma visão geral de como planejar falhas em aplicativos de missão crítica e maximizar a resiliência:

Considerações sobre o design

  • Redundância. Seu aplicativo deve ser implantado em várias regiões. Além disso, em uma região, é altamente recomendável que você use zonas de disponibilidade para permitir a tolerância a falhas no nível do datacenter. As zonas de disponibilidade têm um perímetro de latência de menos de 2 milissegundos entre as zonas de disponibilidade. Para cargas de trabalho que são "tagarelas" entre zonas, essa latência pode introduzir uma penalidade de desempenho para a transferência de dados entre zonas.

  • Modelo ativo/ativo. Uma estratégia de implantação ativa/ativa é recomendada porque maximiza a disponibilidade e fornece um SLA (contrato de nível de serviço) composto mais alto. No entanto, ele pode apresentar desafios em torno da sincronização e consistência de dados para muitos cenários de aplicativos. Enfrente os desafios no nível da plataforma de dados, considerando as compensações do aumento do custo e do esforço de engenharia.

    Uma implantação ativa/ativa em vários provedores de nuvem é uma maneira de mitigar potencialmente a dependência de recursos globais em um único provedor de nuvem. No entanto, uma estratégia de implantação ativa/ativa multicloud introduz uma quantidade significativa de complexidade em torno de CI/CD. Além disso, dadas as diferenças nas especificações e recursos de recursos entre os provedores de nuvem, você precisaria de selos de implantação especializados para cada nuvem.

  • Distribuição geográfica. A carga de trabalho pode ter requisitos de conformidade para residência de dados geográficos, proteção de dados e retenção de dados. Considere se há regiões específicas onde os dados devem residir ou onde os recursos precisam ser implantados.

  • Origem da solicitação. A proximidade geográfica e a densidade de usuários ou sistemas dependentes devem informar as decisões de design sobre a distribuição global.

  • Conectividade. A forma como a carga de trabalho é acessada por usuários ou sistemas externos influenciará seu design. Considere se o aplicativo está disponível na Internet pública ou em redes privadas que usam circuitos VPN ou Azure ExpressRoute.

Para recomendações de design e opções de configuração no nível da plataforma, consulte Plataforma de aplicativos: distribuição global.

Arquitetura orientada a eventos fracamente acoplada

O acoplamento permite a comunicação entre serviços por meio de interfaces bem definidas. Um acoplamento flexível permite que um componente de aplicação opere de forma independente. Um estilo de arquitetura de microsserviços é consistente com os requisitos críticos. Ele facilita a alta disponibilidade, evitando falhas em cascata.

Para acoplamento flexível, é altamente recomendável que você incorpore o design orientado a eventos. O processamento assíncrono de mensagens por meio de um intermediário pode criar resiliência.

Diagrama que ilustra a comunicação assíncrona controlada por eventos.

Em alguns cenários, os aplicativos podem combinar acoplamento flexível e rígido, dependendo dos objetivos de negócios.

Considerações sobre o design

  • Dependências de tempo de execução. Os serviços acoplados de forma flexível não devem ser restritos a usar a mesma plataforma de computação, linguagem de programação, tempo de execução ou sistema operacional.

  • Dimensionamento. Os serviços devem ser capazes de escalar de forma independente. Otimize o uso de recursos de infraestrutura e plataforma.

  • Tolerância a falhas. As falhas devem ser tratadas separadamente e não devem afetar as transações do cliente.

  • Integridade transacional. Considere o efeito da criação e persistência de dados que ocorre em serviços separados.

  • Rastreamento distribuído. O rastreamento de ponta a ponta pode exigir orquestração complexa.

Recomendações de design

  • Alinhe os limites do microsserviço com os fluxos de usuários críticos.

  • Use a comunicação assíncrona orientada por eventos sempre que possível para dar suporte à escala sustentável e ao desempenho ideal.

  • Use padrões como Caixa de Saída e Sessão Transacional para garantir a consistência para que todas as mensagens sejam processadas corretamente.

Exemplo: abordagem orientada a eventos

A implementação de referência Mission-Critical Online usa microsserviços para processar uma única transação comercial. Ele aplica operações de gravação de forma assíncrona com um agente de mensagens e um trabalhador. As operações de leitura são síncronas, com o resultado retornado diretamente ao chamador.

Diagrama que mostra a comunicação orientada a eventos.

Padrões de resiliência e tratamento de erros no código do aplicativo

Um aplicativo de missão crítica deve ser projetado para ser resiliente para que ele aborde o maior número possível de cenários de falha. Essa resiliência maximiza a disponibilidade e a confiabilidade do serviço. O aplicativo deve ter recursos de autocorreção, que você pode implementar usando padrões de design como Repetições com Recuo e Disjuntor.

Para falhas não transitórias que você não pode mitigar totalmente na lógica do aplicativo, o modelo de integridade e os wrappers operacionais precisam tomar medidas corretivas. O código do aplicativo deve incorporar instrumentação e registro em log adequados para informar o modelo de integridade e facilitar a solução de problemas subsequente ou a análise de causa raiz, conforme necessário. Você precisa implementar o rastreamento distribuído para fornecer ao chamador uma mensagem de erro abrangente que inclua uma ID de correlação quando ocorrer uma falha.

Ferramentas como o Application Insights podem ajudá-lo a consultar, correlacionar e visualizar rastreamentos de aplicativos.

Considerações sobre o design

  • Configurações adequadas. Não é incomum que problemas transitórios causem falhas em cascata. Por exemplo, tentar novamente sem a retirada apropriada agrava o problema quando um serviço está sendo limitado. Você pode espaçar os atrasos de repetição linearmente ou aumentá-los exponencialmente para recuar por meio de atrasos crescentes.

  • Pontos de extremidade de integridade. Você pode expor verificações funcionais no código do aplicativo usando pontos de extremidade de integridade que as soluções externas podem sondar para recuperar o status de integridade do componente do aplicativo.

Recomendações de design

Aqui estão alguns padrões comuns de engenharia de software para aplicativos resilientes:

Padrão Resumo
Nivelamento de Carga Baseado em Fila Introduz um buffer entre os consumidores e os recursos solicitados para garantir níveis de carga consistentes. À medida que as solicitações do consumidor são enfileiradas, um processo de trabalho as manipula no recurso solicitado em um ritmo definido pelo trabalho e pela capacidade do recurso solicitado de processar as solicitações. Se os consumidores esperam respostas às suas solicitações, você precisa implementar um mecanismo de resposta separado. Aplique uma ordem priorizada para que as atividades mais importantes sejam executadas primeiro.
Interruptor de Circuito Fornece estabilidade aguardando a recuperação ou rejeitando solicitações rapidamente, em vez de bloquear enquanto aguarda um serviço ou recurso remoto indisponível. Esse padrão também lida com falhas que podem levar um tempo variável para serem recuperadas quando uma conexão é feita com um serviço ou recurso remoto.
Bulkhead Tenta particionar instâncias de serviço em grupos com base nos requisitos de carga e disponibilidade, isolando falhas para sustentar a funcionalidade do serviço.
Saga Gerencia a consistência de dados entre microsserviços que têm repositórios de dados independentes, garantindo que os serviços se atualizem por meio de canais de eventos ou mensagens definidos. Cada serviço executa transações locais para atualizar seu próprio estado e publica um evento para disparar a próxima transação local na saga. Se uma atualização de serviço falhar, a saga executará transações de compensação para neutralizar as etapas de atualização de serviço anteriores. As etapas de atualização de serviço individuais podem implementar padrões de resiliência, como repetição.
Monitoramento do Ponto de Extremidade de Integridade Implementa verificações funcionais em um aplicativo que ferramentas externas podem acessar por meio de pontos de extremidade expostos em intervalos regulares. Você pode interpretar as respostas dos pontos de extremidade usando as principais métricas operacionais para informar a integridade do aplicativo e disparar respostas operacionais, como gerar um alerta ou executar uma implantação de reversão de compensação.
Repetir Lida com falhas transitórias de forma elegante e transparente.
- Cancele se for improvável que a falha seja transitória e seja improvável que seja bem-sucedida se a operação for tentada novamente.
- Tente novamente se a falha for incomum ou rara e a operação provavelmente será bem-sucedida se for tentada novamente imediatamente.
- Tente novamente após um atraso se a falha for causada por uma condição que pode precisar de um curto período de tempo para se recuperar, como conectividade de rede ou falhas de alta carga. Aplique uma estratégia de retirada adequada à medida que os atrasos de repetição aumentam.
Limitação Controla o consumo de recursos usados pelos componentes do aplicativo, protegendo-os de ficarem sobrecarregados. Quando um recurso atinge um limite de carga, ele adia as operações de prioridade mais baixa e a degradação da funcionalidade não essencial para que a funcionalidade essencial possa continuar até que recursos suficientes estejam disponíveis para retornar à operação normal.

Aqui estão algumas recomendações adicionais:

  • Use SDKs fornecidos pelo fornecedor, como os SDKs do Azure, para se conectar a serviços dependentes. Use recursos de resiliência internos em vez de implementar a funcionalidade personalizada.

  • Aplique uma estratégia de retirada adequada ao repetir chamadas de dependência com falha para evitar um cenário de DDoS autoinfligido.

  • Defina critérios comuns de engenharia para todas as equipes de microsserviços de aplicativos para impulsionar a consistência e a velocidade no uso de padrões de resiliência no nível do aplicativo.

  • Implemente padrões de resiliência usando pacotes padronizados comprovados, como Polly para C# ou Sentinel para Java.

  • Use IDs de correlação para todos os eventos de rastreamento e mensagens de log para vinculá-los a uma determinada solicitação. Retorne IDs de correlação ao chamador para todas as chamadas, não apenas para as solicitações com falha.

  • Use o log estruturado para todas as mensagens de log. Selecione um coletor de dados operacionais unificado para rastreamentos, métricas e logs de aplicativos para permitir que os operadores depurem problemas facilmente. Para obter mais informações, consulte Coletar, agregar e armazenar dados de monitoramento para aplicativos de nuvem.

  • Certifique-se de que os dados operacionais sejam usados junto com os requisitos de negócios para informar um modelo de integridade do aplicativo.

Seleção de linguagem de programação

É importante selecionar as linguagens de programação e estruturas corretas. Essas decisões geralmente são orientadas pelos conjuntos de habilidades ou tecnologias padronizadas da organização. No entanto, é essencial avaliar o desempenho, a resiliência e os recursos gerais de várias linguagens e estruturas.

Considerações sobre o design

  • Recursos do kit de desenvolvimento. Há diferenças nos recursos oferecidos pelos SDKs de serviço do Azure em várias linguagens. Essas diferenças podem influenciar sua escolha de um serviço ou linguagem de programação do Azure. Por exemplo, se o Azure Cosmos DB for uma opção viável, Go poderá não ser uma linguagem de desenvolvimento apropriada porque não há nenhum SDK primário.

  • Atualizações de recursos. Considere a frequência com que o SDK é atualizado com novos recursos para o idioma selecionado. Os SDKs comumente usados, como bibliotecas .NET e Java, são atualizados com frequência. Outros SDKs ou SDKs para outros idiomas podem ser atualizados com menos frequência.

  • Várias linguagens de programação ou frameworks. Você pode usar várias tecnologias para dar suporte a várias cargas de trabalho compostas. No entanto, você deve evitar a expansão porque ela introduz complexidade de gerenciamento e desafios operacionais.

  • Opção de computação. O software herdado ou proprietário pode não ser executado em serviços de PaaS. Além disso, talvez você não consiga incluir software herdado ou proprietário em contêineres.

Recomendações de design

  • Avalie todos os SDKs relevantes do Azure para os recursos necessários e as linguagens de programação escolhidas. Verifique o alinhamento com os requisitos não funcionais.

  • Otimize a seleção de linguagens de programação e estruturas no nível do microsserviço. Use várias tecnologias conforme apropriado.

  • Priorize o SDK do .NET para otimizar a confiabilidade e o desempenho. Os SDKs do .NET Azure normalmente fornecem mais recursos e são atualizados com frequência.

Próxima etapa

Revise as considerações para a plataforma de aplicativos.