RabbitMQ高级特性

文章目录

  • 1. 简述
  • 2. 特性示例:
    • 2.1 消息可靠性投递
    • 2.2 Consumer Ack
    • 2.3 消费端限流
    • 2.4 TTL
      • 2.5 死信队列
    • 2.6 延迟队列

1. 简述

在rabbitMQ都官方文档中,高级特性又叫做Our Extensions
RabbitMQ高级特性_第1张图片
常用的有:

  • 消息可靠性投递
  • Consumer ACK
  • 消费端限流
  • TTL
  • 死信队列
  • 延迟队列

2. 特性示例:

2.1 消息可靠性投递

在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。

  • confirm 确认模式
  • return 退回模式

其中rabbitmq 整个消息投递的路径为:
producer ---> rabbitmq broker ---> exchange ---> queue ---> consumer

  • 消息从 producer 到 exchange 则会返回一个 confirmCallback 。
  • 消息从 exchange–>queue 投递失败则会返回一个 returnCallback 。

我们使用这两个callBack控制消息的可靠性投递

// 生产者
 	/**
     * 确认模式:
     *      1. 开启确认模式,在yml中将 publisher-confirms 设置为true
     *      2. 在rabbitTemplate中定义回调函数 setConfirmCallback
     */
    @Test
    public void testConfirm() throws InterruptedException {

        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             *
             * @param correlationData 交换机相关配置
             * @param b 是否发送成功, true 为成功
             * @param s 发送失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
                System.out.println("发送消息....");
                if (b) {
                    System.out.println("消息发送成功 " + s);
                } else {
                    System.out.println("消息发送失败 " + s);
                }
            }
        });
        rabbitTemplate.convertAndSend("test_Exchange_confirm","confirm","mq confirm ~~~");

        Thread.sleep(2000);
    }

    /**
     * 回退模式: 当exchange将消息发送给 queue后,发送失败,才会执行returnCallBack
     *  步骤:
     *      1. 开启回退模式: publisher-returns: true
     *      2. 设置 returnCallBack
     *      3. 设置Exchange的处理模式
     *          1. 如果消息没有路由到queue,丢弃消息 (默认)
     *          2. 如果消息没有路由到queue, 返回给消息发送方 returnCallBack
     */
    @Test
    public void testReturn() throws InterruptedException {
        // 设置交换机处理消息的模式
        rabbitTemplate.setMandatory(true);

        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             * @param message 消息对象
             * @param replyCode 错误码
             * @param replyText 错误信息
             * @param exchange 交换机信息
             * @param routingKey 路由键
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                System.out.println("执行returnCallBack");
                System.out.println(message);
                System.out.println(replyCode);
                System.out.println(replyText);
                System.out.println(exchange);
                System.out.println(routingKey);
            }
        });

        rabbitTemplate.convertAndSend("test_Exchange_confirm","confirm","mq confirm ~~~");
        Thread.sleep(3000);
    }

步骤:

  1. 开启消息发送的回调模式
  2. 重写ConfirmCallbackReturnCallBack 两个方法,用来接收消息是否完成发送的信息

小结

  • 设置ConnectionFactory的publisher-confirms=“true” 开启 确认模式。
  • 使用rabbitTemplate.setConfirmCallback设置回调函数。当消息发送到exchange后回
    调confirm方法。在方法中判断ack,如果为true,则发送成功,如果为false,则发
    送失败,需要处理。
  • 设置ConnectionFactory的publisher-returns=“true” 开启 退回模式
  • 使用rabbitTemplate.setReturnCallback设置退回函数,当消息从exchange路由到
    queue失败后,如果设置了rabbitTemplate.setMandatory(true)参数,则会将消息退
    回给producer。并执行回调函数returnedMessage。

2.2 Consumer Ack

当 RabbitMQ 将消息传递给消费者时,RabbbitMQ需要知道这个消息是否到达,那么消费者共有三种模式:

  • 自动确认:acknowledge=“none”
  • 手动确认:acknowledge=“manual”
  • 根据异常情况确认:acknowledge=“auto”

其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。
但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。


开启手动确认模式

acknowledge-mode: manual

注意
当节点将消息传递给使用者时,它必须决定是否应将该消息视为由使用者处理(或至少接收)。由于多个事物(客户端连接、使用者应用等)可能会失败,因此此决策是一个数据安全问题。消息传递协议通常提供一种确认机制,允许使用者确认对其所连接到的节点的传递。是否使用该机制是在消费者订阅时决定的。

根据所使用的确认模式,RabbitMQ 可以认为消息在发送出去(写入 TCP 套接字)或收到显式(“手动”)客户端确认后立即成功传递。手动发送的确认可以是正数或负数,并使用以下协议方法之一:

  • basic.ack : 用于正面确认
  • basic.nack : 用于否定确认,可以异常拒绝多个失败请求
  • basic.reject: 用于否定确认,只能拒绝一个
	/**
     *  consumer Ack步骤:
     *      1. 在yml中开启 acknowledge-mode: manual
     *      2. 监听器加上RabbitHandler
     *      3. 定义方法参数为 (String msg, Channel channel, Message message)
     *      4. 如果方法执行成功,调用 channel.basicAck
     *      5. 如果方法执行失败,调用 channel.basicNAck
     * @param message
     */

    @RabbitHandler
    public void ListenerMessage(String msg, Channel channel, Message message) throws InterruptedException {
        Thread.sleep(1000);
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            System.out.println("接收到消息 :" + msg);
            System.out.println("开始处理消息");
            int a = 1 / 0;
            /**
             * basicAck(long deliveryTag, boolean multiple)
             *      deliveryTag 类似于TCP中的ACK号
             *      multiple 确认所有消息到达
             */
            channel.basicAck(deliveryTag, true);
            System.out.println("消息处理成功");
        } catch (Exception e) {
            try {
                /**
                 * basicNack(long deliveryTag, boolean multiple, boolean requeue)
                 *      requeue : true 表示重新丢回队列
                 *                  false 表示直接丢弃
                 */
                channel.basicNack(deliveryTag, true, true);
                System.out.println("消息发送失败。。。");
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        }
    }

