Files
mq-starter/README.md
hongawen ae0caafa63 docs(mq-starter): 重写 README + 新增 CLAUDE.md,gitignore docs/
- README 重写为面向使用者的完整说明(快速开始/配置项/两 driver 能力对照/架构/TODO)
- 新增 CLAUDE.md:补代码结构、装配链路、消费 ACK 语义等踩坑点,供 Claude Code 参考
- .gitignore 忽略 docs/(AI 生成的本地参考/教学资料,不纳入版本库)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 20:41:54 +08:00

244 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# mq-starter
把消息队列封装成一套统一接口的 Spring Boot Starter。业务代码用 `MqTemplate` 发、用 `@MqListener` 收,底层到底走 RocketMQ 还是 Redis Stream交给一行配置决定切换的时候业务代码一行都不用动。
思路和 JDBC 差不多:你面向 `Connection` / `Statement` 写代码,换 MySQL 还是 Oracle 只是改驱动和连接串;这里换 MQ 只是改 `mq.type`
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE)
[![JDK](https://img.shields.io/badge/JDK-1.8-orange.svg)](https://www.oracle.com/java/)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.3.12-brightgreen.svg)](https://spring.io/projects/spring-boot)
```yaml
mq:
type: redis-stream # 换成 rocketmq 也行,业务代码不动
```
```java
@Resource MqTemplate mq;
mq.send("LN_Topic", payload);
@MqListener(topic = "LN_Topic", group = "ln_consumer")
public void onData(FrontData msg) {
// msg 已经反序列化好了,这里只写业务
}
```
## 为什么写它
之前项目里跟 MQ 打交道producer / consumer 的注册、ack、序列化、失败重试、去重这些样板代码散落得到处都是真要把 RocketMQ 换成别的,消费层基本得重写一遍。
mq-starter 想做的就是把这些脏活收进 starter业务侧只保留「发什么」和「收什么」剩下的连接管理、线程模型、确认机制都不用管。顺手把消费侧几个常见需求去重、批量、失败落库也做成了注解开关。
## 快速开始
### 引入
业务方只需要引一个聚合坐标,两套 driver 都打在里面,运行时按 `mq.type` 二选一装配:
```xml
<dependency>
<groupId>com.njcn</groupId>
<artifactId>mq-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
```
目前还没发到中央仓库,先在项目根目录 `mvn install` 装到本地仓库用。
### 配置
用 Redis Stream
```yaml
mq:
type: redis-stream
spring:
redis:
host: localhost
port: 6379
```
用 RocketMQ
```yaml
mq:
type: rocketmq
rocketmq:
name-server: localhost:9876
producer:
group: my-producer-group
```
`mq.type` 是唯一的装配开关。没配它,`MqTemplate``@MqListener` 都不会生效。
### 发送
```java
@Resource
private MqTemplate mq;
mq.send("LN_Topic", frontData); // topic + 消息体
mq.send("File_Topic", nodeId, fileData); // 带路由 key映射成 tag
```
消息体传任意对象就行driver 负责序列化。每条消息会自动生成一个雪花 ID 当 key去重时会用到。
### 消费
方法上打个注解就行方法参数写你期望的类型starter 按类型反序列化:
```java
@Component
public class FrontDataConsumer {
@MqListener(topic = "LN_Topic", group = "ln_consumer")
public void onFrontData(FrontData msg) {
// ...
}
// 想拿 key / tag / timestamp / headers 这些元数据,就再加一个 MqContext 参数
@MqListener(topic = "File_Topic", group = "file_consumer")
public void onFile(FileData msg, MqContext ctx) {
String key = ctx.getKey();
}
}
```
方法签名只认这两种:`(payload)``(payload, MqContext)`
## 进阶用法
去重、批量、失败落库都是 `@MqListener` 上的属性,业务方法本身不用改:
```java
// 去重:重复消息不会再进业务(需要 MqIdempotentStoreredis-stream 自带了一个)
@MqListener(topic = "LN_Topic", group = "g", idempotent = true)
public void onData(FrontData msg) { }
// 批量:攒够 batchSize 条一次给你,参数要用 List
@MqListener(topic = "LN_Topic", group = "g", batchSize = 50)
public void onBatch(List<FrontData> msgs) { }
// 失败落库:消费失败的消息交给你自己的 handler 落库
@MqListener(topic = "LN_Topic", group = "g", errorHandler = "myErrorHandler")
public void onData2(FrontData msg) { }
```
落库 handler 自己实现一个 bean
```java
@Component("myErrorHandler")
public class MyErrorHandler implements MqConsumeErrorHandler {
@Override
public void onError(MqMessage message, MqErrorIdentity identity, Exception ex) {
// identity 区分是单次失败(SINGLE)还是重试耗尽(RETRY_EXHAUSTED)
// 把 message 存到异常表,后面人工或定时补偿
}
}
```
## 两个 driver 不太一样
这点得说在前面两套实现的完整度并不一致。Redis Stream 这套是照着 core 的设计意图做全的去重、批量、失败落库、reclaim 重试都接上了RocketMQ 那套目前只做了基础收发加原生的重试和死信,去重和 `RETRY_EXHAUSTED` 落库还没接进去。
| 能力 | Redis Stream | RocketMQ |
|---|:---:|:---:|
| 收发 / 并发消费 | ✅ | ✅ |
| 去重 `idempotent` | ✅ 内置实现 | ❌ 还没接,配了也不生效 |
| 失败落库 `RETRY_EXHAUSTED` | ✅ | ❌ 只会进原生 DLQ |
| 失败落库 `SINGLE` | ✅ | ⚠️ 异常会被转成 RECONSUME_LATER |
| 批量 `batchSize` | ✅ | ⚠️ 逐条回调,靠 core 内存攒批 |
| 重试 | ✅ PEL reclaim | ✅ 原生重投 |
| 死信队列 | ❌ 只落库 | ✅ 原生 `%DLQ%` |
| 顺序消息 | ❌ | ❌ 用的 Concurrently |
| 事务 / 延迟消息 | ❌ | ❌ 没暴露 |
| 序列化 | Jackson | fastjson |
所以现在如果你依赖去重或者失败落库,先用 Redis Stream。RocketMQ driver 能收能发,但还没「放量」,具体见下面的 TODO。
## 架构
```
业务代码(只见 MqTemplate / @MqListener
┌──────────────────────┴──────────────────────┐
│ mq-starter-core │
│ MqTemplate · @MqListener 扫描 · MqMessage │
│ 消费链:去重 → 攒批 → 消费 → 落库/重试 │
│ SPIMqDriver / MqSubscription / ... │
└──────────────┬──────────────────┬────────────┘
@ConditionalOnProperty(mq.type) 二选一
┌──────────────┴─────┐ ┌─────────┴───────────┐
│ redis-stream driver │ │ rocketmq driver │
└─────────────────────┘ └─────────────────────┘
```
四个模块:
| 模块 | 干什么 |
|---|---|
| `mq-starter-core` | 门面 `MqTemplate``@MqListener` 扫描注册、中立信封 `MqMessage`、消费处理链、几个 SPI还有基于 `SmartLifecycle` 的启停 |
| `mq-starter-redis-stream` | Redis Stream 实现XADD / 消费组 XREADGROUP / XACK / XPENDING+XCLAIM reclaim另带一个默认的去重实现 |
| `mq-starter-rocketmq` | RocketMQ 实现,基于 rocketmq-spring 编程式注册 |
| `mq-spring-boot-starter` | 聚合 jar业务方引这一个就够 |
core 是中立的,编排逻辑不依赖 Spring 和 Redis可以直接单测。想看更细的图文消费链、SPI 契约、配置速查、FAQ 都在里面),打开 [`docs/mq-starter-全景手册.html`](./docs/mq-starter-全景手册.html)。
## 配置项
`@MqListener` 的属性:
| 属性 | 默认 | 说明 |
|---|---|---|
| `topic` / `group` | 必填 | 主题 / 消费组 |
| `concurrency` | 1 | 并发消费线程数,实际由 driver 兑现 |
| `batchSize` | 1 | 攒批大小,大于 1 要用 `List` 参数 |
| `maxRetry` | 3 | 最大重试次数 |
| `tag` | `*` | tag 过滤 |
| `idempotent` | false | 是否去重 |
| `errorHandler` | `""` | 失败落库的 bean 名 |
Redis Stream 这边的参数(前缀 `mq.redis-stream`,都有默认值):
| 配置 | 默认 | 说明 |
|---|---|---|
| `reclaim-min-idle` | 30s | 消息投出去多久没 ack 才 reclaim 重投,要大于单条处理耗时 |
| `idempotent.processing-ttl` | 60s | 「处理中」标记存多久,建议大于处理耗时、小于 reclaim-min-idle |
| `idempotent.success-ttl` | 300s | 「已完成」标记存多久(去重窗口) |
| `idempotent.fail-ttl` | 300s | 失败标记保留多久 |
| `idempotent.key-prefix` | `mq:idem:` | 去重键前缀 |
RocketMQ 这边直接复用 rocketmq-spring 原生的 `rocketmq.*` 配置。
## 已知限制 / TODO
按优先级列,详细的在 [`docs/mq-starter-改进优化清单.md`](./docs/mq-starter-改进优化清单.md)。
P0放量到生产前想补的
- [x] 失败落库、去重、批量消费core 已做)
- [ ] 给 rocketmq driver 补上去重和 `RETRY_EXHAUSTED` 落库,跟 redis-stream 对齐
- [ ] 批量消费加 idle flush现在低流量下攒不满会卡着进程重启还会丢内存里没攒满的那批
- [ ] 顺序消息
- [ ] 事务消息
P1延迟消息、死信队列统一抽象、消息轨迹 / 指标、序列化可插拔(现在两边一个 Jackson 一个 fastjson、广播消费。
P2并发度语义统一、headers 完整透传、`mq.type` 错配时 fail-fast、监控告警。
## 构建
```bash
git clone https://github.com/EdmundNightmare/mq-starter.git
cd mq-starter
mvn clean install
```
需要 JDK 1.8+ 和 Maven 3.6+。单元测试 `mvn test` 不依赖外部中间件;带真实 Redis 的集成测试(`*IT`)在 `mvn verify` 阶段跑。
有想法欢迎提 issue 或 PR优先认领上面 P0 的几项,提交前跑一下 `mvn verify`,改动尽量带上测试。
## License
[Apache License 2.0](./LICENSE)Copyright 2026 EdmundNightmare。