tongyifan的博客

后端开发 / 云原生 / 分布式系统 · 记录学习与思考

← 返回归档

Nginx 与 OpenResty 在高并发网关中的取舍

2026-05-19 · 10 分钟 · Nginx网关

去年做网关选型时,我们在原生 Nginx、OpenResty 和 Envoy 之间纠结了将近三周。最终选了 OpenResty,但这个结论并不通用——它高度依赖"你的网关需要多频繁地变更路由规则"这一个变量。这里把当时的对比和压测数据整理出来。

核心分歧:配置是静态的还是动态的

原生 Nginx 的配置模型是启动时确定。你可以 nginx -s reload,但 reload 的实质是启动一批新 worker、优雅关闭旧 worker。当你的连接是长连接(WebSocket、gRPC stream)时,旧 worker 会一直挂着直到 worker_shutdown_timeout,配置变更的代价随连接数线性增长。

OpenResty 通过 Lua 把一部分逻辑挪到了运行时。路由表可以放在共享内存 lua_shared_dict 里,由一个定时任务从配置中心拉取更新,不需要 reload

location / {
    access_by_lua_block {
        local routes = ngx.shared.routes
        local upstream = routes:get(ngx.var.host .. ngx.var.uri)
        if not upstream then
            return ngx.exit(404)
        end
        ngx.ctx.upstream = upstream
    }
    proxy_pass http://$ctx_upstream;
}

(实际实现比这复杂,要处理前缀匹配和优先级,但思路是这样。)

压测数据

环境:4C8G × 3 台,wrk 从另外 6 台机器打流,后端是一个返回固定 256 字节 JSON 的 Go 服务。全部走 keepalive。

场景 原生 Nginx OpenResty 差异
纯反向代理,1k 并发 48,200 QPS 47,600 QPS -1.2%
纯反向代理,5k 并发 51,300 QPS 50,100 QPS -2.3%
+ Lua 动态路由(dict 命中) 46,800 QPS -3.1%
+ Lua JWT 校验(纯 Lua 实现) 31,400 QPS -34.9%
p99 延迟(1k 并发,动态路由) 4.1 ms 4.6 ms +12%

结论很清楚:Lua 本身不慢,慢的是你在 Lua 里干了什么。查一次共享内存字典的开销可以忽略(-3%),但做一次 JWT 签名校验就吃掉了三分之一的吞吐。

那些没写进压测表的成本

选 OpenResty 之后,我们额外承担了这些东西:

如果重来一次

我会先问一个问题:路由规则的变更频率是多少?

如果是每天几次,那原生 Nginx + reload 完全够用,reload 在几百个连接规模下是无感的,为此引入 Lua 是过度设计。如果是每分钟都有变更、或者有数千个长连接不能断,那 OpenResty 的动态能力就是刚需,那 3% 的性能损耗和额外的运维复杂度是值得付的。

我们的场景是后者(多租户,租户自助配置路由),所以选了 OpenResty。但如果只是做一个内部的 API 网关,我会直接选原生 Nginx——少一层运行时,少一堆说不清楚的问题。

至于 Envoy,它的 xDS 动态配置确实更优雅,但当时团队的 C++/Go 技术栈储备不足以支撑,运维侧也没有经验。技术选型里"团队能不能 hold 住"这一项的权重,往往比性能数据高得多。