ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

浏览器输入URL回车后发生了什么:DNS解析、TCP连接、渲染与性能优化全解析

浏览器输入URL回车后发生了什么:DNS解析、TCP连接、渲染与性能优化全解析 1. 回车之前这个动作其实已经触发了一连串预判逻辑很多人聊这道题习惯性第一句话是“浏览器先解析URL然后DNS解析……”这没错但漏掉了最前端的环节。你按下回车这个动作在网线里还没跳动任何一个字节的时候浏览器已经替你做了好几件事而这些事直接决定了你这次访问到底是“秒开”还是“转圈三秒”。1.1 URL解析浏览器先搞懂你输入的到底是不是网址地址栏不是只能输入网址也可以输入搜索词。所以浏览器拿到你输入的内容后第一步是判断这串字符是域名、是IP地址、还是用户想搜索的关键词。这个判断规则不难理解——如果输入的内容像域名比如“example.com”这种结构浏览器会按网址处理如果输入的是乱序中文或词组就默认走搜索引擎接口。这里有一个很多人忽略的细节URL不一定完整。你输入“example.com”一路回车浏览器要自己补全协议。现在的主流做法是默认走HTTPS如果你手动输入的是“http://”就按HTTP请求。补全协议之外浏览器还会补全路径比如把“example.com”理解成“https://example.com/”这个根路径“/”会被自动带上。在动手查询之前浏览器还做了另一层判断——HSTS安全策略。简单说有些站点的证书服务端配置了强制HTTPS浏览器会有一个内部列表记住这些站点。如果这个域名在列表里即使你输入的是http协议浏览器也会在发起请求前自动升级成HTTPS。现实场景里这一层帮了很多倒忙的人明明服务端已经禁用了HTTP但用户手动输入http结果被浏览器“悄悄”改成https反而把问题掩盖了排查的时候容易第一眼找错方向。1.2 缓存检查能不出门就不出门能少走一步就少走一步判断完URL之后浏览器立刻去做缓存检查。这里有两层缓存需要分别看第一层是DNS缓存。域名对应的IP地址在上一次访问时已经被记录过浏览器会把“域名 → IP”的映射关系存起来TTL到期之前直接用就不用再走一遍DNS解析流程。这一层命中后面讲的那一大段“DNS逐级查询”就全部省掉了。第二层是HTTP缓存。也就是这个页面、图片、脚本资源之前已经下载过了如果响应头里带了缓存标记比如Cache-Control: max-age300浏览器会判断这300秒内可以直接从本地磁盘缓存里读连请求都懒得发。有人测页面加载速度的时候刷新一次快、硬刷新慢就是缓存命中与否的差别。缓存检查完毕如果发现资源有效整个流程到这里就结束了页面直接从本地还原。如果缓存过期或者资源被标记为要每次校验浏览器才会继续往下走。这一步的实际感受很直接你清空缓存后再访问一个站点明显比平时慢原因就是之前省掉的所有步骤全部被重新执行了一遍。2. 网址变IPDNS解析实际上是一场“接力问路”缓存没命中浏览器需要知道这个域名对应的IP地址才能建立连接。这就进入所有人都能背两句、但很少有人能讲完整的DNS解析环节。2.1 缓存层级浏览器、操作系统、本地DNS服务器各查一遍DNS查询不是一条路冲到根服务器而是层层递进。浏览器先查自己进程里的DNS缓存没命中就交给操作系统。操作系统会先翻本地的hosts文件——很多开发者改hosts做域名指向测试改的就是这一层——然后查系统DNS缓存还没有就走“网络配置里填的那个DNS服务器地址”。这个网络配置里填的地址通常被称为“本地DNS服务器”或者“递归解析器”。你家里路由器自动分配的DNS公司网络管理员配置的DNS公共DNS服务商提供的地址都算这一层。它收到你的查询请求后如果自己缓存里有结果就直接返回没有它就替你往上追溯。整个过程对使用者来说是透明的你只发出了一个“example.com对应什么IP”的请求复杂度全被这层服务器消化掉了。2.2 根服务器、顶级域服务器、权威服务器层层转交最终拿到答案当地址在本地DNS服务器也没命中时真正的“问路”才刚开始。第一步本地DNS服务器向根服务器发起查询问题是“example.com是哪个服务器在管”根服务器自己不知道具体地址但它知道顶级域“com”由谁负责于是返回com顶级域名服务器的地址列表。第二步本地DNS服务器再去问com顶级域服务器“example.com在哪”顶级域服务器依然不给最终答案它只返回example.com这个域名的权威服务器地址也就是真正维护这个域名记录的那台DNS服务器。第三步才是关键本地DNS服务器向权威服务器发起查询这一次得到的才是“example.com → 具体IP地址”的最终映射。拿到结果后本地DNS服务器会把这个映射缓存一份返回给浏览器浏览器也缓存一份再交给操作系统缓存一份。这里有个很实用的小知识整个过程中“本地DNS服务器替代你问了三次”所以如果本地DNS服务器配置得离你很近、缓存命中率又高你打开站点的速度会非常快。反之如果一个站点刚换了IP但访问还指向旧地址往往就是中间某层缓存没过期。排查的时候先用系统自带的nslookup或者dig直接查权威服务器再对比本地DNS返回的结果立刻能定位是哪一层缓存出了问题。2.3 容易被忽略的CDN与DNS安全细节如今大部分体量稍大的站点都接了CDN这会让DNS解析比看起来复杂一步。CDN厂商会在权威DNS服务器上做“智能解析”它根据你本地DNS服务器的地理位置和运营商信息直接返回一个距离你最近、负载最轻的缓存节点IP而不是源站IP。也就是说同样一个域名你在不同城市解析出来的IP很可能不一样。理解了这一点就不会在看到两个IP不同的大惊小怪。另一个值得提的是DNS查询本身是明文传输的。正常情况下你访问什么域名、查询记录是什么网络路径上的设备是可以看到的。这些年有些公共DNS服务商支持加密查询流程上变成了客户端和本地DNS服务器之间先建立一条加密通道再走普通查询逻辑。它对用户的影响基本无感但对隐私保护有意义。3. 建立连接从TCP三次握手到TLS加密协商先对暗号再进门拿到IP地址和端口之后浏览器开始发起真正的网络连接。端口号大多数站点是443HTTPS老一些没上证书的站点可能走80HTTP。这一步的核心是TCP协议而HTTPS站点还要叠加一层TLS握手。3.1 TCP三次握手为什么非要三次能不能省一次TCP连接本质上是客户端和服务端各维护一个状态三次握手的目标是双方确认“你收到我的消息了我也收到你的消息了”。过程不复杂客户端发送SYN包带上自己的初始序列号意思是“我要建立连接”。服务端收到后回复SYNACK包意思是“我收到了你的同步请求这是我的序列号同时确认你的序列号”。客户端再回一个ACK包意思是“我收到你的确认了连接正式建立”。为什么要三次而不是两次最标准的解释是第二次ACK时客户端没法确认“服务端已经正确收到了自己第一次的请求”。如果只有两次握手可能出现一种情况——客户端发了一个旧的、延迟很久的SYN包服务端误以为是新的连接请求于是同意建立连接但实际上客户端根本不需要这个连接白白浪费了服务端资源。三次握手通过客户端的最后一个ACK让服务端确认“这个连接确实是我想要建立的没有过期”从而避免这个问题。在大多数HTTP场景里这个握手带来了实实在在的延迟成本。假设客户端和服务端之间的网络往返要80毫秒三次握手本身就占了一个半往返差不多120毫秒。这也是为什么现在HTTP连接都默认开启keep-alive一次连接处理完请求后不立即关闭后面继续请求同一个站点时复用。3.2 TLS握手验证证书、协商密钥、生成会话密钥如果是HTTPS站点TCP三次握手完成之后紧接着就是TLS四次握手。可以把TCP握手理解成“双方确认门口碰头”TLS则是“碰头之后对暗号确定只跟你一个人说话”。TLS 1.3版本的流程已经优化得相当简洁客户端发ClientHello消息里面带着支持的加密算法列表和一个随机数。服务端回复ServerHello选定加密算法同时下发自己的数字证书和密钥交换参数。客户端验证证书——这一步很关键浏览器会检查证书是否由可信根证书签发、证书域名是否匹配、证书是否过期。任何一项不通过浏览器都会给出红色警告页。客户端和服务端各自根据随机数和交换参数计算出相同的会话密钥之后所有数据都用这个密钥做对称加密传输。整个TLS握手通常需要额外一个完整往返在TLS 1.3的会话恢复场景下可以降到0个往返。所以“访问HTTPS站点比HTTP慢”这个说法在现代协议版本里已经越来越不成立性能差异主要来自协议版本和服务端配置。实际排查中我遇到过很多次“页面打开以后一直转圈看状态栏就是建立连接中”的案例最后定位成TLS握手卡死。推荐的做法是在服务端加一个握手超时限制比如10秒没完成握手直接断开避免客户端无限期等待。另外证书链不完整也是一类高频问题——服务端只发了站点证书没有附带中间证书导致浏览器链式验证失败。这类问题用在线证书检查工具或者自己下载证书链验证一下马上就能定位。3.3 连接复用与队头阻塞老协议的历史包袱HTTP/1.1时代有个著名的问题叫队头阻塞。同一个连接上请求必须是排队完成的第一个请求的响应没返回第二个请求就得等着。浏览器为了解决这个问题最大胆的做法是一个站点开6个并发连接来模拟“并行”效果。但即使这样如果一个页面有几十个资源每个连接都在排队整体耗时依然很可观。这个问题在HTTP/2里用“多路复用”做了缓解在HTTP/3里更是直接把底层传输协议换掉。后面章节会专门展开这里先提一句你看到的所有“建连优化”手段本质上都是尽量把一个页面的多次资源请求合并到有限的往返次数里。4. 请求与响应数据包如何抵达服务器又如何带着结果回来连接建立之后浏览器开始发送真正的HTTP请求。这是很多人理解的“终点”但其实只是服务器侧处理流程的“起点”。4.1 HTTP请求报文里到底装了什么浏览器按HTTP协议格式生成请求报文核心由请求行、请求头、请求体三部分组成。请求行是三个要素方法、路径、协议版本。比如“GET /index.html HTTP/1.1”意思是获取根目录下的index.html这个文件使用HTTP/1.1协议。对于现代站点路径常常不是真实文件路径而是后端路由决定的逻辑地址。请求头则是一堆键值对里面藏着很多关键信息Host告诉服务器目标是哪个域名。这是HTTP/1.1必须带的字段也是同一个IP上部署多个站点虚拟主机的判断依据。User-Agent标识浏览器类型和版本服务端经常用它做不同设备的适配。Accept、Accept-Encoding告诉服务器自己接受什么格式的返回内容、支持什么压缩算法gzip、br等。Cookie把站点以前种下的身份凭证原样带回去让服务端知道“我还是上次那个人”。Connectionkeep-alive时表示希望复用连接。请求体平时用GET时基本是空的但提交表单、上传文件或POST接口请求时就派上用场了里面装着用户表单数据、JSON字符串或者二进制文件内容。4.2 数据包到达服务器后的处理链条数据包沿着网络链路走到目标服务器往往不是直接被你写的后端代码处理。现代系统里这个链条大致是网络接口卡收到数据包拆掉以太网和IP包头把TCP数据段交给内核协议栈。内核完成TCP重组、确认数据完整之后把数据交给监听在80或443端口上的Web服务器软件。Web服务器软件先做两件事一是基于Host字段做虚拟主机匹配决定这个请求该由哪个站点处理二是根据请求路径做静态资源或动态请求的区分。如果请求的是图片、CSS、JS这类静态文件很多Web服务器能直接从磁盘或缓存里读出文件返回完全不需要进入后端应用。如果请求的是动态内容Web服务器就充当反向代理把请求转发给后端的应用服务进程。应用进程里会经历路由匹配、中间件处理、业务逻辑执行、数据库查询等一连串步骤。数据库可能还会查缓存查不到再去磁盘存储里找。这一整套下来前面网络阶段花了几十毫秒而这里经常要花几百毫秒甚至几秒。这里有个很多人调试时容易忽略的坑出现在浏览器Network面板里的耗时只是“从客户端发请求到收到响应报文的耗时”服务器内部具体卡在哪一步你是看不见的。要定位服务器侧性能瓶颈得从访问日志、应用日志、数据库慢查询日志三个地方交叉比对而不是在浏览器端瞎猜。4.3 响应状态码与响应头里的关键信号服务器处理完请求会生成HTTP响应。第一行是状态码本质含义可以按系列记忆2xx成功。200代表请求成功206代表返回了部分内容常用于断点续传和视频拖动播放。3xx重定向。301是永久重定向302是临时重定向304代表资源未被修改浏览器可以直接用缓存。4xx客户端错误。404是路径不存在403是没权限429是请求太频繁被限流。5xx服务端错误。500是应用内部异常502是网关拿到上游错误响应503是服务暂时不可用。响应头里几个字段值得重点关注。Cache-Control决定浏览器能否缓存、缓冲多久Set-Cookie用于种下会话凭证Content-Type声明返回内容的类型是HTML、JSON还是图片。这些字段直接影响页面下一次访问的速度也是做性能优化时最常调整的地方。5. 渲染阶段浏览器如何把字节流变成你眼前的一屏画面响应报文从服务器传输回来浏览器只把其中的响应体交给“渲染引擎”处理。到这里绝大多数讲“回车后发生了什么”的答案就结束了但真正的重头戏——页面渲染——其实才刚开始。5.1 从字节流到DOM树HTML解析远没你想的那么简单浏览器接收到的响应体通常是一串HTML字节流一堆尖括号包裹的文字。渲染引擎先按编码格式通常UTF-8把字节解码成字符再通过状态机把字符“Token化”识别出一个个开始标签、结束标签、属性、文本节点最后按嵌套关系构建成一棵DOM文档对象模型树。这个过程里有一点很多人理解不到位HTML解析器不是一次性把整个文档读完了再建树而是边读边解析、边建树。遇到一张不阻塞的外部图片解析器会继续往下走遇到一段没加任何异步属性的普通JavaScript脚本解析器会停下来等这个脚本下载并执行完再继续解析后面的HTML。这种行为是历史原因造成的——早期的JavaScript被设计成可以随时修改文档结构浏览器为了保证一致性只能让脚本后面的HTML先等着。后果就是脚本放头部且没有加async或defer整页渲染会被明显拖慢。这也是现在前端性能规范里都建议把脚本放底部、或者用async、defer加载的原因。CSS解析是另一条战线。浏览器并行下载CSS文件并构建CSSOMCSS对象模型树。CSSOM和DOM是两棵独立的树DOM描述页面结构CSSOM描述每个元素的样式规则。5.2 布局、绘制与合成一次排版的完整流水线两棵树都准备好之后浏览器开始做“合并”和“排版”这五个阶段连起来就是关键渲染路径DOM树构建CSSOM树构建RenderTree合并阶段只把可见的节点合到一起display:none的节点、head标签这类不可见内容会被跳过Layout布局阶段计算每个可见节点的几何位置。浏览器从左到右、从上到下遍历RenderTree根据CSS里的宽度、高度、浮动、定位等规则算出每个元素在视口里的精确坐标和尺寸。这个阶段通常也叫Reflow回流。Paint绘制阶段把每个节点的视觉信息填充成像素包括文字、颜色、边框、阴影。再往下还有合成阶段——把不同层级的绘制结果按正确顺序叠加交给显卡显示到屏幕上。现代浏览器对绘制阶段做了分层处理。某些属性比如transform、opacity会被单独提到一个图层上做合成不需要每一帧都重新走一遍布局和绘制。这就是为什么滚动页面的时候用transform做动画比改top、left要流畅得多——前者走合成的快速通道后者每一帧都要重新布局。布局阶段是页面前端性能最敏感的部分。页面复杂、DOM节点多、样式嵌套深布局耗时就会急剧上升。优化思路通常从三条线走减少DOM节点嵌套层级、避免使用昂贵的CSS选择器、对高频变化元素做图层提升。核心原则就是尽量别让浏览器从“计算位置”这一步开始重走。5.3 JavaScript执行常常是整个渲染流程里最贵的环节现代页面里JavaScript已经不再是“往页面里加一段交互”那么简单很多站点是纯JS渲染的单页应用。这意味着浏览器要先下载JS文件、再解析、再执行执行过程中创建各种对象、绑定事件、调用API最后才生成DOM结构。脚本执行最直接影响的是时间线。一个内联在head里的脚本执行期间DOM树可能还没开始构建一个放在body末尾的普通脚本等执行时DOM已经就绪代价是用户要等整个HTML解析完才看到交互效果。至于那种“下载一个巨大的JS文件、执行时把整个页面组件全部渲染出来”的架构首屏渲染往往要等脚本执行完用户的等待时间就变成了“加载时间 解析时间 执行时间”。这块优化手段也不少包括代码拆包、懒加载、按需执行、服务端渲染等。这里不展开但需要理解一个本质哪个环节发生在关键渲染路径上哪个环节就是优化重点。脚本加载如果异步化、放到首屏必要内容之后它就不挡首屏代价是首屏后几秒内交互可能还没完全生效。6. 从“能打开”到“更快”现代协议与基础设施改变了哪些环节前面几章讲的流程是经典HTTP/1.1时代的完整链路。但今天的站点很多环节已经被新协议和新架构悄悄替换掉了。面试时只答到老流程不够完整因为现实世界里的“回车后发生的事”已经是进化过的版本。6.1 HTTP/2多路复用与HTTP/3的彻底换底HTTP/2针对老协议的问题做了三件大事多路复用——一个TCP连接上可以同时并发多个请求和响应每个请求有自己的流ID彻底解决HTTP/1.1的队头阻塞问题头部压缩——用哈希表维护重复字段大幅减少请求头体积二进制分帧——把所有请求切成更小的帧传输方便灵活调度。多路复用带来的实际变化是页面里几十个资源的加载不再需要排队等连接可以在一个连接里同时传。但HTTP/2依然被一个问题束缚——TCP层面的队头阻塞。TCP协议保证包有序到达如果一个数据包丢失了后续所有包都得等着重传。哪怕你发的是不同请求的数据只要共用一条TCP连接丢了包就全都卡住。HTTP/3干脆把底层从TCP换成基于UDP的QUIC协议在用户态自己实现可靠传输、加密和多路复用。因为QUIC的每个流之间互相独立一个流丢包不影响其他流而且连接建立时把TCP握手和TLS握手合并成了更少的往返。所以这些年浏览器和服务器厂商都在大力推广HTTP/3尤其实时性要求高的场景收益最明显。日常开发里如果站点已经支持HTTP/2或HTTP/3前面说的“浏览器开6个并发连接优化”就成了无用功——新协议不需要靠开多连接来模拟并发。而且因为多路复用下所有请求共享连接强制域名拆分反而会破坏这个优势。判断站点用的什么协议打开浏览器开发者工具看响应头的访问协议即可。6.2 CDN、预连接与关键渲染路径优化把所有等待时间压缩到极致对普通用户来说整个“回车后”过程里最大的体感变化来自三个基础设施层面的优化。第一个是CDN。DNS解析阶段已经提到CDN会把用户调度到最近的边缘节点。这样静态资源不必跨越半个国家去源站拉取十几毫秒就够。一些动态内容也能通过边缘计算在离用户更近的地方完成处理。第二个是预连接。浏览器学习用户行为后会主动预判用户可能会访问某个链接那就在空闲时间提前完成DNS解析、TCP建连、TLS握手。当用户真正点击时连接已经就绪省掉了前面所有握手延迟。这也是很多站点首屏资源里都加上preconnect、preload提示的原因。第三个是Critical Rendering Path优化。服务端把首屏需要的样式内联进HTML、脚本异步化、图片懒加载、字体子集化所有手段的目标一致让浏览器用尽可能少的“往返次数”和“处理量”渲染出首屏内容。理解了整条链路之后优化不再是背技巧清单而是能准确判断“这一步到底在优化哪个环节”。这道题问到根子上实际上是在考察你对整条链路每个环节的真实理解以及能不能定位性能瓶颈出现在哪一环。我在实际调优中遇到过很多类似的场景页面白屏很久排查方向放在DNS上结果症结是TLS握手超时加载速度时快时慢各种缓存头调了一遍最后发现是CDN节点回源策略有问题。能画出整条请求链路并知道每一步的耗时边界排查这类问题才会有一个明确的坐标系而不是靠感觉东试一下西试一下。所以如果你也想系统地提升这块能力建议把排查一个慢页面的完整过程从头到尾记录一遍每一步都问一次“这里能快吗为什么没快”这个习惯远比记住某个单个协议的细节更有价值。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进