OpenResty 高性能 Web 平台
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 的工作模式。
-
创建工作目录,例如
~/openresty-test,并在其中创建conf和logs子目录:mkdir -p ~/openresty-test/{conf,logs} -
编辑配置文件
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 代码直接生成响应内容。 -
启动 OpenResty:
openresty -p ~/openresty-test -c conf/nginx.conf参数说明:
-p指定工作目录前缀,-c指定配置文件路径。 -
测试:
curl http://localhost:8080/hello返回
Hello, OpenResty!表示运行成功。你可以修改配置文件后,使用
openresty -p ~/openresty-test -c conf/nginx.conf -s reload平滑重载。
理解 Nginx 阶段与 Lua 注入点
OpenResty 的强大在于它可以介入 Nginx 处理请求的多个阶段。常见的顺序如下:
- post-read:读取请求头之后,很少使用。
- server-rewrite:server 块内的重写。
- find-config:匹配 location。
- rewrite:location 块内的重写阶段,可使用
rewrite_by_lua*。 - access:访问控制阶段,常用
access_by_lua*做鉴权。 - content:内容生成阶段,
content_by_lua*在此处生成响应。 - header-filter:响应头过滤阶段,
header_filter_by_lua*。 - body-filter:响应体过滤阶段,
body_filter_by_lua*。 - 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)
}
}
注意:connect、set、get 等操作都是非阻塞的,意味着 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_file 或 access_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;;";