OpenResty #

一、OpenResty 简介 #

1.1 什么是 OpenResty #

OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,由章亦春开发。它将 Nginx 核心与许多 Lua 库集成在一起,使得开发者能够使用 Lua 脚本调动 Nginx 支持的各种模块。

1.2 核心特点 #

  • 高性能:基于 Nginx 事件模型
  • 非阻塞 I/O:所有操作都是异步的
  • 轻量级:内存占用低,并发能力强
  • 可编程:使用 Lua 扩展 Nginx 功能

1.3 应用场景 #

  • API 网关(如 Kong)
  • Web 应用防火墙(WAF)
  • 负载均衡
  • 动态路由
  • 缓存服务

二、安装与配置 #

2.1 安装 OpenResty #

bash
# macOS
brew install openresty/brew/openresty

# Ubuntu
wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
sudo apt-get -y install software-properties-common
sudo add-apt-repository -y "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main"
sudo apt-get update
sudo apt-get install -y openresty

# CentOS
sudo yum install -y openresty

2.2 基本配置 #

nginx
# nginx.conf
worker_processes auto;
error_log logs/error.log info;

events {
    worker_connections 1024;
}

http {
    lua_package_path "/usr/local/openresty/lualib/?.lua;;";
    lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
    
    server {
        listen 8080;
        
        location / {
            default_type text/html;
            content_by_lua_block {
                ngx.say("Hello, OpenResty!")
            }
        }
    }
}

2.3 启动服务 #

bash
# 启动
openresty

# 重启
openresty -s reload

# 停止
openresty -s stop

# 测试配置
openresty -t

三、基本用法 #

3.1 content_by_lua_block #

nginx
location /hello {
    content_by_lua_block {
        ngx.say("Hello, World!")
        ngx.say("当前时间:", ngx.now())
    }
}

3.2 处理请求参数 #

nginx
location /api {
    content_by_lua_block {
        local args = ngx.req.get_uri_args()
        local name = args.name or "World"
        
        ngx.say("Hello, ", name, "!")
    }
}

3.3 处理 POST 请求 #

nginx
location /submit {
    methods POST;
    
    content_by_lua_block {
        ngx.req.read_body()
        local data = ngx.req.get_body_data()
        
        if not data then
            ngx.status = 400
            ngx.say("缺少请求体")
            return ngx.exit(400)
        end
        
        ngx.say("收到数据:", data)
    }
}

3.4 返回 JSON #

nginx
location /json {
    content_by_lua_block {
        local cjson = require "cjson"
        
        local response = {
            status = "success",
            data = {
                name = "OpenResty",
                version = "1.21.4"
            }
        }
        
        ngx.header["Content-Type"] = "application/json"
        ngx.say(cjson.encode(response))
    }
}

四、访问数据库 #

4.1 连接 MySQL #

lua
local mysql = require "resty.mysql"
local db, err = mysql:new()
if not db then
    ngx.log(ngx.ERR, "failed to instantiate mysql: ", err)
    return ngx.exit(500)
end

db:set_timeout(1000) -- 1 秒超时

local ok, err, errcode, sqlstate = db:connect{
    host = "127.0.0.1",
    port = 3306,
    database = "test",
    user = "root",
    password = "password",
    charset = "utf8mb4"
}

if not ok then
    ngx.log(ngx.ERR, "failed to connect: ", err)
    return ngx.exit(500)
end

-- 执行查询
local res, err, errcode, sqlstate = db:query("SELECT * FROM users LIMIT 10")
if not res then
    ngx.log(ngx.ERR, "bad result: ", err)
    return ngx.exit(500)
end

local cjson = require "cjson"
ngx.say(cjson.encode(res))

-- 关闭连接
db:close()

4.2 连接 Redis #

lua
local redis = require "resty.redis"
local red = redis:new()

red:set_timeout(1000)

local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
    ngx.log(ngx.ERR, "failed to connect: ", err)
    return ngx.exit(500)
end

-- 设置值
local ok, err = red:set("key", "value")
if not ok then
    ngx.log(ngx.ERR, "failed to set: ", err)
end

-- 获取值
local res, err = red:get("key")
ngx.say("value: ", res)

