lua-nginx-module核心指令详解与实践

lua-nginx-module核心指令详解与实践

本文深入解析了OpenResty中lua-nginx-module的核心指令,包括content_by_lua、rewrite_by_lua、access_by_lua、header_filter_by_lua和log_by_lua。文章详细介绍了每个指令的工作原理、执行阶段、使用场景和最佳实践,涵盖了从请求处理到响应生成的全流程,以及性能监控、安全防护和日志记录等关键主题。通过丰富的代码示例和架构图,帮助开发者深入理解如何利用这些指令构建高性能、安全的Web应用和服务。

content_by_lua指令原理与使用场景

content_by_lua指令是lua-nginx-module中最核心的指令之一,它允许开发者在Nginx的内容处理阶段(content phase)直接执行Lua代码来生成HTTP响应内容。这个指令为Nginx提供了强大的动态内容生成能力,使得开发者可以在不依赖外部应用服务器的情况下,直接在Nginx层面实现复杂的业务逻辑。

指令原理与架构设计

content_by_lua指令的工作原理基于Nginx的模块化架构和LuaJIT虚拟机。当Nginx处理HTTP请求到达内容阶段时,content_by_lua指令会启动LuaJIT虚拟机来执行指定的Lua代码。

执行流程

mermaid

核心组件交互

content_by_lua指令的实现涉及多个核心组件的协同工作:

  1. Lua虚拟机管理:每个Nginx worker进程维护一个LuaJIT虚拟机实例,所有请求共享这个虚拟机但通过轻量级协程隔离上下文
  2. 内存管理:采用内存池和引用计数机制,确保Lua模块在worker级别持久化,减少重复加载开销
  3. 异步IO处理:通过Nginx事件模型与Lua协程结合,实现非阻塞的网络操作

三种使用形式

content_by_lua指令支持三种不同的使用形式,适应不同的开发场景:

1. content_by_lua(字符串形式)
location /hello {
    content_by_lua '
        ngx.say("Hello, World!")
        ngx.log(ngx.INFO, "Request processed")
    ';
}

适用场景:简单的内联脚本,代码量较少的情况

2. content_by_lua_block(代码块形式)
location /api {
    content_by_lua_block {
        local args = ngx.req.get_uri_args()
        local name = args.name or "Guest"
        
        ngx.header["Content-Type"] = "application/json"
        ngx.say('{"message": "Hello, ' .. name .. '"}')
    }
}

适用场景:推荐使用的方式,代码可读性更好,支持复杂的逻辑

3. content_by_lua_file(文件形式)
location /user {
    content_by_lua_file /path/to/user_handler.lua;
}

适用场景:复杂的业务逻辑,代码需要模块化和重用

核心功能特性

1. 动态内容生成

content_by_lua最核心的功能是动态生成HTTP响应内容:

location /dynamic {
    content_by_lua_block {
        -- 获取当前时间
        local now = ngx.localtime()
        
        -- 生成动态内容
        ngx.say("Current time: ", now)
        ngx.say("Request URI: ", ngx.var.request_uri)
        ngx.say("Client IP: ", ngx.var.remote_addr)
    }
}
2. 请求参数处理

强大的请求参数解析和处理能力:

location /search {
    content_by_lua_block {
        ngx.req.read_body()
        local args = ngx.req.get_uri_args()
        local post_args = ngx.req.get_post_args()
        
        -- 参数验证和处理
        local query = args.q or ""
        if query == "" then
            ngx.exit(ngx.HTTP_BAD_REQUEST)
        end
        
        -- 执行搜索逻辑
        local results = perform_search(query)
        ngx.say(json.encode(results))
    }
}
3. 数据库和缓存操作

支持直接操作后端存储:

location /user-profile {
    content_by_lua_block {
        local redis = require "resty.redis"
        local red = redis:new()
        
        local ok, err = red:connect("127.0.0.1", 6379)
        if not ok then
            ngx.log(ngx.ERR, "Redis连接失败: ", err)
            ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
        end
        
        local user_id = ngx.var.arg_user_id
        local user_data = red:get("user:" .. user_id)
        
        if user_data == ngx.null then
            ngx.exit(ngx.HTTP_NOT_FOUND)
        end
        
        ngx.header["Content-Type"] = "application/json"
        ngx.say(user_data)
    }
}
4. 子请求和API聚合

