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


80

Um dos principais problemas que eu vi ocorrer em um sistema com microsserviços é a maneira como as transações funcionam quando se estendem por diferentes serviços. Dentro de nossa própria arquitetura, usamos transações distribuídas para resolver isso, mas elas vêm com seus próprios problemas. Especialmente os impasses têm sido uma dor até agora.

Outra opção parece ser algum tipo de gerenciador de transações customizado, que conhece os fluxos dentro do seu sistema, e cuidará das reversões para você como um processo em segundo plano que abrange todo o sistema (para informar os outros serviços a retroceder e se estiverem inoperantes, notifique-os mais tarde).

Existe outra opção aceita? Ambos parecem ter suas desvantagens. O primeiro pode causar conflitos e vários outros problemas, o segundo pode resultar em inconsistência dos dados. Existem melhores opções?


Só para ter certeza, esses microsserviços são usados ​​por muitos clientes ao mesmo tempo ou apenas um de cada vez?
Marcel

2
Eu estava tentando fazer uma pergunta independente disso, Marcel. Mas vamos supor que estamos usando um sistema grande o suficiente para fazer isso e queremos ter uma arquitetura que suporte ambos.
Kristof

4
Esta é uma pergunta mal formulada, mas muito importante. Quando a maioria das pessoas pensa em "transações", pensa quase exclusivamente em consistência e não nos aspectos de atomicidade, isolamento ou durabilidade das transações. A questão realmente deve ser declarado "Como você cria um sistema ACID-compliant dada uma arquitetura microservices Só implementação consistência e não o resto do ácido não é realmente útil A menos que você como dados errados...
Jeff Fischer

10
Você sempre pode tentar alterar minha pergunta para torná-la menos "mal formulada", Jeff Fischer.
Kristof

Esta questão e discussão estão intimamente relacionadas a essa outra questão no SO .
Paulo Merson 24/03

Respostas:


42

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

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

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 transação única como a que você faria em um banco de dados. Você não sabe quais sistemas os bancos estão executando, portanto, deve tratar efetivamente cada um como seus microsserviços. Nesse caso, meu banco transfere meu dinheiro da minha conta para uma conta retida e informa ao seu banco que eles têm algum dinheiro; se esse envio falhar, meu banco reembolsará minha conta com o dinheiro que eles tentaram enviar.


5
Você está assumindo que o reembolso nunca pode falhar.
Sanjeev Kumar Dangi

9
@SanjeevKumarDangi é menos provável e, mesmo que falhe, pode ser facilmente manipulado, pois a conta corrente e a conta real estão sob o controle do banco.
gldraphael

30

Eu acho que a sabedoria padrão é nunca ter transações além dos limites dos microsserviços. Se um determinado conjunto de dados realmente precisa ser atomicamente consistente com outro, essas duas coisas estão juntas.

Esse é um dos motivos pelos quais é muito difícil dividir um sistema em serviços até que você o tenha projetado completamente. O que no mundo moderno provavelmente significa escrito ...


37
Essa abordagem poderia muito bem levar à mesclagem de todos os microsserviços a um único aplicativo monolítico no final.
Slava Fomin II 23/03

4
Essa é a abordagem correta. Na verdade, é simples: se você precisa de uma transação entre serviços, seus serviços estão errados: redesenha-os! @SlavaFominII, o que você diz é verdade apenas se você não sabe como projetar um sistema de microsserviço. Se você se deparar com a abordagem dos microsserviços, não faça isso, seu monólito será melhor do que um projeto ruim de microsserviço. Somente quando você encontra os limites de serviço corretos é que deve dividir o monólito em serviços. Caso contrário, o uso de microsserviços não é a opção arquitetural certa, é apenas seguir o hype.
Francesc Castells

@FrancescCastells E se nossos serviços realmente exigirem transações entre outros serviços, você quer dizer que devemos ignorar contextos limitados e modelar nossos serviços de maneira que eles acabem como uma transação única? Eu sou um novato em microservices, ainda está lendo assim perdoe a minha pergunta se isso soa ingênuo ....: D: D
Bilbo Baggins

@BilboBaggins Quero dizer o contrário de ignorar contextos limitados. Por definição, as transações acontecem dentro de um contexto limitado e não em vários deles. Portanto, se você projetar corretamente seus microsserviços junto com seus contextos limitados, suas transações não deverão abranger vários serviços. Observe que, em algum momento, o que você precisa não são transações, mas um tratamento melhor da consistência eventual e ações de compensação adequadas quando as coisas não correm corretamente.
Francesc Castells

Não estou entendendo seu ponto de vista, existe uma chance de discutir isso em um bate-papo?
Bilbo Bolseiro

17

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

Os microsserviços apresentam eventuais problemas de consistência devido à sua louvável insistência no gerenciamento descentralizado de dados. Com um monólito, você pode atualizar várias coisas juntas em uma única transação. Os microsserviços exigem vários recursos para atualizar, e as transações distribuídas são desaprovadas (por um bom motivo). Portanto, agora, os desenvolvedores precisam estar cientes dos problemas de consistência e descobrir como detectar quando as coisas estão fora de sincronia antes de fazer qualquer coisa que o código se arrependa.

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

Os processos de negócios geralmente são mais tolerantes a inconsistências do que você pensa, porque as empresas geralmente prezam mais a disponibilidade.

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 lhe dar meus dois centavos com o sempre excelente artigo de Martin Fowler e o teorema da PAC .


1
Em transações comerciais distribuídas, a consistência às vezes é abordada pela adição de uma camada de orquestração como um mecanismo de fluxo de trabalho ou uma fila cuidadosamente projetada. Essa camada lida com todas as duas fases de consolidação e reversão e permite que os microsserviços se concentrem na lógica comercial específica. Mas voltando ao CAP, investir em disponibilidade e consistência torna o desempenho a vítima. Como os microsserviços se comparam a muitas classes dissociadas que compõem seus negócios no OOP?
Greg

