<- Back to blog archive
Guide

AMQP routing in LavinMQ: an interactive guide to exchange types

AMQP routing in LavinMQ: an interactive guide to exchange types

In AMQP, a producer never talks to a queue directly. It talks to an exchange. In this post, we’ll go through five exchange types in LavinMQ (direct, fanout, topic, headers, and consistent hash), what each one is good for, and how to pick the right one.

Each section comes with a simulation, so you can publish messages and see the routing happen.

Think of the exchange as the sorting room in a post office. Every message comes in through the same door, and the exchange decides which queues get a copy. That one decision is where the flexibility in AMQP lives.

Four building blocks

Routing in LavinMQ comes down to four pieces:

  • Exchange: receives every message and decides where it goes, based on its type.
  • Queue: holds messages until a consumer picks them up.
  • Binding: the link between an exchange and a queue, often with a binding key that says which messages the queue wants.
  • Routing key: a short label the producer puts on each message. Some exchange types read it, some ignore it.

If no binding matches, the message is unroutable and LavinMQ drops it. Two settings can catch it instead: the mandatory flag sends it back to the producer, and an alternate exchange routes it somewhere else. Each simulation counts unroutable messages, so you’ll see right away when one slips through.

Want the code side of things? The AMQP exchanges reference shows how to declare each type, and the message routing tutorial walks you through it step by step.

Direct exchange: exact match

A direct exchange is the simplest one. It compares the routing key on the message with the binding key on each queue, and only an exact match counts. card never matches cards or Card.

Two queues can bind with the same key, and then both get a copy. In the simulation below, card-payments and fraud-check both listen for card, so every card payment reaches the payment service and the fraud check at the same time.

Simulation of a direct exchange called payments. The queues card-payments and fraud-check both bind with the key card, and invoice-payments binds with invoice. A message with the key bank-transfer matches no binding, unless an alternate exchange sends it to the unrouted queue.

Now try bank-transfer. No queue has that binding, so the exchange drops it. In a payment system, that’s rarely OK. An alternate exchange catches every unroutable message. Bind a queue to it, like unrouted in the simulation, and those messages wait there until someone follows up. The mandatory flag works from the other side: if no queue takes the message, not even through the alternate exchange, it goes back to the producer. Switch on the alternate exchange in the simulation and send bank-transfer again: this time it lands in unrouted.

You’ve already used a direct exchange, even if you didn’t know it. The default exchange binds every queue with its own name as the key. That’s why publishing with the routing key orders lands in the queue called orders.

Fanout exchange: a copy for everyone

A fanout exchange doesn’t care about routing keys at all. It copies every message to every queue bound to it. Change the routing key below and nothing changes: three queues, three copies.

Fanout is perfect for events that several services care about. One order event can trigger an email, update analytics, and reserve stock, and the producer doesn’t need to know any of those services exist. Need a new consumer? Bind one more queue. The producer stays exactly the same.

Simulation of a fanout exchange called order-events with three bound queues: email, analytics and inventory. Every message goes to all three, whatever the routing key.

Topic exchange: pattern matching

A topic exchange is where routing gets really useful. The routing key is a list of words separated by dots, and each binding can use two wildcards: * matches exactly one word, and # matches zero or more words.

In the simulation, vehicles send telemetry with keys like region.vehicle.event. The binding eu.# catches everything from Europe. *.*.alert catches alerts from any region and any vehicle. *.scooter.battery only wants scooter battery readings.

Type your own key to test the edge cases. eu on its own matches eu.#, because # also matches zero words. eu.alert does not match *.*.alert, because that pattern needs exactly three words.

Simulation of a topic exchange called vehicle-telemetry. The queue eu-dashboard binds with eu.#, alerts with *.*.alert and scooter-battery with *.scooter.battery. The key eu.scooter.alert goes to eu-dashboard and alerts, while us.scooter.gps matches nothing.

Fun fact: a binding key without wildcards turns a topic exchange into a direct exchange, and # on its own turns it into a fanout. We’re proud of our topic exchange. We rewrote it from the ground up to make it faster and more correct. For a hands-on example, the topic exchange tutorial builds a full notification system.

Headers exchange: route on message headers

A headers exchange ignores the routing key and looks at the message headers instead, the small key-value labels a producer can attach to each message. The x-match argument on each binding decides how picky the queue is:

  • x-match: all means every header in the binding must match.
  • x-match: any means one match is enough.

In the simulation, pdf-reports wants both format=pdf and type=report. The archive queue takes anything that’s a PDF or an invoice. csv-export only checks the format, so extra headers don’t matter. Clear both headers and nothing matches.

Simulation of a headers exchange called documents. The queue pdf-reports binds with x-match: all, format=pdf and type=report. The archive queue binds with x-match: any, format=pdf and type=invoice. The csv-export queue binds with x-match: all and format=csv.

Headers exchanges shine when routing depends on several separate attributes, and squeezing them all into one dotted routing key starts to feel awkward.

Consistent hash exchange: same key, same queue

The consistent hash exchange solves a classic problem: you want to spread the load across several queues, but all messages for one customer must stay in order. It sends every message with the same routing key to the same queue, every time.

Picture the hash ring as a clock face. Each queue gets one or more spots on the clock, and a higher weight (the binding key) gives it more spots. LavinMQ turns the routing key into a position on the clock and sends the message to the next queue spot clockwise.

Publish customer-42 a few times and watch every message land in the same queue. Then change a weight. A new spot takes over part of a neighbor’s range, and only the keys in that range move. Everything else stays put.

Simulation of a consistent hash exchange called orders with three queues: orders-1 and orders-2 bind with weight 1, and orders-3 binds with weight 2. Every message with the same routing key, such as customer-42, lands in the same queue.

With only a few spots on the clock, the split can get uneven. That’s why LavinMQ also supports jump consistent hash, which spreads keys evenly. The consistent hash exchange documentation covers setup.

Which exchange to pick

  • Direct when each message has one clear destination, such as a task type or a payment method.
  • Fanout when every subscriber needs every message, such as live sports scores or price updates sent to every connected client.
  • Topic when consumers want to filter on parts of a structured key, such as region or event type. It’s also the exchange type we use the most.
  • Headers when routing depends on several attributes that don’t fit in one key.
  • Consistent hash when you need to spread the load across queues without losing order per key.

Routing to streams

Everything in this post works for streams too. A stream binds to an exchange just like a queue, so the same rules decide which messages land in it. The difference shows up after routing: a stream keeps every message in a log, and each consumer reads from its own position without removing anything. The LavinMQ streams documentation has the details.

Wrap up

Most systems end up using more than one exchange type. A fanout for domain events, a topic exchange for telemetry and a consistent hash exchange for ordered processing can all live side by side on the same LavinMQ broker. Pick the one that matches how your consumers think about their messages, and let the exchange do the sorting.

Author

Lovisa Johansson