直播平臺製作,重試機制和死信佇列的合理運用

zhibo系統開發發表於2024-01-06

在直播平臺製作中,如何實現訊息消費失敗後重試至多三次,仍失敗則加入死信佇列?

一、重試機制

首先說一下RabbitMQ的訊息重試機制,顧名思義,就是訊息消費失敗後進行重試,重試機制的觸發條件是消費者顯式的丟擲異常,如果沒有顯式地丟擲異常或者try catch起來沒有手動回滾,事務是不會回滾的。
以下程式碼可以觸發重試機制
編輯

還有一種情況就是訊息被拒絕後重新加入佇列,比如basic.reject和basic.nack,並且requeue = true , 但是個人認為這個不算是觸發了重試機制,這個是重新進入到了訊息佇列然後重新被消費,並且也不會觸發我們重試機制的配置(如重試間隔、最大重試次數等等)。

在直播平臺製作中重試機制是預設開啟的,但是如果沒有重試機制相關的配置會導致訊息一直無間隔的重試,直到消費成功,所以要使用重試機制一定要有相關配置。

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    virtual-host: mq-test
    username: ********
    password: ********
    listener:
      simple:
        # ACK模式(none,auto,manual,預設為auto)
        acknowledge-mode: auto
        # 開啟重試
        retry:
          # 是否開啟重試機制
          enabled: true
          # 最大重試次數
          max-attempts: 5
          # 重試間隔(ms)
          initial-interval: 5000

二、死信佇列

說到死信佇列,首先需要知道什麼是死信
死信就是訊息在直播平臺製作的特定場景下的一種表現形式,這些場景包括:

訊息被拒絕(basic.reject / basic.nack),並且requeue = false
訊息的 TTL 過期時
訊息佇列達到最大長度
達到最大重試限制

訊息在這些場景中時,被稱為死信。
死信佇列就是用於儲存死信的訊息佇列,在死信佇列中,有且只有死信構成,不會存在其餘型別的訊息。死信佇列也是一個普通佇列,也可以被消費者消費,區別在於業務佇列需要繫結在死信佇列上,才能正常地把死信傳送到死信佇列上。
業務佇列繫結死信佇列
編輯

三、重試+死信的實現

文章開頭的場景有兩種方案可以實現
方案一:使用自動ACK + RabbitMQ重試機制
方案二:使用手動ACK + 手動重試機制

3.1 自動ACK + RabbitMQ重試機制

配置

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    virtual-host: mq-test
    username: ********
    password: ********
    listener:
      simple:
        # ACK模式(預設為auto)
        acknowledge-mode: auto
        # 開啟重試
        retry:
          enabled: true
          max-attempts: 5
          initial-interval: 5000

消費者

 @RabbitListener(queues = RabbitMqConfig.USER_ADD_QUEUE, concurrency = "10")
    public void userAddReceiver(String data, Message message, Channel channel) throws Exception {
        UserVo vo = OBJECT_MAPPER.readValue(data, UserVo.class);
        boolean success = messageHandle(vo);
        // 透過業務控制是否消費成功,消費失敗則丟擲異常觸發重試
        if (!success) {
            log.error("消費失敗");
            throw new Exception("訊息消費失敗");
        }
    }

需要說明的是,上述的方法一定要在直播平臺製作時開啟自動ACK,才會在到達最大重試上限後傳送到死信佇列,而且在重試過程中會獨佔當前執行緒,如果是單執行緒的消費者會導致其他訊息阻塞,直至重試完成,所以可以使用 @RabbitListener上的concurrency屬性來控制併發數量。

3.2 手動ACK + 手動重試

配置

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    virtual-host: mq-test
    username: ********
    password: ********
    listener:
      simple:
        # ACK模式(預設為auto)
        acknowledge-mode: manual

需要說明的是,如果是手動ACK配置了重試機制,在丟擲異常的時候仍會觸發重試,但是達到重試上限之後,會永遠處於Unacked狀態,不會進入到死信佇列,必須要手動拒絕才可以進入死信佇列,所以說這裡不用配置重試機制而是採用手動重試的方式

消費者

/**
 * 訊息最大重試次數
 */
private static final int MAX_RETRIES = 3;
/**
 * 重試間隔(秒)
 */
private static final long RETRY_INTERVAL = 5;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
	
@RabbitListener(queues = RabbitMqConfig.USER_ADD_QUEUE, concurrency = "10")
public void userAddReceiver(String data, Message message, Channel channel) throws IOException, InterruptedException {
	UserVo vo = OBJECT_MAPPER.readValue(data, UserVo.class);
	// 重試次數
	int retryCount = 0;
	boolean success = false;
    // 消費失敗並且重試次數<=重試上限次數
	while (!success && retryCount < MAX_RETRIES) {
		retryCount++;
		// 具體業務邏輯
		success = messageHandle(vo);
		// 如果失敗則重試
		if (!success) {
			String errorTip = "第" + retryCount + "次消費失敗" +
					((retryCount < 3) ? "," + RETRY_INTERVAL + "s後重試" : ",進入死信佇列");
			log.error(errorTip);
			Thread.sleep(RETRY_INTERVAL * 1000);
		}
	}
	if (success) {
		// 消費成功,確認
		channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
		log.info("建立訂單資料消費成功");
	} else {
		// 重試多次之後仍失敗,進入死信佇列
		channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
		log.info("建立訂單資料消費失敗");
	}
}

總結:兩種方案都可以在直播平臺製作時達到我們的預期效果,相比起來方案一會更加的方便簡潔,方案二的可控性更高,以上就是直播平臺製作,重試機制和死信佇列的合理運用, 更多內容歡迎關注之後的文章


來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/69978258/viewspace-3002918/,如需轉載,請註明出處,否則將追究法律責任。

相關文章