Implementation of a 100% reliable delivery solution for RabbitMQ messages (2)

Consumer custom monitoring

Inherit the DefaultConsumer and override the handleDelivery() method

public class Producer {
    public static final String MQ_HOST = "192.168.222.101";
    public static final String MQ_VHOST = "/";
    public static final int MQ_PORT = 5672;

    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 创建一个ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(MQ_HOST);//配置host
        connectionFactory.setPort(MQ_PORT);//配置port
        connectionFactory.setVirtualHost(MQ_VHOST);//配置vHost

        //2. 通过连接工厂创建连接
        Connection connection = connectionFactory.newConnection();
        //3. 通过connection创建一个Channel
        Channel channel = connection.createChannel();
        String exchange = "test_consumer_exchange";
        String routingKey = "consumer.save";

        //4. 通过Channel发送数据
        String message = "Hello Consumer Message";

        for (int i = 0; i < 5; i++) {
            channel.basicPublish(exchange,routingKey,null,message.getBytes());
        }
    }
}

public class Consumer {

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConn();

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

        String exchange = "test_consumer_exchange";
        String routingKey = "consumer.save";
        String queueName = "test_consumer_queue";

        //2. 声明一个exchange
        channel.exchangeDeclare(exchange,"topic",true,false,null);
        //3. 声明一个队列
        channel.queueDeclare(queueName,true,false,false,null);
        //4. 绑定
        channel.queueBind(queueName,exchange,routingKey);
        //5. 创建消费者
        QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
        //6. 设置Channel
        channel.basicConsume(queueName,true,new MyConsumer(channel));
       /* //7. 获取消息
       之前的方式,很ugly
        while (true) {
            //nextDelivery 会阻塞直到有消息过来
            QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println("收到:" + message);
        }*/
    }

    private static class MyConsumer extends DefaultConsumer {
        public MyConsumer(Channel channel) {
            super(channel);
        }

        @Override
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
            System.out.println("——consume message——");
            System.out.println("consumerTag:"+consumerTag);
            System.out.println("envelope:"+envelope);
            System.out.println("properties:"+properties);
            System.out.println("body:"+new String(body));
        }
    }
}


The print is as follows:

——consume message——
consumerTag:amq.ctag-DLKq_dy8aYspCTUBrnHTew
envelope:Envelope(deliveryTag=1, redeliver=false, exchange=test_consumer_exchange, routingKey=consumer.save)
properties:#contentHeader<basic>(content-type=null, content-encoding=null, headers=null, delivery-mode=null, priority=null, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null)
body:Hello Consumer Message
——consume message——
consumerTag:amq.ctag-DLKq_dy8aYspCTUBrnHTew
envelope:Envelope(deliveryTag=2, redeliver=false, exchange=test_consumer_exchange, routingKey=consumer.save)
properties:#contentHeader<basic>(content-type=null, content-encoding=null, headers=null, delivery-mode=null, priority=null, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null)
body:Hello Consumer Message
...

Message flow limit

Current limit on the consumer side:

Assuming that there are tens of thousands of unprocessed messages on the MQ server, just open a consumer client, and the following situation will occur: a
huge amount of messages are pushed over in an instant, but a single client cannot process so much data at the same time, resulting in the server collapse

solution:

  • RabbitMQ provides a qos (Quality of Service Assurance) function, that is, under the premise of non-automatic confirmation of messages (set autoAck to false), if a certain number of messages (by setting the value of Qos based on consume or channel) are not confirmed, Do not consume new messages

void BasicQos(unit prefetchSize,unshort prefetchCount,bool global)

  • prefetchSize: 0 The size limit of a single message consumed, 0 means no limit
  • prefetchCount: Do not push more than N messages to a consumer at the same time, that is, once there are N messages without ack, the consumer will block until there is a message ack
  • global: true\false true: the above settings apply to the channel level; false: apply to the consumer level
public class Producer {
    public static final String MQ_HOST = "192.168.222.101";
    public static final String MQ_VHOST = "/";
    public static final int MQ_PORT = 5672;

    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 创建一个ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(MQ_HOST);//配置host
        connectionFactory.setPort(MQ_PORT);//配置port
        connectionFactory.setVirtualHost(MQ_VHOST);//配置vHost

        //2. 通过连接工厂创建连接
        Connection connection = connectionFactory.newConnection();
        //3. 通过connection创建一个Channel
        Channel channel = connection.createChannel();
        String exchange = "test_qos_exchange";
        String routingKey = "qos.save";


        //4. 通过Channel发送数据
        String message = "Hello Qos Message";

        for (int i = 0; i < 5; i++) {
            channel.basicPublish(exchange,routingKey,null,message.getBytes());
        }
    }
}

