# rdms-spring-boot-starter-mq ## 模块定位 `rdms-spring-boot-starter-mq` 是项目里的消息队列基础封装,目标是: 1. 对 Redis 消息模型做统一抽象,降低业务接入复杂度 2. 通过自动配置按需启用消费者容器,不强制业务使用全部能力 3. 为后续多 MQ 方案(Redis / RabbitMQ / RocketMQ / Kafka)提供统一入口 当前实现里,**核心能力在 Redis**,RabbitMQ 是轻量补充(消息转换器)。 ## 设计思路 ### 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` 2. `AbstractRedisStreamMessageListener` 自动配置采用 `@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. 引入依赖 一般由业务模块直接依赖: ```xml com.njcn rdms-spring-boot-starter-mq ``` ## 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 定义消息 ```java 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 定义消费者 ```java 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 { @Override public void onMessage(DemoNotifyMessage message) { log.info("收到广播消息 userId={}, content={}", message.getUserId(), message.getContent()); } } ``` ### 2.3 发送消息 ```java 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 定义消息 ```java 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 定义消费者 ```java 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 { @Override public void onMessage(DemoTaskMessage message) { log.info("消费任务 taskId={}, bizType={}", message.getTaskId(), message.getBizType()); // 这里抛异常会导致本次消费失败,不会执行 ACK } } ``` ### 3.3 发送消息 ```java 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`,对发送/消费前后做统一处理(比如租户透传、审计埋点)。 ```java 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.name`(Stream 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. 需要复杂顺序语义、死信重试拓扑、跨机房高可用治理