← 路由设计 · 稳定入口与可变目标 / 反向代理 / API 网关 · 一个入口分流到一群后端 待审核 13 / 23

反向代理 / API 网关 · 一个入口分流到一群后端

匹配引擎讲的是「path → 哪个 handler」。把同一件事搬到服务端最前面那一层,就是 reverse proxy / API gateway:外界只看见一个入口(shop.vega.link),网关拿一张 location 规则表决定这条请求该转发给哪个 upstream(后端服务 / 静态 CDN / SPA 前端)。这正是路由这一层dispatch 那一半在生产环境的真实形态。

它和那台平表 matcher 最大的不同在消歧规则:平表是「写在前面的先命中」,而 nginx 这类网关用一套固定优先级——精确 = > 最长前缀 > 正则(按定义顺序),与你把规则写在表里的先后无关。下面输入一条请求,看网关怎样按这套优先级选中一条、再把请求改写后转发给对应 upstream。

那个著名的陷阱:正则会反超前缀。 试试 /static/app.js:前缀 /static/ 明明匹配,命中的却是后面那条正则 ~ \.(js|css|png)$ → CDN。因为 nginx 的优先级里,正则排在普通前缀之前(精确 = 才是最高)。想让前缀「赢回来」,得把它标成优先前缀 ^~——命中后直接采用、根本不再看正则。勾上开关看这一翻转。这是真实运维里极易踩的配置坑。

1 · 选中之后还有一步:改写再转发

匹配只是上半场。命中一条规则后,网关通常还要改写请求再发给 upstream——最常见的是剥掉 location 前缀:外界访问 /api/v2/orders/42,但后端 api-v2 服务自己只认识 /orders/42(它不知道自己被挂在 /api/v2 下)。nginx 里 proxy_pass 末尾带不带斜杠决定剥不剥前缀——又一个一字之差、行为两样的经典点。上面的转发框会显示改写后真正发给后端的 path。

2 · nginx 的 location 表:四种类型、优先级固定

同一张表里,= / ^~ / ~ / 普通前缀四种 location 各有固定档位,与书写先后无关:

server {
  listen 443 ssl;
  server_name shop.vega.link;

  location = /healthz         { proxy_pass http://health-check; }
  location ^~ /static/        { proxy_pass http://spa-files; }
  location ~ \.(js|css|png)$  { proxy_pass http://cdn-static; }
  location /api/v2/           { proxy_pass http://api-v2/; }
  location /api/              { proxy_pass http://api-v1/; }
  location /                  { proxy_pass http://spa-fallback; }
}
location 类型 优先级 转发行为
= /healthz 精确 = 最高,命中即停 原样转发给 health-check
^~ /static/ 优先前缀 ^~ 高于正则 命中即采用、跳过正则
~ \.(js|css|png)$ 正则 ~ 高于普通前缀 按定义顺序取第一个命中
/api/v2/ 普通前缀 取最长匹配 末尾带 /,剥掉 /api/v2 再转发
/api/ 普通前缀 /api/v2/ 短,输给它 同样剥前缀转发
/ 普通前缀 最短,兜底 转发给 spa-fallback

3 · 网关的匹配内核

网关择优不是「第一条命中」,而是按四档固定优先级择优:

  • 精确 =——命中即停,最高。
  • 优先前缀 ^~——最长那条若带 ^~,采用并跳过正则
  • 正则 ~——按定义顺序取第一个命中(注意:排在普通前缀之前)。
  • 普通前缀——回退到最长匹配前缀。
function resolve(rules, path) {
  const exact = rules.find(r => r.type === 'exact' && r.loc === path);
  if (exact) return exact;

  let prefix = null;
  for (const r of rules)
    if (r.type === 'prefix' && path.startsWith(r.loc))
      if (!prefix || r.loc.length > prefix.loc.length) prefix = r;

  if (prefix && prefix.caret) return prefix;

  const rx = rules.find(r => r.type === 'regex' && new RegExp(r.loc).test(path));
  if (rx) return rx;

  return prefix;
}

同一台引擎,换了消歧策略与出口。 网关和前端 router 内核一致——都是「一条 path 跑一张规则表」。差别只在两点。消歧:router 多用「顺序即优先级」或 specificity 打分,网关用「精确 > 最长前缀 > 正则」这套固定档位;出口:router 命中后渲染一个视图,网关命中后把请求转发到另一台服务器(并可改写 path / header)。Envoy、Traefik、Kubernetes Ingress 概念相同,只是规则写法(CRD / 标签 / 注解)各异。

4 · 相关页

  • 本系列 · 路由匹配引擎——网关是这台 path → handler 引擎搬到服务端最前层的形态;那页讲的「顺序即优先级 / specificity / radix tree」是另一套消歧策略,和这里的「= > prefix > regex」对照看。
  • 本系列 · 分享地址统一入口——那页 SPA 接入的三条服务端路由(/api/* 反代 · /s/:code · * fallback),正是这里 location 表的一个最小实例。
  • 本系列 · 加权流量切分——网关选中一个 upstream「组」之后,组里有多个实例——按权重 / 请求头分流到哪个版本,是这条链路的下一环。
  • nginx · location 指令——权威定义:= / ^~ / ~ / 普通前缀四种 location 的匹配优先级与求值顺序——本页演示的就是这套规则。