高效异步处理:基于RocketMQ的延迟消费系统架构全解析

基于RocketMQ的延迟消费系统架构全解析

一、消息实体定义

二、生产者服务实现

三、消费者监听器实现

四、配置文件

五、Maven依赖

六、关键设计要点

[6.1 生产者发送策略](#6.1 生产者发送策略)

[6.2 消费者处理策略](#6.2 消费者处理策略)

[6.3 配置优化](#6.3 配置优化)

一、消息实体定义

java 复制代码
package jnpf.model.attendance.event;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;

/**
 * @author shitou
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AttendanceStatisticsSingleDto implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 租户Id
     */
    @NotBlank(message = "租户Id不能为空")
    private String tenantId;
    /**
     * 考勤组Id
     */
    @NotBlank(message = "考勤组Id不能为空")
    private String groupId;
    /**
     * 用户Id
     */
    @NotBlank(message = "用户Id不能为空")
    private String userId;
    /**
     * 日期
     */
    @NotNull(message = "日期不能为空")
    private Date day;
}

二、生产者服务实现

java 复制代码
    @NoDataSourceBind
    @Operation(summary = "模拟统计数据消息推送")
    @GetMapping("/mockStatisticsPush")
    public ActionResult<Boolean> mockStatisticsPush(@RequestParam("tenantId") String tenantId,
                                                    @RequestParam("groupId") String groupId,
                                                    @RequestParam("userId") String userId,
                                                    @RequestParam("day") String day) {
        AttendanceStatisticsSingleDto courseEventDTO = AttendanceStatisticsSingleDto.builder()
                .tenantId(tenantId)
                .groupId(groupId)
                .userId(userId)
                .day(DateUtil.parse(day))
                .build();
        Message<AttendanceStatisticsSingleDto> message = MessageBuilder.withPayload(courseEventDTO).build();
        rocketMqTemplate.syncSend(MessageTopicConstants.ATTENDANCE_STATISTICS_SINGLE_TOPIC, message, 3000L, 2);
        return ActionResult.success();
    }

三、消费者监听器实现

java 复制代码
package jnpf.attendance.event;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import jnpf.attendance.service.AttendanceDayStatisticsService;
import jnpf.constants.MessageTopicConstants;
import jnpf.model.attendance.event.AttendanceStatisticsSingleDto;
import jnpf.util.CustomTenantUtil;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.apache.rocketmq.spring.core.RocketMQPushConsumerLifecycleListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Objects;

/**
 * 监听单个用户的考勤日统计消息
 * 消费失败会重试最多2次(共3次),之后进入死信队列
 */
@Slf4j
@Component
@RocketMQMessageListener(
        topic = MessageTopicConstants.ATTENDANCE_STATISTICS_SINGLE_TOPIC,
        consumerGroup = MessageTopicConstants.ATTENDANCE_STATISTICS_SINGLE_CONSUMER_GROUP,
        consumeMode = ConsumeMode.CONCURRENTLY,
        consumeThreadNumber = 2,
        maxReconsumeTimes = 2)
public class StatisticsSingleMQListener implements RocketMQListener<AttendanceStatisticsSingleDto>, RocketMQPushConsumerLifecycleListener {

    @Autowired
    private CustomTenantUtil tenantUtil;
    @Resource
    private AttendanceDayStatisticsService attendanceDayStatisticsService;

    @Override
    public void onMessage(AttendanceStatisticsSingleDto singleDto) {
        if (Objects.isNull(singleDto)) {
            log.warn("接收到空消息,忽略处理");
            return;
        }
        log.error("接受到一条生成用户日统计消息,{}", JSONObject.toJSONString(singleDto));
        // 校验必要字段
        if (ObjectUtil.isNull(singleDto) || StringUtil.isEmpty(singleDto.getUserId()) ||
                StringUtil.isEmpty(singleDto.getTenantId()) || StringUtil.isEmpty(singleDto.getGroupId()) ||
                ObjectUtil.isNull(singleDto.getDay())) {
            log.error("生成用户日统计消息消费失败:消息格式无效,内容: {}", JSONObject.toJSONString(singleDto));
            return;
        }
        try {
            tenantUtil.checkOutTenant(singleDto.getTenantId());
            attendanceDayStatisticsService.statisticDataChange(singleDto.getGroupId(), singleDto.getUserId(), singleDto.getDay());
        } catch (Exception ex) {
            log.error("处理用户日统计消息失败,触发重试,消息 {}, 错误: ", JSONObject.toJSONString(singleDto), ex);
            throw new RuntimeException("处理用户日统计消息失败,触发重试", ex);
        }
    }

    @Override
    public void prepareStart(DefaultMQPushConsumer consumer) {
        // 优化消费参数 每次拉取的消息数量
        consumer.setPullBatchSize(16);
        // 顺序消费每次批量消费2条
        consumer.setConsumeMessageBatchMaxSize(2);
        // 设置消费间隔,避免过于频繁拉取
        consumer.setPullInterval(3000);
        // 设置消费超时时间(分钟)
        consumer.setConsumeTimeout(10);
        // 设置消费起始位置(从上次消费的位置继续)
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
        // 调整消费线程池最小线程数
        consumer.setConsumeThreadMin(2);
        // 调整消费线程池最大线程数
        consumer.setConsumeThreadMax(4);
    }
}

四、配置文件

application.yml:

yaml 复制代码
rocketmq:
  name-server: ${ROCKETMQ_NAME_SERVER:192.168.3.24:30094}
  producer:
    group: ${ROCKETMQ_PRODUCER_GROUP:fantaibao-producer-group}
    send-message-timeout: ${ROCKETMQ_SEND_MESSAGE_TIMEOUT:30000}
    max-message-size: ${ROCKETMQ_MAX_MESSAGE_SIZE:8388608}
 

消息主题常量:

java 复制代码
package jnpf.constants;

public interface MessageTopicConstants {
        //考勤统计单个生成-发送的延迟消息(等级是2(5秒))
    String ATTENDANCE_STATISTICS_SINGLE_TOPIC = "attendance-statistics-single-topic";
    String ATTENDANCE_STATISTICS_SINGLE_CONSUMER_GROUP = "attendance-statistics-single--consumer-group";
}

五、Maven依赖

xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
    <version>2022.0.0.0</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.32</version>
</dependency>
 

六、关键设计要点

6.1 生产者发送策略

  1. 延迟发送:定时触发或削峰填谷

6.2 消费者处理策略

  1. 异步处理:使用线程池异步执行,避免阻塞消费线程

  2. 延迟执行:2秒延迟实现削峰填谷

  3. 参数校验:三层校验确保消息有效性

  4. 异常处理:异常抛出触发重试机制

6.3 配置优化

  1. 拉取参数:平衡拉取频率和消息堆积

  2. 超时设置:根据业务处理时间设置合理超时

  3. 重试策略:设置最大重试次数避免无限重试

这个实现提供了完整的基于RocketMQ的考勤统计批量处理方案,包含生产者、消费者业务服务实现,可以直接在项目中集成使用。

相关推荐
阿里云云原生2 天前
秒触达、零资损:亲宝宝基于 RocketMQ 支撑千万家庭实时互动与成长记录
serverless·rocketmq
刀法如飞3 天前
从程序员到架构师:6大编程范式全解析与实践对比
设计模式·系统架构·编程范式
初次攀爬者3 天前
RocketMQ 消息可靠性保障与堆积处理
后端·消息队列·rocketmq
用户8307196840823 天前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者3 天前
RocketMQ 集群介绍
后端·消息队列·rocketmq
初次攀爬者3 天前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq
初次攀爬者3 天前
RocketMQ 基础学习
后端·消息队列·rocketmq
阿里云云原生6 天前
下单丝滑,大促自由:古茗奶茶背后的云原生力量
serverless·rocketmq
郑州光合科技余经理7 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
王九思7 天前
Thrift Server 介绍
大数据·系统架构·运维开发