Partilhar via


Design de aplicativo de cargas de trabalho de missão crítica no Azure

Quando você projeta um aplicativo, os requisitos funcionais e não funcionais do aplicativo são críticos. Esta á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 carga de trabalho de missão crítica do Azure Well-Architected Framework. Se você não está familiarizado com esta série, recomendamos que comece com O que é uma carga de trabalho de missão crítica?.

Arquitetura de unidade de escala

Todos os aspetos funcionais de uma solução devem ser capazes de escalar para atender às mudanças na demanda. Recomendamos que você use uma arquitetura de unidade de escala para otimizar a escalabilidade de ponta a ponta por meio da compartimentaçã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 por componentes de código, plataformas de hospedagem de aplicativos, os carimbos de implantação que cobrem os componentes relacionados e até mesmo assinaturas para dar suporte a requisitos multilocatário.

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, pois 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. Ele tem 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, além de oferecer suporte a componentes como um ponto de extremidade OAuth, armazenamento de dados e filas de mensagens. Os pontos de extremidade de API sem estado representam unidades funcionais granulares que devem se adaptar às mudanças sob demanda. A plataforma de aplicativos subjacente também deve ser capaz de ser dimensionada de acordo. Para evitar estrangulamentos no desempenho, os componentes e as dependências a jusante devem também ser dimensionados de forma adequada. Eles podem escalar independentemente, 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 carimbos de implantação regional.

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

Considerações de design

  • Âmbito de aplicação. O âmbito de uma unidade de escala, a relação entre as unidades de escala e os 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 de escala e as cotas 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 contornar os limites de escala de um serviço. Por exemplo, se um cluster AKS em uma unidade pode ter apenas 1.000 nós, você pode 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ários, a taxa de pico de solicitações esperada (solicitações por segundo) e os padrões de tráfego diários/semanais/sazonais para informar os requisitos de escala principal. Ter igualmente em conta os padrões de crescimento esperados tanto para o tráfego como para 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 quanto à resiliência, disponibilidade, latência, capacidade e observabilidade. Analise esses requisitos no contexto dos principais fluxos de usuários 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 independentemente ou como parte de uma unidade de escala que inclua outros componentes relacionados.

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

  • Defina um carimbo 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, dentro da 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 com 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 scale-out e scale-in 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 das operações de escala como uma métrica operacional.

Nota

Ao implantar em uma zona de aterrissagem do Azure, verifique se a assinatura da zona de aterrissagem é 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. A aplicação 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 de design

  • Redundância. Seu aplicativo deve ser implantado em várias regiões. Além disso, dentro de uma região, é altamente recomendável usar zonas de disponibilidade para permitir tolerância a falhas no nível do datacenter. As zonas de disponibilidade têm um perímetro de latência inferior a 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 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 (Service Level Agreement, contrato de nível de serviço) composto mais alto. No entanto, ele pode introduzir desafios em torno da sincronização de dados e consistência para muitos cenários de aplicativos. Enfrente os desafios em nível de plataforma de dados, considerando as compensações do aumento de custos e esforços de engenharia.

    Uma implantação ativa/ativa em vários provedores de nuvem é uma maneira de potencialmente mitigar 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 de recursos e 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 do pedido. A proximidade geográfica e a densidade dos utilizadores ou dos sistemas dependentes devem informar as decisões de conceção sobre a distribuição global.

  • Conectividade. A forma como a carga de trabalho é acedida pelos utilizadores ou sistemas externos influenciará o 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 obter recomendações de design e opções de configuração no nível da plataforma, consulte Plataforma de aplicativo: distribuição global.

Arquitetura orientada a eventos com acoplamento flexível

O acoplamento permite a comunicação interserviços através de interfaces bem definidas. Um acoplamento solto permite que um componente de aplicação opere de forma independente. Um estilo de arquitetura de microsserviços é consistente com os requisitos de missão crítica. Facilita a alta disponibilidade, evitando falhas em cascata.

