导读:本期聚焦于小伙伴创作的《Nginx如何记录请求中的DNS解析时间?配置与原理详解》,敬请观看详情。把后端域名交给Nginx做代理时,若上游使用域名而非IP,每次连接都可能触发DNS查询。想定位接口变慢是否由解析延迟引起,就得在日志里拿到解析耗时。Nginx的$upstream_response_time只覆盖连接和读取,并不单独拆出DNS阶段。其实借助核心模块的内置变量与resolver指令,能把解析开始与结束的差值写进访问日志。本文说明如何通过log_format扩展字段,配合valid参数和超时设置,稳定输出每条请求的DNS耗时,并解释变量生效条件和常见漏记原因。

在Nginx作为反向代理或七层负载时,很多配置会把proxy_pass写成带域名的地址,例如 http://api.ippipp.com。此时Nginx需要在建立连接前把域名变成IP,这一步就是DNS解析。如果解析慢,用户看到的请求延迟就会升高,但默认的访问日志并不会告诉你时间花在了这里。要解决这个问题,我们必须理解Nginx处理上游域名时的内部阶段,并利用它提供的变量把解析耗时单独记下来。

Nginx如何记录请求中的DNS解析时间?配置与原理详解

一、Nginx解析DNS的触发机制与可用变量

Nginx只有在配置里出现域名形式的上游地址,并且使用了resolver指令时,才会在运行时做动态解析。如果直接写IP,或者依赖系统静态/etc/hosts,根本不存在Nginx层面的DNS查询,自然也无从记录解析时间。在带resolver的场景中,Nginx核心模块提供了$upstream_addr、$upstream_connect_time等变量,但并没有一个现成的$dns_time。我们需要通过组合变量和计算差值的方式间接得到。

具体做法是利用Nginx在处理请求时的内部计时点。虽然官方变量列表未直接暴露DNS耗时,但可以在log_format中借助第三方模块如nginx-module-vts,或使用Lua配合ngx.now()打点。更轻量的原生思路是:在resolver配置中打开日志,然后用error_log的debug级别观察,但这不适合生产。生产环境常用OpenResty或嵌入Lua,在访问阶段前记录ngx.now(),解析后再次记录,两者相减写入自定义变量。

下面是一段在OpenResty中利用Lua打点DNS耗时的简化示例。它先记下进入时间,再用ngx.socket.tcp()手动做一次域名解析,从而精确拿到耗时。注意这只是演示原理,真实代理不必手动建连,可结合cosocket的dns解析接口。

local sock = ngx.socket.tcp()
local start = ngx.now()
-- 手动触发一次DNS解析,拿到所有IP
local ips, err = sock:getreusedtimes()  -- 仅为示意,实际应使用resolver查询
local ok, resolved = pcall(function()
    return sock:connect("api.ipipp.com", 80)
end)
local cost = ngx.now() - start
ngx.var.dns_cost = tostring(cost)
sock:close()

二、通过log_format把解析时间写入访问日志

拿到解析耗时变量后,下一步是把它输出到access_log。Nginx的log_format指令允许组合任意变量,包括用set指令定义的自定义变量。我们可以在http块中先声明一个变量,例如$dns_cost,默认值为0,再由Lua或map赋值。随后在log_format里追加这个字段,就能在每行日志末尾看到数字。

配置时需注意变量作用域。自定义变量要在server或location之外用set指令初始化,否则Lua赋值可能报空。另外,如果上游使用了keepalive,长连接复用会导致后续请求不再解析DNS,此时dns_cost应为0或上次值,需要在日志中结合$connection复用标识来判断。下面给出一份典型的nginx.conf片段,展示格式定义与变量初始化。

http {
    log_format main '$remote_addr - $time_local "$request" '
                     '$status $body_bytes_sent '
                     'dns=$dns_cost';
    server {
        set $dns_cost "0";
        location / {
            access_by_lua_block {
                local start = ngx.now()
                -- 假设此处调用了解析函数得到cost
                local cost = 0.012
                ngx.var.dns_cost = string.format("%.3f", cost)
            }
            proxy_pass http://api.ipipp.com;
            access_log /var/log/nginx/access.log main;
        }
    }
}

上述配置中,log_format里的dns=$dns_cost会在每条日志追加解析时间。若某个请求未走代理域名,可保持0。通过这种方式,运维人员可以用awk或ELK筛选dns值偏大的记录,快速锁定解析异常。同时建议把resolver的timeout设小一些,避免解析卡死拖垮整体。

三、原生Nginx无Lua时的替代记录方案

并非所有环境都能上OpenResty。纯Nginx用户若想记录DNS解析时间,可以借助error_log加resolver调试,或者用split_clients与map做间接标记。例如,把解析失败的请求单独打标,再在日志中用标志位反映。虽然拿不到精确毫秒,但能知道哪些请求经历了重解析。

另一个办法是使用Nginx Plus或商业模块,它们内置了更丰富的$upstream指标,其中包含解析阶段细分。开源版则可以考虑在系统层用tcpdump抓53端口,离线算耗时,再和access_log的$request_time对齐。这种方法不侵入Nginx,适合短期排查。下面的命令示例展示如何抓取DNS包并简单统计。

tcpdump -i any udp port 53 -tttt -nn 2> /tmp/dns.log &
# 请求发生后停止抓包,用awk筛选耗时
awk '/A?/ {print $1, $NF}' /tmp/dns.log

无论采用哪种方案,核心原则都是把DNS阶段从整体延迟中剥离。只有明确记录了解析时间,才能在下一次接口抖动时,第一时间判断是该扩容解析缓存,还是该把域名换成IP直连。对于大规模微服务架构,建议在网关层统一埋点,避免每个业务Nginx重复造轮子。

NginxDNS解析access_log修改时间:2026-08-13 12:57:36

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。