1. 开篇:当Lua遇见Nginx变量

作为OpenResty生态的核心要素,Lua与Nginx的变量系统结合堪称网关开发的瑞士军刀。本文将从实际案例出发,带您掌握在请求处理各个阶段操作变量的"正确姿势"。


2. 内置变量的妙用指南

Nginx原生提供的变量如同乐高积木的基础模块,这里以OpenResty 1.21.4技术栈为例演示常用场景:

2.1 请求属性获取

location /debug {
    access_by_lua_block {
        -- 输出完整请求路径(含参数)
        ngx.log(ngx.INFO, "请求URI:", ngx.var.request_uri)
        
        -- 解析客户端连接属性
        local client_ip = ngx.var.remote_addr
        local keepalive = ngx.var.keepalive_requests
        ngx.header["X-Client-IP"] = client_ip
    }
}

2.2 流量管控实战

location /api {
    access_by_lua_block {
        -- 读取请求速率计数器
        local req_count = tonumber(ngx.var.connections_active) or 0
        
        if req_count > 100 then
            ngx.exit(503) -- 触发过载保护
        end
        
        -- 动态设置缓冲大小
        ngx.var.limit_rate = "100k" -- 限速到100KB/s
    }
}

3. 自定义变量的进阶玩法

通过Lua语言扩展变量系统,可以实现传统配置难以企及的灵活逻辑。

3.1 变量声明三步曲

server {
    set_by_lua_block $dynamic_var {
        -- 初始化阶段创建变量
        local math = require("math")
        return math.random(1000) -- 生成随机标识
    }

    location /custom {
        rewrite_by_lua_block {
            -- 动态修改变量值
            ngx.var.dynamic_var = "user_" .. ngx.var.http_user_id
            
            -- 使用共享字典保持状态
            local shdict = ngx.shared.my_cache
            shdict:set("last_request", ngx.time())
        }

        content_by_lua_block {
            -- 输出复合变量
            ngx.say("当前会话标识:", ngx.var.dynamic_var)
        }
    }
}

3.2 变量的生命周期掌控

通过不同阶段的变量操作实现流水线处理:

location /flow {
    set $phase "init"; -- 配置阶段初始化

    access_by_lua_block {
        ngx.var.phase = "auth"  -- 访问控制阶段
        local auth_code = validate_token()
        ngx.var.auth_code = auth_code
    }

    header_filter_by_lua_block {
        ngx.var.phase = "header" -- 响应头处理阶段
        ngx.header["X-Phase"] = ngx.var.phase
    }
}

4. 与关联技术的协同工作

结合Redis实现带缓存的用户状态管理:

location /profile {
    access_by_lua_block {
        local redis = require("resty.redis")
        local red = redis:new()
        
        -- 从变量获取用户ID
        local uid = ngx.var.cookie_user_id
        
        -- 查询Redis缓存
        local res, err = red:get("user:" .. uid)
        if not res then
            ngx.log(ngx.ERR, "Redis查询失败:", err)
            return
        end
        
        -- 注入变量供后续使用
        ngx.var.user_profile = res
    }

    content_by_lua_block {
        ngx.say("用户资料:", ngx.var.user_profile)
    }
}

5. 性能优化的关键要点

5.1 存储方式的三级火箭

-- 方式1:直接操作Nginx变量(最慢但兼容性好)
ngx.var.custom_value = "data"

-- 方式2:使用ngx.ctx上下文(速度提升3倍)
ngx.ctx.cached_data = expensive_calculation()

-- 方式3:模块级变量(最快但需注意生命周期)
local _cache = {}
function get_cached_value(key)
    return _cache[key] or fetch_from_db(key)
end

5.2 内存管理原则

location /memcheck {
    content_by_lua_block {
        -- 错误示范:大对象存储在变量
        local big_data = string.rep("A", 1024000) -- 1MB数据
        ngx.var.big_var = big_data  -- 触发内存暴涨
        
        -- 正确做法:使用共享字典
        local shdict = ngx.shared.temp_storage
        shdict:set("temp_key", big_data, 60)  -- 限制存储时间
    }
}

6. 实战中的避坑指南

6.1 变量作用域陷阱

location /scope {
    set_by_lua_block $counter {
        -- 此代码仅在配置加载时执行一次!
        local count = 0
        count = count + 1
        return count
    }

    content_by_lua_block {
        ngx.say("当前计数:", ngx.var.counter) -- 永远输出1
    }
}

改用共享字典实现真正计数:

set_by_lua_block $counter {
    local shdict = ngx.shared.counter_store
    return shdict:incr("global_counter", 1, 0)
}

6.2 特殊字符处理

location /escape {
    content_by_lua_block {
        local raw_value = "含有空格&特殊字符"
        ngx.var.encoded_value = ngx.escape_uri(raw_value)
        -- 输出:%E5%90%AB%E6%9C%89%E7%A9%BA%E6%A0%BC%26%E7%89%B9%E6%AE%8A
    }
}

7. 总结与最佳实践

应用场景

  • 动态路由决策(基于变量值的A/B测试)
  • 请求属性透传(跨阶段数据传递)
  • 实时监控指标(流量统计与限流)

技术选型建议

  • 优先使用内置变量完成基础功能
  • 关键路径避免高频变量操作
  • 复杂逻辑建议使用ngx.ctx代替临时变量

注意事项检查表

  • [ ] 是否在热代码路径操作变量?
  • [ ] 变量命名是否可能冲突?
  • [ ] 超大值是否考虑存储方式?
  • [ ] 是否需要URI编解码处理?