nginx可通过openresty的access_by_lua_block调用resty.redis实时查redis路由规则,结合lua_shared_dict缓存结果并设ttl,解析key(如route:user)获取预存纯url值(如http://10.0.1.100:8080),赋给ngx.var.upstream_target后由proxy_pass $upstream_target动态转发,超时或失败时降级至默认upstream,全程无需reload。

能直接用 redis 存路由规则、Nginx 请求时实时查、改完即生效——不需要 nginx -s reload,也不依赖 Consul 或其他服务发现组件。关键在于把 Lua 脚本嵌在 access_by_lua_block 或 rewrite_by_lua_block 里,用 resty.redis 查缓存,再通过变量驱动 proxy_pass。
怎么让 Nginx 在请求阶段读 Redis 并动态选 upstream
核心不是“改配置”,而是“在每次请求中计算目标地址”。OpenResty 提供了完整的 Redis 客户端和共享内存机制,配合 lua_shared_dict 缓存结果,性能足够支撑万级 QPS。
-
lua_shared_dict redis_route_cache 10m;必须在http块里声明,用于缓存 Redis 查询结果(TTL 控制) - 在
location中用access_by_lua_block查询 Redis:提取 URL 路径第一段(如/user/xxx→user),拼成 key(如route:user),查 value(格式为http://10.0.1.100:8080) - 查不到或超时,必须 fallback 到预设的默认 upstream(如
backend_default),不能让请求卡住或 502 - 查到后,用
ngx.var.upstream_target = "http://...",再在proxy_pass中引用:proxy_pass $upstream_target;
Redis 里存什么格式?怎么避免解析失败
Redis 的 value 必须是纯字符串,且格式严格限定为可直接用于 proxy_pass 的 URL。不要存 JSON、IP+Port 分离字段或带注释的文本——Lua 解析成本高、易出错、难调试。
- 推荐格式:
http://172.16.5.20:9001或https://svc-order-v2.internal:443(协议、host、port 全写死) - 禁止存
{"host":"172.16.5.20","port":9001}这类结构,cjson.decode会增加延迟,且空值、编码错误直接导致 Lua 报attempt to index a nil value - key 命名建议统一前缀,如
route:api:user,方便批量清理;用EXPIRE设置 TTL(如 30 秒),避免脏数据长期滞留 - 写入时用
SET route:api:user http://172.16.5.20:9001 EX 30,确保原子性与过期控制
为什么不能在 init_by_lua_block 里一次性加载所有路由
因为做不到“热更新”。init_by_lua_block 只在 worker 进程启动时执行一次,后续 Redis 里的变更对它完全不可见。你改了路由,Nginx 不会自动感知,除非 kill worker 进程强制重启——这等于变相 reload,违背初衷。
- 真正动态的前提是:每次请求都走一次“查缓存 → 缓存失效则查 Redis → 写回缓存”的链路
- 缓存失效策略必须明确:比如用
shared_dict:get_stale获取可能过期的值,再异步刷新,而不是等 TTL 到才查 - 如果真想预热,可用定时任务调用一个内部
/api/trigger-route-refresh接口,在content_by_lua_block里主动刷一遍shared_dict,但不替代请求时的兜底逻辑
容易被忽略的超时与降级细节
Redis 查询一旦阻塞,整个请求就 hang 住。OpenResty 默认没有全局超时,必须每处显式设置。
-
redis:set_timeout(200)和redis:set_keepalive(60000, 100)是底线配置:连接/读写超时 ≤ 200ms,连接池最大空闲时间 60 秒 - 查 Redis 失败时,不能只打日志就放弃——要从
shared_dict里取last_known_value(需自己用shared_dict:expire配合时间戳维护) - 连
shared_dict都没值?立刻ngx.exit(503)或ngx.exec("/fallback"),别让上游等 - 所有 Redis 操作必须包在
pcall里:local ok, res = pcall(redis.get, red, key),否则 Lua 异常会导致 worker crash











