Implementando Circuit Breaker con Redis en PHP para Microservicios
En arquitecturas de microservicios, la resiliencia es crucial. Un fallo en un servicio no debe propagarse y derribar toda la aplicación. El patrón Circuit Breaker ayuda a prevenir esto, actuando como un interruptor que impide llamar a un servicio que está fallando, dando tiempo a que se recupere. Este post muestra cómo implementar un Circuit Breaker en PHP, utilizando Redis para el almacenamiento del estado.
La idea principal es almacenar el estado (cerrado, abierto, medio-abierto) del circuito en Redis. Cada vez que intentamos llamar al servicio, verificamos el estado en Redis. Si está cerrado, la llamada se realiza. Si está abierto, la llamada se evita y se devuelve una respuesta predeterminada o un error. Si está medio-abierto, se permite una llamada para verificar si el servicio se ha recuperado.
<?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 = 5, int $retryTimeout = 10) {
$this->redis = new Client(['host' => '127.0.0.1', 'port' => 6379]); // Reemplazar con tu config de Redis
$this->serviceName = $serviceName;
$this->failureThreshold = $failureThreshold;
$this->retryTimeout = $retryTimeout;
}
private function getStateKey(): string {
return "circuit_breaker:{$this->serviceName}:state";
}
private function getFailureCountKey(): string {
return "circuit_breaker:{$this->serviceName}:failure_count";
}
private function getLastFailureTimeKey(): string {
return "circuit_breaker:{$this->serviceName}:last_failure_time";
}
public function execute(callable $operation, callable $fallback) {
$state = $this->redis->get($this->getStateKey()) ?? 'closed';
if ($state === 'open') {
$lastFailureTime = (int)$this->redis->get($this->getLastFailureTimeKey());
if (time() - $lastFailureTime < $this->retryTimeout) {
return $fallback(); // Cortocircuito: No llamar al servicio
} else {
$this->redis->set($this->getStateKey(), 'half_open');
}
}
try {
$result = $operation(); // Llamar al servicio
$this->reset(); // Si la llamada fue exitosa, resetear el circuito
return $result;
} catch (\Exception $e) {
$this->recordFailure();
return $fallback(); // Cortocircuito: Devolver fallback
}
}
private function recordFailure() {
$failureCount = (int)$this->redis->incr($this->getFailureCountKey());
$this->redis->set($this->getLastFailureTimeKey(), time());
if ($failureCount >= $this->failureThreshold) {
$this->open();
}
}
private function open() {
$this->redis->set($this->getStateKey(), 'open');
$this->redis->set($this->getLastFailureTimeKey(), time());
}
private function reset() {
$this->redis->set($this->getStateKey(), 'closed');
$this->redis->del($this->getFailureCountKey());
$this->redis->del($this->getLastFailureTimeKey());
}
}
Este código define la clase CircuitBreaker
. El constructor recibe el nombre del servicio, el número máximo de fallos permitidos antes de abrir el circuito (failureThreshold
) y el tiempo que el circuito debe permanecer abierto antes de permitir una nueva prueba (retryTimeout
). Los métodos getStateKey
, getFailureCountKey
y getLastFailureTimeKey
generan claves únicas para Redis basadas en el nombre del servicio.
<?php
// Ejemplo de uso:
require_once 'vendor/autoload.php'; // Asegúrate de tener predis/predis instalado
$breaker = new CircuitBreaker('UserService', 3, 5); // 3 fallos, reintentar en 5 segundos
$result = $breaker->execute(
function() {
// Aquí iría la llamada al microservicio UserService
// Simulación de un fallo:
if (rand(0, 5) < 2) {
throw new Exception("Error al llamar a UserService!");
}
return "Respuesta del UserService";
},
function() {
return "Respuesta fallback (UserService no disponible)";
}
);
echo $result . PHP_EOL;
El ejemplo de uso muestra cómo instanciar la clase CircuitBreaker
y utilizar el método execute
. Este método recibe dos funciones anónimas: la primera es la operación que intenta llamar al microservicio, y la segunda es la función de "fallback" que se ejecuta si el circuito está abierto o si la llamada al microservicio falla.
Con esta implementación, podemos proteger nuestra aplicación PHP de fallos en microservicios, mejorando la resiliencia y la experiencia del usuario.
No hay comentarios:
Publicar un comentario