初始化

This commit is contained in:
2026-03-11 19:32:37 +08:00
commit 5708f80091
904 changed files with 68154 additions and 0 deletions

View File

@@ -0,0 +1,256 @@
# 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<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. 引入依赖
一般由业务模块直接依赖:
```xml
<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 定义消息
```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<DemoNotifyMessage> {
@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<DemoTaskMessage> {
@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. 需要复杂顺序语义、死信重试拓扑、跨机房高可用治理