# 與 API 供應商的錢包轉帳：讓請求佇列保持暢通

> 平台與 API 供應商之間串接的轉帳呼叫如何塞爆請求佇列，以及佇列、快速回應和推送結果如何讓平台在供應商變慢時依然穩定。

Source: https://hibira.io/zh-tw/topics/wallet-transfers-api-providers
Updated: 2026-11-09

平台保管使用者的錢包；API 供應商則運行平台所提供服務背後的伺服器。使用者要用某個供應商的服務前，資金從平台轉到供應商；用完之後再轉回來。大多數整合把這些轉帳做成雙向串接的同步 HTTP 呼叫。流量一大，這條鏈會同時塞滿雙方的請求佇列，一個變慢的供應商就能拖垮整個平台。你的平台穩不穩定，不該取決於別人的伺服器。

## 一筆轉帳就是一條請求鏈

```text
sequence: 改之前：一筆轉帳，靠一串 HTTP 呼叫完成
使用者 | 平台 | API 供應商
1 > 2: 開始使用服務 | 平台的 worker 在等
2 > 3: POST /transfer-in | 等供應商回應
3 > 2: GET /wallet/verify | 供應商的 worker 也在等
2 >> 3: 200 OK
3 >> 2: 200 | 或逾時，狀態不明
2 > 3: GET /transfer/123 | 每 2 秒一次，直到完成
2 >> 1: 可以開始
```

平台的 worker 在整趟往返中都被佔住，包括供應商回呼平台的那一段。供應商的 worker 則在等那次回呼時被佔住。雙方互相等待，所以流量上升時兩邊的連線池會同時塞滿。一次逾時就讓轉帳狀態不明，接著就是狀態輪詢和重試；如果沒有冪等鍵，重試還可能讓錢轉兩次。

## 一個慢供應商怎麼變成所有人的問題

```text
flow: 一時變慢，演變成請求洪水
活動開始|轉帳變多 --> 供應商 C 變慢|150 ms → 5 秒 --> 平台 worker 被佔住|都在等 C --> 共用連線池用光|所有請求排隊
請求逾時 --> 重試與狀態輪詢|請求更多 --> 登入、儲值和其他供應商失敗|都與 C 無關
```

以下是推算範例，不是壓測結果（撰寫於 2026-11-09）：每秒 500 筆轉帳，分散在三個供應商，平台的連線池有 200 個 worker。[Little 定律](https://en.wikipedia.org/wiki/Little%27s_law)算出使用中的 worker 數：每秒轉帳數 × 每筆佔住 worker 的秒數。

| | 串接 HTTP | 佇列、快速受理、推送結果 |
| --- | --- | --- |
| 一個使用者請求佔住平台 worker 的時間 | 20 ms + 150 ms 的供應商呼叫 = 170 ms | 20 ms |
| 每秒 500 筆轉帳時的忙碌 worker 數 | 500 × 0.17 秒 = 85 | 500 × 0.02 秒 = 10 |
| 三個供應商之一卡住直到 5 秒逾時 | 約 900：200 個的連線池用光，所有供應商都失敗 | 仍然 10：只有那個供應商的轉帳在等 |
| 等待結果期間的請求 | 每筆待處理轉帳每 2 秒輪詢一次狀態 | 沒有，只有每個供應商每幾分鐘一次批次檢查 |

## 改之後：快速受理、背景結算、推送結果

```text
sequence: 改之後：雙方都快速回應，結果以事件送達
使用者 | 平台 | API 供應商 | *Hibira
1 > 2: 開始使用服務
2 >> 1: 處理中 | 已寫入帳本，worker 釋放
2 > 3: POST /transfers | 冪等鍵，從佇列送出
3 >> 2: 202 Accepted | 已記錄，worker 釋放
note: 供應商從自己的佇列、以自己的步調結算
3 > 4: transfer.settled | private-partner-acme
4 > 2: transfer.settled | id 123、金額
2 > 4: balance.updated | private-wallet-42
4 > 1: balance.updated
```

1. **平台先回應使用者。** 把轉帳以「處理中」寫進帳本、放進佇列，然後回應。使用者的頁面顯示「處理中」，事件到達時自動更新。
2. **由派送器把轉帳送給各供應商**，帶冪等鍵、短逾時，而且每個供應商有固定數量的名額。供應商一記錄好轉帳就回 202，所以用同一個鍵重試永遠安全。
3. **不要回呼。** 請求本身帶齊供應商需要的一切並附上簽章，供應商不必回呼平台驗證。
4. **結果用推送。** 供應商把 `transfer.settled` 發佈到給該平台的私有頻道，例如 `private-partner-acme`。平台後端保持一條 WebSocket 連線並訂閱，由供應商的授權端點授權。平台更新帳本，再把 `balance.updated` 發佈到使用者的錢包頻道，做法同[跨產品餘額同步](https://hibira.io/zh-tw/topics/balance-sync-websocket)。
5. **用一次檢查補上遺漏。** 平台每幾分鐘向每個供應商用一個批次請求查詢仍在處理中的轉帳，而不是逐筆輪詢。

## 供應商不穩時，平台照樣穩定

- **每個供應商各有名額。** 變慢的供應商只會佔滿派送器裡自己的名額。登入、儲值和其他供應商的 worker 都不受影響。
- **供應商不回應就暫停送出。** 連續逾時後，暫停該供應商的佇列，把它的服務顯示為暫時無法使用，等一下再試。轉帳停在你的帳本裡處於「處理中」，而不是卡在被佔住的連線上。
- **WebSocket 斷線不會擋住任何事。** 轉帳照常受理。連線恢復後，批次檢查會把中間發生的都結算好。
- **使用者從不等供應商的回應時間。** 頁面以平台自己的速度回應，結果好了就會出現。

## 常見問題

### 為什麼一個慢的 API 供應商會拖慢整個平台？

因為平台的 worker 在等它。每個同步呼叫都會佔住一個 worker，直到供應商回應為止，而這些 worker 是和其他所有功能共用的。一個供應商變慢，worker 就被用光，連根本不碰那個供應商的請求也得排在後面。

### WebSocket 會取代轉帳 API 嗎？

不會。轉帳本身仍是帶冪等鍵的 HTTP 請求，並記錄在雙方帳本裡。WebSocket 取代的是等待：被佔住的連線、狀態輪詢和回呼。結果改以事件送達。

### 轉帳完成時平台剛好斷線怎麼辦？

什麼都不會遺失，因為雙方帳本都已記下這筆轉帳。pusher-js 和其他用戶端函式庫會自動重新連線，處理中轉帳的批次檢查會結算任何漏掉的部分。用轉帳 ID 忽略已經套用過的事件。

### 這需要多少條連線？

平台後端到每個供應商的頻道各一條 WebSocket 連線，就足以處理與該供應商的所有轉帳，不論服務多少使用者。在 Hibira 上每次發佈算一則訊息；付費方案不計算送給訂閱者的份數。請看[價格](https://hibira.io/zh-tw/pricing)。
