Gairebé tot el que fem servir cada dia a Internet —cercar una web, pagar amb targeta, mirar una sèrie— funciona gràcies a desenes o milers d'ordinadors que cooperen sense que ens n'adonem. Aquesta lliçó estableix el vocabulari i les idees fonamentals sobre les quals es construeix la resta del curs: què és exactament un sistema distribuït, per què se'n construeixen, quines característiques el distingeixen d'un programa "normal" i de quines peces està fet. A més, presentarem el cas pràctic que ens acompanyarà durant tot el curs, Quilòmetre Zero, un marketplace de productors locals que comença com una aplicació senzilla en un únic servidor i que, per culpa del seu propi èxit, haurà de convertir-se en una plataforma distribuïda.
Entendre bé aquests fonaments és important perquè els sistemes distribuïts no són simplement "diversos ordinadors": introdueixen una manera de pensar diferent, en què la fallada parcial, la concurrència i l'absència d'una veritat única són la norma i no l'excepció.
Contingut
- Què és un sistema distribuït?
- Per què existeixen els sistemes distribuïts?
- Característiques clau
- Transparència: amagar la distribució
- Components d'un sistema distribuït
- Exemples quotidians
- Presentació de Quilòmetre Zero: el monòlit inicial
- Exemple de codi: simulant nodes que fallen de manera independent
- Errors comuns i consells
- Exercicis
- Conclusió
- Què és un sistema distribuït?
Hi ha moltes definicions, però dues són especialment útils perquè capturen aspectes complementaris:
- Definició formal (Andrew Tanenbaum): "Un sistema distribuït és una col·lecció d'ordinadors independents que apareix davant dels seus usuaris com un únic sistema coherent." Aquesta definició subratlla l'objectiu: cooperació amb aparença d'unitat. L'usuari d'un cercador no sap (ni li importa) quantes màquines han participat en la seva consulta.
- Definició irònica (Leslie Lamport): "Un sistema distribuït és aquell en què la fallada d'una màquina que ni tan sols sabies que existia pot deixar el teu propi ordinador inutilitzable." Aquesta definició subratlla el problema: la dependència oculta i la fallada parcial. En un sistema distribuït, el teu programa pot deixar de funcionar per causes completament alienes a ell.
Totes dues definicions són certes alhora, i la tensió entre elles és l'essència d'aquesta disciplina: volem que moltes màquines semblin una de sola, però cada màquina pot fallar per separat.
Convé fixar una distinció senzilla:
| Sistema | On s'executa | Com es comunica | Mode de fallada habitual |
|---|---|---|---|
| Centralitzat (un procés) | Una màquina | Crides a funcions en memòria | Tot o res: el procés funciona o cau |
| Concurrent en una màquina | Una màquina, diversos fils o processos | Memòria compartida, canonades | Tot o res (amb condicions de carrera) |
| Distribuït | Diverses màquines | Missatges per xarxa | Parcial: unes parts fallen i d'altres no |
La diferència essencial no és la quantitat d'ordinadors, sinó que l'única manera de comunicar-se és enviant missatges per una xarxa que pot retardar-los o perdre'ls, i que cada node pot fallar mentre els altres continuen funcionant.
- Per què existeixen els sistemes distribuïts?
Ningú no construeix un sistema distribuït per gust: són més difícils de dissenyar, provar i operar que un programa en una sola màquina. Es construeixen perquè hi ha motius que ho justifiquen:
- Escala. Una sola màquina té un límit de CPU, memòria, disc i connexions de xarxa. Quan la demanda supera aquest límit, l'única sortida és repartir la feina entre diverses màquines.
- Geografia. Els usuaris estan repartits pel món, i la velocitat de la llum imposa una latència mínima. Servir cada usuari des d'un centre de dades proper exigeix tenir màquines en diversos llocs. A més, algunes fonts de dades estan inherentment distribuïdes (sensors, dispositius mòbils, sucursals).
- Disponibilitat i tolerància a fallades. Si el servei corre en una sola màquina, aquesta màquina és un punt únic de fallada. Amb diverses màquines, el sistema pot continuar funcionant encara que alguna caigui.
- Cost. A partir d'una certa mida, moltes màquines modestes són molt més barates que una única màquina enorme (i una màquina enorme, al final, també té un límit). A més, permet pagar només per la capacitat que es fa servir.
- Organització. Les empreses grans tenen molts equips; que cada equip sigui propietari de la seva part del sistema, amb el seu propi cicle de desplegament, requereix que aquestes parts estiguin separades.
A la lliçó 01-03 analitzarem en detall els avantatges i els costos de distribuir. De moment n'hi ha prou amb tenir clar que la distribució sempre és una resposta a un problema concret, mai una finalitat en si mateixa.
- Característiques clau
Tot sistema distribuït, independentment de la seva mida, comparteix tres característiques fonamentals. Són conseqüències directes de "diverses màquines que es comuniquen per missatges".
3.1 Concurrència
Els nodes s'executen simultàniament i de manera independent. A Quilòmetre Zero, mentre l'Anna afegeix un formatge al cistell des de Barcelona, en Marc paga una comanda des de València i un repartidor envia la seva posició des de Saragossa. No hi ha cap "torn": tot passa alhora, i els recursos compartits (per exemple, l'estoc d'un producte) s'han de coordinar explícitament.
3.2 Absència de rellotge global
Cada màquina té el seu propi rellotge, i aquests rellotges mai no estan perfectament sincronitzats. Si el node A registra un esdeveniment a les 10:00:00.120 i el node B un altre a les 10:00:00.118, no podem afirmar amb seguretat quin va passar abans: la diferència pot ser més petita que l'error de sincronització. Això té conseqüències profundes per a qualsevol cosa que depengui de "qui va ser primer". Ho estudiarem a fons a la lliçó 01-05.
3.3 Fallades independents
En un programa monolític, si alguna cosa falla, falla tot, i normalment ho detectes de seguida (una excepció, un procés mort). En un sistema distribuït:
- Un node pot caure mentre la resta continua en marxa (fallada parcial).
- La xarxa pot perdre missatges, duplicar-los o retardar-los.
- Un node pot anar tan lent que sigui indistingible d'un de caigut.
- I el pitjor: no sempre hi ha manera de saber què ha passat. Si envies una petició i no reps resposta, la petició no va arribar? Va arribar, es va processar i es va perdre la resposta? El node està caigut o simplement sobrecarregat?
Aquesta incertesa és l'arrel de la majoria de dificultats del curs. Dissenyar sistemes distribuïts consisteix, en bona part, a assumir que la fallada parcial és inevitable i decidir com comportar-se quan passa.
- Transparència: amagar la distribució
Recordem la definició de Tanenbaum: el sistema ha d'aparèixer com un de sol. Cadascun dels aspectes de la distribució que s'amaguen a l'usuari (o al programador) rep el nom de transparència. Les més importants:
| Transparència | Què amaga | Exemple a Quilòmetre Zero |
|---|---|---|
| D'accés | Les diferències en la representació de dades i en com s'accedeix a un recurs local o remot | El codi que consulta el catàleg s'escriu igual tant si el catàleg és al mateix procés com si és en una altra màquina |
| D'ubicació | On és físicament un recurs | L'app crida cataleg.cercar("formatge") sense saber en quin servidor ni en quina ciutat s'executa |
| De replicació | Que existeixen diverses còpies del mateix recurs | El client veu "el catàleg", encara que hi hagi cinc còpies servint-lo |
| De fallada | Que un component ha fallat i s'ha recuperat (o s'ha substituït) | Si una còpia del catàleg cau, les peticions van a una altra sense que l'Anna se n'adoni |
| D'escalat | Que el sistema creix o decreix en mida | Afegir servidors durant una campanya no canvia la manera de fer servir la plataforma |
| De concurrència | Que diversos usuaris comparteixen el mateix recurs | L'Anna i en Marc consulten el mateix estoc sense interferir-se |
| De migració | Que un recurs es mou de lloc mentre es fa servir | Una sessió passa d'un servidor a un altre sense tallar la connexió |
És important entendre que la transparència total és un ideal, no una realitat. Amagar completament la distribució té costos (latència, complexitat) i, en molts casos, és contraproduent: si el programador no sap que una crida viatja per la xarxa, escriurà codi que assumeix que mai no falla i que sempre respon ràpid. Veurem exactament aquest problema a la lliçó 01-04, dedicada a les fal·làcies de la computació distribuïda.
- Components d'un sistema distribuït
Podem descriure qualsevol sistema distribuït en funció de quatre tipus de peces:
flowchart LR
subgraph Node_A["Node A"]
A1[Aplicació]
A2[Middleware]
A3[Sistema operatiu]
end
subgraph Node_B["Node B"]
B1[Aplicació]
B2[Middleware]
B3[Sistema operatiu]
end
A2 <-- "Protocols (missatges)" --> B2
A3 <-- "Xarxa" --> B3
- Nodes. Cadascuna de les màquines (físiques o virtuals, contenidors inclosos) que executen una part del sistema. Un node té la seva pròpia CPU, memòria, disc i rellotge. Entre els nodes no hi ha memòria compartida: només missatges.
- Xarxa. El mitjà pel qual viatgen els missatges: cables, commutadors, encaminadors, enllaços sense fil, Internet. La xarxa és un component més del sistema, amb les seves pròpies fallades i limitacions, encara que sovint s'oblida.
- Protocols. Les regles que defineixen com s'estructuren i s'intercanvien els missatges: format, ordre, què fer davant d'un error. Des dels protocols de transport (TCP, UDP) fins als d'aplicació (HTTP, gRPC, MQTT), s'estudien al Mòdul 2.
- Middleware. La capa de programari que se situa entre les aplicacions i el sistema operatiu/xarxa i que proporciona les transparències: localització de serveis, crides remotes, cues de missatges, replicació... És el que permet que el programador d'aplicacions no hagi de treballar directament amb sockets i bytes. Bona part del curs (RPC, cues, bases de dades distribuïdes, orquestradors) tracta precisament de middleware.
- Exemples quotidians
Per convèncer-se que els sistemes distribuïts són a tot arreu, n'hi ha prou amb mirar quatre coses que fem cada dia:
| Sistema | Què distribueix | Transparències que ofereix | Què passaria en una fallada parcial |
|---|---|---|---|
| DNS | Una base de dades jeràrquica de noms, replicada en milers de servidors d'arreu del món | Ubicació, replicació, fallada | El teu resolutor pregunta a un altre servidor; gairebé mai no ho notes |
| La Web | Contingut servit per milions de servidors, amb memòries cau (CDN) a prop de cada usuari | Ubicació, replicació, escalat | Una web concreta no carrega, però la resta d'Internet continua funcionant |
| Banca electrònica | Comptes, transaccions i caixers repartits en molts sistemes que han de mantenir la coherència | Accés, concurrència, replicació | Pots consultar el saldo però no fer transferències (degradació parcial) |
| Streaming de vídeo | Còpies de cada pel·lícula en servidors repartits geogràficament, més els sistemes de recomanació i facturació | Ubicació, replicació, escalat | Cau la recomanació, però pots continuar mirant la teva sèrie |
Fixa't en l'última columna: en tots els casos, el sistema està dissenyat per degradar-se parcialment en lloc de caure del tot. Aquesta és una de les promeses dels sistemes distribuïts... i també una de les coses més difícils d'aconseguir.
- Presentació de Quilòmetre Zero: el monòlit inicial
Al llarg del curs treballarem amb Quilòmetre Zero, un marketplace en línia fictici que connecta productors locals amb consumidors de diverses ciutats. Entre els seus productors hi ha Horta La Vega (verdures de temporada), Formatgeria Montblanc (formatges artesans) i Celler Roure Alt (vins). Entre els seus clients, l'Anna, en Marc i la Llúcia.
7.1 L'arquitectura de partida
Quilòmetre Zero va néixer com qualsevol projecte assenyat: un monòlit. Una única aplicació web en Python, amb tots els mòduls al mateix procés, parlant amb una única base de dades PostgreSQL, tot en un únic servidor.
flowchart TB
U1[Anna - navegador] --> S
U2[Marc - app mobil] --> S
U3[Llucia - navegador] --> S
subgraph S["Servidor únic (8 vCPU, 32 GB RAM)"]
direction TB
subgraph APP["Aplicació Python (un procés)"]
C[cataleg]
P[comandes]
I[inventari]
PA[pagaments]
R[repartiment]
AN[analitica]
end
DB[(PostgreSQL)]
APP --> DB
end
Aquesta arquitectura té virtuts enormes, i convé dir-ho clarament per no caure en el prejudici que "monòlit" és sinònim de "mal disseny":
- Simplicitat. Una crida del mòdul
comandesal mòdulinventariés una simple crida a una funció Python: no hi ha xarxa, ni serialització, ni timeouts. - Transaccions senzilles. Descomptar estoc, crear la comanda i registrar el pagament passen en una única transacció de PostgreSQL: o es fa tot o no es fa res.
- Desplegament trivial. Un únic artefacte que s'instal·la en un únic servidor.
- Depuració directa. Un únic registre de logs, una única traça de pila.
En una màquina amb 8 vCPU i 32 GB de RAM, Quilòmetre Zero atén sense problemes les seves dues primeres ciutats: unes 300 comandes al dia i pics de 40 peticions per segon.
7.2 El primer símptoma: la "Setmana de la Verema"
Al setembre, Quilòmetre Zero llança la seva primera gran campanya de temporada: la Setmana de la Verema, amb ofertes del Celler Roure Alt i d'altres cellers, promocionada a les xarxes socials en cinc ciutats. El dilluns a les 10:00 s'obre la campanya i passa el següent:
| Hora | Peticions/s | Símptoma |
|---|---|---|
| 09:55 | 45 | Normal |
| 10:02 | 380 | Les pàgines del catàleg triguen 3-4 s a carregar |
| 10:06 | 610 | PostgreSQL esgota el seu límit de connexions; els pagaments comencen a fallar |
| 10:09 | 700+ | El procés Python es queda sense memòria i el servidor deixa de respondre |
| 10:10 | 0 | Caiguda total. Ni catàleg, ni comandes, ni el seguiment dels repartidors |
Observa un detall revelador: el pic de trànsit el provoca la gent mirant el catàleg de vins, però el que cau és tot, inclòs el seguiment dels repartidors, que no té res a veure amb la campanya. En un monòlit, tots els mòduls comparteixen el mateix destí: la mateixa CPU, la mateixa memòria, el mateix pool de connexions a la base de dades.
Aquest incident serà el punt de partida de l'evolució de Quilòmetre Zero que anirem seguint durant el curs. Convé resistir la temptació de saltar ja a la solució: primer (lliçons 01-02 a 01-05) necessitem entendre què vol dir distribuir i quins nous problemes apareixen; a la lliçó 01-06 dibuixarem l'arquitectura objectiu.
- Exemple de codi: simulant nodes que fallen de manera independent
Construirem una petita simulació en Python per sentir la diferència entre una fallada total i una fallada parcial. No necessitem xarxes reals: representarem cada node amb una classe, i la "xarxa" amb crides a mètodes. És una simplificació enorme, però suficient per il·lustrar la idea.
L'escenari: Quilòmetre Zero té tres nodes que atenen comandes (comandes-1, comandes-2 i comandes-3) darrere d'un repartidor de càrrega molt senzill. Un dels nodes fallarà a mitja campanya.
import random
from dataclasses import dataclass, field
@dataclass
class Comanda:
"""Una comanda d'un client de Quilòmetre Zero."""
id: int
client: str
producte: str
productor: str
class NodeCaigutError(Exception):
"""Es llança quan intentem parlar amb un node que ha fallat."""
@dataclass
class NodeComandes:
"""Simula una màquina que executa el servei 'comandes'.
Cada node té el seu propi estat (les comandes que ha atès) i la seva
pròpia 'salut'. No comparteix memòria amb els altres nodes: si volem
saber-ne alguna cosa, li hem d'enviar 'un missatge' (cridar un mètode).
"""
nom: str
actiu: bool = True
atesos: list = field(default_factory=list)
def processar(self, comanda: Comanda) -> str:
if not self.actiu:
# A la realitat no hi hauria excepció: simplement no arribaria
# cap resposta. Aquí ho modelem com una excepció per simplificar.
raise NodeCaigutError(f"{self.nom} no respon")
self.atesos.append(comanda.id)
return f"{self.nom} va confirmar la comanda {comanda.id} de {comanda.client}"
def fallar(self) -> None:
"""Simula que la màquina s'apaga de cop."""
self.actiu = False
class RepartidorDeCarrega:
"""Envia cada comanda a un node diferent, per torns (round-robin)."""
def __init__(self, nodes: list[NodeComandes]):
self.nodes = nodes
self._seguent = 0
def enviar(self, comanda: Comanda) -> str:
node = self.nodes[self._seguent % len(self.nodes)]
self._seguent += 1
return node.processar(comanda)
def generar_comandes(quantitat: int) -> list[Comanda]:
clients = ["Anna", "Marc", "Llúcia"]
productes = [
("Tomàquets de temporada", "Horta La Vega"),
("Formatge curat", "Formatgeria Montblanc"),
("Vi negre criança", "Celler Roure Alt"),
]
comandes = []
for i in range(1, quantitat + 1):
producte, productor = random.choice(productes)
comandes.append(Comanda(i, random.choice(clients), producte, productor))
return comandes
if __name__ == "__main__":
random.seed(7) # perquè l'execució sigui reproduïble
nodes = [NodeComandes("comandes-1"), NodeComandes("comandes-2"), NodeComandes("comandes-3")]
balancejador = RepartidorDeCarrega(nodes)
correctes, fallits = 0, 0
for comanda in generar_comandes(12):
if comanda.id == 5:
print(">>> El node comandes-2 ha caigut! <<<")
nodes[1].fallar()
try:
print(balancejador.enviar(comanda))
correctes += 1
except NodeCaigutError as e:
print(f"ERROR en processar la comanda {comanda.id}: {e}")
fallits += 1
print(f"\nResultat: {correctes} comandes correctes, {fallits} fallides")
for node in nodes:
estat = "actiu" if node.actiu else "CAIGUT"
print(f" {node.nom} ({estat}): va atendre {node.atesos}")Analitzem el programa pas a pas:
Comandaés un simple contenidor de dades: identificador, client, producte i productor. Fem servir@dataclassper no haver d'escriure el constructor a mà.NodeComandesrepresenta una màquina. L'essencial és que té estat propi (atesos) i salut pròpia (actiu). QuanactiuésFalse, qualsevol intent de fer-lo servir llançaNodeCaigutError. Això modela la fallada independent: un node pot caure sense que els altres se n'assabentin.RepartidorDeCarregareparteix les comandes entre els nodes per torns. És la peça que, en un sistema real, seria un balancejador de càrrega; aquí és un simple comptador.- Al bucle principal, quan arribem a la comanda 5 apaguem
comandes-2. A partir d'aquí, una de cada tres comandes falla, però els altres dos terços es continuen processant amb normalitat.
La sortida (abreujada) és una cosa així:
comandes-1 va confirmar la comanda 1 de Anna comandes-2 va confirmar la comanda 2 de Llúcia comandes-3 va confirmar la comanda 3 de Anna comandes-1 va confirmar la comanda 4 de Anna >>> El node comandes-2 ha caigut! <<< ERROR en processar la comanda 5: comandes-2 no respon comandes-3 va confirmar la comanda 6 de Llúcia comandes-1 va confirmar la comanda 7 de Anna ERROR en processar la comanda 8: comandes-2 no respon ... Resultat: 9 comandes correctes, 3 fallides comandes-1 (actiu): va atendre [1, 4, 7, 10] comandes-2 (CAIGUT): va atendre [2] comandes-3 (actiu): va atendre [3, 6, 9, 12]
Comparem-ho amb el monòlit de la Setmana de la Verema: allà, una única fallada deixava 0 comandes correctes. Aquí, el sistema continua atenent el 67 % de les comandes. Això és una fallada parcial: pitjor que "tot funciona", molt millor que "res no funciona".
Però fixa't també en el que la simulació no resol, i que anticipa temes de lliçons futures:
- El balancejador continua enviant comandes a
comandes-2encara que estigui caigut. Caldria detectar la fallada i deixar de fer-lo servir (lliçó 07-03). - Les comandes 5, 8 i 11 s'han perdut. Les hauríem de reintentar en un altre node? I si
comandes-2sí que va arribar a processar la comanda abans de caure? (lliçons 02-05 i 07-04). - L'estat de
comandes-2(la comanda 2) s'ha quedat atrapat en un node caigut. Com el recuperem? (Mòdul 3, replicació). - A la simulació sabem amb certesa que el node ha caigut perquè llança una excepció. A la realitat, l'únic que veuríem és que no respon, i hauríem de decidir quant esperar (lliçó 01-04).
Errors Comuns i Consells
- Confondre "distribuït" amb "molts servidors". El que defineix un sistema distribuït és que les parts només es comuniquen per missatges i poden fallar per separat. Dos processos a la mateixa màquina que parlen per sockets ja presenten molts dels problemes d'un sistema distribuït (encara que no tots).
- Pensar que la transparència total és desitjable. Amagar completament que una crida és remota porta a escriure codi que ignora la latència i les fallades. Els bons dissenys fan la distribució còmoda, no invisible.
- Distribuir massa aviat. Quilòmetre Zero va fer bé de començar com a monòlit: li va permetre llançar ràpid i amb pocs costos. La distribució arriba quan hi ha un problema concret (escala, disponibilitat, organització) que la justifica. Distribuir "per si de cas" afegeix complexitat sense benefici.
- Oblidar que la xarxa és un component. En dibuixar arquitectures és habitual posar caixes (els nodes) i fletxes (la comunicació) i tractar les fletxes com si fossin gratuïtes i infal·libles. Les fletxes fallen, triguen i costen diners.
- Assumir que un node que no respon està caigut. Pot estar sobrecarregat, pot estar aïllat per un problema de xarxa, o pot haver processat la petició i haver-se perdut només la resposta. Aquesta ambigüitat reapareixerà una vegada i una altra al llarg del curs.
- Consell: quan dissenyis qualsevol interacció entre dos components, fes-te sempre tres preguntes: què passa si l'altre no respon?, què passa si respon molt tard? i què passa si el meu missatge va arribar però la resposta es va perdre?. Si no tens resposta per a les tres, el disseny no està acabat.
Exercicis
Exercici 1: Classificar sistemes
Per a cadascun dels casos següents, indica si es tracta d'un sistema distribuït segons la definició d'aquesta lliçó i justifica en una o dues frases quina de les característiques clau (concurrència, absència de rellotge global, fallades independents) s'hi aplica i quina no:
- Un programa de full de càlcul que fa servir diversos fils per recalcular fórmules en paral·lel.
- Una aplicació mòbil de Quilòmetre Zero que desa el cistell al telèfon i el sincronitza amb el servidor quan hi ha cobertura.
- Un script de còpia de seguretat que copia fitxers d'un disc a un altre al mateix ordinador.
- Tres repartidors que envien la seva posició cada 5 segons al servei
repartiment.
Exercici 2: Transparències a la Setmana de la Verema
Imagina que, després de l'incident, Quilòmetre Zero afegeix dos servidors més per al catàleg i un balancejador de càrrega al davant. Indica quines transparències (de la taula de l'apartat 4) s'estan proporcionant als clients (l'Anna, en Marc, la Llúcia) i quines no es proporcionen als desenvolupadors del mòdul comandes, que ara ha de cridar el catàleg per xarxa en lloc de fer-ho amb una crida de funció.
Exercici 3: Millorar la simulació
Modifica la simulació de l'apartat 8 perquè el RepartidorDeCarrega salti els nodes caiguts: quan un node llanci NodeCaigutError, ha de reintentar la mateixa comanda al següent node actiu. Si cap node no està actiu, ha de llançar una excepció SenseNodesDisponiblesError. Comprova que, amb comandes-2 caigut, les 12 comandes es processen correctament.
Solucions
Solució 1:
- No és distribuït. Hi ha concurrència (diversos fils), però tots comparteixen memòria i el mateix rellotge, i si el procés falla, fallen tots els fils alhora. És un sistema concurrent centralitzat.
- Sí que és distribuït. El telèfon i el servidor són nodes independents que només es comuniquen per missatges, amb rellotges diferents, i el telèfon pot quedar-se sense cobertura (fallada de xarxa) mentre el servidor continua funcionant. Fixa't que apareixeran problemes de coherència (quin cistell és el "bo" si s'ha modificat a tots dos llocs?), que tractarem al Mòdul 3.
- No és distribuït. Un únic procés en una única màquina. Si el disc de destinació falla, és una fallada de maquinari local, però no hi ha nodes independents comunicant-se per missatges.
- Sí que és distribuït. Cada repartidor és un node amb el seu rellotge (el del mòbil), envia missatges per una xarxa poc fiable (4G) i pot desconnectar-se de manera independent. El servei
repartimentha d'assumir que les posicions poden arribar tard, desordenades o no arribar.
Solució 2:
Als clients se'ls proporciona transparència de replicació (veuen "el catàleg", encara que n'hi hagi tres còpies), d'ubicació (no saben a quin servidor els envia el balancejador), de fallada (si un servidor de catàleg cau, el balancejador els envia a un altre) i d'escalat (afegir el tercer servidor no va canviar res per a ells).
Als desenvolupadors de comandes no se'ls proporciona plenament transparència d'accés: abans cridaven una funció local i ara han de fer una petició per xarxa, gestionar timeouts i errors de connexió, i serialitzar dades. Tampoc no tenen transparència de fallada completa: si el balancejador o els tres servidors de catàleg cauen, comandes rep un error i ha de decidir què fer. Aquesta és precisament la lliçó: la distribució es pot amagar a l'usuari final, però rarament es pot amagar del tot al programador.
Solució 3:
class SenseNodesDisponiblesError(Exception):
"""Cap node del clúster no està actiu."""
class RepartidorDeCarregaResilient(RepartidorDeCarrega):
"""Igual que RepartidorDeCarrega, però salta els nodes caiguts."""
def enviar(self, comanda: Comanda) -> str:
intents = 0
while intents < len(self.nodes):
node = self.nodes[self._seguent % len(self.nodes)]
self._seguent += 1
intents += 1
try:
return node.processar(comanda)
except NodeCaigutError:
# Aquest node no respon: provem amb el següent.
continue
raise SenseNodesDisponiblesError(
f"No hi ha nodes actius per a la comanda {comanda.id}"
)N'hi ha prou amb substituir RepartidorDeCarrega(nodes) per RepartidorDeCarregaResilient(nodes) al programa principal. Amb comandes-2 caigut, la sortida final mostrarà 12 comandes correctes, 0 fallides, amb les comandes repartides entre comandes-1 i comandes-3.
Reflexió addicional: aquesta solució assumeix que si un node llança NodeCaigutError és segur reintentar en un altre. A la simulació és cert, perquè el node caigut no ha fet res. En un sistema real, la petició podria haver arribat i haver-se processat abans que es perdés la resposta; reintentar a cegues podria crear la comanda dues vegades. La solució a això (idempotència) s'estudia a la lliçó 02-05.
Conclusió
En aquesta lliçó hem fixat els fonaments del curs. Un sistema distribuït és un conjunt de nodes independents que cooperen intercanviant missatges per una xarxa i que es presenten davant de l'usuari com un únic sistema. Es construeixen per necessitat —escala, geografia, disponibilitat, cost, organització— i no per gust, perquè introdueixen tres característiques que canvien per complet la manera de raonar: concurrència real, absència d'un rellotge global i fallades parcials i independents. Per fer la distribució suportable, el middleware ofereix diferents transparències (accés, ubicació, replicació, fallada, escalat), que sempre són parcials i tenen un cost.
Hem conegut Quilòmetre Zero en el seu estat inicial —un monòlit Python amb PostgreSQL en un únic servidor— i hem vist com la Setmana de la Verema el tomba per complet, inclosos mòduls que no tenien res a veure amb la campanya. La simulació de nodes ha mostrat el costat positiu (el sistema continua atenent comandes quan un node cau) i també ha deixat a la vista els problemes que encara no sabem resoldre.
A la lliçó següent, Models de Sistemes Distribuïts, veurem les diferents maneres d'organitzar els nodes (client-servidor, multicapa, peer-to-peer, serveis), els supòsits que podem fer sobre la seva comunicació i les seves fallades, i començarem a dibuixar com podria reorganitzar-se Quilòmetre Zero.
Curs d'Arquitectures Distribuïdes
Mòdul 1: Introducció als Sistemes Distribuïts
- Conceptes Bàsics de Sistemes Distribuïts
- Models de Sistemes Distribuïts
- Avantatges i Desafiaments dels Sistemes Distribuïts
- Les Fal·làcies de la Computació Distribuïda
- Temps, Rellotges i Ordenació d'Esdeveniments
- Del Monòlit a la Plataforma Distribuïda: el Cas Quilòmetre Zero
Mòdul 2: Comunicació en Sistemes Distribuïts
- Protocols de Comunicació
- RPC i RMI
- gRPC i Serialització de Dades
- Missatgeria i Cues de Missatges
- Patrons de Comunicació Asíncrona
Mòdul 3: Consistència i Replicació
- Models de Consistència
- El Teorema CAP i PACELC
- Algorismes de Consens
- Replicació de Dades
- Transaccions Distribuïdes i Sagues
Mòdul 4: Emmagatzematge Distribuït
- Particionament de Dades i Hashing Consistent
- Sistemes de Fitxers Distribuïts
- Emmagatzematge d'Objectes
- Bases de Dades Distribuïdes
- Memòries Cau Distribuïdes
Mòdul 5: Computació Distribuïda
- Models de Computació Distribuïda
- MapReduce i Hadoop
- Spark i Computació en Memòria
- Processament de Fluxos de Dades
- Planificació de Treballs i Pipelines de Dades
Mòdul 6: Seguretat en Sistemes Distribuïts
- Autenticació i Autorització
- Xifratge i Protecció de Dades
- Gestió d'Identitats
- Seguretat entre Serveis: mTLS i Gestió de Secrets
- Passarel·les d'API, Limitació de Taxa i Auditoria
Mòdul 7: Monitoratge i Manteniment
- Monitoratge de Sistemes Distribuïts
- Logs Centralitzats i Traçabilitat Distribuïda
- Gestió de Fallades i Recuperació
- Patrons de Resiliència: Timeouts, Reintents i Circuit Breaker
- Automatització i Orquestració
- Proves en Sistemes Distribuïts i Enginyeria del Caos
