Mensageria e streaming de eventos

ActiveMQ Classic 6: instalação, activemq.xml e configuração de transportes

Intermediário10 min de leituraAtualizado

Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês

O ActiveMQ Classic é o veterano broker JMS do projeto Apache ActiveMQ. Este guia leva você de um arquivo recém-baixado a um broker que você entende: a estrutura de diretórios, as seções do activemq.xml que importam, os transport connectors e os protocolos, os limites de uso do sistema e o producer flow control, as URLs de failover no cliente, os brokers embarcados e as redes de brokers — apontando também onde o ActiveMQ Artemis é diferente.

O que você vai aprender

  • Instalar e executar o ActiveMQ Classic 6 como serviço no Linux e como contêiner
  • Ler e editar com segurança as principais seções do conf/activemq.xml
  • Configurar transport connectors para clientes OpenWire, AMQP, STOMP, MQTT e WebSocket
  • Definir os limites de memória, armazenamento e espaço temporário e entender o producer flow control
  • Escrever URLs de conexão com failover e ajustar o prefetch dos clientes
  • Conectar brokers em rede e saber quando o Artemis é a melhor escolha

O Apache ActiveMQ Classic é um broker JMS maduro escrito em Java: você baixa, inicia e, poucos segundos depois, tem filas, tópicos, um console web e listeners para cinco protocolos. A simplicidade é real, mas colocá-lo em produção depende de um punhado de decisões de configuração no conf/activemq.xml que os valores padrão não tomam por você. Este guia cobre a linha 6.x, que exige Java 17 e usa a API jakarta.jms, e aponta onde o ActiveMQ Artemis — o outro broker do projeto — faz as coisas de outro jeito.

Instalando o broker

O ActiveMQ é distribuído como um único arquivo compactado com o broker, suas bibliotecas e um console web. Não há instalador nem banco de dados para preparar.

Instale um JDK 17 ou mais recente. No Debian ou Ubuntu, sudo apt install openjdk-17-jre-headless; em sistemas da família RHEL, sudo dnf install java-17-openjdk-headless. Confira com java -version.

Crie um usuário de serviço. Rode o broker com uma conta sem privilégios: sudo useradd --system --home /opt/activemq --shell /usr/sbin/nologin activemq.

Baixe e descompacte. Baixe o tarball binário da página de downloads da Apache, verifique a assinatura ou o checksum, descompacte em /opt e mantenha um link simbólico independente da versão, para que uma atualização se resuma a trocar esse link:

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

