SpringBoot整合RocketMQ,老鳥們都是這麼玩的!

今天我們來討論如何在項目開發中優雅地使用RocketMQ。本文分爲三部分,第一部分實現SpringBoot與RocketMQ的整合,第二部分解決在使用RocketMQ過程中可能遇到的一些問題並解決他們,第三部分介紹如何封裝RocketMQ以便更好地使用。

1. SpringBoot整合RocketMQ

在SpringBoot中集成RocketMQ,只需要簡單四步:

  1. 引入相關依賴
<dependency>
  <groupId>org.apache.rocketmq</groupId>
  <artifactId>rocketmq-spring-boot-starter</artifactId>
</dependency>
  1. 添加RocketMQ的相關配置
rocketmq:
    consumer:
        group: springboot_consumer_group
        # 一次拉取消息最大值,注意是拉取消息的最大值而非消費最大值
        pull-batch-size: 10
    name-server: 10.5.103.6:9876
    producer:
        # 發送同一類消息的設置爲同一個group,保證唯一
        group: springboot_producer_group
        # 發送消息超時時間,默認3000
        sendMessageTimeout: 10000
        # 發送消息失敗重試次數,默認2
        retryTimesWhenSendFailed: 2
        # 異步消息重試此處,默認2
        retryTimesWhenSendAsyncFailed: 2
        # 消息最大長度,默認1024 * 1024 * 4(默認4M)
        maxMessageSize: 4096
        # 壓縮消息閾值,默認4k(1024 * 4)
        compressMessageBodyThreshold: 4096
        # 是否在內部發送失敗時重試另一個broker,默認false
        retryNextServer: false
  1. 使用提供的模板工具類RocketMQTemplate發送消息
@RestController
public class NormalProduceController {
  @Setter(onMethod_ = @Autowired)
  private RocketMQTemplate rocketmqTemplate;
  
  @GetMapping("/test")
  public SendResult test() {
    Message<String> msg = MessageBuilder.withPayload("Hello,RocketMQ").build();
    SendResult sendResult = rocketmqTemplate.send(topic, msg);
  }
}
  1. 實現RocketMQListener接口消費消息
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

@Component
@RocketMQMessageListener(topic = "your_topic_name", consumerGroup = "your_consumer_group_name")
public class MyConsumer implements RocketMQListener<String> {

    @Override
    public void onMessage(String message) {
        // 處理消息的邏輯
        System.out.println("Received message: " + message);
    }
}

以上4步即可實現SpringBoot與RocketMQ的整合,這部分屬於基礎知識,不做過多說明。

2 使用RocketMQ會遇到的問題

以下是一些在SpringBoot中使用RocketMQ時常遇到的問題,現在爲您逐一解決。

2.1 WARN No appenders could be found for logger

啓動項目時會在日誌中看到如下告警

RocketMQLog:WARN No appenders could be found for logger (io.netty.util.internal.InternalThreadLocalMap).
RocketMQLog:WARN Please initialize the logger system properly.

此時我們只需要在啓動類中設置環境變量 rocketmq.client.logUseSlf4j 爲 true 明確指定RocketMQ的日誌框架

@SpringBootApplication
public class RocketDemoApplication {

    public static void main(String[] args) {
        /*
         * 指定使用的日誌框架,否則將會告警
         * RocketMQLog:WARN No appenders could be found for logger (io.netty.util.internal.InternalThreadLocalMap).
         * RocketMQLog:WARN Please initialize the logger system properly.
         */
        System.setProperty("rocketmq.client.logUseSlf4j", "true");
      
        SpringApplication.run(RocketDemoApplication.class, args);
    }
}

同時還得在配置文件中調整日誌級別,不然在控制檯會一直看到broker的日誌信息

logging:
	level:
  	RocketmqClient: ERROR
    io:
    	netty: ERROR

2.2 不支持LocalDate 和 LocalDateTime

