Exactly-once语义
什么是exactly-once实际上是
"Exactly-once"通常被理解为两种不同的东西:- 交付:消息将恰好传递给消费者一次。
- 处理:最终副作用(DB记录,平衡变化,其他事件的发射)将恰好发生一次,即使交付或尝试更大。
在分布式系统中,谈论处理语义更为可靠。交付完全难以提供(重复和重复是不可避免的),但是可以使最终状态等同于单个处理。
何时需要EOS,何时不需要
需要EOS,如果:- 现金交易和资产负债表:双重注销是不可接受的。
- 许可证/配额核算,计费表。
- 不可逆的外部调用(例如,一次性密钥激活)。
- 效果是可逆的或可补偿的(传奇,回报)。
- 允许在店面/徽标中临时重复。
- 比将交易拖过整个路线便宜。
模型: 终端vs. hop-by-hop
Hop-by-hop EOS:每个部分(源→处理器→接收器)确保其操作正确应用一次。
终端EOS:整个链确保从"事实"到"侧面效应"的结果等同于单个处理。
实际上,通过在每个跳上结合事务和/或幂等来实现端到端。
基本构件
1.等效手术
在操作密钥上重复相同的请求会产生相同的结果。
Ключи: `idempotency_key`/`event_id`/`operation_id`.
实现:TTL的"所见"运算表≥输入日志的拒绝。
2."Read-process-write"交易)
在一个原子单元中,工作记录了副作用和阅读进展(正面/位置)。这消除了"鬼魂"在步骤之间的下降。
3.Version/SEQUENCE
对于单元,存储版本/计数器;仅当"expected_version"匹配时才应用更改。相同事件的重播不会一次增加版本→效果。
4.重复数据消除
索引通过"(consumer_id,event_id)"或自然的"business_id"操作。
实现模式
1)带有正交提交的Transactional Log+ Transactional sink
非常适合流处理。
我们从日志中阅读(仅确认记录)。
我们正在进行处理。
- (a)将效果写成sink (DB/表),
- (b)以"读到ofset N"(在同一DBD)记录。
- Commit。在重新开始时,要么全部被埋葬(并且将移位),要么一无所有。
属性:重复执行不会伤害;效果上的"恰好一次",即使消息读了两次。
2) Outbox+偶数消费者
用于事务制作服务。
在一个DB事务中:我们修改域条目并在outbox中编写事件。
Republicator使用相同的"event_id"将事件传递到总线。
消费者平均应用事件("event_id"的前提)。
属性:制作人确保事实不会丢失;消费者保证一个效果。
3)Kafka/Flink样系统中的EOS(概念上)
相同的制作人:在发货回程时防止拍摄。
制作人交易:一组录音到拓扑+consumer移位在原子上进行;读者使用"read_committed"隔离。
处理方存储状态(state store)并将其与事务一起发布。
属性:重新启动托架/塔斯卡不会产生双重效果;复制的"不可见"下游。
4)通过upsert/merge进行异位的"siki"(sinks)
Sink接受"operation_id"/"event_id"并执行"UPSERT……WHERE NOT EXISTS`.
副作用(例如权重)在原子上执行,并检查"是否已应用"。
属性:便宜的EOS方法与存储边缘,没有分布式事务。
实现的关键细节
操作标识符
必须确定重播(不要在转发时生成新的UUID)。
具有稳定的可见性区域(每个消费者/每个单元/每个系统)。
重复数据消除表
Колонки: `consumer_id`, `operation_id`, `applied_at`, `ttl_expires_at`.
"(consumer_id,operation_id)"索引。
TTL ≥最大重播窗口(log retency+潜在延迟)。
乐观的竞争
在write模型中,存储单元版本。
当应用事件/命令时,请使用"WHERE版本=:expected";复制不会增加版本。
订单/顺序
EOS不等于"完全相同的顺序"。通过批次密钥(所有聚合事件→单个批次)和/或比较"序列"来确保一致性。
异位外部挑战
对于不安全的方法(例如HTTP webhooks到第三方服务),请添加"Idempotency-Key",并要求合作伙伴支持它。
频繁的陷阱
EOS只在一个地方:如果sink是偶然的,但是您会在没有偶然性的情况下发布次要事件,则将获得"完全多次"下游。
两种commites:首先在DB中,然后在经纪人中商量ofset-它们之间的下降会产生重复的效果。
原始CDC向外:DB计划的改变打破了消费者的平均水平。
不稳定的键:"operation_id"取决于时间/随机,并在转发时更改。
成本和权衡
潜伏期:交易/孤立阅读→ p95/p99增长。
跨存储:重复数据消除表、状态存储、事务日志。
操作难度:交易定时,线程重组,"折叠"会话。
诊断:更多状态("kamite","显示为read_committed","滚动")。
逐点选择EOS:用于关键聚合和效果;其余的则由等效性和补偿性覆盖。
exactly-once测试
1.断层喷射:步骤之间的过程下降"记录了效果"和"记录了正交"。
2.重复:将相同的消息抽出2-5次,确保一个效果。
3.重新启动和重新调整:停止/重新启动窃贼,检查是否存在双重处理。
4.Network flappy:在事务中间的时间间隔,commit重播。
5.负载测试:队列增长→是否降级为"永久事务"。
迷你模板(伪版)
具有正交固定功能的Idempotent sink
pseudo begin tx if not exists(select 1 from dedup where consumer_id=:c and op_id=:id)
then apply_effect(...) -- upsert / merge / add_one_time_action insert into dedup(c, id, applied_at) values(:c,:id, now)
end if update offsets set pos=:pos where consumer_id=:c commit
具有聚合版本的命令
pseudo begin tx update account set balance = balance +:delta,
version = version + 1 where id=:account_id and version=:expected_version;
if row_count=0 then error CONCURRENT_MODIFICATION commit
安全和合规性
PII/PCI在重复数据消除表中:存储最小值,使用令牌代替"原始"数据。
审计:编写"operation_id","trace_id",结果(APPLIED/ALREADY_APPLIED)。
保留策略: TTL在dedup表上,归档期权/log.
反模式
"真正的exactly-once交付":试图消除传输协议级别的双打,而没有效果的幂等。
全局分布式事务到所有:通过所有服务的XA/2PC是脆弱和缓慢的。
混合非偶数小袋(例如,电子邮件发送到ofset commit)。
缺少操作密钥:重视有效载荷的"唯一性"。
生产支票清单
- 在每个关键效应上都有等效键。
- Ofset/读取位置被固定在一个具有效果的事务中。
- 已对重复数据消除表进行索引;TTL ≥博客的关注。
- 对于单元,包括乐观的竞争(版本/序列)。
- 流/拓扑以"仅加密"模式(如果可用)读取。
- CI/CD中存在重复和下降测试。
- Dashbords:重播、交易失败、锁定时间、滞后率。
- 关于"Idempotency-Keu"/重复/时间表的集成商文档。
FAQ
无需事务即可提供EOS?
通常,是的-通过sink's(upsert/merge)的幂等性和单元的旋转。交易简化了保修,但增加了成本。
每个人都需要一个"exactly-once"吗?
没有。他是道路。在无法补偿的地方/道路上逐点应用。
如何将电子邮件/webhooks链接到EOS?
将通知缓冲到commit,在提交效果后发送;保存"notification_id"并使发送具有等效性。
更重要的是-交付或处理?
处理。交货可能会重复;最终状态必须正确且唯一。
结果
Exactly-once是关于效果的正确性,而不是关于布线中缺少配音。它通过相容性,原子锁定效果和阅读进展,合理分期和考试纪律的结合来实现。将EOS应用到错误成本不可接受的地方,并通过跌倒和双打测试检查其现实-不相信运输。