支持发起子请求并聚合结果:

location /aggregate {
    content_by_lua_block {
        local res1 = ngx.location.capture("/api/users")
        local res2 = ngx.location.capture("/api/products")
        
        if res1.status ~= 200 or res2.status ~= 200 then
            ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
        end
        
        local data = {
            users = json.decode(res1.body),
            products = json.decode(res2.body)
        }
        
        ngx.header["Content-Type"] = "application/json"
        ngx.say(json.encode(data))
    }
}

性能优化实践

1. 代码缓存机制

content_by_lua采用智能的代码缓存策略:

mermaid

2. 内存管理最佳实践
location /optimized {
    content_by_lua_block {
        -- 避免在循环中创建大量临时表
        local results = {}
        for i = 1, 1000 do
            -- 使用预分配的方式
            results[i] = process_item(i)
        end
        
        -- 及时释放不再使用的变量
        results = nil
        collectgarbage()
        
        ngx.say("Processing completed")
    }
}

安全注意事项

1. 输入验证和过滤
location /safe-handler {
    content_by_lua_block {
        local args = ngx.req.get_uri_args()
        
        -- 输入验证
        local username = args.username
        if not username or #username > 50 then
            ngx.exit(ngx.HTTP_BAD_REQUEST)
        end
        
        -- SQL注入防护
        if string.match(username, "[%c%p]") then
            ngx.exit(ngx.HTTP_BAD_REQUEST)
        end
        
        -- 安全处理
        ngx.say("Hello, ", ngx.quote_sql_str(username))
    }
}
2. 文件路径安全
location ~ ^/app/([a-zA-Z0-9_-]+)$ {
    set $endpoint $1;
    content_by_lua_file /path/to/handlers/$endpoint.lua;
}

典型应用场景

1. API网关和路由
location ~ ^/api/(v[0-9]+)/(.*)$ {
    set $api_version $1;
    set $api_endpoint $2;
    
    content_by_lua_block {
        local router = require "api_router"
        router.dispatch(ngx.var.api_version, ngx.var.api_endpoint)
    }
}
2. 实时数据处理
location /realtime {
    content_by_lua_block {
        -- 处理WebSocket或SSE连接
        local websocket = require "resty.websocket"
        local wb, err = websocket:new()
        
        if not wb then
            ngx.log(ngx.ERR, "WebSocket创建失败: ", err)
            ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
        end
        
        -- 实时数据推送
        while true do
            local data = get_realtime_data()
            local bytes, err = wb:send_text(data)
            if not bytes then
                break
            end
            ngx.sleep(1)  -- 每秒推送一次
        end
    }
}
3. 身份验证和授权
location /protected {
    access_by_lua_block {
        -- 访问控制逻辑
        if not check_authentication() then
            ngx.exit(ngx.HTTP_UNAUTHORIZED)
        end
    }
    
    content_by_lua_block {
        -- 业务逻辑处理
        local user_info = get_user_info()
        ngx.say(json.encode(user_info))
    }
}

错误处理和日志记录

1. 结构化错误处理
location /robust {
    content_by_lua_block {
        local status, err = pcall(function()
            -- 业务逻辑
            if not validate_input() then
                error("Invalid input")
            end
            
            return process_request()
        end)
        
        if not status then
            ngx.log(ngx.ERR, "处理失败: ", err)
            ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
        end
        
        ngx.say("Success: ", err)
    }
}
2. 详细的日志记录
location /logged {
    log_by_lua_block {
        -- 请求完成后记录日志
        local request_time = tonumber(ngx.var.request_time)
        ngx.log(ngx.INFO, "请求处理完成, 耗时: ", request_time, "s")
    }
    
    content_by_lua_block {
        -- 业务处理
        ngx.say("Request processed")
        
        -- 记录业务日志
        ngx.log(ngx.INFO, "用户操作: ", ngx.var.remote_user)
    }
}

性能监控和调试

1. 执行时间统计
location /monitored {
    content_by_lua_block {
        local start_time = ngx.now()
        
        -- 业务处理
        process_business_logic()
        
        local elapsed = ngx.now() - start_time
        ngx.log(ngx.INFO, "业务处理耗时: ", elapsed, "秒")
        
        if elapsed > 1.0 then
            ngx.log(ngx.WARN, "处理时间过长: ", elapsed, "秒")
        end
        
        ngx.say("Processing completed in ", elapsed, " seconds")
    }
}

