【消息队列RocketMQ】三、RocketMQ 高级特性:事务消息、顺序消息与延时消息

本篇文章详细介绍了 RocketMQ 的事务消息、顺序消息与延时消息这三大高级特性,包括原理、CentOS 7 下的配置与使用方法,以及对应的应用场景。

一、事务消息​

1.1 事务消息原理​

事务消息是 RocketMQ 为解决分布式事务一致性问题而设计的特性,基于两阶段提交(2PC)实现。其核心流程如下:​

        1、第一阶段:Half Message:Producer 发送一条 “半消息” 到 Broker。半消息与普通消息的区别在于,此时 Consumer 无法消费该消息。Broker 接收到半消息后,会将其持久化并返回响应给 Producer。​

        2、第二阶段:本地事务执行:Producer 收到 Broker 对半消息的确认响应后,开始执行本地事务。​

        3、第二阶段:消息状态回查:若 Producer 在执行本地事务过程中出现异常,或因网络问题无法及时向 Broker 发送事务状态,Broker 会定时对这些半消息进行回查,询问 Producer 该消息对应的本地事务是否执行成功,以确定最终的消息状态。​

        4、第二阶段:消息提交或回滚:根据本地事务的执行结果,Producer 向 Broker 发送 Commit 或 Rollback 指令。若本地事务执行成功,发送 Commit 指令,Broker 将半消息标记为可消费状态;若执行失败,发送 Rollback 指令,Broker 删除该半消息。​

1.2 配置与使用​

        1、配置文件修改:在 CentOS 7 上,若使用 RocketMQ 自带示例代码,通常无需额外修改配置文件。但在实际项目中,如需调整事务消息相关参数,可修改 Broker 的配置文件broker.conf。例如,调整事务消息回查间隔时间,可添加如下配置:

transactionCheckInterval=60000 # 单位为毫秒,这里设置为1分钟

        2、代码示例:

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.concurrent.atomic.AtomicInteger;

public class TransactionProducer {
    private static final AtomicInteger TRANSACTION_ID = new AtomicInteger(0);

    public static void main(String[] args) throws MQClientException, InterruptedException {
        TransactionMQProducer producer = new TransactionMQProducer("transaction_producer_group");
        producer.setNamesrvAddr("localhost:9876");
        producer.setTransactionListener(new TransactionListener() {
            @Override
            public LocalTransactionState executeLocalTransaction(Message message, Object o) {
                // 模拟本地事务执行
                try {
                    Thread.sleep(2000);
                    // 这里可根据业务逻辑返回不同状态
                    return LocalTransactionState.COMMIT_MESSAGE;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return LocalTransactionState.ROLLBACK_MESSAGE;
                }
            }

            @Override
            public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
                System.out.println("Checking transaction for message: " + messageExt.getMsgId());
                // 可根据消息ID等信息查询本地事务状态
                return LocalTransactionState.COMMIT_MESSAGE;
            }
        });
        producer.start();

        Message msg = new Message("TransactionTopic", "TagA",
                ("Transaction Message " + TRANSACTION_ID.incrementAndGet()).getBytes());
        producer.sendMessageInTransaction(msg, null);

        Thread.sleep(10000);
        producer.shutdown();
    }
}

在上述代码中,创建了TransactionMQProducer实例,设置 NameServer 地址和TransactionListener。TransactionListener接口的executeLocalTransaction方法用于执行本地事务,checkLocalTransaction方法用于处理 Broker 的事务状态回查。通过sendMessageInTransaction方法发送事务消息。​

1.3 应用场景​

事务消息适用于电商订单处理、金融转账等对数据一致性要求较高的场景。例如在电商系统中,用户下单后,需要同时扣减库存和生成订单,这两个操作可通过事务消息确保要么都成功,要么都失败。

二、顺序消息​

2.1 顺序消息原理​

