(6) Reliability of RabbitMQ messages in RabbitMQ Practical Tutorial (for Java developers)

Posted by Blob on Wed, 15 May 2019 11:52:53 +0200

Message Reliability

When using RabbitMQ in a project, we may encounter problems such as adding a record to the message middleware to record the expected message consumers to modify the order status when the user pays successfully, but the actual order status is not changed successfully in the end. In the face of such problems, we have the following ideas for investigation:

1. Has the message been successfully sent to the message middleware
 2. Is there a missing message? Has the message been consumed successfully?

In the production environment, message delivery/consumption errors are not allowed, because they may cause huge losses to enterprises. This blog will introduce how RabbitMQ guarantees the reliability of messages (producer guarantees reliable delivery of messages, consumer guarantees reliable consumption of messages, RabbitMQ persistence)

Producer guarantees reliable delivery of messages

To ensure that messages are delivered correctly to message middleware, RabbitMQ provides the following two configurations to ensure the reliability of message delivery:

1. We can set the Mandatory property when sending a message. If the Mandatory property is set, Return Method will be triggered when the message cannot be routed to the queue correctly. If Mandatory is not set, Broker will discard the message when the message cannot be routed to the queue correctly, so that we can process the related business in Return Method.

2.RabbitMQ also provides a Publisher Confirm. The producer sets Channel to Confirm mode. When the Confirm mode is set, all messages published on the channel are assigned a unique ID (from the beginning, the ID is unique in the same Channel range). Once the message is delivered to all matching queues, Broker sends an acknowledgement to the producer (including the unique ID of the message), which enables the producer to know the message. The destination queue has arrived correctly.

If the message and queue are persistent, the confirmation message will be written to disk and sent out. The DeliverTag field in the confirmation message sent back to the producer contains the serial number of the confirmation message. In addition, Broker can set the multiple field of basic.ack to indicate that all messages before the serial number have been processed (multiple is less than or equal if true). All deliveryTag messages have been delivered successfully. If they are false, it means that only messages equal to deliveryTag have been delivered successfully.

In addition to using Publisher Confirm mode, RabbitMQ also provides transaction mechanism to ensure message delivery, but using transaction will greatly reduce the throughput of the system, thus losing the significance of message middleware. This blog will not discuss it. The biggest advantage of Publisher Confirm mode is that it is asynchronous. Once a message producer application is published, it can continue sending the next message while waiting for the channel to return the confirmation. When the message is finally confirmed, the producer application can process the confirmation message by calling back ACK method. If RabbitMQ loses the message due to its internal error, production The user application can process the acknowledgement message by calling back the NACK method.

Publisher Confirm mechanism is much better than transaction mechanism in performance, but Publisher Confirm mechanism can not roll back. Once the server crashes, the producer can not get Confirm information, the producer itself does not know whether the message has been persisted, only continue to resend to ensure that the message is not lost, but if the previously persisted message will not be rolled back. In this way, there will be two identical messages in the queue, and the system needs to support de-duplication.

RabbitMQ Java Client

1. Create the Connection Factory Tool Class

public class ChannelUtils {
    public static Channel getChannelInstance(String connectionDescription) {
        try {
            ConnectionFactory connectionFactory = getConnectionFactory();
            Connection connection = connectionFactory.newConnection(connectionDescription);
            return connection.createChannel();
        } catch (Exception e) {
            throw new RuntimeException("Obtain Channel connection failed");
        }
    }

