Els sistemes distribuïts són aquells en els quals components situats en diferents nodes de la xarxa col·laboren per aconseguir un objectiu comú. Aquests sistemes presenten desafiaments únics, com la latència de xarxa, la tolerància a fallades i la consistència de dades. Els patrons de disseny poden ajudar a abordar aquests desafiaments de manera eficient i estructurada.

Objectius d'aquest Tema

  • Entendre els desafiaments específics dels sistemes distribuïts.
  • Aprendre sobre patrons de disseny específics per a sistemes distribuïts.
  • Veure exemples pràctics de com implementar aquests patrons.
  • Realitzar exercicis pràctics per reforçar els conceptes apresos.

Desafiaments dels Sistemes Distribuïts

  1. Latència de Xarxa: La comunicació entre nodes pot ser lenta i imprevisible.
  2. Tolerància a Fallades: Els nodes poden fallar, i el sistema ha de continuar funcionant.
  3. Consistència de Dades: Mantenir les dades consistents entre diferents nodes és complex.
  4. Escalabilitat: El sistema ha de poder manejar un augment en la càrrega de treball.
  5. Seguretat: La comunicació entre nodes ha de ser segura.

Patrons de Disseny per a Sistemes Distribuïts

  1. Patró de Proxy Distribuït

Descripció

El patró de Proxy Distribuït permet que un objecte actui com a representant d'un altre objecte situat en un node diferent. Això ajuda a encapsular la complexitat de la comunicació remota.

Exemple de Codi

// Interfície del servei
public interface RemoteService {
    String fetchData();
}

// Implementació del servei en el servidor
public class RemoteServiceImpl implements RemoteService {
    @Override
    public String fetchData() {
        return "Dades des del servidor";
    }
}

// Proxy que actua com a representant del servei remot
public class RemoteServiceProxy implements RemoteService {
    private RemoteService remoteService;

    public RemoteServiceProxy(RemoteService remoteService) {
        this.remoteService = remoteService;
    }

    @Override
    public String fetchData() {
        // Aquí es podria afegir la lògica de comunicació remota
        return remoteService.fetchData();
    }
}

// Client que utilitza el proxy
public class Client {
    public static void main(String[] args) {
        RemoteService service = new RemoteServiceProxy(new RemoteServiceImpl());
        System.out.println(service.fetchData());
    }
}

Explicació

  • RemoteService: Interfície que defineix el servei.
  • RemoteServiceImpl: Implementació del servei en el servidor.
  • RemoteServiceProxy: Proxy que encapsula la comunicació remota.
  • Client: Client que utilitza el servei a través del proxy.

  1. Patró de Consistència Eventual

Descripció

Aquest patró permet que les dades siguin eventualment consistents en tots els nodes, acceptant que poden estar temporalment inconsistents.

Exemple de Codi

// Classe que representa un esdeveniment
public class Event {
    private String data;

    public Event(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }
}

// Classe que maneja la replicació d'esdeveniments
public class EventReplicator {
    private List<Event> eventLog = new ArrayList<>();

    public void replicateEvent(Event event) {
        eventLog.add(event);
        // Lògica per replicar l'esdeveniment a altres nodes
    }

    public List<Event> getEventLog() {
        return eventLog;
    }
}

// Exemple d'ús
public class Main {
    public static void main(String[] args) {
        EventReplicator replicator = new EventReplicator();
        Event event = new Event("Dades noves");
        replicator.replicateEvent(event);

        // Simulació de la consistència eventual
        System.out.println("Esdeveniments replicats: " + replicator.getEventLog().size());
    }
}

Explicació

  • Event: Classe que representa un esdeveniment que ha de ser replicat.
  • EventReplicator: Classe que maneja la replicació d'esdeveniments.
  • Main: Exemple d'ús que mostra com replicar un esdeveniment.

  1. Patró de Circuit Breaker

Descripció

El patró de Circuit Breaker evita que un sistema intenti fer operacions que probablement fallaran, permetent que es recuperi més ràpidament.

Exemple de Codi

// Classe que implementa el patró de Circuit Breaker
public class CircuitBreaker {
    private boolean isOpen = false;
    private int failureCount = 0;
    private int failureThreshold = 3;

