RocketMQ(5)---RocketMQ重試機制

雨點的名字發表於2019-07-02

RocketMQ重試機制

訊息重試分為兩種:Producer傳送訊息的重試 Consumer訊息消費的重試

一、Producer端重試

Producer端重試是指: Producer往MQ上發訊息沒有傳送成功,比如網路原因導致生產者傳送訊息到MQ失敗。

看一下程式碼:

@Slf4j
public class RocketMQTest {
    /**
     * 生產者組
     */
    private static String PRODUCE_RGROUP = "test_producer";
  
    public static void main(String[] args) throws Exception {
        //1、建立生產者物件
        DefaultMQProducer producer = new DefaultMQProducer(PRODUCE_RGROUP);
        //設定重試次數(預設2次)
        producer.setRetryTimesWhenSendFailed(3000);
        //繫結name server
        producer.setNamesrvAddr("74.49.203.55:9876");
        producer.start();
        //建立訊息
        Message message = new Message("topic_family", ("小小今年3歲" ).getBytes());
        //傳送 這裡填寫超時時間是5毫秒 所以每次都會傳送失敗
        SendResult sendResult = producer.send(message,5);
        log.info("輸出生產者資訊={}",sendResult);
    }
}

超時重試 針對網上說的超時異常會重試的說法都是錯誤的,想想都覺得可怕,我查的所以文章都說超時異常都會重試,難道這麼多人都沒有去測試一下 或者去看個原始碼。

我發現這個問題,是因為我上面超時時間設定為5毫秒 ,按照正常肯定會報超時異常,但我設定1次重試和3000次的重試,雖然最終都會報下面異常,但輸出錯誤時間報

顯然不應該是一個級別。但測試發現無論我設定的多少次的重試次數,報異常的時間都差不多。

org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException: sendDefaultImpl call timeout

針對這個疑惑,我去看了原始碼之後,才恍然大悟。

   /**
     * 說明 抽取部分程式碼
     */
    private SendResult sendDefaultImpl(Message msg, final CommunicationMode communicationMode, final SendCallback sendCallback, final long timeout) {
        
        //1、獲取當前時間
        long beginTimestampFirst = System.currentTimeMillis();
        long beginTimestampPrev ;
        //2、去伺服器看下有沒有主題訊息
        TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
        if (topicPublishInfo != null && topicPublishInfo.ok()) {
            boolean callTimeout = false;
            //3、通過這裡可以很明顯看出 如果不是同步傳送訊息 那麼訊息重試只有1次
            int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
            //4、根據設定的重試次數,迴圈再去獲取伺服器主題訊息
            for (times = 0; times < timesTotal; times++) {
                MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
                beginTimestampPrev = System.currentTimeMillis();
                long costTime = beginTimestampPrev - beginTimestampFirst;
                //5、前後時間對比 如果前後時間差 大於 設定的等待時間 那麼直接跳出for迴圈了 這就說明連線超時是不進行多次連線重試的
                if (timeout < costTime) {
                    callTimeout = true;
                    break;

                }
                //6、如果超時直接報錯
                if (callTimeout) {
                    throw new RemotingTooMuchRequestException("sendDefaultImpl call timeout");
                }
        }
    }

通過這段原始碼很明顯可以看出以下幾點

  1. 如果是非同步傳送 那麼重試次數只有1次
  2. 對於同步而言,超時異常也是不會再去重試
  3. 如果發生重試是在一個for 迴圈裡去重試,所以它是立即重試而不是隔一段時間去重試。

真是實踐出真知!!!


二、 Consumer端重試

消費端比較有意思,而且在實際開發過程中,我們也更應該考慮的是消費端的重試。

消費者端的失敗主要分為2種情況,ExceptionTimeout

1、Exception

@Slf4j
@Component
public class Consumer {
    /**
     * 消費者實體物件
     */
    private DefaultMQPushConsumer consumer;
    /**
     * 消費者組
     */
    public static final String CONSUMER_GROUP = "test_consumer";
    /**
     * 通過建構函式 例項化物件
     */
    public Consumer() throws MQClientException {
        consumer = new DefaultMQPushConsumer(CONSUMER_GROUP);
        consumer.setNamesrvAddr("47.99.203.55:9876;47.99.203.55:9877");
        //訂閱topic和 tags( * 代表所有標籤)下資訊
        consumer.subscribe("topic_family", "*");
        //註冊消費的監聽 並在此監聽中消費資訊,並返回消費的狀態資訊
        consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
            //1、獲取訊息
            Message msg = msgs.get(0);
            try {
                //2、消費者獲取訊息
                String body = new String(msg.getBody(), "utf-8");
                //3、獲取重試次數
                int count = ((MessageExt) msg).getReconsumeTimes();
                log.info("當前消費重試次數為 = {}", count);
                //4、這裡設定重試大於3次 那麼通過儲存資料庫 人工來兜底
                if (count >= 2) {
                    log.info("該訊息已經重試3次,儲存資料庫。topic={},keys={},msg={}", msg.getTopic(), msg.getKeys(), body);
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }
                //直接丟擲異常
                throw new Exception("=======這裡出錯了============");
                //return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            } catch (Exception e) {
                e.printStackTrace();
                return ConsumeConcurrentlyStatus.RECONSUME_LATER;
            }
        });
        //啟動監聽
        consumer.start();
    }
}

這裡的程式碼意思很明顯: 主動丟擲一個異常,然後如果超過3次,那麼就不繼續重試下去,而是將該條記錄儲存到資料庫由人工來兜底。

看下執行結果

RocketMQ(5)---RocketMQ重試機制

注意 消費者和生產者的重試還是有區別的,主要有兩點

1、預設重試次數:Product預設是2次,而Consumer預設是16次

2、重試時間間隔:Product是立刻重試,而Consumer是有一定時間間隔的。它照1S,5S,10S,30S,1M,2M····2H進行重試。

2、Timeout

說明 這裡的超時異常並非真正意義上的超時,它指的是指獲取訊息後,因為某種原因沒有給RocketMQ返回消費的狀態,即沒有return ConsumeConcurrentlyStatus.CONSUME_SUCCESSreturn ConsumeConcurrentlyStatus.RECONSUME_LATER

那麼 RocketMQ會認為該訊息沒有傳送,會一直髮送。因為它會認為該訊息根本就沒有傳送給消費者,所以肯定沒消費。

做這個測試很簡單。

        //1、消費者獲得訊息
        String body = new String(msg.getBody(), "utf-8");
        //2、獲取重試次數
        int count = ((MessageExt) msg).getReconsumeTimes();
        log.info("當前消費重試次數為 = {}", count);
        //3、這裡睡眠60秒
        Thread.sleep(60000);
       log.info("休眠60秒 看還能不能走到這裡。topic={},keys={},msg={}", msg.getTopic(), msg.getKeys(), body);
        //返回成功
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;

當獲得 當前消費重試次數為 = 0 後 , 關掉該程式。再重新啟動該程式,那麼依然能夠獲取該條訊息

consumer消費者  當前消費重試次數為 = 0
休眠60秒 看還能不能走到這裡。topic=topic_family,keys=1a2b3c4d5f,msg=小小今年3歲




只要自己變優秀了,其他的事情才會跟著好起來(上將2)

相關文章