顺序消息是指消息按照特定的顺序进行发送和消费。RocketMQ 通过将同一业务的消息发送到同一个队列,并确保消费者按顺序消费该队列中的消息,实现消息的顺序性。可分为分区有序和全局有序:​

  • 分区有序:同一 Topic 下的消息,根据一定的规则(如订单 ID)分配到同一个队列,在该队列内保证消息的顺序性。​
  • 全局有序:将整个 Topic 的所有消息都发送到同一个队列,从而保证整个 Topic 的消息顺序性,但会降低消息处理的并发度。​

2.2 配置与使用​

1、配置文件修改:一般情况下,顺序消息无需特殊修改配置文件。但在实际应用中,可根据业务需求调整 Broker 的队列数量等参数,在broker.conf中修改:        

defaultTopicQueueNums=4 # 设置默认Topic的队列数量

2、代码示例:

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;

import java.util.List;

public class OrderlyProducer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        DefaultMQProducer producer = new DefaultMQProducer("orderly_producer_group");
        producer.setNamesrvAddr("localhost:9876");
        producer.start();

        String[] orderIds = {"1001", "1002", "1001", "1002"};
        for (int i = 0; i < orderIds.length; i++) {
            Message msg = new Message("OrderlyTopic", "TagA",
                    ("Order Message " + i).getBytes());
            producer.send(msg, new MessageQueueSelector() {
                @Override
                public MessageQueue select(List mqs, Message msg, Object arg) {
                    String orderId = (String) arg;
                    int index = Math.abs(orderId.hashCode() % mqs.size());
                    return mqs.get(index);
                }
            }, orderIds[i]);
        }
        producer.shutdown();
    }
}
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;

public class OrderlyConsumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("orderly_consumer_group");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("OrderlyTopic", "*");

        consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) {
                for (org.apache.rocketmq.common.message.MessageExt msg : msgs) {
                    System.out.println("Consumed message: " + new String(msg.getBody()));
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

        consumer.start();
        System.out.println("Consumer started");
    }
}

在生产者代码中,通过MessageQueueSelector将具有相同orderId的消息发送到同一个队列;消费者代码中,通过MessageListenerOrderly按顺序消费消息。​

2.3 应用场景​

顺序消息适用于日志处理、订单处理流程、数据库 binlog 同步等场景。例如在数据库 binlog 同步中,需要保证 SQL 语句的执行顺序,以确保数据的一致性。​

三、延时消息​

3.1 延时消息原理​

延时消息是指消息在发送后,不会立即被消费者消费,而是在经过指定的延迟时间后,才进入可消费状态。RocketMQ 通过将延时消息存储在特定的队列中,并根据延迟时间进行调度,实现延时功能。​

3.2 配置与使用​

1、配置文件修改:RocketMQ 默认支持 18 个延时等级,对应不同的延迟时间,可在broker.conf中查看或修改:

messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

2、代码示例:

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;

public class ScheduledProducer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        DefaultMQProducer producer = new DefaultMQProducer("scheduled_producer_group");
        producer.setNamesrvAddr("localhost:9876");
        producer.start();

        Message msg = new Message("ScheduledTopic", "TagA",
                ("Scheduled Message").getBytes());
        // 设置延时等级为3,对应10秒延迟
        msg.setDelayTimeLevel(3);
        producer.send(msg);

        producer.shutdown();
    }
}
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.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class ScheduledConsumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("scheduled_consumer_group");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("ScheduledTopic", "*");

        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.println("Consumed scheduled message: " + new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        consumer.start();
        System.out.println("Consumer started");
    }
}

在生产者代码中,通过msg.setDelayTimeLevel设置消息的延时等级;消费者代码与普通消息消费类似,接收并处理延时消息。​

3.3 应用场景​

延时消息适用于订单超时取消、任务定时调度、优惠券过期提醒等场景。例如在电商系统中,用户下单后若长时间未支付,可通过延时消息实现订单自动取消。

你可能感兴趣的:(云原生中间件,rocketmq,中间件,云计算,优化策略,消息队列)