Qual é a estratégia de transação mais aceita para microsserviços

71

Um dos principais problemas que tenho visto ocorrer em um sistema com microsserviços é o modo como as transações funcionam quando elas abrangem diferentes serviços. Dentro de nossa própria arquitetura, temos usado transações distribuídas para resolver isso, mas elas vêm com seus próprios problemas. Especialmente deadlocks tem sido uma dor até agora.

Outra opção parece ser algum tipo de gerenciador de transações customizado, que conhece os fluxos dentro de seu sistema, e cuidará das reversões para você como um processo em segundo plano que abrange todo o seu sistema (assim ele dirá ao outro serviços para reverter e se eles estão em baixo, notificá-los mais tarde).

Existe outra opção aceita? Ambos parecem ter suas desvantagens. O primeiro poderia causar deadlocks e um monte de outros problemas, o segundo poderia resultar em inconsistência de dados. Existem melhores opções?

    
por Kristof 27.07.2015 / 07:13
fonte

7 respostas

36

A abordagem usual é isolar os microsserviços o máximo possível - tratá-los como unidades únicas. Em seguida, as transações podem ser desenvolvidas no contexto do serviço como um todo (ou seja, não fazem parte de transações de banco de dados usuais, embora você ainda possa ter transações de banco de dados internas ao serviço).

Pense como as transações ocorrem e que tipo faz sentido para seus serviços, então você pode implementar um mecanismo de reversão que desfaz a operação original ou um sistema de confirmação de duas fases que reserva a operação original até que seja confirmada como real. É claro que ambos os sistemas significam que você está implementando o seu próprio, mas então você já está implementando seus microsserviços.

Serviços financeiros fazem esse tipo de coisa o tempo todo - se eu quiser transferir dinheiro do meu banco para o seu banco, não há uma única transação como a que você teria em um banco de dados. Você não sabe quais sistemas o banco está executando, portanto, deve tratar cada um deles de maneira eficaz, como seus microsserviços. Nesse caso, meu banco transferiria meu dinheiro da minha conta para uma conta de depósito e informaria ao seu banco que ele tinha algum dinheiro. Se esse envio falhar, meu banco reembolsará minha conta com o dinheiro que eles tentaram enviar.

    
por 27.07.2015 / 14:41
fonte
27

Acho que a sabedoria padrão é nunca ter transações cruzando limites de microsserviço. Se qualquer dado conjunto de dados realmente precisa ser atomicamente consistente com o outro, essas duas coisas pertencem a elas.

Esta é uma das razões pelas quais é muito difícil dividir um sistema em serviços até que você o tenha projetado completamente. Que no mundo moderno provavelmente significa escrito ...

    
por 27.07.2015 / 09:00
fonte
16

Acho que, se a consistência for um requisito strong em seu aplicativo, você deve se perguntar se os microsserviços são a melhor abordagem. Como Martin Fowler diz :

Microservices introduce eventual consistency issues because of their laudable insistence on decentralized data management. With a monolith, you can update a bunch of things together in a single transaction. Microservices require multiple resources to update, and distributed transactions are frowned upon (for good reason). So now, developers need to be aware of consistency issues, and figure out how to detect when things are out of sync before doing anything the code will regret.

Mas, talvez no seu caso, você pode sacrificar a consistência na posição de disponibilidade

Business processes are often more tolerant of inconsistencies than you think because businesses often prize availability more.

No entanto, também me pergunto se existe uma estratégia para transações distribuídas em microsserviços, mas talvez o custo seja muito alto. Eu queria dar-lhe meus dois centavos com o sempre excelente artigo de Martin Fowler e o CAP

    
por 27.07.2015 / 14:00
fonte
14

Como sugerido em pelo menos uma das respostas aqui, mas também em outro lugar na Web, é possível projetar um microsserviço que persista entidades juntas dentro de uma transação normal se você precisar de consistência entre as duas entidades.

Mas, ao mesmo tempo, você pode ter a situação em que as entidades realmente não pertencem ao mesmo microsserviço, por exemplo, registros de vendas e registros de pedidos (quando você pede algo para realizar a venda). Nesses casos, você pode exigir uma maneira de garantir a consistência entre os dois microservices.