在使用Java8後經常會使用LocalDate/LocalDateTime這兩個時間類型字段,然而RocketMQ原始配置並不支持Java時間類型,當我們發送的實體消息中包含上述兩個字段時,消費端在消費時會出現如下所示的錯誤。

比如生產者的代碼如下:

@GetMapping("/test")
public void test(){
  //普通消息無返回值,只負責發送消息⽽不等待服務器迴應且沒有回調函數觸發。
  RocketMessage rocketMessage = RocketMessage.builder().
    id(1111L).
    message("hello,world")
    .localDate(LocalDate.now())
    .localDateTime(LocalDateTime.now())
    .build();
  rocketmqTemplate.convertAndSend(destination,rocketMessage);
}

消費者的代碼如下:

@Component
@RocketMQMessageListener(consumerGroup = "springboot_consumer_group",topic = "consumer_topic")
public class RocketMQConsumer implements RocketMQListener<RocketMessage> {
    @Override
    public void onMessage(RocketMessage message) {
        System.out.println("消費消息-" + message);
    }
}

消費者開始消費時會出現類型轉換異常錯誤Cannot construct instance of java.time.LocalDate,錯誤詳情如下:

image-20230322163904100

原因:RocketMQ內置使用的轉換器是RocketMQMessageConverter,轉換Json時使用的是MappingJackson2MessageConverter,但是這個轉換器不支持時間類型。

解決辦法:需要自定義消息轉換器,將MappingJackson2MessageConverter進行替換,並添加支持時間模塊

@Configuration
public class RocketMQEnhanceConfig {

    /**
     * 解決RocketMQ Jackson不支持Java時間類型配置
     * 源碼參考:{@link org.apache.rocketmq.spring.autoconfigure.MessageConverterConfiguration}
     */
    @Bean
    @Primary
    public RocketMQMessageConverter enhanceRocketMQMessageConverter(){
        RocketMQMessageConverter converter = new RocketMQMessageConverter();
        CompositeMessageConverter compositeMessageConverter = (CompositeMessageConverter) converter.getMessageConverter();
        List<MessageConverter> messageConverterList = compositeMessageConverter.getConverters();
        for (MessageConverter messageConverter : messageConverterList) {
            if(messageConverter instanceof MappingJackson2MessageConverter){
                MappingJackson2MessageConverter jackson2MessageConverter = (MappingJackson2MessageConverter) messageConverter;
                ObjectMapper objectMapper = jackson2MessageConverter.getObjectMapper();
                objectMapper.registerModules(new JavaTimeModule());
            }
        }
        return converter;
    }
}

2.3 RockeMQ環境隔離

在使用RocketMQ時,通常會在代碼中直接指定消息主題(topic),而且開發環境和測試環境可能共用一個RocketMQ環境。如果沒有進行處理,在開發環境發送的消息就可能被測試環境的消費者消費,測試環境發送的消息也可能被開發環境的消費者消費,從而導致數據混亂的問題。

爲了解決這個問題,我們可以根據不同的環境實現自動隔離。通過簡單配置一個選項,如dev、test、prod等不同環境,所有的消息都會被自動隔離。例如,當發送的消息主題爲consumer_topic時,可以自動在topic後面加上環境後綴,如consumer_topic_dev

那麼,我們該如何實現呢?

可以編寫一個配置類實現BeanPostProcessor,並重寫postProcessBeforeInitialization方法,在監聽器實例初始化前修改對應的topic。

BeanPostProcessor是Spring框架中的一個接口,它的作用是在Spring容器實例化、配置完bean之後,在bean初始化前後進行一些額外的處理工作。

具體來說,BeanPostProcessor接口定義了兩個方法:

  • postProcessBeforeInitialization(Object bean, String beanName): 在bean初始化之前進行處理,可以對bean做一些修改等操作。
  • postProcessAfterInitialization(Object bean, String beanName): 在bean初始化之後進行處理,可以進行一些清理或者其他操作。

