Event Race Microservice Race Condition

2

Diciamo che abbiamo 4 servizi, da A a D, che comunicano (per la maggior parte) attraverso una sorta di sistema asincrono basato sugli eventi. Quando una nuova entità viene creata in A, B & C riceve quell'evento. B crea un'entità propria in base a quell'evento e C effettua una chiamata sincrona a D per eseguire un'azione. Infine D riceve l'entità sia di A che di B ed esegue l'azione richiesta da C che richiede le entità di A e B.

Nota: A, B e D sono essenzialmente servizi CRUD che hanno anche API REST, mentre C è logica aziendale e non ha stato. A, B e D sono intesi come servizi indipendenti dal prodotto in un ambiente cloud.

Questo flusso funziona bene quando si tratta di aggiornare entità esistenti in quanto il servizio D memorizza una copia parziale dei dati ricevuti da A e B, ma crea una sorta di condizione di competizione quando A crea una nuova entità. Nota che anche se A e B hanno creato le loro entità prima che C effettui la chiamata, non c'è alcuna garanzia che D abbia letto entrambi gli eventi.

Quali sono i modi più comuni per affrontare questo? Ne ho creati diversi, ma nessuno di loro sembra particolarmente eccezionale.

  1. Riprova pattern
    • Quale entità esegue il tentativo? Poiché questa azione deve essere eseguita alla fine, presumo che averla in servizio D sia una cattiva idea, poiché non può distinguere da input errati tramite l'API REST o richiede funzionalità duplicate per distinguere tra un evento e una chiamata REST.
    • Anche se fosse accettabile, spinge la logica aziendale in un servizio CRUD.
  2. Webhook tra D e B
    • Ancora una volta, mette la logica in un servizio CRUD (kinda).
  3. Thread sleep on C
    • Questi ovunque in un servizio in esecuzione in genere sembrano un'idea orribile ...
    • Non aiuta con la condizione in cui B si abbassa mentre il resto rimane attivo.

Modifica in risposta alle risposte:

  1. Avere C aspettare i due eventi prima che invochi D
    • Questo purtroppo non garantisce che D abbia letto quegli stessi eventi, anche se è più probabile.
    • C non può inviare l'intero contenuto dell'evento da A alla richiesta, solo l'ID dell'oggetto senza duplicare i metodi dell'interfaccia REST su D; gli altri servizi a venire non avranno nulla tranne l'ID quando richiedono il / i servizio / i di D. In tutti gli altri casi, tutte le informazioni necessarie risiederanno in D per eseguire il suo servizio, il caso scomodo sembra essere solo per le nuove entità.
  2. D aspettare che entrambi creino gli eventi da A e B e poi eseguano la logica senza che C debba dirlo.
    • Questo sembra infrangere il principio della responsabilità unica.
    • A, B e D sono intesi come servizi "indipendenti dal prodotto", vedi modifica alla nota sopra. L'implicazione è che ciò porterebbe la logica in un servizio utilizzato da più prodotti, anche se la logica è specifica per un solo prodotto.
posta Asmodean 14.05.2017 - 00:21
fonte

1 risposta

1

Pensa alla tua situazione come a dati e processi che dipendono da questo. Che risultati ha questo:

  • Il servizio A crea l'entità a
  • Il servizio B crea l'entità b
  • Il servizio D esegue un processo d che dipende da a e b
  • La decisione su quando d dovrebbe accadere è fatta dal servizio C

D deve fare il suo dovere quando C glielo dice, ma C non fornisce tutte le dipendenze per quel processo.

Questo è il problema di come lo vedo. Passiamo ora alla soluzione: se vuoi mantenere D il più semplice possibile, C deve fornire tutte le informazioni necessarie a D . Non c'è modo di aggirare questo.

Se è accettabile inserire una logica in D (a seconda del linguaggio e dei framework in uso, quella logica potrebbe essere 2 LoC), puoi aspettare tutti i dati in D :

  1. L'entità a viene creata con ID 11
  2. L'entità b viene creata con ID 22 e fa riferimento a a#11
  3. C ascolta l'evento di creazione di b e poi dice a D quanto segue: "Non appena hai a#11 e b#22 , fai d con loro.
  4. D attende entrambi gli eventi e poi fa la sua cosa.

Se stai usando strumenti come Promises e ReactiveX, questa diventa una cosa davvero semplice:

// within C
class EntityEvents
    Observable<EntityCreatedEvent<T>> onCreated(Class<T> entityClass)

entityEvents.onCreated(EntityB.class).subscribe(b -> d.doYourThing(b.aId, b.id));

// within D
class EntityRepository
    Promise<EntityA> getExistingOrOnCreated(long aId)
    Promise<EntityB> getExistingOrOnCreated(long bId)

// within D - when the call "doYourThing" from C is received
Promise.all(
   entityRepository.getExistingOrOnCreated(aId),
   entityRepository.getExistingOrOnCreated(bId)
).then((entityA, entityB) -> {
   // do whatever it is you need to do
})

Questo diventa molto più difficile quando devi persiste nell'attesa per le entità. In tal caso, un lavoro programmato potrebbe essere la scelta migliore.

    
risposta data 14.05.2017 - 10:31
fonte