content_by_lua指令为Nginx提供了强大的动态内容处理能力,通过合理的架构设计和最佳实践,可以在保持高性能的同时实现复杂的业务逻辑。掌握其原理和使用场景对于构建高性能的Web应用和服务至关重要。

rewrite_by_lua与access_by_lua指令区别

在OpenResty的lua-nginx-module中,rewrite_by_luaaccess_by_lua是两个核心的请求处理指令,它们在Nginx请求处理流程中扮演着不同但互补的角色。理解它们的区别对于构建高效、安全的Web应用至关重要。

执行阶段与处理流程差异

首先,让我们通过流程图来直观展示这两个指令在Nginx请求处理流程中的位置:

mermaid

从流程图中可以看出,rewrite_by_lua在**重写阶段(Rewrite Phase)执行,而access_by_lua访问控制阶段(Access Phase)**执行。这是它们最根本的区别。

功能定位与使用场景

rewrite_by_lua:URI处理与重定向

rewrite_by_lua主要用于URI的重写、参数处理和内部重定向逻辑。它是在Nginx的rewrite阶段执行的,这意味着它可以修改请求的URI、参数,甚至发起内部重定向。

典型使用场景:

  • 动态URL重写
  • 参数验证和规范化
  • A/B测试路由
  • 基于条件的内部重定向

示例代码:

rewrite_by_lua_block {
    -- 基于用户代理的重写
    local ua = ngx.var.http_user_agent
    if ua and ua:match("Mobile") then
        ngx.var.uri = "/mobile" .. ngx.var.uri
    end
    
    -- 参数验证
    local args = ngx.req.get_uri_args()
    if not args.token or args.token == "" then
        ngx.redirect("/login?redirect=" .. ngx.var.request_uri)
    end
}
access_by_lua:访问控制与安全验证

access_by_lua专门用于访问控制、权限验证和安全检查。它在access阶段执行,这个阶段专门处理访问权限相关的逻辑。

典型使用场景:

  • API密钥验证
  • 用户身份认证
  • 频率限制
  • IP黑白名单
  • 请求过滤和安全检查

示例代码:

access_by_lua_block {
    -- IP黑名单检查
    local blacklist = {"192.168.1.100", "10.0.0.5"}
    local client_ip = ngx.var.remote_addr
    
    for _, ip in ipairs(blacklist) do
        if client_ip == ip then
            ngx.exit(ngx.HTTP_FORBIDDEN)
        end
    end
    
    -- API密钥验证
    local api_key = ngx.var.arg_api_key or ngx.var.http_x_api_key
    if not validate_api_key(api_key) then
        ngx.exit(ngx.HTTP_UNAUTHORIZED)
    end
}

技术实现细节对比

从源码层面来看,这两个指令的实现有着明显的差异:

特性rewrite_by_luaaccess_by_lua
执行阶段NGX_HTTP_REWRITE_PHASENGX_HTTP_ACCESS_PHASE
主要用途URI重写、参数处理访问控制、权限验证
可修改URI✅ 是❌ 否
可发起重定向✅ 是❌ 否
典型返回值NGX_DECLINEDNGX_OK/NGX_HTTP_*
头部发送时机可发送响应头通常不发送响应头

执行顺序与依赖关系

在实际配置中,这两个指令通常需要配合使用:

location /api {
    # 先进行URI重写和参数处理
    rewrite_by_lua_block {
        -- 规范化参数
        local args = ngx.req.get_uri_args()
        if args.page then
            args.page = tonumber(args.page) or 1
            ngx.req.set_uri_args(args)
        end
    }
    
    # 再进行访问控制和安全验证
    access_by_lua_block {
        -- 验证访问权限
        if not check_access(ngx.var.uri) then
            ngx.exit(ngx.HTTP_FORBIDDEN)
        end
    }
    
    # 最后处理内容
    content_by_lua_block {
        -- 业务逻辑处理
        process_request()
    }
}

