Skip to content

OpenResty vs Nginx. #30

@leno23

Description

@leno23

OpenResty vs Nginx: 完整指南

什么是 Nginx?

Nginx 是一个高性能的 HTTP 服务器和反向代理,同时也是 IMAP/POP3 代理服务器。它以以下特性著称:

  • 高并发处理能力
  • 低内存占用
  • 静态内容服务
  • 负载均衡
  • 反向代理

什么是 OpenResty?

OpenResty 是一个功能完整的 Web 平台,它将 Nginx 与 LuaJIT(Lua 的即时编译器)整合在一起。本质上它是:

  • Nginx 核心 + LuaJIT + 许多精心编写的 Lua 库
  • 一个强大的 Web 应用服务器
  • 一个动态 Web 平台

核心差异

1. 核心架构

特性 Nginx OpenResty
基础 纯 C 实现 Nginx + LuaJIT
可扩展性 仅 C 模块 Lua 脚本 + C 模块
动态逻辑 有限(通过配置) 完整编程能力
学习曲线 基于配置 编程 + 配置

2. 编程能力

Nginx:

# 仅限于配置指令
location /api {
    proxy_pass http://backend;
    proxy_set_header X-Real-IP $remote_addr;
}

OpenResty:

# 完整的 Lua 编程能力
location /api {
    content_by_lua_block {
        local cjson = require "cjson"
        local redis = require "resty.redis"
        
        -- 复杂业务逻辑
        local red = redis:new()
        red:connect("127.0.0.1", 6379)
        
        local data = red:get("user:123")
        ngx.say(cjson.encode({status = "ok", data = data}))
    }
}

3. 使用场景

Nginx 更适合:

  • 简单反向代理
  • 静态文件服务
  • 基础负载均衡
  • SSL 终端
  • 简单 URL 重写

OpenResty 更适合:

  • 动态内容生成
  • 复杂认证/授权
  • API 网关
  • WAF(Web 应用防火墙)
  • 实时数据处理
  • 微服务编排
  • 复杂规则的限流
  • A/B 测试
  • 动态路由

为什么使用 OpenResty?

1. 性能

  • LuaJIT 提供接近原生 C 的性能
  • 非阻塞 I/O 操作
  • 工作进程间的共享内存
  • 高效的连接池

2. 灵活性

  • 无需重新编译 Nginx 即可编写复杂逻辑
  • 无需重启即可热加载 Lua 代码
  • 可访问丰富的 Lua 生态系统
  • 易于与数据库、Redis 等集成

3. 生产力

  • 快速开发和迭代
  • 无需编写 C 模块
  • 丰富的库生态系统(resty 库)
  • 比 C 模块更容易调试

4. 实际应用示例

-- 示例:基于用户等级的动态限流
access_by_lua_block {
    local redis = require "resty.redis"
    local red = redis:new()
    red:connect("127.0.0.1", 6379)
    
    local user_id = ngx.var.arg_user_id
    local user_tier = red:get("user:" .. user_id .. ":tier")
    
    local limit = user_tier == "premium" and 1000 or 100
    
    -- 实现限流逻辑
    local current = red:incr("rate:" .. user_id)
    if current > limit then
        ngx.exit(429) -- 请求过多
    end
}

如何使用 OpenResty

安装

Linux (Ubuntu/Debian)

# 添加 OpenResty 仓库
wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
sudo apt-get -y install software-properties-common
sudo add-apt-repository -y "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main"

# 安装 OpenResty
sudo apt-get update
sudo apt-get install openresty

macOS

brew install openresty/brew/openresty

Docker

FROM openresty/openresty:alpine

COPY nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
COPY lua/ /usr/local/openresty/nginx/lua/

EXPOSE 80

CMD ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"]

基础配置

nginx.conf

worker_processes auto;
error_log logs/error.log;

events {
    worker_connections 1024;
}

http {
    # Lua 包路径
    lua_package_path "/usr/local/openresty/nginx/lua/?.lua;;";
    
    # 用于缓存的共享内存
    lua_shared_dict my_cache 10m;
    
    server {
        listen 80;
        server_name localhost;
        
        # 简单的 Lua 处理器
        location /hello {
            content_by_lua_block {
                ngx.say("Hello, OpenResty!")
            }
        }
        
        # 加载外部 Lua 文件
        location /api {
            content_by_lua_file lua/api.lua;
        }
        
        # 访问控制
        location /protected {
            access_by_lua_block {
                local token = ngx.var.http_authorization
                if not token or token ~= "Bearer secret123" then
                    ngx.exit(401)
                end
            }
            
            proxy_pass http://backend;
        }
    }
}

常见使用场景

1. API 网关

location /api/v1/ {
    access_by_lua_block {
        -- 身份验证
        local jwt = require "resty.jwt"
        local token = ngx.var.http_authorization
        
        if not token then
            ngx.exit(401)
        end
        
        local jwt_obj = jwt:verify("secret", token)
        if not jwt_obj.verified then
            ngx.exit(403)
        end
        
        -- 设置用户上下文
        ngx.var.user_id = jwt_obj.payload.sub
    }
    
    # 路由到后端
    proxy_pass http://backend_service;
}

2. Redis 缓存

