# 与 API 供应商的钱包转账：让请求队列保持畅通

> 平台与 API 供应商之间串联的转账调用如何塞爆请求队列，以及队列、快速响应和推送结果如何让平台在供应商变慢时依然稳定。

Source: https://hibira.io/zh-cn/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。[利特尔定律](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-cn/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-cn/pricing)。
