API 接入 CDN 后开始返回 429 Too Many Requests,需要先定位哪个环节发出了响应,再决定调整规则还是降低请求频率。请求可能经过 CDN、安全规则、API 网关和应用服务,任意一层的限流都可能让客户端收到 429。只看状态码,就去提高 CDN 阈值,容易改错位置。
本文面向维护网站接口、开放 API 和移动端服务的开发与运维人员。排查以一次只读请求和已有日志为起点,不需要向生产接口连续发请求来“试出”限流阈值。
429 能说明什么,不能说明什么
RFC 6585 将 429 定义为客户端在一定时间内发送了过多请求。响应可以包含 Retry-After,告知再次请求前应等待多久;这个字段并非必填。标准也没有统一规定按 IP、账号还是其他身份计数。[1]
这意味着,同样是 429,背后可能是完全不同的规则:
| 观察到的现象 | 优先核对的范围 |
|---|---|
| 某个接口突发请求后失败,其他接口正常 | 路径匹配、方法条件、接口级规则 |
| 同一办公网络的多个账号同时失败 | 出口 IP 是否被当作同一计数对象 |
| 同一个 API Key 换网络后仍失败 | 账号、租户、密钥配额或共享计数器 |
| CDN 有拦截事件,源站没有对应请求 | 边缘限流或安全动作;仍需核对日志完整性 |
| CDN 转发成功,网关或应用记录了 429 | 网关配额、应用限流、上游依赖限制 |
这些现象用于缩小范围,不能单独作为结论。响应头里的服务器标识、错误页样式和请求 ID 都可以提供线索,但中间层可能添加或改写它们。最终要把同一请求在各层的记录对齐。
保存一份完整响应,再沿请求链定位
先从已有失败请求中取一个样本,记录发生时间与时区、域名、路径、HTTP 方法、状态码、响应头、错误正文和请求 ID。排查记录中应隐藏令牌、Cookie、个人信息和 URL 查询参数里的密钥。
如果需要补采样,可对自己管理的只读接口发起一次请求。下面的示例适用于 Linux、macOS 或 WSL,要求本机已安装 curl;Windows PowerShell 请使用 curl.exe,并按该终端的语法调整换行。
curl --silent --show-error \
--connect-timeout 5 --max-time 15 \
--dump-header response-headers.txt \
--output response-body.txt \
--write-out 'status=%{http_code} total=%{time_total}s\n' \
'https://api.example.com/v1/status'
请替换成实际的只读接口。示例不自动重试,也不自动跟随重定向,便于保留本次请求的原始结果。无鉴权请求如果得到 401 或 403,说明这个样本不足以复现登录用户的问题,不能据此排除限流。
拿到响应后,按以下顺序查看已有记录:
- CDN 或安全事件:找到同一时间、路径、来源和请求 ID 对应的规则,确认执行的动作以及命中条件。检查接口是否真的被转发。
- API 网关或负载均衡:确认接收到请求后返回的状态,以及是否把上游状态转交给客户端。
- 应用与依赖服务:查看限流日志、配额用量及依赖接口的错误。有些应用会把第三方服务的额度不足转成自己的 429。
源站查不到请求时,还应核对时区、日志采样、查询的节点和日志延迟。仅凭“没搜到”就认定 CDN 拦截,证据还不够。
如需与绕过 CDN 的路径对比,只使用团队已有、受控的诊断入口,并保持 Host、TLS、鉴权和请求参数一致。不要为了这次排查把源站开放到公网,也不要停掉整站防护。
检查限流计数是否把正常用户合并了
定位规则之后,最值得检查的是“谁和谁共用同一个计数器”。
按 IP 限流时,企业网络、校园网络或移动网络出口可能聚合许多正常用户。应用如果只看见反向代理的连接地址,也可能把不同用户归到同一个桶。此时应检查真实客户端地址的传递与解析,并把可接受转发头的范围限定到可信代理。直接信任公网请求里的任意 X-Forwarded-For,会让攻击者伪造身份。
账号或 API Key 限流则要检查共享凭证。测试脚本、定时任务和线上服务如果共用同一个 Key,后台任务可能已经消耗了前台请求的额度。应在配额统计中分开观察调用方,必要时拆分凭证和预算,而非只把总阈值调高。
还需要核对规则覆盖的路径和方法。例如,原本保护登录提交的规则,如果同时匹配整个 /api/,可能连状态查询和资源列表也一起限制。
不同 CDN 对计数位置、更新延迟和统计维度的实现不同。Cloudflare 官方文档明确提醒,其速率限制不保证只有精确数量的请求到达源站,检测和计数更新之间可能存在延迟。因此,不能把边缘阈值直接当作源站容量上限。[3]
修改前保存规则、匹配范围、阈值和当前观察值。优先缩小误匹配范围、修正计数身份,或降低调用方并发;确需调整阈值时,结合源站余量小范围验证,并保留恢复原配置的路径。
让重试等待足够久,也能及时结束
Retry-After 有两种合法形式:非负整数秒数,以及 HTTP 日期。客户端需要同时处理这两种形式。日期形式还要考虑本机时钟偏差;缺失、无法解析或已经过期的值不能直接当成有效等待时间。[2]
例如,下面的响应建议等待 30 秒:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Cache-Control: no-store
Content-Type: application/json
30 秒仅用于说明字段语法,不能直接套用到所有接口。服务端应按实际配额恢复条件设置它。RFC 6585 规定 429 响应不得被缓存;示例额外写明 no-store,便于表达响应的存储策略。[1]
客户端可采用有上限的指数退避,并加入随机抖动,分散同时失败的请求。一个便于讨论的设计示例如下,参数需要按接口时延目标调整:
最多尝试 3 次,包括首次请求
整体预算 20 秒,每次请求另设超时
只有一个调用层负责重试
收到允许重试的 429 后:
第一次重试取 n=0,后续依次递增
backoff = 随机取值(0, min(4 秒, 0.5 秒 × 2^n))
若 Retry-After 有效:delay = max(Retry-After, backoff)
否则:delay = backoff
若 delay 已耗尽剩余预算:结束,向调用方返回可识别的限流结果
等待 delay 后,在剩余预算内发起下一次请求
这里的退避上限只约束客户端生成的退避时间。服务端要求等待 30 秒,而调用预算只剩 10 秒时,应结束本轮调用,不能把 30 秒截成 4 秒后提前重试。也应把等待时间算入整体预算,并支持调用方取消等待。
随机退避仍可能抽到很短的间隔,所以还需要并发上限、队列长度和重试预算共同约束流量。AWS 的工程文档建议,在调用栈中选择一个位置重试,并使用抖动减少集中重试造成的流量突发。[4]
如果客户端、网关和业务 SDK 各自最多尝试 3 次,一次业务调用在最坏情况下可能放大为 27 次下游请求。这是三层尝试次数相乘的示例,不代表任何具体产品的默认行为。排查时应把 SDK 自带策略也算进去。
POST、支付和任务创建需要单独处理
对只读 GET 接口,可以在次数、超时和预算受控的前提下设计自动重试。支付、下单、发消息、创建任务等写操作则要先确认是否具备可靠的幂等机制。
RFC 9110 不建议客户端自动重试非幂等方法,除非客户端知道该请求的实际语义是幂等的,或能确认原请求从未生效。[2] 对外接口返回 429,并不足以替代对应用处理流程的确认,尤其是响应经过多个代理或业务层转换时。
如果接口支持幂等键,同一次业务操作的后续重试必须按服务端约定复用同一键和相同业务参数。每次生成新键,可能被服务端视作不同操作。还要确认幂等记录的有效期、并发处理方式,以及超时后如何查询原操作结果;单纯加一个名为 Idempotency-Key 的请求头,并不会自动产生去重能力。
对于需要长时间等待的任务,适合由受控队列调度后续尝试。交互式请求则应及时提示稍后再试,并让前端停止无休止的自动刷新。
用恢复后的流量验证修改
完成配置后,用少量、已获授权的业务请求验证,不在生产接口上制造大规模突发。至少观察一个完整的限流统计窗口,并覆盖一次真实业务波峰;实际观察时长取决于规则周期和业务节奏。
检查时同时看几个结果:合法用户是否恢复、目标规则是否仍能约束异常流量、源站负载和错误是否上升,以及客户端的实际请求数是否因重试增加。429 下降但源站开始大量超时,说明压力被转移到了后端,仍需收紧并发或恢复原阈值。
还应检查 SDK 和浏览器是否重复提交、失败响应是否被自定义缓存错误地复用,以及长 Retry-After 是否能让客户端及时结束本轮等待。保留规则修改前后的配置、时间点和观察结果,后续才能判断是哪项改动产生了效果。
排查完成后,应能明确回答三个问题:429 由哪一层返回、使用什么身份和周期计数、客户端如何在预算内结束或重试。这些信息清楚了,再决定调整边缘规则、拆分共享配额,还是优化调用方的并发与排队方式。
官方参考资料
资料核验日期:2026 年 10 月 15 日。文中的命令、等待参数与请求次数均为诊断或设计示例,未声称在某个生产环境完成压测。