性能考虑与最佳实践

  1. 执行顺序优化:将轻量级的重写操作放在rewrite_by_lua中,重量级的安全检查放在access_by_lua
  2. 缓存策略:在rewrite_by_lua中可以使用ngx.ctx共享数据,避免重复计算
  3. 错误处理access_by_lua中应该使用ngx.exit()直接终止请求,而rewrite_by_lua通常返回NGX_DECLINED继续处理
access_by_lua_block {
    -- 正确的访问拒绝方式
    if not has_permission() then
        ngx.exit(ngx.HTTP_FORBIDDEN)
    end
}

rewrite_by_lua_block {
    -- 正确的重写处理方式
    if need_rewrite() then
        ngx.req.set_uri("/new/path")
    end
    return NGX_DECLINED
}

常见误区与注意事项

  1. 不要混淆阶段:在access_by_lua中尝试修改URI会导致不可预期的行为
  2. 响应头处理:在rewrite_by_lua中发送响应头后,后续阶段可能无法正常工作
  3. 子请求处理:两个指令在子请求中都会执行,需要注意上下文隔离

通过深入理解rewrite_by_luaaccess_by_lua的区别,开发者可以更好地设计OpenResty应用的架构,确保每个阶段都专注于其最擅长的职责,从而构建出既高效又安全的Web服务。

header_filter_by_lua响应头处理技巧

在Nginx的请求处理流程中,响应头过滤阶段是一个关键环节,它允许我们在响应发送给客户端之前对响应头进行最后的修改和调整。header_filter_by_lua指令为开发者提供了强大的Lua脚本能力,使得响应头的处理变得灵活而高效。

指令概述与语法

header_filter_by_lua系列指令包含三种形式:

  • header_filter_by_lua:直接内联Lua代码(不推荐使用)
  • header_filter_by_lua_block:使用代码块形式的Lua脚本(推荐)
  • header_filter_by_lua_file:从外部文件加载Lua脚本

基本语法:

location /api {
    proxy_pass http://backend;
    header_filter_by_lua_block {
        -- Lua代码在这里执行
        ngx.header["X-Custom-Header"] = "processed"
        ngx.header["Content-Type"] = "application/json; charset=utf-8"
    }
}

核心功能与使用场景

1. 响应头动态修改

header_filter_by_lua最常见的用途是动态修改响应头信息。通过ngx.header表,我们可以轻松地添加、修改或删除响应头字段。

location /dynamic {
    proxy_pass http://backend;
    header_filter_by_lua_block {
        -- 添加自定义响应头
        ngx.header["X-Processed-By"] = "OpenResty"
        ngx.header["X-Request-ID"] = ngx.var.request_id
        
        -- 修改Content-Type
        if ngx.header["Content-Type"] then
            ngx.header["Content-Type"] = ngx.header["Content-Type"] .. "; charset=utf-8"
        end
        
        -- 删除不必要的头信息
        ngx.header["Server"] = nil
        ngx.header["X-Powered-By"] = nil
    }
}
2. 安全头信息增强

在安全敏感的应用程序中,使用header_filter_by_lua可以统一添加安全相关的HTTP头信息:

