死信队列是 RabbitMQ 里处理失败消息、做延迟和重试的基础。消息什么时候会死、死的时候带着什么信息、死信交换机配错了会怎样、死信能不能转圈?这篇一项项在 RabbitMQ 4.3.6 上验证。
死信队列收到: body='bad-msg',exchange='dlx',routing_key='m1'
x-death = [{count: 1, reason: 'rejected', queue: 'm1', exchange: '', routing-keys: ['m1']}]
其它头: {'k': 'v', 'x-first-death-exchange': '', 'x-first-death-queue': 'm1', 'x-first-death-reason': 'rejected', 'x-last-death-exchange': '', 'x-last-death-queue': 'm1', 'x-last-death-reason': 'rejected'}
x-death[0] 的全部键: ['count', 'exchange', 'queue', 'reason', 'routing-keys', 'time']消息成为死信有三种常见原因:被 reject / nack 且 requeue=false(rejected)、TTL 到期(expired)、队列超过 x-max-length 被挤出(maxlen)。队列声明时设置了 x-dead-letter-exchange,死信才会被重新发布到那个 exchange,否则就是丢弃。
'queue-ttl': x-death = [{count: 1, reason: 'expired', queue: 'm2', exchange: '', routing-keys: ['m2']}]
'msg-ttl': x-death = [{count: 1, reason: 'expired', queue: 'm2b', exchange: '', routing-keys: ['m2b']}]死信会带上 x-death 数组,每项记录某队列因某原因死亡的 count、queue、exchange、routing-keys 和 time;另有 x-first-death-* 与 x-last-death-*。原来的 body 和自定义头原样保留。
主队列里剩: ['msg3', 'msg4', 'msg5']
死信队列收到: [('msg0', 'maxlen'), ('msg1', 'maxlen'), ('msg2', 'maxlen')]设置 x-max-length 且溢出策略是默认的 drop-head 时,队头(最老的)被挤出,配了死信交换机就变成 reason=maxlen 的死信,而不是被丢弃。
dlq.a(绑定 key=orig) 收到: [('to-both', 'm4a', ['orig'])]
dlq.b(绑定 key=fixed) 收到: [('to-both', 'm4b', ['orig'])]
m4a 没设 x-dead-letter-routing-key:死信带原来的 key orig,进了 dlq.a;m4b 设成 fixed:进了 dlq.b,x-death 里记的 routing-keys 仍是原来的 orig死信的 routing key 默认沿用原来的,可以用 x-dead-letter-routing-key 改写。死信交换机就是普通 exchange,有没有队列收到完全取决于它的绑定。
拒绝了 3 条,m5 里剩 (0, 0),没有绑定的 DLX 没有留下任何东西(消息消失) DLX 指向一个不存在的 exchange:拒绝后 m5 里剩 (0, 0),channel 仍然 打开,消息消失
DLX 没有匹配的绑定,或者根本不存在,死信就直接消失,既不报错也不影响 channel。
每次拿到消息的 (距开始 ms, x-death 里 main 的 count, redelivered): [(0, 0, False), (319, 1, True), (628, 2, True), (962, 3, True)]
第 5 次拿到时的 x-death: x-death = [{count: 4, reason: 'expired', queue: 'wait', exchange: 'retry.dlx', routing-keys: ['wait']}, {count: 4, reason: 'rejected', queue: 'main', exchange: '', routing-keys: ['main']}]
first-death: queue='main' reason='rejected' exchange=''; last-death: queue='wait' reason='expired'main 的死信交换机指向一个带 TTL 的 wait 队列,wait 的死信再回到 main,就组成了“失败 → 等一会 → 重试”的环;x-death 里的 count 可以用来限制重试次数。
1.5 秒后 loopq 里剩 0 条(TTL 200 ms,如果没有循环检测它会一直转下去)
broker 日志里关于这个循环的行:
<0.1085.0> Message dropped because the following list of queues (ordered by death recency) contains a dead letter cycle without reason 'rejected'. This list will not be logged again: [<<"loopq">>,
<0.1085.0> <<"loopq">>]如果环路上全是 expired 之类的非显式原因,RabbitMQ 会检测到并丢弃消息、日志里提示一次;含有 rejected 的环是允许的。