⏩ 一次浏览器访问 www.xxx.com
背后发生了什么?
—— 以及“我点了 ×,数据会不会丢?”的深度剖析
适读人群:Web 开发者、运维工程师、性能调优/安全从业者
1️⃣ 打开浏览器敲下网址:链路是如何启动的?
阶段 | 关键词 | 关键细节 |
---|---|---|
解析阶段 | DNS、缓存 | 浏览器先查本地/系统 DNS 缓存 → hosts → 浏览器 DNS 缓存 → 递归查询(可能走 DoH/DoT)。拿到 A / AAAA 记录。 |
连接阶段 | TCP 3‑way、TLS 1.3 | TCP:SYN → SYN‑ACK → ACK;若启用 QUIC/HTTP‑3 则直接 UDP 把 3 次握手+TLS 合并。 TLS:ClientHello→ServerHello→ … → Finished,现代浏览器基本启用 0‑RTT/1‑RTT。 |
请求阶段 | HTTP/2、HPACK、首部压缩 | 浏览器构造 GET / ,附带头(UA、Cookie、Accept‑Encoding…),优先用 HTTP/2 多路复用并发流。 |
服务器内部 | 负载均衡、反向代理、应用层 | 可能经历 ELB/Nginx/Sidecar,再到业务容器;中间会有 队列、限流、熔断。 |
响应阶段 | TCP 流/ QUIC 流 | 服务器写回状态行+头+Body;浏览器解析 HTML 发现静态资源→并发下载→渲染。 |
结束阶段 | FIN、连接复用 | HTTP/1.1的长连接或 H2/H3 的流会保持空闲等待复用;超时或浏览器关闭时发送 FIN 。 |
2️⃣ “我等得太慢点了 ×,服务器会怎么想?”
2.1 浏览器侧:取消动作
- 显式取消:用户点击“停止加载”或关闭 Tab。
浏览器对活跃的 TCP/QUIC 流发送RST
(或 HTTP/2RST_STREAM
)。 - 隐式取消:超时时间到、页面卸载等;浏览器会 abort fetch,内核可能直接
close()
套接字。
2.2 网络层:信号传递
协议 | 取消信号 | 服务器感知 |
---|---|---|
TCP | RST 包 | 立即终止连接;内核返回 ECONNRESET ,应用读/写触发异常。 |
HTTP/2 | RST_STREAM (error‑code=0) | 仅关闭对应流,复用的其他流不受影响;h2 服务器收到回调。 |
QUIC/HTTP‑3 | STOP_SENDING / RESET_STREAM | 与 h2 类似,但基于 UDP;更快释放流。 |
2.3 服务器侧:请求已经“进厨房”怎么办?
-
刚到负载均衡
- 连接被复用,RST 直接丢弃,Nginx 可能还没把请求转给应用,成本小。
-
已进应用但未执行业务
- 线程/协程池检测到 socket 断开会抛
IOException
;大多数框架(Spring MVC、Express、Gin…)会捕获记录ClientAbortException
后结束。
- 线程/协程池检测到 socket 断开会抛
-
业务正在执行
- CPU 正烧:图片压缩、大 SQL、RPC…
- 同步模型:直到代码写响应时才发现对端关闭 → 计算白做了,但数据未写出,资源浪费。
- 异步/事件模型:中途可检测
connectionAborted
flag 主动中止。
-
下游已持久化
- 例如下单接口:一旦写入数据库/消息队列,就算用户取消,也已成功。
- 于是就有了 支付回调、“查看订单”按钮来补偿交互的不确定性。
3️⃣ “数据丢不丢?”—— 三个不同层次的回答
角度 | 是否丢失? | 解释 |
---|---|---|
传输层 | ✅ 丢 | 浏览器取消后,剩余报文不再发送;未到达的响应也不再接收。 |
应用层 | ❌ 不一定 | 服务器可能已完整处理,持久化成功。仅“用户没看到”而已。 |
业务语义 | 需要幂等 | 好的接口要做到 幂等&可重试:PUT/DELETE 设计有幂等键,POST 返回可查询的 operationId 。 |
4️⃣ 工程实践:让“取消”更友好
-
前端
AbortController
取消 fetch;- 超时反馈:Skeleton Screen / 旋转骨架;
- 使用 web‑worker + keep‑alive 提升体验。
-
网关/反代
- 配置 proxy_request_buffering off (Nginx) 让大上传即时回源;
- 针对
client_abort
及时丢弃后端响应。
-
后端
- 设计 可中断任务:定期检查
Thread.currentThread().isInterrupted()
; - 重型计算放入 作业队列,前端轮询
jobId
; - 数据库写操作加 事务/唯一约束 保证幂等。
- 设计 可中断任务:定期检查
-
监控
- 关注
499 Client Closed Request
(Nginx)、444 No Response
; - 链路追踪标记
aborted=true
,评估浪费。
- 关注
5️⃣ 小结
- 用户取消请求后,网络传输层面数据一定中断;
- 服务器逻辑可能仍在继续,甚至已成功写库;
- 工程上通过 幂等、作业分离、连接检测 将“浪费”降到最低;
- 让前端与后端都能 优雅应对取消,才是真正的端到端体验优化。