    private static ConnectionFactory getConnectionFactory() {
        ConnectionFactory connectionFactory = new ConnectionFactory();

        connectionFactory.setHost("192.168.56.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("roberto");
        connectionFactory.setPassword("roberto");

        connectionFactory.setAutomaticRecoveryEnabled(true);
        connectionFactory.setNetworkRecoveryInterval(10000);

        Map<String, Object> connectionFactoryPropertiesMap = new HashMap();
        connectionFactoryPropertiesMap.put("principal", "RobertoHuang");
        connectionFactoryPropertiesMap.put("description", "RGP Order system V2.0");
        connectionFactoryPropertiesMap.put("emailAddress", "RobertoHuang@foxmail.com");
        connectionFactory.setClientProperties(connectionFactoryPropertiesMap);

        return connectionFactory;
    }
}

2. Creating a message producer sets the Mandatory property of channel. basic Publish () method to true to open message validation, adds ReturnListener to Channel, opens message validation mode using channel.confirmSelect() and adds ConfirmListener to channel to send two messages (modifying Routing Key of one message to make the message not routed correctly)

public class MessageProducer {
    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = ChannelUtils.getChannelInstance("RGP Order System Message Producer");

        channel.exchangeDeclare("roberto.order", BuiltinExchangeType.DIRECT, true, false, false,new HashMap<>());

        AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder().deliveryMode(2).contentType("UTF-8").build();

        // Callback ReturnListener when the message is not routed correctly
        channel.addReturnListener(new ReturnListener() {
            @Override
            public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("replyCode:" + replyCode);
                System.out.println("replyText:" + replyText);
                System.out.println("exchange:" + exchange);
                System.out.println("routingKey:" + routingKey);
                System.out.println("properties:" + properties);
                System.out.println("body:" + new String(body, "UTF-8"));
            }
        });

        // Open message confirmation
        channel.confirmSelect();
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("----------Ack----------");
                System.out.println(deliveryTag);
                System.out.println(multiple);
            }

            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("----------Nack----------");
                System.out.println(deliveryTag);
                System.out.println(multiple);
            }
        });

        // Set the mandatory property to true
        channel.basicPublish("roberto.order", "add", true, basicProperties, "Order information".getBytes());
        channel.basicPublish("roberto.order", "addXXX", true, basicProperties, "Order information".getBytes());
    }
}

3. Creating message consumers

public class MessageConsumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = ChannelUtils.getChannelInstance("RGP Order System Message Consumer");

        AMQP.Queue.DeclareOk declareOk = channel.queueDeclare("roberto.order.add", true, false, false, new HashMap<>());

        channel.exchangeDeclare("roberto.order", BuiltinExchangeType.DIRECT, true, false, false, new HashMap<>());

        channel.queueBind(declareOk.getQueue(), "roberto.order", "add", new HashMap<>());

        channel.basicConsume(declareOk.getQueue(), true, "RGP Order system ADD Dealing with Logical Consumers", new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(consumerTag);
                System.out.println(envelope.toString());
                System.out.println(properties.toString());
                System.out.println("Message content:" + new String(body));
            }
        });
    }
}

4. Start message consumers and producers one after another. The console output is as follows

replyCode:312
replyText:NO_ROUTE
exchange:roberto.order
routingKey:addXXX
properties:#contentHeader<basic>(content-type=UTF-8, content-encoding=null, headers=null, delivery-mode=2, 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:Order message

----------Ack----------
2
false

----------Ack----------
1
false

The above output shows that the handleReturn method of ReturnListener is invoked when the message cannot be confirmed to be routed, and that if the message is not routed correctly, the ACK method is still used, Publisher Confirm can only guarantee that the message reaches the message middleware. If you want to test the NACK method, you can stop the RabbitMQ service for testing before sending a message is confirmed.

Reliable delivery of Spring AMQP messages

1. Create a message producer configuration class that sets Publisher Returns of CachingConnectionFactory to true, Mandatory attribute of RabbitTemplate to true, and ReturnCallback for RabbitTemplate. Set Publisher Confirms for Caching Connection Factory to true and Confirm Callback for Rabbit Template