    public void callService() throws Exception {
        if (isOpen) {
            throw new Exception("Circuit is open. Service call blocked.");
        }

        try {
            // Simulació de la crida al servei
            boolean success = simulateServiceCall();
            if (!success) {
                failureCount++;
                if (failureCount >= failureThreshold) {
                    isOpen = true;
                }
            } else {
                failureCount = 0;
            }
        } catch (Exception e) {
            failureCount++;
            if (failureCount >= failureThreshold) {
                isOpen = true;
            }
            throw e;
        }
    }

    private boolean simulateServiceCall() {
        // Simulació d'una crida al servei que pot fallar
        return Math.random() > 0.5;
    }

    public void reset() {
        isOpen = false;
        failureCount = 0;
    }
}

// Exemple d'ús
public class Main {
    public static void main(String[] args) {
        CircuitBreaker circuitBreaker = new CircuitBreaker();

        for (int i = 0; i < 10; i++) {
            try {
                circuitBreaker.callService();
                System.out.println("Service call successful");
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
    }
}

Explicació

  • CircuitBreaker: Implementació del patró de Circuit Breaker.
  • simulateServiceCall: Simulació d'una crida al servei que pot fallar.
  • Main: Exemple d'ús que mostra com utilitzar el Circuit Breaker.

Exercicis Pràctics

Exercici 1: Implementar un Proxy Distribuït

Descripció: Implementa un proxy distribuït per a un servei que retorna la data i hora actual des d'un servidor remot.

Solució:

// Interfície del servei
public interface DateTimeService {
    String getCurrentDateTime();
}

// Implementació del servei en el servidor
public class DateTimeServiceImpl implements DateTimeService {
    @Override
    public String getCurrentDateTime() {
        return LocalDateTime.now().toString();
    }
}

// Proxy que actua com a representant del servei remot
public class DateTimeServiceProxy implements DateTimeService {
    private DateTimeService dateTimeService;

    public DateTimeServiceProxy(DateTimeService dateTimeService) {
        this.dateTimeService = dateTimeService;
    }

    @Override
    public String getCurrentDateTime() {
        // Aquí es podria afegir la lògica de comunicació remota
        return dateTimeService.getCurrentDateTime();
    }
}

// Client que utilitza el proxy
public class Client {
    public static void main(String[] args) {
        DateTimeService service = new DateTimeServiceProxy(new DateTimeServiceImpl());
        System.out.println(service.getCurrentDateTime());
    }
}

Exercici 2: Implementar un Circuit Breaker

Descripció: Implementa un Circuit Breaker per a un servei que retorna un missatge de benvinguda. El servei ha de fallar aleatòriament per simular un entorn real.

Solució:

// Classe que implementa el patró de Circuit Breaker
public class CircuitBreaker {
    private boolean isOpen = false;
    private int failureCount = 0;
    private int failureThreshold = 3;

    public void callService() throws Exception {
        if (isOpen) {
            throw new Exception("Circuit is open. Service call blocked.");
        }

        try {
            // Simulació de la crida al servei
            boolean success = simulateServiceCall();
            if (!success) {
                failureCount++;
                if (failureCount >= failureThreshold) {
                    isOpen = true;
                }
            } else {
                failureCount = 0;
            }
        } catch (Exception e) {
            failureCount++;
            if (failureCount >= failureThreshold) {
                isOpen = true;
            }
            throw e;
        }
    }

    private boolean simulateServiceCall() {
        // Simulació d'una crida al servei que pot fallar
        return Math.random() > 0.5;
    }

    public void reset() {
        isOpen = false;
        failureCount = 0;
    }
}

// Exemple d'ús
public class Main {
    public static void main(String[] args) {
        CircuitBreaker circuitBreaker = new CircuitBreaker();

        for (int i = 0; i < 10; i++) {
            try {
                circuitBreaker.callService();
                System.out.println("Service call successful");
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
    }
}

Resum

En aquesta secció, hem explorat diversos patrons de disseny específics per a sistemes distribuïts, com el Proxy Distribuït, la Consistència Eventual i el Circuit Breaker. Aquests patrons ajuden a abordar els desafiaments únics dels sistemes distribuïts, com la latència de xarxa, la tolerància a fallades i la consistència de dades. Hem vist exemples pràctics de com implementar aquests patrons i hem realitzat exercicis per reforçar els conceptes apresos.

En el següent mòdul, explorarem com aplicar aquests patrons en arquitectures modernes com els microserveis i els sistemes distribuïts.

© Copyright 2024. Tots els drets reservats