消息中间件MQ如何保证信息100%投递成功、消息幂等性

图片

一、简介

消息中间件MQ(Message Queue)是一种常用的异步通信技术,它通过将消息存储在队列中,实现生产者和消费者之间的解耦。MQ的主要作用是保证消息的可靠传输和幂等性。本文将介绍如何使用消息中间件MQ保证信息100%投递成功、消息幂等性,并通过Java代码详细说明实现过程。

二、MQ的特点

  • 解耦:将消息写入消息队列,需要消息的系统自己从消息队列中订阅,从而系统A不需要做任何修改。

  • 异步:将发送请求的操作放到消息队列中,由消息队列异步地发送请求。

  • 削峰填谷:使用它可以解决短时间内爆发式的请求任务,在不使用MQ的情况下会导致服务处理不过来,出现应用程序假死的情况,而使用了MQ之后可以把这些请求先暂存到消息队列中,然后进行排队执行,那么就不会出现应用程序假死的情况了。

  • 可靠性:可以通过消息队列来实现故障转移和负载均衡。

  • 多种消息模式:支持多种消息模式,如点对点、发布/订阅等。

图片

三、保证信息100%投递成功的方法

为了保证消息100%投递成功,我们可以采用以下方法:

3.1 持久化设置

在创建Queue时,可以通过设置持久化参数来保证Broker持久化Queue的元数据,但是不会持久化Queue里面的消息。这样即使消息发送失败,也可以通过Broker重新拉取最新的Queue元数据并重新投递该消息。具体实现如下:

3.1.1 队列持久化设置
// 创建持久化的队列
public Queue createDurableQueue(String queueName) {
    return new Queue(queueName, true);
}
3.1.2 RocketMQ支持两种持久化

RocketMQ支持两种持久化方式:CommitLog和ConsumeQueue。其中,CommitLog是存储消息的物理文件,而ConsumeQueue则是消费者消费消息时存储消息的队列。

1)CommitLog持久化设置

在创建Topic时,可以通过设置参数来指定是否开启CommitLog持久化。如果开启了CommitLog持久化,则每个消息都会被写入到磁盘上的CommitLog文件中。具体代码如下:

// 创建Topic时设置CommitLog持久化
public static final String PRODUCER_GROUP = "producer_group";
public static final String TOPIC = "topic";
public static void main(String[] args) throws Exception {
    DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP);
    producer.setNamesrvAddr("localhost:9876");
    producer.start();
    // 创建Topic时设置CommitLog持久化
    TopicConfig topicConfig = new TopicConfig();
    topicConfig.setWriteQueueNums(1);
    topicConfig.setPerm(6);
    topicConfig.setTopicSynFlag(false);
    topicConfig.setReadQueueNums(1);
    topicConfig.setMaxMessageSize(1024 * 1024 * 30);
    topicConfig.setDequeueTotalMaxNums(10);
    topicConfig.setReserveTimeMillis(100);
    topicConfig.setCommitLogDiskDirs(new String[]{"/home/user/rocketmq/commitlog"});
    producer.createTopic(TOPIC, topicConfig);
}

其中,topicConfig.setCommitLogDiskDirs()方法用于设置CommitLog文件的存储路径。可以根据实际情况修改为合适的路径。

2)ConsumeQueue持久化设置

在创建Consumer时,可以通过设置参数来指定是否开启ConsumeQueue持久化。如果开启了ConsumeQueue持久化,则每个消息都会被存储到磁盘上的ConsumeQueue文件中。具体代码如下:

// 创建Consumer时设置ConsumeQueue持久化
public static final String CONSUMER_GROUP = "consumer_group";
public static final String TOPIC = "topic";
public static final String CONSUMER_ID = "consumer_id";
public static void main(String[] args) throws Exception {
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP);
    consumer.setNamesrvAddr("localhost:9876");
    consumer.subscribe(TOPIC, "*");
    // 创建Consumer时设置ConsumeQueue持久化
    SubscriptionConfig subscriptionConfig = new SubscriptionConfig();
    subscriptionConfig.setConsumeAfterCommit(true);
    subscriptionConfig.setEnableAutoCommit(false);
    subscriptionConfig.setConsumeMessageBatchMaxSize(1);
    subscriptionConfig.setConsumeThreadMax(2);
    consumer.registerMessageListener(new MessageListenerConcurrently() {
        @Override
        public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
            // 处理消息的逻辑
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        }
    });
    consumer.subscribe(Collections.singletonList(new TopicFilter(TOPIC, "*")), subscriptionConfig);
    consumer.start();
}

