KubeMQ
ConnectorsRabbitMQ (AMQP 0-9-1)Concepts

Exchanges and Routing

How RabbitMQ exchange types (direct, fanout, topic, headers) work as virtual connector-side routing, resolved at publish time into KubeMQ Queue channels.

In the KubeMQ RabbitMQ connector, exchanges and bindings are virtual connector-side routing metadata, not data stores. There is no exchange object holding messages — a publish to an exchange is resolved into a set of target queues at publish time, and each resolved queue is written to its KubeMQ Queue channel amqp.{vhost}.{queue}. This guide covers the exchange types, topic wildcards, declare idempotency, and what happens to the routing result.

Every AMQP queue maps to a single KubeMQ Queue channel amqp.{vhost}.{queue}. Exchanges and bindings exist only to select which queues a publish lands in — they are evaluated by the connector at publish time, then discarded. See Architecture.

How a publish routes

The publish is first authorized on the exchange (Write, before routing), then the matcher produces a set of target queues, deduplicates it, and writes the message to each queue's KubeMQ channel — where the shared queue layer every KubeMQ protocol goes through authorizes each destination once more.

Exchange types

Exchange typeRouting semanticsPre-declared (per vhost)
default ("")Implicit per-queue binding; routing-key = queue name; a missing queue is unroutableimplicit
directExact routing-key match; multiple bindings on the same key all matchamq.direct (durable)
fanoutAll bound queues; routing key ignoredamq.fanout (durable)
topicTrie matcher; * = exactly one word, # = zero or more words, . = word separatoramq.topic (durable)
headersx-match ∈ any-with-x (default all); x--prefixed headers are excluded from matchingamq.headers / amq.match (durable)
x-delayed-messageThe delayed-message plugin's type; routes by the x-delayed-type argument (one of the four above). A missing or non-string x-delayed-type → 406; one naming an unknown type → 503. The x-delay header delays delivery on every exchange, plugin or not—

Pre-declared amq.* exchanges

The amq.direct, amq.fanout, amq.topic, amq.headers, and amq.match exchanges are pre-declared durable per vhost, together with the internal topic exchange amq.rabbitmq.trace; the default vhost additionally carries the internal amq.rabbitmq.log, matching RabbitMQ 4.3.4. They:

  • cannot be deleted by a client → 403 access-refused;
  • cannot be redeclared with different args → 406 precondition-failed;
  • a client declaring any exchange with an amq.* prefix → 403.

The two amq.rabbitmq.* exchanges exist so tooling that declares or binds to them keeps working. A direct publish to either is refused 403, and nothing is ever routed through them — there is no firehose tracing and no log forwarding; a queue bound to either stays empty.

Exchange-to-exchange bindings and auto-delete

exchange.bind / exchange.unbind work with the semantics measured on RabbitMQ 4.3.4: a message published to the source is routed on through the destination exchange's own bindings with its routing key and headers unchanged; a queue reachable by several paths receives one copy; cycles and an exchange bound to itself are legal (each exchange is visited once per publish); an internal exchange receives through a binding — internal forbids only a direct publish. The default exchange is refused at either end (403); a missing exchange is 404 on bind, while exchange.unbind succeeds even when an end does not exist. Permissions follow RabbitMQ's pair: write on the destination, read on the source.

An exchange declared auto-delete is removed when its last binding goes — by queue.unbind, by the bound queue's deletion, or by an exclusive owner's connection closing — and one that has never been bound stays. if-unused on exchange.delete counts bindings from the exchange.

Topic wildcards

The topic matcher uses . as the word separator:

TokenMatches
*exactly one word
#zero or more words

Worked examples:

Binding patternMatchesDoes NOT match
stock.*.nysestock.ibm.nysestock.ibm.us.nyse (two words for *)
stock.#stock, stock.a, stock.a.bstocks.a
#any key, including the empty key—
*.orange.*quick.orange.rabbitlazy.orange.elephant.x

Bindings and declare idempotency

  • Declare idempotency: identical args → ok; any field mismatch (type / durable / auto-delete / internal / deep-equal arguments) → 406.
  • Passive declare (passive=true): exists → ok; missing → 404 not-found.

The routing result

  1. The publish is authorized on the exchange — Write, checked before routing and before the exchange's existence is resolved. A denial closes the channel with 403 naming the exchange (audited as amqp.publish.denied); the publish is never acknowledged and never returned as 312. See Authentication.
  2. The exchange matcher produces a set of target queues, following exchange-to-exchange bindings and, when nothing matched, the exchange's alternate-exchange.
  3. The set is deduplicated — a message matching multiple bindings to the same queue is delivered exactly once.
  4. If the routed set is empty:
    • mandatory=true → basic.return(312 NO_ROUTE) with the full message content;
    • otherwise the message is silently dropped.
  5. Each destination queue is authorized once more by the shared queue layer every KubeMQ protocol goes through. A refusal there fails the whole publish as one outcome — a basic.nack in confirm mode, a 541 channel close without confirms — never a partial delivery to the permitted queues. 312 is reserved for a publish that was permitted and matched no binding.

See Reliability for mandatory / basic.return.

Alternate exchanges

alternate-exchange on exchange.declare is honored: an exchange whose own bindings match nothing hands the message to the exchange its alternate-exchange argument names, which may have an alternate of its own; an internal exchange works as an alternate; a missing alternate or an alternate cycle is simply unroutable (a mandatory publish is returned 312, the channel stays open); and a match on an exchange-to-exchange binding that leads to no queue still counts as a match, so the alternate does not fire — all as measured on RabbitMQ 4.3.4. Declaring a new exchange with the argument needs read on the exchange itself and write on the alternate it names.

Consumer-side arguments that shape delivery

  • x-single-active-consumer: true on queue.declare: only the first-registered consumer receives; the next in registration order takes over when it cancels or its connection drops (and gets whatever it left unacknowledged); a later consumer never pre-empts the active one, whatever its priority. Held across the cluster by node presence.
  • x-priority on basic.consume: while a higher-priority consumer has prefetch room it receives every message, lower-priority consumers get only the overflow, and equal priorities share round-robin. A non-integer is 406. Priorities are arbitrated per node.

Inert arguments. Only x-max-length-bytes, x-queue-type / x-queue-mode and x-max-priority are accepted-and-ignored (badged in the dashboard topology view). Every other argument that older versions of this page listed as inert — alternate-exchange, x-max-length / x-overflow, x-single-active-consumer, x-expires, consumer x-priority — is now enforced, and x-expires deletes an idle queue. See Capabilities.

Error quick reference

TriggerCode
Unmatched direct routing key, no mandatorysilent drop
mandatory=true and unroutable312
Redeclare with mismatched args406
Passive declare of a missing exchange404
Client declares an amq.* exchange / deletes a pre-declared exchange / publishes directly to an internal exchange403
Publish denied on the exchange403 (channel closed; no ack, no 312)
exchange.bind / exchange.unbind naming the default exchange403
exchange.bind naming a missing exchange404
x-delayed-message without a string x-delayed-type406

Was this page helpful?

On this page