public class Consumer {

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConn();

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

        String exchange = "test_qos_exchange";
        String routingKey = "qos.#";
        String queueName = "test_qos_queue";

        //2. 声明一个exchange
        channel.exchangeDeclare(exchange,"topic",true,false,null);
        //3. 声明一个队列
        channel.queueDeclare(queueName,true,false,false,null);
        //4. 绑定
        channel.queueBind(queueName,exchange,routingKey);
        //5. 创建消费者
        QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
        //6.限流方式 每次只推3条
        channel.basicQos(0,3,false);
        //7. 设置Channel autoAck一定要设置为false,才能做限流
        channel.basicConsume(queueName,false,new MyConsumer(channel));
        
    }

    private static class MyConsumer extends DefaultConsumer {
        private Channel channel;
        public MyConsumer(Channel channel) {
            super(channel);
            this.channel = channel;
        }

        @Override
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
            System.out.println("——consume message——");
            System.out.println("consumerTag:"+consumerTag);
            System.out.println("envelope:"+envelope);
            System.out.println("properties:"+properties);
            System.out.println("body:"+new String(body));
            // 手动签收
            channel.basicAck(envelope.getDeliveryTag(),false);
        }
    }
}

ACK on the consumer side and return to the queue

The consumer can perform manual ACK and NACK (not confirm, indicating failure)

  • When the consumer is consuming, if the business is abnormal, it can log records and then compensate
  • If there are serious problems such as server downtime, you need to manually perform ACK to ensure the successful consumption of the consumer

Return to the queue on the consumer side:

For messages that have not been successfully processed, the message is re-delivered to the Broker
generally in actual applications, it will be closed and returned to the queue

public class Producer {
    public static final String MQ_HOST = "192.168.222.101";
    public static final String MQ_VHOST = "/";
    public static final int MQ_PORT = 5672;

    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 创建一个ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(MQ_HOST);//配置host
        connectionFactory.setPort(MQ_PORT);//配置port
        connectionFactory.setVirtualHost(MQ_VHOST);//配置vHost

        //2. 通过连接工厂创建连接
        Connection connection = connectionFactory.newConnection();
        //3. 通过connection创建一个Channel
        Channel channel = connection.createChannel();
        String exchange = "test_ack_exchange";
        String routingKey = "ack.save";

        //4. 通过Channel发送数据
        String message = "Hello Ack Message";

        for (int i = 0; i < 5; i++) {
            Map<String,Object> headers = new HashMap<>();
            headers.put("num",i);
            AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
                    .deliveryMode(2) //持久化
                    .contentEncoding("UTF-8")
                    .headers(headers)
                    .build();
            channel.basicPublish(exchange,routingKey,properties,message.getBytes());
        }
    }
}
public class Consumer {

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConn();

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

        String exchange = "test_ack_exchange";
        String routingKey = "ack.#";
        String queueName = "test_ack_queue";

        //2. 声明一个exchange
        channel.exchangeDeclare(exchange,"topic",true,false,null);
        //3. 声明一个队列
        channel.queueDeclare(queueName,true,false,false,null);
        //4. 绑定
        channel.queueBind(queueName,exchange,routingKey);
        //5. autoAck = false
        channel.basicConsume(queueName,false,new MyConsumer(channel));


    }

    private static class MyConsumer extends DefaultConsumer {
        private Channel channel;
        public MyConsumer(Channel channel) {
            super(channel);
            this.channel = channel;
        }

        @Override
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
            System.out.println("——consume message——");
            System.out.println("body:"+new String(body));
            System.out.println("num:" + properties.getHeaders().get("num"));
            if ((Integer)properties.getHeaders().get("num") == 0 ) {
                //requeue:true表示重新入队,重传
                channel.basicNack(envelope.getDeliveryTag(),false,true);
            } else {
                channel.basicAck(envelope.getDeliveryTag(),false);
            }

        }
    }
}

Log:

...
——consume message——
body:Hello Ack Message
num:0
——consume message——
body:Hello Ack Message
num:0
——consume message——
body:Hello Ack Message
num:0
...

The message whose num is 0 will always return to the end of the MQ queue, and then print in a loop, because it has been unable to be consumed

TTL message

  • Time to Live (TTL), refers to the generation time of the message
  • RabbitMQ supports the expiration time of the message, which can be specified when the message is sent
  • It also supports the expiration time of the queue, starting from the time the message enters the queue, as long as the timeout time of the queue is exceeded, the message will be automatically cleared
 AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                .deliveryMode(2) //2:持久化投递;1:非持久化(未消费的消息重启后就没了)
                .contentEncoding("UTF-8")
                .expiration("5000")//5s 设置消息的TTL
                .headers(headers)
                .build();
      
            String message = "Hello";
            channel.basicPublish("","testQueue",properties,message.getBytes());
        }

