Mecanismo de Failover - santig005/Distributed-Systems-gRPC GitHub Wiki

Mecanismo de Failover con MOM (RabbitMQ)

Este sistema utiliza RabbitMQ como Middleware Orientado a Mensajes (MOM) para implementar un mecanismo de failover, principalmente para la operación de creación de órdenes.

Propósito

El objetivo es evitar la pérdida de solicitudes de creación de órdenes si uno de los servicios requeridos (User Service, Product Service) o el propio Order Service están temporalmente indisponibles o fallan durante el procesamiento de una manera que se considera recuperable (ej., error de red temporal, error interno momentáneo).

Configuración de RabbitMQ

  • Cola Principal: Se utiliza una cola dedicada llamada order-service-queue.
  • Durabilidad: La cola se declara como durable: true. Esto significa que los mensajes en la cola sobrevivirán a un reinicio del broker RabbitMQ.
  • Acknowledgements (ACKs): El consumidor en el order_service está configurado con noAck: false. Esto requiere que el consumidor envíe explícitamente un reconocimiento (channel.ack(msg)) a RabbitMQ después de procesar exitosamente un mensaje. Si el consumidor falla antes de enviar el ACK, RabbitMQ re-entregará el mensaje (a este u otro consumidor disponible).

Flujo de Failover (Creación de Orden)

  1. Petición Inicial: El API Gateway recibe un POST /api/orders.
  2. Llamada gRPC: El Gateway llama al createOrder del OrderServiceClient.
  3. Procesamiento en Order Service: El createOrderHandler en order_service intenta:
    • Obtener datos del usuario (userClient.GetUser).
    • Obtener datos de los productos (productClient.GetProduct).
    • Verificar saldo.
    • Actualizar saldo del usuario (userClient.UpdateUserBalance).
  4. Detección de Error: Si alguna de las llamadas gRPC a user_service o product_service falla con un error considerado "retryable" (como UNAVAILABLE, INTERNAL, DEADLINE_EXCEEDED), o si ocurre un error interno similar antes de crear la orden localmente, el catch en createOrderHandler (o la lógica en el API Gateway si el error ocurre allí) intercepta el fallo.
  5. Decisión de Encolar: El GatewayGrpcErrorHandler (en el API Gateway) o una lógica similar determina si el error (StatusRuntimeException) es retryable.
  6. Publicación en RabbitMQ: Si el error es retryable, el API Gateway no intenta procesar más, sino que serializa la solicitud original (userId, productIds) como un mensaje JSON y lo envía a la cola order-service-queue en RabbitMQ usando RabbitMQService.
  7. Respuesta al Cliente: El API Gateway responde inmediatamente al cliente con 202 Accepted, indicando que la solicitud fue recibida pero será procesada más tarde.
  8. Consumo Asíncrono (Order Service): El consumidor RabbitMQ dentro del order_service (startRabbitMQConsumer en server.js) está escuchando constantemente en la cola order-service-queue.
  9. Recepción del Mensaje: Cuando un mensaje llega, el consumidor lo recibe. Gracias a channel.prefetch(1), solo procesa un mensaje a la vez.
  10. Intento de Procesamiento: El consumidor deserializa el mensaje JSON y llama internamente a la lógica de createOrderHandler con los datos recuperados (userId, productIds).
  11. Éxito: Si esta vez createOrderHandler se ejecuta sin errores (porque los servicios dependientes ya están disponibles), procesa la orden, la guarda en orders.json, y finalmente llama a channel.ack(msg). RabbitMQ elimina el mensaje de la cola.
  12. Fallo Persistente / Error No Retryable en Consumidor:
    • Si el procesamiento dentro del consumidor vuelve a fallar con un error retryable, el consumidor llama a channel.nack(msg, false, true) después de un backoff. requeue=true le dice a RabbitMQ que vuelva a poner el mensaje en la cola para otro intento (potencialmente por el mismo consumidor después del backoff u otro si hubiera más instancias).
    • Si el procesamiento falla con un error no retryable (ej: datos inválidos en el mensaje, saldo insuficiente persistente), el consumidor llama a channel.ack(msg) para eliminar el mensaje de la cola y evitar bucles infinitos. El error se registra en los logs del order_service. Este mensaje se consideraría "perdido" en términos de procesamiento automático y requeriría intervención manual o una estrategia de "Dead Letter Queue" (no implementada actualmente).