rdms-spring-boot-starter-mq
模块定位
rdms-spring-boot-starter-mq 是项目里的消息队列基础封装,目标是:
- 对 Redis 消息模型做统一抽象,降低业务接入复杂度
- 通过自动配置按需启用消费者容器,不强制业务使用全部能力
- 为后续多 MQ 方案(Redis / RabbitMQ / RocketMQ / Kafka)提供统一入口
当前实现里,核心能力在 Redis,RabbitMQ 是轻量补充(消息转换器)。
设计思路
1. Redis 统一模型
Redis 相关能力分成两类:
Pub/Sub广播模型Stream分组消费模型
两者的消息基类分别是:
AbstractRedisChannelMessageAbstractRedisStreamMessage
共同父类是 AbstractRedisMessage,内置 headers,方便扩展链路信息。
2. 统一发送模板
发送端统一走 RedisMQTemplate:
send(AbstractRedisChannelMessage)-> RedisconvertAndSendsend(AbstractRedisStreamMessage)-> Redis StreamXADD
这样业务代码不需要关心底层命令细节。
3. 监听器抽象 + 惰性自动装配
消费者由两个抽象监听器承接:
AbstractRedisChannelMessageListener<T>AbstractRedisStreamMessageListener<T>
自动配置采用 @ConditionalOnBean(...):
- 只有你定义了对应监听器 Bean,容器才会注册消费者
- 没有监听器时不会额外启动 MQ 消费组件
4. Stream 运维补偿
针对 Redis Stream,框架额外提供两个定时任务:
RedisPendingMessageResendJob
扫描 pending 超时消息并重投(默认超时 5 分钟)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:
RdmsRedisMQProducerAutoConfigurationRdmsRedisMQConsumerAutoConfigurationRdmsRabbitMQAutoConfiguration
如何使用
1. 引入依赖
一般由业务模块直接依赖:
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-mq</artifactId>
</dependency>
1.1 项目现有 Redis 作为 MQ 的常规用法(推荐)
推荐按这个顺序接入:
- 配好
spring.data.redis.*。 - 先按第 2 节使用
Pub/Sub(这是项目里最常见路径)。 - 需要可恢复消费时,再按第 3 节接入
Stream。
WebSocket 场景建议:
- 多实例广播:
rdms.websocket.sender-type: redis - 单机部署: 保持
rdms.websocket.sender-type: local
说明:
- 只要存在
AbstractRedisChannelMessageListenerBean,框架会自动注册 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.* 配置项,主要依赖:
spring.data.redis.*(Redis 连接)spring.application.name(Stream consumer group 默认值)
另外:
- Redis Stream 要求 Redis 版本 >= 5.0
- 只有存在 Stream 监听器时,重投/清理定时任务才会生效
与 WebSocket 的关系
rdms-spring-boot-starter-websocket 直接复用本模块能力:
sender-type=redis时使用RedisMQTemplate+AbstractRedisChannelMessageListenersender-type=rabbitmq/rocketmq/kafka时切换到对应中间件实现
你可以参考 websocket 模块作为本模块的落地样板。
当前已知注意点
AbstractRedisStreamMessageListener有一个(streamKey, group)构造器,内部把messageType置空;而消费反序列化仍依赖messageType。
建议优先使用无参构造路径(即泛型推断消息类型的默认方式)。- Stream 的重投机制本质是“至少一次投递”语义,业务侧应自行保证幂等。
适用与不适用
适用:
- 需要快速接入 Redis 广播/异步任务队列
- 希望保留基础的失败补偿和消息清理能力
- 希望通过抽象基类统一消息定义风格
不适用:
- 对事务一致性、延迟、吞吐有强约束且需要完整 MQ 运维体系
- 需要复杂顺序语义、死信重试拓扑、跨机房高可用治理