应使用 $upstream_connect_time 定位负载均衡链路建连毛刺,它涵盖 DNS、TCP、TLS 全过程耗时;$request_time 无法反映建连阶段问题,因其从接收首字节开始计时。

通过 log_format 记录连接建立耗时(即 TCP 握手完成到 Nginx 接收第一个字节的时间),能有效识别负载均衡链路中由网络抖动、TLS 协商延迟、中间设备策略等引发的“毛刺”。关键在于捕获真实客户端建连阶段的耗时,而非请求处理时间。
使用 $request_time 无法定位建连毛刺
$request_time 是从接收到第一个字节开始计时,到响应发送完毕为止,它不包含 TCP 连接建立、TLS 握手、客户端发包前等待等环节。因此当出现偶发性长连接建立失败或超时,该变量始终为 0 或极小值,无法暴露问题。
真正需要的是:从 accept() 系统调用返回(连接已建立)到读取到第一个字节之间的时间差——Nginx 原生提供 $upstream_connect_time(仅限代理场景)和 $connection_requests 等辅助字段,但建连耗时需借助更底层指标。
启用 $time_iso8601 + 自定义变量记录 accept 时间戳
Nginx 本身不直接暴露 accept 时间,但可通过以下组合方式逼近建连耗时:
- 启用
log_format中的$msec(毫秒级时间戳)与$connection(连接 ID),配合 access log 的写入时机(在读取首行后触发),可反推建连延迟 - 更可靠的做法:在
http块中启用proxy_set_header X-Request-Start "t=$msec";,将请求接收时刻透传给上游;再结合 upstream 日志中的$upstream_connect_time,即可分离出“建连+握手”总耗时 - 若使用 OpenResty,可用
init_worker_by_lua_block注册 socket accept 钩子,用ngx.now()打点并存入 shared dict,再于 log_format 中引用 —— 这是最精准的方式
在 log_format 中嵌入建连相关变量
推荐一个实用的 log_format 示例,兼顾可观测性与排障效率:
log_format upstream_detail'$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time $upstream_connect_time '
'$upstream_header_time $upstream_cache_status '
'rt="$msec" conn="$connection" reqs="$connection_requests"';
其中:
-
$upstream_connect_time:从发起 upstream 连接到连接成功的时间(含 DNS、TCP、TLS),适用于 proxy_pass 场景,是定位 LB 后端建连毛刺的核心字段 -
$msec与$connection组合可用于关联同一连接的多次请求,识别是否某次建连后持续慢(如 TLS session reuse 失效) - 搭配 Prometheus + nginx-lua-prometheus,可对
$upstream_connect_time > 300ms的样本打标告警,快速发现链路抖动
配合 tcpdump 和 ssl_log 定位毛刺根因
日志只能指出“哪里慢”,不能说明“为什么慢”。需联动验证:
- 在 Nginx 机器抓包:
tcpdump -i any -w conn.pcap 'tcp[tcpflags] & (TCP-SYN|TCP-ACK) != 0 and port 443',观察 SYN/SYN-ACK 往返时间(RTT)是否突增 - 开启 OpenSSL 日志(如使用 BoringSSL 或 patch 版本),记录 TLS handshake 各阶段耗时,区分是网络层丢包还是证书校验/OCSP stapling 导致延迟
- 检查 LB 设备(如 AWS ALB、F5、Traefik)自身 access log 或 metrics,确认是否在连接调度、健康检查探测、连接池复用环节引入延迟