Para acoplamento solto, recomendamos vivamente que incorpore um design orientado para 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 orientada a eventos.

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

Considerações de design

  • Dependências de tempo de execução. Serviços de acoplamento flexível não devem ser restringidos a usar a mesma plataforma de computação, linguagem de programação, tempo de execução ou sistema operacional.

  • Dimensionamento. Os serviços devem poder ser dimensionados de forma independente. Otimizar 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 acontece 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 fluxos críticos de usuários.

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

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

Exemplo: Abordagem orientada a eventos

A implementação de referência on-line de missão crítica 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 por 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, de modo que 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 autorrecuperação, que você pode implementar usando padrões de design como Repetições com Backoff e Disjuntor.

Para falhas não transitórias que não podem ser totalmente atenuadas 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 da 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 de design

  • Configurações adequadas. Não é incomum que problemas transitórios causem falhas em cascata. Por exemplo, repetir sem o back-off apropriado agrava o problema quando um serviço está sendo limitado. Você pode espaçar atrasos de repetição linearmente ou aumentá-los exponencialmente para recuar através de atrasos crescentes.

  • Objetivos de saúde. 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
Redistribuição de Carga Baseada na 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 lida com o recurso solicitado em um ritmo definido pelo trabalhador e pela capacidade do recurso solicitado de processar as solicitações. Se os consumidores esperam respostas aos seus pedidos, é necessário implementar um mecanismo de resposta separado. Aplique uma ordem de prioridades para que as atividades mais importantes sejam executadas primeiro.
Disjuntor Automático 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 em microsserviços que têm armazenamentos de dados independentes, garantindo que os serviços se atualizem uns aos outros 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 da saga. Se uma atualização de serviço falhar, a saga executará transações de compensação para neutralizar as etapas anteriores de atualização de serviço. As etapas individuais de atualização de serviço podem, elas próprias, implementar padrões de resiliência, como repetir.
Monitorização do Ponto Final do Estado de Funcionamento 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 métricas operacionais importantes para informar a integridade do aplicativo e disparar respostas operacionais, como gerar um alerta ou executar uma implantação de reversão compensadora.
Repetir Lida com falhas transitórias de forma elegante e transparente.
- Cancelar se a falha for improvável de ser transitória e é improvável que tenha sucesso se a operação for tentada novamente.
- Tente novamente se a falha for incomum ou rara e a operação provavelmente terá sucesso 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 back-off 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 operações de prioridade mais baixa e degrada a 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 internos de resiliência em vez de implementar funcionalidades personalizadas.

  • Aplique uma estratégia de back-off adequada ao tentar novamente chamadas de dependência com falha para evitar um cenário de DDoS autoinfligido.

  • Defina critérios de engenharia comuns 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 para o chamador para todas as chamadas, não apenas 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 em nuvem.

  • Certifique-se de que os dados operacionais sejam usados juntamente 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 frameworks certos. Essas decisões geralmente são impulsionadas pelos conjuntos de habilidades ou tecnologias padronizadas na organização. No entanto, é essencial avaliar o desempenho, a resiliência e as capacidades gerais de várias linguagens e estruturas.

Considerações de design

  • Capacidades do kit de desenvolvimento. Há diferenças nos recursos oferecidos pelos SDKs de serviço do Azure em vários idiomas. 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 pode não ser uma linguagem de desenvolvimento apropriada porque não há SDK primário.

  • Atualizações de recursos. Considere a frequência com que o SDK é atualizado com novos recursos para o idioma selecionado. 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 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 PaaS. Além disso, talvez não seja possível incluir software legado ou proprietário em contêineres.

Recomendações de design

  • Avalie todos os SDKs do Azure relevantes para os recursos de que você precisa e suas linguagens de programação escolhidas. Verifique o alinhamento com requisitos não funcionais.

  • Otimize a seleção de linguagens de programação e frameworks no nível de 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óximo passo

Analise as considerações para a plataforma de aplicativo.