RabbitMQ-高级特性

文章目录

      • 1. Confirm 确认消息
      • 2. Return 消息机制
      • 3. 消费端自动监听
      • 4. 消费端限流
      • 5. 消费端ACK与重回队列
      • 6. TTL队列/消息
      • 7. 死信队列(DLX-Dead letter exchange)

1. Confirm 确认消息

  1. 概念:主要保证broker能够肯定收到消息;当Broker收到消息的时候,会给生产者回复一个应答。
  2. 案例
public class RabbitMQConfirmConsumer {
    public static void main(String[] args) throws Exception {
        // 1.创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");

        // 2.根据connectionFactory获取Connection
        Connection connection = connectionFactory.newConnection();

        // 3.通过Connection创建一个Channel
        Channel channel = connection.createChannel();

        // 4.声明一个交换机, 队列以及队列的绑定.
        String exchangeName = "test_confirm_exchange";
        String queueName = "confirm_queue";
        String routingKey = "confirm.save";
        channel.queueDeclare(queueName, true, false, false, null);
        channel.exchangeDeclare(exchangeName, "topic", true);
        channel.queueBind(queueName, exchangeName, routingKey);

        // 5.创建消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);

        // 6.设置channel
        channel.basicConsume(queueName, true, consumer);

        while (true) {            
        	// 7.获取信息
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String body = new String(delivery.getBody());
            System.out.println("消费端: " + body);
        }    
     }
}


public class RabbitMQConfirmProduct {
    public static void main(String[] args) throws Exception {
        // 1.创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");

        // 2.根据connectionFactory获取Connection
        Connection connection = connectionFactory.newConnection();

        // 3.通过Connection创建一个Channel
        Channel channel = connection.createChannel();

        // 4.指定消息投递模式: 消息确认模式
        channel.confirmSelect();

        // 4.通过channel发生数据
        String message = "Hello world!, RabbitMQ!";

        String exchangeName = "test_confirm_exchange";
        String routingKey = "confirm.save";

        IntStream.range(0, 5).forEach(item -> {            try {
                channel.basicPublish(exchangeName, routingKey, null, message.getBytes());
            } catch (IOException e) {                e.printStackTrace();
            }        });

        // 5.监听Broker的返回值
        channel.addConfirmListener(new ConfirmListener() {            
        	/**
             * 正确返回
             * @param deliveryTag: message的唯一ID.
             * @param multiple: 是否批量.
             * @throws IOException
             */
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
            }

            /**
             * 错误返回
             * @param deliveryTag: message的唯一ID.
             * @param multiple: 是否批量.
             * @throws IOException
             */
            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
            }
        });

        // 6.不能关闭连接, 因为addConfirmListener时异步的不会阻塞程序执行.
//        channel.close();
//        connection.close();
    }
}

2. Return 消息机制

  1. 概念:
    1. Return Listener:用于处理一些不可路由的消息!
    2. 通常使用Exchange和Routing Key把消息发送到某个队列中去,然后消费端监听队列,消费队列消息。
    3. 而有时,生产端发送消息时,指定的Exchange或是Routing Key找不到时,这时就需要使用Return Listener监听这种不可达的消息,然后进行相应的处理。
  2. 重要参数Mandatory:为true的时候,监听器会接受这些不可达的消息,然后进行相应的后续处理;若设置为false,则Broker直接删除不可达的消息。
  3. 案例:
