OpenResty 详细教程

OpenResty 是一个基于 Nginx 和 Lua 的高性能 Web 平台,集成了大量精良的 Lua 库和第三方模块,用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

1. OpenResty 简介

核心特点

  • 基于 Nginx:高性能 HTTP 服务器和反向代理
  • LuaJIT:使用 LuaJIT 2.x 作为脚本语言
  • 非阻塞 I/O:处理高并发请求
  • 丰富的模块:内置大量实用模块
  • 可扩展性:轻松添加自定义功能

主要应用场景

  • Web 应用和 API 服务
  • 动态网关和反向代理
  • 流量控制和限速
  • 缓存和 CDN 边缘计算
  • 安全防护(WAF)
  • 日志分析和处理

2. OpenResty 安装

Linux 安装(CentOS/RHEL)

# 添加 OpenResty 仓库
sudo yum install yum-utils
sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo

# 安装 OpenResty
sudo yum install openresty

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

# 启动服务
sudo systemctl enable openresty
sudo systemctl start openresty

Linux 安装(Ubuntu/Debian)

# 导入 GPG 密钥
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

# 安装 OpenResty
sudo apt-get install openresty

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

Mac 安装

brew install openresty/brew/openresty

从源码安装

wget https://openresty.org/download/openresty-1.19.9.1.tar.gz
tar -xzvf openresty-1.19.9.1.tar.gz
cd openresty-1.19.9.1/
./configure
make
sudo make install

验证安装

openresty -v
# 应该显示类似:nginx version: openresty/1.19.9.1

3. OpenResty 基本使用

目录结构

/usr/local/openresty/
├── nginx/
│   ├── conf/             # 配置文件目录
│   │   ├── nginx.conf    # 主配置文件
│   ├── html/             # 默认网页根目录
│   ├── logs/             # 日志目录
├── bin/
│   ├── openresty         # 主程序
│   ├── resty             # 命令行工具
├── lualib/               # Lua 库目录

启动/停止/重启

# 启动
sudo /usr/local/openresty/nginx/sbin/nginx

# 停止
sudo /usr/local/openresty/nginx/sbin/nginx -s stop

# 优雅停止
sudo /usr/local/openresty/nginx/sbin/nginx -s quit

# 重新加载配置
sudo /usr/local/openresty/nginx/sbin/nginx -s reload

# 检查配置
sudo /usr/local/openresty/nginx/sbin/nginx -t

4. Nginx 配置基础

基本配置示例

nginx

worker_processes  1;        # 工作进程数,通常设置为CPU核心数
error_log  logs/error.log;  # 错误日志路径

events {
    worker_connections  1024;  # 每个工作进程的最大连接数
}

http {
    server {
        listen       8080;     # 监听端口
        server_name  localhost; # 服务器名

        location / {
            root   html;       # 根目录
            index  index.html index.htm;
        }
    }
}

常用指令

  • listen:监听端口
  • server_name:服务器名称
  • root:根目录
  • index:默认文件
  • access_log:访问日志
  • error_log:错误日志
  • location:URL 匹配规则

5. Lua 与 OpenResty 集成

content_by_lua 指令

nginx

location /hello {
    default_type text/html;
    content_by_lua_block {
        ngx.say("<h1>Hello, OpenResty!</h1>")
    }
}

访问 Nginx 变量

nginx

location /request_info {
    content_by_lua_block {
        ngx.say("Method: ", ngx.var.request_method)
        ngx.say("URI: ", ngx.var.request_uri)
        ngx.say("Host: ", ngx.var.host)
    }
}

常用 ngx API

  • ngx.say():输出内容并换行
  • ngx.print():输出内容不换行
  • ngx.log():记录日志
  • ngx.exit():退出请求处理
  • ngx.redirect():重定向
  • ngx.req.get_headers():获取请求头
  • ngx.req.get_uri_args():获取查询参数
  • ngx.req.get_post_args():获取 POST 参数

6. OpenResty 核心模块

ngx_http_lua_module

Lua 脚本的核心模块,提供与 Nginx 交互的 API。

lua-resty-core

OpenResty 核心库,提供更多高性能 API。

lua-resty-redis

nginx

