Escalando RabbitMQ: Guía para Optimizar Topologías de Clúster

Diseña clústeres de RabbitMQ que escalen sin confundir clustering, replicación y rendimiento.

Escalando RabbitMQ: Guía para Optimizar Topologías de Clúster

Escalar RabbitMQ comienza con un hecho incómodo: un clúster no es un broker mágicamente más grande. Es un conjunto de brokers que comparten metadatos y, dependiendo del tipo de cola, pueden replicar datos de la cola. Si una sola cola está sobrecargada, agregar nodos a su alrededor puede mejorar la disponibilidad, pero no hará automáticamente que esa cola consuma más rápido.

Esa distinción ahorra muchos diseños defectuosos. He visto equipos agregar dos nodos a una implementación ocupada de RabbitMQ, sin mover nada, sin cambiar el diseño de las colas, y preguntarse por qué la misma cola sigue acumulándose cada tarde. El líder de la cola seguía en el mismo nodo. Los mismos consumidores seguían haciendo el mismo trabajo. El clúster tenía más máquinas, pero el cuello de botella no se había movido.

La topología del clúster de RabbitMQ se trata principalmente de decidir dónde viven las colas, cuántas copias de mensajes importantes necesitas y cuánta falla puedes tolerar antes de que el rendimiento disminuya. La respuesta correcta para un pipeline de métricas de corta duración no es la misma que para pagos, cumplimiento de pedidos o eventos de auditoría.

Lo que el clustering realmente comparte

Los nodos de RabbitMQ en un clúster comparten definiciones: hosts virtuales, usuarios, permisos, exchanges, colas, bindings, políticas y metadatos de tiempo de ejecución necesarios para que el clúster funcione. Un productor conectado a un nodo puede publicar en un exchange cuyo líder de cola está en otro nodo. Un consumidor puede conectarse a un nodo diferente del que aloja la cola.

Eso no significa que cada mensaje exista en todas partes.

Las colas clásicas tienen un líder en un nodo. Las colas de quórum tienen un líder más réplicas. Los streams tienen su propio modelo de replicación. Si un líder de cola está remoto de la mayoría de los clientes que lo usan, RabbitMQ tiene que mover tráfico a través del interconecto del clúster. Eso está bien con moderación. Se vuelve costoso cuando cada editor se conecta al nodo A, cada cola activa vive en el nodo B y cada consumidor se conecta al nodo C.

Una primera regla simple funciona bien: conecta las aplicaciones a nodos que estén cerca de las colas que usan, o coloca un balanceador de carga frente al clúster y verifica que los líderes de las colas estén razonablemente equilibrados. No asumas que las conexiones de clientes round-robin crean una carga round-robin en las colas.

Prefiere tres nodos antes de ponerte creativo

Para la mayoría de los clústeres de producción de RabbitMQ, tres nodos en un grupo de baja latencia o zona de disponibilidad es el punto de partida limpio. Proporciona a las colas de quórum un modelo mayoritario que puede sobrevivir a la falla de un nodo, y mantiene la coordinación del clúster lo suficientemente simple como para razonar durante un incidente.

Los clústeres de dos nodos parecen más baratos, pero son incómodos para colas replicadas. Con sistemas basados en quórum, se requiere una mayoría. Si uno de los dos nodos desaparece, no hay mayoría. Puedes agregar un tercer nodo de tipo testigo en algunos sistemas distribuidos, pero para RabbitMQ suele ser más simple y confiable ejecutar tres nodos reales con suficiente capacidad de disco y red.

Cinco nodos pueden tener sentido cuando tienes muchas colas, necesitas más opciones de ubicación o quieres distribuir la carga en más máquinas. También aumenta la cantidad de comunicación del clúster y la superficie operativa. Antes de pasar de tres a cinco, verifica si estás resolviendo la saturación de nodos o un problema de diseño de colas. Si una cola está caliente, más nodos por sí solos no dividirán el trabajo de esa cola.

Las colas de quórum son para confiabilidad replicada, no para velocidad gratuita

Para nuevas cargas de trabajo de alta disponibilidad, las colas de quórum suelen ser el valor predeterminado correcto cuando la durabilidad de los mensajes importa. Replican mensajes usando un protocolo de consenso. Una cola de quórum con tres miembros puede seguir operando si un miembro no está disponible, siempre que una mayoría se mantenga saludable.

La compensación es el costo de escritura. Un mensaje persistente publicado debe replicarse a suficientes miembros antes de considerarse aceptado de manera segura. Eso es exactamente lo que quieres para trabajos importantes, pero no es el mismo perfil de rendimiento que una cola clásica transitoria.

Declara una cola de quórum con un argumento o política, dependiendo de cómo tu aplicación gestione la topología:

rabbitmqadmin declare queue name=orders durable=true arguments='{"x-queue-type":"quorum"}'

