SQS 的消息真的能「至少投递一次」吗 - At-Least-Once 投递的机制与陷阱

讲解 SQS Standard 队列的 At-Least-Once 投递为何会产生重复、可见性超时的内部机制、与 FIFO 队列 Exactly-Once 的区别以及幂等性设计的必要性。

什么是 At-Least-Once 投递

SQS Standard 队列保证「At-Least-Once Delivery」(至少一次投递)。这意味着发送的消息至少会被投递给消费者一次,但同一条消息也可能被投递两次或更多次。为什么会产生重复?SQS 将消息冗余存储在多台服务器上。在消费者接收消息、完成处理并发送删除请求之前,另一台服务器可能会将同一条消息投递给另一个消费者。在分布式系统中,由于网络延迟和服务器间同步的时间差,这种重复在原理上无法避免。重复投递的频率未公开,但 AWS 文档描述为「偶尔」发生。实际运营中,在每秒处理数千条消息的环境下,有报告称每天观察到数条至数十条重复。

可见性超时 - 消息「不可见」的机制

SQS 的可见性超时 (Visibility Timeout) 是防止消息重复处理的机制。消费者接收消息后,该消息在一定时间内对其他消费者「不可见」。这个时间就是可见性超时,默认为 30 秒。如果消费者在可见性超时内完成处理并通过 DeleteMessage API 删除消息,则消息被正常处理。如果在可见性超时内未被删除,消息会重新回到队列,被投递给其他消费者。这里存在陷阱:如果处理时间超过 30 秒,可见性超时到期后消息回到队列,另一个消费者接收同一条消息导致重复处理。对策有两个:第一,将可见性超时设置为远大于处理时间;第二,在处理过程中使用 ChangeMessageVisibility API 延长可见性超时(心跳模式)。当 Lambda 由 SQS 触发时,Lambda 服务会自动将可见性超时设置为函数超时值的 6 倍。

FIFO 队列的 Exactly-Once 处理

2016 年推出的 FIFO (First-In-First-Out) 队列提供消息顺序保证和去重功能。在 FIFO 队列中,发送消息时指定 MessageDeduplicationId,在 5 分钟的去重窗口内,相同 ID 的消息只会被投递一次。这消除了生产者端的重复发送(如网络超时导致的重试)。消费者端的重复处理也通过 MessageGroupId 的顺序保证得到缓解。同一 MessageGroupId 的消息在前一条消息被删除之前不会投递下一条,因此同一组内不会因并行处理产生重复。但 FIFO 队列有吞吐量限制:无批处理时每秒 300 条消息,有批处理时每秒 3,000 条。启用高吞吐量模式可扩展到每秒 30,000 条,但与 Standard 队列事实上无限的吞吐量相比仍有很大限制。对于不需要顺序保证和去重的工作负载,Standard 队列在吞吐量和成本方面更有优势。

幂等性设计 - 以重复为前提的架构

使用 SQS Standard 队列时,在消费者端确保幂等性 (Idempotency) 的设计不可或缺。幂等性是指同一操作执行多次结果不变的性质。例如,「将用户 A 的余额设为 1,000 元」是幂等的,但「给用户 A 的余额加 1,000 元」不是幂等的。后者重复执行会导致余额变为 2,000 元。实现幂等性最常见的模式是将已处理消息的 ID 记录到 DynamoDB。接收消息后,先检查 DynamoDB 中是否存在该 MessageId,存在则跳过处理,不存在则执行处理并记录 MessageId。使用 DynamoDB 的条件写入 (ConditionExpression) 可原子性地执行检查和记录。Lambda 的 Powertools 库提供了简便实现此幂等性模式的装饰器。只需为函数添加 @idempotent 装饰器,即可自动集成基于 DynamoDB 的幂等性检查。

死信队列 - 无法处理的消息的去向

当消息处理反复失败时,该消息会永远留在队列中,浪费消费者资源。死信队列 (DLQ) 是将处理失败指定次数的消息移动到另一个队列的机制。将 maxReceiveCount 设为 3,则被接收 3 次仍未被删除的消息会移动到 DLQ。移动到 DLQ 的消息需要手动确认、调查原因,然后决定重新发送到原队列还是丢弃。DLQ 设计中容易忽略的是 DLQ 本身的消息保留期。SQS 的消息保留期默认 4 天,最长 14 天。DLQ 的消息也适用相同的保留期,因此如果 14 天内未处理,消息会被自动删除。处理重要消息时,请为 DLQ 设置 CloudWatch 告警,在消息到达时立即收到通知。2021 年新增的 DLQ 重新驱动功能可从控制台一键将 DLQ 的消息重新发送到原队列。如需系统学习消息队列的设计模式,专业书籍 (Amazon)可作为参考。