Skip to content

HTTP、TCP、TLS 与网络排障

后端排障先回答三个问题:请求有没有到达、连接在哪一步失败、服务是否已经执行。不要把所有错误都归因于“接口慢”。

一次请求经过什么

txt
DNS → TCP 三次握手 → TLS 握手 → HTTP 请求/响应 → 连接复用或关闭

DNS 失败通常没有连接;TCP 连接失败说明端口、路由或防火墙问题;TLS 失败要看证书、SNI、协议和 ALPN;HTTP 4xx/5xx 则说明请求已经到达某个 HTTP 服务。

用 curl 拆时间

bash
curl -sS -o /dev/null -w 'dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://example.com/health

这是观测单次请求的线索,不是完整分布式追踪。connect 包含 TCP 建连,time_appconnect 反映 TLS 完成;复用连接时这些值可能接近零。对比域名、IP、代理和容器内外结果,先定位作用域再改配置。

HTTP 语义决定能否重试

连接超时不代表服务没有执行:请求可能已经到达并完成写库,只是响应丢失。GET、HEAD 等幂等请求通常更容易安全重试;POST 要使用幂等键或业务去重。重试必须有超时、次数上限、退避和总预算,否则会把下游故障放大。

看到 502 要继续区分:网关无法连接上游、上游提前断开、响应头超时、响应体读取超时。分别检查网关错误日志、上游监听地址、连接池和应用 request id。

TLS 快速核对

bash
curl -vkI https://api.example.com/health
openssl s_client -connect api.example.com:443 -servername api.example.com -alpn h2

-k 只用于本地观察握手,不能作为修复方案。证书的 SAN 必须匹配域名;多站点 HTTPS 依赖 SNI;HTTP/2 协商依赖 ALPN。证书过期、链不完整、系统时钟错误都可能表现为“HTTPS 连不上”。

排障顺序

  1. 记录时间、请求 ID、客户端与服务端地址。
  2. 从客户端、网关、容器、应用日志对齐同一请求。
  3. getent hostsss -lntpcurl -v 证明 DNS、监听和协议状态。
  4. 检查连接池、文件描述符、线程/协程池和下游超时。
  5. 修复后重复同一条探针,并确认业务副作用没有重复发生。

不要默认执行 kill -9 或重启;先保留日志、堆栈和连接状态。生产命令需按权限、变更流程和回滚方案执行。

练习

本地起一个 HTTP 服务,分别制造 DNS 错误、未监听端口、延迟响应和 500;用 curl -v-w 记录差异。练习只使用本机地址,不对生产服务发压测。

参考

DNS:先确认解析结果,再谈应用

bash
getent ahosts api.example.com
# 容器内执行,比较宿主机结果
cat /etc/resolv.conf

同一域名可能返回多个地址(轮询、IPv4/IPv6、地域 DNS)。getent 证明的是当前解析器给出的结果,不证明每个地址都可达。关注 TTL、搜索域、容器 DNS 和是否存在 AAAA 记录导致客户端优先尝试 IPv6。不要直接把 /etc/hosts 当生产修复;它会绕过 DNS 变更并制造漂移。

TCP:连接成功不等于请求成功

三次握手建立连接,四次挥手关闭连接;RST 表示连接被异常重置。排查监听和连接状态:

bash
ss -lntp                 # 谁在监听 TCP 端口
ss -ant state syn-recv  # 是否堆积半连接
ss -ant state time-wait | wc -l

SYN-RECV 多且持续增长,优先查服务 accept、网络策略和 backlog;TIME-WAIT 是主动关闭方的正常状态,不能仅凭数量下结论。容器中 127.0.0.1 只代表容器自身,服务间访问应使用监听的 0.0.0.0 与服务名。

TLS 与 HTTP 版本

HTTP/1.1 常用一个连接顺序处理请求,队头阻塞会放大慢请求;HTTP/2 在一条 TCP 连接上多路复用,但 TCP 丢包仍会影响整条连接;HTTP/3 基于 QUIC/UDP,是否可用由客户端、网关和负载均衡共同决定。不要把“启用 HTTP/2”当作单纯改应用代码。

bash
curl -v --http1.1 https://api.example.com/health
curl -v --http2   https://api.example.com/health

看输出中的 ALPN: server accepted、协议版本和响应头,确认协商结果。代理可能在客户端到网关使用 HTTP/2、网关到上游仍使用 HTTP/1.1,因此要分别测两段。

超时必须形成预算

一次请求通常包含连接超时、TLS 超时、响应头超时、响应体读取超时和总截止时间。下游调用的总预算必须小于上游剩余预算,否则上游已经超时,后台调用还会继续占用连接和线程。

txt
入口总截止时间 2s
  ├─ DNS/TCP/TLS 300ms
  ├─ 下游 A 800ms(含最多一次重试)
  └─ 数据库 600ms

超时后要取消下游请求并释放连接;只在调用方返回 504、却让下游继续跑,会造成“幽灵请求”。日志至少记录 timeout 类型、耗时、attempt、上游地址和 request id。

502、503、504 怎么拆

状态典型边界首先查什么
502网关拿到无效响应、连接被上游重置网关 error log、上游进程日志、协议/端口
503当前服务不可用或没有健康实例readiness、连接池、限流和负载均衡成员
504网关等待上游超时上游耗时分位数、下游依赖、网关 timeout 配置

状态码只是观测点。应用也可能自己返回 502;必须结合 Server、网关 request id 和应用日志确认产生者。

连接池与资源耗尽

