跳至主要內容
ESC
engineering-architecture-breakdown — 第 3/6 篇

經典架構拆解 · 03 — Stripe API 冪等性設計

CLOUD

前兩篇拆解了「規模怎麼撐」(Netflix)與「即時配對怎麼做」(Uber),這一篇換個主題:正確性。在支付系統裡,「少扣一次」是客訴,「多扣一次」是災難。Stripe 直接在 API 設計這一層就把這問題解掉,是所有在做 B2B API 的工程師都該看一遍的案例。


為什麼值得拆解

在分散式系統裡,「請求只會送達一次」幾乎不可能保證。網路抖動、客戶端重試、負載平衡器超時,任何一個都會讓同一個請求被送出兩次。Stripe 的解法不是「想辦法不重送」,而是讓重送變得安全,這就是冪等性(idempotency)。

對 PCA 考生來說,這個案例直接打中 3 個高頻考點:at-least-once delivery 的處理、webhook retry 策略、事件溯源與 ledger 設計。考題裡只要問到 Pub/Sub 消費者收到重複訊息怎麼辦,底層其實都是 Stripe 這套邏輯。


商業規模與壓力

根據 Stripe 於 2024 年公開的年度信件(Stripe’s 2024 Annual Letter),Stripe 在 2024 年處理超過 1.4 兆美元的總支付金額(Total Payment Volume),且其平台成為多家大型企業的金流基礎設施。根據 Stripe Engineering Blog 於 2017 年的公開文章《Designing robust and predictable APIs with idempotency》,Stripe API 每天處理的 API 請求數級別在億次以上,webhook 也以類似等級對外派送。

在這種規模下,只要有一個不具冪等性的 API 呼叫被重試一次,就可能變成一次重複扣款、一筆建錯的訂閱、一次重複寄出的通知。每一件都是客服成本,也是財報風險。


架構演進簡史

年份里程碑意義
2011Stripe API 初版上線極度簡單的 REST 設計,成為當年「開發者友善 API」的代名詞
2013+正式引入 Idempotency-Key header讓同一個 key 在 24 小時內重試任意次,結果都一樣
2017公開發表冪等性設計文章把內部的「upsert with fencing」寫法公開,成為產業標準作法之一
2017+webhook retry 策略公開明確告訴客戶:webhook 會以指數退避(exponential backoff)重試達 3 天
2020+Stripe Workbench、可觀測性(observability)優先所有 API 請求可 replay、追蹤請求 ID,debug 成為產品功能

核心技術決策

決策為何這樣選替代方案與為何沒選
所有寫入 API 支援 Idempotency-Key header客戶端只要在重試時帶同一個 key,Stripe 就能辨識是重送還是新請求讓客戶端自己做 dedup:每個串接方都重造一次輪子,錯誤率不可控
key 的有效期為 24 小時夠長,涵蓋得了大多數重試情境;又夠短,不用永久佔著儲存永久保留:儲存成本和查詢延遲都會爆掉
webhook 採 at-least-once + 指數退避重試接收端偶爾會掛,不重試等於漏事件;at-least-once 比 at-most-once 安全得多只送一次(at-most-once):任何接收端暫時失敗都會掉事件
金流狀態以 state machine 明確建模PaymentIntent 有明確的 requires_payment_method → requires_confirmation → succeeded 等狀態,每一步可查核用 boolean flag 集合:狀態組合爆炸、無法稽核
observability 視為產品功能客戶自己可以在 Dashboard 看到每個請求、每次 webhook 的完整 trace只做內部 log:客戶遇到問題只能開 ticket,支援成本高

如果用 GCP 重新蓋

把這套設計搬到 GCP,大致對應如下:

  • 冪等性快取(idempotency cache): Firestore 或 Memorystore 存 Idempotency-Key → response 映射,TTL 設 24 小時。Firestore 適合跨 region 強一致;Redis 適合超低延遲。
  • 支付帳本(ledger): Spanner 存 PaymentIntent 與交易紀錄,利用強一致與 multi-region 能力確保同一 key 的第二次寫入會被拒絕(透過唯一約束或 transactional read-modify-write)。
  • webhook 派送: Cloud Tasks 最適合 —— 它原生支援指數退避、最長重試期間(maxRetryDuration)、可設定最大重試次數(maxAttempts),完全符合 Stripe 的 3 天重試模型。失敗用盡重試的任務需自行實作 dead-letter 模式(Cloud Tasks 沒有內建 DLQ)。
  • 事件主幹線: Pub/Sub 做內部事件解耦(付款成功 → 通知 / 發票 / 風控),記得消費者端要自行實作 dedup(Pub/Sub 原生支援 dead-letter topic 處理無法消費的訊息)。
  • 可觀測性: Cloud Logging + Cloud Trace,搭配 BigQuery 做長期查詢介面給客戶 Dashboard 用。

PCA 考點映射

考點Stripe 對應
at-least-once 與消費者冪等性webhook retry 模型,題目常問「如果訊息被消費兩次怎麼辦」
非同步 webhook 可靠派送Cloud Tasks / Pub/Sub + 重試策略,對應 Stripe webhook
強一致交易 ledger 選型Spanner vs Cloud SQL vs Firestore,考點是「為什麼金流要強一致」

常見誤解

  • 「冪等性就是加一個 UNIQUE 索引」 —— 這只做了一半。完整的冪等性還得「記住上一次的 response 並回放」,不然第二次請求會撞到 UNIQUE 衝突報錯,客戶端以為失敗又重試,就卡進死循環了。
  • 「webhook 接收端不用做 dedup」 —— 錯。at-least-once 代表同一個事件可能送達 N 次,接收端一定要拿 event.id 去重,不然會把同一筆重複寫進自己的資料庫。
  • 「冪等性只是 API 層的技巧」 —— 其實它是整條寫入路徑的設計原則。從 API → 資料庫 → 下游訊息 → webhook,每一段都得想清楚「重送會不會出錯」,少顧一段就會漏。

來源與延伸閱讀


這篇結束了本系列上半部。下一篇換系列第 4 篇接手:Slack 的即時訊息架構,看他們怎麼用 WebSocket 加 fanout 模型撐起數千萬同時在線的使用者。

🎯 換你練習

想動手設計類似系統?到 架構師設計工作坊 用這套思考步驟走一遍,也可以對照 PCA 五大案例庫 的官方題目練手。

engineering-architecture-breakdown — 3/6 完成 查看系列全覽 →

留言討論

徽章解鎖!