@Configuration
public class SpringAMQPProducerConfig {
    @Bean
    public ConnectionFactory connectionFactory() {
        com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();

        connectionFactory.setHost("192.168.56.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("roberto");
        connectionFactory.setPassword("roberto");

        connectionFactory.setAutomaticRecoveryEnabled(true);
        connectionFactory.setNetworkRecoveryInterval(10000);

        Map<String, Object> connectionFactoryPropertiesMap = new HashMap();
        connectionFactoryPropertiesMap.put("principal", "RobertoHuang");
        connectionFactoryPropertiesMap.put("description", "RGP Order system V2.0");
        connectionFactoryPropertiesMap.put("emailAddress", "RobertoHuang@foxmail.com");
        connectionFactory.setClientProperties(connectionFactoryPropertiesMap);

        CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(connectionFactory);
        // Set Publisher Confirms for Caching Connection Factory to true
        cachingConnectionFactory.setPublisherConfirms(true);
        // Set Publisher Returns of Caching Connection Factory to true
        cachingConnectionFactory.setPublisherReturns(true);

        return cachingConnectionFactory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        // Set the Manatory property of RabbitTemplate to true
        rabbitTemplate.setMandatory(true);
        // Setting Return Callback for Rabbit Template
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                try {
                    System.out.println("replyCode:" + replyCode);
                    System.out.println("replyText:" + replyText);
                    System.out.println("exchange:" + exchange);
                    System.out.println("routingKey:" + routingKey);
                    System.out.println("properties:" + message.getMessageProperties());
                    System.out.println("body:" + new String(message.getBody(), "UTF-8"));
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }
        });
        // Setting Confirm Callback for Rabbit Template
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println(ack);
                System.out.println(cause);
                System.out.println(correlationData.getId());
            }
        });

        return rabbitTemplate;
    }
}

2. Create a producer startup class to send two messages (modify the Routing key of one message so that the message cannot be routed correctly), and set the correlation Data property when sending the message

@ComponentScan(basePackages = "roberto.growth.process.rabbitmq.dependable.deliver.spring.amqp.producer")
public class ProducerApplication {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProducerApplication.class);

        RabbitAdmin rabbitAdmin = context.getBean(RabbitAdmin.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        rabbitAdmin.declareExchange(new DirectExchange("roberto.order", true, false, new HashMap<>()));

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
        messageProperties.setContentType("UTF-8");
        Message message = new Message("Order information".getBytes(), messageProperties);

        rabbitTemplate.send("roberto.order", "add", message, new CorrelationData("201210704116"));
        rabbitTemplate.send("roberto.order", "addXXX", message, new CorrelationData("201210704116"));
    }
}

3. Create consumer configuration classes

