Commit Graph

19 Commits

Author SHA1 Message Date
02469dce11 fix(mq-starter-core): stop() 等待在途定时刷批,堵住停机静默丢批缝隙
review 发现 #1:定时线程刚做完超时快照(buffer 已清空)、正在 doFlush 跑业务
方法时优雅停机——shutdown() 不等它,forceFlush() 见空 buffer 直接返回(连
flushLock 都不进),stop 走完后 JVM 退出杀掉 daemon 线程,该批消息早已逐条
ACK,静默丢失。这是终审 shutdownNow→shutdown 的对偶缝隙:温和关闭解决了
"不打断在刷批",但没人等它刷完。

修复:shutdown() 后补 awaitTermination(30s 上界,超时/中断打 WARN 继续停机,
不让挂死的业务方法永久卡住 Spring 关闭)。

测试:往真定时线程塞在途任务模拟"stop 瞬间定时线程正在刷批",断言 stop
返回前任务已完成(修复前该测试失败:stop 立即返回)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 02:52:43 +01:00
d5161fa650 fix(mq-starter-core): 终审两处停机缝隙——dispatcher 打烊拒收 + 定时器温和关闭
1) forceFlush 置 closed:driver.stop() 异步返回后残余线程再投递的消息一律
   拒收抛 InProgress(不 ACK、下次启动重投),堵住正常停机丢最后一轮消息的缝隙;
   检查先于去重,不留 processing 标记。
2) 定时器 shutdownNow→shutdown:不再打断正在执行的刷出,避免整批被误判失败;
   后续执行照常取消,残留批由 forceFlush 接住(flushLock 串行兜底)。

终审 Important #1/#2,处置方案经用户确认。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:27:10 +08:00
244d4d3854 feat(mq-starter-core): MqListenerRegistry 接线批量超时刷出定时器 + 停机强刷残留批
有"批量且 batchTimeoutMs>0"监听器才建全局单线程 daemon(mq-batch-flush)
每 1s 扫描 flushIfTimeout,任务体逐个 catch-all 防调度线程死亡;stop 顺序
改为 driver 停→定时器停→forceFlush 残留→running=false,正常停机不丢未满批。
两 driver 零改动,redis-stream/rocketmq 同时受益。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 21:55:39 +08:00
20f06613e8 feat(mq-starter-core): MqConsumeDispatcher 批量超时/停机强制刷出(idle flush)
从本批首条进入缓冲起算(linger 语义),flushIfTimeout 到点刷未满批、
forceFlush 停机非空即刷,均汇入 doFlush;时钟经 Builder 注入,单测用
假时钟全覆盖(不真等待)。Builder 缺省 batchTimeoutMs=0 保持旧行为。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 14:47:26 +01:00
98731c3591 refactor(mq-starter-core): 抽取 doFlush 统一批量刷出路径 + flushLock 串行化业务调用
为 idle flush 铺路:满批/超时/停机三路径将共用同一刷出段,失败语义只写一份。
flushLock 使业务批量方法任一时刻至多一个调用(rocketmq 端原有多满批并发刷
顺带收紧为串行,设计已确认)。行为对既有测试不变。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 20:56:08 +08:00
ad864e5b93 feat(mq-starter-core): @MqListener 新增 batchTimeoutMs(批量超时刷出,缺省 60s)+ 启动软校验
字段仅批量监听器生效;batchSize<=1 且显式配非缺省值时打 WARN 不阻断
(延续 batchSize 软校验风格;恰好显式写 60000 检测不出,已知可接受)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 20:49:48 +08:00
94d5de5182 feat(mq-starter-core): @MqListener idempotent 默认值翻转为 true——去重默认开启
不需要去重的监听器显式 idempotent=false,或 mq.idempotent.enabled=false 全局关;
Registry 无 store 时的 WARN 文案同步改为默认开启口径。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:02:44 +01:00
8c39399d09 feat(mq-starter-core): @MqListener batchSize 与首参 List 错配时打 WARN 软校验
走不走批量始终只由首参是否 List 决定,错配不阻断启动:
- batchSize>1 但首参非 List → WARN 提示 batchSize 不生效、按单条逐一回调
- 首参 List 但 batchSize<=1 → WARN 提示将按 1 条一批回调

测试:ListAppender 捕获日志,覆盖两个错配方向 + 匹配时无 WARN。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 04:19:17 +01:00
e660afe2a2 fix(mq-starter): 去重跳过区分 processing/success,processing 残留不 ACK 避免静默丢消息
tryAcquire 返回 false 的两类跳过此前一律静默 return,导致 driver 无差别 ACK:
消费者在 markSuccess/markFail 前崩溃残留的 processing 标记,会被当成"已处理"
而 ACK,该消息从未成功消费却被丢弃(Critical 静默丢消息)。

- MqIdempotentStore 新增 isCompleted(key),默认 true 保持旧行为,状态机实现须覆写
- MqConsumeDispatcher:跳过时按 isCompleted 分流——success 正常 return 让 driver ACK;
  仍 processing 则抛 MqMessageInProgressException(新,纯控制流信号、不带栈)让 driver 不 ACK
