Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Minimize a coordenação para obter escalabilidade
A maioria dos aplicativos de nuvem consiste em vários serviços de aplicativos, como front-ends da Web, bancos de dados, processos de negócios e relatórios e análise. Para obter escalabilidade e confiabilidade, cada um desses serviços deve ser executado em várias instâncias.
Sistemas descoordenados, onde o trabalho pode ser tratado de forma independente, sem a necessidade de passar mensagens entre máquinas, geralmente são mais simples de escalar. A coordenação geralmente não é um estado binário, mas um espectro. A coordenação ocorre em diferentes camadas, como dados ou computação.
O que acontece quando duas instâncias tentam realizar operações simultâneas que afetam a algum estado compartilhado? Em alguns casos, deve haver coordenação entre nós, por exemplo, para preservar as garantias ACID. Neste diagrama, Node2 está aguardando Node1 para liberar um bloqueio de banco de dados:
A coordenação limita os benefícios de escala horizontal e cria afunilamentos. Neste exemplo, ao escalar horizontalmente o aplicativo e adicionar mais instâncias, você verá uma maior contenção de bloqueio. No pior dos casos, as instâncias de front-end passam a maior parte do tempo aguardando bloqueios.
A semântica "Exatamente uma vez" é outra fonte frequente de coordenação. Por exemplo, um pedido deve ser processado exatamente uma vez. Dois trabalhadores estão ouvindo novas ordens.
Worker1 recebe um pedido para processamento. O aplicativo deve garantir que Worker2 não duplique o trabalho e que, caso Worker1 falhe, o pedido não seja descartado.
Você pode usar um padrão como Supervisor de Agente de Agendador para coordenar entre os trabalhadores, mas nesse caso, uma abordagem melhor seria particionar o trabalho. Cada trabalhador recebe um determinado intervalo de pedidos (por exemplo, por região de cobrança). Se um trabalhador falhar, uma nova instância assume de onde a instância anterior parou, mas várias instâncias não estão em contenda.
Recomendações
Use componentes desacoplados que se comunicam de forma assíncrona. De modo ideal, os componentes devem usar eventos para se comunicarem entre si.
Adote a consistência eventual. Quando os dados são distribuídos, é necessário coordenação para impor as garantias de consistência rigorosa. Por exemplo, suponha que uma operação atualize dois bancos de dados. Em vez de colocá-lo em um escopo de transação única, é melhor se o sistema puder acomodar a consistência eventual, talvez usando o padrão Transação Compensatória para reverter logicamente após uma falha.
Usar eventos de domínio para sincronizar o estado. Um evento de domínio é um evento que registra quando acontecer algo que tenha importância no domínio. Os serviços interessados podem escutar o evento, em vez de usar uma transação global para coordenar entre vários serviços. Se essa abordagem for usada, o sistema deverá tolerar a consistência eventual (veja o item anterior).
Considere padrões como CQRS e sourcing de eventos. Esses dois padrões podem ajudar a reduzir a contenção entre as cargas de trabalho de leitura e gravação.
O padrão CQRS separa as operações de gravação e de leitura. Em algumas implementações, os dados de leitura são separados fisicamente dos dados de gravação.
No Event Sourcing pattern, as alterações de estado são registradas como uma série de eventos em um armazenamento de dados somente de adição. Adicionar um evento ao fluxo é uma operação atômica, exigindo bloqueio mínimo.
Esses dois padrões se complementam. Se o armazenamento de somente gravação no CQRS utiliza event sourcing, o armazenamento de somente leitura pode escutar os mesmos eventos e criar um instantâneo legível do estado atual, otimizado para consultas. Antes de adotar o CQRS ou o sourcing de eventos, esteja ciente dos desafios dessa abordagem.
Dividir dados e estado. Evite colocar todos os seus dados em um esquema de dados que seja compartilhado entre vários serviços de aplicativo. Uma arquitetura de microsserviços impõe esse princípio, tornando cada serviço responsável por seu próprio armazenamento de dados. Em um único banco de dados, particionar os dados em fragmentos pode melhorar a simultaneidade, pois uma gravação de serviço em um fragmento não afeta uma gravação de serviço em um fragmento diferente. Embora o particionamento adicione algum grau de coordenação, você pode usar o particionamento para aumentar o paralelismo para melhor escalabilidade. Particione o estado monolítico em partes menores para que os dados possam ser gerenciados de forma independente.
Criar operações idempotentes. Quando possível, projete as operações como idempotentes. Dessa forma, elas poderão ser manipuladas usando a semântica de pelo menos uma vez. Por exemplo, você pode colocar itens de trabalho em uma fila. Se um trabalhador deixar de funcionar no meio de uma operação, outro trabalhador continuará com a tarefa. Se o trabalhador precisar atualizar dados e emitir outras mensagens como parte de sua lógica, o padrão de processamento de mensagens idempotente deverá ser usado.
Use concorrência otimista quando possível. O controle de simultaneidade pessimista usa bloqueios de banco de dados para evitar conflitos. Isso pode causar baixo desempenho e reduzir a disponibilidade. Com o controle de simultaneidade otimista, cada transação modifica uma cópia ou um instantâneo dos dados. Quando a transação é confirmada, o mecanismo de banco de dados valida a transação e rejeita qualquer transação que possa afetar a consistência do banco de dados.
O banco de dados SQL do Azure e o SQL Server oferecem suporte à simultaneidade otimista por meio de isolamento por instantâneo. Alguns serviços de armazenamento do Azure oferecem suporte à simultaneidade otimista com Etags, incluindo o Azure Cosmos DB e o Armazenamento do Azure.
Considere o MapReduce ou outros algoritmos paralelos e distribuídos. Dependendo dos dados e do tipo de trabalho a serem executados, você poderá dividir o trabalho em tarefas independentes que podem ser executadas por vários nós trabalhando em paralelo. Veja Estilo de arquitetura de computação de alto desempenho.
Use eleição de líder para coordenação. Em casos em que você precisa coordenar operações, verifique se o coordenador não se torne um ponto único de falha no aplicativo. Com o padrão Eleição do Líder, uma instância sempre atua como líder e coordena as operações. Se o líder falhar, uma nova instância é escolhida para ser o líder.