Queue TTL:

Map<String, Object> args = new HashMap<>();  
args.put("x-expires", 1800000);  //30分钟
channel.queueDeclare("myqueue", false, false, false, args); 

Dead letter queue

  • Dead letter queue (Dead-Letter-Exchange, DLX)
  • Using DLX, when a message becomes a dead letter ( dead message:没有消费者去消费) in the queue , it can be republished to another Exchange, which is the dead letter queue
  • DLX is a normal Exchange, no difference from a normal Exchange, it can be specified on any queue
  • When there is a dead letter in this queue, RabbitMQ will automatically republish the message to the set Exchange, and then be routed to another queue
  • You can listen to the messages in this queue and do the corresponding processing

The situation where the news becomes a dead letter

  1. The message was rejected (basic.rejcet/basic.nack) and requeue=false
  2. Message TTL expired
  3. The queue reaches the maximum length

What should I do if the message processing consumed by the message middleware fails?
There is a special background thread to monitor whether the consumer (such as logistics) system is normal, whether it can be requested, and constantly monitor.
Once the consumer system is found to be back to normal, the background thread will take out the message that failed the consumer processing from the dead letter queue and re-execute the corresponding logic.

Dead letter queue setting

1. First, set the exchange and queue of the dead letter queue, and then bind:

  • Exchange:dlx.exchange
  • Queue:dlx.queue
  • RoutingKey:#

2. Normally declare the exchange, queue, and binding, but you need to add a parameter to the queue: (consumer里添加)arguments.put("x-dead-letter-exchange","dlx.exchange);
3. When the message expires, requeue, and the queue reaches the maximum length, the message can be directly routed to the dead letter queue


public class Producer {
    public static final String MQ_HOST = "192.168.222.101";
    public static final String MQ_VHOST = "/";
    public static final int MQ_PORT = 5672;

    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 创建一个ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(MQ_HOST);//配置host
        connectionFactory.setPort(MQ_PORT);//配置port
        connectionFactory.setVirtualHost(MQ_VHOST);//配置vHost

        //2. 通过连接工厂创建连接
        Connection connection = connectionFactory.newConnection();
        //3. 通过connection创建一个Channel
        Channel channel = connection.createChannel();
        String exchange = "test_dlx_exchange";
        String routingKey = "dlx.save";


        //4. 通过Channel发送数据
        String message = "Hello DLX Message";

        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                .deliveryMode(2) //2:持久化投递;1:非持久化(未消费的消息重启后就没了)
                .contentEncoding("UTF-8")
                .expiration("5000")//5s后如果没有消费端消费,会变成死信
                .build();

        for (int i = 0; i < 1; i++) {
            channel.basicPublish(exchange,routingKey,properties,message.getBytes());
        }

    }
}

public class Consumer {

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConn();

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

        String exchange = "test_dlx_exchange";
        String routingKey = "dlx.#";
        String queueName = "test_dlx_queue";

        String dlxExchange = "dlx.exchange";
        String dlxQueue = "dlx.queue";

        //2. 声明一个exchange
        channel.exchangeDeclare(exchange,"topic",true,false,null);
        Map<String,Object> arguments = new HashMap<>();

        //路由失败,重发到dlx.exchange
        arguments.put("x-dead-letter-exchange",dlxExchange);
        /**
         * 声明正常队列
         * arguments要设置到声明队列上
         */
        channel.queueDeclare(queueName,true,false,false,arguments);
        channel.queueBind(queueName,exchange,routingKey);

        //进行死信队列的声明
        channel.exchangeDeclare(dlxExchange,"topic",true,false,null);
        channel.queueDeclare(dlxQueue,true,false,false,null);
        channel.queueBind(dlxQueue,dlxExchange,"#");

    }

    private static class MyConsumer extends DefaultConsumer {
        public MyConsumer(Channel channel) {
            super(channel);
        }

        @Override
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
            System.out.println("——consume message——");
            System.out.println("body:"+new String(body));
        }
    }
}

Start Consumer first and perform operations such as generating queues
Exchanges包含dlx.exchange、test_dlx_exchange

Queues include test_ack_queue, test_dlx_queue
Then, in order to make the message a dead letter, stop Consumer

Finally,
There is no content in the dead letter queue
5 seconds after starting the Producer , the message is not consumed, and then it goes into the dead letter queue (note that the initial value in the dead letter queue is 1)
The message enters the dead letter queue

Transfer from: RabbitMQ learning-advanced features

Guess you like

Origin blog.csdn.net/eluanshi12/article/details/90264409