必须用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 then
ngx.log(ngx.ERR, "failed to create DNS resolver: ", err)
end
}
全异步解析:在 balancer_by_lua_block 中动态选后端
这是网关层真正“活起来”的一步:不再依赖 static upstream,而是在每次请求分发前,异步查 DNS 获取 IP 列表,并结合健康状态、权重、地域标签等决策目标节点。整个过程不等待、不阻塞、不锁 worker。
- 把域名从请求头或路由规则中提取出来(例如 $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_cache
local domain = ngx.var.backend_host or "default.api.example.com"
local key = "dns:" .. domain
local cached = cache:get(key)
if cached and type(cached) == "table" and #cached > 0 then
local ip = cached[1].addr -- 取第一个 A 记录
ngx.balancer.set_current_peer(ip, 80)
return
end
local 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 end
local answers, err = r:query(domain, { qtype = dns.TYPE_A })
if not answers or #answers == 0 then
ngx.log(ngx.WARN, "DNS query failed for ", domain, ": ", err)
ngx.balancer.set_current_peer("127.0.0.1", 8080)
return
end
local ips = {}
for _, ans in ipairs(answers) do
if ans.address then table.insert(ips, { addr = ans.address }) end
end
cache: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)。