@Configuration
public class SpringAMQPConsumerConfig {
    @Bean
    public ConnectionFactory connectionFactory() {
        com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();

        connectionFactory.setHost("192.168.56.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("roberto");
        connectionFactory.setPassword("roberto");

        connectionFactory.setAutomaticRecoveryEnabled(true);
        connectionFactory.setNetworkRecoveryInterval(10000);

        Map<String, Object> connectionFactoryPropertiesMap = new HashMap();
        connectionFactoryPropertiesMap.put("principal", "RobertoHuang");
        connectionFactoryPropertiesMap.put("description", "RGP Order system V2.0");
        connectionFactoryPropertiesMap.put("emailAddress", "RobertoHuang@foxmail.com");
        connectionFactory.setClientProperties(connectionFactoryPropertiesMap);

        CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(connectionFactory);
        return cachingConnectionFactory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public MessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory) {
        SimpleMessageListenerContainer messageListenerContainer = new SimpleMessageListenerContainer();
        messageListenerContainer.setConnectionFactory(connectionFactory);
        messageListenerContainer.setQueueNames("roberto.order.add");

        messageListenerContainer.setConcurrentConsumers(5);
        messageListenerContainer.setMaxConcurrentConsumers(10);

        Map<String, Object> argumentMap = new HashMap();
        messageListenerContainer.setConsumerArguments(argumentMap);

        messageListenerContainer.setConsumerTagStrategy(new ConsumerTagStrategy() {
            @Override
            public String createConsumerTag(String s) {
                return "RGP Order system ADD Dealing with Logical Consumers";
            }
        });

        messageListenerContainer.setAutoStartup(false);
        messageListenerContainer.setMessageListener(new MessageListener() {
            @Override
            public void onMessage(Message message) {
                try {
                    System.out.println(new String(message.getBody(), "UTF-8"));
                    System.out.println(message.getMessageProperties());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        return messageListenerContainer;
    }
}

4. Create a consumer startup class

@ComponentScan(basePackages = "roberto.growth.process.rabbitmq.dependable.deliver.spring.amqp.consumer")
public class ConsumerApplication {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerApplication.class);

        RabbitAdmin rabbitAdmin = context.getBean(RabbitAdmin.class);
        MessageListenerContainer messageListenerContainer = context.getBean(MessageListenerContainer.class);

        rabbitAdmin.declareQueue(new Queue("roberto.order.add", true, false, false, new HashMap<>()));

        rabbitAdmin.declareExchange(new DirectExchange("roberto.order", true, false, new HashMap<>()));

        rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue("roberto.order.add")).to(new DirectExchange("roberto.order")).with("add"));

        messageListenerContainer.start();
    }
}

5. Start message consumer and producer console output in turn as follows

replyCode:312
replyText:NO_ROUTE
exchange:roberto.order
routingKey:addXXX
properties:MessageProperties [headers={}, contentType=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0]
body:Order information

true
null
201210704116

true
null
201210704116

The above output indicates that the returned Message method of Return Callback is invoked when the message cannot be confirmed as routing, and that Publisher Confirm can only guarantee that the message reaches the message middleware if the value of ACK is still true if the message is not routed correctly. If you want ACK to be false, you can stop the RabbitMQ service for testing before sending a message is confirmed.

At the same time, Spring AMQP encapsulates Publisher Confirm. We can pass correlation data when sending messages. When calling message confirmation callback method, we can get Correlation data when sending messages. This function provides great convenience for our business processing. We no longer need to spend money to maintain Delivery Tag, we can use correlation data directly. getId() method to get business primary key

Consumers Guarantee Reliable Information Consumption

In the above chapters, we use message return and message confirmation mechanism to ensure that messages can reach the message middleware and be routed to the queue correctly, but we can not get feedback when consumers consume messages, and we can not know whether the messages have been consumed successfully. In order to achieve this function, RabbitMQ provides Consumer Acknowledgements mechanism. Users of Consumer Acknowledgements can give feedback to Broker after consumers consume messages. Broker processes messages according to feedback.

RabbitMQ Java Client

1. Create the Connection Factory Tool Class

public class ChannelUtils {
    public static Channel getChannelInstance(String connectionDescription) {
        try {
            ConnectionFactory connectionFactory = getConnectionFactory();
            Connection connection = connectionFactory.newConnection(connectionDescription);
            return connection.createChannel();
        } catch (Exception e) {
            throw new RuntimeException("Obtain Channel connection failed");
        }
    }

    private static ConnectionFactory getConnectionFactory() {
        ConnectionFactory connectionFactory = new ConnectionFactory();

        connectionFactory.setHost("192.168.56.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("roberto");
        connectionFactory.setPassword("roberto");

        connectionFactory.setAutomaticRecoveryEnabled(true);
        connectionFactory.setNetworkRecoveryInterval(10000);

        Map<String, Object> connectionFactoryPropertiesMap = new HashMap();
        connectionFactoryPropertiesMap.put("principal", "RobertoHuang");
        connectionFactoryPropertiesMap.put("description", "RGP Order system V2.0");
        connectionFactoryPropertiesMap.put("emailAddress", "RobertoHuang@foxmail.com");
        connectionFactory.setClientProperties(connectionFactoryPropertiesMap);

        return connectionFactory;
    }
}

2. Create a message producer to send two messages

public class MessageProducer {
    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = ChannelUtils.getChannelInstance("RGP Order System Message Producer");

        channel.exchangeDeclare("roberto.order", BuiltinExchangeType.DIRECT, true, false, false, new HashMap<>());

        AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder().deliveryMode(2).contentType("UTF-8").build();

