1. 从一次页面访问说起:先搞清楚DNS和CDN分别在忙什么
很多人对DNS的理解停留在“把www开头的域名翻译成IP地址”这一步,对CDN的理解停留在“把静态文件缓存到离用户近的服务器”这一步。这两句话没错,但远远不够。如果你手里正好有一个域名要接入CDN,或者你正在排查某个页面访问慢、部分地区打不开的问题,只停留在这种认知层面,很容易被表象绕晕。
我习惯把整个链路理解为三个层:解析层(DNS)、调度层(CDN的GSLB)、缓存执行层(边缘节点)。解析层负责回答“这个域名应该找谁”,调度层负责回答“离你最近且最合适的是谁”,缓存执行层负责回答“这个内容我这里有现成的还是得回源站拿”。三个层之间靠DNS报文、CNAME记录、HTTP头部协议串起来,任何一个环节出了问题,用户最终看到的就是打不开、白屏、加载慢,而且表象几乎完全一样。
这篇内容就沿着一条真实的用户请求路径往下走。从你在浏览器输入一个域名开始,逐步拆开DNS递归查询、报文结构、CNAME调度、GSLB选点、边缘缓存与回源这个过程,最后落到我实际维护域名时经常踩的坑。会涉及一些抓包思路和命令行验证方法,适合做站点运维、后端开发、前端工程化的人读,也适合刚接触网络基础但想把“为什么CDN要CNAME”这件事弄明白的读者。
一条重要经验先放在前面:DNS和CDN不是两个孤立系统,它们的耦合点就是CNAME记录和ECS(EDNS Client Subnet)扩展。理解了这一点,后面很多排查方向都会清晰得多。
1.1 输入网址之后的第一件小事
从用户视角看,输入一个网址然后回车,似乎是瞬间发生的。但在底层,浏览器首先要把网址解析成“我要访问哪台服务器”。这个解析不是一步到位,而是一条从近到远、逐级询问的链路。
第一级是浏览器自身的DNS缓存。浏览器为了减少重复解析,会在内存或磁盘里保存最近解析过的域名结果,缓存时间受TTL控制。第二级是操作系统层面的缓存,Chrome这类浏览器如果没命中自己的缓存,会调用操作系统的解析接口,系统会先查hosts文件,再查看本地DNS解析器缓存。很多人忽略hosts文件,但它在日常调试里用途极大:你想绕过线上解析强制访问某个IP时,改hosts往往是最快的手段。
第三级才轮到真正的外部DNS服务器。系统配置的DNS服务器地址通常是本地网关下发的,或者是一个公共DNS。这个服务器在DNS术语里叫“递归解析器”,本地DNS拿到请求后,会代替客户端去完成剩下所有查询工作。绝大多数用户不会感知到这个分层过程,但运维人员必须清楚:解析结果的来源不同,排查的抓手就不同。用户说“我这边解析出来是旧IP”,你首先要问他用的是哪个DNS、有没有走代理、hosts有没有被改过,而不是直接去动权威解析记录。
1.2 两套系统解决的是完全不同的问题
DNS的核心价值是建立一个“分布式电话簿”。它解决的是“域名到IP”的映射关系,让使用者不需要记忆一串数字。但电话簿本身不管“你离这个号码远不远”,也不管“这个号码现在忙不忙”。如果把互联网比作一个大城市,DNS只负责告诉你某某大厦在哪条街,至于你从当前位置开车过去要多久、那条路堵不堵,它一概不管。
CDN就是来做这部分的。CDN的全称是内容分发网络,核心思路是把源站内容复制到遍布各地的边缘节点,再通过某种调度手段让用户从离自己最近的节点拿数据。为什么近就快?因为网络延迟受物理距离影响,光在光纤里跑一圈,每1000公里大约增加5毫秒延迟,跨省多跳路由还会额外增加网络设备处理和排队时间。用户和源站隔着一个大洲,延迟就是数百毫秒;和本地边缘节点通信,延迟往往是几毫秒到几十毫秒。
所以DNS和CDN的关系很明确:DNS负责在合理范围内找到目标,CDN负责在找到目标后给你最优的目标。而CDN要完成“给你最优目标”这件事,必须抢在DNS返回A记录之前参与决策,最优雅的方式就是让CDN接管解析权。
2. DNS解析链路底层拆解:从本地缓存到根服务器再到权威记录
要理解CDN如何“接管”解析,必须先真正搞懂一次普通DNS查询经历了什么。这里用一次未命中任何缓存的查询来拆解,假设我要访问的是www.example.com。
2.1 递归查询与迭代查询:一条链路怎么走的
从用户设备发往本地递归解析器的请求叫递归查询——你只需要给我最终答案,中间过程我不管。递归解析器收到后,如果它的缓存里没有,就需要替用户完成一组迭代查询。
迭代查询的第一步是找根服务器。全球有13个根服务器逻辑节点,它们的IP地址固化在解析器配置里,不通过DNS解析获得。递归解析器向根服务器问:“www.example.com的A记录在哪?”根服务器当然不知道这么具体的记录,但它会回答:“example.com这个域名段由.com顶级域的某组服务器负责,你去问它们。”同时返回对应NS记录和粘合地址。
第二步递归解析器转去问.com顶级域服务器,问题变成:“example.com的权威服务器是谁?”顶级域服务器查自己的数据库,返回example.com的NS记录,以及NS服务器对应的IP。第三步,递归解析器再向example.com的权威服务器问:“www.example.com的A记录是什么?”这次权威服务器能给出最终答案,因为它是这个域名段的最终权力来源。
整个链路像是客服层层转接。注意每一层都只告诉你“下一个该找谁”,只有最后一层给你真正的A记录或CNAME记录。如果中间任何一层没有响应,整体查询直接失败。所以排查DNS故障时,第一个要区分的就是问题出在递归端还是权威端。
2.2 DNS报文细节:除了IP地址,解析器还在交换什么
DNS查询看起来只是“问一个名字返回一个IP”,实际跑在UDP 53端口上。报文格式分为头部、问题部分、回答部分、权威部分和附加部分。
头部最关键的是16位的查询ID、标志位和三个计数。查询ID用于匹配请求和响应,客户端发出一个ID为123的查询,接收到的响应ID也必须为123,否则直接丢弃。标志位里QR表示查询还是响应,Opcode表示是标准查询还是通知等,AA表示响应是否来自权威服务器,TC表示响应是否因太长被截断,RD和RA分别表示递归期望和递归可用。
问题部分包含QNAME(即查询的域名)、QTYPE(比如A记录就是类型1)、QCLASS(通常为IN表示互联网)。回答部分则是一条或多条资源记录,每条记录包含域名、类型、TTL、数据长度和具体数据。发送方会用“域名压缩”手段把重复出现的域名部分用指针替代,这是报文里面容易忽略的优化。
递归解析器默认用UDP发送查询,但UDP报文有大小限制。早期响应超过512字节就会被截断,标志位TC置1,客户端只能重新用TCP查询。后来增加了EDNS0扩展,通过额外OPT记录协商更大的UDP报文大小,这才支撑起DNSSEC这类大响应。对于CDN调度还有另一个关键扩展叫ECS,就是EDNS Client Subnet——客户端子网。它允许递归解析器在转发查询时带上用户的IP段信息,CDN调度系统就能根据真实用户位置选点,而不是根据递归解析器的位置选点。
日常排查里,如果你发现某个公共DNS总是把用户调度到离得远的地方,八成是ECS没有正确传递,或者是递归解析器对EDNS支持不好。
2.3 TTL与多级缓存的连锁影响
每条DNS资源记录都带有TTL,单位是秒。TTL的意思是“这条记录可以被缓存多久”。比如权威服务器上A记录的TTL设置为300秒,本地递归解析器把它缓存5分钟,5分钟内的同一域名查询直接命中缓存,不回源。
但TTL只是“建议值”,不是强制值。客户端和递归解析器可以选择提前过期,也可能在超时后才去重新查询。浏览器层面的DNS缓存往往有自己的上限,比如超过几十秒的TTL会被压缩到固定时长,操作系统也有自己的缓存策略。这就导致了一个常见现象:你修改了权威解析,但用户侧要过很久才能感知。
具体多久?不是看新TTL,而是看旧的残留TTL。假设某个域名原本TTL是3600秒,权威服务器上已经改了新IP,但用户本地的递归解析器是在10分钟前解析的,它还要等待剩下50分钟缓存过期才愿意发起新一轮查询。所以专业做法是:计划做解析切换时,提前几天把TTL调低到60秒,切换完成后再调回正常值。这也解释了为什么接入CDN时服务商会要求你设置比较短的CNAME记录TTL——调度需要灵活变更节点。
3. CDN调度机制:CNAME、GSLB与边缘节点如何决定你连哪台服务器
普通域名解析最终返回源站IP,一个域名对应一个IP或者一组VIP。接入CDN后完全变样,因为你需要让不同位置的用户拿到不同的边缘节点IP。一个IP满足不了这种需求,所以CDN必须介入解析过程。
3.1 CNAME把业务域名挂到调度系统上的关键一步
大多数CDN接入流程是这样:你在CDN控制台添加一个加速域名,比如用cdn.example.com给静态资源做加速,CDN会分配给你一个调度域名,类似cdn.example.com.cdn-service.global。然后你在自己的权威DNS上把cdn.example.com设置成一条CNAME记录,指向这个调度域名。
为什么要用CNAME而不是直接把权威解析交给CDN?因为CNAME允许域名解析权仍然掌握在你自己手里,你只需要在合适的记录类型里做一个“别名跳转”。用户设备查询cdn.example.com时,权威DNS返回的不是具体IP,而是一串指向CDN调度系统的名字。递归解析器拿到这个结果后,会继续去解析CNAME指向的目标域名,这时才真正进入CDN的领地。
CNAME方案的好处是接入和退出都很干净,你随时可以删掉CNAME改回源站A记录。隐患是CNAME会带来额外的解析跳数,每增加一次CNAME解析就增加一次迭代消耗。不少CDN服务商会用两层CNAME:第一层是你指向调度域名的CNAME,第二层是调度域名内部再根据地域解析成不同边缘节点的域名。多一层跳转会增加一点延迟,但因为解析结果的TTL通常很短,实际影响会被缓存掩盖。
另外一个必须知道的点:CNAME记录不能和同名的其他记录共存,比如你不能让cdn.example.com同时存在CNAME和A记录。严格来说,CNAME指向的目标域名才能承载A记录。这意味着你的业务域名如果是“域名根”(example.com本身)想接CDN,没办法直接配CNAME,得换用NS委托方案或者用CDN厂商的DNS托管。这是个非常经典的接入限制。
3.2 全局调度策略怎么选点:就近、负载与用户体验的平衡
CDN调度系统通常叫GSLB,全局负载均衡。它不是简单的“离你近就给你”,而是一个综合判断过程。
最常见的地域表调度:把全球IP库按国家和区域划分,结合递归解析器的来源IP判断用户大概在哪个城市,再返回距离最近的边缘节点IP。问题在于IP地理位置数据库精度有限,运营商NAT、公司出口等场景会让IP定位完全失真。所以现在主流调度都会依赖ECS,让递归解析器把用户真实子网传上来,定位粒度从“美国某州”细化到“某个城市某个运营商”。
光靠距离还不够。边缘节点也有负载能力、带宽成本和故障状态。一个健康的调度系统会周期性下发节点探活结果,把故障节点在调度表里摘除;同时对每个节点设置权重,避免所有用户都涌入同一个最优节点造成过载。有些CDN还会做实时网络质量探测,比较各个节点到用户网络的RTT和丢包率,用探测结果修正静态地理表。这已经很像一个实时路由系统了。
对我做运维的体会:调度算法再智能,也不可能覆盖所有情况。用户拿到的IP不满意,可能是ECS没生效、可能是某运营商和CDN节点的互联链路抽风、也可能只是调度策略为了全局负载而牺牲了个别区域的绝对最优。所以配置CDN后一定要做灰度验证,至少用不同省份、不同运营商的环境各测一遍节点归属。
3.3 边缘节点缓存与回源逻辑:命中还是回源,在哪些情况下发生
调度系统把用户引到了某个边缘节点,但这只是第一步。边缘节点收到HTTP请求后,要判断自己有没有资格直接返回内容。
判断依据是缓存键,通常由“请求方法 + 域名 + URI”组合而成。URL里的查询参数会不会进入缓存键,服务商策略不同。默认情况下,同一个文件的不同参数版本会被当成多个缓存对象;如果业务方对每个请求都加一个随机参数做防缓存,那缓存命中率会直接崩塌。
缓存还有有效期。边缘节点根据源站返回的HTTP头部,比如Cache-Control: max-age=3600决定这个对象可以在节点上缓存多久。没有Cache-Control或者响应中带有Set-Cookie、私有标记时,默认大概率不缓存。这里有个容易踩的坑:源站已经返回了缓存头,但CDN边缘还有一层“刷新”逻辑,你改了源站文件却不主动刷新CDN缓存,用户看到的一直是旧内容。需要在发布流程里把刷新缓存作为一个固定步骤。
如果边缘节点缓存未命中,它会向源站发起回源请求。回源不是直接用用户当前的URL改写,而是走一套独立的回源配置:回源地址可能是源站域名、源站IP或者源站负载均衡器;回源HTTP请求里的Host头默认保持原始域名,这样源站Web服务器才能正确匹配虚拟主机;跨HTTPS回源时还有SNI字段要处理。回源成功后边缘节点把内容缓存一份,再返回给用户。如果回源失败,节点会尝试下一个可用源站地址,或者把错误状态返回给用户。
4. 全链路串讲:从一个完整请求出发,把前面拆开的环节重新接上
前面几章分别拆开了DNS和CDN,现在把它们拼回去,走一遍完整链路。我们以一个具体场景为例:用户在北京打开浏览器,输入https://static.example.com/app.js,这个域名已经接入了CDN。
4.1 浏览器发起请求时的第一个动作
浏览器先检查自身DNS缓存里有没有static.example.com的IP。如果没有,往操作系统要解析结果。操作系统同样检查本地缓存和hosts文件,全部未命中后,把查询请求发给系统配置的递归解析器。
这里有一个影响后续所有行为的细节:浏览器需要知道目标服务器的IP才能建立TCP连接,所以在HTTP请求真正发出之前,一定会先完成一次DNS查询,无论这个域名之前是否被解析过。如果是首次访问,页面里的HTML、CSS、JS、图片全都分布在不同域名下,每个域名都要独立做一次解析。域名数量越多,DNS解析总耗时越可能成为首屏瓶颈。这也是很多前端优化指南建议“减少域名数量”的原因之一。
解析完成后,浏览器拿到一个边缘节点IP。然后开始TCP三次握手,如果是HTTPS还要再做TLS握手。TLS握手时需要用到域名,因为证书验证和SNI扩展都依赖域名。如果CDN边缘节点收到的SNI和证书域名不匹配,就会报证书错误。所以CNAME之后“域名”这个概念并没有消失,只是从解析目标变成了TLS握手和Host头的关键字段。
4.2 网络中的DNS查询与CDN调度交互
假设递归解析器缓存里什么都没有,它会经历前文说的三层迭代。拿到static.example.com的权威服务器地址后,再去询问权威服务器。这一次权威服务器返回的不是A记录,而是一条CNAME,指向CDN的调度域名,比如static.example.com.cdn-service.global。
递归解析器收到CNAME后不会直接结束,而是继续解析这个新的调度域名。它会再走一次迭代链路,这次最终来到CDN自建的GSLB权威集群。GSLB根据查询来源IP、ECS信息和实时调度策略,决定返回哪个边缘节点的IP。整个过程对用户来说毫无感知,但对解析器而言,一次完整的static.example.com解析可能触发了6到10次迭代询问。
CDN之所以要把自己的GSLB作为权威解析终点,是因为这样才能做到“不同用户不同答案”。同样的域名,在北京用户解析出来可能是A机房的IP,在广东用户解析出来就是B机房的IP,这完全合法,递归解析器也不会有意见。这其实就是DNS天然支持的地理负载均衡能力。
解析完成后,递归解析器会把CNAME和最终A记录一起缓存下来。CDN调度域名的TTL通常很短,甚至短到几十秒,这样一旦边缘节点变更,递归解析器很快就能拿到新IP。但注意:这条链路上的CDN调度TTL只影响“CDN调度域名→边缘节点”这一小段,业务域名本身的TTL由你自己的权威DNS控制。
4.3 边缘节点返回内容,以及后续并发请求的加速效应
浏览器和边缘节点完成TCP与TLS握手后,发出真正的HTTP请求。边缘节点根据缓存键查找本地缓存,如果命中,直接返回200和文件内容,整个请求结束,源站没有任何感知。
如果未命中,边缘节点立刻发起回源。回源请求从边缘节点到源站,走的是数据中心之间的高带宽线路,通常比用户直接访问源站质量更好。源站返回内容后,边缘节点按规则缓存一份,再回复给用户。用户看到的响应时间和未命中时回源时间成正比;如果源站本身很慢,那么CDN的首次访问同样会慢,只是后续访问会被缓存优势覆盖。
一个页面通常包含数十个资源。浏览器在拿到HTML后,会并发加载CSS、JS、图片。只要这些资源的域名解析到同一组边缘IP,浏览器就会复用同一个TCP连接或使用HTTP/2多路复用,在一条连接上同时请求多个文件。边缘节点上这些文件大概率都已命中缓存,所以整体加载速度会非常快。
这也是为什么CDN不只是“静态文件加速”:它通过边缘节点的缓存把用户请求拦截在离用户最近的地方,从而大幅降低源站压力和跨地域网络延迟。如果某个请求是动态接口、不能被缓存,CDN也可以做动态加速,通过专用的回源优化链路线路降低端到端时延,但这是另外一个话题了。
5. 实际运维里最常见的坑:验证手段和排查链路
理论再多,最后都要靠命令行来验证。我自己的排查习惯是“先看解析,再看节点,最后看缓存行为”,顺序不能乱。如果你从缓存行为开始查,很可能在错误的方向上折腾半天。
5.1 用dig和curl把链路拆开看
最常用的工具是dig。查A记录:
dig static.example.com A +noall +answer这条命令能直接告诉你当前递归解析器返回的IP和TTL。如果返回的是CDN的节点IP,说明解析链路已经走到了GSLB;如果返回的是源站IP,说明CNAME可能还没生效,或者你在用没接CDN的域名测试。
查CNAME链路:
dig static.example.com CNAME +noall +answer dig +trace static.example.com+trace会从根服务器逐级打到权威服务器,能看到整个迭代过程。使用它能准确判断问题出在顶级域、权威DNS还是递归缓存。如果线上解析始终不对,但你用+trace查询的答案是新的,那就是递归缓存未过期,只能等TTL归零或用特定解析器反向验证。
验证节点归属时,可以解析出IP后用whois或者IP归属查询工具看看运营商和区域。进一步测HTTP层,用curl指定解析IP:
curl -I https://static.example.com/app.js --resolve static.example.com:443:1.2.3.4--resolve的意思是强制让域名解析到指定IP,绕过当前DNS。这样你可以直接测试某个边缘节点是否正常。响应头里的x-cache一类字段会显示HIT或MISS,据此判断该节点上缓存是否存在。
5.2 几个典型故障的定位路径
我整理了几个最容易遇到的问题。它们表面症状都一样,但根因完全不同。
| 现象 | 可能原因 | 排查重点 |
|---|---|---|
| 部分地区访问还是旧IP | 解析缓存TTL未过期;ECS没有生效 | 用+trace看权威返回,用ECS环境测试调度结果 |
| 添加CNAME后解析失败 | CNAME与同名的其他记录冲突 | 检查权威DNS是否有同名A记录、MX冲突 |
| 页面返回旧内容 | CDN边缘缓存未刷新 | 看响应头缓存状态,主动调用刷新接口 |
| 首次访问很慢,后续变快 | 边缘节点未命中,回源链路慢 | 检查源站响应时间、回源线路状态 |
| HTTPS证书报错 | SNI域名和边缘证书不匹配;证书没绑定到加速域名 | 抓包看TLS ClientHello里的SNI字段 |
这里有一个通用排查法:永远先把DNS和HTTP拆开测。DNS层面只问“解析到了谁”,HTTP层面只问“请求到了那个IP后发生了什么”。很多问题看起来是DNS不生效,实际是HTTP层节点返回了错误内容;也有看起来是节点故障,实际是CNAME没配置对导致请求根本没到CDN。
5.3 接入和接管CDN的几点实操心得
最后分享几个我在实际项目里总结的经验,算不上多高深,但确实能帮后来人少走弯路。
第一,接入CDN前先把源站压测做扎实。CDN边缘节点回源时,瞬间流量可能远高于平时。如果源站扛不住,CDN缓存未命中的那部分请求会直接把源站打挂。我见过不止一次业务方接CDN后发现源站CPU飙升,不是CDN有问题,是源站能力本身就不足。
第二,CNAME和回源Host是两回事。CNAME让用户流量到达CDN,回源Host决定CDN去源站时请求哪个虚拟主机。很多新手只配了CNAME,源站上开着多个站点,结果回源时因为Host不对返回404或者错误站点的内容。配置完成后一定要用curl的-H "Host: 原域名"模拟回源请求测试。
第三,TTL是排障里最容易被忽略的变量。线上环境出问题时,你想立刻修改DNS指向,却发现因为TTL设置太长,用户要等几十分钟甚至几小时才能感知。建议所有线上业务域名默认保持一个较短的TTL,比如300秒,既不影响性能,又能给紧急切换留出余地。真正需要低延迟查询的核心域名,再把TTL适当调高。
第四,刷新缓存要纳入发布流程。缓存刷新不只是“我改了源站文件就能生效”,必须在部署脚本里增加CDN刷新接口调用,并且刷新后要抽样验证缓存状态确实变成了MISS或已更新。否则等用户反馈你看不到旧内容时,往往已经影响了一批访问。
整套链路跑下来,你会发现DNS和CDN之间的CNAME链是整个体系的核心衔接点。掌握了递归查询、配置规范和报文细节,你不仅能排查绝大多数访问问题,还能在设计高可用架构时更清楚地判断解析层和分发层各自应该承担什么样的职责。