Add the rabbitmq module
This commit is contained in:
59
rabbitmq/docs/01-the-on-ramp.md
Normal file
59
rabbitmq/docs/01-the-on-ramp.md
Normal file
@@ -0,0 +1,59 @@
|
||||
[Module README](../README.md) · [Exchanges →](02-exchanges.md)
|
||||
|
||||
# 1. The on-ramp
|
||||
|
||||
Two dependency decisions decide whether anything works, and neither produces a helpful error.
|
||||
|
||||
## Use the starter, not `spring-rabbit`
|
||||
|
||||
Boot 4 moved every auto-configuration out of `spring-boot-autoconfigure` into a per-technology
|
||||
module. RabbitMQ's lives in `spring-boot-amqp`, package
|
||||
`org.springframework.boot.amqp.autoconfigure`, and a bare `org.springframework.amqp:spring-rabbit`
|
||||
dependency does not bring it. You get no `RabbitTemplate`, no `RabbitAdmin`, no listener
|
||||
container factory — and a context that starts cleanly.
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Boot does not give you a JSON converter
|
||||
|
||||
Spring Kafka defaults to serializers you configure. Spring AMQP defaults to
|
||||
`SimpleMessageConverter`, which handles `String`, `byte[]` and `Serializable` and refuses
|
||||
everything else:
|
||||
|
||||
```
|
||||
IllegalArgumentException: SimpleMessageConverter only supports String, byte[] and
|
||||
Serializable payloads, received: com.ankurm.rabbit.OrderMessage
|
||||
```
|
||||
|
||||
One bean fixes both directions, because the auto-configured `RabbitTemplate` and the listener
|
||||
container factory both look for a `MessageConverter`:
|
||||
|
||||
```java
|
||||
@Bean
|
||||
MessageConverter messageConverter() {
|
||||
return new JacksonJsonMessageConverter();
|
||||
}
|
||||
```
|
||||
|
||||
**Note the class name.** Spring AMQP 4.1 ships `Jackson2JsonMessageConverter` (Jackson 2) and
|
||||
`JacksonJsonMessageConverter` (Jackson 3) side by side — exactly as Spring Kafka ships
|
||||
`JsonSerializer` and `JacksonJsonSerializer`. Boot 4 is a Jackson 3 application. The rule across
|
||||
both stacks is the same: **if the class name contains a `2`, it belongs to the previous major
|
||||
version of Jackson.**
|
||||
|
||||
## Versions
|
||||
|
||||
Boot 4.1.1 manages Spring AMQP **4.1.1** and `com.rabbitmq:amqp-client` **5.30.0**. Maven Central
|
||||
has amqp-client 5.35.0; overriding the managed version to reach it is a change you should have a
|
||||
reason for.
|
||||
|
||||
One API change to know about: `RabbitAdmin.QUEUE_MESSAGE_COUNT` now holds a `Long`. Every older
|
||||
example casts it to `Integer`, which is a `ClassCastException` at runtime and not at compile
|
||||
time.
|
||||
|
||||
[Exchanges →](02-exchanges.md)
|
||||
71
rabbitmq/docs/02-exchanges.md
Normal file
71
rabbitmq/docs/02-exchanges.md
Normal file
@@ -0,0 +1,71 @@
|
||||
[← The on-ramp](01-the-on-ramp.md) · [Module README](../README.md) · [The silent drop →](03-the-silent-drop.md)
|
||||
|
||||
# 2. Four exchange types
|
||||
|
||||
A producer never publishes to a queue. It publishes to an **exchange** with a **routing key**,
|
||||
and bindings decide where that lands. The whole topology is in
|
||||
[`Topology.java`](../src/main/java/com/ankurm/rabbit/Topology.java) as beans; `RabbitAdmin`
|
||||
declares them when the connection opens.
|
||||
|
||||
## Direct — exact match
|
||||
|
||||
Binding key `new` receives routing key `new`. Nothing else. This is the workhorse: one queue per
|
||||
command type.
|
||||
|
||||
## Fanout — routing key ignored entirely
|
||||
|
||||
Every bound queue gets a copy. `audit.all` and `analytics.all` both receive it, and the routing
|
||||
key you passed is not consulted at all. Use it for broadcast; use it knowing that adding a queue
|
||||
adds a full copy of the traffic.
|
||||
|
||||
## Topic — wildcards over dot-separated words
|
||||
|
||||
`*` is **exactly one word**. `#` is **zero or more**. The distinction is the one people get
|
||||
wrong, so here it is against a real broker
|
||||
([`docs/output/topic-wildcards.txt`](output/topic-wildcards.txt)):
|
||||
|
||||
```
|
||||
routing key orders.eu orders.high note
|
||||
order.eu.high true true matches both
|
||||
order.eu.low true false matches order.eu.* only
|
||||
order.us.high false true matches order.#.high only
|
||||
order.eu.west.high false true matches order.#.high only - * is one word
|
||||
order.high false true matches order.#.high - # can be zero words
|
||||
```
|
||||
|
||||
Bindings are `order.eu.*` and `order.#.high`. Two rows are worth pausing on:
|
||||
|
||||
- `order.eu.west.high` does **not** match `order.eu.*`, because `*` matches one word and `west.high`
|
||||
is two. Regex intuition says otherwise.
|
||||
- `order.high` **does** match `order.#.high`, because `#` matches zero words. So a binding you
|
||||
wrote to mean "something in the middle" also matches "nothing in the middle".
|
||||
|
||||
Design routing keys most-general-to-most-specific (`order.eu.west.high`, not
|
||||
`high.order.eu.west`), because `#` and `*` work left to right and a hierarchy you can bind
|
||||
usefully is one that starts broad.
|
||||
|
||||
## Headers — match a map, ignore the routing key
|
||||
|
||||
`x-match=all` requires every named header to be present **and equal**. `x-match=any` requires
|
||||
one. From `RoutingTest`:
|
||||
|
||||
| Message headers | `x-match=all` queue | `x-match=any` queue |
|
||||
|---|---|---|
|
||||
| `priority=high, region=eu` | yes | yes |
|
||||
| `priority=high` | no | yes |
|
||||
| `priority=high, region=us` | no | yes |
|
||||
|
||||
The third row is the one to remember: `all` matches on **value**, not on presence. A header that
|
||||
is there with the wrong value fails the same way a missing one does.
|
||||
|
||||
Headers exchanges are slower than topic exchanges and much less common. Reach for them when the
|
||||
routing criteria are genuinely multi-dimensional and do not compose into a hierarchy.
|
||||
|
||||
## The default exchange
|
||||
|
||||
Publishing to the empty exchange name `""` routes by **queue name**, using the routing key as the
|
||||
queue name. Every queue is implicitly bound to it. That is how
|
||||
`convertAndSend("", "orders.work", message)` works, and it is the one piece of AMQP that behaves
|
||||
like a magic constant.
|
||||
|
||||
[The silent drop →](03-the-silent-drop.md)
|
||||
78
rabbitmq/docs/03-the-silent-drop.md
Normal file
78
rabbitmq/docs/03-the-silent-drop.md
Normal file
@@ -0,0 +1,78 @@
|
||||
[← Exchanges](02-exchanges.md) · [Module README](../README.md) · [Dead-lettering →](04-dead-lettering.md)
|
||||
|
||||
# 3. The message that goes nowhere and says nothing
|
||||
|
||||
Publish to `orders.direct` with routing key `amend`. No binding matches. The broker discards the
|
||||
message. `convertAndSend` returns normally. Nothing is logged, by the broker or by Spring, and
|
||||
the message is gone.
|
||||
|
||||
This is correct AMQP behaviour and it is the single most expensive default in RabbitMQ, because
|
||||
the symptom is "the consumer isn't running" and the cause is three services away.
|
||||
|
||||
**Two settings are required, and they live in different places:**
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
rabbitmq:
|
||||
publisher-returns: true # on the connection factory
|
||||
template:
|
||||
mandatory: true # on the template
|
||||
```
|
||||
|
||||
`publisher-returns` puts the connection into a mode where returns are listened for at all.
|
||||
`mandatory` asks the broker to return *this* publish if it is unroutable. Setting only
|
||||
`mandatory` — which is what most examples show — changes nothing, and the test in this module
|
||||
was written that way first and failed.
|
||||
|
||||
Then register a callback:
|
||||
|
||||
```java
|
||||
template.setReturnsCallback((returned) -> log.error("unroutable: exchange={} key={} {} {}",
|
||||
returned.getExchange(), returned.getRoutingKey(),
|
||||
returned.getReplyCode(), returned.getReplyText()));
|
||||
```
|
||||
|
||||
From [`docs/output/unroutable.txt`](output/unroutable.txt):
|
||||
|
||||
```
|
||||
replyCode 312
|
||||
replyText NO_ROUTE
|
||||
exchange orders.direct
|
||||
routingKey amend
|
||||
```
|
||||
|
||||
`312 NO_ROUTE` is the code worth putting in an alert.
|
||||
|
||||
## What returns still do not give you
|
||||
|
||||
The callback fires **asynchronously, on the connection's thread**. `convertAndSend` had already
|
||||
returned successfully by then. So a return tells you a message was lost; it does not let you
|
||||
stop the caller from believing it succeeded.
|
||||
|
||||
If the caller must know, you need **publisher confirms** as well:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
rabbitmq:
|
||||
publisher-confirm-type: correlated
|
||||
```
|
||||
|
||||
Confirms and returns answer different questions, and you usually want both:
|
||||
|
||||
| | answers |
|
||||
|---|---|
|
||||
| **return** | the broker had nowhere to route this |
|
||||
| **confirm** | the broker took responsibility for this (`ack`), or refused it (`nack`) |
|
||||
|
||||
A message that is unroutable gets a **return followed by an `ack`** — the broker successfully did
|
||||
nothing with it. So a confirm alone will not tell you the message vanished, which is the trap
|
||||
inside the trap.
|
||||
|
||||
## The alternate exchange
|
||||
|
||||
The topology-level version of the same fix: give an exchange an `alternate-exchange` argument and
|
||||
unroutable messages go there instead of being dropped, with no publisher-side configuration at
|
||||
all. It costs one exchange and one queue, and it catches the publishes made by services that
|
||||
forgot to set `mandatory`.
|
||||
|
||||
[Dead-lettering →](04-dead-lettering.md)
|
||||
112
rabbitmq/docs/04-dead-lettering.md
Normal file
112
rabbitmq/docs/04-dead-lettering.md
Normal file
@@ -0,0 +1,112 @@
|
||||
[← The silent drop](03-the-silent-drop.md) · [Module README](../README.md) · [Acknowledgement →](05-acknowledgement.md)
|
||||
|
||||
# 4. A dead-letter queue that actually works
|
||||
|
||||
There is no "send to DLQ" operation in AMQP. A dead-letter exchange is a **queue argument**, and
|
||||
messages arrive there as a side effect of three specific events.
|
||||
|
||||
```java
|
||||
QueueBuilder.durable("orders.work")
|
||||
.deadLetterExchange("orders.dlx")
|
||||
.deadLetterRoutingKey("failed")
|
||||
.build();
|
||||
```
|
||||
|
||||
`deadLetterRoutingKey` matters more than it looks. Without it, the message is republished with
|
||||
its **original** routing key, so your DLQ binding has to anticipate every routing key the
|
||||
upstream might have used. Setting it to a constant means one binding catches everything.
|
||||
|
||||
## The three triggers
|
||||
|
||||
| Event | `x-death` reason |
|
||||
|---|---|
|
||||
| `basicNack` / `basicReject` with `requeue=false` | `rejected` |
|
||||
| message TTL expires (`x-message-ttl` or per-message) | `expired` |
|
||||
| queue length or byte limit exceeded (`x-max-length`) | `maxlen` |
|
||||
|
||||
And the one that is **not** a trigger: `requeue=true`. More on that below.
|
||||
|
||||
## What arrives
|
||||
|
||||
Both captured from real runs, in
|
||||
[`docs/output/dead-letter.txt`](output/dead-letter.txt):
|
||||
|
||||
```
|
||||
=== x-death after basicNack(requeue=false) ===
|
||||
reason rejected
|
||||
count 1
|
||||
exchange
|
||||
time Sat Aug 29 09:53:17 IST 2026
|
||||
routing-keys [orders.work]
|
||||
queue orders.work
|
||||
|
||||
=== x-death after x-message-ttl expiry ===
|
||||
reason expired
|
||||
count 1
|
||||
queue orders.ttl
|
||||
```
|
||||
|
||||
Same destination queue, different `reason`. That field is the whole diagnostic: `rejected` means
|
||||
a consumer looked at the work and refused it; `expired` means nobody got to it in time. Those are
|
||||
different incidents with different fixes, and they are indistinguishable without reading the
|
||||
header.
|
||||
|
||||
The third trigger has a detail worth knowing: when `x-max-length` is exceeded RabbitMQ drops from
|
||||
the **head**, so the message that gets dead-lettered is the **oldest** one already queued, not the
|
||||
one that just arrived:
|
||||
|
||||
```
|
||||
=== x-death after x-max-length overflow ===
|
||||
reason maxlen
|
||||
queue orders.bounded
|
||||
body {"orderId":"m-1","detail":"detail for m-1"}
|
||||
```
|
||||
|
||||
Three messages into a queue that holds two, and `m-1` is the one on the DLQ. A bounded queue
|
||||
under sustained overload therefore dead-letters your *backlog* while continuing to accept new
|
||||
work — which is usually what you want for telemetry and exactly wrong for orders.
|
||||
|
||||
`x-death` is a **list**, not a map — one entry per queue the message has been dead-lettered from,
|
||||
and `count` accumulates. That is how you build a retry limit: read
|
||||
`x-death[0].count`, and stop republishing past a threshold.
|
||||
|
||||
## The infinite loop
|
||||
|
||||
```java
|
||||
channel.basicNack(deliveryTag, false, true); // requeue = true
|
||||
```
|
||||
|
||||
From [`docs/output/requeue-loop.txt`](output/requeue-loop.txt), 200 delivery attempts:
|
||||
|
||||
```
|
||||
redelivered 199 times
|
||||
dead-lettered 0
|
||||
still on queue 1
|
||||
```
|
||||
|
||||
The dead-letter exchange is never consulted. `x-death` is never written. There is no counter to
|
||||
exhaust and no backoff. One poison message with an unconditional `requeue=true` handler will
|
||||
saturate a consumer indefinitely, and the queue depth stays at 1 the whole time — so a depth
|
||||
alarm will not fire either. The only visible signal is CPU.
|
||||
|
||||
**`requeue=true` is only correct when the failure is transient and you have a delay.** With no
|
||||
delay, a message that fails because a database is down comes straight back while the database is
|
||||
still down. The `redelivered` flag on the envelope is your one piece of state:
|
||||
|
||||
```java
|
||||
boolean retried = response.getEnvelope().isRedeliver();
|
||||
channel.basicNack(tag, false, !retried); // one requeue, then dead-letter
|
||||
```
|
||||
|
||||
That is a one-attempt retry with no extra infrastructure. For anything more, use a delay queue:
|
||||
a queue with a TTL and a DLX pointing back at the work exchange turns "expire after 30 seconds"
|
||||
into "retry in 30 seconds".
|
||||
|
||||
## Do not point the DLQ's DLX at its own source
|
||||
|
||||
If `orders.dlq` dead-letters to an exchange bound back to `orders.work`, a message that fails
|
||||
consistently cycles between the two forever, gaining an `x-death` entry each time until the
|
||||
header itself is the biggest thing in the message. The DLQ in this module deliberately has no
|
||||
dead-letter exchange at all.
|
||||
|
||||
[Acknowledgement →](05-acknowledgement.md)
|
||||
82
rabbitmq/docs/05-acknowledgement.md
Normal file
82
rabbitmq/docs/05-acknowledgement.md
Normal file
@@ -0,0 +1,82 @@
|
||||
[← Dead-lettering](04-dead-lettering.md) · [Module README](../README.md) · [Changing your mind →](06-changing-your-mind.md)
|
||||
|
||||
# 5. Acknowledgement
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
rabbitmq:
|
||||
listener:
|
||||
simple:
|
||||
acknowledge-mode: manual
|
||||
prefetch: 1
|
||||
```
|
||||
|
||||
Three modes, and the default is not the one you want for work that matters.
|
||||
|
||||
| Mode | Behaviour |
|
||||
|---|---|
|
||||
| `auto` (default) | the container acks after the listener returns, nacks on exception |
|
||||
| `manual` | your code calls `basicAck` / `basicNack`; nothing else does |
|
||||
| `none` | the broker forgets the message the moment it is delivered |
|
||||
|
||||
(Boot publishes no default for `acknowledge-mode` either; the field initialiser in
|
||||
`AbstractMessageListenerContainer` is `AcknowledgeMode.AUTO`.)
|
||||
|
||||
`none` is genuinely fire-and-forget: no redelivery, ever, and no flow control either, because
|
||||
`prefetch` is meaningless without acknowledgement. It is the right choice for metrics and the
|
||||
wrong choice for everything else.
|
||||
|
||||
`auto` is a reasonable default and its name is misleading: it does not mean "auto-ack" in the
|
||||
AMQP sense (that is `none`). It means the container decides, and it decides by whether your
|
||||
listener threw.
|
||||
|
||||
`manual` is what you want when the acknowledgement must happen after some other durable side
|
||||
effect — a database commit, a downstream call. In `manual` mode the listener takes a `Channel`
|
||||
and a delivery tag:
|
||||
|
||||
```java
|
||||
@RabbitListener(queues = "orders.work")
|
||||
void onOrder(OrderMessage message, Channel channel,
|
||||
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
|
||||
try {
|
||||
process(message);
|
||||
channel.basicAck(tag, false);
|
||||
}
|
||||
catch (TransientFailure ex) {
|
||||
channel.basicNack(tag, false, !alreadyRedelivered);
|
||||
}
|
||||
catch (PermanentFailure ex) {
|
||||
channel.basicNack(tag, false, false); // dead-letter it
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The two catch blocks are the point. A single `catch (Exception)` that nacks with `requeue=true`
|
||||
is the loop from [chapter 4](04-dead-lettering.md); a single one that nacks with `requeue=false`
|
||||
dead-letters transient failures that would have succeeded on a retry. Deciding which kind of
|
||||
failure you had is work you cannot delegate to the container.
|
||||
|
||||
## `prefetch` is the flow-control knob
|
||||
|
||||
`prefetch` is how many un-acknowledged messages the broker will send a consumer. Spring Boot sets
|
||||
no default, so `AbstractMessageListenerContainer.DEFAULT_PREFETCH_COUNT` applies, and that
|
||||
constant is **250** — throughput-oriented: a slow consumer holds 250 messages that no
|
||||
other consumer can take, so one stuck worker parks 250 messages behind it.
|
||||
|
||||
Set it to 1 when messages are expensive and unevenly sized — it gives you round-robin
|
||||
distribution across consumers. Leave it high when messages are small and uniform and you want
|
||||
throughput. This is the single most effective RabbitMQ tuning parameter and it is almost never
|
||||
touched.
|
||||
|
||||
## The forgotten ack
|
||||
|
||||
Not calling `basicAck` does not lose the message and does not throw. The message stays
|
||||
un-acknowledged, counting against `prefetch`, until the channel closes. With `prefetch: 1` the
|
||||
consumer stops after exactly one message; with the default it stops after 250. Both look like the
|
||||
consumer "just stopped", hours apart in wall-clock time depending on traffic.
|
||||
|
||||
`rabbitmqctl list_queues name messages_ready messages_unacknowledged` is the command that
|
||||
distinguishes them. A large `messages_unacknowledged` with a live consumer means somebody forgot
|
||||
an ack.
|
||||
|
||||
[Changing your mind →](06-changing-your-mind.md)
|
||||
58
rabbitmq/docs/06-changing-your-mind.md
Normal file
58
rabbitmq/docs/06-changing-your-mind.md
Normal file
@@ -0,0 +1,58 @@
|
||||
[← Acknowledgement](05-acknowledgement.md) · [Module README](../README.md)
|
||||
|
||||
# 6. Changing your mind about a queue
|
||||
|
||||
Queue arguments are immutable. There is no `ALTER QUEUE`. Redeclaring an existing queue with
|
||||
different arguments is a channel-level error
|
||||
([`docs/output/precondition-failed.txt`](output/precondition-failed.txt)):
|
||||
|
||||
```
|
||||
PRECONDITION_FAILED - inequivalent arg 'x-message-ttl' for queue 'orders.ttl' in vhost '/':
|
||||
received '9999' but current is '1500', class-id=50, method-id=10
|
||||
```
|
||||
|
||||
Reply code **406**. The message is precise and helpful, and you will probably never see it,
|
||||
because of what Spring wraps it in:
|
||||
|
||||
```
|
||||
AmqpIOException: java.io.IOException
|
||||
caused by: null
|
||||
caused by: channel error; protocol method: #method<channel.close>(reply-code=406, ...)
|
||||
```
|
||||
|
||||
`AmqpIOException`'s own message is the string `java.io.IOException`. The next cause's message is
|
||||
`null`. The useful text is two levels down. This is why the failure gets reported as "some
|
||||
IOException from RabbitMQ" and why it is worth writing a log statement that walks the cause
|
||||
chain.
|
||||
|
||||
## What this means operationally
|
||||
|
||||
Changing a TTL, a max-length, a dead-letter exchange or a queue type on a live queue is not a
|
||||
config edit. It is a migration:
|
||||
|
||||
1. declare the new queue under a new name
|
||||
2. bind it alongside the old one
|
||||
3. move consumers over
|
||||
4. drain the old queue
|
||||
5. delete it
|
||||
|
||||
Plan the rename into the change from the start — `orders.work.v2` is not ugly, it is honest.
|
||||
|
||||
## Related failure modes
|
||||
|
||||
**A failed declaration stops the ones after it.** `RabbitAdmin` declares beans on connection, and
|
||||
a `PRECONDITION_FAILED` closes the channel. Declarations queued behind it on that channel do not
|
||||
happen. So one stale queue argument can leave half your topology undeclared, and the symptom is a
|
||||
*different* queue being missing.
|
||||
|
||||
**Declarations happen on connection, not on context refresh.** A broken topology does not fail
|
||||
startup; it fails the first publish. If you want it to fail at boot, force a connection early —
|
||||
`connectionFactory.createConnection()` in an `ApplicationRunner` is enough.
|
||||
|
||||
**`missing-queues-fatal` defaults differently for the two container types.** It is `true` for
|
||||
`spring.rabbitmq.listener.simple` and `false` for `spring.rabbitmq.listener.direct`. So the same
|
||||
missing queue kills a simple container and is quietly tolerated by a direct one, which makes
|
||||
"it works in that service" an unhelpful data point. Combined with the point above, a topology
|
||||
error can present as a listener startup problem in one service and as silence in another.
|
||||
|
||||
[Module README](../README.md)
|
||||
22
rabbitmq/docs/output/dead-letter.txt
Normal file
22
rabbitmq/docs/output/dead-letter.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
=== x-death after basicNack(requeue=false) ===
|
||||
reason rejected
|
||||
count 1
|
||||
exchange
|
||||
time Sat Aug 29 10:30:04 IST 2026
|
||||
routing-keys [orders.work]
|
||||
queue orders.work
|
||||
=== x-death after x-message-ttl expiry ===
|
||||
reason expired
|
||||
count 1
|
||||
exchange
|
||||
time Sat Aug 29 10:30:06 IST 2026
|
||||
routing-keys [orders.ttl]
|
||||
queue orders.ttl
|
||||
=== x-death after x-max-length overflow ===
|
||||
reason maxlen
|
||||
count 1
|
||||
exchange orders.direct
|
||||
time Sat Aug 29 10:30:06 IST 2026
|
||||
routing-keys [bounded]
|
||||
queue orders.bounded
|
||||
body {"orderId":"m-1","detail":"detail for m-1"}
|
||||
2
rabbitmq/docs/output/precondition-failed.txt
Normal file
2
rabbitmq/docs/output/precondition-failed.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
=== redeclaring orders.ttl with x-message-ttl=9999 ===
|
||||
java.io.IOException | null | channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'x-message-ttl' for queue 'orders.ttl' in vhost '/': received '9999' but current is '1500', class-id=50, method-id=10) | channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'x-message-ttl' for queue 'orders.ttl' in vhost '/': received '9999' but current is '1500', class-id=50, method-id=10) |
|
||||
4
rabbitmq/docs/output/requeue-loop.txt
Normal file
4
rabbitmq/docs/output/requeue-loop.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
=== basicNack(requeue=true), 200 attempts ===
|
||||
redelivered 199 times
|
||||
dead-lettered 0
|
||||
still on queue 1
|
||||
4
rabbitmq/docs/output/tests.txt
Normal file
4
rabbitmq/docs/output/tests.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.132 s -- in com.ankurm.rabbit.TopologyTrapsTest
|
||||
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.714 s -- in com.ankurm.rabbit.DeadLetterTest
|
||||
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.061 s -- in com.ankurm.rabbit.MaxLengthTest
|
||||
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.476 s -- in com.ankurm.rabbit.RoutingTest
|
||||
7
rabbitmq/docs/output/topic-wildcards.txt
Normal file
7
rabbitmq/docs/output/topic-wildcards.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
=== topic exchange ===
|
||||
routing key orders.eu orders.high note
|
||||
order.eu.high true true matches both
|
||||
order.eu.low true false matches order.eu.* only
|
||||
order.us.high false true matches order.#.high only
|
||||
order.eu.west.high false true matches order.#.high only - * is one word
|
||||
order.high false true matches order.#.high - # can be zero words
|
||||
5
rabbitmq/docs/output/unroutable.txt
Normal file
5
rabbitmq/docs/output/unroutable.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
=== returned message ===
|
||||
replyCode 312
|
||||
replyText NO_ROUTE
|
||||
exchange orders.direct
|
||||
routingKey amend
|
||||
Reference in New Issue
Block a user