public class ReturnListenerProduct {
    public static void main(String[] args) throws Exception {
        // 1.创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");

        // 2.根据connectionFactory获取Connection
        Connection connection = connectionFactory.newConnection();

        // 3.通过Connection创建一个Channel
        Channel channel = connection.createChannel();

        // 4.通过channel发送数据
        String message = "Hello world!, RabbitMQ!";

        String exchangeName = "test_return_exchange";
        String routingKey = "return.save";
        String routingErrorKey = "abc.save";

        IntStream.range(0, 5).forEach(item -> {            
        	try {
        		// 第三个参数即为Mandatory, 为true的时候, 会调用addReturnListener事件.
                channel.basicPublish(exchangeName, routingKey, true, null, message.getBytes());
            } catch (IOException e) {                
            	e.printStackTrace();
            }        
       });

        // 5.监听Broker的返回值
        channel.addReturnListener((replyCode, replyText, exchange, routingKey1, properties, body) -> {            System.out.println("Return handle");
            // 可以把返回的这些数据, 输出日志, 获取传输到监控台, 能够及时发现错误.
        });

        // 6.不能关闭连接, 因为addConfirmListener时异步的不会阻塞程序执行.
//        channel.close();
//        connection.close();
    }}


public class ReturnListenerConsumer {
    public static void main(String[] args) throws Exception {
        // 1.创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");

        // 2.根据connectionFactory获取Connection
        Connection connection = connectionFactory.newConnection();

        // 3.通过Connection创建一个Channel
        Channel channel = connection.createChannel();

        // 4.声明交换机、队列, 以及绑定队列.
        String exchangeName = "test_return_exchange";
        String routingKey = "return.save";
        String queueName = "test001";
        channel.exchangeDeclare(exchangeName, "topic", true);
        channel.queueDeclare(queueName, true, false, false, null);
        channel.queueBind(queueName, exchangeName, routingKey, null);

        // 5.创建消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);

        // 6.设置channel
        channel.basicConsume(queueName, true, consumer);

        while (true) {            // 7.获取信息
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String body = new String(delivery.getBody());
            System.out.println("消费端: " + body);
        }    }
}

3. 消费端自动监听

  1. 概述:不能使用上面代码中的while(true); 这样会一直阻塞住程序往下执行, 且不管有没有数据都往Broker去获取消息,很不优雅。
  2. 解决方法:只需要consumer去继承一个DefaultConsumer类即可。
  3. 案例:
// 设置channel, 自定义消费者监听
channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
    /**
     * 监听返回.
     * @param consumerTag: rabbitMQ生成的一串标记.
     * @param envelope: 包含(消息的唯一ID, Exchange_Name, Queue_Name)
     * @param properties: 信息头.
     * @param body: 信息体.
     * @throws IOException
     */
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope,
                               AMQP.BasicProperties properties, byte[] body) throws IOException {        super.handleDelivery(consumerTag, envelope, properties, body);
    }});

4. 消费端限流

  1. RabbitMQ提供一直qos(服务质量保障)功能,即在非自动确认消息的前提下,如果一定数目的消息(通过Consumer或者Channel设置Qos值)未被确认前,不进行消费新的消息。
  2. 在consumer端使用void BasicQos(unit prefetchSize, ushort prefetchCount, bool global)
  3. BasicQos参数详解:只有在不自动ACK的时候, 才起效。
    1. prefetchSize:消息大小限制。0则代表不做限制。
    2. prefetchCount:一次能够处理多少条数据;实际中设置为1为佳;
    3. global:表示这个限流策略在什么上应用的;true:在Channel上;false:在Consumer上;实际中设置为false,在consumer上使用限流。
  4. 案例:若消费端一直没有ACK, 则Broker会一直阻塞其它的消息发送给改消费端。
// 处理限流第一步
channel.basicQos(0, 1, false);
// 处理限流第二步: 设置autoAck设置为false.
channel.basicConsume(queueName, false, new DefaultConsumer(channel) {
    /**
     * 监听返回.
     * @param consumerTag: rabbitMQ生成的一串标记.
     * @param envelope: 包含(消息的唯一ID, Exchange_Name, Queue_Name)
     * @param properties: 信息头.
     * @param body: 信息体.
     * @throws IOException
     */
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope,
                               AMQP.BasicProperties properties, byte[] body) throws IOException {        super.handleDelivery(consumerTag, envelope, properties, body);

        // 处理限流第三步: 手动设置ACK;
        channel.basicAck(envelope.getDeliveryTag(), false);
    }});

