Hoje chamamos sua atenção para um pequeno material sobre microsserviços e arquitetura distribuída. Em particular, ele toca na ideia de Martin Fowler de que um novo sistema deve começar com um monólito e, mesmo em uma arquitetura de microsserviço desenvolvida, é aconselhável deixar um grande núcleo monolítico.
Gostar de ler!
Hoje, todo mundo pensa em microsserviços e os escreve - e não sou exceção. Com base nos princípios básicos de microsserviços e seu verdadeiro contexto, fica claro que microsserviços são um sistema distribuído.
O que é uma transação distribuída?
As transações que abrangem vários sistemas físicos ou computadores em uma rede são chamadas simplesmente de transações distribuídas. No mundo dos microsserviços, uma transação é dividida em vários serviços que são chamados em uma sequência para concluir a transação inteira.
Aqui está um sistema monolítico de loja online que usa transações:

Fig. 1: Transação em Monolith
Se no sistema acima, o usuário envia uma solicitação de pedido (Check-out) para a plataforma, a plataforma cria uma transação local no banco de dados, e essa transação abrange muitas tabelas de banco de dados para processar (processar) o pedido e reservar(Reserva) mercadorias do armazém. Se qualquer uma dessas etapas falhar, a transação pode ser revertida , o que significa a recusa do próprio pedido e das mercadorias reservadas. Este conjunto de princípios é denominado ACID (Atomicity, Consistency, Isolation, Durability) e é garantido no nível do sistema de banco de dados.
Aqui está uma decomposição de um sistema de loja online construído a partir de microsserviços:

Figura 2: Transações em um microsserviço
Após decompor este sistema, criamos microsserviços
OrderMicroservice
eInventoryMicroservice
com bancos de dados separados. Quando uma solicitação do Checkout vem de um usuário, esses microsserviços são chamados e cada um deles faz alterações em seu banco de dados. Como uma transação agora está espalhada por vários bancos de dados em vários sistemas, ela é considerada distribuída .
Qual é o problema ao confirmar transações distribuídas em microsserviços?
Com a introdução da arquitetura de microsserviço, os bancos de dados estão perdendo sua natureza ACID. Devido à possível proliferação de transações entre muitos microsserviços e, portanto, bancos de dados, é necessário lidar com os seguintes problemas principais:
Como manter a atomicidade da transação?
Atomicidade significa que, em qualquer transação, todas as etapas podem ser concluídas ou nenhuma. Se o exemplo acima falhar em concluir a operação de 'itens de pedido' no método
InventoryMicroservice
, como reverter as alterações no 'processamento de pedido' que foram aplicadas OrderMicroservice
?
Como faço para lidar com solicitações competitivas?
Suponha que um objeto de qualquer um dos microsserviços entre no banco de dados para armazenamento de longo prazo e, ao mesmo tempo, outra solicitação leia o mesmo objeto. Quais dados o serviço deve retornar - antigos ou novos? No exemplo acima, quando
OrderMicroservice
já tiver concluído o trabalho e InventoryMicroservice
estiver em processo de atualização, é necessário incluir o pedido atual no número de pedidos de pedidos feitos pelo usuário?
Os sistemas modernos são projetados com potenciais falhas em mente, e um dos principais problemas no processamento de transações distribuídas é bem articulado por Pat Helland.
Como regra, os desenvolvedores simplesmente não criam grandes aplicativos escaláveis que envolveriam o trabalho com transações distribuídas.
Soluções possíveis
Os dois problemas acima são muito críticos no contexto de projeto e construção de aplicativos baseados em microsserviços. Para resolvê-los, as seguintes duas abordagens são usadas:
- Fixação bifásica
- Consistência e Compensação Final / SAGA
1. Fixação bifásica
Como o nome indica, esse método de processamento de transações envolve dois estágios: uma fase de preparação e uma fase de confirmação. Um papel importante neste caso é desempenhado pelo coordenador da transação, organizando o ciclo de vida da transação.
Como funciona
Na fase preparatória, todos os microsserviços participantes do trabalho se preparam para o commit e notificam o coordenador de que estão prontos para concluir a transação. Então, na próxima etapa, ocorre uma confirmação ou o coordenador da transação instrui todos os microsserviços a reverter.
Considere novamente um sistema de loja online como exemplo:

Figura 3: Confirmação de duas fases bem-sucedida em um sistema de microsserviço
No exemplo acima (Figura 3), quando um usuário envia uma solicitação de pedido, o coordenador
TransactionCoordinator
primeiro inicia uma transação global com informações de contexto completas. Primeiro, ele envia o comando de preparação ao microsserviço OrderMicroservice
para criar o pedido. Em seguida, ele envia o comando de preparação paraInventoryMicroservice
para reservar itens. Quando os dois serviços estão prontos para fazer alterações, eles bloqueiam os objetos de novas alterações e notificam sobre isso TransactionCoordinator
. Depois de TransactionCoordinator
confirmar que todos os microsserviços estão prontos para aplicar suas alterações, ele solicitará que esses microsserviços os salvem, solicitando um commit da transação. Neste ponto, todos os objetos serão desbloqueados.