Inicie uma vez em primeiro plano. /opt/activemq/bin/activemq console roda o broker preso ao seu terminal, para você acompanhar o log de inicialização. Você deve ver linhas anunciando o store KahaDB, cada transport connector (tcp://0.0.0.0:61616, amqp://... e assim por diante) e o console web em http://0.0.0.0:8161/. Pare com Ctrl+C.

Rode como serviço do systemd. Crie /etc/systemd/system/activemq.service e depois execute 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 contêineres, o projeto publica uma imagem oficial; uma única linha já entrega um broker com os mesmos padrões do 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

Monte também /opt/apache-activemq/conf se quiser fornecer seu próprio activemq.xml, e monte sempre o diretório de dados: sem um volume, todas as mensagens persistentes se perdem quando o contêiner é removido.

O que vem na distribuição

Conhecer a estrutura economiza tempo quando algo dá errado.

DiretórioConteúdo
bin/O script de inicialização activemq (Linux/macOS) e activemq.bat; env guarda as opções da JVM, como o tamanho do heap
conf/activemq.xml (broker), jetty.xml e jetty-realm.properties (console web), login.config, users.properties, groups.properties (segurança JAAS), log4j2.properties
data/O store KahaDB, activemq.log, o arquivo de PID; é o diretório que você deve incluir no backup e colocar em disco rápido
lib/Os jars do broker e do cliente; activemq-all-*.jar reúne tudo para testes rápidos de cliente
webapps/O console web de administração e o endpoint REST do Jolokia
examples/Producers e consumers de exemplo para cada protocolo

O script de inicialização tem mais subcomandos do que start e stop. bin/activemq status informa se o broker está rodando; bin/activemq bstat mostra estatísticas do broker; dstat lista os destinos com o tamanho da fila e os contadores de enqueue e dequeue; browse --amqurl tcp://localhost:61616 orders.placed exibe as mensagens de uma fila sem consumi-las; purge esvazia uma fila; producer e consumer geram e consomem tráfego de teste. Esses comandos falam com o broker via JMX ou JMS, então também funcionam contra brokers remotos.

O tamanho do heap fica em bin/env, como ACTIVEMQ_OPTS_MEMORY="-Xms64M -Xmx1G". Aumente o máximo para cargas reais e lembre-se de que o limite memoryUsage do próprio broker (veja abaixo) é uma fração desse heap.

Lendo o activemq.xml

O arquivo de configuração é um application context do Spring em XML, e é por isso que ele pode definir data sources e outros beans ao lado do broker. O broker em si é o elemento <broker>; seus filhos são as seções que você vai 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&amp;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>

Três atributos de <broker> importam logo de cara. brokerName identifica o broker no JMX, nos logs e nas redes de brokers — dê a cada broker um nome único. dataDirectory é onde ficam o store, o scheduler e o arquivo de lock. advisorySupport controla as advisory messages, tópicos internos nos quais o broker publica eventos como "consumer conectado" ou "mensagem expirada"; elas são úteis para ferramentas, mas adicionam overhead, e muitas equipes as desativam.

Políticas de destino

<destinationPolicy> aplica configurações por destino de acordo com um padrão de nome. O curinga > abrange tudo abaixo de um prefixo e * corresponde a um único segmento do nome, então orders.* cobre orders.placed, mas não orders.eu.placed. As políticas podem definir limites de memória, estratégias de dead letter, o comportamento do dispatch e, para tópicos, quantas mensagens um assinante não durável lento pode acumular antes que as mais antigas sejam descartadas — é o pendingMessageLimitStrategy acima, que impede um assinante travado de esgotar a memória do broker.

A individualDeadLetterStrategy mostrada substitui a fila única e compartilhada ActiveMQ.DLQ por uma fila de dead letter para cada fila de origem (DLQ.orders.placed), o que torna muito mais fácil rastrear a origem das poison messages.

Uso do sistema e producer flow control

<systemUsage> define três tetos: memory para as mensagens mantidas em RAM, store para o store persistente em disco e temp para as mensagens não persistentes despejadas em disco. Quando um destino ou o broker atinge um teto, o producer flow control entra em ação: o broker para de confirmar os envios nos destinos afetados, e os producers síncronos ficam bloqueados até que os consumers liberem espaço. Isso evita que o broker fique sem memória, mas, para a aplicação que envia, parece um travamento. sendFailIfNoSpace="true" transforma esse travamento em uma ResourceAllocationException que o producer pode tratar; producerFlowControl="false" em uma política desativa o mecanismo para aquele destino e deixa as mensagens transbordarem para o store, que é a escolha usual para filas que devem acumular backlog.

Transport connectors e protocolos

Cada <transportConnector> abre um socket de escuta para um protocolo. O esquema da URI seleciona o protocolo e a query string o ajusta.

EsquemaProtocoloClientes típicosObservações
tcp://OpenWire, o protocolo binário nativo do ActiveMQJava (activemq-client), .NET (NMS), C++ (CMS)O mais rápido para JMS; uma thread por conexão
nio://OpenWire sobre Java NIOOs mesmosMenos threads para muitas conexões; prefira em escala
ssl://, nio+ssl://OpenWire sobre TLSOs mesmosConfigure uma keystore em <sslContext>
amqp://AMQP 1.0Qpid JMS, Apache Qpid Proton, .NET, GoInteroperabilidade baseada em padrões
stomp://STOMPLinguagens de script, testes rápidosProtocolo de texto; stomp+nio para escalar
mqtt://MQTT 3.1.1Dispositivos IoT, mobileOs QoS 0/1/2 correspondem aos modos de entrega
ws://, wss://STOMP ou MQTT sobre WebSocketNavegadoresUse com um cliente STOMP em JavaScript
vm://Dentro da JVMTestes, uso embarcadoSem socket; o broker sobe na primeira conexão

Entre as opções úteis estão maximumConnections (recusar novas conexões acima de um limite em vez de esgotar as threads), wireFormat.maxFrameSize (rejeitar mensagens grandes demais antes que cheguem à memória; o padrão é 100 MB), transport.soTimeout e transport.keepAlive. Faça bind em 0.0.0.0 só quando o broker precisar ser acessível de outros hosts, e coloque os listeners atrás de um firewall — um broker exposto à internet com as credenciais padrão é um padrão de incidente bem conhecido.

URLs do cliente: failover e prefetch

Os clientes configuram muita coisa pela URL de conexão, e duas opções devem estar em toda URL de produção.

O failover transport envolve um ou mais endereços de broker e reconecta de forma transparente quando a conexão cai, reenviando os envios não confirmados e restabelecendo os consumers:

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

Mesmo com um único broker, failover:(tcp://broker:61616) vale a pena: sem ele, um restart do broker deixa cada cliente com uma conexão morta e uma exceção que ele mesmo precisa tratar.

O prefetch é o número de mensagens que o broker empurra para um consumer antes da confirmação. O padrão de 1000 para filas maximiza o throughput com consumers rápidos, mas é inadequado para os lentos: um consumer monopoliza mil mensagens enquanto seus nove pares ficam ociosos, e uma queda obriga a reentregar essas mil. Ajuste por conexão, tcp://broker:61616?jms.prefetchPolicy.queuePrefetch=10, ou por destino, orders.placed?consumer.prefetchSize=1. Um prefetch de 1 garante uma distribuição estritamente justa ao custo de uma ida e volta por mensagem; de 10 a 50 é um meio-termo comum para processamentos que levam dezenas de milissegundos.

Brokers embarcados para testes

O transporte vm:// sobe um broker dentro da JVM do primeiro cliente que se conecta, o que torna os testes de integração autossuficientes:

// 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");

O spring-boot-starter-activemq do Spring Boot costumava embarcar um broker automaticamente; desde o Boot 3.x esse suporte não existe mais, então aponte spring.activemq.broker-url para uma URL vm:// nos testes ou, melhor ainda, para um contêiner iniciado pelo Testcontainers, para que os testes exercitem o caminho de rede real.

Redes de brokers

Uma rede de brokers conecta vários brokers para que uma mensagem produzida em um possa ser consumida a partir de outro. Cada broker mantém seu próprio store e seus próprios clientes; um <networkConnector> só encaminha mensagens para um par quando esse par tem um consumer para o destino, de modo que a rede reage à demanda em vez de replicar tudo.

<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>

Redes servem para escalar consumers e conectar localidades, mas não oferecem alta disponibilidade: uma mensagem armazenada no broker-a fica indisponível enquanto o broker-a estiver fora do ar. No Classic, a alta disponibilidade vem de um store compartilhado (KahaDB em armazenamento compartilhado ou um store JDBC), com um broker ativo e brokers em standby aguardando o lock, como mostra o guia de persistência. decreaseNetworkConsumerPriority="true" mantém as mensagens locais quando existe um consumer local, e networkTTL limita quantos saltos uma mensagem pode dar — deixe em 1, a menos que você tenha uma topologia multi-hop deliberada, porque mensagens que ficam quicando entre brokers são um pesadelo para depurar.

Onde o Artemis é diferente

Se você está começando uma implantação nova, avalie o ActiveMQ Artemis antes de adotar o Classic por padrão. As diferenças que afetam a configuração inicial são:

  • A configuração fica em etc/broker.xml, e artemis create gera um diretório de instância do broker separado da instalação.
  • O modelo de endereços tem addresses, queues e routing types (anycast para ponto a ponto, multicast para pub-sub) em vez de destinos JMS mapeados diretamente em filas e tópicos.
  • A persistência usa um journal append-only feito sob medida (com backend NIO ou Linux AIO) em vez do KahaDB.
  • A alta disponibilidade é nativa: pares live/backup replicados ou com store compartilhado, e connection routers.
  • O AMQP 1.0 é um protocolo de primeira classe, com federação e mirroring próprios entre brokers.
  • O throughput costuma ser maior, e tanto o WildFly quanto o broker AMQ da Red Hat são baseados nele.

O Classic continua sendo a resposta certa quando suas ferramentas de operação, políticas no activemq.xml, clientes OpenWire ou dashboards JMX já existem, ou quando você depende de recursos exclusivos do Classic, como virtual topics e composite destinations. Os dois são mantidos ativamente.

Erros comuns

  • Deixar admin/admin no console web. Altere conf/jetty-realm.properties antes que o broker fique acessível de qualquer lugar além de localhost, e restrinja a porta 8161 a uma rede de operações.
  • Rodar com o heap padrão em uma carga real. O -Xmx de 1 GB do bin/env, combinado com percentOfJvmHeap="70", deixa pouca folga; dimensione o heap de forma deliberada e monitore o percentual de memória usado.
  • Nenhuma URL de failover nos clientes. Um restart do broker vira um incidente da aplicação. Envolva toda URL em failover:( ).
  • Prefetch de 1000 com consumers lentos. Os sintomas são um consumer sobrecarregado e pares ociosos, além de grandes rajadas de reentrega depois de uma queda. Reduza o prefetch.
  • Desativar o producer flow control sem limites. Sem flow control e sem limite de store, um producer pode encher o disco. Mantenha o storeUsage e configure alertas sobre ele.
  • Tratar uma rede de brokers como replicação. É roteamento, não redundância. Use um store compartilhado para failover.

Principais pontos

  • O ActiveMQ Classic 6 exige Java 17, fala OpenWire, AMQP 1.0, STOMP, MQTT e WebSocket, e roda a partir de um tarball, de uma unit do systemd ou da imagem de contêiner oficial.
  • conf/activemq.xml é um arquivo Spring XML; as partes que importam são <destinationPolicy>, <persistenceAdapter>, <systemUsage> e <transportConnectors>.
  • Os limites de memória, armazenamento e espaço temporário disparam o producer flow control; decida, destino a destino, se os producers devem bloquear, falhar ou transbordar para o disco.
  • A resiliência dos clientes vem das URLs failover:( ), e a distribuição justa, de um prefetch sensato — não das configurações do broker.
  • Uma rede de brokers roteia mensagens entre brokers sob demanda; alta disponibilidade exige um store compartilhado.
  • O Artemis é o outro broker do projeto, com configuração e modelo de endereços diferentes; prefira-o para implantações novas de alto throughput ou em cluster, e o Classic quando as ferramentas e os recursos existentes prendem você a ele.

Fontes e leituras complementares

Perguntas frequentes

Qual versão do Java o ActiveMQ Classic 6 exige?
Java 17 ou mais recente. A linha 6.x migrou ao mesmo tempo para o namespace jakarta.jms e para o Java 17, então combina com servidores Jakarta EE 10 e com o Spring Boot 3.2+. Se você está preso ao Java 8 ou 11 ou a clientes javax.jms, fique na linha 5.18: ela continua mantida, mas não vai receber funcionalidades novas.
Qual é a diferença entre o ActiveMQ Classic e o ActiveMQ Artemis?
São dois brokers distintos dentro do mesmo projeto Apache. O Classic é a base de código original, com activemq.xml, KahaDB e OpenWire. O Artemis descende do HornetQ: usa outro modelo de endereços, um journal próprio e uma configuração broker.xml, e trata o AMQP 1.0 como protocolo de primeira classe; é ele que o WildFly embarca. Os dois implementam JMS. O Classic é a escolha segura quando você já tem configuração e ferramentas; o Artemis é a plataforma mais indicada para implantações novas, de alto throughput ou em cluster.
Por que meu producer trava e nunca retorna de send()?
Quase sempre é o producer flow control. O destino ou o broker atingiu seu limite de memória, armazenamento ou espaço temporário e o broker parou de confirmar os envios, então um producer síncrono fica bloqueado. Procure no console web os destinos com percentual de memória alto, adicione consumers ou aumente os limites, e ative sendFailIfNoSpace se preferir uma exceção a um travamento.
Posso rodar o ActiveMQ dentro da minha aplicação em vez de como servidor separado?
Sim. O transporte vm:// sobe um broker embarcado dentro da JVM na primeira conexão; adicione ?broker.persistent=false para ter um broker puramente em memória. É ideal para testes de integração e pequenas ferramentas de nó único, mas um broker de produção compartilhado deve rodar em processo próprio, com ciclo de vida e monitoramento próprios.