server {
    header_filter_by_lua_block {
        -- 添加安全头信息
        ngx.header["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
        ngx.header["X-Content-Type-Options"] = "nosniff"
        ngx.header["X-Frame-Options"] = "DENY"
        ngx.header["X-XSS-Protection"] = "1; mode=block"
        ngx.header["Content-Security-Policy"] = "default-src 'self'"
        
        -- 根据响应状态码动态设置缓存头
        if ngx.status == 200 then
            ngx.header["Cache-Control"] = "public, max-age=300"
        elseif ngx.status >= 400 then
            ngx.header["Cache-Control"] = "no-cache, no-store, must-revalidate"
        end
    }
}
3. 响应头验证与修正

在处理代理响应时,经常需要验证和修正来自后端的响应头:

location /proxy {
    proxy_pass http://upstream;
    header_filter_by_lua_block {
        -- 确保Content-Length正确性
        local content_length = tonumber(ngx.header["Content-Length"])
        if content_length and content_length < 0 then
            ngx.header["Content-Length"] = nil
        end
        
        -- 规范化Location头(重定向场景)
        if ngx.header.Location then
            local location = ngx.header.Location
            if not location:find("^https?://") then
                ngx.header.Location = "https://" .. ngx.var.host .. location
            end
        end
        
        -- 处理Set-Cookie头的安全属性
        local set_cookie = ngx.header["Set-Cookie"]
        if set_cookie then
            if type(set_cookie) == "table" then
                for i, cookie in ipairs(set_cookie) do
                    if not cookie:find("; Secure") and ngx.var.https == "on" then
                        set_cookie[i] = cookie .. "; Secure"
                    end
                    if not cookie:find("; HttpOnly") then
                        set_cookie[i] = cookie .. "; HttpOnly"
                    end
                end
            end
        end
    }
}

高级技巧与最佳实践

1. 性能优化策略

由于header_filter_by_lua在每个响应中都会执行,性能优化至关重要:

header_filter_by_lua_block {
    -- 使用局部变量缓存频繁访问的值
    local status = ngx.status
    local headers = ngx.header
    
    -- 避免不必要的操作:只在特定状态下处理
    if status == 200 or status == 304 then
        headers["Cache-Control"] = "public, max-age=3600"
        headers["ETag"] = ngx.md5(ngx.var.request_uri .. ngx.var.query_string)
    end
    
    -- 使用位运算进行状态码检查(性能优化)
    if (status >= 200 and status < 300) or status == 304 then
        headers["X-Cacheable"] = "true"
    else
        headers["X-Cacheable"] = "false"
    end
}
2. 条件性头处理

根据请求特征动态调整响应头:

header_filter_by_lua_block {
    local user_agent = ngx.var.http_user_agent or ""
    local accept = ngx.var.http_accept or ""
    
    -- 根据User-Agent调整响应头
    if user_agent:find("MSIE") then
        ngx.header["X-UA-Compatible"] = "IE=edge,chrome=1"
    end
    
    -- 根据Accept头提供适当的Content-Type
    if accept:find("application/json") then
        ngx.header["Content-Type"] = "application/json"
    elseif accept:find("application/xml") then
        ngx.header["Content-Type"] = "application/xml"
    end
    
    -- 根据客户端IP设置调试头
    local client_ip = ngx.var.remote_addr
    if client_ip == "192.168.1.100" then  -- 开发环境IP
        ngx.header["X-Debug-Mode"] = "enabled"
        ngx.header["X-Request-Time"] = ngx.now() - (ngx.req.start_time() or 0)
    end
}
3. 多值头处理

正确处理包含多个值的响应头:

header_filter_by_lua_block {
    -- 处理多个Set-Cookie头
    local set_cookies = ngx.header["Set-Cookie"]
    if set_cookies and type(set_cookies) == "table" then
        local processed_cookies = {}
        for i, cookie in ipairs(set_cookies) do
            -- 为每个cookie添加安全属性
            if not cookie:find("; Secure") then
                cookie = cookie .. "; Secure"
            end
            if not cookie:find("; HttpOnly") then
                cookie = cookie .. "; HttpOnly"
            end
            processed_cookies[i] = cookie
        end
        ngx.header["Set-Cookie"] = processed_cookies
    end
    
    -- 处理多个Link头(HTTP/2服务器推送)
    local link_headers = ngx.header["Link"]
    if link_headers and type(link_headers) == "table" then
        local new_links = {}
        for _, link in ipairs(link_headers) do
            if link:find("rel=\"preload\"") then
                table.insert(new_links, link)
            end
        end
        if #new_links > 0 then
            ngx.header["Link"] = new_links
        end
    end
}

实际应用案例

1. API网关响应头标准化
# API网关统一的响应头处理
header_filter_by_lua_block {
    -- 添加API版本信息
    ngx.header["X-API-Version"] = "v2.1.0"
    
    -- 添加请求追踪信息
    ngx.header["X-Request-ID"] = ngx.var.request_id
    ngx.header["X-Response-Time"] = string.format("%.3f", ngx.now() - (ngx.req.start_time() or 0))
    
    -- 标准化错误响应
    if ngx.status >= 400 then
        ngx.header["Content-Type"] = "application/problem+json"
        local error_info = {
            type = "https://example.com/errors/" .. ngx.status,
            title = "An error occurred",
            status = ngx.status,
            instance = ngx.var.request_uri
        }
        -- 这里可以记录错误日志或发送到监控系统
    end
    
    -- 添加速率限制信息
    local remaining = tonumber(ngx.var.rate_limit_remaining) or -1
    if remaining >= 0 then
        ngx.header["X-RateLimit-Limit"] = ngx.var.rate_limit_limit or "60"
        ngx.header["X-RateLimit-Remaining"] = tostring(remaining)
        ngx.header["X-RateLimit-Reset"] = ngx.var.rate_limit_reset or os.time() + 3600
    end
}
2. 静态资源优化头处理
location ~* \.(js|css|png|jpg|jpeg|gif|ico|woff2?|ttf|eot|svg)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    
    header_filter_by_lua_block {
        -- 根据文件类型设置更精确的缓存策略
        local uri = ngx.var.uri
        if uri:match("%.js$") then
            ngx.header["Content-Type"] = "application/javascript; charset=utf-8"
        elseif uri:match("%.css$") then
            ngx.header["Content-Type"] = "text/css; charset=utf-8"
        elseif uri:match("%.svg$") then
            ngx.header["Content-Type"] = "image/svg+xml"
        end
        
        -- 添加资源完整性校验头
        if ngx.header["ETag"] then
            local etag = ngx.header["ETag"]
            ngx.header["X-Content-Hash"] = etag:gsub('"', '')
        end
        
        -- 为重要资源添加预加载提示
        if uri:match("critical%.css$") or uri:match("runtime%.js$") then
            ngx.header["Link"] = '<' .. uri .. '>; rel=preload; as=' .. 
                                (uri:match("%.css$") and "style" or "script")
        end
    }
}