其中,subscriptionConfig.setConsumeMessageBatchMaxSize()方法用于设置每次从CommitLog中拉取的最大消息数。subscriptionConfig.setConsumeThreadMax()方法用于设置并发消费的最大线程数。

以上就是RocketMQ持久化的设置方法。需要注意的是,持久化会占用一定的磁盘空间,因此需要根据实际情况合理设置参数。

3.2 事务机制

在发送消息时,可以使用事务机制来保证消息的原子性。如果发送失败,则回滚事务并重新发送。具体实现如下:

// 发送消息
public void sendMessage(String queueName, Message message) {
    try {
        // 开启事务
        Transaction transaction = producer.getTransaction();
        transaction.begin();
        // 发送消息
        producer.send(queueName, message);
        // 提交事务
        transaction.commit();
    } catch (Exception e) {
        // 回滚事务并重新发送
        transaction.rollback();
        producer.send(queueName, message);
    }
}

3.3 死信队列

当消息无法被消费时,可以将其发送到死信队列中。通过监控死信队列,可以及时发现问题并进行修复。具体实现如下:

public class DeadLetterQueueProducer {
    private static final String DEAD_LETTER_QUEUE = "dead_letter_queue";
    private DefaultMQProducer producer;
    private DefaultMQPushConsumer consumer;

    public DeadLetterQueueProducer() throws MQClientException, InterruptedException {
        // 创建生产者
        producer = new DefaultMQProducer("producer_group");
        producer.setNamesrvAddr("localhost:9876");
        producer.start();

        // 创建消费者
        consumer = new DefaultMQPushConsumer("consumer_group");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.subscribe(Arrays.asList("topic1", "topic2"), "*");
        consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
            try {
                // 处理消息
                System.out.println("Receive message: " + new String(msgs[0].getBody()));
            } catch (Exception e) {
                // 消息处理失败,发送到死信队列
                sendToDeadLetterQueue(new MessageExt(DEAD_LETTER_QUEUE, msgs[0]));
            }
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });
        consumer.start();
    }

    private void sendToDeadLetterQueue(MessageExt message) throws MQClientException {
        // 创建死信队列生产者
        DefaultMQProducer deadLetterProducer = new DefaultMQProducer("dead_letter_producer_group");
        deadLetterProducer.setNamesrvAddr("localhost:9876");
        deadLetterProducer.start();

        // 发送消息到死信队列
        deadLetterProducer.send(message);
    }

    public static void main(String[] args) throws MQClientException, InterruptedException {
        DeadLetterQueueProducer producer = new DeadLetterQueueProducer();
        Thread.sleep(Long.MAX_VALUE);
        producer.shutdown();
    }
}

在上面的代码中,我们首先创建了一个名为DeadLetterQueueProducer的类,该类包含了一个生产者和一个消费者。在消费者的MessageListenerConcurrently方法中,我们尝试处理消息,如果处理失败则将消息发送到死信队列中。 在sendToDeadLetterQueue方法中,我们创建了一个死信队列生产者,并将消息发送到死信队列中。最后,我们在main方法中启动了生产者和消费者,并让程序一直运行下去。

四、保证消息幂等性的方法

为了保证消息幂等性,可以使用以下方法:

4.1 唯一标识符

为每个消息分配一个唯一标识符,以便在出现重复消息时进行去重处理。具体实现如下:​​​​​​​

// 生成唯一标识符的方法
private String generateUniqueId() {
    UUID uuid = UUID.randomUUID();
    return uuid.toString();
}

4.2 时间戳

使用时间戳可以确保消息的幂等性。具体实现如下:​​​​​​​

// 生成时间戳的方法
private long generateTimestamp() {
    return System.currentTimeMillis();
}

五、总结

本文介绍了如何使用消息中间件MQ保证信息100%投递成功、消息幂等性,并给出了Java代码详细说明。通过持久化设置、事务机制和死信队列等方法,可以确保消息的可靠性和幂等性。同时,使用唯一标识符和时间戳等方法,可以有效地避免重复消息的出现。

图片

猜你喜欢

转载自blog.csdn.net/weixin_40381772/article/details/133384532
今日推荐