HTTP keep-alive、数据库连接池和文件描述符都属于有限资源。连接池过小会排队,过大则把压力推给下游;必须同时观察 active、idle、pending、获取连接耗时。ulimit -n 只是上限,不能替代应用指标。

bash
lsof -p <pid> | wc -l
# 仅查看自己启动的本地进程;生产需遵守权限与变更流程

出现“偶发超时”时,比较连接池等待时间与网络连接时间:前者高说明应用资源排队,后者高才更像网络或下游问题。

抓包与证据

应用日志回答“代码走到哪里”,curl -v 回答“客户端看到了什么”,抓包回答“线上传了什么”。在有权限且经过审批的环境使用 tcpdump,只抓目标地址和短时间窗口,避免采集敏感 payload:

bash
tcpdump -nn -i any 'host 10.0.0.8 and port 443' -c 100

TLS 加密后抓包通常只能看到握手、IP、端口和时序;不要声称抓包能直接读取 HTTPS 业务内容。优先使用脱敏后的 trace、指标和网关日志。

一张故障决策表

txt
DNS 无结果?       → resolver / TTL / 容器 DNS
有 IP 但 connect 失败? → 路由 / 安全组 / 监听 / backlog
TCP 成功 TLS 失败?    → 时间、SAN、SNI、链、ALPN
HTTP 4xx?             → 请求语义、鉴权、路由
HTTP 5xx?             → 产生者、上游、资源池、依赖
只有高并发才失败?     → 连接池、FD、队列、限流、backlog、GC
只有重试后数据异常?   → 幂等键、请求是否已执行、超时预算

每一步都留下可复现探针和时间窗口。修复后的成功响应不够,还要检查延迟分位数、错误率、连接池水位和业务副作用。

面试问答

1. curl -w 的那串时间指标分别代表什么?怎么用来定位问题?

  • dns 是域名解析、connect 是 TCP 建连、tls(time_appconnect)是 TLS 完成、ttfb 是首字节、total 是总耗时
  • 复用连接时 connect 和 tls 会接近零,这是正常的
  • 对比域名、IP、代理、容器内外的结果,先定位作用域再改配置
  • 注意它是观测单次请求的线索,不是完整分布式追踪

2. 502、503、504 的排查方向有什么不同?

  • 502:网关拿到无效响应、连接被上游重置——先查网关 error log、上游进程日志、协议和端口
  • 503:当前服务不可用或没有健康实例——先查 readiness、连接池、限流和负载均衡成员
  • 504:网关等待上游超时——先查上游耗时分位数、下游依赖、网关 timeout 配置
  • 状态码只是观测点:应用自己也可能返回 502,要结合 Server 头、网关 request id 和应用日志确认产生者

3. 超时为什么必须做成预算?超时之后下游还在跑怎么办?

  • 一次请求包含连接超时、TLS 超时、响应头超时、响应体读取超时和总截止时间,下游调用的总预算必须小于上游剩余预算
  • 超时后要取消下游请求并释放连接,否则上游已经超时、后台调用还在继续,就是「幽灵请求」,白占连接和线程
  • 加分:能画出一个入口 2s 的预算分配——DNS/TCP/TLS 300ms、下游 A 800ms(含最多一次重试)、数据库 600ms
  • 超时不等于未执行:写操作重试前要有幂等键,用 request id 和数据库记录确认是否已执行;重试只放在明确的幂等边界,避免客户端、网关、SDK 多层叠加重试

4. TCP 连接状态里,SYN-RECV 和 TIME-WAIT 堆积分别说明什么?

  • SYN-RECV 多且持续增长才是问题:优先查服务 accept 速度、网络策略和 backlog
  • TIME-WAIT 是主动关闭方的正常状态,不能仅凭数量下结论
  • ss -lntp 看谁在监听,ss -ant state syn-recv / state time-wait 看状态堆积
  • 容器里 127.0.0.1 只代表容器自身,服务间访问要用 0.0.0.0 监听加服务名访问

5. DNS 排障从哪里入手?

  • getent ahosts 看当前解析器给出的结果;同一域名可能返回多个地址(轮询、IPv4/IPv6、地域 DNS),有结果不代表每个地址都可达
  • 容器内外对比 /etc/resolv.conf,关注 TTL、搜索域,以及是否存在 AAAA 记录导致客户端优先尝试 IPv6
  • 别踩的坑:不要把改 /etc/hosts 当生产修复——它会绕过 DNS 变更流程,制造配置漂移

6. ping 通但 HTTP 不通,问题在哪一层?

  • ping 走 ICMP,只能证明主机可达;TCP 端口没监听、TLS 握手失败、HTTP 路由出错,ping 全是通的
  • curl -v 分段确认:connect 通说明 TCP 层没问题,卡在 TLS 查证书和协议版本,有响应了看状态码

7. HTTP/2 一定比 HTTP/1.1 快吗?

  • 多路复用确实减少连接数和队头阻塞,但实际收益受服务端、代理、TLS、丢包和请求形态影响
  • 别踩的坑:把"上了 h2"当成优化结论——请求形态不匹配或弱网高丢包时可能不升反降,用压测数据说话

8. 怎么证明瓶颈在连接池,而不是服务本身慢?

  • 把总耗时拆成三段对比:等池子的时间、建立连接的时间、服务处理的时间
  • 只有池等待长才是连接池问题;建连慢查网络和 DNS,处理慢查服务本身
  • 别踩的坑:只看总耗时永远定位不了——"服务慢"和"等不到连接"的修法完全不同