OpenResty 高性能 Web 平台

FreeGuideOnline 最新 2026-07-11

bash

添加官方仓库

sudo yum install yum-utils sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo

安装 openresty

sudo yum install openresty

安装命令行工具 (如 resty、opm)

sudo yum install openresty-resty


#### Ubuntu/Debian 系统

```bash
# 导入 GPG 密钥
wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add -

# 添加仓库
sudo add-apt-repository "deb https://openresty.org/package/ubuntu $(lsb_release -sc) main"
# 如果是 Debian,将 ubuntu 替换为 debian

sudo apt-get update
sudo apt-get install openresty

# 安装命令行工具
sudo apt-get install openresty-resty

安装完成后,OpenResty 默认安装在 /usr/local/openresty/ 目录,可执行文件在 bin/ 下,包括 openresty (Nginx 扩展版本)。建议将 bin 目录加入 PATH:

export PATH=/usr/local/openresty/bin:$PATH

验证安装:

openresty -v

你应该能看到包含 nginx version: openresty/x.x.x.x 的版本信息。

Hello World 示例

现在通过一个最简单的例子熟悉 OpenResty 的工作模式。

  1. 创建工作目录,例如 ~/openresty-test,并在其中创建 conflogs 子目录:

    mkdir -p ~/openresty-test/{conf,logs}
    
  2. 编辑配置文件 conf/nginx.conf

    worker_processes  1;
    error_log logs/error.log;
    events {
        worker_connections 1024;
    }
    http {
        server {
            listen 8080;
            location /hello {
                default_type text/plain;
                content_by_lua_block {
                    ngx.say("Hello, OpenResty!")
                }
            }
        }
    }
    

    这里的关键是 content_by_lua_block 指令,它告诉 Nginx 使用 Lua 代码直接生成响应内容。

  3. 启动 OpenResty

    openresty -p ~/openresty-test -c conf/nginx.conf
    

    参数说明:-p 指定工作目录前缀,-c 指定配置文件路径。

  4. 测试

    curl http://localhost:8080/hello
    

    返回 Hello, OpenResty! 表示运行成功。

    你可以修改配置文件后,使用 openresty -p ~/openresty-test -c conf/nginx.conf -s reload 平滑重载。

理解 Nginx 阶段与 Lua 注入点

OpenResty 的强大在于它可以介入 Nginx 处理请求的多个阶段。常见的顺序如下:

  1. post-read:读取请求头之后,很少使用。
  2. server-rewrite:server 块内的重写。
  3. find-config:匹配 location。
  4. rewrite:location 块内的重写阶段,可使用 rewrite_by_lua*
  5. access:访问控制阶段,常用 access_by_lua* 做鉴权。
  6. content:内容生成阶段,content_by_lua* 在此处生成响应。
  7. header-filter:响应头过滤阶段,header_filter_by_lua*
  8. body-filter:响应体过滤阶段,body_filter_by_lua*
  9. log:日志记录阶段,log_by_lua*

合理利用这些阶段可以设计出精细的请求处理流水线。例如,一个典型的 API 网关流程:在 rewrite_by_lua 中解析参数,access_by_lua 中进行签名验证、IP 黑名单检查,然后 content_by_lua 里并发发起多个子请求(通过 ngx.location.capture_multi)聚合数据后返回。

核心 API 快速上手

掌握几个最常用的 Lua API,即可开始构建简单的应用。

  • ngx.say(...):发送响应数据,自动添加换行符(输出到客户端)。
  • ngx.print(...):发送响应数据,不自动换行。
  • ngx.req.get_uri_args():获取 GET 参数表格。
  • ngx.req.read_body():读取请求体(在需要 POST 数据时)。
  • ngx.req.get_post_args():获取 POST 表单数据。
  • ngx.header:读写响应头,如 ngx.header["Content-Type"] = "application/json"
  • ngx.var.xxx:读写 Nginx 变量,例如 ngx.var.remote_addr
  • ngx.redirect(uri, status):发送重定向。
  • ngx.exit(status):终止请求并返回状态码(如 ngx.HTTP_FORBIDDEN)。

下面是一个示例,展示如何返回 JSON 数据并使用请求参数:

location /api {
    default_type application/json;
    content_by_lua_block {
        local args = ngx.req.get_uri_args()
        local name = args.name or "Guest"
        ngx.say('{"message": "Hello, ' .. name .. '"}')
    }
}

访问 /api?name=Lua 将得到 {"message": "Hello, Lua"}

全异步连接外部服务(Redis 示例)

OpenResty 的 cosocket 让与外部服务通信变得既高性能又简单。以 Redis 为例,需要使用 lua-resty-redis 库(已内置)。

首先,确保 Redis 服务已运行。然后配置如下 location:

location /redis {
    content_by_lua_block {
        local redis = require "resty.redis"
        local red = redis:new()
        -- 设置超时(毫秒)
        red:set_timeouts(1000, 1000, 1000)

        -- 连接到 Redis
        local ok, err = red:connect("127.0.0.1", 6379)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        -- 执行命令
        local res, err = red:set("greeting", "Hello from OpenResty!")
        if not res then
            ngx.say("failed to set key: ", err)
            return
        end

        -- 获取值
        local resp, err = red:get("greeting")
        if not resp then
            ngx.say("failed to get key: ", err)
            return
        end

        -- 必须将连接放回池中(或关闭)
        red:set_keepalive(10000, 100)
        ngx.say("Redis replied: ", resp)
    }
}

注意:connectsetget 等操作都是非阻塞的,意味着 Nginx worker 进程在等待 Redis 响应时可以处理其他请求。连接通过 set_keepalive 放回连接池复用,避免了频繁建立的消耗。

缓存:worker 内共享字典与 LRU 缓存

OpenResty 提供了两种高效的缓存机制。

1. 共享字典(shared dictionary)

lua_shared_dict 指令在 http 块中定义一个在所有 worker 进程间共享的内存区域。适合存储需要跨 worker 保持一致的数据。

http {
    lua_shared_dict my_cache 10m;  # 分配 10MB 共享内存

    server {
        location /cache {
            content_by_lua_block {
                local cache = ngx.shared.my_cache
                -- 尝试获取
                local val = cache:get("key")
                if not val then
                    val = "expensive_computed_value"
                    cache:set("key", val, 60) -- 60 秒过期
                end
                ngx.say(val)
            }
        }
    }
}

2. worker 进程内 LRU 缓存

lua-resty-lrucache 提供 worker 内的高速缓存,操作比共享字典更快,但不共享,且容量有限(虚拟内存不会很大)。适用于不要求绝对一致的数据。

location /lru {
    content_by_lua_block {
        local lrucache = require "resty.lrucache"
        -- 通常在 init_by_lua  init_worker_by_lua 中初始化,这里仅为示例
        local cache, err = lrucache.new(200)  -- 最多 200 个项
        if not cache then
            ngx.say(err)
            return
        end
        local val, stale = cache:get("lru_key")
        if not val then
            val = "computed_value"
            cache:set("lru_key", val, 30)  -- 30  TTL
        end
        ngx.say(val)
    }
}

实际项目中,缓存初始化应放在 init_worker_by_lua_block 中,并在模块级别共享变量。

子请求与并发

OpenResty 允许你在当前请求中发起“子请求”到其他 location(内部重定向),处理完再聚合结果。ngx.location.capture 会阻塞当前 Lua 协程,但底层仍是异步非阻塞;而 ngx.location.capture_multi 可以并行发起多个子请求,大幅提升响应速度。

location /main {
    content_by_lua_block {
        local res1, res2 = ngx.location.capture_multi{
            {"/data1"},
            {"/data2"}
        }
        ngx.say("Data1: ", res1.body)
        ngx.say("Data2: ", res2.body)
    }
}
location /data1 {
    content_by_lua_block {
        ngx.sleep(0.1)  -- 模拟耗时
        ngx.say("result1")
    }
}
location /data2 {
    content_by_lua_block {
        ngx.say("result2")
    }
}

两个子请求并发执行,总耗时取最慢的那个,而不是相加。

编写模块与项目结构

建议将业务逻辑封装为 Lua 模块,在 content_by_lua_fileaccess_by_lua_file 等指令中通过文件引入。例如:

app/
  conf/
    nginx.conf
  lua/
    biz/
      auth.lua
      handler.lua
  logs/

配置文件使用相对路径:

server {
    location / {
        content_by_lua_file lua/biz/handler.lua;
    }
}

handler.lua 中:

local auth = require("biz.auth")
-- 业务处理...

nginx.conf 中需要设置 Lua 的 package path:

lua_package_path "$prefix/lua/?.lua;;";