死信队列

  “死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。

       延迟队列场景:

  订单业务:在电商中,用户下单后30分钟后未付款则取消订单。

  短信通知:用户下单并付款后,1分钟后发短信给用户。

  Time To Live(TTL)

  RabbitMQ可以针对Queue设置x-expires 或者 针对Message设置 x-message-ttl,来控制消息的生存时间,如果超时(两者同时设置以最先到期的时间为准),则消息变为dead letter(死信)
  RabbitMQ针对队列中的消息过期时间有两种方法可以设置。
  A: 通过队列属性设置,队列中所有消息都有相同的过期时间。
  B: 对消息进行单独设置,每条消息TTL可以不同。

  如果同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就成为dead letter
  Dead Letter Exchanges(DLX)

  RabbitMQ的Queue可以配置x-dead-letter-exchangex-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter,则按照这两个参数重新路由转发到指定的队列。
  x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchange
  x-dead-letter-routing-key:出现dead letter之后将dead letter重新按照指定的routing-key发送
  队列出现dead letter的情况有:1、消息或者队列的TTL过期  2、队列达到最大长度 3、消息被消费端拒绝(basic.reject or basic.nack)并且不再重新投递requeue=false

  

 

   示例

  1. 配置业务队列,绑定到业务交换机上
  2. 为业务队列配置死信交换机和路由key
  3. 为死信交换机配置死信队列

  一般来说,会为每个业务队列分配一个独有的路由key,并对应的配置一个死信队列进行监听,也就是说,一般会为每个重要的业务队列配置一个死信队列。

  那么“死信”被丢到死信队列中后,会发生什么变化呢?

  如果队列配置了参数 x-dead-letter-routing-key 的话,“死信”的路由key将会被替换成该参数对应的值。如果没有设置,则保留该消息原有的路由key。

  消费者

@Slf4j
public class DlxConsumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.223.144");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();

        String businessExchange = "business_exchange";
        String businessQueue = "business_queue";
        String businessRouteKey = "business.#";
        String dlxExchange = "dlx.exchange";
        String dlxRouteKey = "#";
        String dlxQueue = "dlx_queue";

        channel.exchangeDeclare(businessExchange, "topic", true, false, null);
        Map<String, Object> arguments = new HashMap<>();
        //声明当前队列绑定的死信交换机
        arguments.put("x-dead-letter-exchange", dlxExchange);
        //声明当前队列的死信路由key
        arguments.put("x-dead-letter-routing-key", dlxRouteKey);
        channel.queueDeclare(businessQueue, true, false, false, arguments);
        channel.queueBind(businessQueue, businessExchange, businessRouteKey);

        channel.exchangeDeclare(dlxExchange, "topic", true, false, null);
        channel.queueDeclare(dlxQueue, true, false, false, null);
        channel.queueBind(dlxQueue, dlxExchange, dlxRouteKey);

        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss");
                log.info("业务接收消息:{} 时间:{}", new String(body, "UTF-8"), dateFormat.format(new Date()));
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //消息确认
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        channel.basicConsume(businessQueue, false, consumer);
        channel.basicConsume(dlxQueue, false, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                log.info("死信队列接收消息:{}", new String(body, "UTF-8"));
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //消息确认
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        });
    }
}

  生产者

public class DlxProvider {

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.223.144");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");

        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        String exchange = "business_exchange";
        String routingKey = "business.save";

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss");
        for (int i = 0; i < 5; i++) {
            String format = dateFormat.format(new Date());
            String message = "hello DLX Message" + format;
            AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().deliveryMode(2)
                    .contentEncoding("utf-8").expiration("1000").build();
            channel.basicPublish(exchange, routingKey, true, properties, message.getBytes());
        }
    }
}

  死信消息的生命周期:

  1. 业务消息被投入业务队列
  2. 消费者消费业务队列的消息,由于处理过程中发生异常,于是进行了nck或者reject操作
  3. 被nck或reject的消息由RabbitMQ投递到死信交换机中
  4. 死信交换机将消息投入相应的死信队列
  5. 死信队列的消费者消费死信消息
posted on 2020-01-01 23:21  溪水静幽  阅读(375)  评论(0)    收藏  举报