BeanPostProcessor可以在應用程序中對Bean的創建和初始化過程進行攔截和修改,對Bean的生命週期進行干預和操作。它可以對所有的Bean類實例進行增強處理,使得開發人員可以在Bean初始化前後自定義一些操作,從而實現自己的業務需求。比如,可以通過BeanPostProcessor來實現注入某些必要的屬性值、加入某一個對象等等。

實現方案如下:

  1. 在配置文件中增加相關配置
rocketmq:
	enhance:
  	# 啓動隔離,用於激活配置類EnvironmentIsolationConfig
  	# 啓動後會自動在topic上拼接激活的配置文件,達到自動隔離的效果
  	enabledIsolation: true
  	# 隔離環境名稱,拼接到topic後,topic_dev,默認空字符串
  	environment: dev
  1. 新增配置類,在實例化消息監聽者之前把topic修改掉
@Configuration
public class EnvironmentIsolationConfig implements BeanPostProcessor {
  	@Value("${rocketmq.enhance.enabledIsolation:true}")
    private boolean enabledIsolation;
    @Value("${rocketmq.enhance.environment:''}")
    private String environmentName;
  
    /**
     * 在裝載Bean之前實現參數修改
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof DefaultRocketMQListenerContainer){

            DefaultRocketMQListenerContainer container = (DefaultRocketMQListenerContainer) bean;
					  //拼接Topic
            if(enabledIsolation && StringUtils.hasText(environmentName)){
                container.setTopic(String.join("_", container.getTopic(),environmentName));
            }
            return container;
        }
        return bean;
    }
}
  1. 啓動項目可以看到日誌中消息監聽的隊列已經被修改了
2023-03-23 17:04:59.726 [main] INFO  o.a.r.s.support.DefaultRocketMQListenerContainer:290 - running container: DefaultRocketMQListenerContainer{consumerGroup='springboot_consumer_group', nameServer='10.5.103.6:9876', topic='consumer_topic_dev', consumeMode=CONCURRENTLY, selectorType=TAG, selectorExpression='*', messageModel=CLUSTERING}

3. RocketMQ二次封裝

在解釋爲什麼要二次封裝之前先來看看RocketMQ官方文檔中推薦的最佳實踐

  1. 消息發送成功或者失敗要打印消息日誌,用於業務排查問題。

  2. 如果消息量較少,建議在消費入口方法打印消息,消費耗時等,方便後續排查問題。

  3. RocketMQ 無法避免消息重複(Exactly-Once),所以如果業務對消費重複非常敏感,務必要在業務層面進行去重處理。可以藉助關係數據庫進行去重。首先需要確定消息的唯一鍵,可以是msgId,也可以是消息內容中的唯一標識字段,例如訂單Id等。

上面三個步驟基本每次發送消息或者消費消息都要實現,屬於重複動作。

接下來討論的是在RocketMQ中發送消息時選擇何種消息類型最爲合適。

在RocketMQ中有四種可選格式:

  1. 發送Json對象
  2. 發送轉Json後的String對象
  3. 根據業務封裝對應實體類
  4. 直接使用原生MessageExt接收。

對於如何選擇消息類型,需要考慮到消費者在不查看消息發送者的情況下,如何獲取消息的含義。因此,在這種情況下,使用第三種方式即根據業務封裝對應實體類的方式最爲合適,也是大多數開發者在發送消息時的常用方式。

有了上面兩點結論以後我們來看看爲什麼要對RocketMQ二次封裝。

3.1 爲什麼要二次封裝

按照上述最佳實踐,一個完整的消息傳遞鏈路從生產到消費應包括 準備消息、發送消息、記錄消息日誌、處理髮送失敗、記錄接收消息日誌、處理業務邏輯、異常處理和異常重試 等步驟。

雖然使用原生RocketMQ可以完成這些動作,但每個生產者和消費者都需要編寫大量重複的代碼來完成相同的任務,這就是需要進行二次封裝的原因。我們希望通過二次封裝,**生產者只需準備好消息實體並調用封裝後的工具類發送,而消費者只需處理核心業務邏輯,其他公共邏輯會得到統一處理。 **

在二次封裝中,關鍵是找出框架在日常使用中所涵蓋的許多操作,以及區分哪些操作是可變的,哪些是不變的。以上述例子爲例,實際上只有生產者的消息準備和消費者的業務處理是可變的操作,需要根據需求進行處理,而其他步驟可以固定下來形成一個模板。

當然,本文提到的二次封裝不是指對源代碼進行封裝,而是針對工具的原始使用方式進行的封裝。可以將其與Mybatis和Mybatis-plus區分開來。這兩者都能完成任務,只不過Mybatis-plus更爲簡單便捷。

3.2 實現二次封裝

實現二次封裝需要創建一個自定義的starter,這樣其他項目只需要依賴此starter即可使用封裝功能。同時,在自定義starter中還需要解決文章第二部分中提到的一些問題。

代碼結構如下所示:

image-20230403160031944

3.2.1 消息實體類的封裝

/**
 * 消息實體,所有消息都需要繼承此類
 * 公衆號:JAVA日知錄
 */
@Data
public abstract class BaseMessage {
    /**
     * 業務鍵,用於RocketMQ控制檯查看消費情況
     */
    protected String key;
    /**
     * 發送消息來源,用於排查問題
     */
    protected String source = "";

