乐于分享
好东西不私藏

Nginx 插件开发深度解析:从 C 模块到 OpenResty

Nginx 插件开发深度解析:从 C 模块到 OpenResty

  1. 1. 引言:为什么选择 Nginx 插件开发
  2. 2. Nginx 架构与模块系统
  3. 3. C 语言模块开发
  4. 4. Lua 模块开发 (OpenResty)
  5. 5. 实战案例:开发自定义模块
  6. 6. 性能优化与调试技巧
  7. 7. 最佳实践与生产经验

1. 引言:为什么选择 Nginx 插件开发

1.1 Nginx 的核心优势

Nginx 作为当今最流行的 Web 服务器和反向代理服务器之一,其高性能和模块化设计使其能够处理超过 40% 的全球网站流量。Nginx 的模块系统是其灵活性的核心,允许开发者通过插件扩展其功能。

1.2 插件开发的两大方向

当前 Nginx 插件开发主要有两条技术路线:

  1. 1. C 语言原生模块开发
    • • 性能极致,直接编译进 Nginx 核心
    • • 适合高性能场景:自定义负载均衡、协议处理
    • • 开发门槛高,需要深入理解 Nginx 内部架构
  2. 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
核心功能,字符串、数组、日志等
ngx_core_module
HTTP
HTTP 协议处理
ngx_http_proxy_module
Stream
TCP/UDP 流量代理
ngx_stream_proxy_module
Event
事件通知机制
ngx_epoll_module
Mail
邮件协议支持
ngx_mail_pop3_module

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. 1. Nginx 为每个请求、连接创建独立的内存池
  2. 2. 小块内存(< 4096 字节)从内存池中分配
  3. 3. 大块内存直接调用系统分配器,释放时会归还系统
  4. 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
配置加载
Master 进程初始化
init_worker_by_lua_block
Worker 启动
每个 Worker 进程启动时
set_by_lua_block
Server/Location 重写
设置变量值
rewrite_by_lua_block
Rewrite 阶段
URL 重写
access_by_lua_block
Access 阶段
访问控制
content_by_lua_block
Content 阶段
内容生成
header_filter_by_lua_block
响应头过滤
修改响应头
body_filter_by_lua_block
响应体过滤
修改响应体
log_by_lua_block
Log 阶段
日志记录

完整示例:

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(1000050)
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 = {
NULLNULLNULLNULL,
NULLNULL,
    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->log0
"Processing request: %V", &r->uri);

// 调试宏
#if (NGX_DEBUG)
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log0,
"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 = 110000do
-- 错误:每次迭代创建新表
-- 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. 1. 模块化设计
    • • 每个功能封装为独立模块
    • • 遵循单一职责原则
    • • 提供清晰的 API 接口
  2. 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. 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. 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. 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. 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. 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 网关
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Nginx 插件开发深度解析:从 C 模块到 OpenResty

评论 抢沙发

7 + 7 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