必须借助OpenResty实现动态路由转发,核心是在access_by_lua_block或balancer_by_lua_block等阶段用Lua实时决策转发目标,支持灰度、参数分流、外部数据联动及热更新,而非静态proxy_pass配置。

在 Nginx 中实现动态路由转发,必须借助 OpenResty(而非原生 Nginx),因为它集成了 Lua 运行时和扩展模块,让配置从“静态声明”变为“实时计算”。核心不是改 proxy_pass 地址字符串,而是在请求处理的关键阶段用 Lua 代码决定转发目标,并支持热更新、外部数据联动与业务逻辑嵌入。
关键阶段选对才能真正“动态”
OpenResty 提供多个 Lua 插入点,不同阶段用途明确:
-
access_by_lua_block:适合做前置判断——读取请求头、参数、Cookie、Body 内容,校验权限或提取路由依据(如
ngx.var.arg_version或ngx.var.cookie_uid),把结果存入ngx.ctx或ngx.var供后续使用 -
balancer_by_lua_block:最核心的动态路由阶段,绕过 Nginx 原生 upstream 的静态 weight,手动调用
balancer.set_current_peer(ip, port)指定后端节点,支持加权随机、一致性哈希、健康度感知等算法 -
rewrite_by_lua_block:适合做路径重写或变量预处理(如把
/api/v2/user映射为 backend_v2),但不建议在此阶段做耗时操作(如 Redis 查询)
动态数据源要高效且可靠
路由决策依赖实时数据,不能每次查文件或建新连接。推荐方式:
-
lua_shared_dict:进程间共享内存字典,用于缓存权重配置、服务列表、灰度规则等。例如:
lua_shared_dict route_conf 5m;,再用ngx.shared.route_conf:set("v2_ratio", 30)更新 -
Redis / etcd:作为外部配置中心,通过
lua-resty-redis异步查询(注意用set_timeout防卡顿)。典型场景:按用户 ID 查所属分组,再映射到对应 upstream -
HTTP API 调用:用
resty.http请求内部策略服务(如鉴权中心、AB 测试平台),但需设置超时和降级逻辑(如 fallback 到默认路由)
典型动态路由模式怎么写
不靠硬编码,而是组合条件与策略:
-
按请求参数分流:解析
ngx.var.arg_env,值为prod→ 走backend_prod,test→ 走backend_test -
按 Body JSON 字段路由:先
ngx.req.read_body(),再用cjson.decode解析,提取service字段,拼接成ngx.var.upstream_target = obj.service .. "_backend" -
按地域+路径双维度调权:匹配
ngx.var.uri是否以/order/开头,再结合ngx.var.geoip_country_code,从 shared_dict 中读取对应组合的权重,用加权随机选节点 -
灰度发布控制:VIP 用户固定走新版本,普通用户按百分比(如 15%)抽样,新注册用户强制走旧版——全部在
access_by_lua_block中完成判断并标记ngx.ctx.version = "v2"
避坑要点
动态路由易出问题的地方往往不在逻辑,而在执行环境:
- 避免阻塞:所有 IO 操作(Redis、HTTP、MySQL)必须用异步接口(
redis:connect不带sync,http:request启用 keepalive) - 防止 worker 竞态:shared_dict 是线程安全的,但自定义 Lua 表变量不是;多 worker 共享状态必须走 shared_dict
- body 读取要谨慎:大请求体启用
lua_need_request_body on会内存暴涨,生产环境应改用ngx.req.read_body()+ 分块处理 - 错误必须兜底:Lua 报错会导致 500,每个
access_by_lua_block建议用pcall包裹,失败时设默认 upstream



















