Mensajería y streaming de eventos
ActiveMQ Classic 6: instalación, activemq.xml y configuración de transportes
Intermedio10 min de lecturaActualizado
Esta página se tradujo del inglés con ayuda de IA y se cotejó con el original. Leer la versión en inglés
ActiveMQ Classic es el veterano broker JMS del proyecto Apache ActiveMQ. Esta guía te lleva de un archivo recién descargado a un broker que entiendes: la estructura de directorios, las secciones de activemq.xml que importan, los transport connectors y los protocolos, los límites de uso del sistema y el producer flow control, las URL de failover en el cliente, los brokers embebidos y las redes de brokers, señalando además en qué se diferencia ActiveMQ Artemis.
Lo que aprenderás
- Instalar y ejecutar ActiveMQ Classic 6 como servicio en Linux y como contenedor
- Leer y modificar con soltura las secciones principales de conf/activemq.xml
- Configurar transport connectors para clientes OpenWire, AMQP, STOMP, MQTT y WebSocket
- Fijar los límites de memoria, almacenamiento y espacio temporal, y entender el producer flow control
- Escribir URL de conexión con failover y ajustar el prefetch de los clientes
- Conectar brokers en red y saber cuándo Artemis es la mejor opción
Apache ActiveMQ Classic es un broker JMS maduro escrito en Java: lo descargas, lo arrancas y a los pocos segundos tienes colas, topics, una consola web y listeners para cinco protocolos. La sencillez es real, pero llevarlo a producción depende de un puñado de decisiones de configuración en conf/activemq.xml que los valores por defecto no toman por ti. Esta guía cubre la rama 6.x, que requiere Java 17 y usa la API jakarta.jms, y señala dónde ActiveMQ Artemis —el otro broker del proyecto— hace las cosas de otra manera.
Instalar el broker
ActiveMQ se distribuye como un único archivo que contiene el broker, sus bibliotecas y una consola web. No hay instalador ni base de datos que preparar.
Instala un JDK 17 o posterior. En Debian o Ubuntu, sudo apt install openjdk-17-jre-headless; en sistemas de la familia RHEL, sudo dnf install java-17-openjdk-headless. Compruébalo con java -version.
Crea un usuario de servicio. Ejecuta el broker con una cuenta sin privilegios: sudo useradd --system --home /opt/activemq --shell /usr/sbin/nologin activemq.
Descarga y descomprime. Baja el tarball binario de la página de descargas de Apache, verifica su firma o su checksum, descomprímelo en /opt y mantén un enlace simbólico independiente de la versión, de modo que actualizar consista en cambiar ese enlace:
curl -LO https://archive.apache.org/dist/activemq/6.1.6/apache-activemq-6.1.6-bin.tar.gz
sudo tar -xzf apache-activemq-6.1.6-bin.tar.gz -C /opt
sudo ln -sfn /opt/apache-activemq-6.1.6 /opt/activemq
sudo chown -R activemq:activemq /opt/apache-activemq-6.1.6
Arráncalo una vez en primer plano. /opt/activemq/bin/activemq console ejecuta el broker enganchado a tu terminal para que puedas leer el log de arranque. Deberías ver líneas que anuncian el store KahaDB, cada transport connector (tcp://0.0.0.0:61616, amqp://..., etc.) y la consola web en http://0.0.0.0:8161/. Detenlo con Ctrl+C.
Ejecútalo como servicio de systemd. Crea /etc/systemd/system/activemq.service y después ejecuta sudo systemctl daemon-reload && sudo systemctl enable --now activemq.
[Unit]
Description=Apache ActiveMQ Classic
After=network-online.target
Wants=network-online.target
[Service]
Type=forking
User=activemq
Group=activemq
Environment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
ExecStart=/opt/activemq/bin/activemq start
ExecStop=/opt/activemq/bin/activemq stop
PIDFile=/opt/activemq/data/activemq.pid
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Para contenedores, el proyecto publica una imagen oficial; con una sola línea obtienes un broker con los mismos valores por defecto que el tarball:
docker run -d --name activemq \
-p 61616:61616 -p 8161:8161 -p 5672:5672 \
-v activemq-data:/opt/apache-activemq/data \
apache/activemq-classic:6.1.6
Monta también /opt/apache-activemq/conf si quieres aportar tu propio activemq.xml, y monta siempre el directorio de datos: sin volumen, todos los mensajes persistentes se pierden al eliminar el contenedor.
Qué trae la distribución
Conocer la estructura ahorra tiempo cuando algo falla.
| Directorio | Contenido |
|---|---|
bin/ | El script de arranque activemq (Linux/macOS) y activemq.bat; env contiene las opciones de la JVM, como el tamaño del heap |
conf/ | activemq.xml (broker), jetty.xml y jetty-realm.properties (consola web), login.config, users.properties, groups.properties (seguridad JAAS), log4j2.properties |
data/ | El store KahaDB, activemq.log, el fichero PID; es el directorio que hay que respaldar y poner en un disco rápido |
lib/ | Los jars del broker y del cliente; activemq-all-*.jar lo agrupa todo para pruebas rápidas de cliente |
webapps/ | La consola web de administración y el endpoint REST de Jolokia |
examples/ | Productores y consumidores de ejemplo para cada protocolo |
El script de arranque tiene más subcomandos que start y stop. bin/activemq status indica si el broker está en marcha; bin/activemq bstat muestra estadísticas del broker; dstat lista los destinos con su tamaño de cola y sus contadores de enqueue y dequeue; browse --amqurl tcp://localhost:61616 orders.placed muestra los mensajes de una cola sin consumirlos; purge vacía una; producer y consumer generan y consumen tráfico de prueba. Estos comandos hablan con el broker por JMX o JMS, así que también funcionan contra brokers remotos.
El tamaño del heap se define en bin/env como ACTIVEMQ_OPTS_MEMORY="-Xms64M -Xmx1G". Sube el máximo para cargas reales y ten en cuenta que el límite memoryUsage del propio broker (más abajo) es una fracción de ese heap.
Cómo leer activemq.xml
El fichero de configuración es un contexto de aplicación de Spring en XML, y por eso puede definir data sources y otros beans junto al broker. El broker en sí es el elemento <broker>; sus hijos son las secciones que vas a editar.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://activemq.apache.org/schema/core
http://activemq.apache.org/schema/core/activemq-core.xsd">
<broker xmlns="http://activemq.apache.org/schema/core"
brokerName="orders-broker"
dataDirectory="${activemq.data}"
useJmx="true"
advisorySupport="false"> <!-- advisory topics cost CPU; enable only if you use them -->
<destinationPolicy>
<policyMap>
<policyEntries>
<!-- ">" matches every queue; "orders.>" would match orders.placed, orders.paid ... -->
<policyEntry queue=">" producerFlowControl="true" memoryLimit="64mb">
<deadLetterStrategy>
<individualDeadLetterStrategy queuePrefix="DLQ." useQueueForQueueMessages="true"/>
</deadLetterStrategy>
</policyEntry>
<policyEntry topic=">" producerFlowControl="true">
<pendingMessageLimitStrategy>
<constantPendingMessageLimitStrategy limit="1000"/>
</pendingMessageLimitStrategy>
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"/>
</persistenceAdapter>
<systemUsage>
<systemUsage sendFailIfNoSpace="true">
<memoryUsage><memoryUsage percentOfJvmHeap="70"/></memoryUsage>
<storeUsage><storeUsage limit="20 gb"/></storeUsage>
<tempUsage><tempUsage limit="5 gb"/></tempUsage>
</systemUsage>
</systemUsage>
<transportConnectors>
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnections=1000"/>
<transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=1000"/>
<transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnections=1000"/>
<transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnections=1000"/>
</transportConnectors>
</broker>
<import resource="jetty.xml"/> <!-- web console on 8161 -->
</beans>
Tres atributos de <broker> importan desde el principio. brokerName identifica al broker en JMX, en los logs y en las redes de brokers: dale a cada broker uno único. dataDirectory es donde viven el store, el scheduler y el fichero de bloqueo. advisorySupport controla los advisory messages, topics internos en los que el broker publica eventos como «consumidor conectado» o «mensaje caducado»; son útiles para las herramientas pero añaden sobrecarga, y muchos equipos los desactivan.
Políticas de destino
<destinationPolicy> aplica ajustes por destino según un patrón de nombre. El comodín > abarca todo lo que cuelga de un prefijo y * corresponde a un solo segmento del nombre, así que orders.* cubre orders.placed pero no orders.eu.placed. Las políticas pueden fijar límites de memoria, estrategias de dead letter, el comportamiento del dispatch y, en los topics, cuántos mensajes puede acumular un suscriptor no durable lento antes de que se descarten los más antiguos: es el pendingMessageLimitStrategy de arriba, que evita que un suscriptor atascado agote la memoria del broker.
La individualDeadLetterStrategy del ejemplo sustituye la única cola compartida ActiveMQ.DLQ por una cola de dead letter por cada cola de origen (DLQ.orders.placed), lo que facilita muchísimo rastrear el origen de los poison messages.
Uso del sistema y producer flow control
<systemUsage> establece tres techos: memory para los mensajes retenidos en RAM, store para el store persistente en disco y temp para los mensajes no persistentes volcados a disco. Cuando un destino o el broker alcanzan un techo, entra en juego el producer flow control: el broker deja de confirmar los envíos en los destinos afectados y los productores síncronos se bloquean hasta que los consumidores liberan espacio. Así el broker no se queda sin memoria, pero para la aplicación que envía parece un cuelgue. sendFailIfNoSpace="true" convierte ese cuelgue en una ResourceAllocationException que el productor puede gestionar; producerFlowControl="false" en una política desactiva el mecanismo para ese destino y deja que los mensajes se desborden al store, que es lo habitual en colas de las que se espera que acumulen backlog.
Transport connectors y protocolos
Cada <transportConnector> abre un socket de escucha para un protocolo. El esquema de la URI selecciona el protocolo y la query string lo ajusta.
| Esquema | Protocolo | Clientes típicos | Notas |
|---|---|---|---|
tcp:// | OpenWire, el protocolo binario nativo de ActiveMQ | Java (activemq-client), .NET (NMS), C++ (CMS) | El más rápido para JMS; un thread por conexión |
nio:// | OpenWire sobre Java NIO | Los mismos | Menos threads para muchas conexiones; preferible a gran escala |
ssl://, nio+ssl:// | OpenWire sobre TLS | Los mismos | Configura un keystore en <sslContext> |
amqp:// | AMQP 1.0 | Qpid JMS, Apache Qpid Proton, .NET, Go | Interoperabilidad basada en estándares |
stomp:// | STOMP | Lenguajes de script, pruebas rápidas | Protocolo de texto; stomp+nio para escalar |
mqtt:// | MQTT 3.1.1 | Dispositivos IoT, móviles | Los QoS 0/1/2 se corresponden con los modos de entrega |
ws://, wss:// | STOMP o MQTT sobre WebSocket | Navegadores | Combínalo con un cliente STOMP en JavaScript |
vm:// | Dentro de la JVM | Tests, uso embebido | Sin socket; el broker arranca con la primera conexión |
Entre las opciones útiles están maximumConnections (rechazar conexiones nuevas por encima de un límite en vez de agotar los threads), wireFormat.maxFrameSize (rechazar mensajes demasiado grandes antes de que lleguen a memoria; el valor por defecto es 100 MB), transport.soTimeout y transport.keepAlive. Escucha en 0.0.0.0 solo cuando el broker deba ser accesible desde otros hosts, y pon los listeners detrás de un firewall: un broker expuesto a Internet con las credenciales por defecto es un patrón de incidente bien conocido.
URL de cliente: failover y prefetch
Los clientes configuran muchas cosas a través de la URL de conexión, y hay dos opciones que deberían estar en toda URL de producción.
El failover transport envuelve una o varias direcciones de broker y se reconecta de forma transparente cuando cae la conexión, reenviando los envíos no confirmados y restableciendo los consumidores:
ConnectionFactory factory = new ActiveMQConnectionFactory(
"failover:(tcp://broker-a:61616,tcp://broker-b:61616)"
+ "?randomize=false" // try broker-a first, broker-b as backup
+ "&initialReconnectDelay=100"
+ "&maxReconnectDelay=10000" // back off to at most 10 s between attempts
+ "&maxReconnectAttempts=-1" // keep trying forever (the default)
+ "&timeout=3000"); // fail a send after 3 s without a broker instead of blocking
Incluso con un solo broker, failover:(tcp://broker:61616) merece la pena: sin él, un reinicio del broker deja a cada cliente con una conexión muerta y una excepción que tiene que gestionar por su cuenta.
El prefetch es el número de mensajes que el broker envía a un consumidor por adelantado, antes de recibir su confirmación. El valor por defecto de 1000 para colas maximiza el rendimiento con consumidores rápidos, pero es un error con consumidores lentos: uno acapara mil mensajes mientras sus nueve compañeros están ociosos, y si se cae, hay que volver a entregar esos mil. Ajústalo por conexión, tcp://broker:61616?jms.prefetchPolicy.queuePrefetch=10, o por destino, orders.placed?consumer.prefetchSize=1. Un prefetch de 1 da un reparto estrictamente equitativo a costa de un viaje de ida y vuelta por mensaje; entre 10 y 50 es un término medio habitual para trabajos que tardan decenas de milisegundos.
Brokers embebidos para tests
El transporte vm:// arranca un broker dentro de la JVM del primer cliente que se conecta, lo que hace que los tests de integración sean autosuficientes:
// Fully in-memory: no KahaDB directory, no ports, gone when the JVM exits
ConnectionFactory factory =
new ActiveMQConnectionFactory("vm://test?broker.persistent=false&broker.useJmx=false");
El spring-boot-starter-activemq de Spring Boot solía embeber un broker automáticamente; desde Boot 3.x ese soporte ha desaparecido, así que apunta spring.activemq.broker-url a una URL vm:// para los tests o, mejor aún, a un contenedor arrancado con Testcontainers, para que los tests recorran la ruta de red real.
Redes de brokers
Una red de brokers conecta varios brokers para que un mensaje producido en uno pueda consumirse desde otro. Cada broker mantiene su propio store y sus propios clientes; un <networkConnector> reenvía mensajes a un par solo cuando ese par tiene un consumidor para el destino, de modo que la red reacciona a la demanda en lugar de replicarlo todo.
<networkConnectors>
<!-- On broker-a: forward to broker-b when broker-b has interested consumers.
duplex="true" carries traffic both ways over this single connector. -->
<networkConnector name="a-to-b"
uri="static:(tcp://broker-b:61616)"
duplex="true"
networkTTL="1"
decreaseNetworkConsumerPriority="true">
<excludedDestinations>
<queue physicalName="internal.>"/>
</excludedDestinations>
</networkConnector>
</networkConnectors>
Las redes permiten escalar consumidores y conectar sedes, pero no dan alta disponibilidad: un mensaje almacenado en broker-a no está disponible mientras broker-a esté caído. En Classic, la alta disponibilidad se consigue con un store compartido (KahaDB sobre almacenamiento compartido o un store JDBC), con un broker activo y brokers en espera aguardando el bloqueo; la guía de persistencia lo explica. decreaseNetworkConsumerPriority="true" mantiene los mensajes en local cuando existe un consumidor local, y networkTTL limita cuántos saltos puede dar un mensaje: déjalo en 1 salvo que tengas una topología multisalto deliberada, porque los mensajes que rebotan entre brokers son un dolor de cabeza a la hora de depurar.
En qué se diferencia Artemis
Si vas a empezar un despliegue nuevo, evalúa ActiveMQ Artemis antes de quedarte con Classic por inercia. Las diferencias que afectan a la puesta en marcha son:
- La configuración está en
etc/broker.xml, yartemis creategenera un directorio de instancia del broker separado de la instalación. - El modelo de direcciones tiene addresses, queues y routing types (anycast para punto a punto, multicast para pub-sub) en lugar de destinos JMS asignados directamente a colas y topics.
- La persistencia usa un journal append-only hecho a medida (con backend NIO o Linux AIO) en lugar de KahaDB.
- La alta disponibilidad viene integrada: parejas live/backup replicadas o con store compartido, y connection routers.
- AMQP 1.0 es un protocolo de primera clase, con su propia federación y su propio mirroring entre brokers.
- El rendimiento suele ser mayor, y tanto WildFly como el broker AMQ de Red Hat se basan en él.
Classic sigue siendo la respuesta correcta cuando ya tienes herramientas de operación, políticas en activemq.xml, clientes OpenWire o dashboards JMX, o cuando dependes de funcionalidades exclusivas de Classic como los virtual topics y las composite destinations. Ambos se mantienen activamente.
Errores habituales
- Dejar
admin/adminen la consola web. Cambiaconf/jetty-realm.propertiesantes de que el broker sea accesible desde algo que no sea localhost, y restringe el puerto 8161 a una red de operaciones. - Usar el heap por defecto con una carga real. El
-Xmxde 1 GB debin/envcombinado conpercentOfJvmHeap="70"deja poco margen; dimensiona el heap a conciencia y vigila el porcentaje de memoria usado. - Clientes sin URL de failover. Un reinicio del broker se convierte en un incidente de la aplicación. Envuelve cada URL en
failover:( ). - Prefetch de 1000 con consumidores lentos. Los síntomas son un consumidor saturado y compañeros ociosos, y grandes ráfagas de reentregas tras una caída. Baja el prefetch.
- Desactivar el producer flow control sin límites. Sin flow control y sin límite de store, un productor puede llenar el disco. Mantén
storeUsagey configura alertas sobre él. - Tratar una red de brokers como replicación. Es enrutamiento, no redundancia. Usa un store compartido para el failover.
Ideas clave
- ActiveMQ Classic 6 necesita Java 17, habla OpenWire, AMQP 1.0, STOMP, MQTT y WebSocket, y se ejecuta desde un tarball, una unidad de systemd o la imagen de contenedor oficial.
conf/activemq.xmles un fichero Spring XML; las partes que importan son<destinationPolicy>,<persistenceAdapter>,<systemUsage>y<transportConnectors>.- Los límites de memoria, almacenamiento y espacio temporal activan el producer flow control; decide para cada destino si los productores deben bloquearse, fallar o desbordarse a disco.
- La resiliencia de los clientes viene de las URL
failover:( ), y el reparto equitativo, de un prefetch razonable; no de los ajustes del broker. - Una red de brokers enruta mensajes entre brokers bajo demanda; la alta disponibilidad requiere un store compartido.
- Artemis es el otro broker del proyecto, con una configuración y un modelo de direcciones distintos; prefiérelo para despliegues nuevos de alto rendimiento o en clúster, y Classic cuando las herramientas y funcionalidades existentes te atan a él.
Fuentes y lecturas recomendadas
Preguntas frecuentes
- ¿Qué versión de Java necesita ActiveMQ Classic 6?
- Java 17 o posterior. La rama 6.x pasó a la vez al espacio de nombres jakarta.jms y a Java 17, así que encaja con servidores Jakarta EE 10 y con Spring Boot 3.2+. Si estás atado a Java 8 u 11 o a clientes javax.jms, quédate en la rama 5.18: sigue mantenida, pero ya no recibirá funcionalidades nuevas.
- ¿Qué diferencia hay entre ActiveMQ Classic y ActiveMQ Artemis?
- Son dos brokers distintos dentro del mismo proyecto Apache. Classic es la base de código original, con activemq.xml, KahaDB y OpenWire. Artemis desciende de HornetQ: usa otro modelo de direcciones, su propio journal y una configuración broker.xml, y trata AMQP 1.0 como protocolo de primera clase; es el que embebe WildFly. Ambos implementan JMS. Classic es la opción segura cuando ya tienes configuración y herramientas; Artemis es la mejor plataforma para despliegues nuevos, de alto rendimiento o en clúster.
- ¿Por qué mi productor se queda colgado y nunca vuelve de send()?
- Casi siempre es el producer flow control. El destino o el broker alcanzó su límite de memoria, almacenamiento o espacio temporal y el broker dejó de confirmar los envíos, así que un productor síncrono se bloquea. Busca en la consola web los destinos con un porcentaje de memoria alto, añade consumidores o sube los límites, y activa sendFailIfNoSpace si prefieres una excepción a un bloqueo.
- ¿Puedo ejecutar ActiveMQ dentro de mi aplicación en lugar de como servidor aparte?
- Sí. El transporte vm:// arranca un broker embebido dentro de la JVM con la primera conexión; añade ?broker.persistent=false para tener un broker puramente en memoria. Es ideal para tests de integración y pequeñas herramientas de un solo nodo, pero un broker de producción compartido debe ejecutarse en su propio proceso, con su propio ciclo de vida y su propia monitorización.