必须用lua-resty-dns而非resolver指令,因其支持按域名定制超时/重试、SRV记录解析、结构化结果返回及Lua中主动控制解析时机,而resolver仅提供全局静态配置,无法满足网关精细化路由与多活容灾需求。
为什么必须用 lua-resty-dns而不是 resolver 指令resolver 指令虽支持运行时 DNS 查询,但它是全局、静态配置的,所有 upstream 共享同一套 DNS 服务器和缓存策略,无法按域名定制超时、重试或 nameserver 列表。更重要的是,它不支持 SRV 记录、不提供查询结果结构化返回、也无法在 Lua 中主动触发并控制解析时机——这些恰恰是网关层做精细化路由、灰度分流、多活容灾所必需的能力。
lua-resty-dns 是 OpenResty 默认内置的异步 DNS 客户端库,完全基于 Nginx 事件循环,调用时不阻塞 worker 进程。它能发起 A/AAAA/SRV/CNAME 等任意类型查询,支持 round-robin nameserver、自定义重试与超时,并可将结果缓存到 shared_dict 实现跨请求复用。
基础配置:启用 lua-resty-dns 并初始化解析器确保你使用的是 OpenResty(≥1.2.1.11),无需额外安装 lua-resty-dns。关键点在于:nameserver 必须显式指定,不能依赖系统 /etc/resolv.conf;且需提前 seed 随机数生成器以保障唯一 ID(用于负载均衡轮转)。
在 http 块中声明共享字典:lua_shared_dict dns_cache 10m;在 init_by_lua_block 中创建并预热解析器:init_by_lua_block {math.randomseed(ngx.time() + ngx.worker.pid())local dns = require "resty.dns.resolver"local r, err = dns:new{nameservers = {"8.8.8.8", {"114.114.114.114", 53}},retrans = 3,timeout = 2000 -- 单次查询毫秒级超时}if not r thenngx.log(ngx.ERR, "failed to create DNS resolver: ", err)end}全异步解析:在 balancer_by_lua_block 中动态选后端这是网关层真正“活起来”的一步:不再依赖 static upstream,而是在每次请求分发前,异步查 DNS 获取 IP 列表,并结合健康状态、权重、地域标签等决策目标节点。整个过程不等待、不阻塞、不锁 worker。
Nginx在宝塔面板中轻松管理Nginx高性能Web服务器。提供可视化配置反向代理、负载均衡、SSL证书及HTTP缓存功能,一键优化高并发性能,助您高效搭建稳定、快速的网站运行环境。
下载把域名从请求头或路由规则中提取出来(例如 $arg_backend 或 map 匹配的变量)
先查 shared_dict 缓存,命中则直接解析;未命中则调用 resolver:query() 发起异步查询query 返回后,用 ngx.balancer.set_current_peer(ip, port) 指定转发目标失败时 fallback 到备用域名或预设兜底地址upstream dynamic_upstream {balancer_by_lua_block {local cache = ngx.shared.dns_cachelocal domain = ngx.var.backend_host or "default.api.example.com"local key = "dns:" .. domainlocal cached = cache:get(key)if cached and type(cached) == "table" and #cached > 0 thenlocal ip = cached[1].addr -- 取第一个 A 记录ngx.balancer.set_current_peer(ip, 80)returnendlocal dns = require "resty.dns.resolver"local r, err = dns:new{ nameservers = {"8.8.8.8"} }if not r then ngx.log(ngx.ERR, err); return endlocal answers, err = r:query(domain, { qtype = dns.TYPE_A })if not answers or #answers == 0 thenngx.log(ngx.WARN, "DNS query failed for ", domain, ": ", err)ngx.balancer.set_current_peer("127.0.0.1", 8080)returnendlocal ips = {}for _, ans in ipairs(answers) doif ans.address then table.insert(ips, { addr = ans.address }) endendcache:set(key, ips, 60) -- 缓存 60 秒ngx.balancer.set_current_peer(ips[1].addr, 80)}}进阶技巧:缓存策略、SRV 支持与错误降级真实网关场景下,仅查 A 记录远远不够。你需要:分级缓存:shared_dict 存 IP 列表(TTL ≤ DNS 记录 TTL),本地 Lua 变量存本次请求已解析域名(防重复 query)
SRV 记录解析:用于服务发现(如 _http._tcp.api.consul),可同时获取 host、port、priority、weight,天然支持加权轮询故障自动降级:DNS 查询超时或失败时,不抛错,而是 fallback 到 Redis 中维护的“最后已知健康地址”或静态兜底集群并发安全:多个请求同时未命中缓存时,避免“缓存击穿”,可用 lua-resty-lock 或原子 setnx 控制仅一个请求去查 DNS例如查 SRV:r:query("_api._tcp.service.example.com", { qtype = dns.TYPE_SRV }),返回结果含 target、port、weight 字段,可直接用于 ngx.balancer.set_current_peer(target, port)。
