一、前言
OpenResty是一个基于 Nginx 与 Lua 的开源高性能 Web 平台,OpenResty团队为Nginx开发了Lua模块,使得开发者/运维可以使用Lua为OpenResty开发扩展,或者为Nginx定制功能,另外OpenResty团队也内置了很多Lua扩展(JWT、MySQL、Redis等),可以通过OpenResty高效率的开发高性能Web服务1
1、本文主要内容
- OpenResty核心机制介绍
- Lua 基础语法示例
- OpenResty核心API 示例
- OpenResty共享内存及缓存示例
2、本文环境信息
| 工具/环境 | 版本说明 | 适用版本 |
|---|---|---|
| OS | macOS 15.7 | *(macOS/Windows/Linux) |
| Docker | Docker CE 28.1.1 | *(默认所有Docker版本可用) |
| OpenResty | 1.27.1.2 | 1.17+ |
3、前置准备
1、安装OpenResty
https://ken.io/note/openresty-install-setup-linux
https://ken.io/note/openresty-install-setup-macos
https://ken.io/note/openresty-install-setup-windows
https://ken.io/note/openresty-install-setup-docker
二、OpenResty 的核心机制
OpenResty 将 Lua 虚拟机嵌入 Nginx 内核,在标准 HTTP 请求处理流程中注入了多个 Lua 执行钩子(hooks)。通过这些指令,开发者可以在不修改 C 代码的前提下,用 Lua 实现动态路由、鉴权、限流、日志增强等高级功能。
1、Nginx 的核心阶段
Nginx 处理每个请求时,会依次经过以下 11 个阶段
| 阶段序号 | 阶段名称 | 说明 |
|---|---|---|
| 1 | post-read | 读取请求头后,最早阶段(通常用于 realip 模块) |
| 2 | server-rewrite | server 块内的 rewrite |
| 3 | find-config | 根据 URI 查找 location(不可干预) |
| 4 | rewrite | 重点:URL 改写、参数预处理 |
| 5 | post-rewrite | rewrite 后检查(如 internal redirect) |
| 6 | pre-access | 访问控制前准备(limit_conn 等) |
| 7 | access | 重点:权限/认证逻辑 |
| 8 | post-access | access 后处理(limit_req 等) |
| 9 | try-files | try_files 指令执行 |
| 10 | content | 重点:生成响应体 |
| 11 | log | 重点:自定义日志、指标上报 |
2、OpenResty 的扩展阶段
| 阶段序号 | 所属阶段 | 指令 | 典型用途 |
|---|---|---|---|
| 1 | rewrite 阶段 | rewrite_by_lua* | URL 重写、参数校验、内部跳转 |
| 2 | access 阶段 | access_by_lua* | 权限控制、IP 黑名单、Token 验证 |
| 3 | content 阶段 | content_by_lua* | 动态生成响应内容(替代 proxy_pass / fastcgi) |
| 4 | log 阶段 | log_by_lua* | 自定义日志记录、埋点统计、性能分析 |
✅ 后缀
*表示支持_block(内联 Lua 代码)和_file(引用外部.lua文件)两种形式,例如:
content_by_lua_block { ngx.say("Hello") }
# 或
content_by_lua_file /path/to/handler.lua;
⚠️ 注意:Lua 代码 不能阻塞 Nginx 事件循环(避免
os.execute()、长 sleep 等),否则会拖垮整个 worker 进程。
3、指令优先级与执行顺序
在同一阶段内,原生 Nginx 指令优先于 Lua 指令执行。例如在 rewrite 阶段:
server {
listen 8008;
server_name localhost;
location / {
default_type 'text/plain';
content_by_lua_block {
local args = ngx.req.get_uri_args()
local name = args["name"] or "OpenResty test by ken.io"
ngx.header["X-Header"] = "ken.io"
ngx.say("Hello, " .. name .. "!")
}
}
location /test {
default_type 'text/plain';
# 1. 原生 set
set $flag "flag from nginx -- ken.io";
set $flag_before "";
# 2. 原生 rewrite
rewrite ^/test$ /ken_test break;
# 3. Lua rewrite
rewrite_by_lua_block {
ngx.var.flag_before = ngx.var.flag
ngx.var.flag = "flag from OpenResty -- ken.io"
}
# 4. 原生 set
set $flag "flag from nginx after -- ken.io";
content_by_lua_block {
ngx.say("Nginx $flag_before: ", ngx.var.flag_before)
ngx.say("Nginx $flag: ", ngx.var.flag)
ngx.say("Current URI: ", ngx.var.uri)
}
}
}
使用 curl 命令,或者浏览器访问:http://localhost:8008 进行测试
# 使用curl访问
curl http://localhost:8008
# 输出示例
Nginx $flag_before: flag from nginx -- ken.io
Nginx $flag: flag from OpenResty -- ken.io
Current URI: /ken_test
根据运行结果可以得出,步骤 4 并未执行,且步骤 3 会覆盖步骤 1 的 flag 赋值
所以:
三、Lua 基础语法
1. 变量、表(table)、函数
-- 变量(全局/局部)
local name = "OpenResty" -- 推荐 always use 'local'
-- 表(table = 数组 + 字典)
local user = {
id = 1001,
tags = {"admin", "vip"}
}
ngx.say(user.id) --> 1001
ngx.say(#user.tags) --> 2
-- 函数
local function greet(n)
return "Hello, " .. n
end
ngx.say(greet(name))
2. 模块化(require 机制)
创建 lib/utils.lua:
local _M = {}
function _M.add(a, b)
return a + b
end
return _M
在 handler 中使用:
local utils = require "lib.utils"
ngx.say(utils.add(2, 3)) --> 5
✅ OpenResty 默认将
lua_package_path指向?/?.lua;;,模块路径相对于conf/目录。
3. 常用标准库
| 库名 | 用途 | 示例 |
|---|---|---|
string | 字符串操作 | string.sub("hello", 1, 3) → "hel" |
table | 表操作 | table.insert(arr, val) |
ngx.* | OpenResty 核心 API | 见下文 |
四、OpenResty 核心API
1. ngx.var:读写 Nginx 变量
location /demo {
set $my_var "";
content_by_lua_block {
ngx.var.my_var = "from lua"
ngx.say(ngx.var.my_var)
ngx.say(ngx.var.uri) -- 当前 URI
}
}
⚠️ 注意:
ngx.var性能较低,避免在高频路径中频繁读写。
2. ngx.req.*:请求信息获取与修改
-- 获取 GET/POST 参数
local args = ngx.req.get_uri_args()
local name = args.name or "guest"
-- 读取请求体(需先调用)
ngx.req.read_body()
local body = ngx.req.get_body_data()
-- 设置新 header
ngx.req.set_header("X-From-Lua", "true")
3. ngx.say / ngx.print:响应输出
ngx.say("line1") -- 自动加 \n
ngx.print("line2") -- 不换行
4. ngx.redirect、ngx.exit:流程控制
if not token_valid then
ngx.redirect("/login", 302) -- 302 跳转
end
ngx.exit(403) -- 直接终止请求,返回 403
5. ngx.timer.*:定时任务(非阻塞)
local function timer_handler(premature, msg)
if not premature then
ngx.log(ngx.NOTICE, "Timer: ", msg)
end
end
-- 5 秒后执行(仅执行一次)
local ok, err = ngx.timer.at(5, timer_handler, "hello")
✅ 定时器在独立上下文中运行,无法访问当前请求的
ngx.var等变量。
五、共享内存与缓存
1. lua_shared_dict 使用
在 nginx.conf 的 http 块中声明共享内存区:
http {
lua_shared_dict my_cache 10m; # 名称 + 大小
...
}
在 Lua 中操作:
local cache = ngx.shared.my_cache
-- 写入(key, value, ttl秒)
cache:set("count", 1, 60)
-- 读取
local value, flags = cache:get("count")
-- 原子自增(常用于计数器)
local new_val, err = cache:incr("count", 1)
2. 实现本地缓存(计数器、令牌桶)
示例:简单限流计数器
local key = ngx.var.binary_remote_addr
local limit = 10
local req, _ = cache:get(key)
if req and req >= limit then
ngx.exit(429)
else
cache:incr(key, 1, 1) -- 初始值=1,TTL=1秒
end
3. 注意事项
- 原子性:
incr()、add()是原子操作;get()+set()不是原子的 - LRU 策略:当内存满时,自动淘汰最近最少使用的 key。
- 性能:共享内存操作是 O(1),远快于 Redis 等外部存储(适合高频小数据)。
六、备注
1、开发建议
- 阶段选择:鉴权放
access_by_lua,动态内容放content_by_lua,日志放log_by_lua。 - 性能敏感:避免在 hot path 使用
ngx.var,优先用 Lua 局部变量。 - 错误处理:Lua 异常会导致 500,务必用
pcall()包裹关键逻辑。 - 调试技巧:用
ngx.log(ngx.ERR, ...)输出日志,查看logs/error.log。