        channel.basicPublish("roberto.order", "add", false, basicProperties, "Order information".getBytes());
        channel.basicPublish("roberto.order", "add", false, basicProperties, "Order Information 2".getBytes());
    }
}

3. Create message consumers, use channel.basicConsume() method to set the message automatic confirmation to false, use channel.basicAck() to confirm the message manually while consuming the message, and channel.basicNack() to reject the message.

public class MessageConsumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = ChannelUtils.getChannelInstance("RGP Order System Message Consumer");

        AMQP.Queue.DeclareOk declareOk = channel.queueDeclare("roberto.order.add", true, false, false, new HashMap<>());

        channel.exchangeDeclare("roberto.order", BuiltinExchangeType.DIRECT, true, false, false, new HashMap<>());

        channel.queueBind(declareOk.getQueue(), "roberto.order", "add", new HashMap<>());

        channel.basicConsume(declareOk.getQueue(), false, "RGP Order system ADD Dealing with Logical Consumers", new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    System.out.println(consumerTag);
                    System.out.println(envelope.toString());
                    System.out.println(properties.toString());
                    System.out.println("Message content:" + new String(body));
                    if ("Order Information 2".equals(new String(body))) {
                        throw new RuntimeException();
                    } else {
                        channel.basicAck(envelope.getDeliveryTag(), false);
                    }
                } catch (Exception e) {
                    channel.basicNack(envelope.getDeliveryTag(), false, true);
                }
            }
        });
    }
}

4. Start message consumers and producers one after another to view the RabbitMQ management console



From the above picture, we can see that two messages are in the unacknowledged state first. One message is consumed successfully after calling channel.basicAck(), and the other message is sent back to the queue because of unacknowledged re-sending. All of them are back to the Ready state, so the cycle is repeated.

Spring AMQP message reliable consumption

1. Create a message producer configuration class

@Configuration
public class SpringAMQPProducerConfig {
    @Bean
    public ConnectionFactory connectionFactory() {
        com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();

        connectionFactory.setHost("192.168.56.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("roberto");
        connectionFactory.setPassword("roberto");

        connectionFactory.setAutomaticRecoveryEnabled(true);
        connectionFactory.setNetworkRecoveryInterval(10000);

        Map<String, Object> connectionFactoryPropertiesMap = new HashMap();
        connectionFactoryPropertiesMap.put("principal", "RobertoHuang");
        connectionFactoryPropertiesMap.put("description", "RGP Order system V2.0");
        connectionFactoryPropertiesMap.put("emailAddress", "RobertoHuang@foxmail.com");
        connectionFactory.setClientProperties(connectionFactoryPropertiesMap);

        CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(connectionFactory);
        return cachingConnectionFactory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        return new RabbitTemplate(connectionFactory);
    }
}

2. Create a producer startup class

@ComponentScan(basePackages = "roberto.growth.process.rabbitmq.dependable.consumer.spring.amqp.producer")
public class ProducerApplication {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProducerApplication.class);

        RabbitAdmin rabbitAdmin = context.getBean(RabbitAdmin.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        rabbitAdmin.declareExchange(new DirectExchange("roberto.order", true, false, new HashMap<>()));

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
        messageProperties.setContentType("UTF-8");
        Message message = new Message("Order information".getBytes(), messageProperties);
        rabbitTemplate.send("roberto.order", "add", message, new CorrelationData("201210704116"));

        MessageProperties messageProperties2 = new MessageProperties();
        messageProperties2.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
        messageProperties2.setContentType("UTF-8");
        Message message2 = new Message("Order Information 2".getBytes(), messageProperties2);
        rabbitTemplate.send("roberto.order", "add", message2, new CorrelationData("201210704116"));
    }
}

3. Create a consumer configuration class to set the AcknowledgeMode l of MessageListener Container to manual, and Channel Aware MessageListener for MessageListener Container to manually use channel.basicAck() for message confirmation and channel.basicNack() for message rejection when consuming messages.

