“订单页一直转圈”,还不是一个可定位的问题
打开订单 O204,页面迟迟没显示结果。可能根本没有拿到服务器地址,可能证书验证失败,也可能已收到响应首部、却还在等正文;甚至正文已完整,页面还在执行 JavaScript。
先记录具体 URL、时间、客户端网络、实际协议、是否复用连接和响应来源,再判断“哪一步已经完成、哪一步仍缺证据”。同样是超时,发生在连接之前和业务响应之前,需要查的对象不同。
排障是用完成边界排除假设,不是按协议名字轮流改参数。 本章的分层路线先限定为新建 HTTPS over TCP;QUIC、代理、多路复用、重定向和缓存会改变观测,需要另行标明。
先找最早缺失的证据
查看 Mermaid 源码
flowchart TB
A["一次新建 HTTPS/TCP 请求"] --> D{"已取得目标地址?"}
D -->|否| DX["查解析结果与实际解析路径"]
D -->|是| C{"连接已完成?"}
C -->|否| CX["查路由、目标端口、监听与过滤"]
C -->|是| T{"TLS 验证已完成?"}
T -->|否| TX["查信任链、名字、时间与握手错误"]
T -->|是| H{"收到响应首字节?"}
H -->|否| HX["查请求发送、代理等待与服务处理"]
H -->|是| R{"正文已完整?"}
R -->|否| RX["查消息边界、传输与消费速度"]
R -->|是| B["核对 HTTP 状态、业务结果与页面处理"]这是一条缩小范围的路线,不是所有请求都必须重新执行每一步。缓存可直接提供内容,复用可跳过新建连接,错误响应也可能只有首部。获得证据后,回到那次请求的真实路径判断。
ping 成功只证明相应 ICMP 探测获得响应,不能证明目标 TCP 端口、TLS 和订单接口正常;ping 失败也可能因为 ICMP 被过滤,不足以宣判网站不可达。不要拿另一个地址、另一个协议的成功替代当前请求的证据。
DNS:先分清“没有回答”和“回答说没有”
可以先向当前配置的解析器查询测试域名的 A、AAAA:
dig +time=2 +tries=1 example.com A
dig +time=2 +tries=1 example.com AAAA| 观察 | 能说明什么 | 还不能说明什么 |
|---|---|---|
| 超时、无法联系服务器 | 本次查询未获得可用回答 | 不能直接认定域名不存在 |
| NXDOMAIN | 回答表示该名字不存在 | 要确认所问名字、解析器和负缓存 |
| NOERROR 但该类型无答案 | 名字查询未报不存在,可能缺少所问记录 | 不能把“无 A”当作“无 AAAA” |
| SERVFAIL | 解析器未能完成本次处理 | 具体是上游、验证还是其他错误需再查 |
| 返回 A/AAAA | 得到了对应类型的地址记录 | 不保证该地址的 HTTP 服务健康 |
保留 status、回答、TTL 和查询对象。只看 +short 的空输出容易丢掉错误类别。比较指定解析器、权威回答或 +trace 可以继续缩小范围,但 +trace 是工具自己进行迭代查询,不是浏览器那次请求经过的原始解析过程;网络限制也可能让它无法走完。ISC BIND:dig
2026-10-09 本机 DiG 9.10.6 对 example.com A 的实际查询得到 NOERROR、两个 A 记录,查询时间 19ms。地址和 TTL 是当时回答,不把它们写成固定预期。浏览器可能使用自己的安全 DNS、缓存或代理,curl 也可能有不同解析后端;一次 dig 成功不能证明浏览器使用了同一答案。
连接与 TLS:改变地址时,别顺便改变身份
若已获得地址但连接失败,检查实际目标地址、端口、IPv4/IPv6、服务监听、路由与过滤。连接被明确拒绝和一直等不到完成,表现不同;没有抓包时,不把超时强行归因于某个防火墙规则。
需要验证某个 IP 时,直接把 URL 改成 IP 会改变 TLS 验证名字和 HTTP 目标。curl 的 --resolve host:port:address 可以指定该名字和端口连接的地址,同时保留 URL 中的主机名。它适合对照地址问题,绕过这次真实 DNS 查询后,就不能拿结果测 DNS 性能。curl:--resolve
TLS 失败则继续看证书链、URL 服务名字、有效时间与具体握手错误。使用正确测试 CA 的 --cacert,只为这次传输建立明确的信任;-k 会跳过验证,得到的“能访问”不证明证书问题已经修好。curl:证书验证
连接超时参数也不只覆盖纯 TCP。curl 的 --connect-timeout 约束连接阶段,包括相关的名字解析与协议握手;--max-time 限制整个操作。哪个参数触发、之前哪些阶段已完成,要结合日志判断。
curl 的时间是累计值,不能直接相加
下面命令演示一次直连、HTTP/1.1、无重定向跟随的测量格式。将 URL 换成待排查地址;它不是下面本地实验的实测命令:
curl -q --noproxy '*' --http1.1 --connect-timeout 3 --max-time 10 \
-sS -o /dev/null \
-w 'dns=%{time_namelookup}\nconnect=%{time_connect}\ntls=%{time_appconnect}\nfirst=%{time_starttransfer}\ntotal=%{time_total}\nhttp=%{http_code}\nip=%{remote_ip}\n' \
https://example.com/-q 放在最前面,避免默认 curl 配置文件改变测试;--noproxy 刻意直连。如果原问题经过代理,应另保留实际代理路径做对照,直连成功不证明那条代理路径正常。-o /dev/null 仍下载正文,只是不保存;它不是 HEAD,也没有跳过传输成本。
| 字段 | 从操作开始累计到哪里 | 当前限定下的解读 |
|---|---|---|
| time_namelookup | 名字查找完成 | 可能使用缓存或指定地址,不等于一次线上 DNS 往返 |
| time_connect | TCP 连接完成 | 扣除前段后可辅助看连接阶段 |
| time_appconnect | TLS 等应用连接握手完成 | 不是单独的 TLS 用时 |
| time_starttransfer | 收到响应首字节 | 包含前序准备与后续等待,不是正文下载完成 |
| time_total | 整个操作结束 | 包括正文传输等后续工作,不包括页面渲染 |
这些值以秒计。以同一次成功的新建直连 HTTPS 请求为前提,可以辅助计算 connect−dns、tls−connect、first−tls、total−first。后两段分别是握手之后到首字节的等待,以及首字节之后到传输结束的时间;first−tls 不是纯服务端计算时间,还可能包含请求发送、网络、代理和排队。curl:write-out、应用连接计时、首字节计时
跟随重定向时,部分计时会累积多次事务;复用、代理 CONNECT 和多路复用还会改变边界。失败请求中某个字段为 0,也可能表示对应阶段未完成或未获得值,不能一律解释成“那一步瞬间完成”。不要对所有结果机械做差、相加或比较。
真实实验:慢在首字节前,还是正文里?
从仓库根目录运行公开入口,也可从任意目录使用绝对路径:
python3 examples/network-interview/user_code/diagnose_https.py需要 Python 3.10+、curl 和 OpenSSL CLI。入口创建临时 CA 与含 shop.example.com SAN 的证书,只监听 127.0.0.1 随机端口;每次启动一份有限 HTTPS 服务,由真实 curl 发起一条新 HTTP/1.1 连接,不使用代理和 --insecure。先运行入口,再按 examples/network-interview/core/README.md 阅读 core/diagnostics.py,证书共用 core/tls.py。
服务端设置三组场景:baseline 立即返回;before 在首部前等待 250ms;body 先发首部,再等 250ms 发正文。请求均为 GET /orders/O204,名称是实验分支标记,不是 URL 路径。--resolve 明确把名字映射到回环,所以这一部分没有测量外部 DNS。
2026-10-09 本机 Python 3.13.3、curl 8.7.1、OpenSSL CLI 3.6.3;服务端记录协商到 TLSv1.3。一次通过的输出换算为毫秒如下,字段仍是同次请求的累计值:
| 分支 | TLS 完成 | 首字节 | 总时间 | first−tls | total−first |
|---|---|---|---|---|---|
| baseline | 2.347 | 2.541 | 2.587 | 0.194 | 0.046 |
| before | 1.904 | 261.557 | 261.865 | 259.653 | 0.308 |
| body | 2.002 | 2.116 | 262.471 | 0.114 | 260.355 |
两个慢分支总时间都约 262ms,但 before 主要等首字节,body 主要在首字节之后等待。数值会随调度和环境变化;公开入口只核对阶段顺序与对应等待至少 200ms,不要求每次精确等于 250ms。
实验还验证了失败和状态码边界:
| 分支 | 实际 curl 退出码 / HTTP | 支持的判断 |
|---|---|---|
| 不信任临时 CA | 60 / 000 | 证书验证失败,未取得 HTTP 状态 |
| 信任 CA,但 URL 名字为 other.example.com | 60 / 000 | 主机名验证失败,信任链成功不足以通过 |
| TLS 已完成,首部前等 500ms;总限时 150ms | 28 / 000 | 本次在等待响应前被限时终止,实际超时检查有调度误差 |
| 立即返回 HTTP 503,未用 --fail | 0 / 503 | 传输完成不等于业务成功 |
| 同样 503,加 --fail | 22 / 503 | 命令将相应 HTTP 错误作为失败 |
| 保留未监听端口,连接阶段限时 1 秒 | 本机 28 / 000 | connect/tls/first 均为 0,尚未完成连接 |
最后一项初始预期“未监听就一定立即拒绝”不成立:本机表现为连接阶段超时。入口允许对应环境返回 7 或 28,并核对连接尚未完成。不能把它与上一项 28 混为“服务器计算慢”。HTTP 000 是 curl 未取得响应码时的输出,不是服务器发出的合法 HTTP 状态。curl:退出码
实验不保存私钥到仓库、不修改系统信任;OpenSSL 单次命令限 15 秒,socket 等待 3 秒,curl 子进程限 5 秒,服务线程结束等待 4 秒。证书验证、超时或 --fail 在 503 后提前关闭,只在实际对应失败分支允许相应断开,其他服务错误会让实验失败。它没有模拟公网丢包、真实代理或数据库,也不从这些毫秒值推算公网 RTT。
动手定位等待与完成边界
先比较两份总时间相同的结果,再看两个退出28的场景。累计值、完成边界和HTTP状态要一起判断,才能选下一条证据。
一份教学观测:新建、直连HTTPS over TCP,无重定向和连接复用。先读累计值,再定位等待或最早未完成的边界。
curl退出码:0 · HTTP:200
first=380ms不是独立的380ms等待;先扣除前面已完成的阶段。失败时先找完成边界,再判断哪些差值有意义。
数值为手算教学样本,不是上方回环实测;失败时curl可能输出0,这里显式标为未取得完成值。HTTP 000表示未取得状态码。对照只支持定位范围,根因仍需各跳证据。
抓包:先看哪条流,再看它能证明什么
有抓包权限时,先按接口、目标地址/端口和时间范围定位对应连接,再观察握手、数据缺口、ACK 和关闭。普通 HTTPS 抓包可以显示相应的传输与加密握手信息,但不会凭空显示订单 JSON;TLS 1.3 的多数握手认证消息也已加密。解密需要相应会话秘密等条件,持有 RSA 证书私钥也不意味着能解开 ECDHE 会话。Wireshark:TLS 解密条件
| 过滤类型 | 示例 | 使用位置 |
|---|---|---|
| 抓取过滤器 | tcp port 443 | 抓取时缩小进入文件的流量 |
| 显示过滤器 | tcp.port == 443 | 已抓数据中的展示条件 |
| 显示过滤器 | tcp.stream == 0 | 选择工具编号的一条 TCP 流 |
| 显示过滤器 | tcp.analysis.retransmission | 查看工具推断的重传线索 |
两类过滤语法不同,tcp.stream 的 0 也只是当前文件里的流编号,不是固定的订单连接。Wireshark:抓取过滤器、显示过滤器
重传标记是工具根据抓取到的包推断出的线索;本机抓取丢包、乱序、分段/合并卸载和抓取位置都会影响观察。应结合两端和应用时间线,不把一条分析标记直接当成唯一根因;发送端 TCP 重传也不能证明订单被重复执行。
本次实际尝试 tcpdump 捕获回环端口,系统拒绝访问 BPF 设备,因此没有取得包文件。正文只解释规范与工具的观察方法,不提供伪造的握手/重传截图。HTTP/3 应按 QUIC/UDP 流量寻找证据,不能因为没有 TCP SYN 就说请求没发出去。
从“慢”走到下一条可验证假设
订单慢在首字节之前,先对齐客户端请求时刻、入口收到/回源时刻、实例开始/完成时刻;业务未开始可能等在连接池或代理,业务已开始才继续看计算和依赖。正文阶段慢,再检查响应量、链路传输、流控/拥塞与消费者读取;网络完成后页面仍慢,则查看解析、脚本和渲染。
每次对照只改变一个关键变量,例如同一 URL 换地址、同一地址比较新建和复用、同一服务比较直连和代理。保留请求身份与时间关联,分享记录时将鉴权值替换为占位,不把另一用户、另一版本或另一个缓存来源的响应当成同一条件。
30—60 秒参考回答
我会先固定 URL、时间、网络和实际请求路径,判断失败还是慢,再按地址解析、连接、TLS、首字节、正文和页面处理找完成边界。dig 看查询状态、记录和 TTL,但不一定走浏览器的解析路径;curl 同时看退出码、HTTP 状态和累计时间,不能把 TTFB 当纯服务端计算,也不能把退出 0 当业务成功。需要指定 IP 时用 --resolve 保留名字验证,不用关闭 TLS 验证掩盖问题。最后结合各跳日志与适当抓包验证假设,注明缓存、复用、代理和协议差异。
四个追问及解析
1. curl 返回 28,就是服务器太慢吗?
不是。连接阶段和响应等待都可能超时,本地两组实验都返回了 28。先看连接和 TLS 是否完成、使用哪个截止条件,再寻找该阶段的原因。
2. TTFB 很大,能断定 SQL 很慢吗?
不能。累计 TTFB 包含名字、连接、握手等准备;即使扣掉握手,也仍包含发送、网络、代理和排队。需要实例日志或链路证据把等待关联到具体业务步骤。
3. dig 成功、浏览器失败,下一步是什么?
确认浏览器实际名字解析和连接地址、代理/安全 DNS、地址族与缓存,再看端口与证书。两种工具使用不同路径时,dig 的成功没有排除浏览器自己的问题。
4. 看到了 TCP ACK,就能证明订单成功吗?
ACK 确认传输层接收范围,不确认应用解析、授权或提交。要看 HTTP/业务结果,超时的不确定提交则按幂等和查询契约处理。
两个容易说错的结论
“DNS、connect、TLS、first、total 相加就是总耗时。” 它们多为从同一操作起点累计到不同事件的值;先限定路径,再按相应边界做差。
“加 -k 能成功,所以 HTTPS 已经修好了。” 它移除了验证条件。正确修复需要让名字、链、时间和信任配置满足真实客户端的要求。
有了分层证据,面试回答也应从结论、机制和边界展开:遇到超时先问卡在哪一步,遇到“可靠”先问保证到哪一层,再用反例检验答案。
资料核验日期:2026-10-09。curl、ISC BIND、Wireshark 官方资料与本地运行结果分别列明;抓包因权限未完成,不据此声称观察过包轨迹。