Você poderia usar JANGADA via microservices para abordar a consistência FWIW
f0ster

1
+1. Muitas vezes me pergunto por que, em um contexto de microsserviços, as transações são desejadas, se todas as visualizações 'pull' dos dados podem ser implementadas como visualizações materializadas. Por exemplo, se um microsserviço implementa débitos de uma conta e outro créditos para outra conta, as exibições de saldos considerariam apenas pares de créditos e débitos, onde créditos e débitos incomparáveis ​​ainda estariam nos buffers da exibição materializada.
Sentinel

16

Conforme sugerido em pelo menos uma das respostas aqui, mas também em outras partes da Web, é possível projetar um microsserviço que persista entidades juntas em 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ê solicita algo para realizar a venda). Nesses casos, você pode precisar de uma maneira de garantir consistência entre os dois microsserviços.

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

Trabalhei com empresas que desenvolveram sua própria estrutura de transação para lidar com esse problema complexo, mas não o recomendo porque é caro e leva tempo para amadurecer.

Existem produtos por aí que podem ser aparafusados ​​ao seu sistema, que cuidam da consistência. Um mecanismo de processo de negócios é um bom exemplo e eles geralmente lidam com consistência eventualmente e usando compensação. Outros produtos funcionam de maneira semelhante. Você normalmente acaba com uma camada de software próxima ao (s) cliente (s), que lida com consistência e transações e chamadas (micro) serviços para realizar o processamento comercial real . Um desses produtos é um conector JCA genérico que pode ser usado com soluções Java EE (para transparência: sou o autor). Consulte http://blog.maxant.co.uk/pebble/2015/08/04/1438716480000.html para obter mais detalhes e uma discussão mais profunda sobre as questões levantadas aqui.

Outra maneira de lidar com transações e consistência é agrupar uma chamada para um microsserviço em uma chamada para algo transacional como uma fila de mensagens. Veja o exemplo de registro de vendas / registro de pedidos 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 escala muito bem. Usando tecnologias como soquetes da web, você pode até solucionar o problema de bloqueio, que geralmente está relacionado à expansão de soluções assíncronas. Para obter mais idéias sobre padrões como esse, consulte outro artigo: http://blog.maxant.co.uk/pebble/2015/08/11/1439322480000.html .

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 maior parte 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 dimensionado corretamente.


Nesse meio tempo, eu diria que é preciso considerar seriamente transformar o processo em assíncrono, onde cada etapa do processo é totalmente transacional. Veja aqui para obter detalhes: blog.maxant.co.uk/pebble/2018/02/18/1518974314273.html
Ant Kutschera #

"Faça o exemplo do registro de vendas / registro de pedidos 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". Não tenho certeza se o que você está sugerindo é basicamente uma transação distribuída, como dito, como você lidaria com o cenário de reversão neste caso? Por exemplo, a mensagem é confirmada na fila de mensagens, mas revertida no lado do banco de dados.
sactiw 8/03

@sactiw não tem certeza se eu poderia ter uma confirmação de duas fases em mente, mas evitaria isso agora e, em vez disso, gravaria meus dados comerciais, e o fato de que uma mensagem precisa ser adicionada à fila, em uma transação no meu banco de dados de microsserviço . O "fato", também conhecido como "comando", é processado de forma assíncrona depois que a transação é confirmada, usando um mecanismo de nova tentativa automatizado. Veja o artigo do blog de 10/03/2018 para um exemplo. Evite reversão ou compensação em favor de uma estratégia de encaminhamento, se possível, pois é mais fácil de implementar.
Ant Kutschera


1

Existem muitas soluções que comprometem mais do que eu estou confortável. É verdade que, se o seu caso de uso for complexo, como movimentar dinheiro entre bancos diferentes, alternativas mais agradáveis ​​podem ser impossíveis. Mas vejamos o que podemos fazer no cenário comum, onde o uso de microsserviços interfere em nossas transações de banco de dados em potencial.

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

Óbvio e mencionado antes, mas ideal se pudermos gerenciá-lo. Os componentes realmente pertencem ao mesmo microsserviço? Ou podemos redesenhar o (s) sistema (s) para 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 terá êxito no que quer que ele faça, podemos chamá-lo através de alguma forma de fila. O item na fila não será coletado até mais tarde, mas podemos garantir que o item esteja na fila .

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

Begin transaction
Insert entity
Insert e-mail
Commit transaction

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

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

Essa abordagem baseia-se na suposição de que é muito improvável que a confirmação da transação falhe.

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

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

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

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

Conforme publicado aqui , podemos fazer com que vários microsserviços criem componentes diferentes, cada um em um estado pendente, sem transação.

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

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

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

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

Dependendo da empresa, este pode ser inaceitável. Estou ciente. Isso não é ortodoxo. Se não for aceitável, siga outra rota. Mas se isso se encaixa na 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 transação simples e única do banco de dados.

Retorne 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. Lembre-se disso, também em relação à escala futura.

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

Recebemos a consulta, incluindo seu tipo de conexão, seus parâmetros e sua cadeia de conexão. Podemos agrupá-lo em uma elegante classe executável de comando, mantendo o fluxo legível: a chamada de microsserviço resulta em um comando que executamos como parte de nossa transação.

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

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


0

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

Serviços diferentes têm seus próprios dados, comportamento, forças motivacionais, governo, regras de negócios, etc. Um 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 existe uma ressalva - por algumas razões (políticas, 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 de que seus serviços também podem incluir pessoas, não é estritamente software. Os serviços devem se comunicar através de 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 também se comunicam por meio de eventos.

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

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.