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應用到錯誤成本不可接受的地方,並通過跌倒和雙打測試檢查其現實-不相信運輸。