1. 脚本是如何寻找的?(require 机制)
在 OpenResty 中,
require 函数的行为和标准 Lua 是一样的,它依赖于 package.path 来寻找文件。但是,在 Nginx 环境下,我们需要通过 Nginx 配置文件来设置这个路径。配置指令:lua_package_path
你需要在
nginx.conf 的 http {} 块中配置它:http {
# ?.lua 表示用 require 的模块名替换问号
# ;; 表示保留 OpenResty 默认的搜索路径(强烈建议加上)
lua_package_path "/path/to/your/project/lib/?.lua;/path/to/your/project/app/?.lua;;";
server {
...
}
}寻找流程:
假设你写了
local tool = require("utils.common"),且配置如上:1. Lua 会尝试查找:/path/to/your/project/lib/utils/common.lua
2. 如果没找到,尝试:/path/to/your/project/app/utils/common.lua
3. 如果还没找到,去 OpenResty 默认路径(如 /usr/local/openresty/lualib/...)查找。
**项目组织建议**:
通常我们会这样组织目录:
my_project/
├── conf/
│ └── nginx.conf
├── logs/
└── src/
├── access.lua (入口脚本)
├── content.lua
└── modules/ (放可以用 require 复用的模块)
├── redis_tool.lua
└── user_logic.lua在
nginx.conf 里指向 src/modules/?.lua 即可。---
2. 脚本是一次性加载的吗?(代码缓存)
**答案取决于
lua_code_cache 开关。**生产环境:lua_code_cache on; (默认是开启的)
- 机制:当 Nginx Worker 启动或第一次处理请求时,它会把 .lua 文件加载到内存,并编译成 LuaJIT Bytecode(字节码)。
- 结果:脚本只加载一次。
- require 的行为:Lua 有一个全局表 package.loaded。当第一次 require("module") 时,模块代码执行,返回值被存在 package.loaded["module"] 中。第二次 require 时,直接返回表里的缓存,不会再次读取磁盘文件,也不会再次运行模块里的代码。
- 性能:极高。
- 坑:如果你修改了 Lua 文件,必须 reload 或重启 Nginx 才会生效。
开发环境:lua_code_cache off;
- 机制:完全禁用缓存。
- 结果:每一个请求进来,OpenResty 都会重新创建一个全新的 Lua VM(或者清理当前 VM),并重新读取磁盘上的文件。
- 性能:很差(不能用于生产)。
- 优点:改了代码刷新浏览器立刻生效,调试方便。
---
3. 变量的生命周期与可见性(核心考点)
这是新手最容易踩坑的地方(导致数据串号)。我们需要区分三种变量:**全局变量**、**模块级变量**、**函数级局部变量**。
**前提**:Nginx 是多进程模型,每个 Worker 进程有一个独立的 Lua VM。**Worker 之间的变量是隔离的**,但在**同一个 Worker 内部处理的所有请求**是共享这个 Lua VM 的。
A. 全局变量(千万别用!)
如果你在脚本里这样写(没有
local):-- 这是一个全局变量,挂载在 _G 表下
current_user_id = 1001- 后果:因为 lua_code_cache on,Lua VM 是常驻内存的。
- 场景:
请求 A 进来,执行 current_user_id = 1001。
- 请求 B 进来(同一个 Worker 处理),由于代码逻辑分支没走到赋值语句,直接读取 current_user_id。
- 恐怖的事情发生了:请求 B 读到了 1001!
- 结论:严禁使用全局变量。OpenResty 默认开启了 lua-releng 工具或编译选项来检测并报错全局变量的使用。
B. 模块级变量(Upvalue)
这是指在模块文件中,写在函数外面的
local 变量。-- src/modules/counter.lua
local _M = {}
-- 这是一个模块级局部变量
local count = 0
function _M.add()
count = count + 1
return count
end
return _M- 生命周期:Worker 级别。因为 require 只执行一次,这个 count 变量会一直存在于内存中,直到 Nginx 重启。
- 可见性:同一个 Worker 处理的所有请求,都会共享这个 count。
- 效果:
请求 A 调 add() -> count 变成 1
- 请求 B 调 add() -> count 变成 2
**用途**:适合做只读配置缓存、连接池对象等。不适合存请求相关的数据(如用户 ID),否则也是数据污染。
C. 函数级局部变量(推荐)
-- content_by_lua
local function handle()
-- 这是一个函数级局部变量
local user_id = ngx.var.arg_uid
ngx.say(user_id)
end
handle()- 生命周期:Request 级别。函数执行完,变量就会离开作用域被 GC(垃圾回收)。
- 可见性:只有当前请求可见。
- 结论:这是保存业务数据唯一正确的地方。
---
总结与最佳实践
1. 加载机制:生产环境开启 lua_code_cache on,Lua 脚本被编译并常驻内存,require 的模块只初始化一次。
2. 变量作用域原则:
- 所有变量必须加 local。
- 请求级数据(User ID, 参数):必须定义在函数内部(local var)。
- 跨请求共享数据(Worker 级):可以用模块顶层的 local var(慎用,注意并发竞争,虽然 Lua 是单线程执行,但逻辑上可能混淆)。
- 跨 Worker 共享数据:必须使用 lua_shared_dict(共享内存)或 Redis。
3. 如何在请求的不同阶段传值?
- 比如在 access 阶段解析了 Token,想传给 content 阶段。
- 不要用模块变量。
- 使用 ngx.ctx:这是 OpenResty 专门提供的表,生命周期等同于当前请求。
-- access_by_lua
ngx.ctx.user_id = 123
-- content_by_lua
ngx.say(ngx.ctx.user_id) -- 输出 123理解了**"Worker 进程长久存活,Lua VM 不销毁"**这一点,你就理解了 OpenResty 变量管理的精髓。