miércoles, 25 de junio de 2025

Implementación de Estrategias Avanzadas de Retry en PHP con Exponential Backoff

Implementación de Estrategias Avanzadas de Retry en PHP con Exponential Backoff

En el desarrollo de aplicaciones PHP robustas y tolerantes a fallos, es crucial implementar mecanismos de reintento (retry) para manejar errores transitorios, como fallos de conexión a bases de datos o servicios externos. Una estrategia efectiva es el "exponential backoff", que incrementa gradualmente el tiempo de espera entre reintentos, evitando así sobrecargar el sistema y dándole tiempo para recuperarse. Este artículo explora cómo implementar esta estrategia en PHP de manera elegante y configurable.


<?php

/**
 * Función para ejecutar una operación con retry y exponential backoff.
 *
 * @param callable $operation La operación a ejecutar (debe retornar true en caso de éxito).
 * @param int $maxRetries El número máximo de reintentos.
 * @param int $initialDelay La demora inicial en milisegundos.
 * @param callable|null $errorHandler Un callback opcional para manejar errores.
 * @return bool True si la operación se completó con éxito, false si se excedió el número de reintentos.
 * @throws Exception Si la operación lanza una excepción y no se proporciona un errorHandler.
 */
function executeWithRetry(callable $operation, int $maxRetries = 3, int $initialDelay = 100, ?callable $errorHandler = null): bool
{
    $retries = 0;
    $delay = $initialDelay;

    while ($retries < $maxRetries) {
        try {
            if ($operation()) {
                return true; // Operación exitosa
            }
        } catch (Exception $e) {
            if ($errorHandler === null) {
                throw $e; // Re-lanza la excepción si no hay un handler
            }
            $errorHandler($e, $retries);
        }

        $retries++;
        if ($retries < $maxRetries) {
            usleep($delay * 1000); // Convertir milisegundos a microsegundos
            $delay *= 2; // Duplicar el tiempo de espera
        }
    }

    return false; // Se excedió el número de reintentos
}

// Ejemplo de uso:
$attemptCounter = 0;
$operation = function () use (&$attemptCounter): bool {
    $attemptCounter++;
    echo "Intento #" . $attemptCounter . "...\n";
    if (rand(0, 4) > 0) { // Simula un fallo ocasional
        throw new Exception("Fallo simulado.");
    }
    echo "Éxito!\n";
    return true;
};

$errorHandler = function (Exception $e, int $retryCount): void {
    echo "Error: " . $e->getMessage() . " (Reintento #" . ($retryCount + 1) . ")\n";
};

$success = executeWithRetry($operation, 4, 500, $errorHandler);

if (!$success) {
    echo "La operación falló después de varios reintentos.\n";
}

?>
    

El código anterior define una función `executeWithRetry` que toma una operación (callable), el número máximo de reintentos, una demora inicial y un handler de errores opcional. Utiliza un bucle `while` para reintentar la operación hasta que tenga éxito o se alcance el límite de reintentos. El tiempo de espera se duplica en cada iteración, implementando la estrategia de exponential backoff. Si ocurre una excepción y se proporciona un `errorHandler`, este se ejecuta; de lo contrario, la excepción se relanza.


<?php

// Ejemplo de uso con una conexión a la base de datos (simplificado).

function connectToDatabase(): bool {
    try {
        // Lógica para conectar a la base de datos.  Aquí, solo simulamos la conexión.
        $connected = rand(0,1) == 1; //Simula una conexión exitosa o fallida.
        if(!$connected){
            throw new Exception("No se pudo conectar a la base de datos.");
        }
        echo "Conexión a la base de datos exitosa.\n";
        return true;
    } catch (Exception $e) {
        echo "Error de conexión: " . $e->getMessage() . "\n";
        throw $e; //Relanzamos la excepción para que executeWithRetry la maneje.
    }
}

$databaseConnectionOperation = function () {
    return connectToDatabase();
};

try {
    $dbConnected = executeWithRetry($databaseConnectionOperation, 3, 200);

    if ($dbConnected) {
        echo "La aplicación se ha conectado exitosamente a la base de datos.\n";
    } else {
        echo "La aplicación no pudo conectarse a la base de datos después de varios reintentos.\n";
    }

} catch (Exception $e) {
    echo "Error fatal: " . $e->getMessage() . "\n";
}

?>
    

Este segundo ejemplo muestra cómo aplicar la estrategia de retry a una conexión de base de datos. La función `connectToDatabase` intenta establecer la conexión y lanza una excepción en caso de fallo. `executeWithRetry` se encarga de reintentar la conexión con exponential backoff. Es importante capturar la excepción final fuera de `executeWithRetry` para manejar errores críticos que persisten después de todos los reintentos.

No hay comentarios:

Publicar un comentario