必须用lua-resty-dns而非resolver指令,因其支持按域名定制超时/重试、srv记录解析、结构化结果返回及lua中主动控制解析时机,而resolver仅提供全局静态配置,无法满足网关精细化路由与多活容灾需求。

必须用 lua-resty-dns,不能用 resolver 指令——后者做不到按域名定制超时、不支持 SRV、无法在 Lua 中主动触发解析,更谈不上网关级流量调度。
为什么 resolver 指令在网关场景下根本不够用
它只是给整个 Nginx 进程配一套全局 DNS 服务器和缓存策略,所有 upstream 共享同一组 valid=30s 和重试逻辑。实际网关中,你可能需要:
- 对核心服务设
timeout=500ms, retry=2,对旁路服务设timeout=3s, retry=1 - 查
backend-v2.service.consul的 SRV 记录拿到 port + weight - 在
access_by_lua_block里根据请求头决定查哪个域名,而不是等proxy_pass被动触发 - 把结果缓存在
shared_dict里供多个请求复用,同时支持手动失效
这些 resolver 全做不了——它连返回结构化结果都不支持,只默默更新 upstream 的 IP 列表。
lua-resty-dns 怎么发起一次带控制权的 A 记录查询
关键点不是“能查”,而是“查得可控”。下面这段代码跑在 access_by_lua_block 里,完全不阻塞 worker:
local dns = require "resty.dns.resolver"
local r, err = dns:new{
nameservers = {{"114.114.114.114", 53}, {"8.8.8.8", 53}},
retrans = 2,
timeout = 1000 -- 单次 UDP 查询超时(毫秒)
}
if not r then
ngx.log(ngx.ERR, "failed to instantiate dns: ", err)
return
end
<p>local answers, err = r:query("api.pay.example.com", {qtype = dns.TYPE_A})
if not answers or answers.errcode then
ngx.log(ngx.WARN, "dns query failed: ", err or answers.errstr)
return
end</p><p>for _, ans in ipairs(answers) do
if ans.address then
ngx.ctx.upstream_ip = ans.address
break
end
end</p>
注意:nameservers 支持 fallback,retrans 控制重试次数,qtype 可换为 dns.TYPE_SRV 查端口权重。
如何把 DNS 结果缓存并跨请求复用
直接存 ngx.shared.my_cache 是最常用做法,但要注意三点:
- key 必须包含域名 + qtype + nameserver 哈希(避免不同解析策略冲突)
- value 存
json.encode({{address="10.1.2.3", ttl=28}, ...}),别只存 IP 字符串 - 读缓存前检查
ttl > now(),过期就丢弃,不自动刷新——刷新动作应由业务逻辑显式触发
示例 key 构造:ngx.md5("api.pay.example.com:A:114.114.114.114");缓存时间建议略小于 DNS TTL(比如 TTL=30s,缓存设 25s)。
SRV 记录解析和灰度路由怎么联动
SRV 返回的是 {target="srv-01.prod", port=8080, priority=10, weight=50} 这类结构,正好用来做灰度:
- 用
target拼出新域名再查一次 A 记录(递归解析) - 按
weight做加权随机:收集全部 SRV 条目后,生成 [0, sum(weight)) 区间,用请求 ID 哈希取模落点 - 把最终选中的
target:port存进ngx.ctx,后续balancer_by_lua_block直接用
这整套流程都在单次请求生命周期内完成,不依赖外部服务、不阻塞事件循环,这才是网关层真正需要的“动态”。
容易被忽略的是错误兜底:DNS 查询失败时,不要直接 502,而应 fallback 到预置的健康节点列表或本地缓存的最后有效结果——网关的可用性不能卡在 DNS 这一环上。











