NGINX 1.31.5 引入了 Predicate Location(谓词 Location),让 location 不再只能根据 URI 匹配,还可以根据变量的值选择路由。
传统 NGINX 的路由核心是 URI:
location /api {
proxy_pass http://backend;
}
而 Predicate Location 可以直接使用变量:
location $post_requests {
proxy_pass http://post_backend;
}
当变量值非空且不等于 0 时,该 Predicate Location 即可命中。
这意味着 NGINX 的路由条件不再局限于 URL,还可以来自请求方法、Header、Cookie、来源 IP,以及其他能够计算为变量的请求特征。
例如,先通过 map 计算条件,再通过 Predicate Location 选择对应的完整处理配置:
map $request_method $post_requests {
POST 1;
default 0;
}
location $post_requests {
proxy_pass http://post_backend;
}
请求体 JSON 路由
更值得关注的是,Predicate Location 还可以结合提前读取请求体和 JSON 变量解析能力,在 Location 匹配阶段直接使用请求体中的业务数据。
例如:
{
"user": {
"params": {
"vip": 1
}
}
}
可以提前读取请求体并提取 JSON 字段:
http {
client_body_early_read on;
json_set $vip $request_body user.params.vip;
server {
location $vip {
return 200 "VIP user\n";
}
}
}
这样,API 网关可以直接根据 JSON 参数、租户、用户类型等业务信息进行路由,而无需额外引入 NJS、Lua 或后端网关完成第一次业务判断。
Location 匹配优先级
Predicate Location 并不会取代原有 URI Location,而是增加了一层新的匹配能力。
同一层 Location 的匹配顺序大致为:
- 精确匹配
location = /path - 查找最长前缀 Location
- 如果前缀 Location 未使用
^~,按配置顺序匹配正则 Location - 正则未命中时,按配置顺序匹配 Predicate Location
多个 Predicate Location 同时满足条件时,按配置顺序优先匹配。 - 都未命中,则回退到最长前缀 Location
没有 Predicate Location 之前
在 Predicate Location 出现之前,NGINX 同样可以根据请求特征进行判断,但条件计算与路由处理通常是分离的。
常见方案主要有以下几种。
1. map + proxy_pass:简单场景最推荐
如果需求只是根据条件选择不同 upstream,其实不需要新的 Location。
例如根据请求方法选择后端:
map $request_method $backend {
POST http://post_backend;
default http://default_backend;
}
server {
location /api/ {
proxy_pass $backend;
}
}
也可以根据 Header 分流:
map $http_x_tenant $backend {
vip http://vip_backend;
default http://normal_backend;
}
这种方式适合:
- 请求方法分流
- Header 分流
- Cookie 分流
- IP 分流
- 灰度发布
- 简单租户路由
这是传统 NGINX 中最常见、也最干净的方案,但它有一个明显特点:条件决定的是变量值,而不是完整的 Location 配置块。
如果只是切换 upstream,非常合适;如果不同请求需要完全不同的限流、日志、缓存、超时和鉴权策略,配置会逐渐碎片化。
例如:
map
├── backend
├── timeout
├── cache
├── rate
└── log
业务策略越复杂,需要维护的变量越多。
2. if:简单判断可以使用
例如:
location /api/ {
if ($request_method = POST) {
proxy_pass http://post_backend;
}
proxy_pass http://default_backend;
}
问题在于,NGINX 的 if 并不是普通编程语言中的流程控制语句,而是 Rewrite 模块的一部分。
因此社区长期流传:If is evil.
简单的 Rewrite、return 等场景通常没有问题,但不建议使用 if 构建复杂的业务流程和路由逻辑,否则可能出现难以理解的配置行为。
3. 内部跳转:间接选择不同处理 Location
传统 NGINX 也可以通过:
try_fileserror_pagerewrite- 命名 Location
- 内部重定向
等机制间接进入不同的处理 Location。
这种方案本质上可以实现“条件决定处理配置”,但配置通常比较绕。
主要问题包括:
- 条件判断与跳转逻辑分散
- 不同指令支持的跳转方式不同
- 内部重定向可能改变 URI
- 配置可读性和维护成本较高
因此更适合已有内部跳转需求,而不是单纯为了实现条件路由。
4. NJS / Lua:复杂业务逻辑
如果条件涉及复杂计算,例如:
- 多个 Header
- Cookie
- IP 库
- JSON Body
- 数据库
- Redis
- 动态规则
以前通常需要引入脚本。
使用 NJS:
location /api/ {
js_content route_handler;
}
或者使用 OpenResty 的 Lua:
location /api/ {
content_by_lua_block {
-- 判断条件
-- 选择处理逻辑
}
}
优点是灵活,几乎可以实现任意逻辑。
缺点也比较明显:
- 增加运行时依赖
- 配置不再是纯 NGINX
- 调试复杂度提高
- Lua 通常意味着进入 OpenResty 生态
- 脚本逻辑可能成为新的性能和维护边界
5. 根据 JSON 请求体路由:以前最麻烦
例如请求:
{
"user": {
"params": {
"vip": 1
}
}
}
希望根据:
user.params.vip
决定请求进入哪个后端。
传统 NGINX 的处理顺序通常是:
请求进入
↓
Location 匹配
↓
读取 Request Body
↓
内容处理
也就是说:Location 选择通常发生在请求体读取之前。
因此,标准 NGINX 很难直接根据 JSON Body 选择 Location。
以前通常只能选择:
方案 A:NJS 解析 JSON
方案 B:Lua / OpenResty
方案 C:代理到 API Gateway
方案 D:后端应用自己判断
这意味着第一次业务路由需要离开 NGINX 的原生配置体系。
Predicate Location 示例
下面通过一个租户路由场景看看它真正解决了什么问题。
假设所有请求都访问:
POST /api/send
但 VIP 租户需要:
- 独立限流
- 独立日志
- 独立超时
- 独立 Header
- 独立 Backend
普通租户则使用默认策略。
首先计算租户条件:
map $http_x_tenant_type $vip_tenant {
vip 1;
default 0;
}
然后直接使用 Predicate Location:
server {
# VIP 请求
location $vip_tenant {
limit_req zone=vip_limit;
access_log /var/log/nginx/vip.log;
proxy_connect_timeout 1s;
proxy_set_header X-Service-Tier VIP;
proxy_pass http://vip_backend;
}
# 普通请求
location /api/send {
limit_req zone=normal_limit;
access_log /var/log/nginx/normal.log;
proxy_connect_timeout 5s;
proxy_pass http://normal_backend;
}
}
请求:
POST /api/send
X-Tenant-Type: vip
处理流程:
Request
↓
URI:/api/send
↓
计算 $vip_tenant
↓
vip → 1
↓
Predicate Location 命中
↓
VIP 独立处理策略
├── VIP 限流
├── VIP 日志
├── VIP 超时
├── VIP Header
└── VIP Backend
Predicate Location 真正改变了什么?
Predicate Location 最大的价值并不是让 NGINX “终于可以判断条件”。此前,map、if、NJS、Lua 都可以完成条件计算。
真正的变化在于:以前可以计算条件,但条件通常只能修改变量,再通过 proxy_pass 等指令影响后续处理;Predicate Location 则让条件直接决定请求进入哪一个完整的 Location 配置块。
传统模式通常是:
请求 → URI Location → 计算条件 → 修改变量 → 同一个 Location 继续处理
例如,map + proxy_pass 可以根据请求方法、Header 或 Cookie 选择不同 upstream,这种方式简单高效,适合“请求发给谁”不同的场景。
Predicate Location 则进一步把条件判断变成路由入口:
请求特征 → map / JSON 解析 → Predicate Location → 对应处理策略
命中不同 Predicate Location 后,请求可以直接进入不同的 NGINX 配置,包括独立的限流、鉴权、缓存、日志、超时和 Upstream。
因此可以简单理解为:
map解决“条件怎么计算”,proxy_pass解决“请求发给谁”,Predicate Location 解决“满足条件后应该进入哪套完整的 NGINX 处理策略”。
如果只是切换后端,传统的 map + proxy_pass 已经足够;如果需要根据业务条件切换一整套 NGINX 处理策略,Predicate Location 的价值就真正体现出来了。
从这个角度看,NGINX 正从传统的“按地址路由”,进一步发展为“按请求事实路由”。