编程 接口 504 排查:ServiceLatency 为 0,说明请求根本没到后端

2026-10-18 00:03:14

接口报错时,先用 X-Ca-Request-Id 和 8 个 IO 时间点判断请求到了哪一层

参考文档:API 调试及自助排错排查 API 调用错误

排障时,比起先看后端日志,先确认一个问题更省时间:这个请求到底有没有到达后端? 网关返回的响应头、请求 ID 和调用链路上的几个时间点,足以把这个问题回答清楚。

1. 从响应头获取错误信息

所有到达网关的 API 请求,网关都会返回结果信息。响应 Header 中以 X-Ca 开头的属性由 API 网关返回,重点看三个:

  • X-Ca-Error-Code:API 网关系统错误码。请求被网关拦截后,由网关给出,6 位长度字符串。
  • X-Ca-Request-Id:请求唯一 ID。请求进入 API 网关应用后生成,通过响应头返回给客户端。建议客户端与后端服务都记录,用于排查与跟踪。
  • X-Ca-Error-Message:网关返回的错误消息,请求出错时随响应头一起回给客户端,通常附有简单的错误原因说明。

拿到 X-Ca-Error-Code 与 X-Ca-Error-Message,基本能明确报错原因;X-Ca-Request-Id 则用来往下查。

2. 用 X-Ca-Request-Id 查调用日志

控制台路径:

  1. 登录 API 网关控制台。
  2. 左侧栏进入「API 调用 > 自助排错」,填入 API 网关所在 Region 和 X-Ca-Request-Id,单击查询,即可看到对应日志。

X-Ca-Request-Id 同样可以用于在日志服务中查询请求日志,或提供给支持人员排查。日志查出来后,结果按下面三类判读。

2.1 网关报错

X-Ca-Error-Code 不为空,说明本次调用被 API 网关拦截。此时网关返回对应错误码和 X-Ca-Error-Message,按消息内容排查拦截原因,更多错误码解释见错误代码表。

2.2 后端服务报错

HTTP 状态码不为 200,且 X-Ca-Error-Code 为空,表示网关已成功把请求转发到后端服务,后端处理完返回了非 200。问题在后端业务逻辑,需要检查后端服务。如果 API 是从云市场购买的,联系云市场服务提供商。

2.3 请求成功

HTTP 状态码为 200,表示网关成功转发请求、后端也返回了成功应答。

三类结果的分界线只有两条:X-Ca-Error-Code 是否非空,以及状态码是否为 200。

3. Trace 日志看什么

在 API 调试页面发送请求后,Trace 日志里能看到完整链路:API 网关收到的请求、网关的处理详情、网关发给后端服务的请求、网关收到的应答、网关给客户端返回的应答。

使用子账号或角色扮演账号时,发送请求前勾选「记录 Trace 日志」,需要主账号给该账号授予当前 API 所在实例的 AcquireGatewayToken 接口 RAM 权限:

{
  "Version": "1",
  "Statement": [
    {
      "Effect": "ALLOW",
      "Action": "apigateway:AcquireGatewayToken",
      "Resource": "acs:apigateway:{#regionId}:{#accountId}:instance/{#InstanceId}"
    }
  ]
}

其中 {#} 为变量标识,需替换为实际值。

4. 用时间点定位耗时在哪一段

自助排错的问题诊断会展示本次调用的部分日志数据,两个耗时字段先看:

  • TotalLatency:网关从接收到客户端请求,到完成客户端响应的总时间。
  • ServiceLatency:网关向后端服务发送请求,到接收完后端服务响应的时间。

判定很直接:ServiceLatency 为 0,请求在网关被拦截、未到达后端;ServiceLatency 不为 0,请求到达了后端。

再往下拆,整条链路的 IO 时间点:

  • FrontRequestStart:网关开始接收客户端请求的时间点。
  • FrontRequestEnd:网关完成接收客户端请求的时间点。
  • BackendRequestStart:网关开始向后端服务发起请求的时间点。
  • BackendRequestEnd:网关完成向后端发起请求的时间点。
  • BackendResponseStart:网关开始接收后端服务响应的时间点。
  • BackendResponseEnd:网关完成接收后端服务响应的时间点。
  • FrontResponseStart:网关开始向客户端发送响应的时间点。
  • FrontResponseEnd:网关完成向客户端发送响应的时间点。

用相邻时间点相减,就能判断耗时落在哪一段:

  • FrontRequestStart → FrontRequestEnd:客户端请求上传阶段,大 body 或弱网会拉长这段。
  • FrontRequestEnd → BackendRequestStart:网关内部处理,认证、鉴权、限流、路由都在这里,ServiceLatency 为 0 却耗时不短时,重点看这里。
  • BackendRequestStart → BackendResponseEnd:真正打到后端的一段。
  • BackendResponseEnd → FrontResponseEnd:网关向客户端回写响应。

5. 一个反直觉点:客户端看到 504,不等于后端慢

504 由网关返回,但原因可能是网关在等上游,也可能是网关自己超时。两者在时间点上长得不一样:如果 BackendRequestStart 到 BackendResponseEnd 很长,或者停在 BackendResponseStart 之前,那是后端处理慢或没回;如果 ServiceLatency 接近 0、请求根本没出去,那是网关侧拦截或网关自身超时,跟后端无关。

5xx 里更值得先确认的是「谁的 5xx」,而不是「超时了几秒」。判据还是那两条:X-Ca-Error-Code 非空,5xx 归网关;X-Ca-Error-Code 为空、状态码非 200,5xx 由后端返回。前者查错误码表,后者查后端日志。响应体格式、后端自定义的响应头也能辅助判断,后端的错误结构通常和网关的不一样。

6. 自建链路上的等价做法

脱离 API 网关后,等价的信息要自己埋。

Nginx / OpenResty:$request_id 由 Nginx 生成,透传给上游,同时打进入口日志。

proxy_set_header X-Request-Id $request_id;

log_format with_rid '$remote_addr "$request" $status '
                    'rt=$request_time urt=$upstream_response_time '
                    'rid=$request_id up=$upstream_addr';
access_log /var/log/nginx/api.log with_rid;

$request_time 对应 TotalLatency,$upstream_response_time 对应 ServiceLatency,$upstream_addr 为空就说明请求没到后端。

Go 中间件:读出上游传入的请求 ID,没有就生成一个,写进日志上下文并回传响应头。

func RequestID(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		id := r.Header.Get("X-Request-Id")
		if id == "" {
			id = uuid.NewString()
		}
		w.Header().Set("X-Request-Id", id)
		ctx := context.WithValue(r.Context(), reqIDKey{}, id)
		log.Printf("rid=%s method=%s path=%s", id, r.Method, r.URL.Path)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

curl 对齐客户端与网关的时间:客户端侧先量出 TTFB 和总耗时,再和网关的 FrontResponseEnd、BackendResponseEnd 对表。

curl -sv -o /dev/null \
  -w 'dns=%{time_namelookup} connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n' \
  https://api.example.com/v1/orders

time_starttransfer 明显大于网关 FrontResponseEnd,耗时在回程网络;两者接近而总耗时大,往 BackendResponseEnd 之前找。

排障顺序可以固定下来:先从响应头取 X-Ca-Error-Code、X-Ca-Error-Message、X-Ca-Request-Id,再拿请求 ID 查日志确认是谁返回的错,需要细节时看 Trace,最后用 ServiceLatency 和 8 个 IO 时间点定位耗时段落。

复制全文 生成海报 接口排障 API网关 故障排查 可观测性

推荐文章

程序员茄子在线接单