1. OpenResty配置基础
1.1 配置文件结构
# nginx.conf 主配置文件
worker_processes auto;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
# Lua相关配置
lua_package_path "/usr/local/openresty/lualib/?.lua;;";
lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
# 共享内存配置
lua_shared_dict my_cache 10m;
lua_shared_dict my_locks 1m;
# 初始化脚本
init_by_lua_file /path/to/init.lua;
init_worker_by_lua_file /path/to/init_worker.lua;
server {
listen 80;
server_name localhost;
# 各种Lua指令的使用
location / {
content_by_lua_file /path/to/content.lua;
}
}
}
1.2 Lua指令概览
# 服务器启动时执行一次
init_by_lua_block { ... }
init_by_lua_file /path/to/init.lua;
# 每个worker进程启动时执行
init_worker_by_lua_block { ... }
init_worker_by_lua_file /path/to/init_worker.lua;
# 设置变量
set_by_lua_block $var { ... }
set_by_lua_file $var /path/to/set.lua;
# 重写阶段
rewrite_by_lua_block { ... }
rewrite_by_lua_file /path/to/rewrite.lua;
# 访问控制阶段
access_by_lua_block { ... }
access_by_lua_file /path/to/access.lua;
# 内容生成阶段
content_by_lua_block { ... }
content_by_lua_file /path/to/content.lua;
# 响应头过滤
header_filter_by_lua_block { ... }
header_filter_by_lua_file /path/to/header_filter.lua;
# 响应体过滤
body_filter_by_lua_block { ... }
body_filter_by_lua_file /path/to/body_filter.lua;
# 日志阶段
log_by_lua_block { ... }
log_by_lua_file /path/to/log.lua;
2. Nginx处理阶段详解
2.1 请求处理流程
┌─────────────────┐
│ 接收请求 │
└─────────────────┘
│
┌─────────────────┐
│ post-read │ ← realip模块
└─────────────────┘
│
┌─────────────────┐
│ server-rewrite│ ← rewrite模块(server级别)
└─────────────────┘
│
┌─────────────────┐
│ find-config │ ← 查找location
└─────────────────┘
│
┌─────────────────┐
│ rewrite │ ← rewrite_by_lua*
└─────────────────┘
│
┌─────────────────┐
│ post-rewrite │ ← rewrite模块(location级别)
└─────────────────┘
│
┌─────────────────┐
│ preaccess │ ← limit_req, limit_conn
└─────────────────┘
│
┌─────────────────┐
│ access │ ← access_by_lua*, auth模块
└─────────────────┘
│
┌─────────────────┐
│ post-access │ ← satisfy指令
└─────────────────┘
│
┌─────────────────┐
│ try-files │ ← try_files指令
└─────────────────┘
│
┌─────────────────┐
│ content │ ← content_by_lua*, proxy_pass
└─────────────────┘
│
┌─────────────────┐
│ log │ ← log_by_lua*, access_log
└─────────────────┘
2.2 各阶段的作用
init_by_lua*
# 服务器启动时执行,用于初始化全局配置
init_by_lua_block {
-- 加载配置
local config = require "config"
config.load("/etc/app/config.json")
-- 初始化全局变量
_G.app_version = "1.0.0"
_G.start_time = ngx.time()
-- 预编译正则表达式
local ngx_re = require "ngx.re"
_G.email_pattern = ngx_re.compile(r"[\w.-]+@[\w.-]+\.\w+")
}
init_worker_by_lua*
# 每个worker进程启动时执行
init_worker_by_lua_block {
-- 初始化定时器
local function cleanup_cache()
local cache = ngx.shared.my_cache
cache:flush_expired()
end
-- 每60秒清理一次过期缓存
local ok, err = ngx.timer.every(60, cleanup_cache)
if not ok then
ngx.log(ngx.ERR, "failed to create timer: ", err)
end
-- 初始化worker级别的资源
local redis = require "resty.redis"
_G.redis_pool = redis:new()
}
set_by_lua*
# 设置Nginx变量
location /api {
set_by_lua_block $user_id {
local token = ngx.var.http_authorization
if not token then
return ""
end
-- 解析JWT token
local jwt = require "resty.jwt"
local payload = jwt:verify("secret", token)
return payload and payload.user_id or ""
}
# 将user_id传递给后端
proxy_set_header X-User-ID $user_id;
proxy_pass http://backend;
}
rewrite_by_lua*
# URL重写和请求预处理
location /old-api {
rewrite_by_lua_block {
-- URL重写
local uri = ngx.var.uri
local new_uri = string.gsub(uri, "^/old%-api", "/api/v1")
ngx.req.set_uri(new_uri)
-- 添加版本头
ngx.req.set_header("API-Version", "v1")
-- 记录重写日志
ngx.log(ngx.INFO, "Rewritten ", uri, " to ", new_uri)
}
proxy_pass http://backend;
}
access_by_lua*
# 访问控制和认证
location /protected {
access_by_lua_block {
-- IP白名单检查
local allowed_ips = {"127.0.0.1", "192.168.1.0/24"}
local client_ip = ngx.var.remote_addr
local function is_ip_allowed(ip)
for _, allowed in ipairs(allowed_ips) do
if ip == allowed then
return true
end
end
return false
end
if not is_ip_allowed(client_ip) then
ngx.status = 403
ngx.say("Access denied")
ngx.exit(403)
end
-- 认证检查
local auth_header = ngx.var.http_authorization
if not auth_header then
ngx.status = 401
ngx.header.www_authenticate = 'Bearer realm="API"'
ngx.say("Authentication required")
ngx.exit(401)
end
-- 验证token
local token = string.match(auth_header, "Bearer%s+(.+)")
if not token or not validate_token(token) then
ngx.status = 401
ngx.say("Invalid token")
ngx.exit(401)
end
}
proxy_pass http://backend;
}
content_by_lua*
# 内容生成
location /api/users {
content_by_lua_block {
-- 解析请求参数
ngx.req.read_body()
local method = ngx.var.request_method
local args = ngx.req.get_uri_args()
if method == "GET" then
-- 获取用户列表
local page = tonumber(args.page) or 1
local limit = tonumber(args.limit) or 10
local users = get_users(page, limit)
ngx.header.content_type = "application/json"
ngx.say(require("cjson").encode({
data = users,
page = page,
limit = limit
}))
elseif method == "POST" then
-- 创建用户
local body = ngx.req.get_body_data()
local user_data = require("cjson").decode(body)
local user_id = create_user(user_data)
ngx.status = 201
ngx.header.content_type = "application/json"
ngx.say(require("cjson").encode({
id = user_id,
message = "User created successfully"
}))
else
ngx.status = 405
ngx.say("Method not allowed")
end
}
}
header_filter_by_lua*
# 响应头过滤
location / {
proxy_pass http://backend;
header_filter_by_lua_block {
-- 添加安全头
ngx.header["X-Frame-Options"] = "DENY"
ngx.header["X-Content-Type-Options"] = "nosniff"
ngx.header["X-XSS-Protection"] = "1; mode=block"
-- 移除敏感头
ngx.header["Server"] = nil
ngx.header["X-Powered-By"] = nil
-- 添加CORS头
local origin = ngx.var.http_origin
if origin then
ngx.header["Access-Control-Allow-Origin"] = origin
ngx.header["Access-Control-Allow-Credentials"] = "true"
end
-- 缓存控制
if ngx.var.uri:match("\.css$") or ngx.var.uri:match("\.js$") then
ngx.header["Cache-Control"] = "public, max-age=31536000"
end
}
}
body_filter_by_lua*
# 响应体过滤
location /api {
proxy_pass http://backend;
body_filter_by_lua_block {
-- 获取响应体
local chunk = ngx.arg[1]
local eof = ngx.arg[2]
if not chunk then
return
end
-- 响应体压缩
if ngx.header.content_type and
ngx.header.content_type:match("application/json") then
-- 移除响应中的敏感信息
local filtered = string.gsub(chunk, '"password":"[^"]*"', '"password":"***"')
ngx.arg[1] = filtered
end
-- 添加水印
if eof and ngx.header.content_type and
ngx.header.content_type:match("text/html") then
local watermark = "\n<!-- Powered by OpenResty -->"
ngx.arg[1] = chunk .. watermark
end
}
}
log_by_lua*
# 日志记录
location / {
proxy_pass http://backend;
log_by_lua_block {
-- 记录详细的访问日志
local log_data = {
timestamp = ngx.time(),
method = ngx.var.request_method,
uri = ngx.var.uri,
args = ngx.var.args,
status = ngx.var.status,
body_bytes_sent = ngx.var.body_bytes_sent,
request_time = ngx.var.request_time,
upstream_response_time = ngx.var.upstream_response_time,
remote_addr = ngx.var.remote_addr,
user_agent = ngx.var.http_user_agent,
referer = ngx.var.http_referer
}
-- 发送到日志系统
local logger = require "logger"
logger.info(require("cjson").encode(log_data))
-- 统计API调用次数
local cache = ngx.shared.my_cache
local key = "api_count:" .. ngx.var.uri
local count = cache:get(key) or 0
cache:set(key, count + 1, 3600)
-- 错误告警
if tonumber(ngx.var.status) >= 500 then
local alert = require "alert"
alert.send("API Error: " .. ngx.var.uri .. " returned " .. ngx.var.status)
end
}
}
3. 共享内存和缓存
3.1 lua_shared_dict配置
http {
# 定义共享内存区域
lua_shared_dict my_cache 100m; # 缓存数据
lua_shared_dict my_locks 10m; # 分布式锁
lua_shared_dict my_stats 50m; # 统计数据
lua_shared_dict rate_limit 10m; # 限流计数器
}
3.2 共享内存操作
-- 获取共享内存对象
local cache = ngx.shared.my_cache
-- 基本操作
cache:set("key", "value", 3600) -- 设置,过期时间3600秒
local value = cache:get("key") -- 获取
cache:delete("key") -- 删除
-- 原子操作
local newval, err = cache:incr("counter", 1, 0) -- 原子递增
local success, err, forcible = cache:add("lock", 1, 10) -- 原子添加
-- 批量操作
local keys = cache:get_keys(100) -- 获取最多100个key
cache:flush_all() -- 清空所有数据
cache:flush_expired() -- 清空过期数据
-- 容量管理
local capacity = cache:capacity() -- 总容量
local free = cache:free_space() -- 剩余空间
3.3 缓存模式实现
-- 缓存穿透保护
local function get_user_with_cache(user_id)
local cache = ngx.shared.my_cache
local cache_key = "user:" .. user_id
-- 尝试从缓存获取
local cached_user = cache:get(cache_key)
if cached_user then
if cached_user == "NULL" then
return nil -- 缓存的空值
end
return require("cjson").decode(cached_user)
end
-- 从数据库获取
local user = get_user_from_db(user_id)
-- 缓存结果(包括空值)
if user then
cache:set(cache_key, require("cjson").encode(user), 3600)
else
cache:set(cache_key, "NULL", 300) -- 空值缓存较短时间
end
return user
end
-- 分布式锁实现
local function acquire_lock(key, timeout)
local locks = ngx.shared.my_locks
local lock_key = "lock:" .. key
-- 尝试获取锁
local success, err, forcible = locks:add(lock_key, 1, timeout or 10)
return success
end
local function release_lock(key)
local locks = ngx.shared.my_locks
local lock_key = "lock:" .. key
locks:delete(lock_key)
end
-- 使用锁保护临界区
local function update_counter_safely(key)
if acquire_lock(key) then
local cache = ngx.shared.my_cache
local current = cache:get(key) or 0
cache:set(key, current + 1, 3600)
release_lock(key)
return true
else
return false, "Failed to acquire lock"
end
end
4. 变量和上下文
4.1 Nginx变量访问
-- 读取Nginx变量
local method = ngx.var.request_method
local uri = ngx.var.uri
local args = ngx.var.args
local remote_addr = ngx.var.remote_addr
local user_agent = ngx.var.http_user_agent
-- 设置Nginx变量
ngx.var.my_var = "custom_value"
-- 请求参数
local arg_name = ngx.var.arg_name -- GET参数
local cookie_session = ngx.var.cookie_session -- Cookie值
-- 上游服务器信息
local upstream_addr = ngx.var.upstream_addr
local upstream_status = ngx.var.upstream_status
local upstream_response_time = ngx.var.upstream_response_time
4.2 请求上下文
-- 请求头操作
local headers = ngx.req.get_headers()
for name, value in pairs(headers) do
ngx.log(ngx.INFO, name, ": ", value)
end
-- 设置请求头
ngx.req.set_header("X-Custom-Header", "value")
ngx.req.clear_header("Unwanted-Header")
-- 请求体操作
ngx.req.read_body()
local body_data = ngx.req.get_body_data()
local body_file = ngx.req.get_body_file()
-- URI和参数操作
local uri_args = ngx.req.get_uri_args()
local post_args = ngx.req.get_post_args()
-- 修改请求
ngx.req.set_uri("/new/path")
ngx.req.set_uri_args({page = 1, limit = 10})
4.3 响应上下文
-- 响应头操作
ngx.header.content_type = "application/json"
ngx.header["Cache-Control"] = "no-cache"
ngx.header["Set-Cookie"] = {"session=abc123", "lang=en"}
-- 响应状态
ngx.status = 200
-- 响应体
ngx.say("Hello, World!") -- 输出并添加换行
ngx.print("Hello") -- 输出不添加换行
ngx.flush() -- 刷新输出缓冲区
-- 结束请求
ngx.exit(200) -- 立即结束请求
ngx.eof() -- 结束响应体输出
5. 配置最佳实践
5.1 性能优化配置
http {
# Lua代码缓存
lua_code_cache on; # 生产环境必须开启
# 正则表达式缓存
lua_regex_cache_max_entries 8192;
# 共享内存优化
lua_shared_dict cache 1000m;
# SSL优化
lua_ssl_verify_depth 2;
lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
# 连接池配置
upstream backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
# 连接保持
keepalive_timeout 65;
keepalive_requests 100;
# 缓冲区优化
client_body_buffer_size 128k;
client_max_body_size 10m;
location / {
# 上游连接保持
proxy_http_version 1.1;
proxy_set_header Connection "";
# 超时设置
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 10s;
proxy_pass http://backend;
}
}
}
5.2 安全配置
http {
# 隐藏版本信息
server_tokens off;
# 限制请求大小
client_max_body_size 1m;
# 限制请求频率
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
# 安全头
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
# 限流
limit_req zone=api burst=20 nodelay;
# SSL配置
listen 443 ssl http2;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256;
location / {
# IP访问控制
access_by_lua_block {
local ip = ngx.var.remote_addr
if ip == "192.168.1.100" then
ngx.exit(403)
end
}
proxy_pass http://backend;
}
}
}
5.3 监控和日志配置
http {
# 日志格式
log_format json_log escape=json
'{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"method":"$request_method",'
'"uri":"$uri",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"user_agent":"$http_user_agent"'
'}';
access_log logs/access.log json_log;
# 状态监控
server {
listen 8080;
server_name localhost;
location /status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
location /lua-status {
content_by_lua_block {
local cache = ngx.shared.my_cache
local stats = {
capacity = cache:capacity(),
free_space = cache:free_space(),
keys_count = #cache:get_keys(0)
}
ngx.header.content_type = "application/json"
ngx.say(require("cjson").encode(stats))
}
}
}
}
6. 总结
Nginx与Lua的集成是OpenResty的核心特性,通过合理配置各个处理阶段的Lua脚本,我们可以实现强大的Web应用功能。理解各个阶段的执行顺序和作用,掌握共享内存、变量操作等核心概念,是开发高性能OpenResty应用的基础。