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