Figura 4: Falha na confirmação de duas fases ao trabalhar com microsserviços
Em um cenário de falha (Figura 4) - se a qualquer momento um único microsserviço não tiver tempo para se preparar,
TransactionCoordinator
cancele a transação e inicie o processo de rollback. No diagrama, OrderMicroservice
por algum motivo, não consegui criar um pedido, mas InventoryMicroservice
respondi que estava pronto para criar um pedido. O coordenador TransactionCoordinator
irá solicitar o cancelamento emInventoryMicroservice
, após o qual o serviço irá reverter todas as alterações feitas e desbloquear os objetos de banco de dados.
Benefícios
- Essa abordagem garante a atomicidade da transação. A transação será concluída quando ambos os microsserviços forem bem-sucedidos ou quando os microsserviços não fizerem nenhuma alteração.
- Em segundo lugar, essa abordagem permite isolar a leitura da gravação, uma vez que as alterações nos objetos não são visíveis até que o coordenador da transação confirme essas alterações.
- Essa abordagem é uma chamada síncrona na qual o cliente será notificado sobre o sucesso ou a falha.
desvantagens
- Nada é perfeito; os commits de duas fases são bastante lentos em comparação com as operações de microsserviço único. Eles são altamente dependentes do coordenador. transações, o que pode reduzir significativamente a velocidade do sistema durante períodos de alta carga.
- Outra grande desvantagem é o bloqueio de linha do banco de dados. O bloqueio pode se tornar um gargalo de desempenho e podem ocorrer deadlocks , onde duas transações se bloqueiam fortemente.
2. Consistência e Compensação Final / SAGA
Uma das melhores definições de consistência é dada em microservices.io: cada serviço publica um evento sempre que seus dados são atualizados. Outros serviços inscrevem-se em eventos. Quando um evento é recebido, o serviço atualiza seus dados .
Com essa abordagem, uma transação distribuída é executada como uma coleção de transações locais assíncronas nos microsserviços correspondentes. Os microsserviços trocam informações por meio do barramento de eventos.
Como funciona
Novamente, vamos dar um exemplo de um sistema em execução em uma loja online:

Figura 5: Consistência Final / SAGA, Sucesso
No exemplo acima (Figura 5), o cliente requer que o sistema processe o pedido. Essa solicitação
Choreographer
gera o evento Create Order, que inicia a transação. O Microservice OrderMicroservice
escuta este evento e cria um pedido - se esta operação for bem-sucedida, ele gerará o evento Pedido Criado. O coordenador Choreographer
ouve este evento e prossegue com os pedidos de itens, levantando o evento Reserve Itens. MicrosserviçoInventoryMicroservice
ouve este evento e encomenda mercadorias; se esse evento for bem-sucedido, ele gerará o evento Itens reservados. Neste exemplo, isso significa que a transação foi encerrada.
Toda a comunicação baseada em eventos entre microsserviços acontece através do barramento de eventos, sendo outro sistema o responsável pela sua organização (coreografia) - é assim que o problema é resolvido com complexidade desnecessária.

Figura 6: Consistência Final / SAGA, Resultado com Falha
Se, por algum motivo, os
InventoryMicroservice
itens não foram reservados (Figura 6), surge o evento Falha ao Reservar Itens. O coordenador Choreographer
escuta este evento e inicia a transação de compensação, levantando o evento Delete Order. MicrosserviçoOrderMicroservice
escuta esse evento e exclui o pedido criado anteriormente.
Benefícios
Uma grande vantagem dessa abordagem é que cada microsserviço se concentra apenas em sua própria transação atômica. Os microsserviços não são bloqueados se outro serviço levar um tempo relativamente longo para ser executado. Isso também significa que você não precisa bloquear o banco de dados. Com essa abordagem, é possível garantir uma boa escalabilidade do sistema ao trabalhar em alta carga, uma vez que a solução proposta é assíncrona e baseada no trabalho com eventos.
desvantagens
A principal desvantagem dessa abordagem é que ela não fornece isolamento de leitura. Assim, no exemplo acima, o cliente verá que o pedido foi criado, mas após um segundo o pedido será excluído durante a transação de compensação. Além disso, conforme o número de microsserviços aumenta, fica mais difícil depurá-los e mantê-los.
Conclusão
A primeira alternativa para a abordagem proposta é abandonar totalmente as transações distribuídas. Se você estiver construindo um novo aplicativo, comece com uma arquitetura monolítica, conforme descrito em MonolithFirst de Martin Fowler. Vou citá-lo.
, , . , , . —Se os dados precisam ser atualizados em dois lugares ao mesmo tempo como resultado de um único evento, então a abordagem de consistência / SAGA final é preferida em vez da abordagem de duas fases ao processar transações distribuídas. O principal motivo é que a abordagem de duas fases em um ambiente distribuído não é escalável. O uso de consistência também eventualmente levanta seu próprio conjunto de problemas, como como atualizar atomicamente o banco de dados e disparar um evento. Passando para essa filosofia de desenvolvimento, é necessário mudar sua percepção do ponto de vista do desenvolvedor e do testador.