503 究竟由哪一层抛出
openresty 是 Nginx 加 Lua 的运行环境,页面上出现 503,并不等于后端程序一定崩了。它通常来自四个位置:本机 limit_req 或 limit_conn 触发了限流;Lua 脚本里用 ngx.exit(503) 主动拒绝;反向代理时上游服务自己返回了 503;健康检查模块把某台后端标记为不可用,proxy_pass 找不到可用节点。判断的第一步,是看本次请求的 access_log 中是否记录了 $upstream_addr 与 $upstream_status 这两个字段。

如果 $upstream_status 明确写着 503,说明请求确实打到了后端,方向应转向应用自身的过载保护、重启过程或它前面的网关;反之,若 upstream 相关字段为空,压力就落在 openresty 自身,需要检查限流配置、Lua 逻辑,以及 worker_connections 与 worker 进程数是否够用。只凭浏览器上那行提示就下结论,往往会把排查方向带偏,浪费大量时间在无关组件上。
为什么同一套配置会在 502 与 503 之间摇摆
这两个状态码的语义并不一样。502 表示作为网关的 openresty 从上游收到了无效响应,或连接被直接拒绝,常见诱因是进程退出、端口未监听、协议不匹配;503 更强调“暂时不可用”,多见于上游过载、正在重启、连接池耗尽、被限流或被主动摘除。上游返回 503 属于可预期的软失败,而 502 更像连接层面的硬失败,两者的处置手法并不通用。
另一个容易被忽略的点是 proxy_next_upstream 的默认值。openresty 默认只在 error、timeout、invalid_header 等情形下尝试下一台后端,http_503 并不在默认重试列表里。也就是说,即便 upstream 块中还有健康节点,收到 503 后默认也不会自动转移,请求会原样把 503 交给客户端。若业务允许且请求幂等,可以把 http_503 加入重试条件,再用 proxy_next_upstream_tries 限制次数,避免放大后端压力。
从响应头与日志里怎么快速定位来源
先看响应头。若响应携带 Retry-After,多半是上游或边缘层主动返回的 503,含义是稍后再试;若只带 openresty 自身的 Server 标识、没有任何上游特征字段,则更可能是本机规则拦截。接着翻 error.log,限流类 503 通常会留下 limiting requests 或 limiting connections 字样,并带着被限制的 zone 名与客户端地址,这条信息能直接把范围缩到某一条 limit_req_zone 或 limit_conn_zone。
access.log 一侧,要保证日志格式里包含 $upstream_addr、$upstream_status、$upstream_response_time、$request_time 与 $status。对比 $request_time 与 $upstream_response_time:前者远大于后者,说明耗时基本发生在排队与内部处理;两者接近且都不大,则更像上游快速拒绝。把这些字段做成聚合统计,就能看出 503 是集中在某台后端、某个时段,还是某个特定接口。
限流、并发与后端摘除之间有什么关联
limit_req 与 limit_conn 的默认返回码正是 503,这也是不少人“没改代码却突然 503”的原因。limit_req 以漏桶算法控制速率,burst 与 nodelay 决定突发流量是排队还是被立即拒绝;limit_conn 限制同一键值下的并发连接数,长连接和慢接口场景更容易撞线。两套规则都按 zone 共享内存统计,zone 容量不足时会记录失败,实际行为可能与预期不符。
后端摘除是另一条路径。upstream 块里的 max_fails 与 fail_timeout 决定失败多少次后把节点临时踢出,若同时启用了健康检查模块,摘除会更及时。当所有节点都被摘除或处于失败冷却期,openresty 没有可用上游,只能返回 503。因此把限流阈值、失败判定条件与健康检查间隔放在一起核对,才能解释清楚为什么某些时段 503 会集中出现。