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. 学习资源
- 官方文档:https://openresty.org/en/
- GitHub:https://github.com/openresty
- OpenResty 书籍:《OpenResty 最佳实践》
- 社区:OpenResty 中文社区 (https://openresty.org.cn/)
- 视频教程:B 站、慕课网等平台的 OpenResty 教程
OpenResty 是一个强大的平台,结合了 Nginx 的高性能和 Lua 的灵活性。通过本教程,您应该已经掌握了 OpenResty 的基本概念和核心功能。要真正掌握 OpenResty,建议通过实际项目实践,并深入阅读官方文档和源码。