Redis as a Message Bus vs RabbitMQ: When to Use Which
The naive comparison is misleading
When evaluating message brokers for a distributed microservices architecture, it is tempting to compare Redis and RabbitMQ purely on raw throughput benchmarks. On paper, both technologies can "deliver a payload from Producer A to Consumer B."
In practice, Redis and RabbitMQ operate under fundamentally different message semantics and failure guarantees:
- Redis Pub/Sub: Fire-and-forget push delivery directly to active TCP client sockets. It possesses zero message persistence. If a subscriber disconnects during a rolling deployment, any message published during that window is permanently lost.
- Redis Streams: Introduced in Redis 5.0, Streams append messages to an in-memory log data structure (
XADD). It supports consumer groups (XREADGROUP) and message offsets, but remains bounded by Redis server RAM limits and memory eviction policies (maxmemory). - RabbitMQ (AMQP 0-9-1): An enterprise-grade message broker built on Erlang OTP. It implements flexible exchange routing (Direct, Fanout, Topic, Headers), publisher acknowledgments (
publisher confirms), explicit consumer ACKs (basic.ack), Dead Letter Exchanges (DLX), and durable disk-backed message queues (durable: true,delivery_mode: 2).
graph TD
subgraph Redis Streams Architecture
Producer1["Producer"] -->|XADD| Stream["Redis Stream (in-memory log)"]
Stream -->|XREADGROUP| CG1["Consumer Group 1"]
Stream -->|XREADGROUP| CG2["Consumer Group 2"]
end
subgraph RabbitMQ AMQP Topology
Producer2["Producer"] -->|BasicPublish| Exchange["Topic Exchange (orders.*)"]
Exchange -->|Binding: orders.created| Queue1["Order Fulfillment Queue (Durable Disk)"]
Exchange -->|Binding: orders.#| Queue2["Audit Log Queue (DLX Guarded)"]
Queue1 -->|BasicAck| Consumer1["Fulfillment Worker"]
Queue2 -->|BasicAck| Consumer2["Audit Worker"]
end
Architectural Deep-Dive: Durability vs Latency
1. High-Frequency Fan-out (Redis Streams)
When building real-time market data pipelines (e.g. broadcasting live price ticks or IoT telemetry to connected frontend dashboards), throughput and ultra-low latency dominate durability requirements.
Redis Streams handles 100,000+ messages per second per core with sub-millisecond p99 latency (0.3ms). Because individual missing price ticks are immediately superseded by the next incoming tick, losing an occasional message during network jitter is acceptable.
namespace App\Services;
use Illuminate\Support\Facades\Redis;
class MarketDataStreamProducer
{
public function publishTick(string $symbol, float $price, int $volume): void
{
// Append tick to Redis Stream with capped log length (~100,000 entries)
Redis::xadd("stream:ticks:{$symbol}", 'MAXLEN', '~', 100000, '*', [
'price' => (string) $price,
'volume' => (string) $volume,
'timestamp' => (string) microtime(true),
]);
}
}

2. Transactional Guarantees (RabbitMQ AMQP)
For critical business events — such as payment processing webhooks, order fulfillment triggers, or audit log entries — losing a single message represents a critical system bug.
RabbitMQ guarantees message delivery across network partitions and node crashes using disk-backed queues and two-way acknowledgments:
namespace App\Services;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
class OrderPublisher
{
public function publishOrderCreated(array $orderPayload): void
{
$connection = new AMQPStreamConnection('rabbitmq.internal', 5672, 'guest', 'guest');
$channel = $connection->channel();
// Declare durable topic exchange
$channel->exchange_declare('events.orders', 'topic', false, true, false);
// Persistent message payload (delivery_mode = 2)
$msg = new AMQPMessage(json_encode($orderPayload), [
'content_type' => 'application/json',
'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
]);
// Enable publisher confirms to ensure RabbitMQ disk write
$channel->confirm_select();
$channel->basic_publish($msg, 'events.orders', 'orders.created');
$channel->wait_for_pending_acks();
$channel->close();
$connection->close();
}
}
The Production Failure Mode That Decided It
During a rolling Kubernetes cluster deployment of our consumer microservices, worker pods were terminated and recreated over a 40-second window.
What Happened to RabbitMQ:
RabbitMQ's topic exchange continued accepting incoming order creation messages from upstream services. The durable queues safely accumulated 150,000 unacknowledged messages on disk. As soon as the new consumer pods booted and sent basic.consume, the queue drained smoothly at 4,000 messages/sec with zero message loss.
What Happened to Redis:
- Redis Pub/Sub: Dropped 100% of messages emitted during the 40-second deployment window because no client TCP sockets were listening.
- Redis Streams: Saved messages in the stream log, but consumer workers required custom recovery logic to query the Pending Entries List (
XPENDING) and claim abandoned messages (XCLAIM) left by terminated worker IDs.
Technology Decision Matrix
| Feature / Metric | Redis Pub/Sub | Redis Streams | RabbitMQ (AMQP) |
|---|---|---|---|
| p99 Latency | < 0.1 ms | < 0.5 ms | 2 - 5 ms |
| Durability | None (In-Memory) | Bounded (RAM / RDB) | Complete (Disk / Mirrored) |
| Consumer Groups | No | Yes (XREADGROUP) |
Yes (Queue bindings) |
| Message Dead-Lettering | No | Manual (XCLAIM) |
Native (DLX / DLQ) |
| Complex Routing | Basic Pattern Matching | Stream Keys | Direct, Fanout, Topic, Headers |
| Backpressure Control | Drops messages | RAM Eviction | Producer Throttling |
Conclusion and Rule of Thumb
Choose Redis Streams / Pub/Sub when latency is critical (<1ms), payloads are transient, and your system already relies on Redis infrastructure.
Choose RabbitMQ when data correctness is mandatory, message routing is complex, and you require battle-tested disk durability, Dead Letter Queues, and publisher confirmations.