OpenResty 不应直接用系统包管理器安装,因其版本严重滞后,易致高并发下内存泄漏和OOM;应下载官方最新包安装,并注意lua_code_cache、access_by_lua_block与content_by_lua_block分工及lua_shared_dict的TTL设置。

OpenResty 安装为什么不能直接用系统包管理器?
大多数 Linux 发行版的 apt 或 yum 仓库里的 OpenResty 版本严重滞后(比如 Ubuntu 22.04 默认是 1.19.x),而新版本修复了关键的 LuaJIT 内存泄漏、ngx.timer.at 精度问题,还支持 lua-resty-http v0.16+ 的连接池自动回收。直接安装旧版,上线后容易在高并发下出现 worker 进程 RSS 持续上涨甚至 OOM。
实操建议:
- 从 官方 Linux Packages 页面 下载对应发行版的最新
.deb或.rpm包(如openresty-1.21.4.2-1~jammy.amd64.deb),避免源码编译带来的依赖混乱 - 安装前先卸载系统自带的
nginx或openresty:运行sudo apt remove nginx* openresty(Ubuntu/Debian)或sudo yum remove nginx* openresty(CentOS/RHEL) - 安装后检查路径:默认安装到
/usr/local/openresty,nginx可执行文件实际是/usr/local/openresty/nginx/sbin/nginx
nginx.conf 里 lua_code_cache off 仅用于开发调试
设成 off 确实能让每次请求都重载 Lua 文件,方便改完代码立刻看到效果,但会彻底破坏 OpenResty 的性能根基——Lua 模块缓存和字节码缓存。线上开启它,QPS 会跌 60% 以上,且 require 调用变成磁盘 I/O,容易触发 too many open files 错误。
实操建议:
- 开发环境可在
http块中加:lua_code_cache off;,但必须限定在127.0.0.1或内网 IP 访问的 server 块里,禁止暴露到公网 - 生产环境必须保持
on,热更新靠kill -HUP $(cat /usr/local/openresty/nginx/logs/nginx.pid)或配合lua-resty-upstream-healthcheck实现平滑 reload - 若需局部调试单个 Lua 文件,可用
package.loaded["mymodule"] = nil+require("mymodule")手动重载,比全局关 cache 更安全
access_by_lua_block 和 content_by_lua_block 的分工陷阱
很多人把鉴权、限流、参数校验全塞进 content_by_lua_block,结果发现日志里大量 500 错误却没报错信息——因为 content_by_lua_block 在输出阶段才执行,一旦出错,header 已经发送,Nginx 只能返回空响应或默认错误页;而 access_by_lua_block 在 access 阶段运行,此时还能用 ngx.exit(ngx.HTTP_FORBIDDEN) 精准控制状态码和响应体。
实操建议:
- 身份校验、IP 黑白名单、频率限制(
resty.limit.count)、请求头合法性检查,一律放access_by_lua_block -
content_by_lua_block只做业务逻辑处理和响应生成,比如调用后端 API、模板渲染、JSON 构造 - 注意:两个 block 共享同一个 Lua VM,但
ngx.ctx是请求级隔离的,跨 block 传数据要用ngx.ctx.foo = "bar",别用全局变量
lua_shared_dict 内存泄漏的典型表现与规避方式
用 lua_shared_dict 存 token 缓存或计数器很常见,但若忘记设置过期时间或未清理旧 key,内存占用会持续增长,nginx -t 检查不出问题,直到 worker process is shutting down on signal 日志频繁出现,或 curl http://127.0.0.1:8080/nginx_status 显示 Shared memory zones: ... used: 98%。
实操建议:
- 定义 dict 时务必指定大小:如
lua_shared_dict auth_cache 128m;,不要写1g图省事,Linux 内存分配不是按需增长的 - 写入必须带 TTL:
auth_cache:set("token_abc", {user_id=123}, 3600),第三个参数是秒数,不传则永不过期 - 定期清理用不到的 key:可起一个
ngx.timer.every(60, function() ... end)定时器扫描并delete,但注意别在 timer 回调里做阻塞操作(如同步 HTTP 请求)
真正难的不是让脚本跑起来,而是让几百个并发请求持续跑一周后,内存不涨、连接不堆积、日志不刷屏。这些细节没压到线上流量里,根本看不出问题。


















