一、为什么需要玩转Nginx变量

在日常的Web服务配置中,我们经常遇到需要根据不同条件返回不同内容的情况。比如根据用户设备跳转不同页面、根据请求参数返回不同数据,这时候硬编码配置就显得力不从心了。而Nginx的变量系统就像乐高积木,能让我们灵活组装各种配置方案。

举个例子,电商网站需要根据用户所在城市展示不同促销信息。传统做法可能要写多个location块,但用变量就能轻松实现:

# 技术栈:Nginx原生配置
server {
    listen 80;
    
    # 从请求头获取城市信息
    set $city $http_x_city;
    
    location /promo {
        # 根据城市变量选择不同响应
        if ($city = "shanghai") {
            return 200 "上海专享5折优惠";
        }
        if ($city = "beijing") {
            return 200 "北京满100减30";
        }
        return 200 "全国通用9折券";
    }
}

二、Nginx变量类型全解析

Nginx的变量家族主要分为三大类,就像不同的工具箱:

  1. 内置变量:Nginx自带的"瑞士军刀",比如:

    • $host:当前请求的主机名
    • $remote_addr:客户端IP地址
    • $request_uri:完整的原始请求URI
  2. 自定义变量:自己打造的"定制工具",通过set指令创建:

    set $api_version "v2";  # 定义API版本变量
    
  3. 模块变量:各种模块扩展的"专业工具",比如:

    • $upstream_http_*:反向代理获取的响应头
    • $sent_http_*:发送给客户端的响应头

这里有个实际案例,记录请求处理时间:

# 技术栈:Nginx原生配置
server {
    listen 80;
    
    location / {
        # 记录请求开始时间
        set $start_time $msec;
        
        # 代理到后端服务
        proxy_pass http://backend;
        
        # 计算处理耗时
        set $response_time $msec;
        set $process_time $response_time-$start_time;
        
        # 添加响应头
        add_header X-Response-Time "$process_time";
    }
}

三、变量使用的高级技巧

变量真正的威力在于组合使用,就像编程中的函数组合。这里分享几个实用技巧:

  1. 变量默认值设置:
# 技术栈:Nginx原生配置
set $mobile_redirect "0";
if ($http_user_agent ~* "(android|iphone)") {
    set $mobile_redirect "1";
}
  1. 多条件判断组合:
# 技术栈:Nginx原生配置
location / {
    set $maintenance "0";
    set $internal "0";
    
    # 内部IP检测
    if ($remote_addr ~ "192.168.") {
        set $internal "1";
    }
    
    # 维护模式逻辑
    if (-f /var/run/maintenance.flag) {
        set $maintenance "1";
    }
    
    # 组合条件判断
    if ($maintenance = "1" && $internal = "0") {
        return 503 "服务维护中,请稍后再试";
    }
}
  1. 与map指令配合实现智能路由:
# 技术栈:Nginx原生配置
map $http_accept_language $lang {
    default en;
    ~zh-CN zh;
    ~zh-TW zh;
}

server {
    listen 80;
    
    location / {
        # 根据语言变量选择不同根目录
        root /var/www/$lang;
    }
}

四、实战:动态反向代理配置

让我们看个完整的动态反向代理案例,根据请求路径自动路由到不同后端:

# 技术栈:Nginx原生配置
# 定义服务映射
map $request_uri $backend {
    ~^/api/v1/  backend_v1;
    ~^/api/v2/  backend_v2;
    default     backend_default;
}

# 上游服务定义
upstream backend_v1 {
    server 192.168.1.10:8000;
}

upstream backend_v2 {
    server 192.168.1.20:8000;
}

upstream backend_default {
    server 192.168.1.30:8000;
}

server {
    listen 80;
    
    location /api/ {
        # 使用映射的变量动态代理
        proxy_pass http://$backend;
        
        # 传递原始请求信息
        proxy_set_header X-Original-URI $request_uri;
    }
}

五、性能优化与陷阱规避

虽然变量很强大,但使用不当也会踩坑:

  1. 变量缓存问题:某些内置变量如$args在请求过程中会变化
  2. if指令的局限性:if内部创建的变量作用域很特殊
  3. 正则表达式性能:复杂的正则会影响处理速度

这里有个优化过的AB测试方案:

# 技术栈:Nginx原生配置
# 使用split_clients进行AB测试分流
split_clients "${remote_addr}${http_user_agent}" $ab_group {
    50%     group_a;
    50%     group_b;
}

server {
    listen 80;
    
    location / {
        # 根据分组设置不同响应
        if ($ab_group = "group_a") {
            proxy_pass http://backend_a;
        }
        if ($ab_group = "group_b") {
            proxy_pass http://backend_b;
        }
    }
}

六、与Lua脚本的梦幻联动

当Nginx原生变量不够用时,可以召唤OpenResty的Lua脚本:

# 技术栈:OpenResty
server {
    listen 80;
    
    location /dynamic {
        # 使用Lua脚本设置复杂变量
        access_by_lua_block {
            -- 根据多个条件计算动态值
            local user_agent = ngx.var.http_user_agent
            local ip = ngx.var.remote_addr
            
            if string.find(user_agent, "Mobile") then
                ngx.var.is_mobile = "1"
            else
                ngx.var.is_mobile = "0"
            end
            
            -- 设置自定义变量
            ngx.var.cache_key = ip .. "_" .. user_agent
        }
        
        # 使用变量
        proxy_cache_key "$cache_key";
        proxy_pass http://backend;
    }
}

七、最佳实践总结

经过多年实战,我总结了这些经验法则:

  1. 变量命名要有意义,比如$api_version$v更好
  2. 复杂逻辑尽量用map指令代替多个if
  3. 频繁访问的变量可以考虑用Lua预先计算
  4. 调试时善用$echo模块打印变量值
  5. 文档化你的变量使用,方便团队协作

最后分享一个生产环境在用的完整示例:

# 技术栈:Nginx + OpenResty
# 全局变量定义
map $http_x_request_id $request_id {
    default $http_x_request_id;
    ""      $request_id;
}

# 用户分组逻辑
map $cookie_user_group $user_group {
    default $cookie_user_group;
    ""      "guest";
}

server {
    listen 80;
    
    # 初始化变量
    set $start_time $msec;
    set $request_id $request_id;
    
    location / {
        access_by_lua_block {
            -- 补充业务逻辑变量
            ngx.var.cache_version = "20230601"
        }
        
        # 动态代理配置
        proxy_pass http://backend/$user_group;
        proxy_set_header X-Request-ID $request_id;
        proxy_set_header X-Start-Time $start_time;
    }
    
    location ~ /logs/(.*) {
        # 安全校验变量
        set $log_file $1;
        if ($remote_addr != "192.168.1.100") {
            return 403;
        }
        
        # 使用变量动态指定文件
        alias /var/log/$log_file;
    }
}