Exactly-once vs At-least-once
1)为什么要讨论语义
交付语义定义了接收者在失败和后退时看到消息的频率:- 最多-没有重播,但是可能会丢失(很少被接受)。
- One-least-once-我们不会丢失,但我们可以重复丢失(大多数经纪人/队列的默认值)。
- Exactly-once-根据观察到的效果,对每个消息进行一次精确处理。
关键真理:在没有全局交易和同步一致性的分布式世界中,无法实现"纯"端到端的异常一事。我们有效地建立了exactly-once:允许在运输中重复使用,但是我们使处理相等,以便观察到的效果"好像曾经"。
2)故障模型和重复出现的位置
重播是由于以下原因而出现的:- 损失ack/commit(制作人/经纪人/consumer"没有听到"确认)。
- 领导者连任/复制,网络中断后的恢复。
- 任何地点的Taymautov/Retraev(kliyent→broker→konsyumer→sink)。
结果:不能依靠运输的"交付独特性"。管理效果:写入DB,注销,发送信件等。
3) Exactly-once在供应商和它实际上是什么
3.1 Kafka
给出砖块:- Idempotent Producer (`enable.idempotence=true')-在撤退时防止生产者侧面的拍摄。
- 交易-以原子方式将消息发布到多个批次,并推销消费期权(读取过程写作模式没有"跳过")。
- Compaction-通过键存储最后一个值。
但是"链条末端"(合成:DB/付款/邮件)仍然需要幂等。否则,处理器双击将产生双击效果。
3.2 NATS / Rabbit / SQS
默认情况下为带有ack/redelivery的at-least-once。Exactly-once是在应用程序级别实现的:按键,滞后源,upsert。
结论:单向运输≠单向效应。后者是在处理程序中完成的。
4)如何在once-least-once之上有效地构建一个有效的exactly-once
4.1 Idempotency key (idempotency key)
每个命令/事件都带有自然键:"payment_id","order_id#step","saga_id#n"。处理程序:- 检查"已经见过吗?"是具有TTL/还原功能的滞后源(Redis/DB)。
- 如果看到-重复先前计算的结果或进行无操作。
lua
-- SET key if not exists; expires in 24h local ok = redis.call("SET", KEYS[1], ARGV[1], "NX", "EX", 86400)
if ok then return "PROCESS" else return "SKIP" end
4.2个底座中的Upsert(等效合成器)
记录是通过UPSERT/ON CONFLICT进行的,并带有版本/金额验证。
PostgreSQL:
sql
INSERT INTO payments(id, status, amount, updated_at)
VALUES ($1, $2, $3, now())
ON CONFLICT (id) DO UPDATE
SET status = EXCLUDED.status,
updated_at = now()
WHERE payments.status <> EXCLUDED.status;
4.3事务性Outbox/Inbox
Outbox:业务事务和记录"发布事件"发生在单个DB事务中。背景发布者读取outbox并发送给经纪人→状态与事件之间没有差异。
收件箱:对于传入的命令,我们在运行前保留"message_id"和结果;重复处理可以看到记录,并且不会重复副作用。
4.4串联链处理(read→process→write)
Kafka:交易"阅读了正版,→在一个原子单元中记录了→ commit"的结果。
无交易:"首先记录结果/收件箱,然后记录结果";在裂缝时,副本将看到Inbox并终止。
4.5 SAGA/赔偿
当无法实现等效性(外部提供商注销了资金)时,使用补偿性操作(refund/void)和等效外部API(使用相同的"Idempotency-Key"的重复"POST"给出了相同的总和)。
5)什么时候足够了
使用按键压缩更新缓存/实例化视图。
可接受重复增量的计数/度量(或将增量存储为版本)。
二次写作不重要的注释(最好还是放下钥匙)。
规则:如果双打没有改变业务含义,或者很容易检测到→ at-least-once+部分保护。
6)生产力和成本
Exactly-once(甚至"有效")成本更高:多余的记录(Inbox/Outbox),密钥存储,交易,更难诊断。
At-least-once便宜/更简单,在throughput/p99上更好。
估价:双打价格×双打赔率与防守成本。
7)配置和代码示例
7.1 Kafka制作人(等效性+交易)
properties enable.idempotence=true acks=all retries=INT_MAX max.in.flight.requests.per.connection=5 transactional.id=orders-writer-1
java producer.initTransactions();
producer.beginTransaction();
producer.send(recordA);
producer.send(recordB);
// также можно atomically commit consumer offsets producer.commitTransaction();
7.2 Concumer with Inbox(伪代码)
pseudo if (inbox.exists(msg.id)) return inbox.result(msg.id)
begin tx if!inbox.insert(msg.id) then return inbox.result(msg.id)
result = handle(msg)
sink.upsert(result) # идемпотентный синк inbox.set_result(msg.id, result)
commit ack(msg)
7.3 HTTP Idempotency-Key(外部API)
POST /payments
Idempotency-Key: 7f1c-42-...
Body: { "payment_id": "p-123", "amount": 10.00 }
使用相同密钥的重复开机自检→相同的结果/状态。
8)可观察性和指标
"duplicate_attempts_total"-捕获了多少次(根据Inbox/Redis)。
'idempotency_hit_rate'是通过幂等性"保存"的重复的比例。
'txn_abort_rate'(Kafka/DB)是回滚的比例。
'outbox_backlog'-发布滞后。
'exactly_once_path_latency {p95, p99} 'vs'at_least_once_path_latency'-开销。
Logs审核:"message_id"、"idempotency_key"、"saga_id"、"attempt"捆绑。
9)花花公子测试(游戏日)
发货重播:制片人在人工定时下转播。
"Sink和ack"之间的裂痕:确保Inbox/Upsert防止双打。
Pere-Deliver:增加经纪人的再分配;检查你的祖父。
外部API的相等性:具有相同键的重复开机自检是相同的响应。
领导者变更/网络中断:检查Kafka 交易/consumer行为。
10)反模式
依靠运输:"我们有一个exactly-once的Kafka,这意味着你可以没有钥匙"-不。
录音前没有行动:无所事事,但无所事事→损失。
缺少DLQ/静音转播:无休止的重播和风暴。
随机UUID代替自然键:没有什么可重复的。
将Inbox/Outbox与无索引的Prod Table溷合:热锁和p99尾巴。
在外部提供商中无需等效的API的业务运营。
11)选择支票清单
1.双重价格(金钱/法律/UX)与保护价格(潜在性/复杂性/价值)。
2.是否有自然事件/操作密钥?如果没有-想出一个稳定的。
3.Sink是否支持Upsert/转化?否则-Inbox+补偿。
4.需要全局交易吗?如果没有-分段到SAGA。
5.需要接力/长接力?Kafka + Outbox.需要快速RPC/低延迟?NATS + Idempotency-Key.
6.多重性与配额:键/空间隔离。
7.可观察性:包括可见度和backlog度量。
12) FAQ
Q: 能否实现"数学"exactly-once终结?
答:仅在狭窄的场景中,单一一致性存储和事务始终如一。通常,没有;通过相容性有效地使用。
Q: 什么更快?
A: At-least-once.Exactly-once添加了超过p99的交易/密钥存储和成本→。
Q: 在哪里存储等容密钥?
A:使用TTL或Inbox表(PK=message_id)的快速堆栈(Redis)。对于付款-更长(天/周)。
Q: 如何选择TTL dedup keys?
A:最低=最长重新交付时间+操作库存(通常为24-72小时)。对于金融-更多。
Q: 如果我在Kafka有按键匹配,需要钥匙吗?
A:是的。Compaction将减少存储,但不会让你的sync保持静止。
13)结果
At-least-once是基本的可靠传输语义。
Exactly once作为一种业务效果是在处理器级别实现的:Idempotency-Key,Inbox/Outbox,Upsert/版本,SAGA/补偿。
选择是一种权衡成本↔双重风险↔易操作性。设计自然键,使胶片保持静止,增加可观察性,并定期进行比赛日-然后即使在暴风雨和故障的情况下,您的吹笛也可以预测和安全。