Distributed transaction of rocketMQ messages

Principle
Half (Prepare) Message
refers to a message that cannot be delivered temporarily. The sender has successfully sent the message to the MQ server, but the server has not received the second confirmation of the message from the producer. At this time, the message is marked as In the "temporarily unavailable" state, messages in this state are half messages.
The Message Status Check
caused the loss of the second confirmation of a transaction message due to network flashing, restart of the producer application, etc. When the MQ server scans and finds that a message is "half message" for a long time, it needs to actively ask the message producer The final status of the message (Commit or Rollback), this process is the message back check.

Execution process
1. The sender sends a message to the MQ server.
2. After the MQ Server successfully persists the message, it ACKs the sender to confirm that the message has been sent successfully. At this time, the message is a half message.
3. The sender starts to execute the local transaction logic.
4. The sender submits a second confirmation (Commit or Rollback) to the MQ Server according to the execution result of the local transaction. When the MQ Server receives the Commit status, the half message is marked as deliverable, and the subscriber will eventually receive the message; MQ Server receives To the Rollback state, the half message will be deleted, and the subscriber will not accept the message.
5. In the special case of network disconnection or application restart, the second confirmation submitted in step 4 above does not reach the MQ Server. After a fixed period of time, MQ Server will initiate a message back-check for the message.
6. After receiving the message, the sender needs to check the final result of the local transaction execution of the corresponding message.
7. The sender submits the second confirmation again according to the final status of the local transaction obtained by the inspection, and the MQ Server still performs the operation on the half-message according to step 4.

 

Code test

TransactionConsumer.java

package com.woodie.rocketmq.transaction;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import java.io.UnsupportedEncodingException;
import java.util.List;
public class TransactionConsumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new
                DefaultMQPushConsumer("HAOKE_CONSUMER");
        consumer.setNamesrvAddr("192.168.142.128:9876");
        // 订阅topic,接收此Topic下的所有消息
        consumer.subscribe("pay_topic", "*");
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt msg : msgs) {
                    try {
                        System.out.println(new String(msg.getBody(), "UTF-8"));
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.start();
    }
}

 

TransactionListenerImpl.java

package com.woodie.rocketmq.transaction;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.HashMap;
import java.util.Map;

/**
 * 事务消息需要实现TransactionListener这个接口
 */
public class TransactionListenerImpl implements TransactionListener {
    private static Map<String, LocalTransactionState> STATE_MAP = new HashMap<>();
    /**
     * 执行具体的业务逻辑
     * @param msg 发送的消息对象
     * @param arg
     * @return
     */
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        try {
            System.out.println("用户A账户减500元.");
            Thread.sleep(500); //模拟调用服务

            // 这里用来模拟事务失败的情况
            // System.out.println(1/0);
            System.out.println("用户B账户加500元.");
            Thread.sleep(500);
            // 回查使用到,
            // 将事务的状态写入到map中,回查通过事件的id,去获取到事务是否成功的值
            STATE_MAP.put(msg.getTransactionId(), LocalTransactionState.COMMIT_MESSAGE);
            // 二次提交确认, 根据事务完成的情况设置返回值,
            return LocalTransactionState.COMMIT_MESSAGE;
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 回查使用到,
        // 将事务的状态写入到map中,回查通过事件的id,去获取到事务是否成功的值
        STATE_MAP.put(msg.getTransactionId(), LocalTransactionState.ROLLBACK_MESSAGE);
        // 回滚
        return LocalTransactionState.ROLLBACK_MESSAGE;
    }
    /**
     * 消息回查
     * @param msg
     * @return
     */
    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        // 根据事务的id获取到消息的状态,做到回查的动作
        return STATE_MAP.get(msg.getTransactionId());
    }
}

TransactionProducer.java

package com.woodie.rocketmq.transaction;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.Message;
public class TransactionProducer {
    public static void main(String[] args) throws Exception {

        // 带有事务的producer
        TransactionMQProducer producer = new TransactionMQProducer("transaction_producer");
        //  设置nameserver的地址
        producer.setNamesrvAddr("192.168.142.128:9876");
        // 设置事务监听器, 具体需要 做那些业务
        producer.setTransactionListener(new TransactionListenerImpl());
        producer.start();
        // 发送消息
        Message message = new Message("pay_topic", "用户A给用户B转账500元".getBytes("UTF-8"));
        producer.sendMessageInTransaction(message, null);
        Thread.sleep(999999);
        producer.shutdown();
    }
}

 

Guess you like

Origin blog.csdn.net/qq_26896085/article/details/104936325