Esta é a sequência comum de dois componentes distribuídos em nosso aplicativo Java:
1 A sends request to B
2 B starts some job J in parallel thread
3 B returns response to A
4 A accepts response
5 Job finishes after some time
6 Job sends information to A
7 A receives response from a Job and updates
Este é o cenário ideal, supondo que tudo funcione. Obviamente, a vida real é cheia de falhas. Por exemplo, um dos piores casos pode ser se #6
falhar simplesmente por causa da rede: o trabalho foi executado corretamente, mas A
não sabe nada sobre ele.
Estou procurando uma abordagem leve sobre como gerenciar erros neste sistema. Observe que temos muitos componentes; portanto, agrupar todos eles apenas por causa do tratamento de erros não faz sentido. Em seguida, abandonei o uso de qualquer memória / repositório distribuído que seria novamente instalado em cada componente pelo mesmo motivo.
Meus pensamentos estão indo na direção de ter um estado absoluto em um B e nunca ter um estado persistente em um A
. Isso significa o seguinte:
- antes de
#1
marcarmosA
que a unidade de trabalho, ou seja, a mudança está prestes a começar - somente
B
pode desmarcar esse estado. A
pode buscar informações sobre aB
qualquer momento, para atualizar o estado.- nenhuma nova alteração na mesma unidade pode ser ativada
A
.
O que você acha? Existe alguma maneira leve de domar os erros neste sistema?
Respostas:
Anexar a um log persistente em A deve ser suficiente. Isso lida com reinicializações e partições de rede para obter consistência eventual ou sinalizar quebras que impedem essa convergência. Com a confirmação do grupo amortizado , pode levar menos de uma única gravação para manter uma entrada de log.
Você sugeriu tornar B responsável por desmarcar o estado. Discordo. Somente A fica ciente do novo trabalho, e somente A deve ser responsável pelo rastreamento e relatório de erros, como tempos limite. B envia mensagens idempotentes para A e A atualiza o estado, consultando novamente a intervalos conforme necessário.
Na etapa 0, A toma conhecimento de uma nova solicitação e a registra. Isso constitui uma obrigação que A deve cumprir posteriormente em algum prazo - A continuará executando e repetirá as etapas subseqüentes até que A saiba que o processamento da solicitação foi concluído.
Alguns pedidos serão mais longos que outros. As estimativas do tempo de processamento estarão disponíveis em A e B, talvez revisadas à medida que o processamento continua. Tais estimativas podem ser retornadas para A, de modo que raramente produzem tempos limite falso-positivos. Pense nisso como uma mensagem keep alive que diz "ainda trabalhando, ainda trabalhando".
fonte
Adote uma atração em vez da estratégia de envio. Faça cada peça extrair alterações das outras e atualize seus próprios registros.
(Estou usando a fila de palavras, mas você pode substituir o log ou o tópico.)
Você pode assar a fila nos serviços ou pode ter um intermediário de mensagens separado. Uma implementação inserida em um serviço pode ser tão simples quanto
GET /jobrequests?from=<timestamp>
(com B acompanhando o registro de data e hora da última solicitação de trabalho processada).Uma parte complicada dessa arquitetura é decidir a semântica, pelo menos uma vez, e no máximo uma vez. Concretamente: se B puxa um item da fila e depois trava enquanto o executa, o que deve acontecer? Existem duas possibilidades, e a mais apropriada depende do seu caso de uso:
Benefícios desta abordagem:
fonte