在自动确认模式下,消息在发送后立即被视为已成功传递。这种模式以更高的吞吐量(只要消费者能够跟上)换取降低交付和消费者处理的安全性。这种模式通常被称为“即发即弃”。与手动确认模型不同,如果消费者的TCP连接或通道在成功传递之前关闭,则服务器发送的消息将丢失。因此,应将自动消息确认视为不安全,并且不适合所有工作负荷。
使用自动确认模式时要考虑的另一件重要事情是使用者重载。手动确认模式通常与有界通道预取一起使用,该预取限制通道上未完成(“进行中”)传递的数量。但是,使用自动确认时,根据定义没有这样的限制。因此,消费者可能会被交付速度所淹没,这可能会在内存中积累积压并耗尽堆或使其进程作系统终止。某些客户端库将应用 TCP 背压(停止从套接字读取,直到未处理的交付的积压超过一定限制)。因此,自动确认模式仅推荐给能够高效且稳定地处理交付的消费者。

2.3 消费端限流

根据消费端处理能力来决定消费端一次最多可以拉取的消息
属性为: prefetch
例如: prefetch = 1 表示消费端一秒只接受一条消息,直到手动确认接收完毕,才接收下一条消息

值得注意的是,如果prefetch=0表示可以接收任意数量的未确认消息

2.4 TTL

TTL 全称 Time To Live(存活时间/过期时间)。当消息到达存活时间后,还没有被消费,会被自动清除。

RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。

	@Test
    public void testSent() throws InterruptedException {
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
                System.out.println("发送消息....");
                if (b) {
                    System.out.println("消息发送成功 " + s);
                } else {
                    System.out.println("消息发送失败 " + s);
                }
            }
        });

        /**
         * 消息后处理对象,设置消息的一些参数
         */
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                // 1. 设置过期时间
                message.getMessageProperties().setExpiration("10000");
                // 2. 返回消息
                return message;
            }
        };
        /**
         * TTL 过期有两种设置
         *      1. 队列整个消息过期 : 需要在队列参数中设置 "x-message-ttl", 10000
         *      2. 单个消息设置过期:
         *          如果设置了队列的过期时间和单个消息的过期时间,谁时间段谁就生效
         *          队列过期后,会将所有消息都清除
         *          RabbitMQ并不会轮询消息,而是消息到了队列顶端,才会判断消息是否过期
         */
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("test_Exchange_TTL","ttl.hhh","mq ttl ~~~" + i, messagePostProcessor);
        }


        Thread.sleep(100000);
    }

2.5 死信队列

RabbitMQ高级特性_第2张图片

消息成为死信的三种方式:

  1. 队列消息长度到达限制;
  2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
  3. 原队列存在消息过期设置,消息到达超时时间未被消费;

配置死信队列

 	/** 测试死信队列
    * 分别创建正常的队列(test_queue_dlx)和正常的交换机(test-exchange_dlx)
    * 创建死信队列(queue_dlx)和死信交换机(exchange_dlx)
    * 正常队列绑定死信交换机,并且指定
    *      x-dead-letter-exchange
    *      x-dead-letter-routing-key
    */

    /**
     * 创建正常队列和正常交换机
     */

    @Bean("test_queue_dlx")
    public Queue testQueueDlx() {
        return QueueBuilder.durable("test_queue_dlx")
                .withArgument("x-dead-letter-exchange", "exchange_dlx") //指定死信交换机
                .withArgument("x-dead-letter-routing-key", "dlx.hhh")  // 指定路由key
                .withArgument("x-message-ttl", 10000) //设置队列过期时间
                .withArgument("x-max-length", 10) //设置最大长度
                .build();
    }

    @Bean("test_exchange_dlx")
    public Exchange testExchangeDlx() {
        return ExchangeBuilder.topicExchange("test_exchange_dlx").build();
    }

    @Bean
    public Binding DlxBinding(@Qualifier("test_queue_dlx") Queue queue, @Qualifier("test_exchange_dlx") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("test.dlx.#").noargs();
    }

    /**
     * 创建死信队列和死信交换机
     */

    @Bean("queue_dlx")
    public Queue QueueDlx() {
        return QueueBuilder.durable("queue_dlx").build();
    }

    @Bean("exchange_dlx")
    public Exchange ExchangeDlx() {
        return ExchangeBuilder.topicExchange("exchange_dlx").build();
    }

    @Bean
    public Binding DlxBindingDead(@Qualifier("queue_dlx") Queue queue, @Qualifier("exchange_dlx") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("dlx.#").noargs();
    }

测试死信队列

	// 1. 队列消息过期
    rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.hhh", "我那些残梦灵翼九霄");

    // 2. 队列长度超出,设置队列长度为10
        for (int i = 0; i < 20; i++) {
            rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.hhh", "我那些残梦灵翼九霄");
       }

        //3. 测试消息拒收
        rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.hhh", "我那些残梦灵翼九霄");

2.6 延迟队列

使用TTL + 死信队列,就是延迟队列
TTL用于定时,消费者从死信队列中取值

你可能感兴趣的:(#,MQ,java-rabbitmq,rabbitmq,java)