    /**
     * 發送時間
     */
    protected LocalDateTime sendTime = LocalDateTime.now();

    /**
     * 重試次數,用於判斷重試次數,超過重試次數發送異常警告
     */
    protected Integer retryTimes = 0;
}

後面所有發送的消息實體都需要繼承此實體類。

3.2.2 消息發送工具類的封裝

@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class RocketMQEnhanceTemplate {
    private final RocketMQTemplate template;

    @Resource
    private RocketEnhanceProperties rocketEnhanceProperties;

    public RocketMQTemplate getTemplate() {
        return template;
    }

    /**
     * 根據系統上下文自動構建隔離後的topic
     * 構建目的地
     */
    public String buildDestination(String topic, String tag) {
        topic = reBuildTopic(topic);
        return topic + ":" + tag;
    }

    /**
     * 根據環境重新隔離topic
     * @param topic 原始topic
     */
    private String reBuildTopic(String topic) {
        if(rocketEnhanceProperties.isEnabledIsolation() && StringUtils.hasText(rocketEnhanceProperties.getEnvironment())){
            return topic +"_" + rocketEnhanceProperties.getEnvironment();
        }
        return topic;
    }

    /**
     * 發送同步消息
     */
    public <T extends BaseMessage> SendResult send(String topic, String tag, T message) {
        // 注意分隔符
        return send(buildDestination(topic,tag), message);
    }


    public <T extends BaseMessage> SendResult send(String destination, T message) {
        // 設置業務鍵,此處根據公共的參數進行處理
        // 更多的其它基礎業務處理...
        Message<T> sendMessage = MessageBuilder.withPayload(message).setHeader(RocketMQHeaders.KEYS, message.getKey()).build();
        SendResult sendResult = template.syncSend(destination, sendMessage);
        // 此處爲了方便查看給日誌轉了json,根據選擇選擇日誌記錄方式,例如ELK採集
        log.info("[{}]同步消息[{}]發送結果[{}]", destination, JSONObject.toJSON(message), JSONObject.toJSON(sendResult));
        return sendResult;
    }

    /**
     * 發送延遲消息
     */
    public <T extends BaseMessage> SendResult send(String topic, String tag, T message, int delayLevel) {
        return send(buildDestination(topic,tag), message, delayLevel);
    }

    public <T extends BaseMessage> SendResult send(String destination, T message, int delayLevel) {
        Message<T> sendMessage = MessageBuilder.withPayload(message).setHeader(RocketMQHeaders.KEYS, message.getKey()).build();
        SendResult sendResult = template.syncSend(destination, sendMessage, 3000, delayLevel);
        log.info("[{}]延遲等級[{}]消息[{}]發送結果[{}]", destination, delayLevel, JSONObject.toJSON(message), JSONObject.toJSON(sendResult));
        return sendResult;
    }
}

這裏封裝了一個消息發送類,實現了日誌記錄以及自動重建topic的功能(即生產者實現環境隔離),後面項目中只需要注入RocketMQEnhanceTemplate來實現消息的發送。

3.2.3 消費者的封裝

@Slf4j
public abstract class EnhanceMessageHandler<T extends BaseMessage> {
    /**
     * 默認重試次數
     */
    private static final int MAX_RETRY_TIMES = 3;

