尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

浏览器输入网址后到底发生了什么?链路解析与性能排障指南

浏览器输入网址后到底发生了什么?链路解析与性能排障指南 不扯别的理论先从一个真实场景说起。早前有位朋友在生产环境遇到一个诡异问题他们的业务网站在公司内网打开特别快换到用户家里就时快时慢用户截图一看浏览器底部一直转圈大概转了七八秒才出内容。当时第一反应是服务端慢但排查了一圈应用耗时、数据库、Redis 全部正常最后把目光放回浏览器本身——输入网址到页面出现中间远远不止“发个请求”那么简单。那次之后我养成了一个习惯遇到网页加载问题先不用假设先把“浏览器输入网址后发生了什么”这条链路完整过一遍每一步都可能藏着问题。这篇就用一个完整案例把这条链路从头到尾拆开讲。适合刚接触前端或想做服务端开发的读者也适合那些天天写接口但没完整梳理过浏览器行为的同学。理解了这条链路再去排查网络问题、做性能优化、处理线上故障思路会清晰很多。1. 先搞清楚一个前提浏览器拿到的“网址”到底是什么很多人以为“网址”就是www.example.com那一串字符其实这是把“域名”和“统一资源定位符”混为一谈了。浏览器地址栏里输入的东西在 HTTP 协议的世界里需要先被拆解得明明白白才能进入后面的流程。1.1 URL 的结构拆解一个标准 URL 长这样https://www.example.com:443/path/to/page?namevalue#section拆开之后是六个部分协议Schemehttps告诉浏览器接下来用什么规则和服务端说话。常见的有http、https、file、data等。主机Hostwww.example.com让人能记住的域名。它不会直接参与网络传输真正的传输需要的是 IP 地址。端口Port443HTTPS 默认是 443HTTP 默认是 80。没写时浏览器会用默认值但端口其实一直存在。路径Path/path/to/page服务端资源在应用里的位置。注意这个路径是服务端自己定义的跟文件系统的目录没有必然对应关系。查询参数Query?namevalue一段键值对通常用于向接口传递附加信息。锚点Fragment#section这一部分有个关键特性——它不会发送到服务器纯粹是浏览器在本地定位页面内部位置用的。这里有个容易忽略的点锚点不会出现在网络请求里。你在浏览器 Network 面板抓请求看到的地址永远是#之前的部分。以前有同事排查问题非说某个请求带了奇怪的#参数导致服务端报错查了半天才发现锚点压根不会传上去。1.2 地址栏的“自作主张”搜索、补全与重定向你现在在地址栏输入一句“python 列表推导式”回车后浏览器并没有发起python 列表推导式这个域名的请求而是直接跳到了搜索引擎结果页。这就是地址栏的“智能处理”Chrome 内核的浏览器会把输入内容先判断一遍能解析成合法 URL 就当网址访问不能解析就交给默认搜索引擎。这也是为什么你输入localhost和输入我的电脑回车后表现完全不同。还有一个容易被忽略的机制是协议补全。你输入www.example.com回车浏览器心里其实会先补上https://或者http://具体补哪个由浏览器策略决定。Chrome 现在默认优先https://如果这个站点不支持 HTTPS才会降级到 HTTP。这个过程会白多一次“连接失败再重试”的开销所以对开发来说本地调试时最好把协议写完整省得每次都被浏览器自动跳转干扰。另外如果服务端配置过重定向比如把www.example.com301 到了example.com那浏览器会收到响应后再发一次全新请求。我见过很多新手在 Network 面板里看到两个请求以为是重复提交实际上第一个请求只负责告诉你“该去这个新地址”第二个请求才是真正拿到内容的。2. DNS 解析从域名到 IP 的逐级接力拆分完 URL浏览器拿到的是www.example.com这样一串字符。但 TCP/IP 协议栈不认识字符它只认 IP 地址。于是进入链路里的第二个关键环节DNS 解析。这个过程可以理解成把“人名”翻译成“身份证号”全互联网共享一套翻译系统。2.1 解析链路到底有几级完整的 DNS 查询路径是这样的浏览器 DNS 缓存浏览器自己在内存里存了一份“最近解析过的域名→IP”对照表命中就直接返回查询结束。操作系统 DNS 缓存浏览器缓存没有命中就交给操作系统。操作系统自己也缓存了一份同时这里还受hosts文件影响。本地 DNS 服务器递归解析器系统缓存也没有就发给网络配置里指定的 DNS 服务器通常是运营商提供的也可以是公共 DNS。根域名服务器本地 DNS 服务器如果也不知道就去问根服务器“.com这个顶级域归谁管”。顶级域名服务器拿到.com服务器的地址后再去问“example.com这个域名归谁管”。权威域名服务器最后问到真正持有该域名解析记录的服务器拿到具体的 A 记录或 AAAA 记录返回给本地 DNS 服务器层层回溯把结果带回浏览器。每一层都会把结果缓存一段时间这个时间由 DNS 响应里的 TTLTime To Live字段控制。TTL 的单位是秒比如设置为 600就意味着这条记录在缓存里最多存活 10 分钟。这里要理解一个关键点权威服务器才是真正“说了算”的地方前面几级都是“缓存中转站”。如果你改了域名解析的记录但客户端机器上各级缓存还没过期那用户访问到的还是旧 IP这就是域名解析变更后总是“有些人生效了有些人没生效”的根本原因。2.2 浏览器缓存与 hosts 的实际优先级很多人对 hosts 文件的优先级理解是错的。实际上在常见的操作系统里hosts 文件的优先级高于 DNS 服务器但未必高于浏览器缓存。举个例子你在 hosts 里把www.example.com指向127.0.0.1但浏览器缓存里还留着之前解析出来的93.184.216.34那浏览器会先用缓存里的 IP根本不会去看 hosts。这也是为什么修改 hosts 后要清理浏览器缓存或者直接开无痕模式才生效的原因——无痕模式没有历史缓存浏览器会老老实实按照“系统缓存 → hosts → DNS 服务器”的顺序走。顺带提一个开发场景前后端联调时频繁改 hosts改完总是遇到“还是连到测试环境”的鬼问题。排查方法很简单打开浏览器的chrome://net-internals/#dns手动清空浏览器 DNS 缓存然后再试。操作顺序是清浏览器缓存 清系统缓存Windows 下 ipconfig /flushdns 重试请求。顺序错了问题就是会反复出现。2.3 用 dig/nslookup 实际看一次解析光说理论不如实际查一次。在 macOS 或 Linux 终端里执行dig www.example.com noall answer输出大概长这样; DiG 9.10.6 www.example.com noall answer ;; ANSWER SECTION: www.example.com. 3600 IN CNAME example.com. example.com. 3600 IN A 93.184.216.34这行输出透露了两个重要信息CNAME记录www.example.com本身不是一条独立记录它“别名”指向example.com。A记录真正的主机记录是 IPv4 地址93.184.216.34。如果是用 Windows 系统用nslookup www.example.com也能拿到类似结果。这告诉我们一个域名最终可能解析出多个 IP浏览器会按顺序尝试连接连不上会换下一个。这就是 DNS 层面的负载均衡和高可用手段很多大流量站点会一次返回多个 IP配合短 TTL实现流量调度。DNS 这部分是整个链路里最容易被忽视的性能瓶颈。以前优化过一个慢查询站点页面本身内容很少但首次加载要 3 秒。用 Performance 面板一看光是 DNS Lookup 就花了 700ms。因为这个站点页面里挂了 20 多个不同域名的静态资源每个都要单独做一轮 DNS 解析。后来做了几件事合并静态资源域名、减少跨域请求数、把部分接口收敛到同域。DNS 解析次数降下来之后首屏时间直接砍掉将近一半。3. 建立连接TCP 握手与 TLS 的叠加过程拿到服务器的 IP 地址之后浏览器要做的下一件事是和服务器建立连接。网络传输不是“直接丢个包裹过去”而是要先把“传输通道”协商好这个协商过程就是 TCP 三次握手。如果是 HTTPS 站点还要在这条通道之上再叠加 TLS 握手。这也是为什么 HTTPS 比 HTTP 慢的主要原因之一但是在安全和速度之间这笔开销是值得付的。3.1 三次握手是怎么发生的TCP 三次握手的报文序列是这样的客户端 → 服务器发送一个 SYN 包seq初始序号 x。服务器 → 客户端回一个 SYNACK 包seq yack x 1。客户端 → 服务器再回一个 ACK 包seq x 1ack y 1。三次握手本质上是在做两件事确认双方都能收发包并同步初始序号。为什么必须是三次而不是两次因为双方都需要确认“我能收到你发的数据你也能收到我发的数据”。两次握手只能让发起方确认自己发的包对方能收到但服务器无法确认自己发的包客户端是否能收到。这是早期网络协议设计留下的一个经典问题如果只有两次握手服务器发了响应之后就开始等数据万一这个响应在网络中丢了服务器会一直傻等浪费资源。三次握手完成后TCP 连接就建立了。从浏览器输入网址回车到连接建立这段时间都显示在开发者工具 Network 面板里的Stalled、Initial connection和SSL三个阶段。我记得刚入行的时候看 Network 面板全是这种英文单词根本不知道什么意思。这里先简单对齐一下Stalled是浏览器在等待资源从连接池里被释放Initial connection是 TCP 握手有时包含 TLSSSL是 TLS 握手阶段TTFB是请求发出后等到服务器返回第一个字节的时间。搞懂这几个阶段排查页面加载慢的问题就有了基本地图。3.2 连接复用、队头阻塞与 HTTPS 的额外开销真实浏览器不会为每个请求都重新做一次三次握手。一个页面有几十个资源请求如果每个都重新握手网页加载速度会惨不忍睹。HTTP/1.1 引入了Keep-Alive 持久连接同一个 TCP 连接可以连续处理多个请求HTTP/2 更进一步支持多路复用把多个请求和响应在同一个连接里交错传输互不阻塞。真正需要注意的问题在 HTTP/1.1 时代的队头阻塞如果同一个连接上的第一个请求迟迟没响应后面的请求都会排队等。浏览器为了解决这个问题会为每个域名同时建立最多 6 个 TCP 连接Chrome 的默认限制并行处理请求。这也是为什么性能优化里常要求“减少同一页面下不同域名的数量”——域名太多会让浏览器建大量连接反而增加开销。HTTPS 的页面在 TCP 握手之后还要做 TLS 握手。TLS 1.2 及之前大概是这样客户端发送 ClientHello说明支持的加密算法和协议版本。服务器返回 ServerHello、证书包含公钥等。客户端验证证书有效性生成会话密钥通过非对称加密保护用公钥加密后发给服务器。双方确认密钥后后续所有数据都改用对称加密传输。这套流程让 HTTPS 站点的首请求要比 HTTP 多一两个来回的往返延迟。TLS 1.3 把握手压缩到一次往返延迟进一步降低。另外Chrome 近年对 HTTPS 站点默认开启HSTS预加载机制浏览器会强制使用 HTTPS 连接且跳过“是否强制跳转”的判断这在无形中帮着省了一轮重定向。在这个环节常见的问题特征是Connection 阶段超长。我帮人排查过一个问题页面在部分网络环境下特别慢查 TCP 连接阶段总是遇到Connection timed out。最后定位到是运营商网络对某些境外 IP 的连通性很差TCP 握手包发出去根本没响应。这种问题不是代码能解决的只能优化网络路径或换接入点。所以遇到真正超时的现象别一头扎进代码里先用ping或telnet 域名 端口看一眼网络通不通。4. 请求的发出与响应的返回协议细节和缓存策略TCP 连接建立好TLS 加密也协商完成浏览器终于可以正式发送 HTTP 请求了。这一阶段同样有很多容易被忽略的细节请求头里带了什么、服务端返回的状态码意味着什么、为什么第二次访问明显比第一次快。这些细节看似琐碎却直接决定我们在实际问题里能不能快速定位根因。4.1 请求行、请求头与请求体一个典型 HTTP 请求由三部分组成请求行、请求头、请求体。请求行最核心的部分是方法和路径比如GET /api/users HTTP/1.1这一行告诉服务器“我要以 GET 方法获取 /api/users 这个资源用的 HTTP 版本是 1.1。”完整请求长什么样用命令行工具curl -v可以看得清清楚楚curl -v https://www.example.com/输出里会有一堆以开头的行这些就是浏览器最终发给服务器的请求头。常见的几个字段含义如下Host告诉服务器请求的是哪个域名。因为一台服务器上可能同时托管多个网站靠 IP 区分不了必须有这个字段。User-Agent标明客户端类型和版本。服务端可以根据它做响应适配。Accept告诉服务器客户端能接受哪些类型的响应内容比如text/html、application/json。Accept-Encoding声明支持的压缩格式常见的有gzip、br服务器据此决定是否压缩响应体。Cookie携带之前服务器种下的身份信息实现会话保持。这是 Web 应用“记住登录状态”的基础。请求体一般只在 POST、PUT 等方法中出现用来提交表单数据或 JSON 内容。GET 请求没有请求体所有参数都放在 URL 的查询字符串中。这里分享一个调试经验很多人在浏览器开发者工具里看到请求头会误以为这些字段是浏览器自己“随便生成”的。实际上其中不少字段是可以被页面代码或浏览器扩展修改的。而且服务器最终拿到的请求头往往是经过代理、CDN 层层转发后“拼接”出来的有些字段如X-Forwarded-For就是代理加上的。所以排查真实 IP、真实协议版本之类的问题时不要把浏览器开发者工具里的展示当作唯一真相最好能在服务器端抓取真实报文看。4.2 响应状态码与服务端的“下一步动作”服务器处理完请求之后返回一个 HTTP 响应开头是状态行包含三位数字的状态码。状态码的含义直接影响浏览器接下来的行为状态码含义浏览器行为200请求成功正常渲染响应内容301永久重定向自动跳转到新地址并会缓存跳转结果302临时重定向自动跳转到新地址但不长期缓存304协商缓存未修改直接用本地缓存不下载新内容404页面不存在按页面内容渲染同时显示错误页500服务器内部错误正常接收响应但内容可能展示错误信息503服务不可用正常接收响应但通常用户看到的是维护页301 和 302 的区别特别容易被人忽略。301 是“永久搬家”浏览器会把www.example.com直接记住指向example.com下次访问不再请求旧地址302 是“临时换地方”每次访问都会先请求旧地址再被引导到新地址。线上遇到过最坑的事情是有人把 HTTP 到 HTTPS 的重定向配成了 302结果所有请求都多一轮跳转浪费一个完整往返的延迟。改用 301 后性能立刻恢复。304 状态码也是高频考点。服务端返回 304 时响应体为空浏览器会直接使用之前缓存的版本。判断是否命中 304 的逻辑会在下一节细说。4.3 强缓存与协商缓存第二次访问为什么快如果你认真比较过同一个页面的第一次和第二次加载时间会发现第二次明显快很多。除了浏览器自己渲染层面的优化更核心的原因是HTTP 缓存机制。HTTP 缓存分两类强缓存浏览器判断缓存还没过期直接使用连请求都不会发出去。控制字段是Cache-Control: max-age秒。比如max-age86400表示这条响应在 24 小时内有效。协商缓存缓存过期了但不确定内容是否真的变化于是带条件地问服务器“这个资源变了吗”服务器根据Last-Modified/ETag判断没变就返回 304变了就返回 200 和新内容。举个实战例子用响应头看一下Cache-Control: public, max-age86400 ETag: 5d8c72a5-2a19第二次请求时浏览器会带上If-None-Match: 5d8c72a5-2a19服务器比较 ETag 一致返回304 Not Modified浏览器直接复用本地缓存。这个机制对静态资源JS、CSS、图片特别重要。很多站点的首屏优化第一刀就砍在“静态资源缓存策略是否合理”上。如果Cache-Control配的是no-cache意味着每次都要发协商请求静态资源加载会多出大量 304 往返如果配的是no-store那干脆每次全新下载完全用不上缓存。很多“页面加载慢”的线下问题根源就在这种不看缓存策略的默认配置上。5. 浏览器拿到字节之后HTML 解析与渲染全流程请求的响应体是一堆 HTML、CSS、JavaScript 的字符流。肉眼看到的是一个完整页面但在浏览器眼里这堆字符要经历解析、构建、计算、绘制几个步骤每一步都可能成为性能瓶颈。很多人以为“页面渲染就是从上到下摆元素”实际完全不是这么简单。5.1 DOM、CSSOM 和 JavaScript 的阻塞关系浏览器的渲染核心流程如下解析收到的 HTML 字符串构建DOM文档对象模型树。解析 CSS 资源构建CSSOMCSS 对象模型树。把 DOM 和 CSSOM 合并生成渲染树只包含可见元素。计算每个节点的几何位置和大小叫布局Layout。根据布局结果把节点绘制到屏幕上叫绘制Paint最后再合成。这个流程里最需要记住的一点是HTML 解析是一个边下边解析边渲染的过程不是全部下载完才开始渲染。浏览器会一边获取 HTML 流一边构建 DOM遇到能显示的节点就先画出来。这也是为什么开发者工具里能看到“FCPFirst Contentful Paint”在“DOMContentLoaded”之前出现。而 JavaScript 会把整个流程打断。当 HTML 解析器遇到一个script标签不带defer或async时会暂停 HTML 解析先去下载并执行这段脚本执行完再继续解析后面的 HTML。这就是人们常说的“脚本阻塞解析”。CSS 则有个反直觉的特性CSS 会阻塞渲染但不会阻塞 HTML 解析本身。浏览器解析 HTML 时遇到link引入的样式表虽然会继续往下解析 HTML但在 CSSOM 构建完成之前浏览器不会开始渲染任何内容。原因是如果 HTML 先渲染出来样式表后加载完用户会先看到没有样式的页面一闪而过这是体验极差的白屏闪烁。基于这个原理前端性能优化里有两个经典法则script标签尽量放在body末尾或者加上defer/async让 HTML 先解析完。link relstylesheet放在head里及时加载但样式表不能太多太重否则首屏渲染会被长时间阻塞。5.2 从布局到绘制一个页面像素的诞生DOM 树和 CSSOM 树合并成渲染树之后浏览器开始计算几何信息。这个步骤叫 Layout在旧版本浏览器里也叫 Reflow。布局阶段要算出每个元素在视口里的宽、高、位置。这个过程跟 CSS 的属性值直接相关比如width、height、margin、padding、display、float、position等。布局完成之后是绘制浏览器把每个节点的颜色、边框、阴影、文字一个个画出来。现代浏览器Chrome 的 Blink 引擎已经把绘制拆成多个图层Layer最后交给 GPU 合成。这也就是为什么transform、opacity这类动画属性的性能要远好于修改width、height、top、left——前者只触发合成后者会触发整棵渲染树的重新布局和重绘代价完全是两个量级。有一个很实际的排查场景页面滚动时卡顿掉帧。打开开发者工具的 Performance 面板录制一段滚动操作会看到大量连续的 Layout 和 Paint 事件这就是卡顿的直接来源。想让滚动顺畅最简单的优化方向是减少布局抖动避免在 JS 里频繁读取offsetTop、scrollHeight这类强制同步布局的属性用transform代替直接修改几何属性尽量让动画作用在独立图层上。我第一次排查这类问题的时候还闹过笑话。页面有个轮播图用的 setTimeout 每隔 300ms 改一次图片的left值。每改一次整页重新布局一次手机上卡得一塌糊涂。后来把方案换成 CSStransform: translateX()再做图层提升性能直接翻了几倍。浏览器的渲染机制决定了你的代码每触发一次布局代价都是整棵渲染树级别的而不是你以为的“只动了那一张图”。6. 顺着链路做一次真实排障网站打不开/白屏到底卡在哪前面把链路拆完了最后实践一下如果你现在接到一个“页面打不开”或者“白屏”的反馈应该怎么顺着刚才这些环节一步步定位问题。这里面有一个核心思路不要从代码层开始猜先分清是“请求没发出去”“请求发出去了但没收到响应”“收到响应了但渲染不出来”这三个大阶段。先确定大阶段再缩小范围。6.1 用开发者工具和 curl 逐层定位收到问题反馈后第一步永远是打开浏览器开发者工具的 Network 面板看一下请求是否发出、状态码是什么、耗时分布如何。关键看几个指标面板字段对应链路阶段异常特征Stalled浏览器排队/连接池等待请求太多或连接已满DNS LookupDNS 解析域名无法解析Initial connectionTCP 握手连接超时SSLTLS 握手证书问题、握手超时TTFB服务器响应首字节服务端处理慢Content Download响应体下载CDN 慢、传输慢如果浏览器开发者工具都打不开页面就用命令行curl -w自定义输出耗时curl -o /dev/null -s -w dns:%{time_namelookup} connect:%{time_connect} tls:%{time_appconnect} ttfb:%{time_starttransfer} total:%{time_total}\n https://www.example.com/这行命令打印出 DNS 解析、TCP 连接、TLS 握手、首字节和总耗时。配合输出能快速判断是本地网络问题、DNS 问题还是服务端响应问题。我排查线上告警时第一波信息全部靠这条命令拿到。6.2 各环节常见故障的特征链路里每个环节出问题表面症状往往是相似的都是“打不开”但细节会给你线索DNS 解析失败浏览器提示“找不到服务器 IP 地址”或者用 nslookup 查完发现根本没拿到 A 记录。通常是域名没配置解析、DNS 服务器不可达、域名过期等。TCP 连接失败地址栏里的 IP 能 ping 通但请求端口连不上。常见原因有服务没启动、防火墙拦了端口、服务监听地址绑错了只监听 127.0.0.1外部无法访问。TLS 握手失败浏览器提示证书不安全、证书过期、证书域名不匹配或者SSL_ERROR_HANDSHAKE_ALERT。常见原因证书链不完整、服务器时间和真实时间偏差太大、客户端和服务端密码套件不匹配。几年前遇到过一次“同一张证书在 A 电脑正常、B 电脑报警”的现象最后定位到是 B 电脑系统时间往前调了一年证书有效期校验直接不过。服务器响应超时TTFB 过长请求发出去了服务端迟迟不返回。常见原因应用线程阻塞、数据库查询慢、上游服务调用超时、反向代理设置的超时时间太短导致连接被切断。响应正常但白屏状态码 200、内容已下载但页面空白。常见原因JavaScript 运行时抛错、CSS 选择器命中不到元素、渲染进程崩溃。这种问题要用 Console 面板看报错而不是在网络层死磕。6.3 一个实际案例的完整排查过程回到文章开头那个“生产环境时快时慢”的问题。当时拿到的线索是用户浏览器底部转圈 7-8 秒才出内容服务端资源占用正常应用日志没有明显异常。按链路排查先看 Network 面板发现一个 API 请求的 TTFB 特别长约 6 秒。排除了 DNS解析很快、TCP/TLS连接阶段正常确认瓶颈在“请求发出到服务器响应”之间。到服务端看 Nginx 访问日志发现该接口对应的 upstream 响应时间确实很长。再看应用日志定位到接口内部在调一个第三方服务第三方服务超时时间为 5 秒且没有配置熔断降级第一次超时后重试导致整体耗时被拖到 6 秒以上。最终方案第三方调用设置合理超时改成 3 秒、加缓存和降级策略、对上游失败快速返回兜底数据。整个过程没有一处涉及复杂调优全部是顺着“DNS → 连接 → 请求 → 响应 → 渲染”这条链路一层层找下去的。如果一开始就在代码里翻很可能翻半天都找不到问题因为问题根本不在自己代码里而是出在了连接上游的等待上。这条链路的价值就在于它给了你一个从浏览器到服务器之间的完整地图。以后再遇到网页加载慢、白屏、打不开这类问题不用慌先确定卡在哪个环节再针对那个环节深入排查。这条路走多了很多“诡异问题”到最后都能发现是在某个极其普通的地方栽了跟头。
返回列表