docs: 补多环境隔离跨 driver 差异与 redis-stream.* 底层配置表

- README 能力矩阵新增「多环境隔离」行:redis-stream 经底层 starter 拼 topic_env 后缀,
  rocketmq 无对应机制;切换 mq.type 须知从两条扩为三条(隔离静默消失)。
- 配置项一节把「redis-stream.* 一般不用动」一句扩成完整表格(env-isolation/env/
  read-count/block-ms/consumer-name/max-retry/trim/autoclaim),并标明 trim/autoclaim
  只认中间件自己的 handler、对 mq-starter 不生效。
- CLAUDE.md 驱动差异清单同步。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 10:24:27 +01:00
parent aa5a949cb9
commit 2b635a4cd7
2 changed files with 15 additions and 2 deletions

View File

@@ -37,7 +37,7 @@ mq-starter-rocketmq RocketMQ driver收发 + 原生重试/死信 + RET
mq-spring-boot-starter 聚合 jar业务方只引这一个恒含 idempotent-redis
```
> ⚠️ **两个 driver 仍有差异**README 有对照表去重、失败落库、tag 过滤三者两端已对齐2026-07 起 rocketmq `idempotent=true` 生效、需 Redis 可达tagrocketmq broker 原生 / redis-stream 消费端模拟,语义子集见 core 的 `MqTagMatcher`dispatch 顶部与 reclaim 耗尽分支前两处调用剩余差距是死信模型redis 只落库 / rocketmq 原生 `%DLQ%`批量**整批失败**的处理(逐条 ACK 后无法精确重投整批,故落库+吞掉);真正两端都未兑现的只有 `concurrency`。批量攒批本身(含 idle flush 超时/停机强刷2026-07 已落地)两端已对齐。改跨 driver 行为前先对照 README 能力表。
> ⚠️ **两个 driver 仍有差异**README 有对照表去重、失败落库、tag 过滤三者两端已对齐2026-07 起 rocketmq `idempotent=true` 生效、需 Redis 可达tagrocketmq broker 原生 / redis-stream 消费端模拟,语义子集见 core 的 `MqTagMatcher`dispatch 顶部与 reclaim 耗尽分支前两处调用剩余差距是死信模型redis 只落库 / rocketmq 原生 `%DLQ%`批量**整批失败**的处理(逐条 ACK 后无法精确重投整批,故落库+吞掉)与**多环境隔离**redis-stream 经底层 starter 的 `redis-stream.env-isolation`/`env` 拼 `topic_env` 后缀rocketmq 无对应机制,切换后隔离静默消失);真正两端都未兑现的只有 `concurrency`。批量攒批本身(含 idle flush 超时/停机强刷2026-07 已落地)两端已对齐。改跨 driver 行为前先对照 README 能力表。
>
> **单独只引 `mq-starter-rocketmq`(不走聚合坐标)时去重需自行加 `mq-starter-idempotent-redis` 依赖**——rocketmq 模块本身不依赖它(刻意零耦合)。

View File

@@ -179,6 +179,7 @@ public class MyErrorHandler implements MqConsumeErrorHandler {
|---|:---:|:---:|
| 收发 | ✅ | ✅ |
| tag 过滤 | ✅ 消费端模拟(不匹配 ACK 丢弃,写法子集同右) | ✅ broker 服务端原生 |
| 多环境隔离topic 拼环境后缀) | ✅ 底层 starter 能力(非 mq-starter 实现):配 `redis-stream.env-isolation=true` + `redis-stream.env=dev` 后实际 stream 名为 `topic_dev`,各环境共用一套 Redis 互不串流 | ❌ topic 原样使用,多环境共用集群需自行隔离(独立集群 / topic 命名约定) |
| 并发消费 `concurrency` | ⚠️ 单线程 poll未兑现 | ⚠️ 原生线程池,不受 concurrency 控制 |
| 去重 `idempotent`(默认开启) | ✅(需 Redis | ✅(需 Redis |
| 失败落库 `RETRY_EXHAUSTED`(需配 `errorHandler`,否则仅 ACK 丢弃) | ✅ | ✅ 落库+ACK |
@@ -193,6 +194,7 @@ public class MyErrorHandler implements MqConsumeErrorHandler {
> ⚠️ **切换 `mq.type` 业务代码零改动,但两处运行时行为会跟着翻转**,切换前按这两条过一遍消费方假设:
> 1. **并发模型**redis-stream 单线程串行、rocketmq 原生线程池并发——消费逻辑若有非线程安全假设,切到 rocketmq 可能暴露。
> 2. **顺序**两端都不保证严格有序rocketmq 用的 Concurrently 而非 Orderly
> 3. **多环境隔离**redis-stream 的环境后缀是底层 starter 拼的rocketmq 没有对应机制——切过去后隔离**静默消失**,多环境共用一套 RocketMQ 时同名 topic+group 会被当成同一消费组互相抢消息,切换前先规划好隔离方式。
去重按**消息 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、不影响启动
@@ -289,7 +291,18 @@ Redis Stream 这边的参数(前缀 `mq.redis-stream`
|---|---|---|
| `reclaim-min-idle` | 30s | 消息投出去多久没 ack reclaim 重投要大于单条处理耗时**不要调到比 `processing-ttl` 还大**需保持 `processing-ttl ≥ reclaim-min-idle`否则存活的慢消费者会被 reclaim 重投副本并行双消费也别调得太小——每次 reclaimxClaim都使 PEL delivery +1 processing 挡回的白跑同样计入需保持 `maxRetry × reclaim-min-idle > processing-ttl`否则崩溃残留会在业务没重跑的情况下烧光重试直接判耗尽 |
Redis 连接走标准 `spring.redis.*`更底层的 `redis-stream.*`readCount / blockMs / consumerName 来自内部 stream 中间件`redis-stream-springboot-starter`一般不用动
Redis 连接走标准 `spring.redis.*`另有一组更底层的参数前缀 `redis-stream`来自内部 stream 中间件 `redis-stream-springboot-starter`mq-starter driver 复用其中一部分
| 配置 | 默认 | 说明 |
|---|---|---|
| `env-isolation` | false | 多环境隔离开关开启且 `env` 非空时实际 stream = `topic_环境值`(如 `order_dev`各环境共用一套 Redis 互不串流发送订阅两侧都生效跨应用收发时两边的 `env` 配置必须一致否则流名对不上收不到 |
| `env` | | 环境标识`dev` / `test` / `prod`…),配合上面的开关用 |
| `read-count` | 10 | 单次 XREADGROUP 拉取条数大堆积追赶想快些可调大 100代价是单批占内存多批内逐条处理时间拉长 |
| `block-ms` | 2000 | 拉取无消息时的阻塞等待毫秒数到点空手返回进下一轮 |
| `consumer-name` | | 组内消费者名留空则每次启动生成随机名推荐重启遗留的 PEL reclaim 拿回写死会让多实例同名PEL 归属混在一起 |
| `max-retry` | 3 | 兜底重试次数 `@MqListener` `maxRetry` 配成 0/负数时才用到 |
| `trim.maxlen` / `trim.interval-ms` | 100000 / 60s | **对 mq-starter 消费的 stream 不生效**中间件的定时裁剪只认自己注册的 handler stream handler 时任务不启动mq-starter 不走那套 handler只经 mq-starter 收发的 topic **没有任何裁剪、无界增长**要封顶得靠直连中间件的服务碰巧订同一 topic或运维层面自行 XTRIM |
| `autoclaim.*` | | 同上对 mq-starter 不生效也是按 handler stream)。mq-starter 有自己的 reclaim配上表的 `mq.redis-stream.reclaim-min-idle` |
RocketMQ 这边直接复用 rocketmq-spring 原生的 `rocketmq.*` 配置`name-server``producer.group` )。