Anche per il tuo scenario a 1 nodo, se vuoi che quanto sopra funzioni correttamente in caso di guasto della macchina / rete, allora quello che stai chiedendo è di risolvere consenso . Questo è impossibile da raggiungere con il significato tecnico di "disponibilità". Questo è il teorema CAP . (Attenzione alla dicitura fuorviante che descrive la "disponibilità" in quella pagina. Una formulazione migliore è: "qualsiasi richiesta valida riceverà una risposta di successo non importa quale ". il sistema non è disponibile se restituisce un messaggio di errore per ogni richiesta o, in effetti, qualsiasi richiesta valida. Una richiesta non valida, come la lettura di una chiave che non esiste, può ovviamente avere una risposta di errore. ) Per "alta disponibilità", tuttavia, probabilmente si intende qualcosa del tipo "funzionerà anche se i server N non funzionano". Questo è realizzabile ed è ciò che fanno algoritmi come Paxos, Viewstamped Replication e Raft.
Detto questo, davvero non vuoi implementare questi algoritmi per l'uso di produzione. È molto facile fare errori molto sottili quando li si implementa. Fortunatamente, altri hanno già realizzato implementazioni pronte per la produzione. In particolare, consiglio di utilizzare Apache ZooKeeper , o possibilmente Apache Kafka (che è stato costruito in cima a ZooKeeper). Se decidi di utilizzare ZooKeeper "direttamente", probabilmente vorrai dare un'occhiata a Apache Curator .
Se le tue operazioni sono idempotenti (e sembra proprio che possano essere) e i lavori degli ordini siano eseguiti su nodi diversi non importa, allora avere il master essere un cluster Kafka e avere i nodi sottoscrivere argomenti indipendenti gestirà questo graziosamente con una buona efficienza. Basta impegnare l'offset dopo aver gestito ogni richiesta. Se l'ordine è importante tra i nodi, è possibile utilizzare un singolo argomento a cui tutti i nodi si iscrivono per ottenere le richieste. Quindi tutti i nodi inviano le loro risposte a un argomento Kafka separato. Infine, tutti i nodi consumano anche l'argomento della risposta e elaborano solo la richiesta successiva quando viene utilizzata una risposta alla richiesta precedente. Le risposte duplicate possono essere facilmente ignorate e compattate.
Per ottenere esattamente una volta la semantica è necessario essere in grado di commettere atomicamente l'offset del log con qualsiasi stato persistente del nodo. Se lo stato del tuo nodo è una funzione deterministica della cronologia delle richieste ricevute, allora una volta la semantica è banale: tutte le informazioni necessarie sono nel registro di Kafka, basta riprodurle. Questo scenario è estremamente desiderabile, si adatta molto naturalmente a Kafka e semplifica notevolmente le cose. In questo scenario, la replica del nodo è banale; puoi semplicemente eseguire più copie, puoi girarle ogni volta e ricreare il loro stato, non è necessario alcun coordinamento tra loro.
Se lo stato dei nodi non è una funzione deterministica della cronologia delle richieste che riceve, allora ci si trova in una posizione potenzialmente pelosa che consiste essenzialmente nell'implementare un registro di replica. La buona notizia è che ZooKeeper e Kafka sono in grado di gestire molti degli aspetti più complicati. Puoi utilizzare ZooKeeper per eleggere un leader e Kafka per archiviare e distribuire il registro di replica. Il leader consumerà il registro delle richieste, eseguirà qualsiasi azione non deterministica come leggere l'orologio locale o fare richieste web ad altri server, calcolare un delta tra lo stato corrente e il nuovo stato provvisorio, quindi pubblicare quel delta, che può essere applicato in modo deterministico a un argomento di Kafka che rappresenta il registro di replica. I nodi (entrambi i follower e il leader) quindi utilizzano il registro di replica applicando i delta al loro stato locale. Il leader pubblica la risposta all'argomento appropriato quando applica il delta che legge dal log di replica. È possibile che vengano pubblicati più delta per la stessa richiesta in caso di modifica della leadership durante l'elaborazione. Questo non è un problema poiché il log di replica è rigorosamente ordinato e qualsiasi delta dopo il primo può essere ignorato. (Nello scenario del paragrafo precedente, il log di richiesta era il log di replica che è il motivo per cui tanto è stato semplificato.)
L'ultima preoccupazione è che probabilmente vorrai fare una qualche forma di checkpoint in modo da non dover mantenere il log completo e riprodurlo dall'inizio. Un'altra cosa bella di questo design incentrato sul log è che questo può essere fatto come un processo in background se lo si desidera.