
要弄清楚HTTP请求经历了哪些环节最直接的办法不是打开浏览器按F12看Network面板而是从你按下回车键那一刻开始沿着数据包的足迹走一遍完整链路。很多人在排查问题时会陷入为什么页面加载特别慢这类模糊的描述里本质上就是因为对整个请求生命周期缺少一个全局视图不清楚该在哪个环节取样、验证、下结论。这篇内容就以DNS域名解析为起点把HTTP请求的完整过程拆开讲透适合刚接触网络基础的后端开发、前端工程师以及所有需要排查线上网络问题、优化页面性能的人参考。1. 从URL输入开始请求发起前的必经步骤很多人以为HTTP请求是从输入网址开始的其实更准确的说法是从浏览器解析你输入的URL那一刻起请求链路已经进入了准备阶段。这个过程看似简单却藏着不少影响后续环节的细节。1.1 URL的组成部分与解析逻辑一个标准的URL通常长这样https://www.example.com:443/path/index.html?id1#result。浏览器拿到这串字符串以后第一件事不是发请求而是先把它拆成结构化数据。拆分结果包括协议部分https、域名部分www.example.com、端口号443、路径/path/index.html、查询参数id1、锚点#result。这里有个容易被忽略的点锚点#result根本不会发给服务器。它是纯客户端行为浏览器拿到之后只是用来定位页面内滚动位置。我见过有人排查问题时抓包发现请求里没有锚点误以为是参数丢失其实是锚点压根不进网络传输。协议部分决定了后续连接方式。http://默认走80端口https://默认走443端口但URL里显式写了端口号时以显式端口为准。浏览器的URL解析器还会做一件事对域名部分执行国际化域名转换中文域名会被转成punycode编码路径里的非ASCII字符会做URL编码。这一步发生在DNS解析之前。1.2 为什么域名不能直接用于建立连接URL解析得到域名之后浏览器不会拿着域名直接去建立TCP连接。原因在于底层网络通信依赖的是IP地址而不是人类可读的域名。域名本质上是一个便于记忆的别名真正在网络中定位一台主机的是类似93.184.216.34这样的IP地址。你可以做个实验在命令行执行curl https://某网站的IP地址如果该站点配置了默认证书和默认站点绑定大概率能访问成功但更常见的情况是返回证书不匹配或站点不存在。这就说明服务器接收请求时不仅依赖IP还会根据请求头里的Host字段来区分同一个IP上的不同站点。所以浏览器必须先把域名解析成IP然后在请求头中携带Host两者配合才能正确访问目标资源。当然现代浏览器对DNS解析并不总是必须等到URL解析完才动手。很多浏览器都有DNS预解析机制比如页面里出现了link reldns-prefetch标签或者浏览器预判用户可能要访问某个外链域名就会提前执行解析。这个优化在后续第六部分展开说。现阶段我们按照一条链路的主线走URL解析完毕下一步就是DNS域名解析。2. DNS域名解析的完整链路从本地缓存到根服务器接力DNS是整个请求链路里至少涉及三层以上网络设备的环节也是排查时最容易被怀疑却又最难看清楚的部分。它解决的核心问题就一句话把域名翻译成IP地址。但翻译过程不是一次查询就结束而是一场分级接力。2.1 浏览器缓存、操作系统缓存和hosts文件的优先级输入域名后真正发出的第一个DNS查询请求之前实际上已经经过了两次本地查找。查找顺序如下浏览器DNS缓存Chrome和Firefox都会在内存里保存近期解析过的域名记录有效期由响应里的TTL字段控制但浏览器还会施加自己的上限值。Chrome的缓存时间常用60秒到300秒之间。操作系统DNS缓存Windows、macOS、Linux都有系统级DNS缓存保存着本机所有应用共享的解析结果。Windows可使用ipconfig /displaydns查看macOS用sudo dscacheutil -cachedump -entries host查看。hosts文件Windows位于C:\Windows\System32\drivers\etc\hostsLinux和macOS位于/etc/hosts。文件里的静态映射优先级高于一切DNS服务器响应。这里有个实际价值当你在开发环境调试时需要把某个域名指向本地IP编辑hosts文件就可以实现本地覆盖。我经常利用这个特性来模拟生产环境的域名访问本地服务或者绕过某些通过域名做访问控制的中间层。但要注意hosts文件改完之后不会自动热生效部分应用需要重启才能真正刷新。如果以上三层缓存全部未命中系统才会把解析请求交给网络配置里设置的DNS服务器通常是运营商分配出来的本地递归DNS。2.2 递归查询与迭代查询两种查询模式的本质区别很多教科书讲到DNS时会区分递归查询和迭代查询但描述得云里雾里。用实际场景来理解会更清晰你向本地DNS服务器发起一个解析请求你希望它返回最终结果这种你把问题交给我我帮你问到答案再告诉你的方式叫递归查询。本地DNS服务器通常会替你做完所有工作。当本地DNS服务器手里的缓存没有答案时它需要去问别人。这时候它不会把压力继续转嫁给下一级而是自己扮演查询者向根DNS服务器发起询问根服务器不会直接给出最终答案而是告诉它你去找顶级域服务器这种我告诉你去哪问不替你去问的方式叫迭代查询。所以准确地说客户端与本地DNS服务器之间是递归查询本地DNS服务器与根/顶级/权威服务器之间是迭代查询。理解这个区分非常有用因为排查问题时如果抓包看到查询去了根域名服务器就知道本地缓存没命中如果根本没出本机说明命中缓存。2.3 根服务器、顶级域服务器与权威服务器各自扮演什么角色DNS全球体系呈树状结构。位于最顶端的是根域名服务器负责管理顶级域名的授权信息。全球有13个根服务器逻辑节点实际物理节点多得多它们分布在全球各地使用任播技术提供就近响应。根服务器不负责记录具体域名的IP它只告诉你.com归谁管。顶级域服务器比如管理.com、.net、.cn的服务器负责管理二级域名的授权记录。你在某注册商购买了一个example.com域名注册商就会向顶级域服务器提交NS记录把example.com的解析权委托给一组权威DNS服务器。顶级域服务器同样不存www.example.com的具体IP它只告诉你example.com的权威服务器在哪。权威服务器才是最终给出答案的地方。www.example.com的A记录、AAAA记录、CNAME记录都存放在这里。当本地DNS服务器找到权威服务器拿到A记录并缓存在本地再把结果返回给客户端整个解析过程才算完成。一个完整未命中缓存的解析流程可以简化成客户端 → 本地DNS → 根服务器 → 顶级域服务器 → 权威服务器 → 返回给客户端。实际产生的网络报文次数由于本地DNS会缓存各级结果远比你想象的少。通常情况下域名解析这一步的耗时占比非常小真正拖慢请求的是后面的TCP连接和业务处理。3. 拿到IP之后TCP三次握手与HTTP请求报文的发出DNS解析拿到IP地址客户端就具备了建立连接的条件。但如果你以为可以立刻发送HTTP数据那就忽略了传输层这道关卡。HTTP本身是应用层协议它必须依托于可靠的传输通道而这个通道由TCP提供。3.1 三次握手的实际过程与隐藏细节TCP三次握手的目标是让通信双方确认彼此的收发能力正常并同步初始序列号。握手过程中的三个报文分别是客户端发送SYN包服务器回复SYNACK包客户端再回复ACK包。这一来一回会产生1.5个RTT的时延。实际项目中三次握手的细节经常通过抓包观察。使用Wireshark或tcpdump抓包时你会在建立连接的阶段连续看到三行TCP报文标志位依次是[SYN]、[SYN, ACK]、[ACK]。需要注意的一个细节是第三次握手的ACK包可以捎带应用层数据。也就是说客户端在发送ACK确认的同时可以把HTTP请求一起发出去节省一次报文往返。在HTTP请求场景中还有一个状态容易被忽略TCP连接建立后默认是长连接。HTTP/1.1协议里默认启用Keep-Alive意味着完成一次请求响应后TCP连接不会马上断开而是保留一段时间用于后续请求复用。判断一个连接何时关闭可以通过抓包看到[FIN]或[RST]标志。RST标志通常出现在异常关闭场景比如服务器主动kill连接、端口不可达等和正常的四次挥手FIN有明显区别。3.2 HTTP请求报文结构请求行、请求头、请求体TCP通道建立好以后客户端会把HTTP请求写入TCP发送缓冲区。一个HTTP/1.1请求报文由三部分组成请求行、请求头、空行、请求体。请求行包含三个要素请求方法、请求URI、协议版本比如GET /index.html HTTP/1.1。请求头则携带大量上下文信息。常见的包括Host必须携带用于服务器做虚拟主机路由User-Agent标识客户端类型服务器常基于它做设备适配Accept声明客户端能接收的响应类型Accept-Encoding声明支持的压缩算法如gzip、brConnectionHTTP/1.1中默认keep-aliveCookie携带会话标识用于身份识别Referer标识请求来源页面请求体常见于POST、PUT等带数据的方法。GET请求一般没有请求体但不是绝对不允许。请求体的数据格式由Content-Type头决定常见有application/x-www-form-urlencoded、application/json、multipart/form-data。值得特别说明的是Content-Length。服务器通过这个字段知道请求体有多长从而判断什么时候算读完整个请求。如果同时使用了Transfer-Encoding: chunked则表示请求体以分块方式发送每块前有长度标记这种方式常用于流式上传。3.3 说好的HTTP为什么实际走的是HTTPS现在的绝大多数线上流量都走HTTPS它本质上是在HTTP和TCP之间插入了一层TLS/SSL加密隧道。HTTPS的连接过程比HTTP多一次TLS握手握手主要完成三件事协商加密算法、验证服务器证书身份、生成会话密钥。TLS握手的现代版本TLS 1.3将握手所需往返次数压缩到1个RTT客户端发送ClientHello服务器回复ServerHello和证书双方直接计算出会话密钥并开始加密传输。TLS 1.2及以下通常需要2个RTT。为什么强调证书验证因为DNS解析只解决了域名对应哪个IP的问题但没解决这个IP是不是我要找的那台服务器的问题。TLS证书由数字证书认证机构CA签名服务器在握手阶段出示证书客户端通过本地信任的CA根证书来验证其真实性。一旦验证失败浏览器会拦截请求并报证书错误这比HTTP时代被中间人劫持篡改内容更能保护用户。实际抓包时会发现TLS握手阶段的报文内容几乎都是密文除了ClientHello和ServerHello早期阶段的少量明文元数据后续内容无法直接以明文查看这给排查增加了难度所以在线排查HTTPS问题时应当同时抓取网络报文和服务器访问日志。4. 服务器处理与响应返回状态码与响应报文的关键信息请求到达服务器后实际的处理过程避不开操作系统接收、Web服务器解析、应用业务逻辑执行这几个阶段。作为客户端视角的请求链路讲解我们需要重点看请求返回时带回了什么因为响应报文包含了判断请求是否成功的所有依据。4.1 响应报文的组成与核心状态码HTTP响应报文同样由状态行、响应头、空行、响应体组成。状态行里最显眼的是三位数字状态码它分为五类1xx信息性响应常见有100 Continue、101 Switching Protocols2xx成功常见有200 OK、201 Created、204 No Content3xx重定向常见有301 Moved Permanently、302 Found、304 Not Modified4xx客户端错误常见有400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found5xx服务器错误常见有500 Internal Server Error、502 Bad Gateway、503 Service Unavailable实际排查时需要特别留意304和301。304 Not Modified表示缓存资源仍有效服务器没有返回实体内容浏览器应该使用本地缓存。它的出现意味着请求到达服务器并完成了条件验证而不是被中途拦截。301则会让浏览器永久记住重定向目标下次访问时直接跳新地址如果配置错误会造成访问异常难以纠正这也是为什么搬迁域名时很多人宁可先用302观察一段时间。响应头中的Set-Cookie用于让客户端存储会话信息Cache-Control控制缓存策略Content-Type声明响应体类型Location配合3xx使用指明跳转目标。响应体在压缩传输时无法直接看到原始文本使用curl加--compressed参数可以让命令行工具自动解压。4.2 一次完整的请求响应序列从抓包角度长什么样综合前面的内容一个没有任何缓存的完整请求序列从数据包层面看大约包含这些报文DNS查询与响应UDP 53端口TCP三次握手3个报文TLS握手如果是HTTPS约4-10个报文HTTP请求行和头的发送一个或多个TCP段HTTP响应头和体的接收一个或多个TCP段这里有个常见误解HTTP响应体很大时你会看到大量TCP报文在传输数据这是TCP分段和IP分片共同作用的结果。TCP层根据MSS最大报文段大小将数据分成多个段每个段加上TCP头成为一个报文。如果HTTP响应过大浏览器会逐步接收、重组而不是等全部收完才渲染。现代的浏览器大多支持流式解析HTML收到一部分就可以开始解析头部CSS和JavaScript会触发额外的资源请求这也是为什么Network面板里经常看到主文档请求还没完成子资源请求已经开始发起了。5. 请求链路的故障排查从现象反推断点网络问题排查的本质是根据现象找到链路中最可疑的那一环然后通过工具验证。DNS解析、TCP连接、HTTP传输、服务器响应每个环节都有标志性特征理顺排查思路比背命令更重要。5.1 排查工具与现场取证手段先介绍几个高频使用的工具curl -v查看请求和响应的详细过程包括DNS解析耗时、TCP连接耗时、TLS握手耗时nslookup发起DNS解析查询查看解析结果dig功能更强大的DNS查询工具可指定DNS服务器、查询记录类型ping判断IP是否可达但不要把ping不通和请求失败直接画等号traceroute查看数据包经过的路由路径tcpdump/Wireshark抓包分析报文细节curl -v输出的信息量很足。它会显示Trying 某IP地址...表示开始TCP连接Connected to 域名表示连接建立成功然后SSL connection using TLSv1.3表示TLS握手完成最后才是GET / HTTP/1.1发送请求。如果卡在Trying这一步说明TCP连接无法建立如果卡在DNS解析前通常curl会直接报Could not resolve host错误。5.2 DNS解析异常的三类典型状况第一类是域名能解析但结果不对。执行dig 域名如果返回的IP和你预期的不一致最常见的原因是DNS服务器缓存了旧的解析结果或者域名配置了多条记录而你访问到的节点并不是想要的那一个。处理方法是更换DNS服务器再解析一次比如使用公共DNS服务器对比。第二类是本地解析失败但别的网络能解析。这种情况常见于公司内网DNS或本地hosts出现错误映射。你可以用nslookup 域名 8.8.8.8指定一个外部公共DNS来做对比如果指定后能解析出IP说明问题出在当前使用的DNS服务器或本机缓存上。第三类是间歇性解析超时。抓包中能看到DNS请求发出后没有响应或响应超时最后客户端重传。这类问题通常是网络路径上某个设备丢弃了UDP包或DNS服务器性能不足。处理方案包括改用TCP DNS查询、更换DNS服务器、或者为关键域名做本地hosts兜底。5.3 访问一个具体站点时如何定位卡点位置假设用户反馈打开网站速度特别慢我不建议直接抓包而是先用curl -o /dev/null -w输出分阶段耗时看耗时集中在哪个阶段curl -o /dev/null -s -w DNS解析: %{time_namelookup}s\nTCP连接: %{time_connect}s\nTLS握手: %{time_appconnect}s\n总耗时: %{time_total}s\n https://某域名输出结果会明确告诉你耗时占比。DNS解析时间过高排查方向偏DNSTCP连接时间过高方向偏网络连通性、防火墙、负载均衡TLS握手过高方向偏证书链长度、服务器性能如果是总耗时高但各阶段都正常那瓶颈就在应用层需要看服务器日志和业务代码。判断浏览器侧问题也有个实用技巧对比同一网络环境下命令行和浏览器访问同站的表现。命令行正常但浏览器异常优先检查浏览器扩展、代理设置、缓存状态命令行也异常再回到系统网络层面排查。6. 从请求链路看性能优化TTL、缓存与连接复用理解了完整请求链路之后性能优化就不再是零散技巧而是对链路每个环节做定向提速。下面按请求出现的先后顺序讲几个实际有效的优化手段。6.1 DNS层的优化空间DNS解析在整体耗时中通常占比不大但对首屏渲染仍然重要尤其是页面引用大量第三方资源的场景。对DNS的优化可以从三方面入手设置合理的TTL。TTL太短会导致DNS服务器频繁回源查询增加解析耗时太长则可能导致域名迁移IP后用户长时间访问旧地址需要在变更灵活性和缓存效率之间平衡使用dns-prefetch预解析。在HTML的head区域添加需要预解析的外链域名让浏览器提前查询DNS减少用户点击外链时的等待对自有域名启用HTTPDNS。HTTPDNS绕过了传统运营商DNS通过HTTP接口直接获取IP列表可以规避DNS劫持和解析污染并支持更精细的调度策略我自己实践过的一个案例是某站点首页加载了来自四个不同域名的静态资源开启dns-prefetch之后从用户点击到首包返回的耗时减少了约80毫秒。这个收益看似不大但对弱网环境的改善很明显。6.2 TCP与HTTP层的优化TCP连接握手带来1.5个RTT的成本HTTPS额外叠加TLS握手使首次请求的握手成本更高。减轻这部分开销的主要手段包括开启HTTP/2。HTTP/2支持多路复用一个TCP连接可以并发传输多个请求极大减少连接数同时头部压缩减少传输字节服务端推送也降低了资源请求的往返次数使用TLS 1.3。相比TLS 1.2握手从2个RTT缩减为1个RTT部分场景下还能借助0-RTT恢复机制实现更快的重复连接保持长连接。服务器侧不要频繁关闭连接客户端复用连接来避免每次请求都重新握手对没有交互需求的低速连接设置超时释放防止大量空闲连接占用服务器资源6.3 资源加载与缓存策略从请求链路中减少实际请求次数往往比让单次请求更快更有效。浏览器缓存机制在这里发挥核心作用具体通过响应头里的Cache-Control和Expires来控制Cache-Control: max-age31536000适用于带指纹的静态资源比如app.7a3f9b.cssCache-Control: no-cache配合ETag可以实现在资源更新时才重新下载否则走缓存Cache-Control: no-store用于敏感数据比如支付信息页面静态资源使用CDN也是缩短链路距离的经典方案。CDN把资源缓存到边缘节点用户请求被路由到就近节点省去了跨地域网络传输的时间。使用CDN后你的请求不再直接到达源站IP而是先经过CDN的调度和缓存逻辑源站只需要处理未命中缓存的回源请求这从整体上承担了大部分流量压力。6.4 一位排障老手总结的持久化经验这一路从DNS解析讲到TCP/TLS握手再到HTTP请求与响应最后谈到优化其实围绕的核心一直没变每一次请求都是一次完整的链路协作任何一个环节掉链子用户体验就会打折。我刚入行时处理问题总喜欢盯着应用日志看后来发现很多慢根本到不了应用层问题出在DNS返回了错误的IP或者TCP握手被防火墙拦了。从那以后我习惯先用分段耗时工具把链路切出轮廓再决定去哪里抓包、看什么日志、改什么配置。最后再分享一个每晚值班时会用到的技巧写一个简单的脚本定期从多个网络位置探测核心域名解析和页面首包耗时记录并对比趋势。很多间歇性的网络问题不用等用户报障趋势图里已经能看出端倪。这个习惯帮我在好几次大故障前提前定位了节点异常算是整个链路体系中最值得投入的一项基础设施。