Questa lezione conclude il modulo sugli algoritmi distribuiti (module 4.2) e introduce l'architettura Service-Oriented (module 4.3). Il professore dedica la prima parte all'Assignment 4, che mette in pratica due paradigmi fondamentali del distributed computing: il message passing con il modello degli attori, e il distributed object computing con Java RMI.
L'Assignment 4 chiede di scegliere consapevolmente tra due filosofie: attori (messaggi asincroni, isolamento, nessuna memoria condivisa) versus RMI (chiamate a metodo sincrone, trasparenza oggettuale, middleware per la distribuzione). Non esiste una scelta giusta in assoluto: dipende dal problema da risolvere.
Il primo esercizio riguarda gli attori come modello di riferimento per il message passing, utilizzando un middleware a scelta (Akka, Quasar o altri). Il secondo esercizio applica la visione del distributed object computing con Java RMI. Il professore propone come esempio un sistema distribuito per giocare a Tic-Tac-Toe (tris) dove due giocatori su nodi diversi interagiscono attraverso oggetti remoti. La consegna e volutamente con pochi requisiti: lo studente deve fare le scelte progettuali in autonomia.
Il professore sottolinea che non bisogna affidarsi ciecamente all'AI per questi esercizi: "se vi fate aiutare dalle AI, fatemi aiutare bene, perche non e detto che vi aiuti bene". I principi del corso (incapsulamento del flusso di controllo, componenti attivi/passivi, modularita) sono concetti che un assistente AI non padroneggia automaticamente. E molto meglio pensare e programmare toccando con mano.
Java RMI (Remote Method Invocation) e un middleware che implementa il paradigma del distributed object computing. Consente di invocare metodi su oggetti che risiedono in JVM diverse, potenzialmente su macchine differenti. L'architettura si basa su quattro pilastri:
java.rmi.Remote, una flag interface vuota che segnala "attenzione: le istanze di questa classe sono destinate ad essere oggetti remoti"throws RemoteException. Non c'e piena trasparenza: se la rete cade, il client deve poter gestire l'erroreIl professore spiega che la RemoteException puo manifestarsi in scenari diversi: il nodo remoto non e raggiungibile al momento della chiamata, oppure la connessione cade durante l'esecuzione del metodo, prima che il risultato torni indietro. In tutti i casi, e lo stub (proxy) a gestire la situazione e sollevare l'eccezione appropriata.
Il professore accenna anche a RabbitMQ come esempio di message-oriented middleware (MOM), visitandolo brevemente nell'ottica dell'Assignment 4. La differenza fondamentale: RMI e sincrono e orientato agli oggetti, RabbitMQ e asincrono e orientato ai messaggi. Uno e basato su distributed object computing, l'altro su message-oriented middleware.
I sistemi distribuiti presentano tre assenze fondamentali che li rendono radicalmente diversi dai sistemi concorrenti tradizionali (basati su memoria condivisa):
La maggior parte degli algoritmi distribuiti assume sistemi asincroni in assenza di guasti (ritardo di consegna finito ma non noto a priori), mentre per gestire i guasti si passa a modelli sincroni con timeout.
Non esiste un limite superiore noto al tempo di consegna dei messaggi. Il ritardo puo essere arbitrario ma finito. La maggior parte degli algoritmi distribuiti assume reti asincrone in assenza di guasti. Il nondeterminismo e massimo, la concorrenza e massima. Ideali per ambienti non critici dove la probabilita di guasto e bassa.
Esiste un limite superiore noto al tempo di consegna dei messaggi e alla durata delle azioni dei processi. Si possono usare timeout per rilevare guasti. La progettazione degli algoritmi e piu semplice. Necessari quando si devono gestire guasti, ad esempio negli algoritmi di consensus o nei protocolli bizantini.
Il professore elenca le cinque macro-categorie che gli algoritmi distribuiti devono affrontare, tutte basate sugli orologi logici e sulla relazione happened before gia introdotta nel modulo precedente:
Tutti questi algoritmi si basano sugli orologi logici e sulla relazione happened before (→) per definire un ordinamento tra eventi correlati. Il concetto di tempo logico di Lamport (1978) e il fondamento su cui poggia l'intera architettura degli algoritmi distribuiti.
Il professore richiama i tre modelli principali per descrivere il comportamento di un programma distribuito:
Il problema della mutua esclusione in ambito distribuito e analogo al caso concorrente, ma i processi sono distribuiti su nodi diversi e comunicano solo via messaggi. Le proprieta classiche di una sezione critica (CS) sono tre:
Esiste un coordinatore (processo P0) che gestisce un token. I processi client (Pi) inviano una richiesta al coordinatore, che concede il token quando possibile. Il punto chiave e la fairness: anche se una richiesta t arriva al coordinatore prima di una richiesta s che la precede causalmente, il coordinatore deve capire che s e anteriore e darle precedenza.
Per garantire la fairness, ogni processo include il proprio vector clock v appeso ai messaggi in uscita (piggybacking). Il coordinatore usa queste informazioni per ritardare una richiesta finche tutte le richieste che la precedono causalmente non sono arrivate. Il coordinatore mantiene due strutture dati:
reqList: lista delle richieste ricevute non ancora soddisfattereqDone[i]: vettore che conta quante richieste di Pi sono state gia soddisfatteAlla ricezione di una richiesta o del token, il coordinatore esegue checkReq: una richiesta w e eligible se per ogni j diverso da w.p si ha w.v[j] == reqDone[j], e per j == w.p si ha w.v[j] == reqDone[j] + 1. In altre parole, non ci sono richieste accadute prima di w che non sono ancora state soddisfatte.
sequenceDiagram
participant P1 as Pi (client)
participant C as P0 (coordinator)
participant P2 as Pj (client)
Note over P1: v[i]++, send(req, v)
P1->>C: (req, v = [1,0,0,...])
Note over C: append reqList
if haveToken: checkReq()
Note over C: w = first(eligible)
send token, incr reqDone
C->>P1: token
Note over P1: inCS = true
Note over P1: [sezione critica]
P1->>C: token (release)
Note over P1: inCS = false
Note over C: haveToken = true
checkReq() again
C->>P2: token (prossimo eligible)
Il processo client Pi, quando riceve un messaggio da un altro processo, aggiorna il proprio vettore v con il component-wise max del suo vecchio valore e del vettore ricevuto. Questo garantisce che la conoscenza causale si propaghi correttamente.
L'algoritmo di Ricart-Agrawala (1981) e una soluzione decentralizzata che non richiede un coordinatore centralizzato. Ogni processo comunica con tutti gli altri per ottenere il consenso ad entrare nella sezione critica. E considerato un algoritmo ottimale per il numero di messaggi.
Il funzionamento si basa su quattro regole semplici:
pendingQ)Numero di messaggi: 2(N-1) per ogni accesso alla sezione critica. L'algoritmo funziona anche con canali non FIFO, perche l'ordinamento causale e garantito dai timestamp logici.
L'elezione del leader e il problema di scegliere un processo tra un insieme di N processi. Viene usata per selezionare coordinatori negli algoritmi centralizzati. Una strategia ampiamente utilizzata nei sistemi distribuiti reali.
L'algoritmo sovrappone una topologia ad anello logico alla rete sottostante. Ogni processo ha un PID unico. L'idea e semplice: il processo con il PID massimo viene eletto leader.
(election, myid) al proprio vicino sinistro nell'anello(leader, myid) nell'anello
graph LR
A[P2
id=2] -->|election,2| B[P7
id=7]
B -->|election,7| C[P3
id=3]
C -->|election,7| D[P1
id=1]
D -->|election,7| E[P5
id=5]
E -->|election,7| F[P4
id=4]
F -->|election,7| A
A -->|leader,7| B
B -->|leader,7| C
C -->|leader,7| D
D -->|leader,7| E
E -->|leader,7| F
F -->|leader,7| A
Complessita nel caso peggiore: 2N-1 messaggi di election + N messaggi di leader.
L'algoritmo usa l'anello logico solo per il controllo dell'elezione, non per il flusso dei dati applicativi. E un esempio classico di superimposing: sovrapporre un protocollo di coordinamento a una topologia fisica arbitraria. Questo pattern si ritrova in molti algoritmi distribuiti.
In un sistema completamente asincrono non ci sono restrizioni sull'ordinamento dei messaggi. Questo massimizza il nondeterminismo e la concorrenza, ma a volte e utile ridurre il nondeterminismo imponendo vincoli sull'ordinamento per semplificare la programmazione.
L'ordinamento FIFO garantisce che se due messaggi sono inviati dallo stesso processo, arrivano nello stesso ordine. L'ordinamento causale e piu forte: dati due messaggi m1, m2 tali che send(m1) → send(m2), allora rec(m1) → rec(m2). Nessun sorpasso (overtaking) e permesso, anche se i messaggi vengono da processi diversi.
L'algoritmo estende i vector clocks con una matrice m di interi (N x N), dove s.m[j,k] rappresenta il numero di messaggi inviati da Pj a Pk noti a Pi nello stato s. Ogni volta che un messaggio viene inviato da Pi a Pj, la matrice viene appesa al messaggio (piggybacked). Alla ricezione, il sistema di comunicazione verifica se il messaggio e eligible prima di consegnarlo al processo.
Un messaggio u e eleggibile per essere ricevuto da Pi quando, per ogni processo k, il numero di messaggi inviati da Pk a Pi riportato in u.m e minore o uguale al numero registrato nella matrice locale di Pi. Se non e eleggibile, il messaggio viene messo in una coda di buffer fino a quando non lo diventa (perche altri messaggi sono arrivati e hanno aggiornato la matrice).
sequenceDiagram
participant A as P1
participant B as P2
participant C as P3
Note over A: m[1,2]++, send to P2
A->>B: msg1 (con matrice m)
Note over B: check eligibility:
m'[1,2] == m[1,2]+1?
se si: update, receive
Note over A: m[1,3]++, send to P3
A->>C: msg2 (con matrice m)
Note over B: P2 invia a P3
B->>C: msg3 (con matrice m')
Note over C: msg3 arriva prima di msg2?
Se send(msg2) -> send(msg3)
allora rec(msg2) -> rec(msg3)
Se msg3 arriva prima, buffer
C->>C: buffer msg3
aspetta msg2
A->>C: msg2 arriva (dopo)
Note over C: msg2 eligible? si
aggiorna matrice
controlla msg3 bufferizzato
ora msg3 eligible
C->>C: deliver msg3
Catturare lo stato globale di un sistema distribuito da un singolo processo e una sfida fondamentale. Lo stato globale e definito come un insieme di stati locali tutti concorrenti tra loro secondo il modello happened before. Per molte applicazioni, e sufficiente catturare uno stato globale che e esistito nel passato, non necessariamente lo stato corrente.
Uno stato globale non e semplicemente il prodotto degli stati locali. Un taglio inconsistente (inconsistent cut) si verifica quando viene registrata la ricezione di un messaggio ma non la sua spedizione. Formalmente, un global state S e consistente se per ogni evento e = receive(msg), l'evento e' = send(msg) (dove e' → e) appartiene a S.
L'algoritmo calcola un consistent cut (taglio consistente) dello spazio-tempo distribuito, assumendo canali unidirezionali FIFO. Il meccanismo e elegante e non richiede di bloccare i processi durante la cattura:
Grazie ai canali FIFO, nessun processo bianco riceve mai un messaggio inviato da un processo rosso. Questo garantisce che gli stati locali siano mutuamente concorrenti. Lo stato dei canali e dato dai messaggi inviati da un processo bianco e ricevuti da uno rosso — messaggi che erano in transito quando lo snapshot e stato preso.
Il consensus e il problema fondamentale del distributed computing: trovare un accordo tra processi distribuiti sul valore di una proprieta o su un'azione da intraprendere. E onnipresente nei sistemi reali: transazioni nei database (commit/abort), leader election, state machine replication, atomic broadcast, sincronizzazione di clock, blockchain.
Per raggiungere il consenso, ogni processo Pi inizia nello stato undecided e propone un singolo valore vi (da un insieme D). I processi comunicano scambiandosi valori. Ogni processo infine imposta una variabile di decisione di con il valore deciso, entrando nello stato decided dal quale non puo piu cambiare di.
FLP (Fischer, Lynch, Patterson, 1985): in una rete asincrona, anche con un singolo processo che puo morire (crash non annunciato), il problema del consensus e impossibile da risolvere. Questo teorema di impossibilita ha profonde implicazioni pratiche: tutti i protocolli di consensus reali devono rilassare qualche assunzione, tipicamente introducendo sincronia o randomizzazione.
Per aggirare FLP, si assume un sistema sincrono (bound superiore sul ritardo dei messaggi e sulla durata delle azioni) e si definiscono modelli di guasto specifici. I modelli principali sono:
| Modello | Descrizione |
|---|---|
| Crash | Un processore si arresta (halt). Il guasto piu semplice da modellare |
| Crash+link | Un processore crasha oppure un link di rete cade e resta giu permanentemente |
| Omission | Un processo invia solo un sottoinsieme dei messaggi che dovrebbe inviare, o ne riceve solo un sottoinsieme |
| Byzantine | Un processore ha comportamento arbitrario: puo inviare messaggi contraddittori, valori falsi, o agire in modo malevolo. Il caso piu generale e difficile |
Ogni processo mantiene V, l'insieme dei valori che conosce essere stati proposti dai processori del sistema. Inizialmente, un processo conosce solo il proprio valore. L'algoritmo procede per f+1 round, dove f e il numero massimo di processori che possono fallire:
decide(V) identica per tutti i processiComplessita: O((f+1)N^2) messaggi. Funziona solo in assenza di comportamenti bizantini.
graph TD
subgraph "Round 1"
P1a[P1: V={v1}] -->|send v1| P2a[P2]
P1a -->|send v1| P3a[P3]
P2a[P2: V={v2}] -->|send v2| P1a
P2a -->|send v2| P3a
P3a[P3: V={v3}] -->|send v3| P1a
P3a -->|send v3| P2a
end
subgraph "Round 2 (f=1, rounds=2)"
P1b[P1: V={v1,v2,v3}] -->|send nuovi| P2b[P2]
P1b -->|send nuovi| P3b[P3]
P2b[P2: V={v1,v2,v3}] -->|send nuovi| P1b
P2b -->|send nuovi| P3b
P3b[P3: V={v1,v2,v3}] -->|send nuovi| P1b
P3b -->|send nuovi| P2b
end
subgraph "Decisione"
P1c[P1: decide(V)]
P2c[P2: decide(V)]
P3c[P3: decide(V)]
end
Il caso in cui i processi guasti possono avere comportamento arbitrario: inviare valori errati, messaggi contraddittori a destinatari diversi, o non inviare nulla. Il problema dei generali bizantini (Lamport, Shostak, Pease, 1982) e un classico: i generali leali (processi corretti) devono coordinarsi (decidere all'unisono se attaccare o ritirarsi) nonostante la presenza di traditori.
Teorema BGA: non esiste un protocollo f-resilient per il Byzantine General Agreement se N ≤ 3f (N = numero totale di processi, f = guasti). Serve almeno 3f+1 processi per tollerare f guasti bizantini. Ad esempio, con 1 traditore servono almeno 4 generali; con 2 traditori almeno 7.
L'algoritmo procede in f+1 round con un coordinatore rotante (chiamato king). In ogni round:
myvalue = maggioranza in V (o "non definito" se non c'e maggioranza)myvalue a tutti. Ogni processore riceve il valore del king. Se il vettore V ha piu di N/2+f copie del proprio myvalue, mantiene myvalue; altrimenti adotta il value del kingAlmeno un round avra un king non guasto, garantendo che tutti i processori corretti convergano sullo stesso valore.
Paxos e una famiglia di protocolli per risolvere il consensus in una rete di processori non affidabili, considerando solo comportamento crash-stop (non bizantino). Il nome deriva dall'articolo "The Part-Time Parliament" (Lamport, 1998), che presenta il consenso come un parlamento che legifera anche quando alcuni parlamentari sono assenti.
Paxos bilancia diversi aspetti: numero di processori, ritardi di messaggio prima di apprendere il valore concordato, livello di attivita dei partecipanti, numero di messaggi scambiati, e tipi di guasto tollerati. E usato in tutti i contesti dove serve durabilita, come la replicazione di file o database.
Raft (Reliable, Replicated, Redundant, And Fault-Tolerant) e un algoritmo di consensus progettato per essere facile da capire. Equivalente a Paxos in fault-tolerance e performance, ma significativamente piu semplice da implementare. Offre un modo generico per distribuire una state machine su un cluster di sistemi, garantendo che ogni nodo concordi sulla stessa serie di transizioni di stato.
Raft e implementato open-source in Go, C++, Java e Scala (riferimento: raft.github.io). E utilizzato in progetti come etcd e Consul.
Proposto da Leslie Lamport nel 1998. Famiglia di protocolli, non un singolo algoritmo monolitico. Considerato storicamente il riferimento per il consensus in produzione, ma notoriamente difficile da comprendere e implementare correttamente. Il nome e la metafora del parlamento riflettono la complessita della presentazione originale.
Proposto da Diego Ongaro e John Ousterhout nel 2013. Progettato esplicitamente per l'insegnamento e la comprensione. Usa un leader forte, elezione del leader con timeout randomizzati, e una struttura a log piu chiara. E lo standard de facto per nuovi progetti che necessitano di consensus.
Il consensus emerge tipicamente nel contesto delle replicated state machines (RSM), un approccio generale per costruire sistemi fault-tolerant. Ogni server ha:
L'algoritmo di consensus coordina il log replicato: garantisce che se una state machine applica "set x to 3" come n-esimo comando, nessun'altra state machine nello stesso cluster applichera mai un comando diverso come n-esimo. Tutte eseguono la stessa identica sequenza di comandi, producendo gli stessi risultati e arrivando agli stessi stati.
graph LR
Client -->|"set x=3, get y, ..."| LB[Load Balancer]
LB --> SM1[Server 1
State Machine
Log: [set x=3,...]]
LB --> SM2[Server 2
State Machine
Log: [set x=3,...]]
LB --> SM3[Server 3
State Machine
Log: [set x=3,...]]
Consensus[Consensus Module
Raft / Paxos] -.-> SM1
Consensus -.-> SM2
Consensus -.-> SM3
SM1 -.->|replica log| Consensus
SM2 -.->|replica log| Consensus
SM3 -.->|replica log| Consensus
Il sistema appare ai client come un'unica state machine affidabile, anche se una minoranza dei server nel cluster fallisce. Questo pattern e alla base di sistemi come Google File System (GFS), HDFS, RAMCloud, e innumerevoli database distribuiti.
Con il modulo 4.3, il professore introduce una prospettiva architetturale e ingegneristica del software distribuito. Mentre gli algoritmi distribuiti risolvono problemi specifici (consensus, mutua esclusione, ordinamento), il Service-Oriented Computing e un paradigma architetturale che abbraccia l'intero ciclo di vita del software distribuito.
Un servizio e un componente software autonomo, indipendente, con un confine ben definito e un contratto esplicito (tipicamente un'interfaccia). I microservizi estendono questo concetto portandolo all'estremo: servizi piccoli, focalizzati su un singolo dominio di business, deployabili indipendentemente, che comunicano via rete (tipicamente HTTP/REST o messaggi asincroni).
Il passaggio dal distributed object computing (Java RMI, CORBA) al service-oriented computing non e solo tecnico: e un cambio di paradigma. Invece di nascondere la distribuzione dietro oggetti remoti (trasparenza), i servizi abbracciano esplicitamente la loro natura distribuita, con contratti, interfacce e API ben definite.
Il Domain-Driven Design e strettamente correlato ai microservizi. Il concetto chiave e il bounded context: ogni microservizio dovrebbe mappare esattamente un bounded context del dominio applicativo. Questo garantisce che:
Oltre ai microservizi tradizionali (sincroni, REST), il professore menziona due architetture emergenti:
Il teorema CAP (Brewer, 2000, formalizzato da Gilbert e Lynch, 2002) e uno dei risultati fondamentali della progettazione di sistemi distribuiti: qualsiasi sistema che condivide dati in rete puo avere al massimo due delle tre proprieta seguenti:
Il professore approfondisce un aspetto spesso frainteso: la latenza. Nell'interpretazione classica, il teorema CAP ignora la latenza, ma in pratica latenza e partizioni sono profondamente correlate. Durante un timeout, il programma deve fare una scelta fondamentale: cancellare l'operazione (riducendo la disponibilita) o procedere (rischiando l'inconsistenza).
Il professore spiega che, pragmaticamente, una partizione e un limite di tempo sulla comunicazione. Se non si riesce a raggiungere la consistenza entro quel limite, ci si trova di fronte al partition decision: il bivio tra consistenza e disponibilita. Due nodi che procedono senza comunicare — questo e il dilemma fondamentale.
Questi concetti catturano la questione progettuale centrale riguardo alla latenza: due parti del sistema stanno andando avanti senza comunicazione? La risposta determina se si sta violando la consistenza o la disponibilita.
Il professore ricorda che dal modello (happened before, potenzial causality) si passa ai meccanismi: orologi logici e vector clocks per implementare il modello happened before. Questi meccanismi sono gli strumenti concreti che i programmatori distribuiti utilizzano per ordinare eventi, rilevare consistenza, e costruire algoritmi corretti.
Il professore chiude la lezione tornando sull'Assignment 4 e fornendo indicazioni pratiche per l'implementazione.
Il pattern proxy e centrale in entrambi i paradigmi: negli attori (ogni attore ha un indirizzo e comunica solo tramite messaggi asincroni, con un proxy che gestisce l'invio) e in RMI (lo stub e un proxy che serializza le chiamate a metodo in messaggi di rete). Il professore sottolinea che il proxy e responsabile di:
Il professore ricorda la progressione storica: si parte dalle Remote Procedure Call (RPC), dove chiamiamo funzioni su nodi remoti come se fossero locali. Con Java RMI, si passa alle Remote Method Invocation, che aggiungono la dimensione object-oriented: non chiamiamo piu funzioni, ma invochiamo metodi su oggetti che risiedono in JVM remote. La visione e quella del distributed object computing classico: oggetti che interagiscono chiamandosi metodi a vicenda attraverso la rete.
Il professore raccomanda di pensare in modo critico alla progettazione: "nel pensare a come progettare lo sviluppo ci sono dei principi che abbiamo visto nel corso importanti e che vengono messi da una descrizione come questa. Se noi diciamo componenti attivi, passivi, incapsulamento del flusso di controllo, sono tutte questioni che non e detto che l'AI subito attacchi, anzi." Il consiglio e di programmare toccando con mano e fare scelte consapevoli.
Con questa lezione si conclude la parte algoritmica del corso. Il percorso e iniziato con la programmazione concorrente (thread, lock, monitor, attori), ha attraversato la programmazione asincrona e reattiva (CompletableFuture, RxJava), ha esplorato i modelli di message passing (attori, CSP), e culmina con gli algoritmi distribuiti (mutua esclusione, elezione, snapshot, consensus). I prossimi moduli si concentreranno sugli aspetti architetturali e operativi del software distribuito, completando il percorso formativo.
Assenza di clock condiviso (non si possono sincronizzare orologi fisici), assenza di memoria condivisa (nessun processo conosce lo stato globale), assenza di rilevamento accurato dei guasti (in un sistema asincrono non si distingue tra processo lento e processo caduto).
w e eleggibile se w.v e "al massimo" uguale a reqDone: per ogni j diverso da w.p si ha w.v[j] == reqDone[j], e per j == w.p si ha w.v[j] == reqDone[j] + 1. Questo significa che non esistono richieste accadute prima di w che non sono ancora state soddisfatte.
2(N-1) messaggi, dove N e il numero di processi: (N-1) richieste inviate a tutti gli altri processi + (N-1) risposte OK ricevute. E considerato ottimale.
In una rete asincrona, anche con un singolo processo che puo fallire (crash non annunciato), il problema del consensus e impossibile da risolvere. Questo e il celebre risultato di Fischer, Lynch e Patterson (1985), che ha profonde implicazioni pratiche per la progettazione di sistemi distribuiti.
N > 3f, dove N e il numero totale di processi e f e il numero di processi guasti con comportamento arbitrario. Se N ≤ 3f, non esiste alcun protocollo f-resilient. Ad esempio, per tollerare 1 guasto bizantino servono almeno 4 processi.
L'ordinamento causale e piu forte del FIFO: dati due messaggi m1, m2 tali che send(m1) → send(m2), allora rec(m1) → rec(m2). Il FIFO garantisce solo l'ordine all'interno dello stesso processo mittente, mentre l'ordinamento causale vieta i sorpassi (overtaking) anche tra processi diversi.
Un sistema distribuito con dati condivisi puo garantire al massimo due delle tre proprieta: Consistency (tutti vedono gli stessi dati), Availability (ogni richiesta riceve risposta), Partition tolerance (funziona nonostante partizioni di rete). La scelta dipende dai requisiti applicativi.
RMI e basato su distributed object computing: chiamate a metodo sincrone, proxy/stub, trasparenza oggettuale, invocazione di metodi su oggetti remoti. Un MOM (es. RabbitMQ) e basato su messaggi asincroni, code, pubblicazione/sottoscrizione, disaccoppiamento temporale e spaziale. RMI e orientato agli oggetti; i MOM sono orientati ai messaggi.
Ogni processo ha un colore (bianco/rosso). Tutti iniziano bianchi. Quando un processo decide di fare lo snapshot (o riceve un marker), salva lo stato locale, diventa rosso, e invia marker su tutti i canali in uscita. I messaggi inviati da processi bianchi e ricevuti da processi rossi costituiscono lo stato dei canali. Canali FIFO garantiscono che nessun processo bianco riceva messaggi da processi rossi, assicurando che gli stati locali siano mutuamente concorrenti (consistent cut).