docs: redis-stream tag 消费端过滤落地,README/CLAUDE.md 能力表述同步

翻转点清单移除 tag 一条;能力表新增 tag 行两端标齐;
信封表/属性表/FAQ 同步为"消费端模拟过滤"语义。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 13:46:10 +08:00
parent 69a84f2912
commit c6e2db1275
2 changed files with 11 additions and 11 deletions

View File

@@ -128,7 +128,7 @@ public void onFile(FileData msg) { } // 只收 tag=node-01 的消息,
- 消费端不写 `tag` 默认 `*`,整个 topic 全收;发送端用两参 `send` 则消息不带 tag——**对端若按 tag 过滤,不带标签的消息它收不到**,跨系统对接时这是个经典坑。
- rocketmq 模式:过滤发生在 **broker 服务端**不匹配的消息根本不会投到消费者tag 表达式直接透传 RocketMQ 原生语法,`"A || B"` 多选一也支持。
- redis-stream 模式:**当前不过滤、全收**见能力表tag 只是随消息带到消费端,可从 `MqContext.getTag()` 读到,需要分流的话业务自己判断——这也是切换 `mq.type` 时的行为翻转点之一
- redis-stream 模式:**消费端模拟过滤**——driver 收到消息后按订阅 tag 比对,不匹配的直接 ACK 丢弃、不进消费链(不碰去重、不算重试、不落库),支持与 rocketmq 相同的写法子集(`*` 全收、单 tag 精确、`A || B` 多选一)。与 rocketmq 的差别只剩过滤位置与成本:消息会先走一趟网络到消费端才被丢弃
## 进阶用法
@@ -172,11 +172,12 @@ public class MyErrorHandler implements MqConsumeErrorHandler {
## 两个 driver 不太一样
两套实现的能力大头已经对齐:去重、失败落库、批量攒批(含 idle flush两端都接上了。剩下的差异主要在死信模型以及批量的**整批失败处理**(攒批本身两端已对齐,糙点在整批失败被吞、无法精确重投):
两套实现的能力大头已经对齐:去重、失败落库、批量攒批(含 idle flush、tag 过滤两端都接上了。剩下的差异主要在死信模型,以及批量的**整批失败处理**(攒批本身两端已对齐,糙点在整批失败被吞、无法精确重投):
| 能力 | Redis Stream | RocketMQ |
|---|:---:|:---:|
| 收发 | ✅ | ✅ |
| tag 过滤 | ✅ 消费端模拟(不匹配 ACK 丢弃,写法子集同右) | ✅ broker 服务端原生 |
| 并发消费 `concurrency` | ⚠️ 单线程 poll未兑现 | ⚠️ 原生线程池,不受 concurrency 控制 |
| 去重 `idempotent`(默认开启) | ✅(需 Redis | ✅(需 Redis |
| 失败落库 `RETRY_EXHAUSTED`(需配 `errorHandler`,否则仅 ACK 丢弃) | ✅ | ✅ 落库+ACK |
@@ -188,10 +189,9 @@ public class MyErrorHandler implements MqConsumeErrorHandler {
| 事务 / 延迟消息 | ❌ | ❌ 没暴露 |
| 序列化 | fastjson | fastjson |
> ⚠️ **切换 `mq.type` 业务代码零改动,但处运行时行为会跟着翻转**,切换前按这条过一遍消费方假设:
> 1. **tag 过滤**redis-stream 消费端不过滤全收、rocketmq 走 broker 服务端按 tag 过滤——依赖 tag 分流的业务切 rocketmq 会"只收匹配的"、切回 redis 会"收到全部"
> 2. **并发模型**redis-stream 单线程串行、rocketmq 原生线程池并发——消费逻辑若有非线程安全假设,切到 rocketmq 可能暴露
> 3. **顺序**两端都不保证严格有序rocketmq 用的 Concurrently 而非 Orderly
> ⚠️ **切换 `mq.type` 业务代码零改动,但处运行时行为会跟着翻转**,切换前按这条过一遍消费方假设:
> 1. **并发模型**redis-stream 单线程串行、rocketmq 原生线程池并发——消费逻辑若有非线程安全假设,切到 rocketmq 可能暴露
> 2. **顺序**两端都不保证严格有序rocketmq 用的 Concurrently 而非 Orderly
去重按**消息 key** 判重:经 `MqTemplate` 发送时自动生成雪花 ID、天然唯一业务无感**不经 `MqTemplate` 的异构发送方必须自己保证 key 逐条唯一**redis-stream = 信封 `key` fieldrocketmq = 报文头 KEYS 属性为空或固定值会把不同消息误判成重复而静默丢弃详见「Redis Stream 消息结构」一节及其末尾的 RocketMQ 补注)。去重的状态存在 Redis`processing / success / fail` 三态 + TTL由独立模块 `mq-starter-idempotent-redis` 提供,**不看 `mq.type`**——所以 RocketMQ 部署只要能连上 Redis标准 `spring.redis.*` 配置),去重一样生效。引聚合坐标的话这个模块已经打在里面;单独只引 `mq-starter-rocketmq` 的话需要自己再加一个 `mq-starter-idempotent-redis` 依赖。**去重默认开启**,消费链路会读写 Redis务必保证 Redis 可达——连不上时消费抛连接异常走重投(日志可见、不是静默丢,但业务会一直消费不进去)。确实不需要去重的监听器显式写 `idempotent = false`;或配 `mq.idempotent.enabled=false` 全局关掉 store关掉后去重不生效、启动打 WARN 提示,完全不碰 Redis、不影响启动
@@ -202,7 +202,7 @@ redis-stream 模式下,消息在 Redis 里**不是裸 JSON**,而是固定五
| field | 内容 |
|---|---|
| `key` | 消息业务 key**每条必须唯一**——消费端去重按它判重(`idempotent` 默认开启key 重复或恒定会把不同消息误判成重复而丢弃。经 `MqTemplate` 发送时自动生成雪花 ID无需关心 |
| `tag` | 路由 / 过滤标记,可为空串(注意 redis-stream 消费端当前不按 tag 过滤,全收) |
| `tag` | 路由 / 过滤标记,可为空串——消费端按订阅 tag 过滤,不带 tag 的消息只有订 `*`(缺省)的监听器收得到 |
| `enc` | **body 的编码方式,压缩标识就放这里**`plain`(明文 JSON`gzip`(压缩,见下) |
| `body` | 消息体:`enc=plain` 时为 JSON 明文;`enc=gzip` 时为 `base64(gzip(json))`——先标准 gzipRFC1952带 gzip 头),再 base64基础编码、不带换行 |
| `ts` | 发送时间戳(毫秒) |
@@ -266,7 +266,7 @@ core 是中立的,编排逻辑不依赖 Spring 和 Redis可以直接单测
| `batchSize` | 1 | 攒批大小,大于 1 要用 `List` 参数;与首参是否 `List` 错配时启动打 WARN、仍按首参跑 |
| `batchTimeoutMs` | 60000 | 批量攒批最长等待毫秒数从本批第一条算起到点未满也强制刷出0/负数关闭;仅批量监听器生效 |
| `maxRetry` | 3 | 最大重试次数redis-stream 按 PEL delivery 计数判耗尽 / rocketmq 写入 broker `maxReconsumeTimes`;被去重标记挡回的 InProgress 白跑**两端都计入** |
| `tag` | `*` | 只收打了指定 tag 的消息(发送端三参 `send` 的第二参打 tag。rocketmq 为 broker 服务端过滤、支持原生表达式(如 `A \|\| B`redis-stream 当前不过滤、全收。详见「消费」一节的 tag 小节 |
| `tag` | `*` | 只收打了指定 tag 的消息(发送端三参 `send` 的第二参打 tag两端都生效:rocketmq 为 broker 服务端过滤原生表达式透传redis-stream 为消费端模拟过滤,支持相同写法子集(`*`、单 tag、`A \|\| B`。详见「消费」一节的 tag 小节 |
| `idempotent` | true | 是否去重(默认开启,需 Redis 可达;单个监听器关闭写 `idempotent = false`,全局关闭配 `mq.idempotent.enabled=false` |
| `errorHandler` | `""` | 失败落库的 bean 名 |
@@ -319,7 +319,7 @@ RocketMQ 这边直接复用 rocketmq-spring 原生的 `rocketmq.*` 配置(`nam
多半是处理中标记没过期挡住了重投消费者崩溃残留标记或单条处理耗时超过 `mq.idempotent.processing-ttl` TTL 过期会自动恢复 `processing-ttl` 时注意配置表里的上下界约束rocketmq 下持续重投烧光 `maxReconsumeTimes` 会进原生 `%DLQ%`进死信不是丢消息)。
**从 redis-stream 切到 rocketmq 后行为变了(多收 / 漏收、并发不一样)?**
正常两个 driver 不太一样里的条运行时翻转tag 过滤并发模型顺序保证
正常两个 driver 不太一样里的条运行时翻转并发模型顺序保证
## 已知限制 / TODO