Solución de problemas comunes de configuración de RabbitMQ

Encuentra y corrige errores de intercambios, colas, enlaces, confirmaciones y permisos en RabbitMQ sin perseguir pistas falsas.

Solución de problemas comunes de configuración de RabbitMQ

La mayoría de los problemas de configuración de RabbitMQ parecen errores de aplicación al principio. Un publicador dice que envió el mensaje. Un consumidor dice que nunca lo vio. El gráfico de la cola está vacío, o peor, está lleno y nadie sabe por qué. La forma más rápida de salir es dejar de adivinar y seguir la ruta del mensaje: publicador, intercambio, enlace, cola, consumidor, confirmación.

RabbitMQ es estricto con la topología. Un intercambio directo no "coincide aproximadamente" con una clave de enrutamiento. Una cola declarada como exclusiva no se comportará como una cola de trabajo compartida. Un mensaje publicado como obligatorio puede ser devuelto, mientras que el mismo mensaje no enrutable sin mandatory puede simplemente ser descartado por el intercambio. Estos detalles son pequeños hasta que te cuestan una tarde.

Comienza con la ruta real

Para una publicación AMQP normal, el productor envía un mensaje a un intercambio con una clave de enrutamiento. El intercambio usa su tipo y enlaces para decidir qué colas deben recibir el mensaje. Luego, los consumidores obtienen entregas de las colas y las confirman después de procesarlas.

Cuando un mensaje desaparece, hazte cuatro preguntas:

  • ¿Publicó el productor en el intercambio y el host virtual que crees?
  • ¿Existe ese intercambio y es del tipo que crees?
  • ¿Hay un enlace desde ese intercambio a la cola prevista?
  • ¿Coincide la clave de enrutamiento con ese enlace para el tipo de intercambio?

Suena básico, pero detecta muchos incidentes reales. Los entornos de staging y producción a menudo usan diferentes hosts virtuales. Un script de despliegue puede declarar orders.created en un entorno y order.created en otro. Una cola puede estar enlazada a un patrón de tema que omite una palabra extra.

Usa la interfaz de administración o CLI para inspeccionar el broker en vivo, no el código que esperas que se esté ejecutando:

rabbitmqctl list_exchanges name type durable auto_delete
rabbitmqctl list_bindings source_name source_kind destination_name destination_kind routing_key
rabbitmqctl list_queues name durable auto_delete exclusive messages_ready messages_unacknowledged consumers

Si usas múltiples hosts virtuales, incluye -p:

rabbitmqctl -p production list_bindings source_name destination_name routing_key

Desajustes de clave de enrutamiento

Los intercambios directos requieren una coincidencia exacta de la clave de enlace. Si una cola está enlazada con invoice.created, un mensaje publicado con invoices.created no llegará. RabbitMQ no corregirá pluralización, mayúsculas, puntos o guiones.

Los intercambios de tema usan * para una palabra y # para cero o más palabras. El separador de palabras es un punto. Un enlace de logs.* coincide con logs.info, pero no con logs.app.info. Un enlace de logs.# coincide con ambos.

Un truco útil para la resolución de problemas es agregar una cola de diagnóstico temporal con un enlace amplio, luego publicar un mensaje de prueba conocido:

rabbitmqadmin declare queue name=debug.routing durable=false auto_delete=true
rabbitmqadmin declare binding source=events destination=debug.routing destination_type=queue routing_key='#'

Haz esto con cuidado en producción y elimina el enlace de diagnóstico cuando termines. El objetivo es demostrar si los mensajes están llegando al intercambio en absoluto.

Para publicadores importantes, habilita las devoluciones de publicador con el indicador mandatory para que los mensajes no enrutables sean visibles para el publicador. Las confirmaciones de publicador te indican que el broker aceptó la publicación; las devoluciones te indican que el intercambio no pudo enrutarlo a ninguna cola. Responden preguntas diferentes.

Errores de tipo de intercambio

Los cambios de tipo de intercambio son una fuente común de confusión porque declarar un intercambio existente con propiedades diferentes falla. Si un servicio declara events como topic y otro declara events como direct, la segunda declaración debería obtener un error de condición previa.

Ese error es bueno. Evita que dos aplicaciones estén en desacuerdo silenciosamente sobre el enrutamiento. La solución no es capturar e ignorar la excepción. La solución es hacer que la propiedad de la topología sea clara. Por lo general, un paso de despliegue o un módulo de infraestructura debe declarar los intercambios y colas compartidos, mientras que las aplicaciones solo declaran colas de respuesta privadas o afirman idempotentemente la topología esperada.

Los intercambios de tipo fanout ignoran las claves de enrutamiento. Los intercambios de tipo headers enrutan por cabeceras, no por claves de enrutamiento. Si tu mensaje de prueba tiene la clave de enrutamiento correcta pero ninguna cola lo recibe, verifica el tipo de intercambio antes de editar cada enlace.

Propiedades de cola que sorprenden a la gente

