深蓝黑背景封面:左侧大标题「502、503、504,到底谁背锅」,副标题「网关与上游之间的故障定位」;右侧原创链路示意——客户端、网关、上游服务三个方框依次相连,网关与上游之间标出三枚错误徽章 502、503、504;左上标注「面试经典 · 网络」,右上标注「Day 02 · 10-20」 图 1:本文封面,三个错误码都挂在网关与上游之间这一段链路上。

先说明属性:这是一篇经典题解析,题目出自本地历史面试资料,与任何公司的当前招聘无关。「50X 错误码的内涵」是网络面试的常见题,背出「服务器内部错误」只能拿一半分——面试官想听的是:在「客户端—网关—上游」这条链路上,每个码各自把锅扣在谁头上。

想象面试现场:「用户报错说接口 502 了,你怎么定位?502、503、504 有什么区别?」

先立坐标系:这三个码都发生在网关与上游之间。

现代架构里请求很少直连应用服务器,中间隔着反向代理或网关:Nginx、API 网关、负载均衡器。502、503、504 的共同点,是它们都是「网关这个角色」替上游发的言;区别在上游到底出了什么状况。RFC 9110 对三者各有精确定义,我们逐个对照着看。

502 Bad Gateway:上游的话,网关听不懂或者根本没等到。

RFC 9110 的定义:服务器作为网关或代理时,从其访问的上游服务器收到了无效响应。「无效响应」在实践里主要是两类:一是根本连不上——上游进程崩溃、端口不通、连接被拒;二是连上了但回的东西不成样子——协议不匹配、返回体损坏。责任方在上游进程或它所在的网络。排查方向:上游服务还活着吗?端口在监听吗?网关到上游的网络通吗?

503 Service Unavailable:上游活着,但主动说不。

RFC 9110 的定义:服务器因临时过载或计划维护,当前无法处理请求,预计过一阵会缓解;并且明确可以带一个 Retry-After 头部,建议客户端等多久再试。关键词是「主动」和「临时」——上游不是死了,是开启了自我保护:过载限流、熔断拒收、滚动维护。责任方在容量。排查方向:上游的 QPS 与资源水位是不是顶到了限流阈值?是不是正好在发布窗口?

504 Gateway Timeout:上游接了活,但没按时交卷。

RFC 9110 的定义:网关没有在时限内从上游收到完成请求所需的响应。与 502 的「连不上」不同,504 是「连上了、请求也接了,但处理超时」——慢查询、死锁、下游依赖卡住、推理超时,都是典型病因。责任方在上游性能。排查方向:上游的慢日志、线程池是不是打满、它自己的下游依赖是不是卡住了。

一段代码把三种形态跑一遍。

下面这份代码模拟网关调用上游,把四种故障形态映射到不同状态码。用 Node.js 22.15.0 直接运行(node gateway.js)即可:

'use strict';

// 模拟网关调用上游:按上游的故障形态映射状态码
function gatewayCall(upstream) {
  let response;
  try {
    response = upstream();
  } catch (e) {
    if (e.code === 'ECONNREFUSED') {
      return { status: 502, note: '连不上上游:进程崩溃或端口不通' };
    }
    throw e;
  }
  if (response.invalid) {
    return { status: 502, note: '上游回了无效响应:协议错误、返回体损坏' };
  }
  if (response.refused) {
    return { status: 503, note: '上游主动拒收:过载保护或维护中', retryAfter: response.retryAfter };
  }
  if (response.elapsedMs > response.deadlineMs) {
    return { status: 504, note: '上游接了请求但没在时限内答完:慢查询或推理超时' };
  }
  return { status: 200, note: '正常' };
}

const scenarios = {
  上游进程崩溃: () => { const e = new Error('connect refused'); e.code = 'ECONNREFUSED'; throw e; },
  上游返回损坏报文: () => ({ invalid: true }),
  上游过载限流: () => ({ refused: true, retryAfter: 30 }),
  上游推理超时: () => ({ elapsedMs: 3200, deadlineMs: 3000 }),
  上游正常: () => ({ elapsedMs: 120, deadlineMs: 3000 }),
};

for (const [name, upstream] of Object.entries(scenarios)) {
  const r = gatewayCall(upstream);
  const extra = r.retryAfter ? `(Retry-After: ${r.retryAfter}s)` : '';
  console.log(`${name} -> ${r.status}${extra}:${r.note}`);
}

实测输出:

上游进程崩溃 -> 502:连不上上游:进程崩溃或端口不通
上游返回损坏报文 -> 502:上游回了无效响应:协议错误、返回体损坏
上游过载限流 -> 503(Retry-After: 30s):上游主动拒收:过载保护或维护中
上游推理超时 -> 504:上游接了请求但没在时限内答完:慢查询或推理超时
上游正常 -> 200:正常

链路加定位决策图:上方链路——客户端、网关、上游服务依次相连,标注三个状态码都发生在「网关与上游之间」这一段。中部三张卡:502 坏网关——网关收到上游的无效响应或根本连不上(上游崩溃、端口不通、协议错误),责任在上游进程或网络;503 服务不可用——上游活着但主动拒收(过载、限流、维护中),责任在容量;504 网关超时——上游接了请求但没在时限内答完(慢查询、死锁、推理超时),责任在上游性能。底部定位口诀:先看上游活没活(502),活着再看忙不忙(503),不忙再看慢不慢(504) 图 2:三码语义对照与「活没活、忙不忙、慢不慢」的定位路径。

定位口诀与易错点。

口诀一句话:先看上游活没活(502),活着再看忙不忙(503),不忙再看慢不慢(504)。工程落地上再加两步:先看网关日志里的 upstream 状态与耗时,把「网关自身故障」和「上游故障」分开;再按码定责任方,502 找进程与网络,503 找容量与限流配置,504 找性能与依赖。

易错点有三。一是把 504 当成网关自己坏了——网关只是计时员,超时发生在上游。二是混淆 500 与 502:500 是上游应用自己处理失败、明明白白回了个错误;502 是网关认为上游的响应根本无效,或者压根没收到响应。三是重试不分码:503 带 Retry-After 就按它说的等;504 之后重试要想清楚上游那笔慢操作是否已经生效(写操作超时重试可能重复执行);502 通常可以安全重试,但先确认上游没有处于半死不活的抖动期。

这套定位法在 AI 应用里几乎天天用:调用模型 API 报错,502 指向服务商网关与推理集群之间的连接问题,503 是上游容量打满在限流,504 是这次推理太慢超时——告警分组和重试策略都该按这个分。你在生产上被 50X 半夜叫醒过吗?最后定位到哪一层?欢迎分享你的排查故事。