-- 放回连接池
red:set_keepalive(10000, 100)

五、HTTP 请求 #

5.1 内部请求 #

nginx
location /internal {
    content_by_lua_block {
        local res = ngx.location.capture("/api/data")
        
        if res.status == 200 then
            ngx.say("响应:", res.body)
        else
            ngx.say("请求失败")
        end
    }
}

location /api/data {
    content_by_lua_block {
        ngx.say("内部数据")
    }
}

5.2 外部 HTTP 请求 #

lua
local http = require "resty.http"
local httpc = http.new()

local res, err = httpc:request_uri("https://api.example.com/data", {
    method = "GET",
    headers = {
        ["Content-Type"] = "application/json"
    }
})

if not res then
    ngx.log(ngx.ERR, "request failed: ", err)
    return ngx.exit(500)
end

ngx.say(res.body)

六、缓存 #

6.1 共享内存缓存 #

nginx
http {
    lua_shared_dict cache 10m;
    
    server {
        location /cache {
            content_by_lua_block {
                local cache = ngx.shared.cache
                
                -- 设置缓存
                cache:set("key", "value", 60)  -- 60秒过期
                
                -- 获取缓存
                local value = cache:get("key")
                ngx.say("value: ", value)
            }
        }
    }
}

6.2 LRU 缓存 #

lua
local lrucache = require "resty.lrucache"
local cache, err = lrucache.new(200)  -- 最多缓存200个对象

if not cache then
    ngx.log(ngx.ERR, "failed to create cache: ", err)
    return
end

-- 设置缓存
cache:set("key", "value", 60)

-- 获取缓存
local value = cache:get("key")
ngx.say("value: ", value)

七、API 网关示例 #

7.1 认证中间件 #

lua
-- auth.lua
local _M = {}

function _M.check_token()
    local token = ngx.req.get_headers()["Authorization"]
    
    if not token then
        ngx.status = 401
        ngx.say('{"error": "Missing token"}')
        return ngx.exit(401)
    end
    
    -- 验证 token
    local redis = require "resty.redis"
    local red = redis:new()
    red:connect("127.0.0.1", 6379)
    
    local user_id = red:get("token:" .. token)
    
    if not user_id or user_id == ngx.null then
        ngx.status = 401
        ngx.say('{"error": "Invalid token"}')
        return ngx.exit(401)
    end
    
    -- 设置用户信息
    ngx.ctx.user_id = user_id
end

return _M

7.2 限流 #

lua
local limit_req = require "resty.limit.req"

local lim, err = limit_req.new("my_limit_req_store", 10, 5)
if not lim then
    ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
    return ngx.exit(500)
end

local key = ngx.var.binary_remote_addr
local delay, err = lim:incoming(key, true)

if not delay then
    if err == "rejected" then
        ngx.status = 429
        ngx.say('{"error": "Too many requests"}')
        return ngx.exit(429)
    end
    ngx.log(ngx.ERR, "failed to limit req: ", err)
    return ngx.exit(500)
end

if delay >= 0.001 then
    ngx.sleep(delay)
end

八、最佳实践 #

8.1 使用连接池 #

lua
-- 放回连接池而不是关闭
red:set_keepalive(10000, 100)  -- 最大空闲时间10秒,连接池大小100

8.2 错误处理 #

lua
local ok, err = pcall(function()
    -- 可能出错的代码
end)

if not ok then
    ngx.log(ngx.ERR, "error: ", err)
    ngx.status = 500
    ngx.say('{"error": "Internal server error"}')
    return ngx.exit(500)
end

8.3 使用模块组织代码 #

lua
-- myapp/utils.lua
local _M = {}

function _M.json_response(data)
    local cjson = require "cjson"
    ngx.header["Content-Type"] = "application/json"
    ngx.say(cjson.encode(data))
end

return _M

九、总结 #

OpenResty 是构建高性能 Web 应用的强大平台:

  1. 高性能:基于 Nginx 事件模型
  2. 异步 I/O:非阻塞操作
  3. 丰富库:数据库、缓存、HTTP 客户端
  4. 应用广泛:API 网关、WAF、负载均衡

下一章,我们将学习 LÖVE 游戏引擎。

最后更新:2026-03-27