API 調用頻率限制與 429 Too Many Requests:支付接口如何限流,你該怎麼處理
API 調用頻率限制是什麼、支付接口為什麼返回 429 Too Many Requests、如何遵循 Retry-After 重試,以及如何設計調用方式避免觸發限流。
API 調用頻率限制(rate limit)規定了客戶端在一段時間內最多能發多少個請求。超過之後,接口不再執行,而是返回 429 Too Many Requests,通常還會帶一個 Retry-After 響應頭,告訴你要等多久。支付接口做限流,是為了不讓某一個接入的失控循環拖慢所有人的收銀台;而只有當代碼的調用頻率遠超業務實際需要時,限流才會真正起作用。
這篇文章説明支付接口的頻率限制如何運作、KukoPay 的具體限額、如何正確處理 429,以及如何設計接入,讓它在正常運行中永遠不會碰到限流。
API 頻率限制是如何運作的?
大多數接口按客戶端(這裏是商戶賬戶)在一個時間窗口(通常是一分鐘)內計數。窗口內配額用完之前正常服務,用完之後,每個請求都會得到 429,直到窗口滾動到下一個。
KukoPay 按商戶賬戶使用固定的一分鐘窗口,不同類型的請求有各自的配額:
| 類型 | 限額 | 覆蓋範圍 |
|---|---|---|
| 寫接口 | 每分鐘 120 次 | 創建訂單、退款及其他變更 |
| 讀接口 | 每分鐘 600 次 | 查詢和列表 |
| 導出 | 每分鐘 10 次 | CSV 導出(format=csv) |
| 易支付網關 | 每分鐘寫 300 次、讀 600 次 | 易支付兼容接口,與 /v1 分開計數 |
- 配額屬於賬戶:賬戶下的每個應用、每把密鑰,無論沙箱還是正式環境,都共用同一組額度。多簽發幾把密鑰並不會增加容量。
- 導出有單獨的小額度,因為一次導出最多會掃描 10,000 行;運行導出的對賬任務不會擠佔收銀台下單的寫額度。
- 易支付的流量單獨計數,所以由買家驅動的易支付收款不會用光你的 API 額度。
429 響應長什麼樣?
HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-Request-Id: req_31b6c0d7f2a94e15b8c3d0a7e6f45219{
"code": 429,
"error": "rate_limited",
"type": "rate_limit_error",
"message": "…",
"request_id": "req_31b6c0d7f2a94e15b8c3d0a7e6f45219"
}Retry-After 是距離當前窗口結束的秒數,也就是重試最早可能成功的時刻。這個錯誤被標記為可以安全重試:請求沒有被執行,稍後重試不會產生重複。完整的錯誤碼見 錯誤碼。
如何處理 429 Too Many Requests
- 等待
Retry-After指定的時間後再重試。 提前重試只會再得到一個 429。 - 加入隨機抖動,也就是額外加一段隨機延遲,避免同時被限流的多個 worker 在同一秒一起重試。
- 限制重試次數,超過之後報錯,而不是無限重試。
- 重試時使用同一個
Idempotency-Key。 429 在請求被處理之前就已經判定,永遠不會存到這個鍵下,所以重試會真正執行;如果之前某次嘗試其實成功了,這個鍵也能防止重複。冪等鍵的原理見 冪等鍵是什麼。
async function callWithRetry(doRequest, maxAttempts = 5) {
for (let attempt = 1; ; attempt++) {
const res = await doRequest() // 每次都使用同樣的請求體和 Idempotency-Key
if (res.status !== 429 || attempt >= maxAttempts) return res
const retryAfter = Number(res.headers.get("Retry-After")) || 2 ** attempt
const jitter = Math.random() * 1000
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000 + jitter))
}
}對於 429 以外的錯誤,比如超時和 5xx,請改用指數退避,做法見冪等鍵指南裏的重試循環。
如何避免觸發頻率限制
大多數 429 來自接入方式,而不是真實的業務量。每分鐘 120 筆訂單,一天就超過 170,000 筆。
- 用 Webhook 代替輪詢。 每隔幾秒查一次訂單狀態、直到它變為已支付,是耗光讀額度的典型方式。
payment.succeededWebhook 會在付款成功的那一刻通知你;如果感覺回調沒到,原因排查見 收不到支付 Webhook 回調怎麼辦。 - 必須輪詢時要剋制。 對於處理中的退款,每 10 秒查一次、最多持續 5 分鐘,之後改為低頻查詢。
- 翻頁時用大頁。 列表接口支持
limit和游標分頁,一次取 100 條,而不是很多個小頁。 - 一次導出,而不是逐條查詢。 對賬時,一次
format=csv導出一天的資金流水,就能取代成千上萬次查詢,見 支付對賬怎麼做。 - 把自己的批處理任務分散開。 大規模的數據回填分攤到幾分鐘內執行,不要一次全部發出。
- 不要在緊湊循環裏重試。 每次出錯就立即重試的循環,會把一次失敗放大成幾百個請求。
頻率限制會影響真實買家嗎?
限額只作用於用你的密鑰發起的 API 調用,也就是你服務器的流量。在託管收銀台或通過收款鏈接付款的買家,不會消耗你的 API 額度;確認付款和重試 Webhook 的任務也在平台一側運行。不過,你服務器上一個失控的任務仍然可能延遲你自己的新訂單,這正是要儘早修好輪詢循環的最好理由。
測試你的 429 處理邏輯
因為沙箱和正式環境的調用計入同一個賬戶級額度,不要用壓測接口的方式去觸發 429:一波測試請求會在同一分鐘裏限流你的正式訂單。應該去測試處理邏輯本身:在你的 HTTP 客戶端前放一個樁(stub),讓它返回帶 Retry-After 頭的 429,檢查你的代碼是否會等待、加入抖動、保持同一個 Idempotency-Key,並在達到上限後停止。值得用真實接口覆蓋的沙箱場景,見 支付測試卡號大全。
總結
- API 頻率限制規定了每段時間的請求上限,超過後返回帶
Retry-After的429 Too Many Requests; - KukoPay 每個商戶賬戶每分鐘允許寫 120 次、讀 600 次、導出 10 次,所有應用、密鑰和兩個環境共用;
- 遇到 429,等待
Retry-After秒並加上抖動,限制重試次數,並保持同一個Idempotency-Key; - 用 Webhook 代替輪詢、用大頁翻頁、用 CSV 導出、分散批處理任務,就能遠離限流;
- 託管收銀台上的買家不消耗你的 API 額度;用樁來測試 429 處理,而不是壓測。
在沙箱裏把它跑通
註冊商戶後即可申請測試密鑰,不需要等待資質審核。