一、导致RocketMQ消息丢失的三种场景

image.png

1.场景1中生产者将消息发送给Rocket MQ的时候,如果出现了网络抖动或者通信异常等问题,消息就有可能会丢失

2.场景2中消息需要持久化到磁盘中,这时会有两种情况导致消息丢失

  • RocketMQ为了减少磁盘的IO,会先将消息写入到os cache中,而不是直接写入到磁盘中,消费者从os cache中获取消息类似于直接从内存中获取消息,速度更快,过一段时间会由os线程异步的将消息刷入磁盘中,此时才算真正完成了消息的持久化。在这个过程中,如果消息还没有完成异步刷盘,RocketMQ中的Broker宕机的话,就会导致消息丢失。
  • 还有一种情况是,如果消息已经被刷入了磁盘中,但是数据没有做任何备份,一旦磁盘损坏,那么消息也会丢失

3.消费者成功从RocketMQ中获取到了消息,还没有将消息完全消费完的时候,就通知RocketMQ我已经将消息消费了,然后消费者宕机,但是RocketMQ认为消费者已经成功消费了数据,所以数据依旧丢失了

二、场景一的解决方案(使用RocketMQ分布式事务)

1.TransactionSimple事务监听器类

package com.powernode.listener;

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 org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;

import java.util.Random;

/**
 * @description:
 * @author: 袁凯
 * @time: 2024/4/19 12:40
 */
@RocketMQTransactionListener(rocketMQTemplateBeanName = "rocketMQTemplate")
public class TransactionSimple implements RocketMQLocalTransactionListener {

    int tag = 0;

    /**
     * 执行本地事务,半事务消息提交成功
     * @param message
     * @param o
     * @return
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(org.springframework.messaging.Message message, Object o) {
        System.out.println("预提交成功");
        // db操作,如果成功,则返回提交状态为成功,反之为失败,tag表示扣款是否成功,1表示成功,0表示失败,这里用Random表示数据库的返回状态
        tag = new Random().nextInt(2);
        try {
            Thread.sleep(200000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        if(tag == 1) {
            return RocketMQLocalTransactionState.COMMIT;
        } else if(tag == 2){
            // 用于通知broker进行半事务消息的回滚
            return RocketMQLocalTransactionState.ROLLBACK;
        }
        return RocketMQLocalTransactionState.UNKNOWN;
    }



    /**
     * 检查本地事务
     * @param message
     * @return
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(org.springframework.messaging.Message message) {
        // 如果这时候producer宕机了,broker就会调用这个函数查看具体的情况,然后根据情况返回COMMIT_MESSAGE或者ROLLBACK_MESSAGE
        // 如果tag为0,则表示事务并没有收到执行结果,用于检查本地事务
        System.out.println("执行回调");
        if(tag == 0) {
            return RocketMQLocalTransactionState.ROLLBACK;
        }
        return null;
    }
}

2.application.yaml

rocketmq:
    producer:
        group: boot-producer-group
        enable-msg-trace: true 
        transactionalListenerName: myTransactionListener

3.测试类

package com.powernode.yktest;

import org.apache.logging.log4j.message.SimpleMessage;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.messaging.support.GenericMessage;

import java.util.Random;

/**
 * @description:
 * @author: 袁凯
 * @time: 2024/4/11 11:38
 */
@SpringBootTest
public class SimpleTest {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    int tag;

    @Test
    public void simpleProducerTest() throws Exception {
        //1.创建SendResult
        // 往powernode的主题里面发送一个简单的字符串同步消息
        SendResult sendResult = rocketMQTemplate.sendMessageInTransaction("boot-simple", new GenericMessage<>("Hello, RocketMQ!"), null);
        System.out.println(sendResult.getSendStatus());
        System.out.println(sendResult.getMsgId());
    }
}

三、场景二的解决方案

1. 在场景2中要保证消息不丢失,首先需要将os cache的异步刷盘策略改为同步刷盘,这一步需要修改Broker的配置文件,将flushDiskType改为SYNC_FLUSH同步刷盘策略,默认的是ASYNC_FLUSH异步刷盘。一旦同步刷盘返回成功,那么就一定保证消息已经持久化到磁盘中了;为了保证磁盘损坏不会丢失数据,我们需要对RocketMQ采用主从机构,集群部署,Leader中的数据在多个Follower中都存有备份,防止单点故障。

`YBBPHQ61U5G$A9QCDIY~$3.png

四、场景三的解决方案

package com.powernode.yktest;

import org.apache.rocketmq.common.message.MessageExt;

import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

import java.sql.SQLOutput;

/**
 * @description:
 * @author: 袁凯
 * @time: 2024/4/11 11:52
 */
@Component
@RocketMQMessageListener(topic = "boot-simple", consumerGroup = "powernode-group",messageModel = MessageModel.BROADCASTING)
public class SimpleMsgListener implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt messageExt) {
        //这里进行了封装,当抛出异常时就会像原生api一样返回ConsumeConcurrentlyStatus.RECONSUME_LATER;
        System.out.println(messageExt.getMsgId());
    }
}

原来是小袁呐
1 声望0 粉丝

引用和评论

0 条评论