注意事项与限制

在使用header_filter_by_lua时需要注意以下限制:

  1. API限制:在该上下文中无法使用输出API(如ngx.say)、控制API(如ngx.redirect)、子请求API和cosocket API
  2. 性能考虑:避免在头过滤阶段执行耗时操作,特别是I/O操作
  3. 内存使用:谨慎处理大型头信息,避免内存泄漏
  4. 错误处理:确保Lua代码的健壮性,任何错误都会导致请求失败
header_filter_by_lua_block {
    -- 安全的头处理,包含错误处理
    local ok, err = pcall(function()
        -- 主要的头处理逻辑
        if ngx.status == 200 then
            ngx.header["X-Processed-At"] = os.date("%Y-%m-%d %H:%M:%S")
        end
        
        -- 复杂的头处理逻辑
        process_complex_headers()
    end)
    
    if not ok then
        -- 记录错误但不让请求失败
        ngx.log(ngx.ERR, "header_filter error: ", err)
        -- 可以设置一个错误标识头
        ngx.header["X-Header-Processing-Error"] = "true"
    end
}

## log_by_lua日志记录与性能监控

在现代Web应用架构中,高效的日志记录和性能监控是确保系统稳定性和可观测性的关键要素。OpenResty通过`log_by_lua`指令提供了强大的日志处理能力,使开发者能够在请求处理的最后阶段执行自定义的Lua代码,实现精细化的日志记录和性能数据采集。

### log_by_lua的工作原理与执行时机

`log_by_lua`指令在Nginx的日志处理阶段执行,这是请求处理流程中的最后一个阶段。在这个阶段,请求已经完成,响应已经发送给客户端,此时执行日志记录操作不会影响请求的响应时间。