Transações tradicionalmente distribuídas foram usadas e, na minha experiência, elas funcionam bem até que elas sejam dimensionadas para um tamanho em que o bloqueio se torne um problema. Você pode relaxar o bloqueio para que apenas os recursos relevantes (por exemplo, o item sendo vendido) sejam "bloqueados" usando uma mudança de estado, mas é aí que ele começa a ficar complicado porque você está entrando no território onde precisa construir lógica para fazer isso, você mesmo, em vez de ter, digamos, que um banco de dados cuida disso para você.

Trabalhei com empresas que seguiram o caminho da construção de sua própria estrutura de transações para lidar com esse problema complexo, mas eu não o recomendo porque é caro e leva tempo para amadurecer.

Existem produtos que podem ser anexados ao seu sistema, que cuidam da consistência. Um mecanismo de processo de negócios é um bom exemplo e eles geralmente lidam com a consistência eventualmente e usando a compensação. Outros produtos funcionam de maneira semelhante. Normalmente, você acaba com uma camada de software perto do (s) cliente (s), que lida com consistência e transações e chama (micro) serviços para fazer o processamento de negócios real . Um desses produtos é um conector JCA genérico que pode ser usado com soluções Java EE (para transparência: eu sou o autor). Consulte o link para obter mais detalhes e uma discussão mais profunda das questões levantadas aqui.

Outra maneira de lidar com transações e consistência é envolver uma chamada em um microsserviço em uma chamada para algo transacional como uma fila de mensagens. Pegue o exemplo de registro de registro / pedido de vendas acima - você pode simplesmente deixar o microsserviço de vendas enviar uma mensagem ao sistema de pedidos, que é confirmado na mesma transação que grava a venda no banco de dados. O resultado é uma solução assíncrona que se adapta bem a muito . Usando tecnologias como sockets da web, você pode até mesmo contornar o problema do bloqueio, que é frequentemente relacionado ao aumento de soluções assíncronas. Para mais ideias sobre padrões como este, consulte outro dos meus artigos: link .

Qualquer que seja a solução que você escolher, é importante reconhecer que apenas uma pequena parte do seu sistema estará escrevendo coisas que precisam ser consistentes - a maioria do acesso provavelmente será somente leitura. Por esse motivo, construa o gerenciamento de transações apenas nas partes relevantes do sistema, para que ele ainda possa ser bem dimensionado.

    
por 15.08.2015 / 10:36
fonte
1

Em microsserviços, existem três maneiras de obter consistência entre diff. serviços:

  1. Orquestração - Um processo que gerencia a transação e a reversão entre os serviços.

  2. Coreografia - Serviço passa as mensagens entre elas e finalmente alcança um estado consistente.

  3. Híbrido - Misturando os dois acima.

Para uma leitura completa, acesse o link: link

    
por 08.02.2018 / 07:21
fonte
0

Eu começaria com a decomposição do espaço do problema - identificando seus limites de serviço . Quando feito corretamente, você nunca precisaria ter transações entre serviços.

Diferentes serviços têm seus próprios dados, comportamento, forças motivacionais, governo, regras de negócios, etc. O bom começo é listar quais recursos de alto nível sua empresa possui. Por exemplo, marketing, vendas, contabilidade, suporte. Outro ponto de partida é a estrutura organizacional, mas esteja ciente de que há uma ressalva - por alguns motivos (políticos, por exemplo), pode não ser o esquema ideal de decomposição de negócios. Uma abordagem mais rigorosa é a análise da cadeia de valor . Lembre-se, seus serviços podem incluir pessoas também, não é estritamente software. Os serviços devem se comunicar entre si por meio dos eventos .

O próximo passo é esculpir esses serviços. Como resultado, você obtém agregados relativamente independentes . Eles representam uma unidade de consistência. Em outras palavras, seus internos devem ser consistentes e ACID. Os agregados se comunicam entre si por meio de eventos.

Se você acha que seu domínio exige consistência primeiro, pense novamente. Nenhum dos sistemas grandes e de missão crítica é construído com isso em mente. Todos eles são distribuídos e eventualmente consistentes. Verifique o artigo clássico de Pat Helland.

Aqui estão algumas dicas práticas sobre como construir um sistema distribuído .

    
por 11.09.2017 / 15:56
fonte
0