- RedisMqIdempotentStore.isCompleted 仅 success 视为完成;RedisStreamMqDriver 捕获信号异常时留 PEL 待重投
- 补 dispatcher 两类跳过 + store isCompleted 语义单测

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 20:41:02 +08:00
886c02ff96 fix(mq-starter): 修复启动 fail-fast 校验 review 发现的缺陷(relaxed binding / 逃生开关 / redis 假地址)
workflow 多智能体 review 后修复:

- rocketmq 探测改用 Binder 读 rocketmq.name-server,与 driver(RocketMQProperties)同源、
  享受 relaxed binding,修复 camelCase(rocketmq.nameServer) 被误判为「未配置」而错误 fail-fast
- MqStartupChecks 逃生开关只认严格 false(忽略大小写/空白),缺省或非法值一律继续校验,
  不再因非法布尔值(如 enabled=disabled)抛类型转换异常炸掉启动
- redis 探测 target() 对 cluster/sentinel 回退到指向真实配置项的文案,
  不再假报使用者从未配过的 localhost:6379
- 补齐回归测试:camelCase 键 / 非法开关值继续校验 / host:port 提示分支 / cluster 回退
  (core 5、redis-stream 5、rocketmq 5,全绿)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 14:06:05 +08:00
5c4a29e1e1 feat(mq-starter-core): 启动期校验 mq.type 有对应 MqDriver(③ fail-fast)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 10:34:50 +08:00
f0dc785998 fix(mq-starter): 落实 opus review 反馈(含 1 个 Critical 静默丢消息)
- [Critical] dispatcher errorHandler 抛异常时仍保证 markFail + 抛原始异常(safeOnError 隔离):
  否则去重标记停在 processing,reclaim 重投被去重挡掉并 ACK → 静默丢消息
- [Important] 重试耗尽回调携带完整 payload(reclaim 解码 body,不再传 null)
- [Important] reclaim 默认 idle 5s→30s,避免多实例误 reclaim in-flight 消息;注释 at-least-once 语义
- [Important] idempotent=true 但无 MqIdempotentStore bean 时 start() 告警
- core 引入 slf4j-api(provided);批量失败 / errorHandler 失败均记日志
- errorHandler bean 未找到时抛带 topic/group 的清晰错误
- 文档化已知限制(批量无 idle flush、去重 fail→reopen 非原子)
- 全仓 29 测试绿(含连真实 redis 的 IT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 20:48:53 +08:00
dd2b933c08 feat(mq-starter): redis-stream 重试可放量(PEL reclaim + 超限落库)
- MqSubscription 增加 retryExhaustedHandler;registry 从 errorHandler 接线 RETRY_EXHAUSTED 回调
- redis driver 每轮 reclaim PEL:connection 层 xClaim 认领超 idle 消息并使 delivery+1
  - delivery <= maxRetry:重投消费,成功 ACK / 失败留 PEL
  - delivery > maxRetry:回调 retryExhaustedHandler 落库 + ACK 终止,不再卡队列
- 可配 reclaimMinIdleMs(默认 5000ms,对齐 legacy 5s 延迟);4 参构造 + 3 参重载
- 集成测试连真实 redis 验证 重试耗尽→落库→ACK;全仓 28 测试绿

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 20:17:05 +08:00
4ee6685cc5 feat(mq-starter-core): 消费侧通用化 去重/批量/异常落库(注解+SPI 织入)
- 新增 MqConsumeDispatcher 消费处理链:去重→(攒批)→业务→成功标记/异常落库
- 新增 SPI:MqIdempotentStore(去重,修正 legacy 读写 key 不一致缺陷)、
  MqConsumeErrorHandler + MqErrorIdentity(异常落库 SINGLE/RETRY_EXHAUSTED)
- @MqListener 扩展 idempotent/errorHandler;BPP 识别 List<T> 批量签名并解析元素类型
- MqListenerRegistry 织入 dispatcher,按注解装配 store/errorHandler;
  autoconfig 经 ObjectProvider/ApplicationContext 注入(4 参构造,保留 2 参重载)
- 重试统一交 driver MQ 原生重投(单条异常 rethrow);批量入库失败落库后吞掉不卡队列
- 19 个单测全绿(-DforkCount=0)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 19:49:22 +08:00
795d4f2629 feat(mq-starter-core): 自动装配 + spring.factories
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 05:06:36 +08:00
016590099a feat(mq-starter-core): MqListenerRegistry 端点→driver.subscribe + payload/MqContext 调用
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 04:58:59 +08:00
744115ca97 feat(mq-starter-core): @MqListener 扫描 → MqListenerEndpoint
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 04:53:01 +08:00
871aef64f8 feat(mq-starter-core): 发送门面 MqTemplate + DefaultMqTemplate
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 04:48:19 +08:00
491b83de94 feat(mq-starter): 多模块脚手架 + core 信封/SPI/注解
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 04:41:54 +08:00