    /**
     * 延時等級
     */
    private static final int DELAY_LEVEL = EnhanceMessageConstant.FIVE_SECOND;


    @Resource
    private RocketMQEnhanceTemplate rocketMQEnhanceTemplate;

    /**
     * 消息處理
     *
     * @param message 待處理消息
     * @throws Exception 消費異常
     */
    protected abstract void handleMessage(T message) throws Exception;

    /**
     * 超過重試次數消息,需要啓用isRetry
     *
     * @param message 待處理消息
     */
    protected abstract void handleMaxRetriesExceeded(T message);


    /**
     * 是否需要根據業務規則過濾消息,去重邏輯可以在此處處理
     * @param message 待處理消息
     * @return true: 本次消息被過濾,false:不過濾
     */
    protected boolean filter(T message) {
        return false;
    }

    /**
     * 是否異常時重複發送
     *
     * @return true: 消息重試,false:不重試
     */
    protected abstract boolean isRetry();

    /**
     * 消費異常時是否拋出異常
     * 返回true,則由rocketmq機制自動重試
     * false:消費異常(如果沒有開啓重試則消息會被自動ack)
     */
    protected abstract boolean throwException();

    /**
     * 最大重試次數
     *
     * @return 最大重試次數,默認5次
     */
    protected int getMaxRetryTimes() {
        return MAX_RETRY_TIMES;
    }

    /**
     * isRetry開啓時,重新入隊延遲時間
     * @return -1:立即入隊重試
     */
    protected int getDelayLevel() {
        return DELAY_LEVEL;
    }

    /**
     * 使用模板模式構建消息消費框架,可自由擴展或刪減
     */
    public void dispatchMessage(T message) {
        // 基礎日誌記錄被父類處理了
        log.info("消費者收到消息[{}]", JSONObject.toJSON(message));

        if (filter(message)) {
            log.info("消息id{}不滿足消費條件,已過濾。",message.getKey());
            return;
        }
        // 超過最大重試次數時調用子類方法處理
        if (message.getRetryTimes() > getMaxRetryTimes()) {
            handleMaxRetriesExceeded(message);
            return;
        }
        try {
            long now = System.currentTimeMillis();
            handleMessage(message);
            long costTime = System.currentTimeMillis() - now;
            log.info("消息{}消費成功,耗時[{}ms]", message.getKey(),costTime);
        } catch (Exception e) {
            log.error("消息{}消費異常", message.getKey(),e);
            // 是捕獲異常還是拋出,由子類決定
            if (throwException()) {
                //拋出異常,由DefaultMessageListenerConcurrently類處理
                throw new RuntimeException(e);
            }
            //此時如果不開啓重試機制,則默認ACK了
            if (isRetry()) {
                handleRetry(message);
            }
        }
    }

