代表性面试主题

后端面试:SQS FIFO 的去重窗口能保证 exactly-once 吗?

后端困难
Offer.cc 编辑团队发布 更新

题干

Amazon SQS FIFO 使用 MessageDeduplicationId 时,5 分钟去重窗口究竟保证什么?如果消费者在处理后、删除前崩溃,你会如何避免业务副作用重复?

题干与适用场景

这道题适合后端、平台和分布式系统面试。面试官要听到边界:SQS 会在去重时间窗内把相同去重 ID 视为重复消息,并持续跟踪该 ID,即使消息已被接收和删除;这不等于外部数据库、支付或邮件副作用天然 exactly-once。

面试官考察点

高质量回答会区分生产端去重、FIFO 顺序、消费者可见性超时、删除确认和业务幂等。AWS 文档把去重 ID 定义为防止重复投递的令牌;未显式提供时,启用 content-based deduplication 可用消息体哈希生成。回答还应说明窗口、失败重试和监控,而不是只说“FIFO 就不会重复”。

回答前需要澄清的问题

语义边界

先问面试官要的是队列层重复抑制、至少一次处理,还是业务结果一次生效。把“消息只出现一次”和“订单只扣款一次”分开。

去重输入

确认生产者是否显式生成稳定的 MessageDeduplicationId、是否启用基于内容的去重、消息组如何划分,以及重试间隔是否落在五分钟窗口内。

失败点

画出接收、处理、写入副作用、DeleteMessage 的时间线;确认消费者崩溃、可见性超时、网络重试和下游超时分别会发生什么。

30 秒回答框架

“FIFO 的去重 ID 在五分钟窗口内抑制相同消息再次进入队列,并保留该 ID 的追踪;它解决的是队列层的重复发送和顺序约束,不自动让外部副作用 exactly-once。消费者应以稳定业务键做幂等记录,在副作用与状态写入之间采用同一事务边界或可重试的 outbox,再在成功后删除消息。崩溃时允许重投,但重复执行只会读取已完成记录。”

分步骤深入解答

第一步:定义可验证的保证

说明同一 deduplication ID 在五分钟 deduplication interval 内会被视为重复;即使消息已接收并删除,SQS 仍继续追踪该 ID。不要把这个窗口描述成永久去重。

第二步:区分生产与消费

生产端用稳定 ID 表示同一业务命令;内容哈希只在消息体足以代表命令时适用。消费端必须按订单号、支付意图或命令 ID 建唯一约束,因为窗口过后重试仍可能是同一业务动作。

第三步:覆盖崩溃窗口

若消费者先写数据库再崩溃,消息可能在可见性超时后再次出现。让副作用写入和幂等键状态处于同一事务,或用 outbox 让后续发送可重放;只有确认处理完成后才删除消息。

第四步:处理顺序与毒消息

需要顺序时使用相同 MessageGroupId,并设置合理的可见性超时。对持续失败的消息转入 dead-letter queue,同时记录原始 ID、接收次数和失败原因,避免无限重试阻塞消息组。

第五步:验证与监控

测试生产重试、消费者在写入后崩溃、DeleteMessage 超时和窗口外重放。监控重复业务键、ApproximateReceiveCount、DLQ 深度、可见性超时和端到端延迟;告警应能区分队列重复与业务重复。

高质量示范回答

我会把 SQS 的五分钟去重当作传输层保护,不把它当作支付结果的一次性保证。生产者为每个支付意图生成稳定命令 ID,重试时复用该 ID;消费者先在数据库的 inbox 表上用命令 ID 建唯一约束,再在同一事务中写入订单状态和 outbox 事件。事务提交后删除消息。若写库后进程崩溃,消息重投只会命中已处理记录,不会再次扣款。监控会统计重复命令、接收次数和 DLQ,另外做窗口内、窗口外及 DeleteMessage 失败的故障测试。

常见错误

  • 错误表现: 说 FIFO 等于永久 exactly-once。→ 失败原因: 忽略五分钟窗口和消费端崩溃。→ 修正方法: 明确去重 ID 的时间范围,并设计业务幂等。
  • 错误表现: 每次重试都生成新的 deduplication ID。→ 失败原因: 同一业务命令会被当成不同消息。→ 修正方法: 让重试复用稳定命令 ID。
  • 错误表现: 处理完成前删除消息。→ 失败原因: 下游失败时消息丢失。→ 修正方法: 成功提交后再 DeleteMessage,并允许可见性超时重投。
  • 错误表现: 只依赖消息体哈希。→ 失败原因: 时间戳或无关字段变化会绕过去重。→ 修正方法: 对业务命令显式定义稳定 ID。

追问及应对

追问一:五分钟后收到相同命令怎么办?

用持久化的业务幂等记录判断是否已完成;队列去重只能降低短窗口重复,不能替代长期业务状态。

追问二:为什么不先删除再处理?

先删除会把下游失败变成消息丢失。除非业务允许丢弃并有其他可靠来源,否则应先完成可重试处理,再删除。

追问三:outbox 解决了什么?

它把业务状态提交和待发送事件放进同一数据库事务,消费者或发布器可安全重试;它仍要求下游按事件 ID 幂等。

追问四:如何证明没有重复扣款?

用故障注入覆盖写入后崩溃、删除超时和窗口外重放,核对数据库唯一约束、支付提供商幂等键及审计日志,而不是只看队列指标。

公开来源

同类题目