Creación de un Web Server con Worker Pool en Go

Clase 28 de 30Curso de Go Intermedio: Programación Orientada a Objetos y Concurrencia

Resumen

¿Cómo crear un servidor web para gestionar trabajos con Go?

Crear un servidor web que gestione trabajos utilizando Go puede ser un proyecto fascinante y muy educativo. Este análisis te guiará paso a paso para generar un "worker pool" que te permita procesar trabajos en paralelo de manera eficiente. Aquí exploraremos cómo estructurar el código y los conceptos clave detrás de esta implementación.

¿Cuál es la estructura principal de nuestro proyecto?

El punto de partida es crear una carpeta para el proyecto, dentro de la cual vamos a definir un archivo principal llamado main.go. El primer paso es definir el paquete principal y comenzar a diseñar los structs fundamentales para nuestro proyecto.

package main

import (
    "time"
)

// Definición del struct Job
type Job struct {
    Name  string
    Delay time.Duration
    Number int
}

// Definición del struct Worker
type Worker struct {
    ID          int
    JobQueue    chan Job
    WorkerPool  chan chan Job
    QuitChan    chan bool
}

// Definición del struct Dispatcher
type Dispatcher struct {
    WorkerPool  chan chan Job
    MaxWorkers  int
    JobQueue    chan Job
}

Aquí hemos definido tres componentes principales: Job, que representa el trabajo a procesar; Worker, que ejecuta los trabajos; y Dispatcher, que se encarga de distribuir los trabajos entre los workers disponibles.

¿Cómo implementar constructores para workers y dispatchers?

Para poner en práctica los patrones de diseño recomendados en Go, es útil implementar constructores que inicialicen nuestros structs.

func NewWorker(id int, workerPool chan chan Job) Worker {
    return Worker{
        ID:         id,
        JobQueue:   make(chan Job),
        WorkerPool: workerPool,
        QuitChan:   make(chan bool),
    }
}

func NewDispatcher(maxWorkers int, jobQueue chan Job) *Dispatcher {
    workerPool := make(chan chan Job, maxWorkers)
    return &Dispatcher{
        WorkerPool: workerPool,
        MaxWorkers: maxWorkers,
        JobQueue:   jobQueue,
    }
}

Utilizar constructores tiene ventajas significativas, como asegurarnos de que todas las propiedades estén correctamente inicializadas.

¿Cómo funcionan los workers y el proceso de dispatch?

Los workers son el núcleo del procesamiento paralelo. Implementaremos métodos para que continuamente escuchen trabajos y los procesen adecuadamente.

func (w Worker) Start() {
    go func() {
        for {
            w.WorkerPool <- w.JobQueue
            select {
            case job := <-w.JobQueue:
                result := fibonacci(job.Number)
                time.Sleep(job.Delay)
                fmt.Printf("Worker %d ha finalizado con resultado %d\n", w.ID, result)
            case <-w.QuitChan:
                fmt.Printf("Worker %d ha sido detenido\n", w.ID)
                return
            }
        }
    }()
}

func (w Worker) Stop() {
    go func() {
        w.QuitChan <- true
    }()
}

Implementamos tanto la ejecución indefinida de los workers como el mecanismo para detenerlos. En cuanto al dispatcher, su objetivo es asignar trabajos a los workers:

func (d *Dispatcher) Dispatch() {
    go func() {
        for {
            select {
            case job := <-d.JobQueue:
                go func(job Job) {
                    workerJobQueue := <-d.WorkerPool
                    workerJobQueue <- job
                }(job)
            }
        }
    }()
}

¿Cómo ejecutar la serie Fibonacci de forma eficiente?

Para el cálculo de la serie de Fibonacci, utilizamos recursión, un método que ya fue analizado previamente y es ampliamente conocido por su elegancia y simplicidad.

func fibonacci(n int) int {
    if n <= 1 {
        return n
    }
    return fibonacci(n-1) + fibonacci(n-2)
}

¿Qué sigue después de estructurar y programar workers y dispatcher?

Hasta este punto, hemos construido los cimientos de nuestro servidor web en Go. En futuras implementaciones, la integración de una interfaz web para enviar trabajos al dispatcher ampliará las capacidades de este proyecto, permitiendo que múltiples usuarios interactúen y aprovechen este sistema eficiente. No dudes en explorar más allá de lo aprendido aquí y aprovecha la oportunidad de ampliar tu comprensión sobre goroutines, canales y la programación concurrente en Go. ¡Buena suerte y sigue aprendiendo!