En aquest tema, explorarem els patrons de disseny escalable, que són solucions provades i reutilitzables per a problemes comuns en el disseny de sistemes escalables. Aquests patrons ajuden a garantir que els sistemes puguin gestionar un augment de la càrrega de treball sense perdre rendiment ni fiabilitat.

Objectius d'Aprenentatge

Al final d'aquest tema, hauràs de ser capaç de:

  1. Comprendre els conceptes bàsics dels patrons de disseny escalable.
  2. Identificar i aplicar diversos patrons de disseny escalable en diferents contextos.
  3. Avaluar els avantatges i desavantatges de cada patró.

Conceptes Bàsics

Què és un Patró de Disseny Escalable?

Un patró de disseny escalable és una solució arquitectònica que permet a un sistema gestionar un augment de la càrrega de treball de manera eficient. Aquests patrons ajuden a distribuir la càrrega, optimitzar l'ús dels recursos i mantenir la qualitat del servei.

Tipus d'Escalabilitat

  1. Escalabilitat Horitzontal (Scale-Out): Afegir més nodes al sistema per distribuir la càrrega.
  2. Escalabilitat Vertical (Scale-Up): Augmentar la capacitat dels nodes existents (més CPU, memòria, etc.).

Patrons de Disseny Escalable

  1. Patró de Compartició (Sharding)

Descripció: Divideix una base de dades o un conjunt de dades en parts més petites, anomenades "shards", que es distribueixen entre diferents servidors.

Avantatges:

  • Millora el rendiment distribuint la càrrega.
  • Permet l'escalabilitat horitzontal.

Desavantatges:

  • Pot ser complex de gestionar.
  • Requereix una lògica addicional per a la distribució i consulta de dades.

Exemple:

-- Suposem una base de dades d'usuaris que es divideix en dos shards
-- Shard 1: Usuaris amb ID parells
-- Shard 2: Usuaris amb ID senars

-- Consulta en Shard 1
SELECT * FROM users WHERE id % 2 = 0;

-- Consulta en Shard 2
SELECT * FROM users WHERE id % 2 = 1;

  1. Patró de Cache

Descripció: Emmagatzema dades freqüentment accedides en una memòria ràpida (cache) per reduir el temps de resposta i la càrrega sobre el sistema principal.

Avantatges:

  • Redueix la latència.
  • Disminueix la càrrega sobre la base de dades principal.

Desavantatges:

  • Pot introduir complexitat en la gestió de la coherència de dades.
  • Requereix espai addicional de memòria.

Exemple:

# Exemple d'ús de Redis com a cache en una aplicació Python
import redis

# Connexió a Redis
cache = redis.StrictRedis(host='localhost', port=6379, db=0)

# Emmagatzemar una clau-valor en la cache
cache.set('user_123', 'John Doe')

# Recuperar el valor de la cache
user = cache.get('user_123')
print(user)  # Output: b'John Doe'

  1. Patró de Queue-Based Load Leveling

Descripció: Utilitza cues per gestionar la càrrega de treball de manera asíncrona, equilibrant la càrrega entre diferents components del sistema.

Avantatges:

  • Millora la resiliència del sistema.
  • Permet gestionar pics de càrrega de manera eficient.

Desavantatges:

  • Pot introduir latència en el processament de tasques.
  • Requereix una gestió addicional de les cues.

Exemple:

# Exemple d'ús de RabbitMQ per a la gestió de cues en una aplicació Python
import pika

# Connexió a RabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# Declarar una cua
channel.queue_declare(queue='task_queue')

# Enviar un missatge a la cua
channel.basic_publish(exchange='', routing_key='task_queue', body='Tasques a processar')
print(" [x] Enviat 'Tasques a processar'")

# Tancar la connexió
connection.close()

  1. Patró de Microserveis

Descripció: Divideix una aplicació en serveis petits i independents que es poden desplegar i escalar de manera autònoma.

Avantatges:

  • Facilita l'escalabilitat independent de components.
  • Millora la mantenibilitat i la flexibilitat del sistema.

Desavantatges:

  • Pot augmentar la complexitat de la gestió i la comunicació entre serveis.
  • Requereix una infraestructura adequada per a la gestió de microserveis.

Exemple:

# Exemple de definició de serveis en un fitxer Docker Compose
version: '3'
services:
  user-service:
    image: user-service:latest
    ports:
      - "5000:5000"
  order-service:
    image: order-service:latest
    ports:
      - "5001:5001"

Exercicis Pràctics

Exercici 1: Implementació de Cache

Descripció: Implementa una solució de cache per a una aplicació web que consulta dades d'una base de dades freqüentment.

Instruccions:

  1. Configura una instància de Redis.
  2. Modifica l'aplicació web per utilitzar Redis com a cache.
  3. Mesura el rendiment abans i després de la implementació de la cache.

Solució:

# Exemple d'implementació de cache en una aplicació web amb Flask i Redis
from flask import Flask, request
import redis
import time

app = Flask(__name__)
cache = redis.StrictRedis(host='localhost', port=6379, db=0)

@app.route('/data')
def get_data():
    data_id = request.args.get('id')
    cached_data = cache.get(data_id)
    
    if cached_data:
        return cached_data
    
    # Simulació de consulta a la base de dades
    time.sleep(2)
    data = f'Dades per a {data_id}'
    
    # Emmagatzemar en la cache
    cache.set(data_id, data)
    
    return data

if __name__ == '__main__':
    app.run(debug=True)

Exercici 2: Implementació de Queue-Based Load Leveling

Descripció: Implementa una solució de cues per gestionar tasques asíncrones en una aplicació.

Instruccions:

  1. Configura una instància de RabbitMQ.
  2. Modifica l'aplicació per enviar tasques a una cua.
  3. Implementa un consumidor que processi les tasques de la cua.

Solució:

# Exemple d'implementació de cues amb RabbitMQ en una aplicació Python
import pika

# Connexió a RabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# Declarar una cua
channel.queue_declare(queue='task_queue')

# Enviar una tasca a la cua
channel.basic_publish(exchange='', routing_key='task_queue', body='Tasques a processar')
print(" [x] Enviat 'Tasques a processar'")

# Tancar la connexió
connection.close()

# Consumidor de la cua
def callback(ch, method, properties, body):
    print(f" [x] Processant {body}")

# Connexió a RabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# Declarar una cua
channel.queue_declare(queue='task_queue')

# Assignar el consumidor a la cua
channel.basic_consume(queue='task_queue', on_message_callback=callback, auto_ack=True)

print(' [*] Esperant tasques. Per sortir, premeu CTRL+C')
channel.start_consuming()

Resum

En aquest tema, hem explorat diversos patrons de disseny escalable, incloent el patró de compartició, el patró de cache, el patró de queue-based load leveling i el patró de microserveis. Cada patró ofereix solucions específiques per a problemes comuns en el disseny de sistemes escalables, i la seva aplicació adequada pot millorar significativament el rendiment i la resiliència dels sistemes tecnològics.

En el proper tema, explorarem el balanceig de càrrega, una altra tècnica clau per garantir l'escalabilitat i la disponibilitat dels sistemes.

© Copyright 2024. Tots els drets reservats