For Java developers, ActiveMQ has been the go-to message broker, majorly due to its tight integration with the Java ecosystem. However, the rising prominence of RabbitMQ intrigued many in the Java community, and now they are asking…
What is RabbitMQ? And how does it stack up against ActiveMQ?
This blog delves into a head-to-head comparison between ActiveMQ and RabbitMQ across four areas:
- History and Background
- Architecture and Protocol Support
- Message Persistence Implementation
- Clustering and High-Availability Support
ActiveMQ vs RabbitMQ: History and Background
Fundamentally, all message brokers facilitate the exchange of messages in a distributed system (commonly). Regardless, each one has its unique origin story, driven by different needs and visions.
Understanding these origins transcends historical interest; it's about grasping the core strengths and purposes of each broker. Here, we take a scenic route through what spurred the creation of ActiveMQ and RabbitMQ. What challenges were they designed to overcome? And how are they being utilized today?
ActiveMQ originated from the need for an open-source message broker that implements the Java Message Service (JMS) specification. But what is the JMS specification — you might ask?
The emergence of distributed Java applications brought the need for inter-service communication between Java applications. Initially, services relied on various proprietary messaging systems, each with its own API. This made it difficult for developers to switch between messaging systems or integrate them — they needed a standardized way to handle messaging between and within Java applications — Enter JMS.
The JMS is an API, a set of interfaces and associated semantics that define how a JMS client can create, send, receive, and read messages. Essentially, it’s a specification that standardizes messaging between applications in the Java ecosystem.
From its inception, ActiveMQ aims to be an alternative to the proprietary JMS providers, making enterprise-level messaging accessible to a broader audience. By implementing the JMS specification, ActiveMQ addressed several challenges in the messaging domain. For example:
- ActiveMQ standardized how Java applications handle messaging.
- Additionally, after its creation, ActiveMQ evolved to support various protocols beyond JMS, like AMQP 1.0 and MQTT, to cater to a wider range of use cases.
ActiveMQ continues to be a popular choice, particularly in environments heavily invested in Java. Its feature-rich nature makes it suitable for enterprise applications. The introduction of ActiveMQ Artemis as a sub-project provides a modern spin to ActiveMQ, promising more advanced features and performance improvements.
RabbitMQ on the other hand, isn’t a JMS-focused broker — From the outset, RabbitMQ adopted the emerging Advanced Message Queuing Protocol (AMQP), which aimed to standardize messaging across different platforms and languages.
By fully embracing the AMQP standard from the beginning, RabbitMQ sought to solve several key problems early on. Some of these problems are, but not limited to:
- Interoperability: RabbitMQ ensured seamless messaging between systems of different types and origins.
- Scalability and Flexibility: RabbitMQ shipped with several solutions for easy scaling and adaptability in diverse messaging use cases — from small startups to large enterprises, from point-to-point to pub-sub.
- Reliability: RabbitMQ provides robust message delivery guarantees, crucial for critical business processes.
Today, RabbitMQ is widely adopted by numerous industries, from telecommunications, and e-commerce to IoT. Its use cases range from acting as a buffer for background job processing to being an integral part of microservices architectures for event-driven systems.
Its ongoing development, including support for various messaging protocols like MQTT and STOMP, keeps it relevant and versatile in the ever-evolving tech ecosystem.
Having explored the distinct origins and motivations behind ActiveMQ and RabbitMQ, let's dive into their architecture and the protocols they support. This will help us understand how their unique histories influence their current design and functionality.
ActiveMQ vs RabbitMQ: Architecture and Protocol Support
Understanding the architecture of a message broker and the protocols it supports is key to evaluating its fit for specific applications. In light of that, this section investigates the architecture of ActiveMQ and RabbitMQ, the protocols they support as well as the messaging domains they cover.
Generally, both ActiveMQ and RabbitMQ follow the common broker architecture involving producers, the broker itself, and consumers:
- Producers send messages to the broker.
- The broker manages these messages, deciding where they should be routed.
- Consumers connect to the broker to receive messages.
Note that we abstracted most of the details here - For example, what messaging resources does each broker uses — Queues? Topics? Or? We will cover that in the ‘messaging domains’ section. But first, let’s take a detour and examine the different protocols that each broker supports.
TLDR: ActiveMQ initially a JMS-centric broker, has, with its Artemis iteration, moved towards a more protocol-agnostic architecture. RabbitMQ, on the other hand, starting with a strong focus on AMQP, has broadened its reach to support IoT and JMS applications. Let’s dive into these exciting shifts.
ActiveMQ, started as a JMS provider, fully implementing the JMS API. However, since JMS is just an API and not a messaging protocol, ActiveMQ needed its own protocol for the actual work of sending messages. Enter OpenWire, ActiveMQ's native protocol, which essentially takes JMS commands and turns them into real messages that can be sent and received.
As technology evolved, so did ActiveMQ. It expanded to support popular protocols like:
- AMQP 1.0(AMQP 0-9-1 not supported), MQTT, STOMP, etc.
- WebSockets: Adding this meant ActiveMQ could work smoothly with web applications too, using the above protocols over WebSockets.
While ActiveMQ Artemis, the new flavour of ActiveMQ, still supports JMS, it is less about JMS and more about being open to different ways of messaging. It uses a core protocol, a kind of universal language for the broker, no matter what the external message format might be. The core API lets different types of clients talk to Artemis without needing to know JMS.
RabbitMQ on the other hand, was designed with AMQP as its core protocol and right now, RabbitMQ primarily supports AMQP 0-9-1. It also supports AMQP 1.0 via a plugin. Additionally, over time, RabbitMQ has evolved to support:
- MQTT and STOMP: Through plugins, RabbitMQ can handle lightweight IoT messages (MQTT) and simpler text-based messaging (STOMP).
- MQTT and STOMP Over WebSockets: Through plugins, RabbitMQ can accept MQTT and STOMP frames wrapped in WebSocket packets. While RabbitMQ does not support AMQP over WebSockets natively, you can implement this in RabbitMQ with CloudAMQP’s websocket-tcp-relay.
- JMS Support: RabbitMQ also supports JMS via a plugin, allowing integration with JMS-based applications.
TLDR: While both ActiveMQ and RabbitMQ address the three common messaging domains — Point-to-Point, Publish-Subscribe, and Request-Reply — they each have their unique ways of handling these. Let's explore these differences…
ActiveMQ supports three core-messaging domains:
- and Request-Reply
In ActiveMQ Classic, the older flavour of ActiveMQ, each supported messaging protocol and API has its own unique set of messaging resources: JMS utilizes queues and topics for point-to-point and publish-subscribe patterns, respectively. Meanwhile, MQTT employs topics for its publish-subscribe pattern.
ActiveMQ Artemis takes a different approach. It doesn't separate messaging resources for each supported protocol and API. Instead, it uses a flexible system called addresses and queues.
An address in Artemis can link to several queues. Here's how it works:
- For Point-to-Point: A message sent to an address gets routed to one of the linked queues, and then one consumer receives it (anycast).
- For Publish-Subscribe: A message goes to an address and then gets sent to all linked queues, so multiple consumers subscribed to these queues get the message (multicast).
RabbitMQ like ActiveMQ, also supports all the three common messaging domains. It uses a combination of exchanges and queues to support these messaging domains:
- Point-to-Point: Where a single consumer consumes a message sent by a producer.
- Publish-Subscribe: Where a message sent by a producer is broadcasted to multiple consumers formally called subscribers.
- Request-Reply Pattern: Where a producer sends a message/request to the broker and waits for a response from the consumer. This is usually achieved with the reply-to headers.
RabbitMQ's flexibility in accommodating different messaging patterns largely comes from its use of exchanges, which are responsible for routing messages to the appropriate queues based on routing keys and binding patterns.
One of RabbitMQ’s defining feature is its use of exchanges, a concept that is non-present in ActiveMQ. While ActiveMQ's address-to-queue system bears resemblance to RabbitMQ's exchange-to-queue system, RabbitMQ's exchanges offer a more robust and flexible approach. Let me show you how...
RabbitMQ distinguishes itself from ActiveMQ with its versatile exchange system, offering a nuanced approach to message routing that goes beyond ActiveMQ's anycast and multicast address-to-queue system. RabbitMQ's exchanges come in several types, each designed for specific routing behaviors, adding a layer of flexibility not seen in ActiveMQ's model.
- Direct Exchange: Routes messages to queues based on a message routing key exactly matching the queue binding key. It's ideal for direct and precise message delivery.
- Fanout Exchange: Ignores the routing key and broadcasts messages to all bound queues, making it perfect for multicast scenarios where a message needs to reach multiple recipients.
- Topic Exchange: Allows for complex routing logic based on wildcard matches between the routing key and the queue binding pattern. This is particularly useful for selectively routing messages based on multiple criteria.
- Headers Exchange: Uses message header attributes for routing decisions, providing a high degree of routing flexibility and customization.
The list of exchanges above, is not exhaustive nor thorough. You can read our blog on RabbitMQ Exchanges, Bindings, and Routing Keys to learn more.
In contrast, ActiveMQ primarily employs the anycast (point-to-point) and multicast (publish-subscribe) patterns for addressing queues. While effective, these patterns do not offer the same level of routing specificity and flexibility found in RabbitMQ’s exchange types.
RabbitMQ's exchange system generally allows a more sophisticated and tailored routing strategies . By extension, this makes RabbitMQ a powerful tool in scenarios requiring complex routing logic and diverse message distribution requirements.
Now that we've covered how ActiveMQ and RabbitMQ are built and the protocols they work with, let's dissect how each broker manages and stores messages, a key factor that significantly influences their performance, reliability, and overall suitability for various applications.
ActiveMQ vs RabbitMQ: Message Persistence
TLDR: Both ActiveMQ and RabbitMQ offer mechanisms for message storage, however, they approach it differently. ActiveMQ adopts a more configurable approach to message persistence, allowing choices between database and file-based options. RabbitMQ, on the other hand, primarily supports file-based storage techniques. Let's interrogate these varying approaches.
ActiveMQ Message Persistence
ActiveMQ adopts a configurable approach to message persistence, offering both file-based and database storage options. This flexibility allows users to choose a storage solution that best fits their specific requirements, whether it's for performance, scalability, or integration with existing systems.
File-Based Storage Options
In ActiveMQ Classic, the two popular file-based storage options are AMQ and KahaDB. However, KahaDB is generally recommended and widely used due to its efficiency and reliability — so we will focus on that here.KahaDB Message Store
KahaDB is a file-based, transactional store. KahaDB uses journal files to persist messages. A transactional journal in ActiveMQ usually comprises of data log files, a single index file, and an in-memory cache.
The in-memory cache holds messages temporarily if there are active consumer(s) for the messages. The data log acts as a message journal, which consists of a rolling log of messages and commands stored in data files of a certain length. Messages in the data log are referenced in index files, which use a BTree structure for efficient indexing by message ID.
Database-Based Storage Options
ActiveMQ's pluggable message store API supports various database implementations for message persistence. One common approach is using JDBC, with Apache Derby being the default JDBC driver in ActiveMQ. However, ActiveMQ is compatible with nearly any database that provides a JDBC driver, including but not limited to MySQL, PostgreSQL, Oracle, and SQL Server.
While ActiveMQ Artemis, the newest flavor of ActiveMQ still supports the JDBC message store option, it steps up the game by replacing the KahaDB and AMQ message store with a more advanced file journal.
This new journal is designed for better performance and efficiency. For more details on the file journal in ActiveMQ Artemis, you can refer to the official documentation.
RabbitMQ Message Persistence
RabbitMQ on the other hand, primarily stores messages on disk. But generally speaking, there are two categories of data that RabbitMQ persists:
- The server's metadata, like queue definitions, exchange definitions, and bindings
- The actual messages published to the queues
Metadata Persistence with Mnesia DB
RabbitMQ uses Mnesia DB, an Erlang-based distributed data store, for persisting its metadata. This includes information about the queues, exchanges, and bindings set up in the RabbitMQ server. It's worth noting that in RabbitMQ 4.0, Mnesia will be replaced by Khepri, a raft-based storage engine.
Message Persistence in RabbitMQ Queues
For message persistence, RabbitMQ offers three types of queues, each with its unique storage mechanism: Classic Queues (versions 1 and 2), Quorum Queues, and Stream Queues.Classic Queues (Version 2)
Classic queues in RabbitMQ use an on-disk message store to persist actual message payloads. Alongside the message store, there's an on-disk index for each queue. This index tracks the location of messages in the message store and their position in the queue.
In version 2 of classic queues, RabbitMQ introduces a per-queue message store. Smaller messages are typically written to this store, while larger messages go to the shared message store.
In quorum queues, a shared Write-Ahead-Log (WAL), also called a journal file, is used on each node to persist all operations, including new messages. This log captures actions as they happen.
The operations stored in the WAL are kept in memory and simultaneously written to disk. When the current WAL file reaches a certain size (default 512 MiB), it's flushed to a segment file on disk, and the memory used by those log entries is released. These segment files are compacted over time, especially as consumers acknowledge deliveries.
Stream queues persist messages using fixed-size segment files on disk. Each message published to a stream queue goes into these segment files.
Once a segment file reaches its predefined size limit (default 500,000,000 bytes), it's closed in favor of a new one. This approach keeps file sizes manageable and optimizes access and retrieval times.
Each stream queue maintains an index, tracking the location of messages within these segment files.
It is also worth highlighting here that RabbitMQ stands out from ActiveMQ with its variety of queue types, each tailored for specific messaging requirements. This diversity is a key aspect that sets RabbitMQ apart. Let's take the case of the Stream Queues.
Stream Queues are a distinctive feature of RabbitMQ that sets it apart from ActiveMQ in a significant way, particularly in their approach to message handling and data processing. Let’s briefly look at some the key features of Stream Queues.
- Non-Destructive Reads: Stream queues allow messages to be read multiple times without being removed from the queue, a feature not commonly found in traditional message brokers like ActiveMQ. This is particularly advantageous for applications that need to process the same data multiple times or by multiple consumers.
- Similarity to Kafka Streams: This functionality mirrors the behavior of Kafka streams, making RabbitMQ's stream queues an attractive option for users seeking Kafka-like features in a RabbitMQ environment.
- Flexibility in Message Consumption: Stream queues provide a level of flexibility in message consumption patterns that is not typically available in ActiveMQ's traditional queues. Consumers can navigate through message streams as needed, accommodating a variety of processing strategies.
Chapter 5 of "ActiveMQ in Action" by Bruce Snyder, Dejan Bosanac, and Rob Davies, provides an in-depth look at the internal workings of ActiveMQ's message storage.
You can also refer to our blog on RabbitMQ Files & Directories for more information on the internals of the RabbitMQ data directory.
As we've explored the intricacies of message persistence in ActiveMQ and RabbitMQ, let's now shift gears to something equally important: how these brokers stay up and running all the time and work together in clusters — in other words, the different mechanisms for high availability and clustering in the two brokers.
ActiveMQ vs RabbitMQ: Clustering and High Availability Support
TLDR: ActiveMQ and RabbitMQ both offer clustering and high availability features, but their approaches vary. ActiveMQ achieves high availability through a Master/Slave topology at the broker level, while RabbitMQ handles it at the queue level with different types of replicated queues. Here, we will study these differences.
ActiveMQ: Clustering and High Availability
ActiveMQ supports connecting multiple ActiveMQ instances into a cluster for one of two reasons (and sometimes, both):
- Scalability and load distribution
- High availability
Clustering for Scalability and Load Distribution
This involves connecting multiple ActiveMQ broker instances, formally called, a “Network of Brokers”. Each broker in this network can handle client connections and messages independently, but they also communicate with each other to share information about the network’s state and message destinations.
Within a Network of Brokers, data sharing is managed through the "store and forward" mechanism. Essentially, when a message arrives at one broker but is destined for a consumer connected to another broker, the first broker (local broker) temporarily stores the message (store) and then forwards it to the appropriate broker (remote broker) in the network (forward).
Clustering for High Availability
ActiveMQ also supports connecting two or more ActiveMQ instances in a Master-Slave topology for high availability. In the Master-Slave setup, there are typically two (or more) instances of ActiveMQ brokers - one acting as the 'master' and the other(s) as 'slave(s)'. The slave broker is a standby system that takes over in case the master broker fails.
ActiveMQ supports two techniques for setting up the Master-Slave topology: shared storage and shared nothing.
In the Shared Storage Approach: both the master and the slave brokers are configured to use the same storage backend. This could be a shared file system or a shared database, depending on the configuration and the persistence adapter being used (like KahaDB or JDBC).
The master broker is responsible for all read and write operations to this shared storage. It writes all messages, along with any other state changes (like queue configurations), to the storage. The slave broker has read access to the same storage but remains passive, i.e., it doesn't perform any write operations while it's in the slave mode.
In a Shared Nothing Architecture: both the master and the slave brokers maintain their separate storage. This means they do not rely on a common shared database or file system for storing messages.
Thus, instead of sharing a common storage, the master broker actively replicates its data to the slave broker. This replication includes all messages, queue configurations, and other state information.
RabbitMQ: Clustering and High Availability
RabbitMQ also supports linking two or more RabbitMQ instances together to form what is formerly known as a cluster in the RabbitMQ parlance. The nodes in a RabbitMQ cluster are linked in a peer-to-peer fashion — so clients can connect to any node in the RabbitMQ cluster, and that node will route the messages appropriately.
This routing of messages is possible because the nodes in a cluster usually share the same schema — meaning, definitions are visible across the nodes.
Like ActiveMQ, in RabbitMQ, it is common to set up a cluster for scalability reasons, or high availability reasons (and sometimes even both).
Clustering for Scalability
Here, the objective of clustering is to be able to distribute traffic across the different nodes in a cluster — the focus isn’t on data replication or redundancy. For example, instead of initiating 30k connections and creating 30k queues on a single node, we could have a 3-node cluster and distribute the traffic across the nodes (10k connections/queues per node).
We can also easily horizontally scale (add more nodes) the cluster if the need arises
Clustering for High Availability
The main objective here is to set up a cluster for service continuity — to give connected clients the impression that a RabbitMQ instance is up and running 24/7. In more general terms, RabbitMQ implements high availability at the queue level — using a variety of replicated queue types, namely: Classic Mirrored Queues, Quorum Queues, and Stream Queues.
Since the Classic Mirrored Queue is deprecated and scheduled for removal in RabbitMQ 4.0, here, we will focus on Quorum and Stream Queues.
Every quorum queue is replicated, comprising a leader and several followers – collectively referred to as replicas. For instance, in a quorum queue with a replication factor of five, there would be one leader and four followers, each residing on a different node.
In this setup, all client interactions go through the leader replica. This leader is responsible for replicating various commands to the follower replicas. The followers play no direct role in client interactions; their function is purely for redundancy.
Should a broker become unavailable, one of the follower replicas from another broker steps up to become the new leader, ensuring uninterrupted service continuity. Quorum queues have their name because all operations (message replication and leader election) require a majority (known as a quorum) of the replicas to agree.
Even though this new queue type was primarily designed to cover other messaging use cases in RabbitMQ like implementing large fan-outs and replay (time-travel) amongst others, it is replicated by default. Hence, it can be used for high availability in a cluster.
Like Quorum Queues, Streams are quorum systems as well in the sense that a majority is needed for leader election (and uses raft for that) but there is a caveat — consumers can read from follower replicas, and writing to a queue does not wait for a majority of replicas to confirm the write. Generally, Streams provide less strong consistency guarantees (weaker ”no-message-loss” guarantees) for the sake of higher throughput performance.
When a RabbitMQ node hosting a stream's leader fails or is stopped, another node hosting one of that stream's replicas will be elected leader and resume operations.
Chapter 10 of "ActiveMQ in Action" by Bruce Snyder, Dejan Bosanac, and Rob Davies, covers the concept of a network of brokers and configuring ActiveMQ for high availability in more detail.
Deciding between ActiveMQ and RabbitMQ boils down to your project's specific needs. RabbitMQ shines with its protocol flexibility, rich routing mechanism and ease of scaling, making it ideal multi-protocol environments and complex messaging scenarios.
On the other hand, ActiveMQ offers robust, reliable messaging, suited for applications where stability and traditional JMS support are crucial. Whichever you choose, both brokers offer solid foundations for your messaging architecture, each excelling in their unique ways.
Ready to start using RabbitMQ in your architecture? CloudAMQP is one of the world's largest RabbitMQ cloud hosting providers. In addition to RabbitMQ, we have also created our in-house message broker, LavinMQ - we benchmarked its throughput at around 1,000,000 messages/sec.
For any suggestions, questions, or feedback, get in touch with us at email@example.com