消息投递语义(message delivery semantics)描述的是:消息从发送方到接收方,系统对“会不会丢、会不会重复”给出的承诺。常见三种:至多一次(At-most-once)、至少一次(At-least-once)、恰好一次(Exactly-once)。
打个生活化的比方。你给朋友转账 100 元,点完“确认”后网络断了。你不知道钱到没到。重试,可能转两次;不重试,可能一次没转。发送方拿不到可靠回执时,只能在“可能丢”和“可能重”之间选。这就是投递语义的核心困境。
至多一次:发出去就不管,不重试。丢了就丢了。适合日志、监控指标这类少一条也能忍的数据。
至少一次:发送方没收到确认(ack)就重试,直到收到。代价是接收方可能收到重复消息。所以消费端必须能处理重复:用唯一订单号、幂等键(idempotency key)、去重表或数据库唯一约束。
恰好一次:听起来是“不丢也不重”。单机单事务内能做到;跨网络、跨服务、跨存储时,由于确认可能丢、节点可能崩,严格端到端恰好一次通常要分布式事务或两阶段提交(2PC),代价是延迟、吞吐和复杂度。很多系统宣传的“Exactly-once”其实是“系统内部状态恰好一次”,或者“至少一次 + 幂等去重 = 效果上的恰好一次(effectively-once)”。这不是文字游戏,而是要问清边界:谁到谁?跨不跨系统?故障恢复算不算?
| 语义 | 会丢吗 | 会重吗 | 主要代价 | 常见场景 |
|---|---|---|---|---|
| 至多一次 | 可能 | 不会 | 简单快,但可能丢 | 日志、指标采集 |
| 至少一次 | 不会 | 可能 | 重试、消费端去重 | 消息队列、支付通知 |
| 恰好一次 | 端到端难保证 | 靠去重实现“不重” | 事务、协调、吞吐下降 | 流处理内部、账务配合幂等 |
和相邻概念的区别:投递语义不等于顺序保证,也不等于一致性级别。消息可以不丢不重,但顺序仍可能乱;顺序一致也不代表不重复。另一个关键概念是幂等性(idempotency):同一操作执行多次,效果和执行一次相同。有幂等,至少一次就能变成事实上的恰好一次。
对从业者:设计数据管道、特征回填、向量库写入、推理请求重试时,默认按“至少一次”假设,给每条消息带唯一 ID,消费端做幂等;跨系统写入用事务或补偿。对普通人:看到“绝不丢、绝不重”的宣传,先问范围。转账、扣库存、发券这类场景,重复一次就是事故,所以系统通常用幂等键兜底。具体产品支持到什么程度,以官方页面为准。
