接口出问题时最常见的场景是:页面上报了个含糊的错误,开发和测试互相问”是不是你那边挂了”。把接口问题调明白,靠的不是猜,而是一套分层排查的思路——先确定问题出在哪一层,再在那一层里找证据。这篇按”分层定位 → 状态码 → 头信息 → 超时与重试 → 抓包与日志 → 常见故障”的顺序把方法过一遍。
从现象到定位:先划层,再猜原因
一次 HTTP 请求穿过的东西比大多数人以为的多:客户端 SDK、本地代理、DNS、负载均衡、API 网关、鉴权中间件、业务服务、下游数据库。出问题时与其挨个猜,不如先做一次”二分”:
- 换个客户端试试。 同一个 URL,用
curl直接打,和用业务代码打,结果不一致基本可以锁定在客户端 SDK 或序列化层;结果一致则继续往下。 - 换个环境试试。 测试环境通、生产不通,差异通常在配置、域名解析、网关路由或证书上。
- 换个接口试试。 同服务下的健康检查接口通了、业务接口挂了,问题大概率在业务逻辑或下游依赖,而不是网络层。
三个问题问完,通常已经能圈到某一层。curl 是这个流程里最趁手的工具,它把请求还原成最小集合,方便”把变量一个个加回来”:
curl -i -X GET 'https://api.example.com/v1/orders/123'
-H 'Authorization: Bearer <token>'
-H 'Accept: application/json'
-i 把响应头也打出来,-v 会额外显示请求头和 TLS 握手过程,-o /dev/null -s -w '%{http_code} %{time_total}n' 只关心状态码和总耗时。批量排查时,把一串 URL 丢进循环里跑,几秒钟就能知道”是全部挂了还是只有一部分挂了”。
状态码:语义先对齐
状态码不是随手填的数字,5xx 和 4xx 的处置方向完全不同。排查时先把码值归类:
| 状态码 | 含义 | 该查什么 |
|---|---|---|
| 200 | 成功 | 业务字段对不对,别被 200 骗了 |
| 301/302 | 重定向 | 跳到哪去了,-L 跟随后是否改方法丢 body |
| 400 | 请求有误 | 参数格式、类型、必填项 |
| 401 | 未认证 | token 是否过期、是否带了、scheme 对不对 |
| 403 | 无权限 | 认证对了但授权不够,看资源归属 |
| 404 | 资源不存在 | 路径拼写、环境、版本前缀 |
| 405 | 方法不对 | GET/POST 用错 |
| 409 | 冲突 | 并发修改同一资源 |
| 422 | 语义校验失败 | 业务规则,字段级错误常在 body 里 |
| 429 | 限流 | 触发了速率限制,看响应头里的配额信息 |
| 500 | 服务端异常 | 查服务端日志,客户端没法修 |
| 502/504 | 网关/超时 | 上游服务挂了或响应太慢,和 500 分开看 |
一个经验:4xx 看请求,5xx 看服务端。看到 500 就在客户端调参数,方向从一开始就错了。另外,网关把上游 5xx 转成 502/504 的情况很常见,此时业务服务的日志里可能根本没有这条请求记录,要去网关或负载均衡的日志里找。
请求头与响应头:这里有大量线索
很多接口问题写在头里,只是没人看。请求头里值得核对的:
Authorization/Cookie:scheme 是Bearer还是别的,token 有没有带前缀、有没有过期。Content-Type:body 是application/json还是application/x-www-form-urlencoded,序列化方式不对,服务端多半报 400 或 415。Accept:你期望application/json,服务端给回text/html,多半是被网关/错误页劫持了。Host、X-Request-Id:多网关、多环境时确认打到了哪,请求能不能在日志里捞出来。
响应头里则看:
WWW-Authenticate:401 时的鉴权要求,提示该用哪种 scheme。Retry-After:429/503 时告诉你要等多久,重试退避就按它来。Cache-Control、ETag:怀疑”改了没生效”时先看这里,可能是缓存。X-Request-Id/traceparent:把这几个值记下来,这是和服务端日志对账的唯一凭据。Set-Cookie的SameSite、Secure:跨域携带 Cookie 失败时先查这些属性。
超时与重试:分清是”太慢”还是”报错”
超时不是错误的一种,它是一种”状态不明”的失败——请求发出去了,但不确定对方收到没收到。这决定了重试策略:
- 连接超时(连不上):请求根本没到服务端,重试安全,可以激进些。
- 读超时(连上了没返回):请求可能已经被处理了,重试前先想幂等。写操作直接重试,可能造成重复下单、重复扣款。
配套的几条实践:客户端设置合理的超时上限,别用默认的无限等;重试次数有限(比如 2~3 次)并带指数退避加随机抖动,避免打挂下游;写接口设计上预留幂等键,让”重试”这件事在业务上是安全的。429 也别硬冲,按 Retry-After 排队。
抓包与日志的配合
客户端日志告诉你”我以为我发了什么”,服务端日志告诉你”我收到了什么”,两者对不上的地方就是问题所在。对账靠的就是 X-Request-Id 这类关联 ID。抓包工具在”我以为发了什么”这一环比日志更可靠——日志可能是 SDK 打的,SDK 自己有可能把请求改写了(加头、改 URL、重编码 body),抓包看到的是真正出网卡的东西。
抓包时优先在客户端出口抓(能看到发出去的原样),必要时再到服务端入口抓(能看到负载均衡是否改写了请求)。TLS 加密环境下要配合解密或改用明文的调试代理,生产环境别为了抓包把 HTTPS 降级,代价不划算。
常见故障模式
跨域(CORS)。 浏览器报 CORS 错误,先看是不是预检(OPTIONS)挂了。服务端要在预检响应里允许对应的 Origin、方法、请求头;带 Cookie 的请求还要求 Access-Control-Allow-Credentials: true 且 Allow-Origin 不能是 *。这类问题用 curl 复现不了,必须用浏览器开发者工具看。
证书。 报错集中在 x509、SSL、certificate verify failed 一类字样,通常是证书链不完整、域名和证书 SAN 不匹配、或本地信任库里缺中间证书。openssl s_client -connect api.example.com:443 -servername api.example.com 能看到实际返回的证书链,逐个核对有效期和签发者。本地开发想临时跳过校验,curl -k 或 SDK 的对应开关可以救急,但只限本地,别把跳过校验的代码带到生产。
幂等。 同一操作因重试被服务端执行了两次,表现为数据重复。排查时看重试逻辑有没有触发、幂等键有没有生效、服务端去重窗口是否够长。
序列化。 数字被写成字符串、字段名大小写不一致(userId vs user_id)、时间戳时区不统一、空值和缺字段含义混用——这类问题客户端和服务端各执一词时,直接抓一份真实请求的原始 body 对着看,比读文档快。
一套可以复用的排查顺序
接口挂了,按这个顺序走一遍,多数情况能自己定位到层:
curl复现,记下状态码和耗时。- 读响应头,看有没有跳转、缓存、限流、关联 ID。
- 对照状态码语义表,确定该查请求还是查服务端。
- 客户端和服务端日志用同一个
X-Request-Id对账。 - 对不上的部分,抓包看真实出网内容。
- 定位到层之后,再谈修复。
这套流程的价值不在于”每次都很快”,而在于它把”猜”变成了”证”——你排除掉的每一层,都是别人问起来时能拿出来的证据。
评论(1)
这个技巧帮我省了不少时间。
请 登录 后参与评论。