    protected void handleRetry(T message) {
        // 獲取子類RocketMQMessageListener註解拿到topic和tag
        RocketMQMessageListener annotation = this.getClass().getAnnotation(RocketMQMessageListener.class);
        if (annotation == null) {
            return;
        }
        //重新構建消息體
        String messageSource = message.getSource();
        if(!messageSource.startsWith(EnhanceMessageConstant.RETRY_PREFIX)){
            message.setSource(EnhanceMessageConstant.RETRY_PREFIX + messageSource);
        }
        message.setRetryTimes(message.getRetryTimes() + 1);

        SendResult sendResult;

        try {
            // 如果消息發送不成功,則再次重新發送,如果發送異常則拋出由MQ再次處理(異常時不走延遲消息)
            sendResult = rocketMQEnhanceTemplate.send(annotation.topic(), annotation.selectorExpression(), message, getDelayLevel());
        } catch (Exception ex) {
            // 此處捕獲之後,相當於此條消息被消息完成然後重新發送新的消息
            //由生產者直接發送
            throw new RuntimeException(ex);
        }
        // 發送失敗的處理就是不進行ACK,由RocketMQ重試
        if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
            throw new RuntimeException("重試消息發送失敗");
        }

    }
}

使用模版設計模式定義了消息消費的骨架,實現了日誌打印,異常處理,異常重試等公共邏輯,消息過濾(查重)、業務處理則交由子類實現。

3.2.4 基礎配置類

@Configuration
@EnableConfigurationProperties(RocketEnhanceProperties.class)
public class RocketMQEnhanceAutoConfiguration {

    /**
     * 注入增強的RocketMQEnhanceTemplate
     */
    @Bean
    public RocketMQEnhanceTemplate rocketMQEnhanceTemplate(RocketMQTemplate rocketMQTemplate){
        return new RocketMQEnhanceTemplate(rocketMQTemplate);
    }

    /**
     * 解決RocketMQ Jackson不支持Java時間類型配置
     * 源碼參考:{@link org.apache.rocketmq.spring.autoconfigure.MessageConverterConfiguration}
     */
    @Bean
    @Primary
    public RocketMQMessageConverter enhanceRocketMQMessageConverter(){
        RocketMQMessageConverter converter = new RocketMQMessageConverter();
        CompositeMessageConverter compositeMessageConverter = (CompositeMessageConverter) converter.getMessageConverter();
        List<MessageConverter> messageConverterList = compositeMessageConverter.getConverters();
        for (MessageConverter messageConverter : messageConverterList) {
            if(messageConverter instanceof MappingJackson2MessageConverter){
                MappingJackson2MessageConverter jackson2MessageConverter = (MappingJackson2MessageConverter) messageConverter;
                ObjectMapper objectMapper = jackson2MessageConverter.getObjectMapper();
                objectMapper.registerModules(new JavaTimeModule());
            }
        }
        return converter;
    }


    /**
     * 環境隔離配置
     */
    @Bean
    @ConditionalOnProperty(name="rocketmq.enhance.enabledIsolation", havingValue="true")
    public EnvironmentIsolationConfig environmentSetup(RocketEnhanceProperties rocketEnhanceProperties){
        return new EnvironmentIsolationConfig(rocketEnhanceProperties);
    }

}
public class EnvironmentIsolationConfig implements BeanPostProcessor {
    private RocketEnhanceProperties rocketEnhanceProperties;

    public EnvironmentIsolationConfig(RocketEnhanceProperties rocketEnhanceProperties) {
        this.rocketEnhanceProperties = rocketEnhanceProperties;
    }


