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

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中都存有备份,防止单点故障。

四、场景三的解决方案
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());
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。