Nginx 到 OpenResty 网关迁移Lua 脚本把 API 路由延迟从 120ms 压到 8ms说实话我一开始觉得 Nginx 已经够快了。直到上周压测报告甩到脸上——同一个接口P99 延迟 120ms其中 80ms 花在了网关层。不是业务逻辑慢不是数据库慢是 Nginx 做反向代理时每次请求都要完整地走完 TCP 三次握手 后端响应 关闭连接。这谁也忍不了。所以我干了件事把 Nginx 换成 OpenResty在网关层用 Lua 做动态路由、本地缓存和轻量鉴权。两周后P99 降到了 8ms。今天把过程写出来包括踩的坑。背景为什么不是 Nginx 不够而是用法不对我们的架构很简单。Nginx 做反向代理后面挂着 6 个 Node.js 服务实例。接口主要是 REST API读多写少很多请求其实读的是配置、用户基础信息这种变化极慢的数据。问题出在 Nginx 的默认行为上每个请求都新建连接虽然开了 keepalive但短连接还是太多路由逻辑写在 location 里配置一多就成了面条请求到后端才做鉴权后端还要自己处理缓存重复劳动压测结果让我清醒了网关层不应该只是个转发器它应该是个智能门卫。方案OpenResty Lua 三级优化我换了 OpenResty核心思路是三件事在网关层做 Lua 共享内存缓存ngx.shared.DICT命中就直接返回用 Lua 做动态路由不用 location 硬编码路由表放 Redis热更新把轻量鉴权Token 校验、IP 白名单从后端移到网关层后端只管业务第一级共享内存缓存热点数据直接挡在网关外我们有些配置接口数据 5 分钟才变一次但 QPS 有 3000。以前全部打到后端现在我在 OpenResty 里加了 10MB 共享内存缓存-- 在 nginx.conf 的 http 段声明lua_shared_dict api_cache 10m;-- access_by_lua_block 或 content_by_lua_block 里localcachengx.shared.api_cachelocalcache_keyuser_config_..user_idlocalcachedcache:get(cache_key)ifcachedthenngx.say(cached)returnngx.exit(ngx.HTTP_OK)end-- 没命中走后端localresngx.location.capture(/proxy..ngx.var.uri)ifres.status200thencache:set(cache_key,res.body,300)-- 缓存 5 分钟endngx.say(res.body)就这么十几行3000 QPS 里 90% 的请求直接由 OpenResty 内存返回不需要经过任何 TCP 握手。单这一步平均延迟从 45ms 降到了 3ms。第二级动态路由告别 location 地狱以前 Nginx 配置里 location 写了 40 多个改一个路由规则要 reload Nginx生产环境谁也不敢随便动。现在我把路由表放 RedisOpenResty 启动时加载到内存同时监听 Redis 的 Keyspace Notification 做热更新-- 路由查找Lua table 查找O(1)localrouterrequirerouterlocalrouterouter.match(ngx.var.uri,ngx.var.request_method)ifnotroutethenngx.exit(ngx.HTTP_NOT_FOUND)endngx.var.upstreamroute.upstream路由表结构大概长这样{/api/v1/users:{upstream:user_service,cache_ttl:300},/api/v1/orders:{upstream:order_service,auth_required:true}}改路由规则只需要改 RedisOpenResty 2 秒内自动感知。运维同事再也不用担心我 reload 搞出 502 了。第三级网关层鉴权把后端从杂活里解放出来以前每个后端服务都自己解析 JWT、校验 Token。现在我在 OpenResty 的 access_by_lua 阶段统一处理localjwtrequireresty.jwtlocaltokenngx.var.http_authorizationifnottokenthenngx.exit(ngx.HTTP_UNAUTHORIZED)endlocalok,errjwt.verify(token,jwt_secret)ifnotokthenngx.exit(ngx.HTTP_UNAUTHORIZED)end-- 把解析后的用户信息塞到 header后端直接读ngx.req.set_header(X-User-Id,jwt.payload.sub)后端服务不再需要引入任何 JWT 库收到的请求已经带好了 X-User-Id。六个 Node.js 服务统一删掉了一千多行鉴权代码。踩坑记录这三件事让我折腾了两天坑 1Lua 共享内存的缓存穿透第一次上线后缓存命中率只有 40%预期是 90%。查了半天发现用户 ID 的分布不均匀某些热点 key 被频繁淘汰LRU。后来加了二级缓存热点 key 单独存一个 dictTTL 更短但容量更大命中率才拉到 92%。坑 2Redis 断连导致路由表为空如果 OpenResty 启动时 Redis 还没就绪路由表加载失败所有请求 404。我加了 fallback 机制本地持久化一份路由表 JSONRedis 连不上时直接用本地版本同时告警通知。localroutes,errload_from_redis()ifnotroutesthenroutesload_from_file(/etc/openresty/routes.json)ngx.log(ngx.WARN,Redis down, using local routes fallback)end坑 3ngx.location.capture 的内存泄漏早期版本我用ngx.location.capture做子请求高并发下内存涨得很奇怪。后来换成ngx.http的 cosocket APIresty.http库连接池复用内存稳定了性能还更好。性能对比数字说话压测环境4C8G6 个后端实例JMeter 500 并发持续 5 分钟。指标NginxOpenResty平均延迟45ms5msP99 延迟120ms8ms后端 QPS3200850缓存命中 90%网关 CPU35%42%网关内存120MB280MB含缓存P99 从 120ms 压到 8ms不是魔法是 90% 的请求根本没走到后端。CPU 涨了 7% 但内存多了 160MB这交换比我觉得值。写在最后Nginx 本身没问题但把它当纯转发器用是把压力无脑往后端推。OpenResty 的价值在于你可以在请求生命周期的任何阶段rewrite、access、content、log插入 Lua 逻辑把网关从管道变成处理层。这次迁移一共改了 200 多行 Lua 代码和一份 nginx.conf后端服务减了 1000 多行鉴权代码。投入产出比我算过了很值。如果你也在用 Nginx 做反向代理接口读多写少不妨看看网关层的开销。有时候瓶颈不在数据库不在业务逻辑在你以为够快的那一层。有问题评论区见或者在 GitHub 搜 “openresty-api-gateway-lua” 能找到我整理的一份模板配置。
Nginx 到 OpenResty 网关迁移:Lua 脚本把 API 路由延迟从 120ms 压到 8ms
Nginx 到 OpenResty 网关迁移Lua 脚本把 API 路由延迟从 120ms 压到 8ms说实话我一开始觉得 Nginx 已经够快了。直到上周压测报告甩到脸上——同一个接口P99 延迟 120ms其中 80ms 花在了网关层。不是业务逻辑慢不是数据库慢是 Nginx 做反向代理时每次请求都要完整地走完 TCP 三次握手 后端响应 关闭连接。这谁也忍不了。所以我干了件事把 Nginx 换成 OpenResty在网关层用 Lua 做动态路由、本地缓存和轻量鉴权。两周后P99 降到了 8ms。今天把过程写出来包括踩的坑。背景为什么不是 Nginx 不够而是用法不对我们的架构很简单。Nginx 做反向代理后面挂着 6 个 Node.js 服务实例。接口主要是 REST API读多写少很多请求其实读的是配置、用户基础信息这种变化极慢的数据。问题出在 Nginx 的默认行为上每个请求都新建连接虽然开了 keepalive但短连接还是太多路由逻辑写在 location 里配置一多就成了面条请求到后端才做鉴权后端还要自己处理缓存重复劳动压测结果让我清醒了网关层不应该只是个转发器它应该是个智能门卫。方案OpenResty Lua 三级优化我换了 OpenResty核心思路是三件事在网关层做 Lua 共享内存缓存ngx.shared.DICT命中就直接返回用 Lua 做动态路由不用 location 硬编码路由表放 Redis热更新把轻量鉴权Token 校验、IP 白名单从后端移到网关层后端只管业务第一级共享内存缓存热点数据直接挡在网关外我们有些配置接口数据 5 分钟才变一次但 QPS 有 3000。以前全部打到后端现在我在 OpenResty 里加了 10MB 共享内存缓存-- 在 nginx.conf 的 http 段声明lua_shared_dict api_cache 10m;-- access_by_lua_block 或 content_by_lua_block 里localcachengx.shared.api_cachelocalcache_keyuser_config_..user_idlocalcachedcache:get(cache_key)ifcachedthenngx.say(cached)returnngx.exit(ngx.HTTP_OK)end-- 没命中走后端localresngx.location.capture(/proxy..ngx.var.uri)ifres.status200thencache:set(cache_key,res.body,300)-- 缓存 5 分钟endngx.say(res.body)就这么十几行3000 QPS 里 90% 的请求直接由 OpenResty 内存返回不需要经过任何 TCP 握手。单这一步平均延迟从 45ms 降到了 3ms。第二级动态路由告别 location 地狱以前 Nginx 配置里 location 写了 40 多个改一个路由规则要 reload Nginx生产环境谁也不敢随便动。现在我把路由表放 RedisOpenResty 启动时加载到内存同时监听 Redis 的 Keyspace Notification 做热更新-- 路由查找Lua table 查找O(1)localrouterrequirerouterlocalrouterouter.match(ngx.var.uri,ngx.var.request_method)ifnotroutethenngx.exit(ngx.HTTP_NOT_FOUND)endngx.var.upstreamroute.upstream路由表结构大概长这样{/api/v1/users:{upstream:user_service,cache_ttl:300},/api/v1/orders:{upstream:order_service,auth_required:true}}改路由规则只需要改 RedisOpenResty 2 秒内自动感知。运维同事再也不用担心我 reload 搞出 502 了。第三级网关层鉴权把后端从杂活里解放出来以前每个后端服务都自己解析 JWT、校验 Token。现在我在 OpenResty 的 access_by_lua 阶段统一处理localjwtrequireresty.jwtlocaltokenngx.var.http_authorizationifnottokenthenngx.exit(ngx.HTTP_UNAUTHORIZED)endlocalok,errjwt.verify(token,jwt_secret)ifnotokthenngx.exit(ngx.HTTP_UNAUTHORIZED)end-- 把解析后的用户信息塞到 header后端直接读ngx.req.set_header(X-User-Id,jwt.payload.sub)后端服务不再需要引入任何 JWT 库收到的请求已经带好了 X-User-Id。六个 Node.js 服务统一删掉了一千多行鉴权代码。踩坑记录这三件事让我折腾了两天坑 1Lua 共享内存的缓存穿透第一次上线后缓存命中率只有 40%预期是 90%。查了半天发现用户 ID 的分布不均匀某些热点 key 被频繁淘汰LRU。后来加了二级缓存热点 key 单独存一个 dictTTL 更短但容量更大命中率才拉到 92%。坑 2Redis 断连导致路由表为空如果 OpenResty 启动时 Redis 还没就绪路由表加载失败所有请求 404。我加了 fallback 机制本地持久化一份路由表 JSONRedis 连不上时直接用本地版本同时告警通知。localroutes,errload_from_redis()ifnotroutesthenroutesload_from_file(/etc/openresty/routes.json)ngx.log(ngx.WARN,Redis down, using local routes fallback)end坑 3ngx.location.capture 的内存泄漏早期版本我用ngx.location.capture做子请求高并发下内存涨得很奇怪。后来换成ngx.http的 cosocket APIresty.http库连接池复用内存稳定了性能还更好。性能对比数字说话压测环境4C8G6 个后端实例JMeter 500 并发持续 5 分钟。指标NginxOpenResty平均延迟45ms5msP99 延迟120ms8ms后端 QPS3200850缓存命中 90%网关 CPU35%42%网关内存120MB280MB含缓存P99 从 120ms 压到 8ms不是魔法是 90% 的请求根本没走到后端。CPU 涨了 7% 但内存多了 160MB这交换比我觉得值。写在最后Nginx 本身没问题但把它当纯转发器用是把压力无脑往后端推。OpenResty 的价值在于你可以在请求生命周期的任何阶段rewrite、access、content、log插入 Lua 逻辑把网关从管道变成处理层。这次迁移一共改了 200 多行 Lua 代码和一份 nginx.conf后端服务减了 1000 多行鉴权代码。投入产出比我算过了很值。如果你也在用 Nginx 做反向代理接口读多写少不妨看看网关层的开销。有时候瓶颈不在数据库不在业务逻辑在你以为够快的那一层。有问题评论区见或者在 GitHub 搜 “openresty-api-gateway-lua” 能找到我整理的一份模板配置。