Existem muitas soluções que comprometem mais do que me sinto confortável. Concedido, se o seu caso de uso for complexo, como mover dinheiro entre diferentes bancos, alternativas mais agradáveis podem ser impossíveis. Mas vamos ver o que podemos fazer no cenário comum, onde o uso de microsserviços interfere em nossas possíveis transações de banco de dados.

Opção 1: evitar a necessidade de transações, se tudo for possível

Óbvio e mencionado antes, mas ideal se pudermos administrá-lo. Os componentes realmente pertencem ao mesmo microsserviço? Ou podemos redesenhar o (s) sistema (s) de tal forma que a transação se torne desnecessária? Talvez aceitar a não-transacionalidade seja o sacrifício mais acessível.

Opção 2: usar uma fila

Se houver certeza suficiente de que o outro serviço será bem-sucedido em tudo o que queremos, podemos chamá-lo por meio de alguma forma de fila. O item enfileirado não será coletado até mais tarde, mas podemos garantir que o item esteja enfileirado .

Por exemplo, digamos que queremos inserir uma entidade e enviar um e-mail como uma única transação. Em vez de chamar o servidor de e-mail, enfileiramos o e-mail em uma tabela.

Begin transaction
Insert entity
Insert e-mail
Commit transaction

Uma clara desvantagem é que vários microsserviços precisarão acessar a mesma tabela.

Opção 3: fazer o trabalho externo por último, pouco antes de concluir a transação

Essa abordagem baseia-se no pressuposto de que comprometer a transação é muito improvável que falhe.

Begin transaction
Insert entity
Insert another entity
Make external call
Commit transaction

Se as consultas falharem, a chamada externa ainda não ocorreu. Se a chamada externa falhar, a transação nunca será confirmada.

Essa abordagem vem com as limitações que só podemos fazer uma chamada externa, e isso deve ser feito por último (ou seja, não podemos usar seu resultado em nossas consultas).

Opção 4: criar coisas em um estado pendente

Conforme postado aqui , podemos ter vários microsserviços criando componentes diferentes, cada um em um estado pendente, não transacional.

Qualquer validação é executada, mas nada é criado em um estado definitivo. Depois que tudo foi criado com sucesso, cada componente é ativado. Normalmente, essa operação é tão simples e as chances de algo dar errado são tão pequenas que podemos até mesmo preferir fazer a ativação de forma não transacional.

A maior desvantagem é provavelmente que temos que explicar a existência de itens pendentes. Qualquer consulta selecionada precisa considerar se deve incluir dados pendentes. A maioria deve ignorá-lo. E as atualizações são outra história.

Opção 5: deixe o microservice compartilhar sua consulta

Nenhuma das outras opções faz isso por você? Então vamos não ortodoxos .

Dependendo da empresa, esta pode ser inaceitável. Estou ciente. Isso é pouco ortodoxo. Se não for aceitável, siga por outro caminho. Mas se isso se adequar à sua situação, resolve o problema de maneira simples e poderosa. Pode ser apenas o compromisso mais aceitável.

Existe uma maneira de transformar consultas de vários microsserviços em uma simples transação de banco de dados.

Retorna a consulta, em vez de executá-la.

Begin transaction
Execute our own query
Make external call, receiving a query
Execute received query
Commit transaction

Em termos de rede, cada microsserviço precisa ser capaz de acessar cada banco de dados. Tenha isso em mente, também em relação ao dimensionamento futuro.

Se os bancos de dados envolvidos na transação estiverem no mesmo servidor, isso será uma transação regular. Se eles estiverem em servidores diferentes, será uma transação distribuída. O código é o mesmo, independentemente disso.

Recebemos a consulta, incluindo seu tipo de conexão, seus parâmetros e sua string de conexão. Podemos envolvê-lo em uma classe Command bem organizada, mantendo o fluxo legível: A chamada do microserviço resulta em um comando, que executamos como parte de nossa transação.

A cadeia de conexão é o que o microserviço de origem nos fornece, portanto, para todos os efeitos, a consulta ainda é considerada executada por esse microserviço. Estamos apenas roteando fisicamente através do microserviço do cliente. Isso faz alguma diferença? Bem, nos permite colocá-lo na mesma transação com outra consulta.

Se o compromisso for aceitável, essa abordagem nos dá a transacionalidade direta de um aplicativo monolítico, em uma arquitetura de microsserviço.

    
por 23.05.2018 / 14:44
fonte