Nginx 插件开发深度解析:从 C 模块到 OpenResty
-
1. 引言:为什么选择 Nginx 插件开发 -
2. Nginx 架构与模块系统 -
3. C 语言模块开发 -
4. Lua 模块开发 (OpenResty) -
5. 实战案例:开发自定义模块 -
6. 性能优化与调试技巧 -
7. 最佳实践与生产经验
1. 引言:为什么选择 Nginx 插件开发
1.1 Nginx 的核心优势
Nginx 作为当今最流行的 Web 服务器和反向代理服务器之一,其高性能和模块化设计使其能够处理超过 40% 的全球网站流量。Nginx 的模块系统是其灵活性的核心,允许开发者通过插件扩展其功能。
1.2 插件开发的两大方向
当前 Nginx 插件开发主要有两条技术路线:
-
1. C 语言原生模块开发 -
• 性能极致,直接编译进 Nginx 核心 -
• 适合高性能场景:自定义负载均衡、协议处理 -
• 开发门槛高,需要深入理解 Nginx 内部架构 -
2. Lua 脚本开发 (OpenResty) -
• 基于 LuaJIT,提供脚本级别的灵活性 -
• 热更新、开发效率高 -
• 被 Cloudflare、淘宝等大型互联网公司广泛使用
2. Nginx 架构与模块系统
2.1 代码目录结构
Nginx 的源码采用模块化设计,核心目录结构如下:
src/
├── core/ # 基础类型和函数(字符串、数组、日志、内存池等)
├── event/ # 事件核心
│ └── modules/ # 事件通知模块:epoll、kqueue、select 等
├── http/ # HTTP 核心模块
│ ├── modules/ # 其他 HTTP 模块
│ └── v2/ # HTTP/2 支持
├── mail/ # 邮件模块
├── stream/ # Stream 模块(TCP/UDP 代理)
└── os/ # 平台特定代码
├── unix/
└── win32/
2.2 模块类型分类
Nginx 模块按功能分为以下几类:
|
|
|
|
| Core |
|
|
| HTTP |
|
|
| Stream |
|
|
| Event |
|
|
|
|
|
2.3 HTTP 处理阶段
Nginx HTTP 模块按处理阶段执行,这是开发 HTTP 模块必须理解的核心概念:
// HTTP 处理阶段枚举(简化版)
typedefenum {
NGX_HTTP_POST_READ_PHASE = 0, // 读取请求头后
NGX_HTTP_SERVER_REWRITE_PHASE, // Server 级别重写
NGX_HTTP_FIND_CONFIG_PHASE, // 查找 location 配置
NGX_HTTP_REWRITE_PHASE, // Location 级别重写
NGX_HTTP_POST_REWRITE_PHASE, // 重写后处理
NGX_HTTP_PREACCESS_PHASE, // 访问控制前
NGX_HTTP_ACCESS_PHASE, // 访问控制
NGX_HTTP_POST_ACCESS_PHASE, // 访问控制后
NGX_HTTP_TRY_FILES_PHASE, // try_files 处理
NGX_HTTP_CONTENT_PHASE, // 内容生成
NGX_HTTP_LOG_PHASE // 日志记录
} ngx_http_phases;
每个阶段可以注册处理函数,Nginx 按顺序调用这些函数。
2.4 模块核心数据结构
所有 Nginx 模块都必须定义 ngx_module_t 结构:
typedefstructngx_module_sngx_module_t;
structngx_module_s {
ngx_uint_t ctx_index; // 模块上下文索引
ngx_uint_t index; // 模块索引
ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t spare2;
ngx_uint_t spare3;
ngx_uint_t version; // 模块版本
void *ctx; // 模块上下文(类型特定)
ngx_command_t *commands; // 配置指令数组
ngx_uint_t type; // 模块类型(HTTP/STREAM等)
ngx_int_t (*init_master)(ngx_log_t *log);
ngx_int_t (*init_module)(ngx_cycle_t *cycle);
ngx_int_t (*init_process)(ngx_cycle_t *cycle);
ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
void (*exit_thread)(ngx_cycle_t *cycle);
void (*exit_process)(ngx_cycle_t *cycle);
void (*exit_master)(ngx_cycle_t *cycle);
uintptr_t spare_hook0;
uintptr_t spare_hook1;
uintptr_t spare_hook2;
uintptr_t spare_hook3;
uintptr_t spare_hook4;
uintptr_t spare_hook5;
uintptr_t spare_hook6;
uintptr_t spare_hook7;
};
3. C 语言模块开发
3.1 模块基本结构
一个最简单的 HTTP 模块包含以下文件:
config – 模块构建配置:
ngx_addon_name=ngx_http_hello_module
HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS$ngx_addon_dir/ngx_http_hello_module.c"
ngx_http_hello_module.c – 模块源码:
#include<ngx_config.h>
#include<ngx_core.h>
#include<ngx_http.h>
// 配置结构体
typedefstruct {
ngx_str_t greeting;
} ngx_http_hello_loc_conf_t;
// 函数声明
staticvoid *ngx_http_hello_create_loc_conf(ngx_conf_t *cf);
staticchar *ngx_http_hello_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
staticngx_int_tngx_http_hello_handler(ngx_http_request_t *r);
// 配置指令定义
staticngx_command_t ngx_http_hello_commands[] = {
{ ngx_string("hello_greeting"),
NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_hello_loc_conf_t, greeting),
NULL },
ngx_null_command
};
// HTTP 模块上下文
staticngx_http_module_t ngx_http_hello_module_ctx = {
NULL, // preconfiguration
NULL, // postconfiguration
NULL, // create main configuration
NULL, // init main configuration
NULL, // create server configuration
NULL, // merge server configuration
ngx_http_hello_create_loc_conf, // create location configuration
ngx_http_hello_merge_loc_conf // merge location configuration
};
// 模块定义
ngx_module_t ngx_http_hello_module = {
NGX_MODULE_V1,
&ngx_http_hello_module_ctx, // module context
ngx_http_hello_commands, // module directives
NGX_HTTP_MODULE, // module type
NULL, // init master
NULL, // init module
NULL, // init process
NULL, // init thread
NULL, // exit thread
NULL, // exit process
NULL, // exit master
NGX_MODULE_V1_PADDING
};
// 创建 location 配置
staticvoid *
ngx_http_hello_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_hello_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_loc_conf_t));
if (conf == NULL) {
returnNULL;
}
return conf;
}
// 合并 location 配置
staticchar *
ngx_http_hello_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_hello_loc_conf_t *prev = parent;
ngx_http_hello_loc_conf_t *conf = child;
ngx_conf_merge_str_value(conf->greeting, prev->greeting, "Hello, Nginx!");
return NGX_CONF_OK;
}
// 请求处理函数
staticngx_int_t
ngx_http_hello_handler(ngx_http_request_t *r)
{
ngx_buf_t *b;
ngx_chain_t out;
ngx_http_hello_loc_conf_t *hlcf;
// 只处理 GET 和 HEAD 请求
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
return NGX_HTTP_NOT_ALLOWED;
}
// 丢弃请求体
ngx_int_t rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
// 设置响应头
r->headers_out.content_type_len = sizeof("text/plain") - 1;
r->headers_out.content_type.data = (u_char *) "text/plain";
// 获取配置
hlcf = ngx_http_get_module_loc_conf(r, ngx_http_hello_module);
// 分配缓冲区
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// 设置响应内容
b->pos = hlcf->greeting.data;
b->last = hlcf->greeting.data + hlcf->greeting.len;
b->memory = 1; // 只读内存
b->last_buf = 1; // 最后一个缓冲区
// 设置输出链
out.buf = b;
out.next = NULL;
// 发送响应头
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = hlcf->greeting.len;
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
// 发送响应体
return ngx_http_output_filter(r, &out);
}
3.2 编译与安装
# 下载 Nginx 源码
cd /usr/local/src
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -xzvf nginx-1.24.0.tar.gz
cd nginx-1.24.0
# 添加自定义模块
./configure \
--prefix=/usr/local/nginx \
--with-http_ssl_module \
--add-module=/path/to/ngx_http_hello_module
# 编译安装
make
sudo make install
# 配置 Nginx 使用该模块
cat > /usr/local/nginx/conf/nginx.conf << 'EOF'
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name localhost;
location /hello {
hello_greeting "Welcome to Nginx Module Development!";
}
}
}
EOF
# 启动 Nginx
sudo /usr/local/nginx/sbin/nginx
# 测试
curl http://localhost/hello
3.3 内存管理
Nginx 使用内存池(Memory Pool)来管理内存,这是其高性能的关键之一:
// 内存池操作函数
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
voidngx_destroy_pool(ngx_pool_t *pool);
voidngx_reset_pool(ngx_pool_t *pool);
// 内存分配
void *ngx_palloc(ngx_pool_t *pool, size_t size); // 对齐分配
void *ngx_pnalloc(ngx_pool_t *pool, size_t size); // 非对齐分配
void *ngx_pcalloc(ngx_pool_t *pool, size_t size); // 对齐分配并清零
ngx_int_tngx_pfree(ngx_pool_t *pool, void *p); // 释放(仅大内存)
内存池工作原理:
-
1. Nginx 为每个请求、连接创建独立的内存池 -
2. 小块内存(< 4096 字节)从内存池中分配 -
3. 大块内存直接调用系统分配器,释放时会归还系统 -
4. 内存池在请求结束时一次性销毁,无需逐个释放
示例:在模块中使用内存池
staticngx_int_t
ngx_http_my_module_handler(ngx_http_request_t *r)
{
// 从请求内存池分配内存
ngx_str_t *my_str = ngx_palloc(r->pool, sizeof(ngx_str_t));
if (my_str == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// 分配字符串数据
my_str->data = ngx_pnalloc(r->pool, 100);
if (my_str->data == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// 使用字符串
my_str->len = ngx_sprintf(my_str->data, "Request URI: %V", &r->uri) - my_str->data;
// 不需要手动释放,请求结束时内存池自动销毁
return NGX_OK;
}
3.4 共享内存
多个工作进程间共享数据需要使用共享内存:
// 共享内存结构
typedefstruct {
ngx_rbtree_t rbtree;
ngx_rbtree_node_t sentinel;
ngx_uint_t count;
} ngx_http_my_shm_ctx_t;
// 初始化共享内存
staticngx_int_t
ngx_http_my_shm_init(ngx_shm_zone_t *zone, void *data)
{
ngx_http_my_shm_ctx_t *octx = data;
ngx_http_my_shm_ctx_t *ctx;
if (octx) {
// 重用旧配置的共享内存
ctx = octx;
return NGX_OK;
}
ctx = ngx_slab_calloc(zone->shm.addr, sizeof(ngx_http_my_shm_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
ngx_rbtree_init(&ctx->rbtree, &ctx->sentinel, ngx_str_rbtree_insert_value);
ctx->count = 0;
return NGX_OK;
}
// 在配置阶段添加共享内存
staticchar *
ngx_http_my_module_init(ngx_conf_t *cf)
{
ngx_str_t name = ngx_string("my_shm_zone");
ngx_shm_zone_t *zone;
zone = ngx_shared_memory_add(cf, &name, 1024 * 1024, &ngx_http_my_module);
if (zone == NULL) {
return NGX_CONF_ERROR;
}
zone->init = ngx_http_my_shm_init;
return NGX_CONF_OK;
}
// 访问共享内存(需要加锁)
staticvoid
ngx_http_my_shm_increment(ngx_http_request_t *r)
{
ngx_http_my_shm_ctx_t *ctx;
ngx_shm_zone_t *zone;
ngx_slab_pool_t *shpool;
zone = ngx_shared_memory_find(&r->cycle->shared_memory,
&ngx_string("my_shm_zone"),
&ngx_http_my_module);
if (zone == NULL) {
return;
}
ctx = zone->data;
shpool = (ngx_slab_pool_t *) zone->shm.addr;
// 加锁
ngx_shmtx_lock(&shpool->mutex);
ctx->count++;
// 解锁
ngx_shmtx_unlock(&shpool->mutex);
}
4. Lua 模块开发 (OpenResty)
4.1 OpenResty 简介
OpenResty 是一个基于 Nginx 与 LuaJIT 的 Web 平台,它将 Lua 解释器嵌入 Nginx,允许使用 Lua 脚本扩展 Nginx 功能。
核心优势:
-
• 热更新:无需重启服务器即可更新代码 -
• 开发效率高:Lua 语法简洁,开发速度快 -
• 性能接近原生:LuaJIT 的 JIT 编译器提供接近 C 的性能 -
• 丰富的生态系统:lua-resty-* 系列库覆盖各种场景
4.2 基础环境搭建
# Ubuntu/Debian 安装
sudo apt-get install -y openresty
# CentOS/RHEL 安装
sudo yum install -y openresty
# macOS 安装
brew install openresty/brew/openresty
# 验证安装
openresty -v
# 输出: nginx version: openresty/1.25.3.1
# 目录结构
mkdir -p ~/openresty-app/{conf,lua,logs}
cd ~/openresty-app
4.3 Hello World 示例
conf/nginx.conf:
worker_processes1;
events {
worker_connections1024;
}
http {
# 设置 Lua 模块搜索路径
lua_package_path"$prefix/lua/?.lua;;";
server {
listen8080;
location /hello {
default_type text/plain;
content_by_lua_block {
ngx.say("Hello, OpenResty!")
}
}
}
}
启动并测试:
# 启动 OpenResty
openresty -p $PWD
# 测试
curl http://localhost:8080/hello
# 输出: Hello, OpenResty!
# 重新加载配置
openresty -p $PWD -s reload
# 停止
openresty -p $PWD -s stop
4.4 Lua 模块开发
lua/hello.lua:
local _M = {}
-- 模块版本
_M._VERSION = '1.0.0'
-- 配置选项
_M.options = {
greeting = "Hello",
suffix = "!"
}
-- 问候函数
function_M.greet(name)
name = name or"World"
returnstring.format("%s, %s%s",
_M.options.greeting,
name,
_M.options.suffix)
end
-- 带错误处理的问候
function_M.safe_greet(name)
local ok, result = pcall(function()
return _M.greet(name)
end)
ifnot ok then
ngx.log(ngx.ERR, "Greet failed: ", result)
return"Hello, Error!"
end
return result
end
-- 批量问候
function_M.greet_many(names)
local results = {}
for i, name inipairs(names) do
results[i] = _M.greet(name)
end
return results
end
return _M
在 Nginx 配置中使用:
location /greet {
content_by_lua_block {
local hello = require "hello"
-- 获取 URL 参数
local name = ngx.var.arg_name
-- 调用模块函数
local message = hello.greet(name)
-- 设置响应头
ngx.header['Content-Type'] = 'application/json'
-- 返回 JSON 响应
local cjson = require "cjson"
ngx.say(cjson.encode({
message = message,
timestamp = ngx.time()
}))
}
}
4.5 常用 Lua 指令
|
|
|
|
init_by_lua_block |
|
|
init_worker_by_lua_block |
|
|
set_by_lua_block |
|
|
rewrite_by_lua_block |
|
|
access_by_lua_block |
|
|
content_by_lua_block |
|
|
header_filter_by_lua_block |
|
|
body_filter_by_lua_block |
|
|
log_by_lua_block |
|
|
完整示例:
http {
lua_shared_dict cache 10m; # 共享内存字典
init_by_lua_block {
-- 预加载模块
require"hello"
-- 全局配置
global_config = {
version = "1.0.0",
env = os.getenv("ENV") or "development"
}
}
init_worker_by_lua_block {
-- 定时任务
local delay = 60 -- 60 秒
local handler
handler = function()
-- 执行定时任务
ngx.log(ngx.INFO, "Running scheduled task")
-- 再次设置定时器
local ok, err = ngx.timer.at(delay, handler)
if not ok then
ngx.log(ngx.ERR, "Failed to create timer: ", err)
end
end
-- 启动定时器
ngx.timer.at(delay, handler)
}
server {
listen8080;
location /api {
access_by_lua_block {
-- 鉴权逻辑
local token = ngx.var.http_authorization
if not token then
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
}
content_by_lua_block {
local cjson = require "cjson"
ngx.say(cjson.encode({
status = "ok",
data = "Protected content"
}))
}
}
location /cache {
content_by_lua_block {
local cache = ngx.shared.cache
local key = ngx.var.arg_key
-- 尝试从缓存获取
local value, flags = cache:get(key)
if value then
ngx.say("Cache hit: ", value)
return
end
-- 模拟数据查询
value = "Expensive data for " .. key
-- 存入缓存,有效期 60 秒
cache:set(key, value, 60)
ngx.say("Cache miss: ", value)
}
}
}
}
4.6 高性能技巧
1. 使用 LuaJIT 的 FFI
-- 使用 FFI 调用 C 函数
local ffi = require"ffi"
local C = ffi.C
ffi.cdef[[
int getpid(void);
unsigned int sleep(unsigned int seconds);
]]
-- 获取当前进程 ID
local pid = C.getpid()
ngx.say("Current PID: ", pid)
2. 连接池
-- MySQL 连接池
local mysql = require"resty.mysql"
localfunctionquery_mysql(sql)
local db, err = mysql:new()
ifnot db then
returnnil, err
end
-- 设置超时
db:set_timeout(1000)
-- 连接数据库
local ok, err = db:connect({
host = "127.0.0.1",
port = 3306,
database = "test",
user = "root",
password = "password",
max_packet_size = 1024 * 1024,
-- 连接池配置
pool = "my_mysql_pool",
pool_size = 10,
backlog = 10
})
ifnot ok then
returnnil, err
end
-- 执行查询
local res, err = db:query(sql)
-- 将连接放回连接池
local ok, err = db:set_keepalive(10000, 50)
ifnot ok then
ngx.log(ngx.ERR, "Failed to set keepalive: ", err)
end
return res, err
end
3. 避免在 Lua 中做 CPU 密集型操作
-- 错误示范:在请求中计算斐波那契数列
functionfibonacci(n)
if n <= 1thenreturn n end
return fibonacci(n - 1) + fibonacci(n - 2)
end
-- 正确做法:使用 C 扩展或预计算
-- 或者使用 ngx.thread 异步处理
5. 实战案例:开发自定义模块
5.1 案例一:C 语言限流模块
实现一个基于令牌桶算法的限流模块:
#include<ngx_config.h>
#include<ngx_core.h>
#include<ngx_http.h>
// 限流配置
typedefstruct {
ngx_uint_t rate; // 每秒令牌数
ngx_uint_t burst; // 桶容量
ngx_shm_zone_t *shm_zone; // 共享内存
} ngx_http_limit_req_loc_conf_t;
// 限流状态
typedefstruct {
ngx_rbtree_node_t node;
ngx_uint_t count; // 当前令牌数
ngx_msec_t last; // 上次更新时间
u_char data[1]; // 键数据
} ngx_http_limit_req_node_t;
// 共享内存上下文
typedefstruct {
ngx_rbtree_t rbtree;
ngx_rbtree_node_t sentinel;
} ngx_http_limit_req_shctx_t;
// 模块上下文
staticngx_http_module_t ngx_http_limit_req_module_ctx = {
NULL, NULL, NULL, NULL,
NULL, NULL,
ngx_http_limit_req_create_loc_conf,
ngx_http_limit_req_merge_loc_conf
};
// 请求处理
staticngx_int_t
ngx_http_limit_req_handler(ngx_http_request_t *r)
{
ngx_http_limit_req_loc_conf_t *lrcf;
ngx_http_limit_req_shctx_t *ctx;
ngx_http_limit_req_node_t *node;
ngx_slab_pool_t *shpool;
ngx_msec_t now;
ngx_int_t excess;
lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module);
if (lrcf->shm_zone == NULL) {
return NGX_DECLINED;
}
ctx = lrcf->shm_zone->data;
shpool = (ngx_slab_pool_t *) lrcf->shm_zone->shm.addr;
// 使用客户端 IP 作为键
ngx_str_t key = r->connection->addr_text;
// 计算哈希
uint32_t hash = ngx_crc32_short(key.data, key.len);
ngx_shmtx_lock(&shpool->mutex);
// 查找或创建节点
node = ngx_http_limit_req_lookup(ctx, &key, hash);
now = ngx_current_msec;
// 计算令牌数
ngx_msec_t elapsed = now - node->last;
node->count += (elapsed * lrcf->rate) / 1000;
if (node->count > lrcf->burst) {
node->count = lrcf->burst;
}
// 检查是否允许请求
if (node->count >= 1) {
node->count--;
excess = 0;
} else {
excess = 1;
}
node->last = now;
ngx_shmtx_unlock(&shpool->mutex);
if (excess) {
return NGX_HTTP_SERVICE_UNAVAILABLE; // 503
}
return NGX_DECLINED; // 继续处理请求
}
5.2 案例二:OpenResty API 网关
实现一个简单的 API 网关,包含路由、鉴权、限流功能:
lua/api_gateway.lua:
local _M = {}
local cjson = require"cjson"
local jwt = require"resty.jwt"
-- 路由配置
_M.routes = {
["/api/users"] = {
upstream = "http://user-service:8080",
auth = true,
rate_limit = 100-- 每分钟 100 请求
},
["/api/orders"] = {
upstream = "http://order-service:8080",
auth = true,
rate_limit = 50
},
["/api/public"] = {
upstream = "http://public-service:8080",
auth = false,
rate_limit = 1000
}
}
-- JWT 验证
function_M.verify_jwt(token)
ifnot token then
returnnil, "Missing token"
end
-- 移除 Bearer 前缀
token = token:gsub("Bearer ", "")
local jwt_obj = jwt:verify(
ngx.var.jwt_secret or"your-secret-key",
token
)
ifnot jwt_obj.verified then
returnnil, jwt_obj.reason
end
return jwt_obj.payload, nil
end
-- 限流检查
function_M.check_rate_limit(key, limit)
local limit_req = require"resty.limit.req"
local lim, err = limit_req.new("my_limit", limit / 60, limit)
ifnot lim then
ngx.log(ngx.ERR, "Failed to create rate limiter: ", err)
returntrue
end
local delay, err = lim:incoming(key, true)
ifnot delay then
if err == "rejected"then
returnfalse
end
ngx.log(ngx.ERR, "Rate limit error: ", err)
returntrue
end
returntrue
end
-- 主处理函数
function_M.handle()
local uri = ngx.var.uri
local route = _M.routes[uri]
ifnot route then
ngx.status = 404
ngx.say(cjson.encode({ error = "Route not found" }))
return
end
-- 限流检查
local client_ip = ngx.var.remote_addr
local allowed, err = _M.check_rate_limit(client_ip, route.rate_limit)
ifnot allowed then
ngx.status = 429
ngx.header['Retry-After'] = '60'
ngx.say(cjson.encode({ error = "Rate limit exceeded" }))
return
end
-- 鉴权
if route.auth then
local auth_header = ngx.var.http_authorization
local payload, err = _M.verify_jwt(auth_header)
ifnot payload then
ngx.status = 401
ngx.header['WWW-Authenticate'] = 'Bearer'
ngx.say(cjson.encode({ error = err }))
return
end
-- 将用户信息传递给后端
ngx.req.set_header("X-User-ID", payload.sub)
ngx.req.set_header("X-User-Role", payload.role)
end
-- 代理到上游服务
local http = require"resty.http"
local httpc = http.new()
local res, err = httpc:request_uri(route.upstream .. uri, {
method = ngx.var.request_method,
headers = ngx.req.get_headers(),
body = ngx.req.get_body_data(),
query = ngx.var.args
})
ifnot res then
ngx.status = 502
ngx.say(cjson.encode({ error = "Upstream error: " .. err }))
return
end
-- 返回响应
ngx.status = res.status
for k, v inpairs(res.headers) do
ngx.header[k] = v
end
ngx.say(res.body)
end
return _M
Nginx 配置:
http {
lua_shared_dict my_limit 10m;
# JWT 密钥
env JWT_SECRET;
server {
listen80;
location /api/ {
access_by_lua_block {
local gateway = require "api_gateway"
gateway.handle()
}
# 如果需要在 Lua 中代理,可以注释掉下面的 proxy_pass
# 目前我们在 Lua 中使用 resty.http 进行代理
proxy_pass http://backend;
}
}
}
6. 性能优化与调试技巧
6.1 性能测试
# 使用 wrk 进行压力测试
wrk -t12 -c400 -d30s http://localhost:8080/api/users
# 使用 wrk2 测试固定速率
wrk2 -t12 -c400 -d30s -R1000 http://localhost:8080/api/users
# 使用 Apache Bench
ab -n 100000 -c 1000 http://localhost:8080/hello
6.2 调试技巧
C 模块调试:
// 使用 nginx 日志系统
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"Processing request: %V", &r->uri);
// 调试宏
#if (NGX_DEBUG)
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"Entering handler");
#endif
OpenResty 调试:
-- 日志输出
ngx.log(ngx.INFO, "Processing request: ", ngx.var.uri)
ngx.log(ngx.ERR, "Error occurred: ", err)
-- 调试变量
local inspect = require"inspect"
ngx.log(ngx.DEBUG, "Table content: ", inspect(my_table))
-- 性能分析
local start = ngx.now()
-- ... 执行代码
local elapsed = ngx.now() - start
ngx.log(ngx.INFO, "Operation took ", elapsed, " seconds")
6.3 火焰图分析
# 使用 SystemTap 生成 Lua 火焰图
sudo stap-lwtools/ngx-lua-bt -p $(pgrep -n nginx) > /tmp/lua.bt
sudo stackcollapse-stap.pl /tmp/lua.bt | flamegraph.pl > /tmp/lua.svg
# 使用 OpenResty XRay(商业工具)
# 自动分析热点代码路径
6.4 内存优化
-- 避免在循环中创建大表
local results = {}
for i = 1, 10000do
-- 错误:每次迭代创建新表
-- results[i] = { id = i, data = get_data(i) }
-- 正确:重用表结构
local item = results[i] or {}
item.id = i
item.data = get_data(i)
results[i] = item
end
-- 及时释放大对象
local big_data = load_large_data()
process(big_data)
big_data = nil-- 允许 GC 回收
-- 使用局部变量缓存全局访问
local ngx_var = ngx.var
local ngx_log = ngx.log
7. 最佳实践与生产经验
7.1 开发流程
-
1. 模块化设计 -
• 每个功能封装为独立模块 -
• 遵循单一职责原则 -
• 提供清晰的 API 接口 -
2. 错误处理 local ok, result = pcall(function()
return risky_operation()
end)
ifnot ok then
ngx.log(ngx.ERR, "Operation failed: ", result)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end -
3. 配置管理 -- 使用环境变量
localconfig = {
redis_host = os.getenv("REDIS_HOST") or"127.0.0.1",
redis_port = tonumber(os.getenv("REDIS_PORT")) or6379,
log_level = os.getenv("LOG_LEVEL") or"info"
}
7.2 安全建议
-
1. 输入验证 localfunctionvalidate_input(data)
iftype(data) ~= "string"then
returnnil, "Invalid input type"
end
if #data > 1000then
returnnil, "Input too long"
end
-- 防止 SQL 注入
if data:match("['\";--]") then
returnnil, "Invalid characters"
end
return data, nil
end -
2. 敏感信息保护 -- 不要在日志中打印敏感信息
ngx.log(ngx.INFO, "User ", user_id, " logged in")
-- 不要: ngx.log(ngx.INFO, "User ", user_id, " with password ", password)
-- 使用安全的密钥存储
local secret = assert(os.getenv("API_SECRET"), "API_SECRET not set")
7.3 监控与告警
-- 自定义指标收集
local statsd = require"resty.statsd"
functionlog_metrics()
local uri = ngx.var.uri
localstatus = ngx.var.status
local request_time = ngx.var.request_time
-- 记录请求延迟
statsd:histogram("nginx.request.latency",
tonumber(request_time) * 1000, -- 转换为毫秒
{ uri = uri, status = status })
-- 记录状态码计数
statsd:increment("nginx.request.count", 1,
{ uri = uri, status = status })
end
-- 在 log_by_lua_block 中调用
log_by_lua_block {
require("metrics").log_metrics()
}
7.4 升级与维护
-
1. 平滑升级 # 测试配置
nginx -t
# 平滑重启
nginx -s reload
# 二进制升级(热升级)
# 1. 启动新的 master 进程
kill -USR2 $(cat /var/run/nginx.pid)
# 2. 优雅关闭旧 worker
kill -WINCH $(cat /var/run/nginx.pid.oldbin)
# 3. 确认无误后关闭旧 master
kill -QUIT $(cat /var/run/nginx.pid.oldbin) -
2. 版本兼容性 -
• 记录依赖的 Nginx/OpenResty 版本 -
• 测试新版本的向后兼容性 -
• 使用 Docker 进行版本隔离测试
学习资源
-
• Nginx 官方开发文档 -
• OpenResty 官方文档 -
• Lua 5.1 参考手册 -
• LuaJIT 文档
参考项目
-
• nginx-development-guide – Nginx 官方源码 -
• openresty/lua-nginx-module – ngx_lua 模块 -
• Kong – 基于 OpenResty 的 API 网关 -
• apisix – 云原生 API 网关
夜雨聆风
