RabbitMQ学习(十三):死信交换机 (Dead Letter Exchanges)

说明

在上篇博文《RabbitMQ学习(十二): 队列和消息的有效期》中,我对官方文档有关队列和消息的TTL进行了翻译学习,消息的TTL是实现延迟重试队列的关键因素,但仅仅有消息的有效期还无法实现过期消息的消费,本篇博文将继续翻译学习官方文档中有关死信交换机(DLX)的内容, 通过此交换机,可以实现过期消息的重新路由,进而实现延迟重试。

正文

概述

队列中的消息可能会变成死信消息(dead-lettered),进而当以下几个事件任意一个发生时,消息将会被重新发送到一个交换机:

  • 消息被消费者使用basic.reject或basic.nack方法并且requeue参数值设置为false的方式进行消息确认(negatively acknowledged)
  • 消息由于消息有效期(per-message TTL)过期
  • 消息由于队列超过其长度限制而被丢弃

注意,队列的有效期并不会导致其中的消息过期

死信交换机(DLXs)就是普通的交换机,可以是任何一种类型,也可以用普通常用的方式进行声明。

对于任何一个队列,死信交换机可以通过在客户端使用队列参数进行声明,或者是在服务器使用policy命令进行声明创建。当同时使用这两种方式声明一个死信交换机时,队列参数声明的方式将被优先使用。

使用policy命令配置死信交换机的方式被推荐使用,因为该方式不会因为应用的重新部署而重新配置。


使用policy配置(Configuration Using a Policy)

使用policy命令的方式配置一个死信交换机,可以在命令中指定“dead-letter-exchange”参数的值,如:

rabbitmqctl set_policy DLX ".*" '{"dead-letter-exchange":"my-dlx"}' --apply-to queues

以上命令声明配置了一个名为“my-dlx”的死信交换机,并应用到所有的队列。这仅仅是一个示例,在实际使用时,不同的队列将设置不同的死信交换机或者不设置。

同样地,也可以通过在policy命令中指定"dead-letter-routing-key"参数值来指定路由关键字。


使用可选的队列参数配置(Configuration Using Optional Queue Arguments)

给队列设置死信交换机时,可以在声明队列时使用可选参数"x-dead-letter-exchange"进行声明配置。该参数值必须是与队列在同一个虚拟主机的交换机名称。

channel.exchangeDeclare("some.exchange.name", "direct");

Map args = new HashMap<>();
args.put("x-dead-letter-exchange", "some.exchange.name");
channel.queueDeclare("myqueue", false, false, false, args);

以上代码声明了一个新的名为some.exchange.name的交换机,并且设置这个新的交换机作为一个新队列的死信交换机。注意,并不要求在声明队列时死信交换机必须已经被声明,但是当消息需要死信路由时,该交换机必须存在,否则,消息将会被丢弃。

你也可以指定一个路由关键字在死信路由时使用,如果没有设置,那么就会使用消息自身原来的路由关键字。

args.put("x-dead-letter-routing-key", "some-routing-key");

当一个死信交换机被指定时,除了通常的配置声明队列的权限外,用户还需要队列的读权限和死信交换机的写权限。当声明对队列时,将会校验这些权限。


路由死信消息(Routing Dead-Lettered Messages)

死信消息将被队列的死信交换机路由到其他队列,在路由时有两种情况:

  • 使用在声明队列时指定的死信路由关键字
  • 没有设置时,使用消息自身原来的路由关键字

例如,如果你使用foo作为路由关键字发送了一条消息到交换机,当消息成为死信后,它使用foo作为路由关键字被发送到队列的死信交换机。如果队列在声明时指定"x-dead-letter-routing-key"的值为bar,那么消息被发送到死信交换机时将会使用bar作为路由关键字。

注意,如果队列没有设置死信路由关键字,那消息被死信路由时将会使用它自身的原始路由关键字。这包含了CC和BCC头参数设置的路由关键字。

当死信消息被重新发送时,消息确认机制也会在内部被开启,因此,在原始队列删除这条消息之前,消息最终到达的队列—死信队列必须确认该消息。换句话说,发送队列在接收到死信队列的确认消息之前不会删除原始消息。注意,如果在特殊情况下服务器宕机,那么同样的消息将会在原始队列和死信队列中同时出现。

消息的死信路由可能会形成一个循环。比如,一个队列的死信的消息没有使用指定的死信路由关键字被发送到默认的交换机时。消息在整个循环(消息到达同一个队列两次)中没有被拒绝,那么消息将被丢弃。


死信对消息的影响(Dead-Lettered Effects on Messages)

死信消息修改了它的头部信息:

  • 交换机的名称被修改为最后的死信交换机
  • 路由关键字可能被改为队列指定的死信路由关键字
  • 如果以上发生,名为CC的头参数将被删除
  • 名为BCC的头参数将被删除

进行死信路由时,会给每个死信消息的头部增加一个名为x-death的数组。这个数组包含了过于每次死信路由的信息实体,通过一个键值对{queue, reason}区分。每个实体是一张表,包含了一下的字段信息:

  • queue:消息称为死信时所在队列的名称
  • reason:消息成为死信的原因
  • time:消息成为死信的时间,是一个64位的时间戳
  • exchange:消息被发送的交换机(当消息多次称为死信消息时,该值为死信交换机)
  • routing-keys: 消息被发送时使用的路由关键字,包含了CC关键字但是不包含BCC
  • count:在该队列中消息由于这个原因被死信路由的次数
  • original-expiration(如果消息时因为消息的TTL称为死信时,有该值):消息的原始过期时间属性,这个值在消息被死信路由时将被移除,为了避免消息在其他队列中再次过期

新的信息实体将被发送x-death数组的首位,如果该数组中已经存在同样的队列和同样的死信原因的信息实体,那么该实体的count字段将加1并且实体被移到数组的首位。

reason这个属性的值表示了消息变为死信的原因,有以下几种:

  • rejected:消息被消息者拒绝并且requeue参数值为false
  • expired:消息因为消息的TTL过期
  • maxlen:超过了队列允许的最大长度

当进行消息进行首次路由时,将添加三个顶级的头信息:

  • x-first-death-reason
  • x-first-death-queue
  • x-first-death-exchange

它们与消息进行首次死信路由时,设置的reason, queue, exchange字段值相同。一旦添加,它们值将不会再被修改。

注意,这个数组是按照最近发生时间排序,所以最近的死信路由将被记录到第一个实体中。

文档地址:https://www.rabbitmq.com/dlx.html

你可能感兴趣的:(消息队列,RabbitMQ学习)