Para las políticas, delimítalas cuidadosamente. No conviertas accidentalmente cada cola en un host virtual en una cola de quórum solo porque un patrón amplio coincidió con .*. Un buen nombre de política y un prefijo de cola estrecho son aburridos de la mejor manera:

rabbitmqctl set_policy qq-orders '^orders\.' '{"queue-type":"quorum"}' --apply-to queues

Si estás migrando desde colas clásicas reflejadas, trátalo como una migración, no como un cambio de bandera. Las colas clásicas reflejadas y las colas de quórum se comportan de manera diferente en cuanto a ordenamiento, mensajes venenosos, uso de memoria y conmutación por error. Crea el nuevo tipo de cola, enruta un segmento controlado de tráfico, observa las confirmaciones y la latencia del consumidor, luego mueve el resto.

Las colas clásicas todavía tienen un lugar

Las colas clásicas siguen siendo útiles para cargas de trabajo donde no se requiere replicación, donde los mensajes son transitorios o donde la cola es local para un servicio y se puede reconstruir desde otra fuente. También son una opción razonable para eventos de alto volumen y bajo valor donde perder algunos mensajes durante una falla de nodo es aceptable.

Usa colas clásicas deliberadamente. Si una cola clásica es duradera y recibe mensajes persistentes, esos mensajes se almacenan en el nodo que aloja esa cola. Si ese nodo está caído, la cola no está disponible hasta que el nodo regrese. Eso puede estar bien para un trabajo de reconciliación en segundo plano. Por lo general, no está bien para el estado de pedidos visible para el cliente.

Para acumulaciones largas, considera si la carga de trabajo debería ser un stream o un sistema de almacenamiento diferente. RabbitMQ puede mantener colas, pero una cola con millones de mensajes antiguos suele ser una señal de que los consumidores son insuficientes, los sistemas posteriores están fallando o el proceso de negocio necesita semántica de reproducción en lugar de semántica de cola.

Pon límites de latencia alrededor del clúster

El clustering de RabbitMQ espera enlaces de red de baja latencia y confiables. Extender un clúster a través de regiones distantes suele ser un mal equilibrio. El tráfico entre nodos se vuelve más lento, la conmutación por error se vuelve más difícil de predecir, y una partición de red puede ser más dañina que la interrupción que intentabas evitar.

Un diseño práctico es un clúster de RabbitMQ por región, con enrutamiento a nivel de aplicación o federation/shovel entre regiones cuando necesitas movimiento entre regiones. Eso mantiene la publicación y el consumo local rápidos. También hace que los dominios de falla sean claros: si la región A no está saludable, la región B no se arrastra al mismo problema de membresía del clúster.

Multi-AZ dentro de una región es diferente. Si la latencia entre zonas es baja y estable, tres nodos en tres zonas pueden funcionar bien. Pruébalo bajo carga real. El hecho de que un proveedor de nube llame a algo una zona de disponibilidad no te dice cómo se comportarán tus tamaños de mensaje, confirmaciones y colas de quórum durante una hora ocupada.

Equilibra los líderes de cola, no solo los nodos

Un clúster puede verse equilibrado en el gráfico de CPU y aún estar muy sesgado a nivel de cola. Un nodo puede ser dueño de los líderes de las colas más ocupadas mientras que los otros principalmente tienen réplicas tranquilas.

Verifica la ubicación de las colas:

rabbitmqctl list_queues name type leader members messages_ready messages_unacknowledged

Si un nodo posee la mayoría de los líderes activos, mueve o reequilibra las colas usando las herramientas compatibles de RabbitMQ para tu versión y tipo de cola. Para colas de quórum, la ubicación de los miembros y la ubicación del líder importan. Para colas clásicas, la ubicación del maestro de la cola importa en terminología más antigua, aunque las versiones más nuevas usan el lenguaje de líder de manera más consistente.

Una buena topología distribuye colas activas no relacionadas entre nodos. Por ejemplo, email.send, image.resize y billing.capture no deberían ser lideradas por el mismo nodo si cada una tiene tráfico pesado. Si billing.capture es la única cola activa, divídela por un shard de negocio real como grupo de comerciantes o región solo si los consumidores pueden procesar esos shards de manera independiente de forma segura.

Diseña el comportamiento de conexión del cliente

La ubicación de la conexión del cliente es parte de la topología. Si cada aplicación se conecta al primer resultado de DNS para siempre, un nodo puede llevar la mayor parte del tráfico de clientes incluso cuando las colas están distribuidas en el clúster. Un balanceador de carga puede ayudar, pero debe usar verificaciones de salud que entiendan si un nodo está realmente disponible para tráfico AMQP.

Mantén las conexiones de larga duración. RabbitMQ puede manejar muchas conexiones, pero la rotación de conexiones consume CPU, memoria, descriptores de archivo y sobrecarga de TLS. Una solicitud web no debería abrir una nueva conexión AMQP, publicar un mensaje y cerrarla. Usa un pool de conexiones o canales apropiado para la biblioteca del cliente.

