API 网关可以保护、验证、限流并路由任务接收请求,从而支持异步处理;但应用必须在返回成功响应前持久化记录这项工作。可靠的设计会返回带有任务标识符和状态地址的 202 Accepted,确保重复提交安全,提供明确的最终结果,并在队列或工作进程饱和时施加背压。
如果没有完成持久化交接,仅由网关返回 202 并不能让任务真正异步,只会让客户端更难发现失败。
核心要点
202 Accepted表示处理尚未完成,并不承诺最终一定成功。- 确认接收前,先持久化任务或消息。
- 为客户端提供状态资源、回调、事件或其他明确的完成通知渠道。
- 保证提交操作幂等,因为客户端不一定能分辨丢失的是响应还是请求。
- 限制队列等待时间、深度、请求大小和每租户接收量;异步处理可能只是转移过载,而非消除过载。
- 明确网关与应用的职责:边缘负责策略,持久化组件负责工作流状态。
何时适合使用异步 API
如果工作能够在调用方的截止时间内稳定完成,同步请求—响应模式更合适。以下操作则适合采用异步契约:
- 需要数秒或数分钟,例如媒体转换或报告生成;
- 存在突发流量,需要工作进程逐步消化;
- 依赖缓慢或受速率限制的外部服务;
- 客户端断开后仍需独立重试和恢复;
- 结果产生得太晚,不适合一直占用同一个 HTTP 连接。
这种模式的性能收益非常具体:客户端和网关可以更早释放接收连接,而持久化队列能够解耦接收速率与工作进程吞吐量。但总工作量并不会消失,队列存储、调度、状态查询、回调和工作进程处理都会引入额外延迟与成本。
不要仅为掩盖过载的依赖项而把快速操作改为异步。应先判断该操作确实需要长时间运行的工作流,还是只需要并发控制和容量修正。
定义接收契约
RFC 9110 第 15.3.3 节将 202 Accepted 定义为:请求已被接收处理,但处理尚未完成,而且最终也可能不被允许。响应应描述请求的当前状态,并在可用时指向状态监视器。
一个实际的提交请求如下:
1POST /v1/report-jobs HTTP/1.1
2Host: api.example.com
3Authorization: Bearer <token>
4Idempotency-Key: 6d28a698-11e4-47bc-a85b-5427fbd89261
5Content-Type: application/json
6
7{"account_id":"acct-42","format":"csv"}1HTTP/1.1 202 Accepted
2Location: /v1/report-jobs/job-8f31
3Retry-After: 3
4Content-Type: application/json
5
6{
7 "id": "job-8f31",
8 "status": "queued",
9 "status_url": "/v1/report-jobs/job-8f31"
10}只有在应用提交了足以恢复和处理任务的状态后,才能返回该响应。安全的处理顺序如下:
1sequenceDiagram
2 participant C as 客户端
3 participant G as API 网关
4 participant A as 接收服务
5 participant Q as 持久化队列/存储
6 participant W as 工作进程
7 C->>G: POST 任务 + 幂等键
8 G->>G: 认证、验证、准入
9 G->>A: 转发已准入请求
10 A->>Q: 原子记录任务/消息
11 Q-->>A: 持久化确认
12 A-->>G: 202 + 任务 ID + 状态 URL
13 G-->>C: 202 Accepted
14 W->>Q: 领取并处理任务
15 C->>G: GET 状态 URL
16 G->>A: 读取已授权状态
17 A-->>G: queued/running/succeeded/failed
18 G-->>C: 返回已授权状态如果服务在持久化确认前发送 202,崩溃可能导致已接收的工作静默丢失;如果等待工作进程完成,则又退回了同步操作。
选择完成通知模式
轮询任务状态资源
对于能发起出站 HTTP 请求、却无法暴露公共回调端点的客户端,轮询最简单。可以把任务建模为带有小型状态机的资源:
1queued -> running -> succeeded
2 -> failed
3queued/running -> cancelled
返回稳定的终止状态、时间戳、安全的错误类别,以及需要认证的结果链接。使用 Retry-After、指数退避轮询或服务端文档规定的轮询间隔,避免状态端点本身成为主要负载来源。
发送回调或 Webhook
回调减少了轮询,却增加了另一个分布式系统边界。应要求使用 HTTPS、认证投递方、对消息体签名、加入时间戳和投递标识符,并定义重放防护。谨慎处理重定向与 DNS 变化,以降低服务端请求伪造风险。采用有界重试和死信路径,不要无限重试。
回调投递通常是至少一次。接收方必须去重;如果事件顺序不确定,应查询权威状态资源进行核对。
发布事件
在受控的服务间环境中,可以向消息代理或事件流发布完成事件。需要定义消息模式、分区和顺序预期、保留策略、消费者认证及重放行为。公共 HTTP 网关仍可管理任务接收和状态访问,而无需伪装成消息代理。
一个 API 可以同时提供轮询和回调,但应始终以同一条持久化任务记录作为事实来源。
确保重复提交安全
应用提交任务后,响应仍可能丢失。客户端看到超时,却无法判断是否应该再次提交。幂等键让服务端能够返回已有任务,而不是新建第二个任务。
可靠的实现应当:
- 将键限定在经过认证的租户与操作范围内;
- 存储相关请求载荷的指纹;
- 原子记录键、任务标识符和响应状态;
- 对完全相同的重放返回同一任务;
- 拒绝用同一个键提交不同载荷;
- 保留记录的时间应长于文档规定的客户端重试窗口。
让网关转发 Idempotency-Key 请求头很有用,但这不等于执行幂等规则。应用或持久化接收组件必须保证原子行为。队列消息可能在崩溃后被重复投递,因此工作进程也需要幂等副作用或去重机制。
在接收入口施加背压
只有当队列深度和任务等待时间仍在可接受范围内时,队列才能平滑突发流量。如果接收速率持续高于完成速率,队列本身就是不断扩大的故障。
应为以下对象定义限制:
- 每租户、每操作可接收的任务数;
- 并发任务和最大排队任务数;
- 最大载荷与结果大小;
- 任务失去价值前允许的最长排队时间;
- 工作进程尝试次数和总处理截止时间;
- 外部服务调用次数与支出;
- 任务记录、结果和死信的保留时间。
系统无法兑现契约时,应在持久化接收前拒绝请求。租户级接收限制通常对应 429,共享资源饱和可以对应 503。不要把任务放入明显没有处理能力的队列后仍返回 202。
对于大体积输入,更合适的做法通常是使用短期、限定范围的上传凭证,将其直接上传到对象存储,再向任务 API 提交引用和完整性元数据,而不是让整个对象经过网关和队列缓冲。
配置 APISIX 接收路由
Apache APISIX 应负责执行边缘策略,持久化工作流状态则应由接收服务负责。下面的 APISIX 3.18 示例会认证调用方、验证小型 JSON 请求体、限制到达速率并导出指标;它并不声称由 APISIX 自身把任务写入队列。
前置条件包括 Apache APISIX 3.18、Admin API 密钥、可访问的 job-intake.internal:8080 服务,以及配置了 key-auth 凭证的 APISIX Consumer。下面是可解析的路由示例;请根据环境替换上游地址、模式和数值策略。
1curl "http://127.0.0.1:9180/apisix/admin/routes/report-jobs" \
2 -X PUT \
3 -H "X-API-KEY: ${admin_key}" \
4 -d '{
5 "uri": "/v1/report-jobs",
6 "methods": ["POST"],
7 "plugins": {
8 "key-auth": {},
9 "request-validation": {
10 "body_schema": {
11 "type": "object",
12 "required": ["account_id", "format"],
13 "properties": {
14 "account_id": {"type": "string", "maxLength": 80},
15 "format": {"type": "string", "enum": ["csv", "json"]}
16 },
17 "additionalProperties": false
18 }
19 },
20 "limit-req": {
21 "rate": 5,
22 "burst": 10,
23 "key_type": "var",
24 "key": "consumer_name",
25 "rejected_code": 429,
26 "policy": "local"
27 },
28 "prometheus": {}
29 },
30 "upstream": {
31 "type": "roundrobin",
32 "nodes": {
33 "job-intake.internal:8080": 1
34 }
35 }
36 }'
request-validation 插件会在请求抵达接收服务前检查配置的模式。业务不变量仍应在应用内再次校验。以上数值限制仅用于说明,必须替换为根据租户与工作进程容量测量得出的值。
该示例明确使用 policy: local,因此每个 APISIX 节点都维护自己的计数器。在多节点集群中,同一 Consumer 的总准入速率可能超过每秒 5 个请求。应保守地把预算分配到各节点;如果接收服务要求集群级共享限制,也可以评估受支持的 Redis 后端策略,并把该依赖项的延迟和可用性纳入设计。
将测试 Consumer 凭证保存在 consumer_api_key 后,发送一个符合模式的请求:
1curl "http://127.0.0.1:9080/v1/report-jobs" \
2 -X POST \
3 -H "apikey: ${consumer_api_key}" \
4 -H "Idempotency-Key: 6d28a698-11e4-47bc-a85b-5427fbd89261" \
5 -H "Content-Type: application/json" \
6 -d '{"account_id":"acct-42","format":"csv"}'成功条件是:完成持久化接收后,返回包含接收服务任务标识符和状态地址的 202 响应。无效请求体应在到达服务前被拒绝,无效凭证应无法通过认证。超过稳定速率但仍在突发余量内的请求可能被延迟;耗尽已配置突发余量的请求应返回 429。具体响应体取决于应用和插件配置。
应为状态读取创建独立路由,以便采用与提交不同的授权、缓存和速率策略。验证调用方只能访问本租户的任务。切勿把凭证或敏感输入放入可预测的任务标识符或状态 URL。
客户端可以使用 Prefer: respond-async 表示倾向异步处理;该首选项由 RFC 7240 定义。它只是偏好而非命令。仅当应用真正实现了这种协商时,才应声明或返回 Preference-Applied: respond-async。
运维整个工作流
只看网关延迟会产生误导。应跟踪:
- 接收率、拒绝率和接收延迟;
- 队列深度及最旧待处理任务的等待时间;
- 从接收到启动、再到终止结果的耗时;
- 工作进程并发数、成功率、失败率、重试率和死信率;
- 重复提交和幂等冲突;
- 状态读取与回调投递负载;
- 已过期、已取消和无人领取的结果;
- 每个已接收及已完成任务的成本。
从提交请求到持久化任务、工作进程尝试、结果和回调,都应传播关联标识符。不要让单个追踪 Span 持续处于活动状态数小时;应使用稳定标识符和遥测系统支持的追踪关系来连接工作流事件。
测试每个交接点的故障
上线前,应测试以下容易产生歧义的时刻:
- 在持久化提交前崩溃:客户端不得收到
202。 - 提交后、响应前崩溃:使用相同幂等键重试时,必须返回已有任务。
- 同一队列消息投递两次:外部可见的副作用不得重复。
- 回调丢失或乱序:接收方必须去重,并通过状态进行核对。
- 工作进程停止而接收继续:队列等待时间告警和准入限制必须生效。
- 客户端轮询期间结果过期:API 必须返回文档规定的终止表示,而不是含糊的“不存在”。
- 租户仍有任务时撤销其权限:明确任务是取消、隔离还是允许完成。
还要对状态读取和回调进行负载测试。把工作移出原始请求后,往往会产生两个或更多新的 API 流量流。
决策检查清单
- 操作是否确实会超过合理的 HTTP 截止时间?
- 哪一个持久化事件允许服务返回
202? - 客户端如何得知成功、失败、取消和过期?
- 如何保证重新提交和工作进程重复投递安全?
- 谁可以读取状态或结果?
- 队列过深或任务过旧时,接收入口何时拒绝请求?
- 保留和删除策略是什么?
- 哪些网关指标可以关联到队列、工作进程和应用信号?
总结
异步 API 是工作流契约,不是状态码捷径。API 网关应认证、验证、计量并观察接收流量;持久化应用组件则必须先记录任务,再确认接收。将 202 Accepted 与状态或通知渠道、原子幂等、有界排队和明确的过载行为结合,才能快速释放请求连接,又不让任务丢失和队列膨胀变成隐藏故障。
常见问题
API 网关可以自行返回 202 并把工作写入队列吗?
只有在文档明确说明某项网关能力能够提供满足所需投递与恢复语义的持久化交接时才可以。在常见设计中,网关把请求转发给接收服务,由接收服务提交任务后再返回 202。
轮询一定比 Webhook 差吗?
不一定。对于无法接收入站请求的客户端,轮询更简单;Webhook 减少了重复读取,却需要经过认证的投递、重放防护、重试和死信处理。
使用队列后还需要限流吗?
需要。队列只能吸收接收与完成之间有界的速率差。如果请求持续快于工作进程,任务等待时间和存储用量会不断增长,直至工作流失败或成本过高。
后续步骤
在配置网关前,先定义持久化接收点和任务状态机。然后参考 APISIX request-validation 文档,验证当前版本支持的接收模式。如需集中治理 Apache APISIX 部署,可以进一步了解 API7 Enterprise。
