OpenResty入门:OpenResty核心机制与API

2025-12-28 488 阅读 中间件
所属系列:OpenResty入门系列教程 查看完整系列

一、前言

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

Linux部署OpenrestymacOS部署OpenrestyWindows部署Openresty使用Docker部署Openresty

二、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脚本时,一定要注意优先级跟顺序

三、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.redirectngx.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.confhttp 块中声明共享内存区:

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

2、引用/参考