一、为什么需要玩转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的变量家族主要分为三大类,就像不同的工具箱:
内置变量:Nginx自带的"瑞士军刀",比如:
$host:当前请求的主机名$remote_addr:客户端IP地址$request_uri:完整的原始请求URI
自定义变量:自己打造的"定制工具",通过set指令创建:
set $api_version "v2"; # 定义API版本变量模块变量:各种模块扩展的"专业工具",比如:
$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";
}
}
三、变量使用的高级技巧
变量真正的威力在于组合使用,就像编程中的函数组合。这里分享几个实用技巧:
- 变量默认值设置:
# 技术栈:Nginx原生配置
set $mobile_redirect "0";
if ($http_user_agent ~* "(android|iphone)") {
set $mobile_redirect "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 "服务维护中,请稍后再试";
}
}
- 与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;
}
}
五、性能优化与陷阱规避
虽然变量很强大,但使用不当也会踩坑:
- 变量缓存问题:某些内置变量如
$args在请求过程中会变化 - if指令的局限性:if内部创建的变量作用域很特殊
- 正则表达式性能:复杂的正则会影响处理速度
这里有个优化过的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;
}
}
七、最佳实践总结
经过多年实战,我总结了这些经验法则:
- 变量命名要有意义,比如
$api_version比$v更好 - 复杂逻辑尽量用map指令代替多个if
- 频繁访问的变量可以考虑用Lua预先计算
- 调试时善用
$echo模块打印变量值 - 文档化你的变量使用,方便团队协作
最后分享一个生产环境在用的完整示例:
# 技术栈: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;
}
}
评论