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

MOOC: RabbitMQ message middleware fast introduction and practical course

RabbitMQ learning-advanced features

RabbitMQ message middleware technology in detail (3)-in-depth RabbitMQ advanced features
https://blog.csdn.net/u012211603/article/details/86118129

How to ensure 100% successful delivery of messages

What is reliable delivery on the production side

Guarantee the successful sending of the message
Guarantee the successful reception of the MQ node The
sender receives the confirmation response from the MQ node (Broker)
Complete message compensation mechanism

Production end-reliability delivery

Common solutions:

The message is dropped from the library, and the message status is marked

Insert picture description here
BIZ DB: Order database (or other specific business)
MSG DB: Message database
Step 1: Put the order into the database , create a MSG (status 0) into the MSG DB database
Step 2: Send the message to
Step 3: Monitor Message response (from Broker)
Step 4: Modify the status of the message to 1 (success)
Step 5: The distributed timing task captures the message with
the state 0 Step 6: Retransmit the message with the state 0
Step 7: If you have tried more than 3 times (can be modified according to the actual situation), set the status to 2 (message delivery failure status)

This kind of scheme needs to enter the library twice, and the performance may not be so good in high concurrency scenarios

Delayed delivery of messages, second confirmation, callback check

Insert picture description here
The second solution can avoid the simultaneous storage of message objects and business objects

Upstream service: upstream service, may be the production end
Downstream service: downstream service, may be the consumer end
MQ Broker: may be the cluster
Callback service: callback service, listen to confirm messages

Step 1: First, the business data is in the database, and then the first message is sent after success.
Step 2: The second message is sent immediately (can be used to find the first message) for delay (maybe 2,3 minutes) After sending the message) check
the message delivery . Step 3: After the Broker receives the message, the consumer will process
the message . Step 4: After the processing is successful, send a confirm message
. Step 5: After receiving the confirm message, store the message persistently
Step 6: After receiving the delay message, check the DB database. If the corresponding first message has been processed, do nothing; if the delay message is received, check the DB database and find that the corresponding first message has failed to process ( Or no record), then send a retransmission command to the upstream service, loop step 1

Message idempotence

Idempotence

For example, we execute a SQL statement to update the inventory (optimistic lock):

update T_REPS set count = count - 1,version = version + 1 where version = 1

The first step is to find out the version. The
second step is to update through this version.

Idempotence can be guaranteed.
So, what is idempotence?
Performing an operation, no matter how many times it is performed, the result is the same, that is, it is idempotent.

How to avoid repeated consumption

How to avoid repeated consumption of messages during peak business periods when massive orders are generated?

  • The consumer realizes idempotence, and then never consumes multiple times, even if it receives multiple identical messages

Mainstream solution

There are two mainstream solutions to achieve idempotence on the consumer side:
unique ID + fingerprint code mechanism,
using database primary keys to remove duplicates

Fingerprint code: It may be a business rule, time stamp + a unique information code within a specific bank range, which can guarantee the absolute uniqueness of this operation

such asselect count(1) from T_ORDER where id = <唯一ID+指纹码>

Set the unique ID+fingerprint code as the primary key. If the above SQL returns 1, it means that the operation has been performed, and there is no need to operate again; otherwise, perform the operation

Advantages: Simple to implement
Disadvantages: high concurrency, there is a performance bottleneck for database writes (solution: use ID to sub-database and table for algorithm routing)

Use Redis's atomicity to achieve

  • Via commands such as setnx

SET order number timestamp expiration time

SET 1893505609317740 1466849127 EX 300 NX

Questions to consider when using Redis for idempotence:

If you want to store data in the database, the key problem to be solved is how to achieve data consistency between the database and the cache.
If you don’t drop the library, then there are all in the cache. How to set the timing synchronization strategy (Synchronization refers to storing data in the database, not dropping the library means not dropping the library temporarily, and it is impossible to never leave the library)

Message delivery mechanism

Confirm confirmation message

  • Message confirmation means that after the producer’s message is delivered, if the Broker receives the message, it will give the producer a response
  • The producer receives the response to determine whether the message is sent to the Broker normally. This method is also the core of the reliable delivery of the message to ensure
    Insert picture description here
    that the producer sends the message and monitors the confirmation asynchronously.

How to realize Confirm confirmation message?

  1. Turn on the confirmation mode on the channel: channel.confirmSelect()
  2. Add a listener on the channel: addConfirmListener, listen to the return results of success and failure, and retransmit or log the message according to the specific return result, etc.
public class Producer {

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        Connection connection = ConnectionUtil.getConn();
        //1. 通过connection创建一个Channel
        Channel channel = connection.createChannel();
        //2.指定消息确认模式
        channel.confirmSelect();
        String exchangeName = "test_confirm_exchange";
        String routingKey = "confirm.save";

        //3. 通过Channel发送数据
        String message = "Hello from Producer";
        channel.basicPublish(exchangeName,routingKey,null,message.getBytes());

        //4. 添加一个确认监听
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                //成功的情况 deliveryTag:消息的唯一标签;
                System.out.println("——get ack——");
            }

            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                //失败的情况
                System.out.println("——have no  ack——");
            }
        });

        // 关闭掉就没confirm了
        // CloseTool.closeElegantly(channel,connection);

    }
}

public class Consumer {

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

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

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

return return message

  • Return Listener is used to process some unroutable messages
  • The producer specifies Exchange and RoutingKey, delivers the message to a certain queue, and then the consumer listens to the queue for message processing
  • But in some cases, when sending a message, if the current exchange does not exist or the specified routing key routing fails, at this time, if you need to listen for such unreachable messages, you must use the return listener

return message mechanism

There is a key configuration item in the basic API:

  • Mandatory: If true, the listener will receive the unreachable message, and then perform post-rough processing; if false, the broker will automatically delete a message
    Insert picture description here
    sent by the sender, but no Exchange is found, you can pass return listener listens to these messages
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_return_exchange";
        String routingKey = "return.save";
        String routingKeyError = "abc.save";


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

        channel.addReturnListener((replyCode, replyText, exchange1, routingKey1, properties, body) -> {
            System.out.println("——handle return——");
            System.out.println("replyCode:" + replyCode);
            System.out.println("replyText:" + replyText);
            System.out.println("exchange1:" + exchange1);
            System.out.println("routingKey1:" + routingKey1);
            System.out.println("properties:" + properties);
            System.out.println("body:" + new String(body));
        });

        //mandatory : true
        //channel.basicPublish(exchange,routingKey,true,null,message.getBytes());
        channel.basicPublish(exchange,routingKeyError,true,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_return_exchange";
        String routingKey = "return.#";
        String queueName = "test_return_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,queueingConsumer);
        //7. 获取消息
        while (true) {
            //nextDelivery 会阻塞直到有消息过来
            QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println("收到:" + message);
        }


    }
}

When the execution channel.basicPublish(exchange,routingKey,true,null,message.getBytes());message on the production side can be sent successfully, you can also see the printing from the consumer side

When the execution channel.basicPublish(exchange,routingKeyError,true,null,message.getBytes());message sending fails, because the routing fails, the production side can see the following print:

——handle return——
replyCode:312
replyText:NO_ROUTE
exchange1:test_return_exchange
routingKey1:abc.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 Return Message

If the production side sets mandatory to false, ReturnListener will not make a callback

Guess you like

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