相似性和鑰匙
什麼是相容性
相似性是操作的屬性,其中具有相同標識符的重復不會更改最終效果。在分布式系統中,這是使結果等效於「正好一個處理」的主要方法,盡管有轉發,重復消息和計時器。
關鍵思想:每個可能重復的操作都必須用一個鍵標記,系統通過該鍵識別「已經完成」並應用最多一次的結果。
這在哪裏很重要
付款和資產負債表:「operation_id」註銷/貸記。
預訂/配額/限額:同一時段/資源。
Webhooks/通知:重新交付不應復制效果。
導入/遷移:重新運行文件/數據包。
流處理:經紀人/CDC的配音。
密鑰的種類及其範圍
1.操作鍵是特定業務操作嘗試的ID
示例:「idempotency_key」(HTTP),「operation_id」(RPC)。
區域:服務/單位;存儲在重復數據消除表中。
2.事件密鑰-唯一事件/消息ID
示例:「event_id」(UUID),「(producer_id,序列)」。
領域:消費者/消費者群體;保護投影。
3.商業鑰匙是學科領域的自然鑰匙
例如:「payment_id」,「invoice_number」,「(user_id,日)」。
區域:聚合體;適用於唯一性/版本檢查。
TTL和保留策略
TTL密鑰≥可能的重播窗口:log retency+網絡/處理延遲。
對於關鍵域(付款),TTL-天/周;遙測-時鐘。
用jobami清除祖先表;用於審計-歸檔。
密鑰存儲(重復數據消除)
事務性DB(推薦):可靠的upsert/unique索引,協同效應事務。
KV/Redis:快速,方便短的TTL,但沒有與OLTP的聯合交易-謹慎。
State store流處理器:經紀人中的本地+cheinjlog;在Flink/KStreams很好。
- idempotency_keys
`consumer_id` (или `service`), `op_id` (PK на пару), `applied_at`, `ttl_expires_at`, `result_hash`/`response_status` (опц.).
索引:「(consumer_id,op_id)」是唯一的。
基本實現技術
1)交易「效應+進展」
在單個事務中寫入結果並提交讀/位置進度。
pseudo begin tx if not exists(select 1 from idempotency_keys where consumer=:c and op_id=:id) then
-- apply effect atomically (upsert/merge/increment)
apply_effect(...)
insert into idempotency_keys(consumer, op_id, applied_at)
values(:c,:id, now)
end if
-- record reading progress (offset/position)
upsert offsets set pos=:pos where consumer=:c commit
2) Optimistic Concurrency(單元版本)
在比賽中防止雙重影響:sql update account set balance = balance +:delta,
version = version + 1 where id=:account_id and version=:expected_version;
-- if 0 rows are updated → retry/conflict
3)異位夾具(upsert/merge)
一次累積操作:sql insert into bonuses(user_id, op_id, amount)
values(:u,:op,:amt)
on conflict (user_id, op_id) do nothing;
協議中的冪等性
HTTP/REST
標題「Idempotency-Key:
服務器存儲密鑰條目並重新返回相同的響應(或不變性沖突中的代碼「409」/「422」)。
對於「不安全」的POST,「Idempotency-Key」+可持續的taymout/retray政策是必需的。
gRPC/RPC
元數據「idempotency_key」,「request_id」+deadline。
服務器實現-如REST:事務中的重復數據消除表。
經紀人/流媒體(Kafka/NATS/Pulsar)
制片人:穩定的「event_id」/偶數制片人(支持的地方)。
消費者:「(consumer_id,event_id)」和/或聚合的業務版本。
單獨的DLQ,用於非偶數/損壞的消息。
Webhuki和外部合作夥伴
在合同中要求「Idempotency-Key」/「event_id」;重新交付必須安全。
存儲「notification_id」和發送狀態;在轉發-不要重復。
密鑰設計
確定性:retrai必須發送相同的密鑰(在客戶/編排器上提前生成)。
可見性區域:將「op_id」形成「服務:aggregate: id: purpose」。
沖突:使用來自業務參數的UUIDv7/ULID或哈希(必要時與鹽)。
層次結構:前面的通用「operation_id」 →翻譯成所有子運算(偶數鏈)。
UX和產品方面
對密鑰的重新查詢必須返回相同的結果(包括主體/狀態)或顯式「已經完成」。
向用戶顯示「操作/完成」狀態,而不是「為好運」重新嘗試。
對於冗長的操作-按鍵(「GET/operations/{op_id}」)。
可觀察性
編寫「op_id」,「event_id」,「trace_id」,結果:「APPLIED」/「ALREADY_APPLIED」。
度量標準:重復百分比、去勢表大小、事務時間、版本沖突、DLQ投註。
Trace:關鍵必須通過命令→事件→投影→外部呼叫。
安全性和合規性
不要將PII存儲在密鑰中;密鑰是ID,不是付費。
在冗長的TTL中加密重復數據消除記錄中的敏感字段。
保留政策:TTL和檔案;被遺忘的權利-通過加密擦除響應/元數據(如果其中包含PII)。
測試
1.重復:運行一個消息/請求2-5次-效果恰好是一個。
2.在步驟之間下降:記錄效果之前/之後,正交固定之前/之後。
3.消費者重啟/重組:無雙重用途。
4.競爭:具有一個「op_id」 →一個效果的並行查詢,第二個效果是「ALREADY_APPLIED/409」。
5.長壽命密鑰:TTL過期和恢復後重復檢查。
反模式
每個回程的隨機新鍵:系統無法識別重復。
兩個獨立的公用程序:首先是效果,然後是失真-它們之間的下降復制了效果。
僅信任經紀商:在合成器/設備中沒有重復數據消除。
缺少聚合版本:重復事件第二次更改狀態。
胖鍵:鍵包括業務領域/PII →泄漏和復雜的索引。
沒有可重復的答案:客戶無法安全地回避。
示例
客戶端:「POST/payments」+「Idempotency-Key: k-789」。
服務器:事務-創建「payment」並寫入「idempotency_keys」。
重播:返回相同的「201」/身體;不變性沖突為「409」。
獎金(sink)
sql insert into credits(user_id, op_id, amount, created_at)
values(:u,:op,:amt, now)
on conflict (user_id, op_id) do nothing;
事件投影
消費者存儲該單元的「seen(event_id)」和「版本」;重復-忽略/等效的upsert。
讀取進度與投影更新記錄在相同的事務中。
生產清單
- 對於所有不安全的操作,都定義了冪等鍵及其可見性區域。
- 有帶有TTL和唯一索引的重復數據消除表。
- 讀取效果和進度以原子方式計算。
- 寫作模型包括樂觀的競爭(版本/序列)。
- API合同記錄了「Idempotency-Key」/「operation_id」和重播行為。
- 度量指標和日誌包含「op_id」/「event_id」/「trace_id」。
- 復制、摔倒和比賽測試-在CI。
- 符合TTL/存檔政策和 PII安全性。
FAQ
「Idempotency-Key」與「Request-Id」有什麼不同?
'Request-Id'-跟蹤;它可以在後臺進行更改。「Idempotency-Key」是操作的語義ID,在重復時必須相同。
沒有DB就能達到等效性嗎?
對於短窗口,是的(Redis/進程內關節),但是如果沒有聯合交易,雙倍的風險就會增加。在關鍵域中-在單個DB交易中更好。
如何與外部合作夥伴合作?
商定密鑰和可重復的響應。如果合作夥伴不支持-將呼叫包裹到其冪等層中並存儲「已經應用」。
如何選擇TTL?
總結最大延遲:log+worst-case 網絡/rebalance+緩沖區。添加庫存(× 2)。
底線
相似性是鑰匙,交易和版本的學科。穩定的操作ID+原子固定的效果和閱讀進展+等效的sinks/投影可產生「完全相同的效果」,而無需傳輸層魔力。使密鑰確定性,TTL逼真,測試惡意。然後,撤退和重復將成為例行公事而不是事件。