Durable significa que la definición de la cola sobrevive a un reinicio del broker. No significa que todos los mensajes dentro de la cola sobrevivan. Para que los mensajes sobrevivan a un reinicio, la cola debe ser durable y el mensaje debe publicarse como persistente. Incluso entonces, los publicadores deben usar confirmaciones si necesitan saber cuándo RabbitMQ ha aceptado el mensaje de forma segura.

Las colas de auto-eliminación se eliminan después de que su último consumidor desaparece. Son útiles para suscripciones temporales, pero son una mala opción para colas de trabajo compartidas. Las colas exclusivas están limitadas a la conexión que las declara y desaparecen cuando esa conexión se cierra. Son útiles para colas de respuesta y consumidores privados, no para múltiples instancias de trabajadores.

Si una cola parece "desaparecer aleatoriamente", verifica estas banderas:

rabbitmqctl list_queues name durable auto_delete exclusive consumers

También verifica si el código de la aplicación declara la cola al inicio con argumentos diferentes a los de la cola existente. RabbitMQ trata los argumentos de la cola, como el tipo de cola, el intercambio de mensajes fallidos, la longitud máxima y algunas configuraciones relacionadas con la durabilidad, como parte del contrato de declaración. Una discrepancia puede cerrar el canal con un error de condición previa.

Los mensajes están listos, pero los consumidores no hacen nada

Si messages_ready es alto y consumers es cero, RabbitMQ está esperando. La aplicación consumidora puede estar caída, conectada al host virtual incorrecto, usando el nombre de cola incorrecto o bloqueada por permisos.

Si los consumidores están conectados pero no se realizan entregas, verifica la capacidad de prefetch y consumidor:

rabbitmqctl list_consumers queue_name channel_pid consumer_tag ack_required prefetch_count active

Un consumidor con confirmaciones manuales y una ventana de prefetch completa no recibirá más mensajes hasta que confirme o rechace algunos de los mensajes que ya tiene. Esto a menudo parece que RabbitMQ dejó de entregar, cuando en realidad el consumidor está reteniendo trabajo no confirmado.

Si messages_unacknowledged es alto, mira los registros del consumidor y los sistemas posteriores. Una base de datos lenta, una dependencia HTTP atascada o un manejador que captura excepciones sin confirmar pueden crear un muro de mensajes no confirmados.

Errores de confirmación

Las confirmaciones manuales son la opción normal para un procesamiento confiable. El consumidor debe confirmar solo después de que el trabajo esté completo. Si falla, debe rechazar o no confirmar con una decisión deliberada de reencolar.

El patrón peligroso es auto_ack=true para trabajos que pueden fallar. Con confirmaciones automáticas, RabbitMQ considera el mensaje manejado tan pronto como se entrega. Si el consumidor se cae después de recibirlo, el mensaje desaparece de la cola.

El error opuesto es nunca confirmar. El consumidor procesa el mensaje con éxito, tal vez incluso escribe en una base de datos, pero olvida basic_ack. RabbitMQ mantiene la entrega no confirmada hasta que el canal se cierra, luego la reenvía. Eso crea trabajo duplicado y recuentos crecientes de no confirmados.

Una forma simple de manejador es más fácil de auditar:

def handle(ch, method, properties, body):
    try:
        process(body)
    except RetryableError:
        ch.basic_nack(method.delivery_tag, requeue=True)
    except Exception:
        ch.basic_nack(method.delivery_tag, requeue=False)
    else:
        ch.basic_ack(method.delivery_tag)

Si reencolas cada fallo para siempre, un mensaje malo puede repetirse infinitamente. Usa un intercambio de mensajes fallidos o un diseño de reintento para mensajes venenosos.

Permisos y hosts virtuales

Los permisos de RabbitMQ están limitados por host virtual. Un usuario puede conectarse pero aún así carecer de permisos de configuración, escritura o lectura para una cola o intercambio. Eso puede aparecer como una excepción de canal en los registros del cliente, no siempre como un error de aplicación amigable.

Verifica los permisos directamente:

rabbitmqctl list_permissions -p production
rabbitmqctl list_user_permissions app_user

Para un servicio que solo publica, otorga permisos de escritura al patrón de intercambio que necesita y evita derechos de configuración amplios. Para un consumidor, otorga lectura en la cola y escritura si necesita publicar no confirmaciones en rutas de mensajes fallidos o usar patrones de respuesta. Los permisos demasiado amplios facilitan la resolución de problemas hoy y dificultan las revisiones de seguridad mañana.

Errores de configuración de mensajes fallidos

Los intercambios de mensajes fallidos están destinados a hacer visibles los fallos. Una configuración incorrecta de mensajes fallidos hace lo contrario: los mensajes fallan, se rechazan y luego desaparecen en un intercambio que no tiene enlace.

Verifica los argumentos de la cola, no solo el nombre de la cola:

rabbitmqctl list_queues name arguments

Para una cola que debe enviar trabajos fallidos a mensajes fallidos, deberías ver argumentos como x-dead-letter-exchange y, a veces, x-dead-letter-routing-key. Luego inspecciona ese intercambio y sus enlaces de la misma manera que inspeccionas la ruta principal.

