miércoles, 25 de junio de 2025

Implementación de Circuit Breaker con Redis en PHP para Resiliencia

Implementación de Circuit Breaker con Redis en PHP para Resiliencia

En arquitecturas de microservicios y sistemas distribuidos, la resiliencia es crucial. Un fallo en un servicio puede propagarse y afectar la estabilidad de todo el sistema. El patrón Circuit Breaker es una técnica poderosa para prevenir fallos en cascada y mejorar la resiliencia de las aplicaciones. Este artículo muestra cómo implementar un Circuit Breaker en PHP, utilizando Redis como almacenamiento compartido para el estado del circuito.


<?php

use Predis\Client;

class CircuitBreaker
{
    private Client $redis;
    private string $serviceName;
    private int $failureThreshold;
    private int $retryTimeout;

    public function __construct(string $serviceName, int $failureThreshold, int $retryTimeout, Client $redisClient)
    {
        $this->redis = $redisClient;
        $this->serviceName = $serviceName;
        $this->failureThreshold = $failureThreshold;
        $this->retryTimeout = $retryTimeout;
    }

    public function execute(callable $operation)
    {
        $state = $this->getState();

        if ($state === 'OPEN') {
            $lastFailureTime = $this->redis->get($this->serviceName . ':last_failure');
            if (time() - $lastFailureTime < $this->retryTimeout) {
                throw new \Exception("Circuit Breaker is OPEN for {$this->serviceName}");
            }
            $this->redis->set($this->serviceName . ':state', 'HALF_OPEN');
            $this->redis->del($this->serviceName . ':failure_count');
        }

        try {
            $result = $operation();
            $this->reset();
            return $result;
        } catch (\Exception $e) {
            $this->recordFailure();
            throw $e;
        }
    }

    private function getState(): string
    {
        return $this->redis->get($this->serviceName . ':state') ?: 'CLOSED';
    }

    private function recordFailure(): void
    {
        $failureCount = $this->redis->incr($this->serviceName . ':failure_count');
        $this->redis->set($this->serviceName . ':last_failure', time());

        if ($failureCount >= $this->failureThreshold) {
            $this->redis->set($this->serviceName . ':state', 'OPEN');
        }
    }

    private function reset(): void
    {
        $this->redis->set($this->serviceName . ':state', 'CLOSED');
        $this->redis->del($this->serviceName . ':failure_count');
        $this->redis->del($this->serviceName . ':last_failure');
    }
}

// Ejemplo de uso:
// $redis = new Predis\Client();
// $breaker = new CircuitBreaker('my_service', 3, 10, $redis); // Servicio, fallos permitidos, tiempo de reintento (segundos), cliente redis

// try {
//     $result = $breaker->execute(function() {
//         // Tu lógica para llamar al servicio externo
//         // Puede lanzar una excepción si falla
//         return 'Success!';
//     });
//     echo "Resultado: " . $result;
// } catch (\Exception $e) {
//     echo "Error: " . $e->getMessage();
// }

Este código define la clase CircuitBreaker, que utiliza Redis para almacenar el estado del circuito (CLOSED, OPEN, HALF_OPEN), el número de fallos y la última vez que ocurrió un fallo. El método execute envuelve la llamada al servicio externo. Si el circuito está OPEN, lanza una excepción, a menos que haya transcurrido el tiempo de reintento. Si la llamada tiene éxito, el circuito se resetea a CLOSED. Si falla, se registra el fallo y, si se alcanza el umbral, el circuito se abre.

Es importante configurar correctamente el failureThreshold y el retryTimeout para cada servicio, basados en sus características y la tolerancia a fallos requerida. La biblioteca Predis se utiliza para la conexión con Redis, pero se puede sustituir por cualquier otra librería compatible.


<?php
// Ejemplo de conexión a Redis usando Predis
require 'vendor/autoload.php'; // Asume que Predis está instalado vía Composer

use Predis\Client;

$redis = new Client([
    'scheme' => 'tcp',
    'host'   => '127.0.0.1',
    'port'   => 6379,
]);

try {
    $redis->ping();
    echo "Conexión a Redis exitosa.\n";
} catch (Exception $e) {
    echo "No se pudo conectar a Redis: " . $e->getMessage() . "\n";
}
    

Esta implementación proporciona una base sólida para añadir resiliencia a tus aplicaciones PHP mediante el patrón Circuit Breaker. Al utilizar Redis como almacenamiento compartido, se garantiza que el estado del circuito sea consistente entre diferentes instancias de la aplicación, lo cual es crucial en entornos distribuidos.

No hay comentarios:

Publicar un comentario