![mermaid](https://kroki.io/mermaid/svg/eNptjkEKgzAQRfeeYi7gBbJwYwtdlCJq15KmgwRikmZG0Ns3jYgVneX7n_eH8DOiVXjRsg9yyCCel4G10l5ahtJotHzAj17b6UCfnjjgieXu-q6ZiXHIUrZY86JIHgG3tq2g_r1Cy1biMV-NAqrgFBLtWmuab6YayTtLuLMscwIatO_zxvaggOuEamQEE9lr7swoU3Wr_M1FCKUbvEHGL5Tybic)

### 基础日志记录功能

#### 1. 基本日志输出

`log_by_lua`最基础的用法是记录请求的基本信息:

```nginx
location /api {
    proxy_pass http://backend;
    
    log_by_lua '
        ngx.log(ngx.INFO, "Request completed: ", ngx.var.uri, 
                " Status: ", ngx.var.status,
                " Upstream: ", ngx.var.upstream_addr,
                " Response time: ", ngx.var.upstream_response_time, "s")
    ';
}
2. 多级别日志控制

OpenResty支持多种日志级别,可以根据需要选择适当的级别:

-- 在log_by_lua中定义日志级别常量
local EMERG = ngx.EMERG    -- 紧急情况
local ALERT = ngx.ALERT    -- 需要立即采取行动
local CRIT = ngx.CRIT      -- 严重情况
local ERR = ngx.ERR        -- 错误
local WARN = ngx.WARN      -- 警告
local NOTICE = ngx.NOTICE  -- 普通但重要的情况
local INFO = ngx.INFO      -- 信息性消息
local DEBUG = ngx.DEBUG    -- 调试级别信息

log_by_lua '
    if ngx.var.status >= 500 then
        ngx.log(ngx.ERR, "Server error: ", ngx.var.status)
    elseif ngx.var.status >= 400 then
        ngx.log(ngx.WARN, "Client error: ", ngx.var.status)
    else
        ngx.log(ngx.INFO, "Request successful: ", ngx.var.status)
    end
';

性能监控与指标采集

1. 响应时间监控

通过log_by_lua可以精确测量请求的处理时间:

location /monitored {
    access_by_lua '
        ngx.ctx.start_time = ngx.now()
    ';
    
    proxy_pass http://backend;
    
    log_by_lua '
        local elapsed = ngx.now() - ngx.ctx.start_time
        ngx.log(ngx.INFO, "Request timing - Total: ", elapsed, 
                "s, Upstream: ", ngx.var.upstream_response_time, "s")
    ';
}
2. 使用共享字典进行指标聚合

共享字典(shared dict)是在多个worker进程间共享内存的高效数据结构,非常适合用于指标聚合:

http {
    lua_shared_dict metrics 10m;
    
    server {
        location /api {
            proxy_pass http://backend;
            
            log_by_lua '
                local metrics = ngx.shared.metrics
                
                -- 统计请求计数
                local key_count = "request_count:" .. ngx.var.status
                metrics:incr(key_count, 1)
                
                -- 记录响应时间
                if ngx.var.upstream_response_time then
                    local resp_time = tonumber(ngx.var.upstream_response_time)
                    local key_time = "response_time:" .. ngx.var.status
                    local sum_key = key_time .. ":sum"
                    local count_key = key_time .. ":count"
                    
                    metrics:incr(sum_key, resp_time * 1000)  -- 转换为毫秒
                    metrics:incr(count_key, 1)
                end
                
                -- 记录上游服务器性能
                if ngx.var.upstream_addr then
                    local upstream_key = "upstream:" .. ngx.var.upstream_addr
                    metrics:incr(upstream_key .. ":requests", 1)
                    
                    if ngx.var.upstream_response_time then
                        local resp_time = tonumber(ngx.var.upstream_response_time)
                        metrics:incr(upstream_key .. ":time_sum", resp_time * 1000)
                    end
                end
            ';
        }
        
        location /metrics {
            content_by_lua '
                local metrics = ngx.shared.metrics
                local keys = metrics:get_keys(0)  -- 获取所有key
                
                ngx.header["Content-Type"] = "text/plain"
                
                for _, key in ipairs(keys) do
                    local value = metrics:get(key)
                    if value then
                        ngx.say(key, " ", value)
                    end
                end
            ';
        }
    }
}

高级日志处理模式

1. 结构化日志输出

对于现代日志处理系统,结构化日志更易于分析和处理:

log_by_lua '
    local log_data = {
        timestamp = ngx.localtime(),
        client_ip = ngx.var.remote_addr,
        method = ngx.var.request_method,
        uri = ngx.var.uri,
        status = tonumber(ngx.var.status),
        response_time = tonumber(ngx.var.upstream_response_time or 0),
        request_length = tonumber(ngx.var.request_length or 0),
        bytes_sent = tonumber(ngx.var.bytes_sent or 0),
        user_agent = ngx.var.http_user_agent,
        referer = ngx.var.http_referer
    }
    
    -- 转换为JSON格式
    local cjson = require "cjson"
    ngx.log(ngx.INFO, cjson.encode(log_data))
';
2. 条件日志记录与采样

为了避免日志量过大,可以实现条件日志记录和采样机制:

log_by_lua '
    -- 只记录慢请求(响应时间超过1秒)
    if ngx.var.upstream_response_time and 
       tonumber(ngx.var.upstream_response_time) > 1 then
        ngx.log(ngx.WARN, "Slow request: ", ngx.var.uri, 
                " Time: ", ngx.var.upstream_response_time, "s")
    end
    
    -- 5%的采样率记录详细日志
    if math.random(100) <= 5 then
        ngx.log(ngx.INFO, "Sampled request - URI: ", ngx.var.uri,
                " Status: ", ngx.var.status,
                " Referer: ", ngx.var.http_referer or "none")
    end
    
    -- 记录所有错误请求
    if ngx.var.status >= 400 then
        ngx.log(ngx.ERR, "Error request - URI: ", ngx.var.uri,
                " Status: ", ngx.var.status,
                " Client: ", ngx.var.remote_addr)
    end
';

性能监控最佳实践

1. 监控指标分类

通过log_by_lua可以收集多种类型的性能指标:

指标类型示例指标描述
吞吐量请求计数、QPS系统处理能力
延迟响应时间、上游时间请求处理速度
错误率HTTP错误计数系统稳定性
资源使用内存、连接数资源消耗情况
2. 实时性能仪表板

结合共享字典和定时器,可以创建实时性能监控:

http {
    lua_shared_dict performance 5m;
    
    init_worker_by_lua '
        local delay = 5  -- 每5秒聚合一次指标
        local handler
        handler = function(premature)
            if premature then
                return
            end
            
            local perf = ngx.shared.performance
            local keys = perf:get_keys(0)
            
            -- 这里可以将聚合数据发送到监控系统
            -- 如Prometheus, InfluxDB, Elasticsearch等
            
            local ok, err = ngx.timer.at(delay, handler)
            if not ok then
                ngx.log(ngx.ERR, "Failed to create timer: ", err)
            end
        end
        
        local ok, err = ngx.timer.at(delay, handler)
        if not ok then
            ngx.log(ngx.ERR, "Failed to create timer: ", err)
        end
    ';
}

错误处理与容错机制

log_by_lua中执行复杂的日志操作时,需要确保错误不会影响主要的请求处理流程:

log_by_lua '
    local ok, err = pcall(function()
        -- 复杂的日志处理逻辑
        local metrics = ngx.shared.metrics
        
        -- 安全地增加计数器
        local newval, err = metrics:incr("request_count", 1, 0)
        if not newval and err ~= "not found" then
            ngx.log(ngx.ERR, "Failed to increment counter: ", err)
        end
        
        -- 记录性能数据
        if ngx.var.upstream_response_time then
            local resp_time = tonumber(ngx.var.upstream_response_time)
            local sum, err = metrics:incr("response_time_sum", resp_time * 1000, 0)
            if not sum then
                ngx.log(ngx.ERR, "Failed to record response time: ", err)
            end
        end
    end)
    
    if not ok then
        ngx.log(ngx.ERR, "Error in log_by_lua: ", err)
    end
';

性能优化建议

  1. 避免阻塞操作log_by_lua中不应执行网络IO等阻塞操作
  2. 使用共享字典:对于跨worker的指标聚合,使用共享字典而非外部存储
  3. 控制日志量:通过采样和条件记录避免日志爆炸
  4. 异步处理:对于复杂的日志处理,可以考虑使用ngx.timer.at进行异步处理
  5. 监控自身性能:定期检查log_by_lua的执行时间,确保不会成为性能瓶颈

通过合理运用log_by_lua指令,开发者可以构建出高效、灵活且功能强大的日志记录和性能监控系统,为应用的稳定运行和持续优化提供坚实的数据支撑。

总结

lua-nginx-module的核心指令为Nginx提供了强大的Lua脚本处理能力,使得开发者能够在不同的请求处理阶段实现精细化的控制。content_by_lua负责动态内容生成,rewrite_by_lua处理URI重写,access_by_lua进行访问控制,header_filter_by_lua管理响应头,log_by_lua实现日志记录和性能监控。这些指令协同工作,形成了一个完整的请求处理流水线。通过深入理解每个指令的特性和适用场景,结合最佳实践和性能优化策略,开发者可以构建出高效、安全、可观测的现代Web应用架构,充分发挥OpenResty在高并发场景下的优势。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值