Un error común es configurar un intercambio de mensajes fallidos llamado jobs.dlx pero enlazar la cola de mensajes fallidos a jobs.failed en un intercambio diferente. Otro es establecer x-dead-letter-routing-key en un valor que ningún enlace coincide. RabbitMQ enrutará el mensaje de mensajes fallidos a través del intercambio de mensajes fallidos como cualquier otra publicación. Si nada coincide, el mensaje no tiene un lugar útil al que ir.

Las colas de reintento necesitan el mismo cuidado. Si construyes reintentos con TTL más mensajes fallidos, dibuja la ruta en papel:

cola principal -> rechazar -> intercambio de reintento -> cola de reintento -> TTL expira -> intercambio principal -> cola principal

Luego verifica cada intercambio, cola, enlace y clave de enrutamiento. Los bucles de reintento son fáciles de crear accidentalmente. Pon un límite en los intentos en las cabeceras de los mensajes o en el estado de la aplicación para que una carga útil rota no gire para siempre.

Sorpresas de políticas

Las políticas pueden cambiar el comportamiento de la cola sin que el código de la aplicación lo mencione. Una política puede establecer el tipo de cola, la longitud máxima, TTL, intercambio de mensajes fallidos u otros argumentos opcionales. Eso es útil para operaciones, pero puede confundir la depuración cuando una cola se comporta de manera diferente a la declaración del código.

Enumera las políticas durante la resolución de problemas:

rabbitmqctl list_policies

Mira el patrón y la prioridad. Una política amplia como .* puede afectar colas creadas más tarde por equipos no relacionados. Si una cola está descartando mensajes más antiguos, verifica la configuración de max-length o desbordamiento. Si los mensajes expiran antes de lo esperado, verifica el TTL a nivel de cola y la expiración por mensaje.

Cuando la aplicación declara un conjunto de argumentos y una política aplica otro, las reglas de RabbitMQ dependen de la configuración. Algunos argumentos opcionales pueden ser controlados por política; otros deben coincidir con la declaración. El hábito operativo seguro es mantener el comportamiento de la cola en un lugar obvio y documentar cualquier política que anule intencionalmente los valores predeterminados de la aplicación.

Cuando productores y consumidores declaran topología

Muchas bibliotecas cliente facilitan que cada servicio declare intercambios, colas y enlaces al inicio. Eso puede ser conveniente en desarrollo. En producción, puede crear problemas de propiedad.

Si tanto el productor como el consumidor declaran la misma cola, deben estar de acuerdo en cada propiedad importante. Si un despliegue cambia una cola de auto-eliminación a durable, o cambia un argumento de mensajes fallidos, el siguiente servicio en iniciar puede fallar con un error de condición previa. Eso es mejor que la deriva silenciosa, pero aún puede romper un despliegue.

Para topología compartida, prefiere un propietario: Terraform, Ansible, un trabajo de migración o un servicio claramente responsable. El inicio de la aplicación aún puede afirmar que la topología esperada existe, pero no debe crear casualmente colas compartidas con valores predeterminados que nadie revisó.

La topología privada es diferente. Un servicio que crea una cola de respuesta temporal o una cola de suscripción exclusiva puede poseer esa cola directamente. La diferencia es si otro servicio depende del nombre y comportamiento de la cola.

Mantén una ruta de publicación conocida y funcional

Para sistemas importantes, mantén un pequeño publicador de diagnóstico o un comando de runbook que envíe un mensaje inofensivo a través del intercambio, clave de enrutamiento y host virtual esperados. Debe usar la misma clase de credenciales que la aplicación real, o al menos un conjunto de permisos lo suficientemente cercano como para detectar problemas de enrutamiento y acceso.

Esa ruta conocida y funcional es útil durante los despliegues. Si el mensaje de diagnóstico se enruta pero el mensaje de la aplicación no, compara la clave de enrutamiento real, las cabeceras y el host virtual de la aplicación. Si el mensaje de diagnóstico también falla, el problema es probablemente topología, permisos o estado del broker.

Una lista de verificación práctica para incidentes

Cuando los mensajes faltan o están atascados, recopila el estado en vivo antes de reiniciar todo:

rabbitmqctl -p production list_queues name messages_ready messages_unacknowledged consumers state
rabbitmqctl -p production list_bindings source_name destination_name routing_key
rabbitmqctl -p production list_exchanges name type durable
rabbitmqctl -p production list_connections name user vhost state

Luego envía un mensaje de prueba conocido con un ID único y rastréalo a través de los registros. No pruebes con un mensaje de producción aleatorio cuyo camino ya no está claro.

La mayoría de los problemas de configuración de RabbitMQ no son misteriosos una vez que alineas la topología declarada con el comportamiento del publicador y consumidor. El broker generalmente está haciendo exactamente lo que se le dijo. El trabajo es encontrar el lugar donde lo que se le dijo difiere de lo que el equipo quería.