5. 消费端ACK与重回队列

  1. 消费端的手工ACK和NACK

    1. 手工ACK:是设置了autoACK为false,需要consumer调用channel.basicAck(envelope.getDeliveryTag(), false);手工ACK,确认这条消息收到啦。
    2. NACK:表示ACK失败,需要生产端再次发送消息。
  2. 消费端的重回队列

    1. 消费端的重回队列是为了没有处理成功的消息,把消息重新回递给Broker。
    2. 一般在实际应用中,都会关闭重回队列,使用补偿机制。
    3. 重回队列的消息,会被直接方法队尾
  3. 案列:

channel.basicConsume(queueName, false, new DefaultConsumer(channel) {

    /**
     * 监听返回.
     * @param consumerTag: rabbitMQ生成的一串标记.
     * @param envelope: 包含(消息的唯一ID, Exchange_Name, Queue_Name)
     * @param properties: 信息头.
     * @param body: 信息体.
     * @throws IOException
     */
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope,
                               AMQP.BasicProperties properties, byte[] body) throws IOException {        super.handleDelivery(consumerTag, envelope, properties, body);

        /**
         * NACK和重回队列
         * 1. 使用的是{@link Channel#basicNack(long deliveryTag, boolean multiple, boolean requeue)} api.
         * 2. 这个api第三个参数 requeue表示是否重回队列.
         */
        channel.basicNack(envelope.getDeliveryTag(), false, false);


        //手动设置ACK;
        channel.basicAck(envelope.getDeliveryTag(), false);
    }});

6. TTL队列/消息

  1. 概述:TTL队列/消息即是队列和消息可以设置过期时间。
  2. 区别:
    1. TTL队列:设置队列的过期时间,到了过期时间,会清空队列,然后删除队列。
    2. TTL消息:到期了会自动删除消息。通过AMQP.properties来设置TTL。

7. 死信队列(DLX-Dead letter exchange)

  1. 概述:利用DLX,当消息在一个队列中变为死信(没有客户端去消费的数据)之后,它能重新publish到另一个Exchange,这个Exchange就是DLX。
  2. 消息变为死信的情况:
    1. 消息被拒绝(basic.reject/basic.nack)并且不重回队列(requeue=false)。
    2. 消息TTL过期。
    3. 队列达到了最大长度时,再过来的消息,会直接放入到死信队列中。
  3. 死信队列设置:
    1. 设置死信队列的exchange和queue,然后进行绑定:
      1. Exchange:dlx.exchange;
      2. queue: dlx.queue;
      3. Routing key:#;只要路由到了这个Exchange,即不管任何的Routing key都直接放入到这个DLX队列中。
    2. 正常队列该如何和DLX绑定呢?
      1. 正常声明Exchange、queue、routing key绑定等等,但是需要在队列上加上一个参数arguments.put(“x-dead-letter-exchange”, “dlx.exchange”)
      2. 这样当队列出现死信的时候,就会自动把死信路由到死信队列中。
  4. 案例:
// 4.正常的声明交换机、队列, 以及绑定队列.
String exchangeName = "test_dlx_exchange";
String routingKey = "dlx.save";
String queueName = "test001";
channel.exchangeDeclare(exchangeName, "topic", true);

// 5.给正常Queue设置, 私信的Exchange. 必须给queueDeclare的arguments设置私信队列配置.
String DLXExchange = "dlx.exchange";

Map<String, Object> arguments = new HashMap<>(1);
arguments.put("x-dead-letter-exchange", DLXExchange); // 这个key是固定的, 不允许被修改.
channel.queueDeclare(queueName, true, false, false, arguments);
channel.queueBind(queueName, exchangeName, routingKey, null);


// 6. 声明私信Exchange和Queue.
channel.exchangeDeclare(DLXExchange, "topic", true);
channel.queueDeclare("DLX_queue", true, false, false, null);
// '#'代表任何的死信, 只要路由到了这个Exchange的时候, 都会被路由到这个queue中.
channel.queueBind("DLX_queue", DLXExchange, "#"); 

你可能感兴趣的:(RabbitMQ)