← 返回题目列表

消息投递超时应该怎么处理?

困难 第 26 / 26 题 更新于 2026/10/01
消息队列投递超时重试幂等
学习 AI 实战项目

简化版

投递超时表示结果未知,不能直接判定失败:Broker 可能已持久化,只是 ACK 丢失。生产者应使用稳定 eventId 做有界重试并查询确认,Broker 去重或消费者幂等兜底;同时区分连接、发送、确认和业务处理超时。

详细版

发送前先生成 eventId 并持久化待发送状态,只有收到满足 durability 要求的 ACK 才标记成功。超时后沿同一 eventId 重试,采用退避、总 deadline 和重试预算;不能每次创建新 ID,否则下游无法识别重复。

若同步发送长期超时,应转 Outbox 异步补发并熔断 Broker,避免占满业务线程。监控 ACK P99、超时率、未知状态量、重试放大、重复命中和最终对账差异,明确 ACK 仅代表 Broker 接收,不代表消费者业务完成。

完整版教学

一、超时为什么是未知状态

请求可能未到 Broker、Broker 可能已写盘但 ACK 丢失,也可能 ACK 正在网络中。调用方只知道在截止时间内没收到结果,无法从超时本身推断消息不存在。

易错点:超时不是失败码;对未知状态盲目换 ID 重发,最容易制造业务重复。

二、拆开不同超时

超时发生阶段处理重点
连接超时建连节点路由/熔断
请求超时发送等待结果未知
Broker ACK 超时复制确认durability
消费超时业务处理重投与幂等

把它们都叫 delivery timeout 会导致错误重试位置和错误告警。

三、稳定 eventId 与重试

第一次发送前生成 eventId,所有重试复用它。Broker 支持幂等生产者时还要维护 producer epoch/sequence;不支持时由消费者数据库以 eventId 唯一约束去重。

eventId=E7 send -> broker persisted -> ACK lost
eventId=E7 retry -> duplicate possible -> consumer deduplicates

重试只改变 attempt,不改变业务身份。

四、ACK 到底证明什么

单副本内存 ACK、Leader 落盘 ACK、多副本确认的耐久性不同。收到 ACK 通常只证明消息进入 Broker 的指定确认边界,不证明消费者已处理,更不证明业务数据库已提交。需要业务完成通知时,应另建回执事件或查询状态。

例如 3 副本要求多数确认,能够承受一个副本故障,但若 Topic 配置退化到单副本,应用层超时参数无法补回耐久性。

五、Outbox 如何收敛未知

业务数据与待发送事件同一本地事务写入 Outbox,后台按 eventId 重试发送。收到 ACK 后标记 SENT;状态更新失败会再次发送,但幂等链路能承受。这样 HTTP 请求超时不会丢掉后续补发事实。

Outbox 扫描要有 claim 租约、退避和死信,不能多个实例无限重复发送同一热记录。

六、重试与熔断边界

采用 100ms、200ms、400ms 等带抖动退避,并受总 deadline 限制。Broker 故障时每个业务请求重试三次会放大流量,应设共享重试预算,持续异常转异步并熔断。

监控 timeout by stage、unknown pending、retry/original ratio、ACK latency、Outbox oldest age 和消费者重复命中;最终用业务对账确认无丢失。

若 Broker 不提供按 eventId 查询,不能把“查不到接口”误当成发送失败;应由 Outbox 状态、消费回执和周期对账共同收敛未知结果。

七、常见误区与追问

  • 误区:发送超时说明 Broker 没收到。 ACK 可能丢失,消息已经持久化。
  • 误区:重试时生成新消息 ID 更安全。 会破坏端到端去重。
  • 误区:收到 ACK 代表业务消费成功。 ACK 只覆盖 Broker 确认边界。
  • 误区:无限重试能避免丢消息。 会造成风暴并耗尽线程。
  • 追问:如何确认未知结果? 查询 Broker 能力、Outbox 状态或等待幂等回执。
  • 追问:消费者为什么仍要幂等? 生产重试与消费重投都可能产生重复。
  • 追问:何时转异步? 同步预算耗尽且业务允许稍后完成时。

八、加强记忆

投递超时按“结果未知”处理:稳定 eventId、明确 ACK 边界、有界重试、Outbox 留事实、消费者幂等。连接、Broker 确认与业务处理是不同阶段;只有把阶段分清并以对账收尾,才能同时避免丢失和重复副作用。