También decide qué hacen los clientes durante una falla de nodo. Los buenos clientes se reconectan con backoff, reabren canales, redeclaran la topología privada si es necesario y reanudan las confirmaciones o el consumo cuidadosamente. Los malos clientes se reconectan en bucles ajustados y convierten un reinicio de nodo en una tormenta de conexiones.

Para los consumidores, piensa en la localidad pero evita el sobreajuste. Conectar un consumidor al mismo nodo que su líder de cola puede reducir el tráfico entre nodos, pero los líderes de cola pueden moverse después de fallas. El consumidor debería sobrevivir a eso sin cambios manuales.

Las particiones son eventos operativos, no solo configuraciones

Las particiones de red son donde los diagramas de clúster se ponen a prueba. RabbitMQ tiene modos de manejo de particiones, pero ninguna configuración elimina la necesidad de una decisión operativa clara. Si dos lados de un clúster no pueden hablar, debes decidir si la disponibilidad o la consistencia importa más para esa carga de trabajo.

Las colas de quórum requieren una mayoría de réplicas. Ese es el punto: un lado minoritario no debería seguir aceptando escrituras que no pueden ser acordadas de manera segura. Esto puede sorprender a los equipos que esperaban que cada nodo sobreviviente permaneciera escribible. Planifícalo. Coloca los miembros de la cola de quórum donde una mayoría pueda sobrevivir a las fallas que te importan.

No distribuyas una cola de quórum de tres nodos en tres regiones distantes y esperes un comportamiento suave durante la latencia normal de internet. El quórum solo será tan agradable como la red entre los miembros. La baja latencia y la baja pérdida de paquetes son requisitos de capacidad, no algo agradable de tener.

Realiza simulacros de partición en un entorno que no sea de producción. Bloquea el tráfico entre nodos, observa qué colas permanecen disponibles, observa cómo se reconectan los clientes y anota los pasos de recuperación. La primera vez que aprendas el comportamiento de tu partición no debería ser durante un incidente de red real.

Escala consumidores antes de escalar brokers

Cuando el síntoma es una cola en crecimiento, el broker no siempre es el cuello de botella. A menudo los consumidores son simplemente más lentos que los publicadores. Antes de agregar nodos de RabbitMQ, verifica la utilización del consumidor, los recuentos no confirmados, el tiempo de procesamiento y la latencia descendente.

Si los mensajes están listos pero no confirmados, RabbitMQ tiene mensajes esperando y los consumidores no los están tomando lo suficientemente rápido. Agrega consumidores, corrige la prefetch o elimina retrasos descendentes. Si los mensajes están mayormente no confirmados, los consumidores ya han recibido trabajo y están tardando demasiado en confirmarlo. Agregar nodos broker no hará que esos manejadores sean más rápidos.

La prefetch importa aquí. Una prefetch de 500 en un trabajador lento puede ocultar un backlog dentro de los procesos del consumidor. Una prefetch de 1 en un trabajador local rápido puede perder tiempo en viajes de ida y vuelta. Comienza con un valor pequeño, mide la latencia de extremo a extremo y la memoria del consumidor, luego ajusta.

Vigila los límites aburridos

Los planes de escalado a menudo hablan de topología y olvidan los descriptores de archivo, las alarmas de disco, las alarmas de memoria, la rotación de conexiones y los recuentos de canales. RabbitMQ es sensible a todos ellos.

Para cada nodo, monitorea la memoria utilizada, el disco libre, los descriptores de archivo, los sockets, la memoria del proceso de la cola, las tasas de mensajes, la latencia de confirmación y la utilización del planificador de Erlang. En el lado del cliente, monitorea los bucles de reconexión y las tasas de creación de canales. Un servicio que abre una nueva conexión para cada publicación puede dañar un clúster mucho antes de que el volumen de mensajes se vea impresionante.

Usa conexiones y canales de larga duración donde tu biblioteca de cliente los admita. Coloca límites de conexión y configuraciones de heartbeat en el diseño, no en un cambio de pánico durante una interrupción.

Una topología que generalmente funciona

Para una aplicación empresarial típica, comenzaría con tres nodos de RabbitMQ en una región, distribuidos en zonas si la red es buena. Usa colas de quórum para flujos de trabajo duraderos importantes. Usa colas clásicas para trabajo transitorio donde el comportamiento de falla sea aceptable. Mantén a los publicadores y consumidores cerca del clúster. Usa un balanceador de carga para el acceso del cliente, pero verifica el equilibrio del líder de la cola en lugar de asumir que el balanceador de carga resolvió la ubicación del broker.

Luego prueba los casos feos: mata un nodo, pausa un grupo de consumidores, llena una cola, ralentiza el disco y reinicia un publicador. Escalar RabbitMQ se trata menos del diagrama más bonito y más de saber qué sucede cuando el diagrama está bajo estrés.