在什么情況下會發生RocketMQ的消息重復消費
1.當系統的調用鏈路比較長的時候,比如系統A調用系統B,系統B再把消息發送到RocketMQ中,在系統A調用系統B的時候,如果系統B處理成功,但是遲遲沒有將調用成功的結果返回給系統A的時候,系統A就會嘗試重新發起請求給系統B,造成系統B重復處理,發起多條消息給RocketMQ造成重復消費;
2.在系統B發送給RocketMQ的時候,也有可能會發生和上面一樣的問題,消息發送超時,節骨系統B重試,導致RocketMQ接收到了重讀消息;
3.當RocketMQ成功接收到消息,並將消息交給消費者處理,如果消費者消費完成后還沒來得及提交offset給RocketMQ,自己宕機或者重啟了,那么RocketMQ沒有接收到offset,就會認為消費失敗了,會重發消息給消費者再次消費;
如何解決消息的重復消費
通過冪等性來保證,只要保證重復消息不對結果產生影響,就完美地解決這個問題。
在生產者端保證冪等性,一下兩種方式:
1.RocketMQ支持消息查詢的功能,只要去RocketMQ查詢一下是否已經發送過該條消息就可以了,不存在則發送,存在則不發送;
2.引入Redis,在發送消息到RocketMQ成功之后,向Redis中插入一條數據,如果發送重試,則先去Redis查詢一個該條消息是否已經發送過了,存在的話就不重復發送消息了;
方法一:RocketMQ消息查詢的性能不是特別好,如果在高並發的場景下,每條消息在發送到RocketMQ時都去查詢一下,可能會影響接口的性能;
方法二:在一些極端的場景下,Redis也無法保證消息發送成功之后,就一定能寫入Redis成功,比如寫入消息成功而Redis此時宕機,那么再次查詢Redis判斷消息是否已經發送過,是無法得到正確結果的;

案例
生產者
package com.wn.producer;
import com.alibaba.rocketmq.client.exception.MQClientException;
import com.alibaba.rocketmq.client.producer.DefaultMQProducer;
import com.alibaba.rocketmq.client.producer.SendResult;
import com.alibaba.rocketmq.common.message.Message;
public class MQProducer {
public static void main(String[] args) throws MQClientException {
//創建生產者
DefaultMQProducer producer=new DefaultMQProducer("rmq-group");
//設置NameServer地址
producer.setNamesrvAddr("192.168.138.187:9876;192.168.138.188:9876");
//設置生產者實例名稱
producer.setInstanceName("producer");
//啟動生產者
producer.start();
try {
//發送消息
for (int i=0;i<1;i++){
Thread.sleep(1000); //每秒發送一次
//創建消息
Message msg = new Message("wn04", // topic 主題名稱
"TagA", // tag 臨時值
("w-"+i).getBytes()// body 內容
);
//消息的唯一標識
msg.setKeys(System.currentTimeMillis() + "");
//發送消息
SendResult sendResult=producer.send(msg);
System.out.println(sendResult.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
producer.shutdown();
}
}
消費者
package com.wn.consumer;
import com.alibaba.rocketmq.client.consumer.DefaultMQPushConsumer;
import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import com.alibaba.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import com.alibaba.rocketmq.client.exception.MQClientException;
import com.alibaba.rocketmq.common.message.MessageExt;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MQConsumer {
//保存標識的集合
static private Map<String, String> logMap = new HashMap<>();
public static void main(String[] args) throws MQClientException {
//創建消費者
DefaultMQPushConsumer consumer=new DefaultMQPushConsumer("rmq-group");
//設置NameServer地址
consumer.setNamesrvAddr("192.168.138.187:9876;192.168.138.188:9876");
//設置消費者實例名稱
consumer.setInstanceName("consumer");
//訂閱topic
consumer.subscribe("wn04","TagA");
//監聽消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
String key = null;
String msgId = null;
try {
for (MessageExt msg : list) {
key = msg.getKeys();
//判斷集合當中有沒有存在key,存在就不需要重試,不存在先存key再回來重試后消費消息
if (logMap.containsKey(key)) {
// 無需繼續重試。
System.out.println("key:"+key+",無需重試...");
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
msgId = msg.getMsgId();
System.out.println("key:" + key + ",msgid:" + msgId + "---" + new String(msg.getBody()));
//模擬異常
int i = 1 / 0;
}
} catch (Exception e) {
//e.printStackTrace();
//重試
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
} finally {
//保存key
logMap.put(key, msgId);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("Consumer Started...");
}
}
實現