@Configuration
public class SpringAMQPConsumerConfig {
    @Bean
    public ConnectionFactory connectionFactory() {
        com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();

        connectionFactory.setHost("192.168.56.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("roberto");
        connectionFactory.setPassword("roberto");

        connectionFactory.setAutomaticRecoveryEnabled(true);
        connectionFactory.setNetworkRecoveryInterval(10000);

        Map<String, Object> connectionFactoryPropertiesMap = new HashMap();
        connectionFactoryPropertiesMap.put("principal", "RobertoHuang");
        connectionFactoryPropertiesMap.put("description", "RGP Order system V2.0");
        connectionFactoryPropertiesMap.put("emailAddress", "RobertoHuang@foxmail.com");
        connectionFactory.setClientProperties(connectionFactoryPropertiesMap);

        CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(connectionFactory);
        return cachingConnectionFactory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public MessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory) {
        SimpleMessageListenerContainer messageListenerContainer = new SimpleMessageListenerContainer();
        messageListenerContainer.setConnectionFactory(connectionFactory);
        messageListenerContainer.setQueueNames("roberto.order.add");

        messageListenerContainer.setConcurrentConsumers(5);
        messageListenerContainer.setMaxConcurrentConsumers(10);

        Map<String, Object> argumentMap = new HashMap();
        messageListenerContainer.setConsumerArguments(argumentMap);

        messageListenerContainer.setConsumerTagStrategy(new ConsumerTagStrategy() {
            @Override
            public String createConsumerTag(String s) {
                return "RGP Order system ADD Dealing with Logical Consumers";
            }
        });

        messageListenerContainer.setAutoStartup(false);
        messageListenerContainer.setMessageListener(new ChannelAwareMessageListener() {
            @Override
            public void onMessage(Message message, Channel channel) throws Exception {
                try {
                    System.out.println(new String(message.getBody(), "UTF-8"));
                    System.out.println(message.getMessageProperties());
                    if ("Order Information 2".equals(new String(message.getBody(), "UTF-8"))) {
                        throw new RuntimeException();
                    } else {
                        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                    }
                } catch (Exception e) {
                    channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
                }
            }
        });
        // Set message confirmation mode to manual mode
        messageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        return messageListenerContainer;
    }
}

4. Create a consumer startup class

@ComponentScan(basePackages = "roberto.growth.process.rabbitmq.dependable.consumer.spring.amqp.consumer")
public class ConsumerApplication {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerApplication.class);

        RabbitAdmin rabbitAdmin = context.getBean(RabbitAdmin.class);
        MessageListenerContainer messageListenerContainer = context.getBean(MessageListenerContainer.class);

        rabbitAdmin.declareQueue(new Queue("roberto.order.add", true, false, false, new HashMap<>()));

        rabbitAdmin.declareExchange(new DirectExchange("roberto.order", true, false, new HashMap<>()));

        rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue("roberto.order.add")).to(new DirectExchange("roberto.order")).with("add"));

        messageListenerContainer.start();
    }
}

The above code uses Spring AMQP to achieve the same effect as RabbitMQ Java Client.

Note: AcknowledgeMode has three optional values: NONE, MANUAL and AUTO. NONE is equivalent to autoAck=true for automatic confirmation, MANUAL is equivalent to autoAck=false for manual confirmation, AUTO is based on the implementation of the method to determine whether to confirm or reject, if the message is successfully consumed, automatic confirmation, if the AmqpRejectAndDontRequeueException message is thrown when consuming the message will be rejected and will not be re-queued, if the ImmediateAcowleKnowleAmqpE is thrown. Xception confirms the message, and if other exceptions are thrown, the message is rejected and re-queued. For more details, consult SimpleMessageListenerContainer's doReceiveAndExecute() method for access

RabbitMQ persistence

In the above section, we describe how to ensure the reliability of producer and consumer messages, but assume that the RabbitMQ server is down during operation, and the message will be lost if there is no persistence operation before. So using RabbitMQ is usually recommended to turn on persistence

1. Switch persistence specifies durable as true when declared
 2. Queue persistence specifies durable as true at declaration time
 3. Message persistence specifies delivery_mode of 2 at declaration time

Only by doing these operations can we guarantee the reliability of RabbitMQ messages

Topics: RabbitMQ Spring Java less