這篇文章主要介紹RocketMQ重試機制的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
一.重試機制
1.由于MQ經常處于復雜的分布式系統中,考慮網絡波動,服務宕機,程序異常因素,很有可能出現消息發送或者消費失敗的問題。因此,消息的重試就是所有MQ中間件必須考慮到的一個關鍵點。如果沒有消息重試,就可能產生消息丟失的問題,可能對系統產生很大的影響。所以,秉承寧可多發消息,也不可丟失消息的原則,大部分MQ都對消息重試提供了很好的支持。
2.RocketMQ為了使用者封裝了消息重試的處理流程,無需開發人員手動處理。RocketMQ支持了生產端和消費端兩類重試機制。
模擬異常
Consumer端消息消費兩種狀態:
package com.alibaba.rocketmq.client.consumer.listener;
public enum ConsumeConcurrentlyStatus {
CONSUME_SUCCESS,
RECONSUME_LATER;
private ConsumeConcurrentlyStatus() {
}
}一個是成功(CONSUME_SUCCESS),一個是失敗&重試(RECONSUME_LATER);
Consumer為了保證消息消費成功,只有使用方明確表示消費成功,返回CONSUME_SUCCESS,RocketMQ才會認為消息消費成功。
如果消息消費失敗,只要返回ConsumeConcurrentlyStatus.RECONSUME_LATER,RocketMQ就會認為消息消費失敗了,需要重新投遞。
1.出現異常
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.List;
public class MQConsumer {
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("wn02","TagA");
//監聽消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
//獲取消息
for (MessageExt msg:list){
System.out.println(msg.getMsgId()+"---"+new String(msg.getBody()));
}
try {
int i=1/0;
}catch (Exception e){
e.printStackTrace();
//需要重試
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}//消息成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("Consumer Started...");
}
} 2.網絡延遲
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.List;
public class MQConsumer {
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("wn03","TagA");
//監聽消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
//獲取消息
for (MessageExt msg:list){
System.out.println(msg.getMsgId()+"---"+new String(msg.getBody()));
}
//網絡延遲
try {
Thread.sleep(600000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//消息成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("Consumer Started...");
}
}二、消息冪等
1、在什么情況下會發生RocketMQ的消息重復消費
①、當系統的調用鏈路比較長的時候,比如系統A調用系統B,系統B再把消息發送到RocketMQ中,在系統A調用系統B的時候,如果系統B處理成功,但是遲遲沒有將調用成功的結果返回給系統A的時候,系統A就會嘗試重新發起請求給系統B,造成系統B重復處理,發起多條消息給RocketMQ造成重復消費;
?、?、在系統B發送給RocketMQ的時候,也有可能會發生和上面一樣的問題,消息發送超時,節骨系統B重試,導致RocketMQ接收到了重讀消息;
?、?、當RocketMQ成功接收到消息,并將消息交給消費者處理,如果消費者消費完成后還沒來得及提交offset給RocketMQ,自己宕機或者重啟了,那么RocketMQ沒有接收到offset,就會認為消費失敗了,會重發消息給消費者再次消費;
2、如何解決消息的重復消費
通過冪等性來保證,只要保證重復消息不對結果產生影響,就完美地解決這個問題。
在生產者端保證冪等性,一下兩種方式:
?、?、RocketMQ支持消息查詢的功能,只要去RocketMQ查詢一下是否已經發送過該條消息就可以了,不存在則發送,存在則不發送;
?、?、引入Redis,在發送消息到RocketMQ成功之后,向Redis中插入一條數據,如果發送重試,則先去Redis查詢一個該條消息是否已經發送過了,存在的話就不重復發送消息了;
方法一:RocketMQ消息查詢的性能不是特別好,如果在高并發的場景下,每條消息在發送到RocketMQ時都去查詢一下,可能會影響接口的性能;
方法二:在一些極端的場景下,Redis也無法保證消息發送成功之后,就一定能寫入Redis成功,比如寫入消息成功而Redis此時宕機,那么再次查詢Redis判斷消息是否已經發送過,是無法得到正確結果的;
3、生產者
package com.zn.idempotent;
import com.alibaba.rocketmq.client.exception.MQBrokerException;
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;
import com.alibaba.rocketmq.remoting.exception.RemotingException;
/**
* 消息冪等生產者
*/
public class IdempotentProvider {
public static void main(String[] args) throws MQClientException, InterruptedException, RemotingException, MQBrokerException {
//創建一個生產者
DefaultMQProducer producer=new DefaultMQProducer("rmq-group");
//設置NameServer地址
producer.setNamesrvAddr("192.168.33.135:9876;192.168.33.136:9876");
//設置生產者實例名稱
producer.setInstanceName("producer");
//啟動生產者
producer.start();
//發送消息
for (int i=1;i<=1;i++){
//模擬網絡延遲,每秒發送一次MQ
Thread.sleep(1000);
//創建消息,topic主題名稱 tags臨時值代表小分類, body代表消息體
Message message=new Message("itmayiedu-topic03","TagA",("itmayiedu-"+i).getBytes());
//消息的唯一標識
message.setKeys("訂單消息:"+i);
//發送消息
SendResult sendResult=producer.send(message);
System.out.println("信息冪等問題來了:"+sendResult.toString());
}
producer.shutdown();
}
}4、消費者
package com.zn.idempotent;
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;
import java.util.logging.LogManager;
import java.util.logging.Logger;
/**
* 消息冪等消費者
*/
public class IdempotentConsumer {
static private Map<String, Object> logMap = new HashMap<>();
public static void main(String[] args) throws MQClientException {
//創建消費者
DefaultMQPushConsumer consumer=new DefaultMQPushConsumer("rmq-group");
//設置NameServer地址
consumer.setNamesrvAddr("192.168.33.135:9876;192.168.33.136:9876");
//設置實例名稱
consumer.setInstanceName("consumer");
//訂閱topic
consumer.subscribe("itmayiedu-topic03","TagA");
//監聽消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
String key=null;
String msgId=null;
for (MessageExt messageExt:list){
key=messageExt.getKeys();
//判讀redis中有沒有當前消息key
if (logMap.containsKey(key)) {
// 無需繼續重試。
System.out.println("key:"+key+",已經消費,無需重試...");
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
//RocketMQ由于是集群環境,所以產生的消息ID可能會重復
msgId = messageExt.getMsgId();
System.out.println("key:" + key + ",msgid:" + msgId + "---" + new String(messageExt.getBody()));
//將當前key保存在redis中
logMap.put(messageExt.getKeys(),messageExt);
}
try {
int i=5/0;
}catch (Exception e){
e.printStackTrace();
//人工補償
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//啟動消費者
consumer.start();
System.out.println("Consumer Started!");
}
}以上是“RocketMQ重試機制的示例分析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。