location /redis {
    content_by_lua_block {
        local redis = require "resty.redis"
        local red = redis:new()

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

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

        ok, err = red:set("dog", "an animal")
        if not ok then
            ngx.say("failed to set dog: ", err)
            return
        end

        local res, err = red:get("dog")
        if not res then
            ngx.say("failed to get dog: ", err)
            return
        end

        ngx.say("dog: ", res)

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

lua-resty-mysql

nginx

location /mysql {
    content_by_lua_block {
        local mysql = require "resty.mysql"
        local db, err = mysql:new()
        if not db then
            ngx.say("failed to instantiate mysql: ", err)
            return
        end

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

        local ok, err, errno, sqlstate = db:connect{
            host = "127.0.0.1",
            port = 3306,
            database = "test",
            user = "root",
            password = "password",
            max_packet_size = 1024 * 1024
        }

        if not ok then
            ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate)
            return
        end

        local res, err, errno, sqlstate = db:query("select * from users limit 10")
        if not res then
            ngx.say("bad result: ", err, ": ", errno, " ", sqlstate)
            return
        end

        ngx.say("result: ", require("cjson").encode(res))

        -- 放回连接池
        local ok, err = db:set_keepalive(10000, 100)
        if not ok then
            ngx.say("failed to set keepalive: ", err)
            return
        end
    }
}

lua-resty-template

模板引擎模块:

nginx

location /template {
    default_type text/html;
    content_by_lua_block {
        local template = require "resty.template"
        template.render("view.html", {
            title = "OpenResty Template",
            message = "Hello, World!",
            names = {"Alice", "Bob", "Charlie"}
        })
    }
}

view.html 文件:

<!DOCTYPE html>
<html>
<head>
    <title>{{title}}</title>
</head>
<body>
    <h1>{{message}}</h1>
    <ul>
        {% for _, name in ipairs(names) do %}
        <li>{{name}}</li>
        {% end %}
    </ul>
</body>
</html>

运行 HTML

7. 高级特性

阶段处理

OpenResty 允许在不同请求处理阶段执行 Lua 代码:

nginx

# 初始化阶段
init_by_lua_block {
    -- 初始化全局变量或预加载模块
}

# SSL 证书阶段
ssl_certificate_by_lua_block {
    -- 动态处理 SSL 证书
}

# 重写阶段
rewrite_by_lua_block {
    -- 重写请求
}

# 访问控制阶段
access_by_lua_block {
    -- 访问控制
}

# 内容生成阶段
content_by_lua_block {
    -- 生成响应内容
}

# 日志记录阶段
log_by_lua_block {
    -- 记录日志
}

共享内存

nginx

http {
    lua_shared_dict shared_data 10m;  # 定义10MB共享内存

    server {
        location /counter {
            content_by_lua_block {
                local shared_data = ngx.shared.shared_data
                local newval, err = shared_data:incr("counter", 1)
                if not newval then
                    if err == "not found" then
                        shared_data:set("counter", 0)
                        newval = 0
                    else
                        ngx.say("failed to increment counter: ", err)
                        return
                    end
                end
                ngx.say("counter: ", newval)
            }
        }
    }
}

定时器

nginx

init_worker_by_lua_block {
    local delay = 5  -- 5秒间隔
    local handler
    handler = function()
        -- 定时任务逻辑
        ngx.log(ngx.NOTICE, "timer triggered")

        -- 再次设置定时器
        local ok, err = ngx.timer.at(delay, handler)
        if not ok then
            ngx.log(ngx.ERR, "failed to create timer: ", err)
            return
        end
    end

    -- 初始启动定时器
    local ok, err = ngx.timer.at(delay, handler)
    if not ok then
        ngx.log(ngx.ERR, "failed to create timer: ", err)
        return
    end
}

8. 性能优化

1. 代码缓存

nginx

lua_code_cache on;  # 生产环境必须开启

2. 连接池

对于 Redis、MySQL 等外部服务,使用连接池避免频繁创建连接。

3. 避免阻塞操作

  • 使用非阻塞 API
  • 避免在 Lua 代码中执行长时间运算
  • 使用 ngx.timer.at 处理后台任务

4. 共享内存

使用 ngx.shared.DICT 替代全局变量进行数据共享。

5. 缓存策略

  • 使用 lua-resty-lrucache 实现 LRU 缓存
  • 使用 lua-resty-mlcache 实现多层缓存

9. 安全实践

1. 输入验证

nginx

location /api {
    access_by_lua_block {
        local args = ngx.req.get_uri_args()
        if not args.id or not tonumber(args.id) then
            ngx.exit(ngx.HTTP_BAD_REQUEST)
        end
    }

    content_by_lua_file /path/to/api.lua;
}

2. 防止 SQL 注入

lua

local mysql = require "resty.mysql"
local db = mysql:new()

local id = ngx.var.arg_id
-- 使用参数化查询
local res, err, errno, sqlstate = db:query("SELECT * FROM users WHERE id = ?", id)

3. 限速

nginx

http {
    lua_shared_dict my_limit_req_store 100m;

    server {
        location /api {
            access_by_lua_block {
                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 limit.req: ", 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
                        return ngx.exit(503)
                    end
                    ngx.log(ngx.ERR, "failed to limit req: ", err)
                    return ngx.exit(500)
                end
            }

            content_by_lua_block {
                ngx.say("Hello, API!")
            }
        }
    }
}

10. 调试与日志

日志记录

lua

ngx.log(ngx.ERR, "error message")
ngx.log(ngx.WARN, "warning message")
ngx.log(ngx.INFO, "info message")
ngx.log(ngx.DEBUG, "debug message")

使用 resty 命令行工具

resty -e 'print("Hello, OpenResty!")'

使用 OpenResty XRay

商业产品,提供性能分析和调试功能。

11. 实战项目

1. 简单 API 服务

nginx

location /api/users {
    content_by_lua_block {
        local cjson = require "cjson"
        local mysql = require "resty.mysql"

        local db = mysql:new()
        db:set_timeout(1000)

        local ok, err = db:connect({
            host = "127.0.0.1",
            port = 3306,
            database = "test",
            user = "root",
            password = "password"
        })

        if not ok then
            ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
            ngx.say(cjson.encode({error = "Database connection failed"}))
            return
        end

        local res, err, errno, sqlstate = db:query("SELECT * FROM users LIMIT 100")
        if not res then
            ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
            ngx.say(cjson.encode({error = "Query failed"}))
            return
        end

        ngx.header.content_type = "application/json"
        ngx.say(cjson.encode(res))

        db:set_keepalive(10000, 100)
    }
}

2. JWT 认证

nginx

location /secure {
    access_by_lua_block {
        local jwt = require "resty.jwt"
        local auth_header = ngx.var.http_Authorization

        if not auth_header then
            ngx.exit(ngx.HTTP_UNAUTHORIZED)
        end

        local _, _, token = string.find(auth_header, "Bearer%s+(.+)")
        if not token then
            ngx.exit(ngx.HTTP_UNAUTHORIZED)
        end

        local jwt_obj = jwt:verify("your-secret-key", token)
        if not jwt_obj.verified then
            ngx.log(ngx.ERR, "JWT verification failed: ", jwt_obj.reason)
            ngx.exit(ngx.HTTP_UNAUTHORIZED)
        end

        -- 将用户信息传递给后续处理
        ngx.ctx.user = jwt_obj.payload
    }

    content_by_lua_block {
        ngx.say("Welcome, ", ngx.ctx.user.username)
    }
}

3. 动态反向代理

nginx

location /proxy {
    set $target "";
    access_by_lua_block {
        -- 根据请求头或其他条件动态设置代理目标
        local host = ngx.req.get_headers()["X-Target-Host"]
        if host then
            ngx.var.target = host
        else
            ngx.var.target = "default.backend.com"
        end
    }

    proxy_pass http://$target;
}

12. 常见问题解决

1. Lua 代码不生效

  • 检查 lua_code_cache 是否开启
  • 检查文件路径是否正确
  • 检查 Nginx 配置是否重新加载

2. 性能问题

  • 检查是否有阻塞操作
  • 检查连接池是否正常工作
  • 使用 ngx.location.capture 并行处理

3. 内存泄漏

  • 避免在 Lua 中创建大量全局变量
  • 确保外部连接正确释放或放回连接池
  • 监控共享内存使用情况

13. 学习资源

  1. 官方文档https://openresty.org/en/
  2. GitHubhttps://github.com/openresty
  3. OpenResty 书籍:《OpenResty 最佳实践》
  4. 社区:OpenResty 中文社区 (https://openresty.org.cn/)
  5. 视频教程:B 站、慕课网等平台的 OpenResty 教程

OpenResty 是一个强大的平台,结合了 Nginx 的高性能和 Lua 的灵活性。通过本教程,您应该已经掌握了 OpenResty 的基本概念和核心功能。要真正掌握 OpenResty,建议通过实际项目实践,并深入阅读官方文档和源码。









results matching ""

    No results matching ""