Files
cn-rdms/rdms-framework/rdms-spring-boot-starter-mq
2026-03-11 19:32:37 +08:00
..
2026-03-11 19:32:37 +08:00
2026-03-11 19:32:37 +08:00
2026-03-11 19:32:37 +08:00
2026-03-11 19:32:37 +08:00

rdms-spring-boot-starter-mq

模块定位

rdms-spring-boot-starter-mq 是项目里的消息队列基础封装,目标是:

  1. 对 Redis 消息模型做统一抽象,降低业务接入复杂度
  2. 通过自动配置按需启用消费者容器,不强制业务使用全部能力
  3. 为后续多 MQ 方案Redis / RabbitMQ / RocketMQ / Kafka提供统一入口

当前实现里,核心能力在 RedisRabbitMQ 是轻量补充(消息转换器)。

设计思路

1. Redis 统一模型

Redis 相关能力分成两类:

  1. Pub/Sub 广播模型
  2. Stream 分组消费模型

两者的消息基类分别是:

  1. AbstractRedisChannelMessage
  2. AbstractRedisStreamMessage

共同父类是 AbstractRedisMessage,内置 headers,方便扩展链路信息。

2. 统一发送模板

发送端统一走 RedisMQTemplate:

  1. send(AbstractRedisChannelMessage) -> Redis convertAndSend
  2. send(AbstractRedisStreamMessage) -> Redis Stream XADD

这样业务代码不需要关心底层命令细节。

3. 监听器抽象 + 惰性自动装配

消费者由两个抽象监听器承接:

  1. AbstractRedisChannelMessageListener<T>
  2. AbstractRedisStreamMessageListener<T>

自动配置采用 @ConditionalOnBean(...):

  1. 只有你定义了对应监听器 Bean容器才会注册消费者
  2. 没有监听器时不会额外启动 MQ 消费组件

4. Stream 运维补偿

针对 Redis Stream框架额外提供两个定时任务:

  1. RedisPendingMessageResendJob
    扫描 pending 超时消息并重投(默认超时 5 分钟)
  2. RedisStreamMessageCleanupJob
    定时 XTRIM,默认仅保留最近 10000 条,防止内存膨胀

两者都使用 Redisson 分布式锁,避免多实例重复执行。

5. RabbitMQ 轻封装

RdmsRabbitMQAutoConfiguration 只提供 Jackson2JsonMessageConverter,让 RabbitTemplate / @RabbitListener 默认按 JSON 处理消息。

6. 多 MQ 的真实边界

虽然模块描述写了支持 Redis / RocketMQ / RabbitMQ / Kafka
但本 starter 的自动配置主要是 Redis + Rabbit。 RocketMQ / Kafka 的业务化接入在 rdms-spring-boot-starter-websocket 里有更完整示例。

自动配置入口

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:

  1. RdmsRedisMQProducerAutoConfiguration
  2. RdmsRedisMQConsumerAutoConfiguration
  3. RdmsRabbitMQAutoConfiguration

如何使用

1. 引入依赖

一般由业务模块直接依赖:

<dependency>
    <groupId>com.njcn</groupId>
    <artifactId>rdms-spring-boot-starter-mq</artifactId>
</dependency>

1.1 项目现有 Redis 作为 MQ 的常规用法(推荐)

推荐按这个顺序接入:

  1. 配好 spring.data.redis.*
  2. 先按第 2 节使用 Pub/Sub(这是项目里最常见路径)。
  3. 需要可恢复消费时,再按第 3 节接入 Stream

WebSocket 场景建议:

  1. 多实例广播: rdms.websocket.sender-type: redis
  2. 单机部署: 保持 rdms.websocket.sender-type: local

说明:

  1. 只要存在 AbstractRedisChannelMessageListener Bean框架会自动注册 Redis 监听容器。

2. 使用 Redis Pub/Sub广播

适用场景: 通知广播、在线会话广播、对可靠性要求不高但强调实时性。

2.1 定义消息

import com.njcn.rdms.framework.mq.redis.core.pubsub.AbstractRedisChannelMessage;
import lombok.Data;

@Data
public class DemoNotifyMessage extends AbstractRedisChannelMessage {

    private Long userId;
    private String content;