location /data {
    content_by_lua_block {
        local redis = require "resty.redis"
        local cjson = require "cjson"
        
        local red = redis:new()
        red:set_timeout(1000)
        
        local ok, err = red:connect("127.0.0.1", 6379)
        if not ok then
            ngx.log(ngx.ERR, "连接 Redis 失败: ", err)
            ngx.exit(500)
        end
        
        local cache_key = "data:" .. ngx.var.arg_id
        local cached = red:get(cache_key)
        
        if cached ~= ngx.null then
            ngx.say(cached)
            return
        end
        
        -- 从数据库获取数据(伪代码)
        local data = fetch_from_db(ngx.var.arg_id)
        
        -- 缓存 1 小时
        red:setex(cache_key, 3600, cjson.encode(data))
        
        ngx.say(cjson.encode(data))
    }
}

3. 限流

location /api {
    access_by_lua_block {
        local limit_req = require "resty.limit.req"
        
        -- 允许每秒 10 个请求,突发 20 个
        local lim, err = limit_req.new("my_limit_store", 10, 20)
        if not lim then
            ngx.log(ngx.ERR, "实例化限流器失败: ", err)
            return ngx.exit(500)
        end
        
        local key = ngx.var.binary_remote_addr
        local delay, err = lim:incoming(key, true)
        
        if not delay then
            if err == "rejected" then
                return ngx.exit(429)
            end
            ngx.log(ngx.ERR, "限流失败: ", err)
            return ngx.exit(500)
        end
        
        if delay >= 0.001 then
            ngx.sleep(delay)
        end
    }
    
    proxy_pass http://backend;
}

4. 动态路由

location /service/ {
    rewrite_by_lua_block {
        local redis = require "resty.redis"
        local red = redis:new()
        red:connect("127.0.0.1", 6379)
        
        -- 从 Redis 获取服务版本
        local version = red:get("service:version")
        
        if version == "v2" then
            ngx.var.backend = "http://service-v2:8080"
        else
            ngx.var.backend = "http://service-v1:8080"
        end
    }
    
    proxy_pass $backend;
}

5. 请求/响应转换

location /transform {
    # 修改请求
    rewrite_by_lua_block {
        ngx.req.set_header("X-Custom-Header", "value")
        
        -- 修改请求体
        ngx.req.read_body()
        local body = ngx.req.get_body_data()
        if body then
            local cjson = require "cjson"
            local data = cjson.decode(body)
            data.timestamp = ngx.time()
            ngx.req.set_body_data(cjson.encode(data))
        end
    }
    
    # 修改响应
    body_filter_by_lua_block {
        local chunk = ngx.arg[1]
        if chunk then
            -- 转换响应
            ngx.arg[1] = string.upper(chunk)
        end
    }
    
    proxy_pass http://backend;
}

常用的 OpenResty 库

-- HTTP 客户端
local http = require "resty.http"

-- JSON 编码/解码
local cjson = require "cjson"

-- Redis 客户端
local redis = require "resty.redis"

-- MySQL 客户端
local mysql = require "resty.mysql"

-- JWT 处理
local jwt = require "resty.jwt"

-- WebSocket
local websocket = require "resty.websocket.server"

-- 模板引擎
local template = require "resty.template"

-- UUID 生成
local uuid = require "resty.jit-uuid"

最佳实践

  1. 使用共享字典进行缓存
lua_shared_dict my_cache 10m;

-- 在 Lua 代码中
local cache = ngx.shared.my_cache
cache:set("key", "value", 60) -- 60 秒 TTL
local value = cache:get("key")
  1. 连接池
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
red:connect("127.0.0.1", 6379)

-- 执行工作...

-- 返回到连接池而不是关闭
red:set_keepalive(10000, 100)
  1. 错误处理
local ok, err = pcall(function()
    -- 你的代码
end)

if not ok then
    ngx.log(ngx.ERR, "错误: ", err)
    ngx.exit(500)
end
  1. 非阻塞操作
-- 使用 resty 库进行非阻塞 I/O
local http = require "resty.http"
local httpc = http.new()

-- 这是非阻塞的
local res, err = httpc:request_uri("http://api.example.com/data")

性能对比

指标 Nginx OpenResty
静态文件 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
简单代理 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
动态内容 ⭐⭐ ⭐⭐⭐⭐⭐
复杂逻辑 ⭐⭐⭐⭐⭐
开发速度 ⭐⭐ ⭐⭐⭐⭐⭐
内存使用 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐

何时选择什么?

选择 Nginx 当:

  • 你只需要反向代理和负载均衡
  • 静态文件服务是你的主要用例
  • 不需要动态请求处理
  • 你希望最小的内存占用
  • 你的团队缺乏 Lua 经验

选择 OpenResty 当:

  • 构建 API 网关
  • 需要复杂的认证/授权
  • 实现 WAF 或安全功能
  • 需要动态路由和内容生成
  • 需要与 Redis、MySQL 等集成
  • 想要避免编写 C 模块
  • 构建微服务基础设施

结论

OpenResty 在保持 Nginx 性能特性的同时,扩展了强大的脚本功能。它非常适合构建现代 Web 应用、API 网关和微服务基础设施,在需要动态逻辑的场景下不会牺牲性能。

在 Nginx 和 OpenResty 之间的选择取决于你的具体需求:

  • 简单用例 → Nginx
  • 复杂、动态用例 → OpenResty

两者都是生产就绪且经过实战考验的,OpenResty 被 Cloudflare、Kong 和许多其他公司用于高流量应用。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions