键入网址到网页显示:DNS、TCP三次握手、CDN加速全链路解析 把“键入网址到网页显示”这个问题彻底吃透基本就摸到了计算机网络这门课的门把手。它既是面试必考题也是后端和DevOps工程师排查线上问题绕不开的基本功你在地址栏敲下www.example.com回车屏幕刷新——看起来一气呵成背后其实是一条完整的链路DNS解析、TCP三次握手、HTTP请求与响应、CDN调度、浏览器渲染任何一个环节出错页面都打不开。这篇文章就是把这条链路拆开揉碎把那些“人人都能背、但不一定说得清”的细节讲透顺带把GET与POST的区别、SYN攻击的原理、CDN加速的工作方式一起串明白。无论你是在准备408考研、准备面试还是日常排查接口调不通这份笔记都值得收藏。1. 键入网址到网页显示一次HTTP请求的完整旅程1.1 敲下回车后的第一件事DNS解析讲原理之前先明确一件事网络世界能通信靠的不是域名而是IP地址。你输入的www.example.com是人类友好的名字机器不认这个机器认的是类似93.184.216.34这种地址。所以客户端的第一步必然是“把这个域名换成IP”这一过程就叫DNS解析。完整的查询顺序很多人只知道一半。客户端会先从最快的地方找浏览器DNS缓存、操作系统DNS缓存、hosts文件三个地方只要命中就直接返回不走网络。所以我平时排查DNS问题第一件事就是看本机到底有没有缓存影响。很多线上事故就是hosts文件里残留了一条测试用的旧解析记录导致线上域名被解析到错误IP症状相当迷惑同事都能访问就你们这几台机器访问不了。缓存没命中的话请求会交给本地DNS服务器也就是你在路由器或网卡上配置的那个地址。本地DNS服务器也没缓存就往上问先问根DNS服务器根服务器不直接告诉你IP它告诉你“这个域名的顶级域服务器在哪里”再问顶级域服务器它又说“权威服务器在哪里”最后问权威服务器这才拿到真正的IP。整条链路走完正常情况几十毫秒这也是为什么DNS有缓存能省下大量时间。新手常踩的坑是以为改了域名解析立刻生效。错了DNS记录有TTL缓存没过期之前老记录会一直被使用。我给客户改DNS的时候都会先问一句“解析记录是刚改的吗”如果是刚改的那就先等几分钟再排查别白忙活。1.2 TCP三次握手为什么是三次不是两次拿到IP之后浏览器开始和服务器建立TCP连接。HTTP是应用层协议但真正可靠传输依赖TCP。TCP必须先通过三次握手确认双方收发能力都正常握手连不上后面的HTTP请求根本发不出去。三次握手的报文顺序是客户端先发一个SYN报文服务器收到后回一个SYNACK客户端再回一个ACK。很多人背得出这个顺序但答不出“为什么不能只有两次握手”。关键原因是防历史重复SYN。想象一个场景客户端发了一个SYN因为网络拥塞这个请求延迟了客户端等不及又重发一个SYN服务器正常响应连接建立。如果只有两次握手之前那个延迟的SYN之后又到达服务器服务器会认为这是一个新连接于是分配资源并进入等待状态但客户端根本不打算理它这就白白浪费了服务器资源。三次握手让服务器把“确认连接是否有效”的权力交还给客户端客户端通过ACK告诉服务器“这个连接我不要了”服务器就能及时清理。同时也顺便验证了双方的收发能力一句话总结三次握手是为了可靠性和资源保护服务。顺着这个点多说一句握手成功之后TCP连接才进入数据传输阶段。HTTP/1.1默认开启Keep-Alive一个连接可以复用减少反复握手的开销。这也是为什么很多性能优化会要求你“尽量少建连接、尽量复用连接”。1.3 HTTP请求发出之后服务器怎么处理浏览器怎么渲染TCP连接建立后浏览器按HTTP协议发请求。请求报文的结构不算复杂请求行、请求头、请求体三部分。请求行里包含方法、路径和协议版本比如GET /index.html HTTP/1.1。请求头里带各种附加信息比如Host、User-Agent、Accept、Cookie。请求体一般只有POST/PUT方法才需要携带。服务器收到请求以后经过路由分发、业务逻辑处理返回状态码和响应体。状态码是最直观的信号200表示正常301/302是重定向403是没权限404是资源没找到500是服务端内部错误。拿到响应后浏览器开始逐行解析HTML遇到CSS就构建CSSOM遇到JavaScript就执行最终把页面画出来。这里要注意的是页面通常不止一个HTML文件。CSS、JS、图片、字体、音视频都是独立的资源每一个都对应一次HTTP请求。一个中等复杂度的页面有几十个资源很常见。在HTTP/1.1下浏览器会为多个资源建立多个TCP连接DNS查询、建连、传输全都要时间总耗时很容易冲到几百毫秒甚至几秒。这也是为什么后来有了HTTP/2多路复用以及为什么CDN成为刚需。浏览器渲染的过程我没有展开太细只提示一个高频考点很多人会把顺序搞反先解析HTML构建DOM树再解析CSS构建CSSOM树两者合并生成渲染树然后计算布局最后绘制到屏幕。被问到“首屏优化怎么做”核心答案其实就是减少请求数、减小资源体积、合理缓存、上CDN、关键CSS内联。这些优化点全部和前面这条链路一一对应理解了链路优化方向就不会跑偏。1.4 整条链路串一遍从回车到显示的“时间账”把整条链路按阶段拆开每一段的耗时因素和优化手段都不一样我整理成一张表方便对照阶段主要耗时因素常见优化手段DNS查询缓存命中率、TTL设置、递归查询往返时间开启DNS预解析、合理设置TTL、使用HTTPDNSTCP握手网络往返时间RTT启用Keep-Alive、升级HTTP/2、减少连接数发送请求上传带宽、请求体大小压缩请求头、精简请求参数服务器处理应用性能、数据库查询、缓存命中后端缓存、异步处理、优化慢SQL下载资源资源总体积、CDN命中率代码压缩、图片格式优化、CDN加速浏览器渲染DOM复杂度、CSS/JS阻塞懒加载、代码分割、关键CSS内联我排查慢请求的第一经验是二话不说先打开浏览器DevTools的Network面板看Waterfall时间线。哪个阶段加载时间长问题就出在哪个阶段。大多数“网页很慢”的情况下最长的柱子是TTFB也就是浏览器等待服务器返回第一个字节的时间这时候该优化的是后端和网络链路而不是盲目压缩前端资源。先定位再动手是排障的基本素养。2. GET与POST同样走HTTP为什么一个是查一个是改2.1 规范上的核心差异语义、幂等、缓存GET和POST是HTTP协议里最常用的两个方法但很多人的理解是片面的以为区别只是“参数放在URL还是放请求体”。真要较真的话规范上的差异至少有三层。第一层是语义。GET表示“获取资源”要求请求对服务器不产生副作用POST表示“提交数据给服务器处理”常用于创建资源或执行操作天然会产生副作用。直接后果就是第二个概念——幂等性。GET是幂等的同一个GET请求发一百次服务器状态不会变返回结果也相同POST不保证幂等同一个POST请求重复发可能产生两条订单、两条留言。所以支付、下单、转账这类接口绝对不能用GET用GET做写操作是设计事故。第三层是缓存。GET请求可以被浏览器、CDN、反向代理缓存POST默认不缓存。这就是为什么静态资源全部用GET而表单提交基本都用POST。后面讲CDN的时候还会回到这一点CDN的缓存规则基本只对GET生效POST请求到了CDN边缘节点会直接回源因为CDN无法判断POST是否会改变源站数据不敢贸然缓存。2.2 报文层面参数放URL还是请求体不只是“好看”的问题技术上最直观的区别确实在参数位置。GET把参数拼在URL查询串里比如GET /search?qdnspage1POST把参数放在请求体中格式可以是application/x-www-form-urlencoded、multipart/form-data现代接口更多直接用JSON。为什么不能把敏感信息放GET参数里主要三个原因一是URL有长度上限服务器和浏览器都有默认限制超长就会被截断或直接报错大数据量时GET根本装不下二是URL会出现在浏览器历史记录、服务器访问日志、反向代理日志里等于把敏感信息写进了日志三是页面引用外部资源时浏览器可能会通过Referer把当前页面的完整URL带出去GET参数跟着泄露给第三方。但这里我必须替POST说句公道话POST把数据放请求体并不等于“更安全”。如果走明文HTTP中间人照样能把请求体扒个精光。真正防偷窥的是HTTPS的加密传输而不是GET和POST的差异。我见过有人把token放在GET参数里结果Nginx的access log把完整URL打了出来再配合日志权限没设好token直接泄露。这是典型的低级事故设计API的时候该放请求体的就放请求体该上HTTPS就上HTTPS两条线都别省。2.3 实际开发里的经典错位GET干POST的活实际项目里接口设计混乱的情况很常见。典型表现是用GET实现删除操作比如GET /api/user/delete?id1。图省事觉得“反正都是调一个URL”但后果很严重浏览器预加载、爬虫、代理缓存都可能在用户不知情的情况下请求这个URL。有些爬虫会把页面里所有带参数的同源链接都爬一遍删除接口就被“爬”掉了CDN也可能把带参数的GET响应缓存下来别人再访问就返回一个缓存的“删除成功”数据状态却完全对不上。正确做法是让HTTP方法严格对应语义查询用GET创建用POST整体更新用PUT部分更新用PATCH删除用DELETE。别只认识GET和POST两个方法RESTful接口设计规范把这些都说得很清楚照着做不会错。再看POST的另一个坑浏览器刷新和返回时如果上一个页面是通过POST跳转的浏览器会弹窗提示“确认重新提交表单”。原因就是POST非幂等浏览器不敢自动重发怕造成重复提交。所以开发规范里有个经典方案叫POST-Redirect-GET表单POST提交成功以后服务器返回302重定向到一个GET页面这样刷新页面时刷的是GET请求不会重复提交表单。很多新手不知道这个套路第一次遇到“刷新弹窗”就以为是浏览器坏了。2.4 一个真实场景Docker报错里的“GET /v2/”是什么如果你玩过Docker大概率见过这种报错error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled很多人第一反应是“我明明在拉镜像为什么是GET请求”其实这里的GET是Docker客户端向Registry服务发起的API探测GET /v2/是Docker Registry HTTP API V2版本的握手请求用来确认远端Registry支持V2协议顺便完成认证准备。这正好体现了GET的语义获取服务端能力信息没有副作用连请求体都不用带。如果你看到这个请求失败基本可以判断是网络链路问题而不是协议或参数问题。再比如Harbor推送镜像失败时常见的这种报错Get https://192.168.209.133/v2/: dial tcp 192.168.209.133:443: connect: connection refused路径同样是/v2/说明Docker或Harbor客户端要先把API这个概念“握手”成功才会进行后续的认证、上传层数据等操作。这里的GET失败原因通常是HTTPS端口不通、证书不受信任或者客户端没把该Registry地址加入信任列表。把这个概念想清楚了以后再看到GET /v2/就知道该往哪个方向排查不会被“为什么是GET”这个问题绕进去。3. SYN攻击三次握手里的“半连接”漏洞3.1 半连接队列SYN攻击盯上的对象前面讲TCP三次握手服务端收到客户端的SYN后会进入SYN_RECV状态把这条连接信息放到一个叫半连接队列的地方同时回复SYNACK然后等着客户端回ACK。如果客户端一直不回这条连接就卡在半连接状态直到内核超时把它清掉。这个“等ACK”的过程就是SYN攻击盯上的窗口。SYN Flood攻击的思路简单得令人咋舌攻击者伪造大量源IP向目标服务器疯狂发送SYN报文但从来不对服务端回复的SYNACK做任何回应。服务器每收到一个SYN就要分配内存、维护状态、回复报文、把连接挂上半连接队列半连接队列很快就被塞满了。队列满了之后新的正常连接请求根本无法进入队列直接被内核丢弃。表现出来的症状很典型服务器还能ping通但网页打不开、接口全部超时因为正常的TCP连接根本三步走不完。这个攻击之所以经典是因为它利用的是TCP协议本身的机制不是某个应用软件的漏洞。只要你是面向公网开放的服务理论上就躲不开这种攻击的威胁。3.2 攻击为什么会“服务器CPU不高但服务连不上”被SYN攻击时有个迷惑性非常强的现象服务器的CPU和内存占用往往不高。很多人会因此判断“服务器没问题”然后花大量时间查应用日志、查数据库、查代码最后什么都查不到。原因其实不难理解SYN Flood的攻击流量根本没到应用层数据全被内核协议栈拦在半路应用进程压根没有活干CPU当然不高。你去查Nginx日志也查不到异常因为请求根本没完成握手没有产生HTTP请求记录访问日志里自然什么都没有。正确的排查方式是看网络连接状态而不是看应用状态。重点看系统里有没有大量SYN_RECV状态的连接netstat -ant | grep SYN_RECV | wc -l也可以查看系统整体的连接状态统计ss -s正常情况下SYN_RECV的连接数非常少几秒内就会被清掉。如果这个数值持续维持在几千甚至上万而且来源IP地址分布很散乱、很多IP一看就是伪造的那基本可以确认正在遭受SYN Flood。我第一次碰到的时候就是被“CPU不高”骗了折腾了两三个小时才发现是网络连接层面的问题这个教训值得记下来。3.3 常用防御手段SYN Cookie、队列调优、流量清洗防御SYN攻击的思路分成三层每一层解决的问题不一样实际部署的时候要配合使用。第一层是内核参数调优。最核心的手段是开启SYN Cookies机制。原理很简单服务端收到SYN之后不再立刻分配资源并放进半连接队列而是通过一个Hash算法把连接信息编码成一个Cookie放在SYNACK报文里回给客户端等客户端的ACK真的回来时服务端再根据ACK里的信息重建连接状态。这样攻击者发送的伪造SYN不会消耗服务端资源因为压根没有状态被维护。具体操作是调整这几个参数# 开启SYN CookiesLinux默认在队列溢出时自动启用 sysctl -w net.ipv4.tcp_syncookies1 # 增大半连接队列长度 sysctl -w net.ipv4.tcp_max_syn_backlog4096 # 缩短SYNACK重传次数减少半连接存活时间 sysctl -w net.ipv4.tcp_synack_retries2第二层是网络层防护。在防火墙设备上设置SYN速率限制比如单IP每秒SYN报文超过阈值就丢包如果攻击流量特别大比如把出口带宽都打满了那就只能靠云服务商的高防清洗能力把攻击流量引到清洗节点过滤之后再放行正常流量。服务器本身扛不住大流量攻击别硬撑。第三层是应用层兜底。在业务前面架负载均衡、设置连接数上限、把应用超时时间缩短。这几层叠加起来才有实际效果。我个人实践下来的结论是内网系统一般把tcp_max_syn_backlog调大、开启syncookies就够了公网服务直接接云高防把专业的事交给专业的产品这是性价比最高的选择。4. CDN让网页打开更快不只是“缓存”两个字4.1 CDN到底解决了什么问题CDN全称是内容分发网络它解决的问题非常直接。源站服务器在A地用户分散在全国甚至全球距离越远网络延迟越高而且所有用户同时打源站源站的带宽和并发连接数很容易被击穿。CDN的做法是在全国各地部署边缘节点提前把内容缓存到离用户最近的节点用户请求直接命中边缘节点根本不用回源。对用户来说是“就近访问”对源站来说是“流量卸载”。把第一部分的链路图拿过来看CDN是在本地DNS服务器拿到域名解析结果之后介入的。接入CDN之后域名解析出来的不是源站IP而是CDN服务商的一个CNAME记录。CDN的调度系统会根据用户的地理位置、所属运营商、边缘节点负载等因素返回一个最优的边缘节点IP给用户。所以判断一个网站到底有没有用CDN最简单的方法就是看域名解析结果是不是CNAME记录后面第4.3节细说。CDN的价值不止缓存和加速还有一个经常被忽略的功能是隐藏源站IP。源站IP藏在CDN后面攻击者想直接打源站就困难得多得先过CDN这一层。对很多中小站点来说这等于白得了一层基础防护。4.2 缓存机制与回源以及动态内容怎么加速CDN的核心是缓存缓存命中率决定了CDN的实际效果。命中率高大部分请求在边缘节点就返回了没命中边缘节点才跑到源站去拉数据这个动作叫回源。这里有两个层面的配置一个是源站返回的HTTP缓存头一个是CDN平台的缓存规则。静态资源天然适合缓存。图片、CSS、JS、字体这些内容基本不变建议在源站的Nginx里设置长过期时间location ~* \.(css|js|png|jpg|jpeg|gif|webp|woff2)$ { expires 30d; add_header Cache-Control public, max-age2592000; }同时配合文件指纹比如app.8f3k2d.css内容更新时文件名变化相当于生成一个新的URL旧缓存自然失效。这就是经典的“缓存一年、内容更新后URL变化”方案既能最大化命中率又不会让用户看到旧文件。动态内容就没这么简单了。比如用户登录后看到的个人订单页绝不能缓存否则用户A可能看到用户B的数据这属于安全事故。CDN对此也有应对方案叫动态加速边缘节点不缓存响应内容只负责优化链路通过智能路由选择一条最优路径回源。原理是把网络链路上的“快”发挥到极致但内容实时性完全交给源站。理解这个区别很重要。很多新手接手线上问题一看“页面好慢”就把动态接口也强制缓存了结果页面是快了数据却串了。原则很简单只缓存可公开的静态内容涉及用户维度的响应一律不缓存。4.3 如何判断一个站点是否挂了CDN、本地如何查询排查CDN相关问题我常用的命令是nslookup和dig。先看解析结果nslookup www.example.com如果返回结果里有一串CNAME比如www.example.com.cdn.cloudprovider.net那基本可以确定这个域名接了CDN。如果直接解析出来一个IP而且能查到IP归属地和网站业务所在机房一致那就是源站直连。接着看响应头。CDN服务商通常会在HTTP响应头里留下标记用curl -I就能看到curl -I https://www.example.com重点关注Via、X-Cache、Server这几个字段。比如X-Cache: HIT from cloudfront表示这一层是边缘节点缓存命中Via: 1.1 xxx-cdn表示经过CDN节点转发。另外用ping看延迟和IP归属地也能辅助判断如果网站的业务机房在华东但ping出来的IP归属地是华南而且延迟只有十几毫秒那多半是CDN边缘节点的IP。实操顺序建议是这样先用nslookup确认是否走CDN调度再用curl -I看缓存命中状态最后结合不同地区用户的访问速度对比。如果某个地区用户反馈慢就要去CDN控制台看该地区的命中率、回源统计和节点健康度基本能锁定问题出在调度还是回源上。5. 常见问题与排查技巧实录5.1 “GET /v2/ 报错”背后的通用HTTP排障思路前面提了Docker和Harbor常见的GET /v2/报错这里实际梳理一套通用的HTTP排障流程。以Docker拉镜像超时为例报错长得像这样error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection排障顺序分四步。第一步先确认网络连通性直接在你自己的机器上执行curl -I https://registry-1.docker.io/v2/如果这个命令也超时说明不是Docker的问题而是这台机器到远端Registry的网络不通继续查DNS解析、查路由、查防火墙放行规则。如果curl能通但Docker不行说明问题出在Docker守护进程的配置上重点检查镜像源配置、HTTP网络策略配置是否符合内网环境要求。第二步看DNS解析是否正常nslookup registry-1.docker.io第三步看证书是否被拦截。很多内网环境会对HTTPS流量做解密导致Docker客户端校验证书失败。第四步确认Docker daemon的配置和镜像源设置没写错改完记得重启Docker服务再试。Harbor推送失败dial tcp 192.168.209.133:443: connect: connection refused这类报错原理一样端口不通或者客户端没把Harbor地址加入信任列表。通用思路就是先通网络再查证书再看客户端配置这个顺序不能乱。5.2 SYN半连接与超时的混叠排查线上出现大量请求超时的时候除了要想到SYN Flood还要考虑半连接堆积导致的系统资源耗尽。有一个很容易混淆的点半连接数量多并不一定等于被攻击。我遇到过一种情况某个业务系统前端接的是负载均衡负载均衡和源站之间的TCP参数不匹配导致源站认为某些握手包是异常包一直不响应。结果就是连接堆积在SYN_RECV状态业务大量超时但流量本身并不大。这种情况用“攻击”来解释是错的真正要查的是链路上各设备之间的TCP参数配置。排查建议用一条命令看清楚系统连接状态的分布ss -ant | awk {print $1} | sort | uniq -c正常系统里ESTAB占绝大多数SYN_RECV几乎看不到TIME_WAIT会有一些但会逐渐消失。如果SYN_RECV持续几千上万并且降不下来再结合服务器带宽监控、来源IP分布一起判断。来源IP非常分散、没有真实流量特征是攻击的可能性大来源IP集中、带宽也正常那大概率是链路参数问题。区分清楚再动手能少走很多弯路。5.3 给DevOps和后端同学的三条实操建议第一接任何服务之前先画链路图。DNS、TCP、HTTP、缓存、CDN一层层列清楚标注每个环节的负责人和链路耗时。链路图画出来了问题自然就找到了。第二接口设计阶段就要把方法、幂等性、缓存策略写进文档。什么接口用GET、什么用POST、能不能被CDN缓存、是否需要做幂等处理这些决策不能只留在某个人的脑子里必须落到文档里不然三个月后没人说得清当初为什么这么设计。第三监控指标要落在链路上。DNS解析耗时、TCP建连耗时、TTFB时间、CDN命中率、SYN_RECV连接数这些指标要真的采集并展示出来。有了这些监控数据再遇到“网页打不开”“接口超时”的问题不需要猜直接看数据就知道问题出在哪一段。写到这里这个经典问题对我来说算是彻底讲透了。“键入网址到网页显示”之所以被反复拿来考人就是因为它把DNS、TCP、HTTP、CDN这些知识点全部串在一条线上比单独背概念有用得多。我个人最大的体会是纸上谈兵终觉浅建议你找个周末打开浏览器开发者工具随便访问一个网站盯着Network面板从头到尾看一遍再找一台服务器故意把一个正常服务的端口关掉看连接握手失败时的系统状态。亲手走一遍链路比背十遍知识点都管用。这条链路里还有更多细节可以继续展开比如HTTPS握手如何嵌套在TCP之上、HTTP/2多路复用怎么减少排队、QUIC协议为什么能替代TCP这些都是后续可以接着聊的话题。