    // 可选: 自定义 channel不重写时默认是类名
    @Override
    public String getChannel() {
        return "demo:notify";
    }
}

2.2 定义消费者

import com.njcn.rdms.framework.mq.redis.core.pubsub.AbstractRedisChannelMessageListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class DemoNotifyListener extends AbstractRedisChannelMessageListener<DemoNotifyMessage> {

    @Override
    public void onMessage(DemoNotifyMessage message) {
        log.info("收到广播消息 userId={}, content={}", message.getUserId(), message.getContent());
    }
}

2.3 发送消息

import com.njcn.rdms.framework.mq.redis.core.RedisMQTemplate;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class DemoNotifyProducer {

    private final RedisMQTemplate redisMQTemplate;

    public void send(Long userId, String content) {
        redisMQTemplate.send(new DemoNotifyMessage()
                .setUserId(userId)
                .setContent(content));
    }
}

3. 使用 Redis Stream分组消费 + ACK

适用场景: 异步任务、可恢复消费、希望具备重投和清理机制。

3.1 定义消息

import com.njcn.rdms.framework.mq.redis.core.stream.AbstractRedisStreamMessage;
import lombok.Data;

@Data
public class DemoTaskMessage extends AbstractRedisStreamMessage {

    private Long taskId;
    private String bizType;

    // 可选: 自定义 stream key不重写时默认是类名
    @Override
    public String getStreamKey() {
        return "demo:task:stream";
    }
}

3.2 定义消费者

import com.njcn.rdms.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class DemoTaskListener extends AbstractRedisStreamMessageListener<DemoTaskMessage> {

    @Override
    public void onMessage(DemoTaskMessage message) {
        log.info("消费任务 taskId={}, bizType={}", message.getTaskId(), message.getBizType());
        // 这里抛异常会导致本次消费失败,不会执行 ACK
    }
}

3.3 发送消息

import com.njcn.rdms.framework.mq.redis.core.RedisMQTemplate;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.connection.stream.RecordId;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class DemoTaskProducer {

    private final RedisMQTemplate redisMQTemplate;

    public RecordId send(Long taskId, String bizType) {
        return redisMQTemplate.send(new DemoTaskMessage()
                .setTaskId(taskId)
                .setBizType(bizType));
    }
}

4. 消息拦截器扩展(可选)

可通过实现 RedisMessageInterceptor,对发送/消费前后做统一处理(比如租户透传、审计埋点)。

import com.njcn.rdms.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
import com.njcn.rdms.framework.mq.redis.core.message.AbstractRedisMessage;
import org.springframework.stereotype.Component;

@Component
public class DemoRedisMessageInterceptor implements RedisMessageInterceptor {

    @Override
    public void sendMessageBefore(AbstractRedisMessage message) {
        message.addHeader("source", "rdms-system");
    }
}

配置说明

本模块本身几乎没有 rdms.mq.* 配置项,主要依赖:

  1. spring.data.redis.*Redis 连接)
  2. spring.application.nameStream consumer group 默认值)

另外:

  1. Redis Stream 要求 Redis 版本 >= 5.0
  2. 只有存在 Stream 监听器时,重投/清理定时任务才会生效

与 WebSocket 的关系

rdms-spring-boot-starter-websocket 直接复用本模块能力:

  1. sender-type=redis 时使用 RedisMQTemplate + AbstractRedisChannelMessageListener
  2. sender-type=rabbitmq/rocketmq/kafka 时切换到对应中间件实现

你可以参考 websocket 模块作为本模块的落地样板。

当前已知注意点

  1. AbstractRedisStreamMessageListener 有一个 (streamKey, group) 构造器,内部把 messageType 置空;而消费反序列化仍依赖 messageType
    建议优先使用无参构造路径(即泛型推断消息类型的默认方式)。
  2. Stream 的重投机制本质是“至少一次投递”语义,业务侧应自行保证幂等。

适用与不适用

适用:

  1. 需要快速接入 Redis 广播/异步任务队列
  2. 希望保留基础的失败补偿和消息清理能力
  3. 希望通过抽象基类统一消息定义风格

不适用:

  1. 对事务一致性、延迟、吞吐有强约束且需要完整 MQ 运维体系
  2. 需要复杂顺序语义、死信重试拓扑、跨机房高可用治理