    /**
     * 在裝載Bean之前實現參數修改
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof DefaultRocketMQListenerContainer){

            DefaultRocketMQListenerContainer container = (DefaultRocketMQListenerContainer) bean;

            if(rocketEnhanceProperties.isEnabledIsolation() && StringUtils.hasText(rocketEnhanceProperties.getEnvironment())){
                container.setTopic(String.join("_", container.getTopic(),rocketEnhanceProperties.getEnvironment()));
            }
            return container;
        }
        return bean;
    }
}
@ConfigurationProperties(prefix = "rocketmq.enhance")
@Data
public class RocketEnhanceProperties {

    private boolean enabledIsolation;

    private String environment;
}

3.3 封裝後的使用

3.3.1 引入依賴

 <dependency>
   <groupId>com.jianzh5</groupId>
   <artifactId>cloud-rocket-starter</artifactId>
</dependency>

3.3.2 自定義配置

rocketmq:
	...
	enhance:
		# 啓動隔離,用於激活配置類EnvironmentIsolationConfig
  	# 啓動後會自動在topic上拼接激活的配置文件,達到自動隔離的效果
  	enabledIsolation: true
    # 隔離環境名稱,拼接到topic後,topic_dev,默認空字符串
    environment: dev

3.3.3 發送消息

@RestController
@RequestMapping("enhance")
@Slf4j
public class EnhanceProduceController {

    //注入增強後的模板,可以自動實現環境隔離,日誌記錄
    @Setter(onMethod_ = @Autowired)
    private RocketMQEnhanceTemplate rocketMQEnhanceTemplate;

    private static final String topic = "rocket_enhance";
    private static final String tag = "member";

    /**
     * 發送實體消息
     */
    @GetMapping("/member")
    public SendResult member() {
        String key = UUID.randomUUID().toString();
        MemberMessage message = new MemberMessage();
        // 設置業務key
        message.setKey(key);
        // 設置消息來源,便於查詢
        message.setSource("MEMBER");
        // 業務消息內容
        message.setUserName("Java日知錄");
        message.setBirthday(LocalDate.now());

        return rocketMQEnhanceTemplate.send(topic, tag, message);
    }
}

注意這裏使用的是封裝後的模板工具類,一旦在配置文件中啓動環境隔離,則生產者的消息也自動發送到隔離後的topic中。

3.3.4 消費者

@Slf4j
@Component
@RocketMQMessageListener(
        consumerGroup = "enhance_consumer_group",
        topic = "rocket_enhance",
        selectorExpression = "*",
        consumeThreadMax = 5 //默認是64個線程併發消息,配置 consumeThreadMax 參數指定併發消費線程數,避免太大導致資源不夠
)
public class EnhanceMemberMessageListener extends EnhanceMessageHandler<MemberMessage> implements RocketMQListener<MemberMessage> {

    @Override
    protected void handleMessage(MemberMessage message) throws Exception {
        // 此時這裏纔是最終的業務處理,代碼只需要處理資源類關閉異常,其他的可以交給父類重試
        System.out.println("業務消息處理:"+message.getUserName());
    }

    @Override
    protected void handleMaxRetriesExceeded(MemberMessage message) {
        // 當超過指定重試次數消息時此處方法會被調用
        // 生產中可以進行回退或其他業務操作
        log.error("消息消費失敗,請執行後續處理");
    }


    /**
     * 是否執行重試機制
     */
    @Override
    protected boolean isRetry() {
        return true;
    }

    @Override
    protected boolean throwException() {
        // 是否拋出異常,false搭配retry自行處理異常
        return false;
    }
  
    @Override
    protected boolean filter() {
        // 消息過濾
        return false;
    }

    /**
     * 監聽消費消息,不需要執行業務處理,委派給父類做基礎操作,父類做完基礎操作後會調用子類的實際處理類型
     */
    @Override
    public void onMessage(MemberMessage memberMessage) {
        super.dispatchMessage(memberMessage);
    }
}

爲了方便消費者對RocketMQ中的消息進行處理,我們可以使用EnhanceMessageHandler來進行消息的處理和邏輯的處理。

消費者實現了RocketMQListener的同時,可以繼承EnhanceMessageHandler來進行公共邏輯的處理,而核心業務邏輯需要自己實現handleMessage方法。 如果需要對消息進行過濾或者去重的處理,則可以重寫父類的filter方法進行實現。這樣可以更加方便地對消息進行處理,減輕開發者的工作量。

以上,就是今天的主要內容,希望對你有所幫助!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章